2. Read a real script
5 minFollow a working GDScript file line by line.
The script is the node
There is no component to add. This file is attached to a CharacterBody2D and becomes its behaviour, so velocity, is_on_floor() and move_and_slide() are simply available.
Coming from Unity: a MonoBehaviour plus GetComponent<CharacterController>() collapsed into one thing.
A GDScript file is attached to one node and becomes its behaviour. Step through two scripts that ship in real projects: the player controller and a coin pickup. Each group of lines is explained in place, with the Unity or Unreal habit it replaces.
Coming from Indentation is the block, := infers the type, and there are no semicolons. If you can read Python you can read this.
To finish: Step through every part of one script.
Notes
The written version of this lesson, for after you have done it.A GDScript file starts with extends and the node type it belongs to, and from that line on it can use everything that node type has. That is why player.gd can call move_and_slide() with nothing imported: the script is the CharacterBody2D.
The two callbacks that matter first are _ready(), which runs once when the node enters the tree, and _physics_process(delta), which runs on the fixed physics tick. Setup goes in the first; anything that touches velocity or collision goes in the second. Visual-only work like animation belongs in _process(delta), the render frame.
Prefer := over = when declaring variables. The colon-equals form infers a type from the first value and keeps it, so a float stays a float and the editor can autocomplete on it. The plain form creates a Variant that will accept anything later and fail somewhere far from the cause.
Try it in the editor five minutes, in a real project
- Select the Player root from lesson 1 and click Attach Script in the Inspector. Keep the default name player.gd.
- Replace the template with the player.gd script from this lesson. Read the two @export lines, then look at the Inspector: speed and jump_force are now editable fields.
- Add three actions in Project Settings under Input Map: move_left, move_right and jump. Bind A, D and Space.
- Add a StaticBody2D floor with a CollisionShape2D under the scene, press F6 to run the current scene, and move with A and D. Change speed in the Inspector and run again.
player.gd Twenty lines that move a character, jump, and collide. This is the first script most people write in Godot and it touches every idea that matters.
- The script is the node lines 1
- There is no component to add. This file is attached to a CharacterBody2D and becomes its behaviour, so velocity, is_on_floor() and move_and_slide() are simply available. Unity: a MonoBehaviour plus GetComponent<CharacterController>() collapsed into one thing.
- Exported values live in the Inspector lines 3–4
- @export shows the value in the Inspector so a designer tunes it without opening the script. := infers the type, so speed is a float from now on and will refuse a string later. Unity: [SerializeField]. Unreal: UPROPERTY(EditAnywhere).
- Read the project setting once lines 6
- Gravity comes from Project Settings so every body in the game agrees on it. This is a plain var, not exported: it is internal state, not something to tune per instance. Unity: Physics2D.gravity.
- The physics tick lines 8
- _physics_process runs at a fixed rate, 60 ticks per second by default and configurable in Project Settings. Update character velocity and call move_and_slide here so movement follows the physics step. Unity: FixedUpdate. Unreal: the physics tick.
- Gravity is just velocity lines 9–10
- velocity is a built-in property of the body. Each tick you add gravity scaled by delta, the time since the last tick, so the fall feels the same at any frame rate.
- A one-shot action, only on the floor lines 12–14
- is_action_just_pressed is true for exactly one frame, which is what a jump wants. is_on_floor() is only accurate after the previous move_and_slide() call, which is why the order in this function matters. Unity: Input.GetButtonDown plus a grounded check.
- An axis from two named actions lines 16–18
- get_axis subtracts the strength of the first action from the second, giving a value from -1 to 1. Keyboard input usually gives -1, 0 or 1; an analog stick can give values in between. Named actions let you remap controls without changing this line. Unity: Input.GetAxis("Horizontal"). Unreal: an Enhanced Input axis value.
- Let the engine move it lines 20
- move_and_slide() reads velocity, moves the body, resolves collisions, slides along walls and floors, and updates is_on_floor(). Nothing moves without this call. Unity: CharacterController.Move with the slope handling built in.
coin.gd A coin that notices the player, announces it was collected, and removes itself. It never touches the score; it only tells whoever is listening.
- Detect, do not block lines 1
- Area2D notices bodies entering its shape without stopping them. It needs a CollisionShape2D child in the scene, or body_entered never fires. Unity: a Collider2D with isTrigger on. Unreal: a TriggerBox.
- Declare what the coin can announce lines 3
- A signal is a named event the node can emit. The coin declares that it can be collected and what it will pass along. It does not know or care who is listening. Unity: a C# event or UnityEvent. Unreal: an event dispatcher.
- A value per instance lines 5
- Because value is exported, every coin placed in a level can carry its own number in the Inspector while sharing this one script.
- Wire the built-in signal to a method lines 7–8
- _ready runs once when the node enters the tree. body_entered is a signal Area2D already has; connect it to the method that should run. The same connection can also be made in the editor's Node dock. Unity: OnTriggerEnter2D, but chosen explicitly rather than by method name.
- Check who arrived lines 10–12
- The handler receives the body that entered. Groups are Godot's tags: a node can be in many, and is_in_group is the standard way a pickup checks for the player. Unity: CompareTag("Player").
- Announce, do not act lines 13
- emit sends the value to connected listeners. A level script can update the score and an audio script can play a chime. Without a connection, neither happens: the coin only announces the event.
- Leave cleanly lines 14–15
- Physics state must not change in the middle of a physics callback, so monitoring is switched off with set_deferred. queue_free removes the node at the end of the frame, not instantly. Unity: Destroy(gameObject), which is also deferred.