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:
local updateA = function()
local updateB = function()
playdate.update = updateA
playdate.update = updateB
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.
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
elseif current_screen == "gameplay" then
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.