Game Development with Godot & GDScript Module 2 – GDScript fundamentals through game data
Functions, scope and variadic arguments
Write GDScript functions with typed parameters, defaults and return types, keep local and member names apart, and use static, recursive and variadic functions.
What you will learn
- Declare functions with typed parameters, default values and return types
- Explain local, member and global scope, and avoid shadowing a name
- Write static and recursive functions, and replace deep recursion with a loop
- Use variadic parameters (Godot 4.5 and later) and callv() to pass any number of arguments
Before you start
What you’ll learn · Before you start
What you will learn
- Declare functions with typed parameters, default values and return types
- Explain local, member and global scope, and avoid shadowing a name
- Write static and recursive functions, and replace deep recursion with a loop
- Use variadic parameters (Godot 4.5 and later) and callv() to pass any number of arguments
Before you start
On this page
A recursive function with no way to stop called itself 2,047 levels deep on Godot 4.7.2 before the engine halted it with “Stack overflow”. Functions are how a game’s rules get names, and the rules for writing them, typed parameters, return values, scope and recursion, decide whether a mistake is caught before the game runs or after a player finds it.
Below you write functions with types and default values and read the messages Godot gives for the usual slips, see where GDScript finds a name and what happens when two share one, write static and recursive functions, and take any number of arguments with the rest parameter that arrived in Godot 4.5.
A function takes typed parameters and returns one value
func declares a function: its name, its parameters in brackets, and after -> the type of the value it returns.
Every parameter is required unless it has a default value, and parameters with defaults must come last. Arguments are
matched by position, and return ends the call and hands back its value; a function typed -> void returns none.
extends SceneTree
# Functions with typed parameters, default values and a return type.
func roll_loot(level: int, luck: float = 1.0, guaranteed: bool = false) -> int:
var gold := level * 10
if guaranteed:
return gold * 2
return roundi(gold * luck)
func announce(item: String) -> void:
if item.is_empty():
return
print("You found ", item, ".")
func _initialize() -> void:
print("roll_loot(3) = ", roll_loot(3))
print("roll_loot(3, 1.5) = ", roll_loot(3, 1.5))
print("roll_loot(3, 1.5, true) = ", roll_loot(3, 1.5, true))
announce("a silver key")
announce("")
quit() extends SceneTree
# A function that promises a String, with a path that returns nothing: a negative score.
func grade(score: int) -> String:
if score >= 50:
return "pass"
elif score >= 0:
return "fail"
func _initialize() -> void:
print(grade(70))
quit() extends SceneTree
# A default value before a parameter without one.
func spawn(count: int = 1, kind: String) -> void:
print(count, " ", kind)
func _initialize() -> void:
spawn(3, "slime")
quit() extends SceneTree
func heal(hp: int, amount: int = 10) -> int:
return hp + amount
func announce(item: String) -> void:
print("You found ", item, ".")
func _initialize() -> void:
print(heal())
print(heal("30"))
print(heal(30, 5, 1))
var said = announce("a map")
quit() from session import files, run
files("project.godot", "loot_roll.gd", "mistakes=.")
run("""
godot --headless --no-header --script loot_roll.gd
!godot --headless --no-header --check-only --script missing_return.gd
!godot --headless --no-header --check-only --script optional_first.gd
!godot --headless --no-header --check-only --script bad_calls.gd
""") Output
$ godot --headless --no-header --script loot_roll.gd
roll_loot(3) = 30
roll_loot(3, 1.5) = 45
roll_loot(3, 1.5, true) = 60
You found a silver key.
$ godot --headless --no-header --check-only --script missing_return.gd
SCRIPT ERROR: Parse Error: Not all code paths return a value.
at: GDScript::reload (res://missing_return.gd:4)
ERROR: Failed to load script "res://missing_return.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
$ godot --headless --no-header --check-only --script optional_first.gd
SCRIPT ERROR: Parse Error: Cannot have mandatory parameters after optional parameters.
at: GDScript::reload (res://optional_first.gd:4)
ERROR: Failed to load script "res://optional_first.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
$ godot --headless --no-header --check-only --script bad_calls.gd
SCRIPT ERROR: Parse Error: Too few arguments for "heal()" call. Expected at least 1 but received 0.
at: GDScript::reload (res://bad_calls.gd:12)
SCRIPT ERROR: Parse Error: Cannot pass a value of type "String" as "int".
at: GDScript::reload (res://bad_calls.gd:13)
SCRIPT ERROR: Parse Error: Invalid argument for "heal()" function: argument 1 should be "int" but is "String".
at: GDScript::reload (res://bad_calls.gd:13)
SCRIPT ERROR: Parse Error: Too many arguments for "heal()" call. Expected at most 2 but received 3.
at: GDScript::reload (res://bad_calls.gd:14)
SCRIPT ERROR: Parse Error: Cannot get return value of call to "announce()" because it returns "void".
at: GDScript::reload (res://bad_calls.gd:15)
ERROR: Failed to load script "res://bad_calls.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 functions_session.py
roll_loot(3) used both defaults, luck 1.0 and guaranteed false, and gave 30; roll_loot(3, 1.5) replaced the
first default, and roll_loot(3, 1.5, true) both. announce("") printed nothing, because its first line returns
early from a void function. Every mistake of the session was found before anything ran, which is what the types buy:
| Mistake | Godot’s message |
|---|---|
a path with no return in a typed function |
Not all code paths return a value. |
| a default before a required parameter | Cannot have mandatory parameters after optional parameters. |
| too few or too many arguments | Too few arguments for "heal()" call. Expected at least 1 but received 0. |
| an argument of the wrong type | Cannot pass a value of type "String" as "int". |
| using the result of a void function | Cannot get return value of call to "announce()" because it returns "void". |
grade() returns nothing for a negative score, so Godot refuses the whole function. Give every path a return, here
an else with its own value, or end the function with a return that covers the rest.
Names are found locally first, then in the script, then globally
A name in a function can mean three things. A local variable or a parameter exists only during one call. A member,
declared with var or const outside any function, belongs to the script and is shared by all its functions. A
global name, such as print(), maxi() or a class like Node2D, is available everywhere. The reference gives the
order of the search: local, then class member, then global. Step through the names of scope.gd:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- amount and left. In take_damage(), amount is a parameter and left a local declared with var. Both are found in the first place GDScript looks, and both are gone when the call returns.
- health. take_damage() declares no local named health, so the search goes on to the members of the script and finds var health, the one every function shares.
- maxi. maxi is neither a local nor a member, so it is found among the global names of @GlobalScope, with print() and roundi().
- A local hides the member. shadowed() declares var health itself. The search finds that local first and stops, so the member stays 0 and the local is 90, as the run printed.
- self reaches past it. self.health skips the locals and reads the member. Better still, give the local another name, which the editor asks for with a warning.
extends SceneTree
# Where names live: a member of the script, locals of a function, and the globals of @GlobalScope.
var health := 100 # a member: every function of the script sees it
const MAX_HEALTH := 100 # a constant member
func take_damage(amount: int) -> void:
var left := health - amount # left is local: it exists only during this call
health = maxi(left, 0) # health is the member; maxi() is a global function
print("took ", amount, ", health is now ", health)
func shadowed(amount: int) -> void:
var health := MAX_HEALTH - amount # a local with the member's name hides the member
print("local health ", health, ", member health ", self.health)
func _initialize() -> void:
take_damage(30)
take_damage(90)
shadowed(10)
print("after both calls, health = ", health)
quit() extends SceneTree
# A parameter named like a global function, max(). It works, but the name now means two things in this function.
func spend(gold: int, max: int) -> int:
print("max is the parameter: ", max, "; max(3, 4) still calls the global function: ", max(3, 4))
return mini(gold, max)
func _initialize() -> void:
print(spend(500, 120))
quit() from session import files, run
# scope.gd and shadow_global.gd run in a project with Godot's default warning levels, then in strict/, whose
# project.godot turns the two shadowing warnings into errors (level 2).
files("project.godot", "scope.gd", "shadow_global.gd", "strict/project.godot", "scope.gd=strict/scope.gd",
"shadow_global.gd=strict/shadow_global.gd")
run("""
godot --headless --no-header --script scope.gd
godot --headless --no-header --script shadow_global.gd
cd strict
!godot --headless --no-header --check-only --script scope.gd
!godot --headless --no-header --check-only --script shadow_global.gd
""") Output
$ godot --headless --no-header --script scope.gd
took 30, health is now 70
took 90, health is now 0
local health 90, member health 0
after both calls, health = 0
$ godot --headless --no-header --script shadow_global.gd
max is the parameter: 120; max(3, 4) still calls the global function: 4
120
$ cd strict
$ godot --headless --no-header --check-only --script scope.gd
SCRIPT ERROR: Parse Error: The local variable "health" is shadowing an already-declared variable at line 4 in the current class. (Warning treated as error.)
at: GDScript::reload (res://scope.gd:15)
ERROR: Failed to load script "res://scope.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
$ godot --headless --no-header --check-only --script shadow_global.gd
SCRIPT ERROR: Parse Error: The function parameter "max" has the same name as a built-in function. (Warning treated as error.)
at: GDScript::reload (res://shadow_global.gd:4)
ERROR: Failed to load script "res://shadow_global.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 scope_session.py
take_damage() changed the member health, so the change lasted after the call: 70, then 0. shadowed() declared a
local called health, which shadowing: Declaring a local variable or parameter with the name of a member or a global, which hides it inside the function: the name then means the local. The editor warns about it. the member: inside the function the name means the
local, 90, and the member stayed 0. self.health reaches past the local to the member.
Key idea
GDScript looks a name up in the locals first, then the members, then the globals, and the first match wins, so a local or parameter with a member’s name hides the member until the call returns.
In shadow_global.gd the name max meant two things at once:
the parameter, 120, as a value, and still the global function in the call max(3, 4). The default project ran both
scripts without a word; the strict project, with the two shadowing warnings at level 2, refused them with the
editor’s messages.
A parameter with a member's name
func boost(speed: float): speed *= 1.5 changes the parameter, not the member speed, so the boost is lost when the
call returns. Give the parameter its own name, such as factor, and write speed *= factor.
Checkpoint
Check: Parameters, defaults and return types · Local, member and global names
A static function needs no instance, and a recursive one needs a way to stop
A static function: A function declared static func. It belongs to the script itself rather than to one object made from it, so it cannot use instance members or self, only its arguments, constants and static variables., declared static func, belongs to the script itself rather than
to one object made from it, so it can be called without one. In exchange, it cannot use anything that belongs to one
object: the reference rules out the instance’s member variables and self. A
recursion: A function calling itself on a smaller part of the problem, until a base case it answers without calling itself. In Godot 4.7.2 a recursion stopped at depth 2,047 with Stack overflow. function calls itself, which suits data that holds smaller copies of itself, such
as chests inside chests. Step through count_gold():
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The outer chest. count_gold(treasure) starts with total 0 and adds the first thing, 5 gold coins: total 5.
- A chest inside. The next thing is an Array, a chest, so the function calls itself on it and waits for the answer.
- Deeper. The middle chest adds 10, then finds another chest and calls count_gold() again. Three calls are now running, each with its own total.
- The base case. The inner chest holds only coins, 20 and 1, so it makes no further call: it returns 21. A recursion needs such a case, or it never stops.
- Back up. The middle chest adds the 21 to its 10 and returns 31 to the outer chest.
- The rest. The outer chest now has 5 + 31 = 36, adds 3, and calls count_gold([]) for the empty chest, which returns 0 at once. It returns 39, as the run printed.
extends SceneTree
# A static helper, and a recursive function that counts the gold in chests inside chests.
const MAX_LEVEL := 50
var gold_found := 0
static func clamp_level(level: int) -> int:
return clampi(level, 1, MAX_LEVEL) # a constant belongs to the script, so a static function may use it
func count_gold(chest: Array) -> int:
var total := 0
for thing in chest:
if thing is Array:
total += count_gold(thing) # a chest inside the chest: the function calls itself
else:
total += thing
return total
func _initialize() -> void:
print("clamp_level(99) = ", clamp_level(99), ", clamp_level(-4) = ", clamp_level(-4))
var treasure := [5, [10, [20, 1]], 3, []]
gold_found = count_gold(treasure)
print("gold in ", treasure, " = ", gold_found)
quit() extends SceneTree
var gold := 100
static func spend(cost: int) -> int:
return gold - cost
func _initialize() -> void:
print(spend(5))
quit() extends SceneTree
# A recursive function with no base case: every call makes another one, until Godot stops it.
var deepest := 0
func dive(depth: int) -> int:
deepest = depth
return dive(depth + 1)
func _initialize() -> void:
quit()
dive(1)
func _finalize() -> void:
print("deepest call reached: ", deepest) from session import files, run
# overflow.gd prints more than three thousand lines, so the session shows its first three, counts the lines of its
# backtrace and the errors that follow them, and shows its last line.
files("project.godot", "chests.gd", "static_mistake.gd", "overflow.gd")
run("""
godot --headless --no-header --script chests.gd
!godot --headless --no-header --check-only --script static_mistake.gd
!godot --headless --no-header --script overflow.gd 2>&1 | head -n 3
godot --headless --no-header --script overflow.gd 2>&1 | grep -c "] dive"
godot --headless --no-header --script overflow.gd 2>&1 | grep -c "Stack underflow"
godot --headless --no-header --script overflow.gd 2>&1 | tail -n 1
""") Output
$ godot --headless --no-header --script chests.gd
clamp_level(99) = 50, clamp_level(-4) = 1
gold in [5, [10, [20, 1]], 3, []] = 39
$ godot --headless --no-header --check-only --script static_mistake.gd
SCRIPT ERROR: Parse Error: Cannot access non-static variable "gold" from the static function "spend()".
at: GDScript::reload (res://static_mistake.gd:7)
ERROR: Failed to load script "res://static_mistake.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
$ godot --headless --no-header --script overflow.gd 2>&1 | head -n 3
SCRIPT ERROR: Stack overflow. Check for infinite recursion in your script.
at: dive (res://overflow.gd:7)
GDScript backtrace (most recent call first):
$ godot --headless --no-header --script overflow.gd 2>&1 | grep -c "] dive"
1023
$ godot --headless --no-header --script overflow.gd 2>&1 | grep -c "Stack underflow"
1024
$ godot --headless --no-header --script overflow.gd 2>&1 | tail -n 1
deepest call reached: 2047
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 recursion_session.py
clamp_level() needs only its parameter and the constant MAX_LEVEL, so it can be static; static_mistake.gd tried
to read the member gold from a static function and was refused. count_gold() found 39 gold in chests nested three
deep, because every call that meets a chest makes one more call, and a chest of coins only, the base case, makes none.
overflow.gd has no base case. Every call made another one until, at depth 2,047, Godot stopped with
Stack overflow. Check for infinite recursion in your script. The session shows the first three lines of the output
and counts the rest: a backtrace that lists 1,023 calls of dive(), then 1,024 errors that read
Stack underflow! (Engine Bug), all from the one missing base case. Data can be that deep without any bug: a
recursive flood fill on an open 64 by 64 map of 4,096 tiles can need a chain of up to 4,096 waiting calls, twice the
limit. When the depth could grow with the data, write the function as a loop over a list of work still to do; a loop
has no such limit.
Static for helpers, recursion for nested data
Make a function static when it works only on its arguments and constants, like clamp_level(). Use recursion when
the data nests and stays shallow, as chests in chests do, and a loop when the depth could run into the thousands.
A rest parameter takes any number of arguments
Since Godot 4.5 a function can end with a rest parameter: The last parameter of a function, written ...name: Array, which collects every argument after the fixed ones into an array. New in Godot 4.5; it takes no default value and no typed array., written ...rest: Array,
which collects every argument after the fixed ones into an array. A function with one is a
variadic function: A function that takes any number of arguments, such as print() and max(). A GDScript function becomes one with a rest parameter, in Godot 4.5 and later., as print() and max() are. GDScript has no
spread syntax to pass an array as separate arguments; callv() does that job.
- 4.3
Built-in functions as Callables new
Utility functions such as print() and methods can be used like a Callable value, so print.callv(values) prints the items of an array as separate arguments.
- 4.5
Variadic functions new syntax
A function can end with a rest parameter, written ...name: Array, that collects every extra argument. GDScript still has no spread syntax for calls; callv() takes an array.
- 4.7
The version of this lesson
Godot 4.7.2 ran every example here, including the rest-parameter rules: one rest parameter, last, with no default value and no typed array.
The timeline has 3 events, from 4.3 to 4.7.
extends SceneTree
# A variadic function collects the extra arguments into an Array (Godot 4.5 and later).
func total(first: float, ...rest: Array) -> float:
var sum := first
for value in rest:
sum += value
return sum
func log_event(kind: String, ...details: Array) -> void:
print(kind, ": ", details.size(), " details ", details)
func _initialize() -> void:
print("total(1, 2, 3) = ", total(1, 2, 3))
print("total(4.5) = ", total(4.5))
log_event("pickup", "coin", 5)
log_event("save")
var scores := [10, 20, 30]
print("total.callv([10, 20, 30]) = ", total.callv(scores))
print("callv(\"total\", [7, 8]) = ", callv("total", [7, 8]))
print.callv(["print", ".callv", " takes ", 3, " pieces"])
print("max(3, 9, 4) = ", max(3, 9, 4), ", min(2.5, 1) = ", min(2.5, 1))
quit() from session import files, run
files("project.godot", "variadic.gd", "rest_mistakes=.")
run("""
godot --headless --no-header --script variadic.gd
!godot --headless --no-header --check-only --script two_rests.gd
!godot --headless --no-header --check-only --script rest_default.gd
!godot --headless --no-header --check-only --script typed_rest.gd
""") Output
$ godot --headless --no-header --script variadic.gd
total(1, 2, 3) = 6.0
total(4.5) = 4.5
pickup: 2 details ["coin", 5]
save: 0 details []
total.callv([10, 20, 30]) = 60.0
callv("total", [7, 8]) = 15.0
print.callv takes 3 pieces
max(3, 9, 4) = 9, min(2.5, 1) = 1
$ godot --headless --no-header --check-only --script two_rests.gd
SCRIPT ERROR: Parse Error: Cannot have parameters after the rest parameter.
at: GDScript::reload (res://two_rests.gd:3)
ERROR: Failed to load script "res://two_rests.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
$ godot --headless --no-header --check-only --script rest_default.gd
SCRIPT ERROR: Parse Error: The rest parameter cannot have a default value.
at: GDScript::reload (res://rest_default.gd:3)
ERROR: Failed to load script "res://rest_default.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
$ godot --headless --no-header --check-only --script typed_rest.gd
SCRIPT ERROR: Parse Error: Typed arrays are currently not supported for the rest parameter.
at: GDScript::reload (res://typed_rest.gd:3)
ERROR: Failed to load script "res://typed_rest.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 variadic_session.py
total(1, 2, 3) put 1 in first and [2, 3] in rest, and returned 6.0; total(4.5) left rest empty.
log_event("save") shows that the rest parameter may stay empty. total.callv(scores) passed the three scores as
three arguments: total without brackets is the function as a value, a Callable: A function as a value. A method's name without brackets, such as total, gives one; call() runs it with arguments and callv() with an array of them., and
callv() calls it with an array of arguments. print.callv([…]) does the same for a built-in function, which works
since Godot 4.3.
The three refused files are the rules of the rest parameter: only one, and it must be last; it cannot have a default value; and its type must be a plain Array, because typed arrays are not supported for it yet.
Checkpoint
Check: Static and recursive functions · Rest parameters and callv()
Practise: a levelling system
Write the three functions of levels.gd, the starter file below: the XP a level needs, the level a total of XP
reaches, and a variadic function that applies bonuses one after another. The rules are in the comments.
As with the earlier tasks of this module, the two scripts go into a fresh folder beside an empty project.godot.
Write the three functions, then call godot --headless --no-header --script check_levels.gd there and read the last
line it prints.
extends Node
# Your task: three functions for a levelling system.
const MAX_LEVEL := 50
# The total XP needed to reach a level: level 1 needs 0, and each next level needs 100 × (the level before it) more,
# so level 2 needs 100, level 3 needs 300 and level 4 needs 600. Below 1 counts as 1; above MAX_LEVEL as MAX_LEVEL.
func xp_for_level(level: int) -> int:
return 0 # replace this line
# The highest level whose XP need is at most xp, from 1 to MAX_LEVEL: 99 XP is level 1, 100 XP is level 2.
func level_from_xp(xp: int) -> int:
return 1 # replace this line
# The base value with every bonus applied in turn, each a fraction: 0.1 means 10 % more, -0.5 means half.
# combine_bonuses(100.0, 0.1, 0.2) is 100 × 1.1 × 1.2 = 132.0, and with no bonuses the base comes back.
func combine_bonuses(base: float, ...bonuses: Array) -> float:
return base # replace this line extends SceneTree
# Checks levels.gd. Run it in the project's folder: godot --headless --no-header --script check_levels.gd
const XP_FOR := [[1, 0], [2, 100], [3, 300], [4, 600], [50, 122500], [0, 0], [-3, 0], [99, 122500]]
const LEVEL_FROM := [[0, 1], [99, 1], [100, 2], [299, 2], [300, 3], [122500, 50], [9999999, 50], [-40, 1]]
func _initialize() -> void:
var levels: Node = load("res://levels.gd").new()
var passed := 0
var total := 0
for case in XP_FOR:
var got: int = levels.xp_for_level(case[0])
total += 1
passed += int(got == case[1])
print("%s xp_for_level(%d) = %d (want %d)" % ["PASS" if got == case[1] else "FAIL", case[0], got, case[1]])
for case in LEVEL_FROM:
var got: int = levels.level_from_xp(case[0])
total += 1
passed += int(got == case[1])
print("%s level_from_xp(%d) = %d (want %d)" % ["PASS" if got == case[1] else "FAIL", case[0], got, case[1]])
for case in [[[100.0, 0.1, 0.2], 132.0], [[80.0], 80.0], [[50.0, -0.5, 0.5], 37.5]]:
var got: float = levels.combine_bonuses.callv(case[0])
total += 1
var ok := is_equal_approx(got, case[1])
passed += int(ok)
print("%s combine_bonuses(%s) = %s (want %s)" % ["PASS" if ok else "FAIL", ", ".join(case[0].map(str)), got, case[1]])
levels.free()
print("%d of %d checks passed" % [passed, total])
quit(0 if passed == total else 1) The session puts a correct levels.gd and a plausible wrong one through the checker:
from session import files, run
# The checker on a levels.gd that solves the task, then on a tempting one with two mistakes.
files("practice/project.godot=right/project.godot", "practice/check_levels.gd=right/check_levels.gd",
"practice/solution/levels.gd=right/levels.gd",
"practice/project.godot=tempting/project.godot", "practice/check_levels.gd=tempting/check_levels.gd",
"practice/tempting/levels.gd=tempting/levels.gd")
run("""
cd right
godot --headless --no-header --script check_levels.gd
cd ../tempting
!godot --headless --no-header --script check_levels.gd
""") Output
$ cd right $ godot --headless --no-header --script check_levels.gd PASS xp_for_level(1) = 0 (want 0) PASS xp_for_level(2) = 100 (want 100) PASS xp_for_level(3) = 300 (want 300) PASS xp_for_level(4) = 600 (want 600) PASS xp_for_level(50) = 122500 (want 122500) PASS xp_for_level(0) = 0 (want 0) PASS xp_for_level(-3) = 0 (want 0) PASS xp_for_level(99) = 122500 (want 122500) PASS level_from_xp(0) = 1 (want 1) PASS level_from_xp(99) = 1 (want 1) PASS level_from_xp(100) = 2 (want 2) PASS level_from_xp(299) = 2 (want 2) PASS level_from_xp(300) = 3 (want 3) PASS level_from_xp(122500) = 50 (want 50) PASS level_from_xp(9999999) = 50 (want 50) PASS level_from_xp(-40) = 1 (want 1) PASS combine_bonuses(100.0, 0.1, 0.2) = 132.0 (want 132.0) PASS combine_bonuses(80.0) = 80.0 (want 80.0) PASS combine_bonuses(50.0, -0.5, 0.5) = 37.5 (want 37.5) 19 of 19 checks passed $ cd ../tempting $ godot --headless --no-header --script check_levels.gd PASS xp_for_level(1) = 0 (want 0) PASS xp_for_level(2) = 100 (want 100) PASS xp_for_level(3) = 300 (want 300) PASS xp_for_level(4) = 600 (want 600) PASS xp_for_level(50) = 122500 (want 122500) PASS xp_for_level(0) = 0 (want 0) PASS xp_for_level(-3) = 0 (want 0) PASS xp_for_level(99) = 122500 (want 122500) PASS level_from_xp(0) = 1 (want 1) PASS level_from_xp(99) = 1 (want 1) FAIL level_from_xp(100) = 1 (want 2) PASS level_from_xp(299) = 2 (want 2) FAIL level_from_xp(300) = 2 (want 3) FAIL level_from_xp(122500) = 49 (want 50) PASS level_from_xp(9999999) = 50 (want 50) PASS level_from_xp(-40) = 1 (want 1) FAIL combine_bonuses(100.0, 0.1, 0.2) = 130.0 (want 132.0) PASS combine_bonuses(80.0) = 80.0 (want 80.0) FAIL combine_bonuses(50.0, -0.5, 0.5) = 50.0 (want 37.5) 14 of 19 checks passed [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_session.py
The tempting answer compares with < where the rule says “at most”, so exactly 100 XP stays at level 1, and it adds
the bonuses together instead of applying them in turn: 100 × (1 + 0.1 + 0.2) is 130, not the 132 the rule asks for,
and a halving followed by +50 % cancels out instead of giving 37.5. Your output should end with
19 of 19 checks passed. Then take the self-check on this page’s four ideas: a miss returns later in
the round, and a note under each option says what it gets right or leaves out:
Practice that adapts to you
Practise this lesson
12 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what is a variadic function in GDScript, and how do you call one with values that are in an array?
A variadic function takes any number of arguments. Since Godot 4.5 its last parameter can be a rest parameter, such as
...rest: Array, which collects every argument after the fixed ones into an array: total(1, 2, 3) with
func total(first: float, ...rest: Array) gets first 1 and rest [2, 3]. There can be only one rest parameter,
it must be last, it cannot have a default and it cannot be a typed array. GDScript has no spread syntax, so to pass
an array’s items as separate arguments, call the function through its Callable: total.callv([10, 20, 30]) gave
60.0. Built-in functions work the same way since Godot 4.3, as in print.callv(values).
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A level editor's fill tool colours connected tiles with a recursive fill(x, y) that calls itself for the four neighbours. It works in small rooms, but filling an open 64 by 64 area prints "Stack overflow" and leaves some tiles uncoloured. Explain the limit with numbers, and rewrite the fill so it works on any map.
-
Fresher to mid
A script has var speed := 200.0 and func boost(speed: float) -> void: speed *= 1.5. After boost(speed) the player is no faster, and no error appears. Explain what happens, fix it, and say which project settings would have flagged it.
Key takeaways
- A function declares typed parameters, defaults at the end, and a return type after
->; Godot refuses a missing return path, a wrong argument count or type and the use of a void result before the game runs. - Names are looked up in the locals, then the members, then the globals, so a local or parameter with a member’s name shadows it; the editor warns, and a strict project refuses it.
- A static function belongs to the script and cannot use instance members, and a recursive function needs a base case; without one Godot stopped at depth 2,047 with “Stack overflow”.
- A rest parameter,
...rest: Array, collects any number of extra arguments (Godot 4.5 and later), andcallv()passes an array’s items as arguments.
Terms in this lesson
- Callable
- A function as a value. A method's name without brackets, such as total, gives one; call() runs it with arguments and callv() with an array of them.
- recursion
- A function calling itself on a smaller part of the problem, until a base case it answers without calling itself. In Godot 4.7.2 a recursion stopped at depth 2,047 with Stack overflow.
- rest parameter
- The last parameter of a function, written ...name: Array, which collects every argument after the fixed ones into an array. New in Godot 4.5; it takes no default value and no typed array.
- shadowing
- Declaring a local variable or parameter with the name of a member or a global, which hides it inside the function: the name then means the local. The editor warns about it.
- static function
- A function declared static func. It belongs to the script itself rather than to one object made from it, so it cannot use instance members or self, only its arguments, constants and static variables.
- variadic function
- A function that takes any number of arguments, such as print() and max(). A GDScript function becomes one with a rest parameter, in Godot 4.5 and later.
References
- GDScript reference: functions, static functions, variadic functions and scope (Godot Engine)
- Static typing in GDScript: return types and void (Godot Engine)
- @GlobalScope class reference: global functions (Godot Engine)
- Callable class reference: call, callv and bind (Godot Engine)
- ProjectSettings class reference: shadowed_variable and shadowed_global_identifier (Godot Engine)
- Godot 4.5 release page (variadic arguments) (Godot Engine)
- Godot 4.3 release page (built-in functions as Callables) (Godot Engine)
Report a problem with this lesson
Kept only in this browser. Your Learn progress