Basics of Ren'Py #6
Understanding the Call Stack
Welcome to another one of my tutorials!
Today, we're taking a look at different ways of moving between labels. We've encountered one so far, that being the call statement, all the way in my first Ren'Py Basics tutorial. In this one, we'll be adding a second way - the jump statement - and talking about the crucial difference between the two.
In the aforementioned Basics #1, we've also ran across the return statement. Remember the door analogy from there?
- "Think of it like closing the door behind you. You don't have to do it, but you should, because otherwise, once the game reaches that point, it will continue to whatever code is below, either taking you places it's not supposed to, or leading to something that straight up throws an error."
This indicates that the return statement prevents trouble. And it does, but today we'll also see how we can further use it to our advantage.
First things first, let's post a quick example of a call and a return as a reminder, although you should be familiar with them by now. This is how you want to move between labels most of the time, with the call statement.
label hallway():
"You're standing in the hallway."
"Let's go to the kitchen!"
call kitchen
"You've left the kitchen and returned to the hallway."
return
label kitchen():
"Looks like someone is cooking dinner."
"Better not intrude, let's go back!"
return
Here, we start inside the hallway label. Once script gets to the call statement, it gets us to the kitchen label. After we reach the end of that one, the return statement gets us back to the original label, the hallway, where the script continues where it left off.
We've just witnessed the call stack in action. Imagine it like a stack of papers - the sheet (call) on top of the stack is what we're always looking at. Initially, there's only a single sheet (call) on the stack - The hallway label. When we reach the call kitchen line inside, the kitchen label is called, and it is added on top of the stack.
So, now the stack has two sheets (calls) - hallway in the bottom, kitchen on the top. We go through the kitchen label and eventually reach the return statement. return statement always removes the top sheet (call), and as such, kitchen is removed from the stack, and it goes to having a single sheet (call) once again - the hallway label.
Let's go for another example. This time, let's look at something practical.
label start():
"(1) Start of the game - One on the stack."
call chapterOne
"(3) Start after ch1 - One on the stack."
call chapterTwo
"(5) Start after ch2, end of the game - One on the stack."
# Back to the main menu.
return
label chapterOne():
"(2) Chapter 1 - Two on the stack."
return
label chapterTwo():
"(4) Chapter 2 - Two on the stack."
return
This is an excellent way of structuring your script. By default, the start label is where the Start button in the main menu takes you. Like this, we can very clearly see where the game takes us - to multiple different labels, which can be defined in different files for even better structure!
We'll go over how the call stack changes once the game has Started. The numbers at the beginning of each dialogue line indicate in which order they are seen, and the rest mentions how many calls are currently on the call stack.
- The call stack begins with only a single call, the start label.
- Game calls the chapterOne label, putting it onto the stack.
- Game pops the chapterOne call, going back to start.
- chapterTwo is called, onto the stack it goes.
- We return from chapterTwo back to the start label.
- The final return in the start label empties out the call stack. The game has nowhere else to go, and as a result returns back to the main menu.
Hopefully you now understand what the call stack is and how it functions. With that out of the way, we can now look at the main reason why this tutorial is important - the difference between the call statement and the jump statement.
label start():
"(1) Start of the game - One on the stack."
jump chapterOne
label chapterOne():
"(2) Chapter 1 - Still one on the stack."
jump chapterTwo
label chapterTwo():
"(3) Chapter 2 - Still one on the stack."
# Back to the main menu.
return
The jump statement doesn't add onto the call stack, as it's not a call. It's that simple.
If we go through the project this time, we will play through in a similar manner, only this time, the call stack will never go above having a single call - Only the start label.
We may not have an overview of the entire structure like in the second code block, but it will still have us moving very smoothly between the chapter labels. Another thing is that, in this case, we'd only have a single return statement, in the very last label, which would get us back to the main menu.
At thing point, using jump statements looks way simpler than using call statements. And it is, and suffices great as long as it's - in my opinion - contained within a scene and isn't used to move between bigger parts of the story, like between chapters. Not using call means being unable to use return, which comes with limitations.
First and foremost, you will never be able to come back to where you jumped from, but more reasons are worth mentioning, like:
- As already mentioned, not having a nice overview of the structure like in the second code block.
- Replays, if coded into the game, will be made impossible, as getting to the end of the label would jump you to another one instead of ending the replay.
- For the same reason, reusing labels to run Python code - like some people do - won't be possible either.
- We won't be able to return any values.
You probably don't know what the last bullet point means. Don't worry, that's what we'll look at next. Let's go back to using call statements and teach you something new.
label start():
"You've heard of a princess held by a terrible dragon."
"You grab your horse and venture to the castle!"
call castle
# Cookies offered.
if _return == "Good":
"The dragon sniffs the cookies before tasting."
"It offers you the princess if you come back with more."
"Good ending!"
# Sword drawn.
else:
"The dragon puffs and burns you to a crisp."
"Bad ending!"
# Back to the main menu.
return
label castle():
"You see the dragon."
"How do you approach this?"
menu:
"Offer grandma's cookies.":
return "Good"
"Draw my sword!":
return "Bad"
As you can see in the castle label, a value can be returned, which is then stored in the _return variable. This allows us to separate the scene in the castle and bring the outcome back to the previous call (the start label in our case).
Transferring a value between labels (among other things) can also be done with a global variable defined by the default statement. This gives the variable an appropriate name and allows it to be brought up anytime, while the _return variable gets overwritten with every new return.
Programmatically speaking, having less variables is a good practice. Practically speaking, I find myself using most variables more than once, and I think naming things well is of greatest importance, which is why I prefer the latter. This means I'll show you an example of this way, even though it doesn't relate to the today's topic of the call stack that much.
default cookiesOffered = None
label start():
"You've heard of a princess held by a terrible dragon."
"You grab your horse and venture to the castle!"
call castle
# Cookies offered.
if cookiesOffered:
"The dragon sniffs the cookies before tasting."
"It offers you the princess if you come back with more."
"Good ending!"
# Sword drawn.
else:
"The dragon puffs and burns you to a crisp."
"Bad ending!"
# Back to the main menu.
return
label castle():
"You see the dragon."
"How do you approach this?"
menu:
"Offer grandma's cookies.":
$ cookiesOffered = True
"Draw my sword!":
$ cookiesOffered = False
return
And I think that could be it for this tutorial. I will also do a tutorial in the Screen Language series on how the call stack interacts with screens that are called and shown, but that's for another time.
What did we go over today?
- A revision of moving between labels with the call statement.
- What the call stack is and how it can change over a Ren'Py game.
- How to move between labels with the jump statement.
- What the difference between call and jump is.
- That a value can be returned to move it between labels.
- Slightly off-topic: How to move a value between labels with a defaulted variable instead.
If you follow these practices, you're bound to save yourself some headaches down the line, when you decide to possibly add more complex features, like already mentioned replays, or maybe a log of all of player's choices. I also find well written code very satisfying, to which good practices definitely contribute.
As always, thank you for reading, and have a great rest of the day..