We provide a standard way to structure your projects.
The PicoSystem API works by calling three functions declared in your code.
init()
update(tick)
draw(tick)
update()
to allow you to draw the new view of the game world. You should only perform drawing operations in this method.The game loop runs at forty frames per second, matching the refresh rate of the screen. On each loop cycle we make a call to your update()
and draw()
functions - the reason why these are separate functions is discussed later.
Each cycle of the game loop we increment the value of tick
- it is effectively a counter of the number of frames drawn so far which can be useful for your project to know. TODO: example of why this can be useful?
You shouldn't rely on tick
to calculate timing specific actions (such as player movement) because while PicoSystem aims for 40fps it cannot guarantee it. Instead use the time()
or time_us()
functions to get the number of milliseconds or microseconds since PicoSystem started up.
To ensure that your game runs at a smooth 40fps (frames per second) you need to perform all of the processing and drawing that your game needs within the time budget available - around 25 milliseconds!
Due to other activity happening in the background this time budget is roughly split in half so you can spend ~12 milliseconds in your update()
function and ~12 milliseconds in your draw()
function.
Your update()
and draw()
functions are separate because it allows the game processing code in your update()
function to run while the framebuffer data is being transferred to the screen in the background. It is also good practise to separate your game processing logic from your drawing code!
You should always avoid calling any drawing functions from inside update()
because they could happen during a screen update resulting in an effect called "tearing" where part of the previous frame and next frame are both visible at once.
The full game loop including the framebuffer transfers and vertical sync looks like this:
From the diagram you can see that the framebuffer transfer starts before update()
and is completed before the next call to draw()
. This makes the most of the CPU time available while also ensuring that draw()
only happens when the screen is not being written to.