Main Menu Cartridge
This lesson continues where Main Menu Version 1 leaves off.
Version 1 introduces and explains what game states are and what a state machine is. So if you have questions about that, go back and watch that video:Main Menu - Version 1
This version is more efficient than the first, so we actually recommend using this in your game, after understanding why it is better and how to use it.
After you made this main menu, change it up! Try some of the challenges below to spark some ideas!
After following the lesson, take on some of these challenges to test your knowledge and skills!
1. If you followed Version 1 already, copy that game, and convert it to use this version.
Improved Main Menu!
Video Coming Soon!
What is a sprite?
Explanation of Code!
In Main Menu - Version 1, we introduced the simplest build of a state machine, and how to control the game states. This required only basic knowledge of programming so it is a good starting point for beginners. It is easy to read and understand, but not as easy for the code to run.
Efficiency is what every programmer works towards. Efficiency is a quality of wasting the least amount of resources. In general, resources are whatever is used in any type of process.
For example, if you are making a poster for a class project. Then you need to find or buy resources such as: poster board, glue, paper, scissors, etc. And you could buy 3 of everything, make 3 different posters, and choose the best one. But that will waste a lot of time and materials spent making the other 2. So that is not efficient.
It would be much more efficient to plan out your poster and know exactly which materials you need and how much. Then you should only get what you need, and use it all in meaningful ways so you have no wasted materials or time.
A good programmer will consider the use of the computer's resources and when we talk about a computer's resources, we usually mean Memory. There are basically 2 types of memory, stored memory and active memory.
Stored Memory is the total amount of data that can be saved. Every file and folder you have on your computer takes up space. And every computer has a limit of how much space it has for saving data. PICO-8 limits our games to a very small space compared to modern video games, so we are forced to keep our games nice and simple.
Active Memory is the amount of data that can be running. Every program you have open right now is using your computer's active memory. Some programs use a lot and you might notice your computer slow down and take time to load. While some programs use very little active memory. They are either small programs, or very efficient programs, and you will notice your computer loads and runs them easily.
Now then, what does all that have to do with making games in PICO-8?
Well there are so many ways to write code that does the same thing in the end. But the trick to being the best programmer is figuring out what is the most efficient way.
Sometimes, we sacrifice the most efficient way for better readability and understanding. For example:
s=1 h=10 a=5 d=3
Those are very efficient variable names because they are only 1 letter each. But what does it mean?! The person who wrote it might be able to understand it, but someone else will have to waste a lot of their time trying to figure it out.
sprite=1 health=10 attack=5 defence=3
Now these variables are a tiny bit less efficient, but way more understandable! So most of the time, we should balance these two to find the best way, which isn't always the most efficient way.
Version 1 is a good example of writing game states for understanding but not for efficiency. And here's what is really not efficient about it.
--main update and draw function _update() if scene=="title" then update_title() elseif scene=="menu" then update_menu() elseif scene=="game" then update_game() elseif scene=="gameover" then update_gameover() end end function _draw() if scene=="title" then draw_title() elseif scene=="menu" then draw_menu() elseif scene=="game" then draw_game() elseif scene=="gameover" then draw_gameover() end end
We don't quite get this far in the lesson, but if you continued to build your game this way, you would add each game state in another
elseifstatement. And this could get very long. So not only does it waste a lot of code which takes up storage memory, but it also takes a lot of active memory to constantly run each of these checks every tick of the game. So that gets expensive and uses too many resources.
Luckily there are more efficient ways of doing the same thing and skipping those checks completely!
_draw functions to run.
So we wrote:
function _update() end function _draw() end
Then we used a variable to store the active game state name and check that variable to run the correct game state. Even though that is easy to follow, it is not efficient, but what it is doing looks something like this:
So basically we are just setting
_update to use our active game state's update function.
And the same for draw.
But here is something you might not have known: Function names are actually just variables that hold functions instead of just a word or a number.
What does this mean??
Well it means that we can treat function names just like variables, because that's what they are! (this is unique to the Lua programming language)
So we can create an empty function:
function myfunction() end
myfunction is a variable, and we can set that variable to whatever we want, for example, a copy of another function's code.
function myfunction() end function _init() myfunction=print --copies print() code into myfunction() myfunction("hello world") end --prints hello world
You should already know the built in function
and usually the first thing we all learn to do in any language is write "hello world" onto the screen.
print("hello world") is how we normally do that in PICO-8, but because we copied the
function from the variable
that means we can then write
myfunction("hello world") to do exactly what
print() would normally do!
Understanding that is very important to understanding how this more efficient State Machine works.
So now that we know how to copy functions from one function name (variable) to another function name (variable),
we can skip the
if elseif checks from Version 1 like this:
We could start by writing the required update and draw functions.
function _update() end function _draw() end
But doing that, is really just creating two variables named
And we would normally just create variables by writing
So by just writing
_update=, we can creating a variable named
_update, which is even more efficient than above.
Then we can jump right into setting the update and draw variables to hold the game state function code we want to run.
Notice how we don't use any parentheses () because we are treating them as the variables they are, and not trying to run them as functions.
In fact, when PICO-8 starts the game, it looks for variables named
_draw and tries to run them as functions automatically.
It is really important to understand all of that, to be able to understand what we will do next. So if that all makes sense, let's start building the more efficient state machine!
What is a variable?
function _init() --variables x=63 y=63 --first game state init_menu() end
By setting up your variables inside of the
you can easily reset your game by calling
the variables here have access to any custom functions you write.
That might come in handy later so it's a good practice.
So far that is the same as Version 1, but the next part is new. We call a function that will be named
init_menu meaning "initialize menu",
but you can name it whatever you want.
open_menu are also good names.
This function will be used to start the menu game state by setting
_draw to the code written in
function init_menu() _update = update_menu _draw = draw_menu end
By doing it this way, we are able to skip the entire "main update and draw" tab from Version 1 that
held the long
elseif checks. To fully understand how this code works, read sections zero and one above.
So now when we start this game, PICO-8 will first run the
_init() function which will set the variables and then run the
init_menu() function. That function will copy whatever code is saved in
draw_menu into the new variables
When PICO-8 finished
_init(), it will then look for variables named
_draw, and run them as functions.
And so they will run whatever code we write in
So that's next.
But let's also create an
init_game() function to change the main
_draw functions to run the game.
function init_game() _update = update_game _draw = draw_game end
init_something functions become our State Machine!
So for each Game State you want to create, just write another one of these functions that change the main
_draw variables to hold your specific
Here are the specific update and draw functions for each game state that we want to have. These are the exact same as Version 1.
The only difference is how we change the active Game State. Instead of using the "scene" variable,
now we just need to run the
init_state function for the state you want to switch to.
update_menu, when the user presses button X, then we run
function update_menu() if btnp(❎) then init_game() end end function draw_menu() cls() print("press ❎ to start",30,63) end
And in the
update_game function, when something happens and we want to run a new Game State, then we run that
Just like Version 1, we are only giving an example of a Main Menu state and a Game state, so in the game state, we are also simply
checking if the user presses the X button again, which will return to the Main Menu by running the
function update_game() if btn(⬅️) then x-=1 end if btn(➡️) then x+=1 end if btn(⬆️) then y-=1 end if btn(⬇️) then y+=1 end if btnp(❎) then init_menu() end end function draw_game() cls() spr(1,x,y) end
You can organize your code in PICO-8 using the tabs however you would like. In the Full Code section below, we organized all of the
init functions in the first tab,
then all of the
update functions in the second tab, and finally all of the
draw functions in the third tab.
--inits function _init() --variables x=63 y=63 --first game state init_menu() end function init_menu() _update=update_menu _draw=draw_menu end function init_game() _update=update_game _draw=draw_game end -->8 --updates function update_menu() if btnp(❎) then init_game() end end function update_game() if btn(⬅️) then x-=1 end if btn(➡️) then x+=1 end if btn(⬆️) then y-=1 end if btn(⬇️) then y+=1 end if btnp(❎) then init_menu() end end -->8 --draws function draw_menu() cls() print("press ❎ to start",30,63) end function draw_game() cls() spr(1,x,y) end
-->8 is a new tab in Pico-8. They don't appear inside the Pico-8 code editor.