Skip to content

P2.1 — Functions

What you'll learn

  • What a function is: a named block of steps you can run by name, as many times as you like.
  • How to define a function and call it, and that defining is not the same as running it.
  • How a function takes inputs (parameters) and gives back an output (return).
  • Why breaking a program into functions is the core habit of thinking like a programmer.

How it applies

  • Functions tame complexity. A big program is unmanageable as one long list of steps. Naming a chunk of steps — take_damage, save_game — lets you think about what it does without holding how in your head. This is the main tool for keeping a growing program understandable.
  • Write once, use many. A step you need in five places becomes one function called five times. Fix a bug in it once and all five callers are fixed — versus hunting down five copies.
  • Defining is not running. A function's body does nothing until the function is called. Beginners often expect the code to run where it is written; it runs where it is called. Knowing this prevents "why didn't my function happen?" confusion.
  • No return, no result to use. A function that computes something but does not return it leaves the caller with nothing to work with. Deciding what a function hands back is part of designing it.

Concepts

A function is named, reusable steps

A function is a block of instructions with a name. You define it once, then call it by name whenever you want those steps to run. You have already been using one — _ready is a function Godot calls for you. Now you write your own:

func greet():
    print("Hello!")
    print("Welcome.")

func starts the definition, greet is the name, the () will hold inputs (none yet), and the indented lines are the body — the steps that run when the function is called. Defining it does not run it. To run the body, you call the function by writing its name with parentheses:

func _ready():
    greet()        # this runs greet's body now
    greet()        # call it again — the body runs a second time

Calling greet() twice runs its two prints twice. The body lives in one place; the calls decide when and how often it happens.

Inputs: parameters and arguments

A function is far more useful when it can work on values you give it. A parameter is a named input the function declares; an argument is the actual value you pass when you call it:

func greet(who):           # `who` is a parameter — a placeholder for an input
    print("Hello, " + who)

func _ready():
    greet("Vex")           # "Vex" is the argument — it fills in `who`
    greet("Mara")          # different argument, same function

Inside the body, who stands for whatever value the caller passed. So greet("Vex") prints Hello, Vex and greet("Mara") prints Hello, Mara — one definition, different inputs each call. (The next book adds types to parameters, like who: String; here, focus on the idea that a parameter is a named slot the caller fills.)

Output: return

A function can hand a value back to whoever called it, using return:

func add(a, b):
    return a + b           # hand back the sum

func _ready():
    var result := add(2, 3)   # result becomes 5
    print(result)             # 5
    print(add(10, 20))        # 30 — use the returned value directly

return a + b computes the sum and gives it back, so add(2, 3) is the value 5 at the call site — you can store it, print it, or use it in a bigger expression. return also ends the function immediately. A function with no return simply does its steps and hands nothing back (like greet, which prints but returns nothing) — fine when you only want the effect, not a result.

Example

Inputs and output together, traced:

func price_with_tax(price, rate):
    var tax := price * rate
    return price + tax

func _ready():
    var total := price_with_tax(100, 0.2)   # price=100, rate=0.2
    print(total)                             # 120

The call passes 100 and 0.2 into the parameters price and rate. Inside, tax becomes 100 * 0.2 = 20, and the function returns 100 + 20 = 120. Back at the call site, total receives that 120. The function packaged a small computation under a name; the caller just supplies inputs and uses the output, without caring how the tax is figured. Call it again with different numbers and it computes a new result — the reuse that makes functions worth defining.

Walkthrough

Use your P1.1 script setup. (Define your functions at the top level of the script, alongside _ready, not inside it; call them from inside _ready.)

  1. Define func greet(): print("Hi"). From _ready, call greet() twice. Run; confirm Hi appears twice. Then remove the calls but keep the definition, run, and confirm nothing prints — defining is not running.
  2. Add a parameter: func greet(who): print("Hi, " + who). Call it with two different names and confirm each greeting.
  3. Write func add(a, b): return a + b. From _ready, store var r := add(4, 5) and print r (9). Then print add(2, 3) + add(10, 10) and predict the result (25) before running.
  4. Write price_with_tax(price, rate) from the example and call it with two different price/rate pairs, printing each total.

Optional sanity check

Put a print("defining now") as the last line of your script file, outside any function, and a print("inside greet") inside greet — but do not call greet. Run. You will see that the function's inner print never happens (it was defined, not called). Add the greet() call and it appears. That is "defining is not running," demonstrated.

Self-check quiz

Q1 — What is the difference between a parameter and an argument?

A. They are the same thing. B. A parameter is the named input in the function's definition; an argument is the actual value passed when the function is called. C. A parameter is the output; an argument is the input. D. An argument is only for numbers.

Reveal answer

B. The definition declares parameters (named slots, like who); the call supplies arguments (actual values, like "Vex") that fill those slots. A misses the distinction. C confuses input with output (return is the output). D invents a restriction.

Q2 — You define a function but never call it. What runs?

A. The function body runs once where it is defined. B. Nothing from the body runs — a function's body only runs when it is called. C. It runs automatically at the end of the program. D. It causes an error.

Reveal answer

B. Defining a function only describes the steps; they execute only when the function is called by name. A is the common misconception (code runs where written). C and D invent behaviors — an uncalled function is simply never run, which is not an error.

Q3 — What does return do in func add(a, b): return a + b?

A. Prints the sum. B. Hands the value a + b back to the caller, so add(2, 3) becomes 5 at the call site. C. Stores the sum in a variable named return. D. Nothing; return is optional decoration.

Reveal answer

B. return gives a value back to whoever called the function, so the call expression is that value and can be stored or used. A confuses returning with printing (they are different — a returned value is not shown unless you print it). C and D misread return entirely.

Integration question

Q4 — open

A program computes a player's final damage three different places, each time as base * 2 + bonus. A teammate copy-pastes that expression into all three spots. Explain why turning it into a function func final_damage(base, bonus): return base * 2 + bonus is better, covering reuse, single-point-of-fix, and the parameter/return roles — and explain what would go wrong if the function computed the value but forgot to return it.

Reveal expected answer

Wrapping the formula in a function replaces three copies with one definition called three times: final_damage(base, bonus) at each site. Reuse: the logic is written once and used wherever needed. Single point of fix: if the formula changes (say base * 3 + bonus), you edit the one function and all three callers update at once — versus finding and fixing three copies and likely missing one, which is how copies silently diverge. Parameter/return roles: base and bonus are parameters (named inputs the callers fill with their own values), and return hands the computed result back so each call is the damage value, usable in an assignment or a bigger expression. If it forgot to return: the function would compute base * 2 + bonus internally and then throw it away, handing nothing back; every caller writing var d := final_damage(...) would receive no usable value, so the damage would effectively be missing. A function that produces a result the caller needs must return it — computing without returning is the same as not computing at all, from the caller's point of view.