Call and Return (Ren'Py)

If jump is equivalent to your mother saying "Go do the dishes" (after which you slope off to play computer games) then call is equivalent to "Go do the dishes then come straight back so I can tell you what to do next." A trivial example in Ren'Py script might run something like this:


label mumChores:
    mother "Go do the dishes."
    call dishes
    mother "Put out the trash."
    pc "Aww, mum!"
    call trash
    return

label dishes:
    "You do the dishes."
    return

label trash:
    "You put out the trash."
    return

In each case the return marks the end of the task and the point where control should transfer back to the caller. The labels dishes and trash become sub-routines that complete a specific task. They can be re-used in the unlikely(?) event you choose to do one of these chores voluntarily, or if your father has a similar set of chores for you to do, possibly in a different order.

The self-contained and re-useable nature of these sub-routines is what makes them useful. If this had been built with jumps instead and we wanted to add a dadChores label then both dishes and trash would have to have conditional code to jump back to the right list of chores.

Another benefit is that the structure of the code and story can be made obvious without traipsing around multiple files:


label start:
    call act1
    call act2
    call act3
    return

label act1:
    call act1Scene1
    call act1Scene2
    call act1Scene3
    return
    ...

How does it work?

Hidden from view is the call stack. The call stack is a list of places to go back to.

The stack effectively grows and shrinks as needed with entries added and removed from the end as needed. Once the last entry (effectively the marking where the player pressed "Start" in the main menu) is returned from the game ends.

Functions (Ren'Py)

You can pass parameters when you make a call, and the subroutine can return a value, making it a function. Returned values are slightly strange in Ren'Py being passed back in the special variable _return. In this next example there's a trivial combat function called fight which takes the name of the opponent monster, and its hit points as parameters and returns True if the player survives:


    # Fight a single monster.
    # Returns True if the player lives.
label fight(monName, monHp):
    if pcHp >= monHp:
        "The quick skirmish with the [monName] goes your way, but not without
        it wounding you."
        $ pcHp -= monHp // 4
        return True
    else:
        "Despite your best effort the [monName] bests you in combat."
        return False

    # Fight a goblin.
    # Returns True if the player lives.
label fightGoblin:
    "This room is full of crates and boxes. As you are checking to see if
    there's anything useful inside a goblin jumps out from behind one,
    startling you!"
    call fight("goblin", 4)
    if _return:
        "You find a couple of coins on the corpse."
        $ pcMoney += 2
    return _return

The second label fightGoblin uses the fight function to have the player fight a goblin with 4 hit points. If the player survives they find two gold pieces. It also returns True if the player survives and False if they died.

Local scopes

Another advantage of call and return is that we can create variables that only exist for the duration of the subroutine using renpy.dynamic. These can hide global variables:


    # Fight a single monster.
    # Returns True if the player lives.
label fight(monName, monHp):
    $ renpy.dynamic('hpDiff')
    $ hpDiff = pcHp - monHp
    if hpDiff >= 0:
        "The quick skirmish with the [monName] goes your way, but not without
        it wounding you."
        $ pcHp -= monHp // 4
        return True
    else:
        "Despite your best effort the [monName] bests you in combat."
        return False

When the return is encountered the local variable is deleted automatically.
Note: This is the opposite of a Python function where variables are local by default, and you must declare if you want to access a global one.

Call from

There's one issue with using call and return that relates to how Ren'Py locates labels when a new version of a game moves lines about in the script file. To ensure this works the line after each call (the one that goes on the call stack) needs to be labelled. Ren'Py provides a shorthand for this using call ... from lbl:


label fightGoblin:
    ...
    call fight("goblin", 4) from fightGoblin_fight

When you build a distribution you can have Ren'Py automatically add any missing from labels, but they tend to be ugly, so it can be better to add your own.