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?
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.
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.
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.
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.
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
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.
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.
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.
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.