Best practices for having different update() loop code for different "scenes"?

I'm thinking of situations where one "level"/"scene"/context is fundamentally different from another: world map vs. shop vs. level, separate puzzle tasks, etc.

For example:

• Can we have different update() loops in different .lua files, and activate/deactivate them at will?

• Can multiple update() loops be active at once, one with "universal" code plus one at a time that adds what the current "scane"/"level"/context needs?

Or does the main playdate.update() loop need to evaluate a conditional every frame to re-check the current context?

I can think of various approaches I know will work; what's recommended?

Thanks in advance!

1 Like

If you want a robust solution to manage several scenes you can look at this library.

It's designed for love2d but it should be easily adaptable for playdate or at least be a good source of inspiration.

1 Like

I don’t think the SDK is opinionated in that way. playdate.update is the only function that is called every frame by default and there are functions like playdate.graphics.sprite.update or playdate.timer.updateTimers which automate sprite and timer updates so you don’t have to call those manually but where you go from there is up to you.

1 Like

Thanks all!

How would one go about activating/deactivating the different playdate.update() loops in different .lua files? (Assuming a very simple case, like world map vs. level, where a complex library may be overkill.)

My default approach would be to evaluate a conditional (checking which context/level) every frame, but that seems like needless work for the machine if there's a more elegant approach.

For one of my projects I separated everything into "screens". They have their own update logic, hold all the context they need, and define the transitions between each other. The current screen is stored in a global variable so the playdate.update can call the current screen‘s update directly without having to check anything.

2 Likes

If you like, you can always reassign the value of playdate.update to point at a new function. For example, here pressing A or B will swap out the update function:

function playdate.update()
	print("default update")
end


local updateA = function()
	print("update A")
end

local updateB = function()
	print("update B")
end


function playdate.AButtonDown()
	playdate.update = updateA
end

function playdate.BButtonDown()
	playdate.update = updateB
end

Although I normally do just check a state flag or some conditional, mostly because I find it more readable.

1 Like

In general, I make a simple "scene" or "module" system. A "module" is just an object that has an update function. Then, the core of my game has a variable called currentModule. Whenever playdate.update runs, it just calls update on currentModule. So you change scenes by assigning a different module to currentModule, and then that module's update function will be called on the next playdate.update. No switch statement or long chained if statements required.

Sometimes I go a step further and add becomeActive and resignActive functions to the modules, which are called when a module becomes or stops being the current module, respectively, so that they can do setup and teardown.

2 Likes

Terrific! I didn't even realize you could do all that. These techniques will keep me from going down an awkward path. Thanks! (Wish I could mark more than 1 as "Solution.)

There is so many ways to handle a scene update, it's difficult to say what is the best practice. The Playdate SDK really let you do whatever you prefer. It's really just a question of taste.

Like Donald, I have my own scene system (I actually have two different ones, depending of the project) but sometimes I even think this is a bit over-engineered.

I even think that a simple series of ifs to call the right functions is totally appropriate. When you feel it starts to be less manageable, you will have a better understanding of what you need and can develop a solution appropriate for your needs and style.

-- This is totally fine
current_screen = "intro"

if current_screen == "intro" then
	intro.update()
elseif current_screen == "gameplay" then
	game.update()
end
2 Likes

Great advice in here.

The only thing I'd add is to not do too much too soon.

In the beginning I found it easy to lose focus, I started to build a state machine with everything i ever thought I'd need, and no game got built. :sweat_smile:

Since then, I implement things as quickly/simply as possible and move on. Sometimes I revisit things for clean up if i know it will help a future goal.

3 Likes

True. I'm even evaluating whether not having ANY update loop might be the simplest approach for at least one of my two projects.

Follow-up... how do I revert back to the default playdate.update loop, after reassigning it temporarily with playdate.update = myUpdateLoop?

(If it's not possible, I just won't use the default playdate.update at all.)

TIA.

The default update loop isn’t anything special. It’s just another function defined by you. If you define it first under another name (e.g. defaultUpdate) and then assign it to playdate.update like the other functions, you’ll be able to reference it later and run playdate.update = defaultUpdate whenever you want it back.

3 Likes

An elaborate example can be found in my project:

pushScreen(screen) and popScreen() can be used from anywhere in the code. When popScreen() is called, a transition to the previous screen is added to pendingNavigators. This means that the current update will continue as normal, and on the next update, the switch is made. If Screens keep state, that state can be restored in Screen:resume.
For example when you select level 3 in level select, start level 3 and then call popScreen(); then the level select screen will be restored with level 3 selected.

2 Likes

Seems very complete! Bookmarked for reference... Thanks!