Starlight

This may be a strange idea but, I'm going to attempt to start a, um, devthread(?) for Starlight here. I figure this may be a slightly better spot for sharing vs Slack where I am possibly sending push notifications to everyone.

Latest: starlight.zip (108.8 KB)

4 Likes

starlight-navigator

Attempting to recreate a version of @davemakes navigator suggestion. This is clearly far nicer than the minimap control I made (so much easier to read, more time looking at the game, and we gain screen space). Thank you for the input!

There is still so much to figure out with this game. Well, at least flying around space is kinda fun. :playdate_goofy:

6 Likes

oh yeah, that's lovely!

Oh I really love the idea of devthreads or devlog. Having a category dedicated to it in the forum, like tigsource, would be awesome and would encourage people to share progress.

(I love the look of the gauge btw)

2 Likes

starlight-computer

So, I started working on selection this evening. This allows you to select planets (ships and other objects later). Making use of the oddly specific but very cool VHS pause image effect included in the SDK: image:vcrPauseFilterImage() to give the popup a feel of an old computer display. I may chase that aesthetic a bit—retro future perhaps add some scanlines.

The selection is distance based but of course this is a bit weird as you move around while changing the selection. I think I need to scan based on distance when you start cycling through objects only to reset once you deselect (reach the end of the list).

Also notice that the corresponding planet indicator at the edge of the screen highlights so you know which direction the planet you have selected is in.

Alright, that's all for now. :playdate_heart:

3 Likes

I'd nearly forgotten how fun it is to learn new things while programming and in the case of Starlight, it is vector math I find myself enjoying.

Last night I learned how to work backwards from a velocity (max velocity) and scale another velocity to match. I'm sure many of the folks on this forum are rolling their eyes right now. :playdate_upside_goofy:

The trick with moving around in space (or at least Starlight's version of space) is that your vehicle doesn't necessarily move in the direction you're facing. You apply thrust which accelerates you in the direction you're pointing. So you can apply a small amount of thrust in a direction off of the ship's actual direction and end up in a direction between the two. Originally I was just maxing the x and y of the ship's velocity, however, this meant that when you were traveling at an angle (both x and y were non-zero) you could actually travel faster than the max velocity.

Limiting velocity in space doesn't make a whole lot of sense I suppose but I want different ships to feel different and max velocity is one of the dials I can turn along with acceleration and rotation speed.

So here's the code. The first two lines, I'm applying acceleration over time (dt is time that has passed since last frame) and the bit of math there is getting horizontal (cosine) and vertical (sine) components of the current rotation angle of the ship. Secondly, I ask the ship's velocity for its magnitude (it's length from 0,0) which is the distance the ship is actually moving each frame, or, it's true velocity. And lastly, I compare the distance we intend to move against the distance we're not allowed to exceed and, if we've exceeded, we scale the vector back by the difference.

self.ship.velocity.x += math.cos(self.ship.rotation) * (self.ship.acceleration * dt)
self.ship.velocity.y += math.sin(self.ship.rotation) * (self.ship.acceleration * dt)			
local velocity_magnitude <const> = self.ship.velocity:magnitude()
if velocity_magnitude > self.ship.max_velocity then
	self.ship.velocity:scale(self.ship.max_velocity / velocity_magnitude)
end

That's it. Easy peasy. And here I was worried I wasn't going to be able to figure this out—though please let me know if there's a better way or if I'm mistaken!

Next up: implementing hyperspace. :playdate_new:

3 Likes

This evening I added a prototype HUD which displays bits of important information while in game: fuel, armor, targets, etc. Also, a Computer that you'll pull up which will allow you to select items/weapons, view current mission and cargo details, choose a system to jump to, etc. All very prototype-y but I'm digging it nonetheless.

starlight-computer-new

4 Likes

I've been looking for a cleaner way to do cutscenes and control NPCs. Folks/the Internet seem to be pointing me towards Behavior Trees. So that's what I have been exploring these past few days in 5 minute bursts while taking care of a 3yo. :playdate_cry_laugh:

Through reading and looking over various libraries I decided to build my own implementation that required less boilerplate code for each action and, well, I just like having implementation knowledge and control over the libraries I use. My first use of this library is for the cutscene/animation when jumping from system to system in Starlight. It seemed like a mess to keep track of in just one function and I didn't want my System screen to have to manage the state of this cutscene.

A rough pass last night has me pretty excited:

starlight_warp

And it appears to run fine on device. Though I am worried about the overhead of calling through the tree as I start to use this mechanism for NPCs in the same system.

If anyone has any recommendations for better or just different approaches, please let me know.

Here's the definition of this cutscene:

	self.hyperspace_cutscene = behave.Tree(self, 
		behave.Sequence({
			hyperspaceReverseShipRotation,
			hyperspaceStopShip,
			hyperspaceRotateShipToSelectedSector,
			hyperspaceWarp,
			behave.Parallel({
				hyperspaceFlashScreen,
				behave.Sequence({
					function() self:loadSystem(MONTA_SYSTEM); return behave.SUCCESS end,
					hyperspaceCoast,
				})
			})
		})
	)

Again, I wanted this to not be too prescriptive, so, you can see how I have an inline function mixed in with functions defined elsewhere. The beauty of this approach is that the run this cutscene I simply start calling the update(dt) on the hyperspace_cutscene object until it stops returning a RUNNING status. However, it's still not entirely clear to me where I should be running this from. Perhaps I should have more logic in my sprites, like this, as this is really a mode of the player sprite (it's speed, rotation, etc. though swapping out the system happens at the System screen level, so — I don't know!).

Okay, that's it. Now I need to polish this cutscene up. Perhaps it should say "Warp engaged" because what is "Hyperspace" anyway? Oh, and if anyone would like this little behave library to play with, let me know. I'll most likely share it on the forum here anyway because I can't help myself.

:playdate_heart:

6 Likes

This all looks great! And your code has continued to be helpful and inspiring for me; I look at this behavior tree and think, "That looks great! I have no idea how it would work, but I like it!" I've been chaining together some cutscene-style animations with timers that create other timers, and it's kind of a nightmare.

Anyway, the game looks cool, especially seeing the warp in motion. And the UI looks great, too!

I should start a devlog and actually show some stuff that we've been working on over here...!

1 Like

Thanks! I'm still figuring out how to use this technique. From what I can tell you're really supposed to define all logic for a sprite/character through this tree but baby steps. I'm looking into adding easing to this so I could ease any function by scaling the time deltas somehow. So far I'm using it as a way to create a complex animation that is contained, easy to change, and reads well.

I'll share the library once I've used it for a bit.

Also, please do start a devlog! I know very few people are reading this currently so I know this must look a bit silly but I find it nice to reflect on the work and I should be able to open this thread up the SDK is widely available.

2 Likes

Also I'm still thinking through how I should communicate between sprites. Should I use events? Should sprites know about one another? :playdate_sick:

I do worry that I am over-engineering this before really trying to implement it more simply. :playdate_agh:

1 Like

Spent some time today wrapping my head around another approach to cutscenes using coroutines based on this blog post by Elias Daler: Redirecting to https://edw.is/

It's a pretty slick approach. The main problem I have with it is that I it doesn't feel as linear (easy to think through) as behavior trees. Perhaps in time it'll be second nature but, for now, it's a real head-tilter thinking through the code path of each call to playdate.update().

With that said, here is the same cutscene but implemented with this new library built on this new coroutine approach.

function warp.cutscene(dt)
	local ship <const> = game_state.ship
	
	if game_state.current_system == DATA_SYSTEMS.monto then
		current_scene.hud:setInspector("Warp Engaged", "Sena System")
	else
		current_scene.hud:setInspector("Warp Engaged", "Monto System")
	end
	
	-- Reverse direction.
	WarpReverseShipDirectionAction(ship)
	
	-- Stop the ship.
	WarpStopShipAction(ship)
	
	-- Rotate towards selected system.
	WarpRotateShip(45, ship)
	
	-- Accelerate to warp speed.
	WarpSpeedAction(40, ship)
	
	-- First warp flash.
	FlashScreen(0.1)
	
	-- Pause before next warp flash.
	kubrick.Wait(0.1);

	-- Flash, load new system, and coast.
	kubrick.Together(
		function() FlashScreen(0.05) end,
		function()
			utils.setVectorMagnitude(ship.velocity, 2)
			if game_state.current_system == DATA_SYSTEMS.monto then
				current_scene:loadSystem(DATA_SYSTEMS.sana);
			else
				current_scene:loadSystem(DATA_SYSTEMS.monto);
			end
			current_scene.hud:clearInspector()
		end
	)
	
	-- Pause before displaying system information.
	kubrick.Wait(0.1);
	
	-- Display system information.
	current_scene.hud:setInspector(game_state.current_system.name .. " System", "Federation")
end

To run this code I first wrap the function in a coroutine which I've front-ended by under the kubrick namespace:

self.warp_cutscene = kubrick.create(warp.cutscene)

Then on each call to playdate.update I simply check if I should be running my cutscene and if so, update it, then check if it's finished and move on:

if game_state.hyperspace_active then
	kubrick.update(self.warp_cutscene, dt)
	if kubrick.finished(self.warp_cutscene) then
		game_state.hyperspace_active = false
	end
end

That's it.

Each of the actions in the cutscene is actually an object that runs from its initializer. I know, yikes, but it's much cleaner than creating the object and then needing to call a method to start the action running. Plus, it makes these actions look more like simple functions.

An implementation of an action is quite simple:

class("WarpSpeedAction").extends(kubrick.Action)
function WarpSpeedAction:update(dt, acceleration, ship)
	local a <const> = (acceleration * dt)
	ship.velocity.x += math.cos(ship.rotation) * a
	ship.velocity.y += math.sin(ship.rotation) * a
	local velocity_magnitude <const> = ship.velocity:magnitude()
	if velocity_magnitude >= 100 then
		self.finished = true
	end
end

Each action is assumed to be a process that requires multiple frames. So, each action has an update function (that is typically all you override), perform the logic you need to perform on each frame until complete. Once complete, set Action.finished to true and the coroutine behind the scenes will die and this update function will no longer be called.

So, I like the look of this vs the behavior tree definition from earlier. It still breaks my brain a bit when debugging, but I suspect I'll come around. I'm going to stick with this implementation for now and move on. I named the library Kubrick because, I don't know, cutscenes are like films or something? :playdate_smirk:

If anyone is interested in this library I expect I'll post it soon. It's surprisingly simple (60 lines) which is another thing to like about this approach.

2 Likes

This also looks really cool! I'm getting into some more cutscene-style stuff now for transitions between scenes and for more elaborate actions. I'm reading the linked essay now and would always like to see your implementation :playdate_happy:

1 Like

I'll try to package it up this weekend and share it.

2 Likes

Just following up here, over the weekend I tried to use a pre-made behavior tree lib, and then to write my own Action Sequence lib based on the article you linked, and I just don't know enough about this kind of code to make them work reliably. :confused: Right now to make sequences of things happen, I'm just chaining together playdate timers and using their timerEndedCallbacks to start the next action. This makes for tedious and brittle code! No pressure to share your code, I just wanted to say this stuff is out of my depth and it's been very helpful to see how you and others write code that might not feel like a big deal to y'all. :+1:

I'll share tonight promise! :slight_smile:

Apologies for the lack of a quality example but hopefully the quick example in that post begins to outline how you might use this. One way to think of it is that if you have a script you need to run over a period of time while the rest of your game loop continues to execute, this is a decent solution. It has worked well for me so far. I'm not even sure how I would implement my warp jump animation otherwise while other ships are flying around and planets/stars move as they do. The other library I put together was a behavior tree library but it felt heavy handed.

1 Like

I just wanted to say @dustin, this has been an incredible well of knowledge you've shared here. Most of it goes over my head right now but your examples are awesome and I look forward to getting up to speed with such brilliant developers like you. I can't wait to see where Starlight goes!

1 Like

I'm enjoying writing about my, um, game dev journey here. I hope others will too! There is some incredible talent in this forum with far more experience than I have. So much to learn! :playdate_proud: