Coding the player¶
In this lesson, we'll add player movement, animation, and set it up to detect collisions.
To do so, we need to add some functionality that we can't get from a built-in
node, so we'll add a script. Click the Player
node and click the "Attach
Script" button:

Skriptin asetukset -ikkunassa voit antaa oletusasetuksien olla sellaisenaan. Napsauta vain "Luo":
Muista
Jos olet luomassa C#-skriptiä tai muun kielistä skriptiä, valitse kieli kieli-pudotusvalikosta ennen kuin huitaiset luontinappia.

Muista
If this is your first time encountering GDScript, please read Scripting languages before continuing.
Aloita luomalla ne jäsenmuuttujat, jotka tämä objekti tulee tarvitsemaan:
extends Area2D
export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
using Godot;
using System;
public class Player : Area2D
{
[Export]
public int Speed = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // Size of the game window.
}
// A `player.gdns` file has already been created for you. Attach it to the Player node.
// Create two files `player.cpp` and `player.hpp` next to `entry.cpp` in `src`.
// This code goes in `player.hpp`. We also define the methods we'll be using here.
#ifndef PLAYER_H
#define PLAYER_H
#include <AnimatedSprite.hpp>
#include <Area2D.hpp>
#include <CollisionShape2D.hpp>
#include <Godot.hpp>
#include <Input.hpp>
class Player : public godot::Area2D {
GODOT_CLASS(Player, godot::Area2D)
godot::AnimatedSprite *_animated_sprite;
godot::CollisionShape2D *_collision_shape;
godot::Input *_input;
godot::Vector2 _screen_size; // Size of the game window.
public:
real_t speed = 400; // How fast the player will move (pixels/sec).
void _init() {}
void _ready();
void _process(const double p_delta);
void start(const godot::Vector2 p_position);
void _on_Player_body_entered(godot::Node2D *_body);
static void _register_methods();
};
#endif // PLAYER_H
Käyttäen export
avainsanaa ensimmäiselle muuttujalle speed
antaa meidän asettaa sen arvon Tarkastelijassa. Se voi olla kätevää arvoille, joita haluat säätää kuin ne olisivat solmun valmiita ominaisuuksia. Napsauta Player
solmua ja aseta nopeusominaisuudeksi 400
.
Varoitus
If you're using C#, you need to (re)build the project assemblies whenever you want to see new export variables or signals. This build can be manually triggered by clicking the word "Mono" at the bottom of the editor window to reveal the Mono Panel, then clicking the "Build Project" button.

_ready()
funktiota kutsutaan, kun solmu lisätään skenepuuhun, mikä on hyvä hetki hakea peli-ikkunan koko:
func _ready():
screen_size = get_viewport_rect().size
public override void _Ready()
{
ScreenSize = GetViewportRect().Size;
}
// This code goes in `player.cpp`.
#include "player.hpp"
void Player::_ready() {
_animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
_collision_shape = get_node<godot::CollisionShape2D>("CollisionShape2D");
_input = godot::Input::get_singleton();
_screen_size = get_viewport_rect().size;
}
Nyt voimme käyttää _process()
funktiota määrittelemään mitä pelaaja tekee. _process()
funktiota kutsutaan jokaisella ruudunpäivityksellä, joten käytämme sitä päivittämään pelimme niitä osasia joiden odotamme muuttuvan usein. Pelaajan osalta teemme siinä seuraavaa:
Tarkista syöte.
Liiku annettuun suuntaan.
Toista asiaankuuluva animaatio.
First, we need to check for input - is the player pressing a key? For this game, we have 4 direction inputs to check. Input actions are defined in the Project Settings under "Input Map". Here, you can define custom events and assign different keys, mouse events, or other inputs to them. For this game, we will map the arrow keys to the four directions.
Click on Project -> Project Settings to open the project settings window and
click on the Input Map tab at the top. Type "move_right" in the top bar and
click the "Add" button to add the move_right
action.

We need to assign a key to this action. Click the "+" icon on the right, then click the "Key" option in the drop-down menu. A dialog asks you to type in the desired key. Press the right arrow on your keyboard and click "Ok".

Repeat these steps to add three more mappings:
move_left
mapped to the left arrow key.move_up
mapped to the up arrow key.And
move_down
mapped to the down arrow key.
Your input map tab should look like this:

Click the "Close" button to close the project settings.
Muista
We only mapped one key to each input action, but you can map multiple keys, joystick buttons, or mouse buttons to the same input action.
You can detect whether a key is pressed using Input.is_action_pressed()
,
which returns true
if it's pressed or false
if it isn't.
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed("move_right"):
velocity.x += 1
if Input.is_action_pressed("move_left"):
velocity.x -= 1
if Input.is_action_pressed("move_down"):
velocity.y += 1
if Input.is_action_pressed("move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite.play()
else:
$AnimatedSprite.stop()
public override void _Process(float delta)
{
var velocity = Vector2.Zero; // The player's movement vector.
if (Input.IsActionPressed("move_right"))
{
velocity.x += 1;
}
if (Input.IsActionPressed("move_left"))
{
velocity.x -= 1;
}
if (Input.IsActionPressed("move_down"))
{
velocity.y += 1;
}
if (Input.IsActionPressed("move_up"))
{
velocity.y -= 1;
}
var animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite.Play();
}
else
{
animatedSprite.Stop();
}
}
// This code goes in `player.cpp`.
void Player::_process(const double p_delta) {
godot::Vector2 velocity(0, 0);
velocity.x = _input->get_action_strength("move_right") - _input->get_action_strength("move_left");
velocity.y = _input->get_action_strength("move_down") - _input->get_action_strength("move_up");
if (velocity.length() > 0) {
velocity = velocity.normalized() * speed;
_animated_sprite->play();
} else {
_animated_sprite->stop();
}
}
We start by setting the velocity
to (0, 0)
- by default, the player
should not be moving. Then we check each input and add/subtract from the
velocity
to obtain a total direction. For example, if you hold right
and
down
at the same time, the resulting velocity
vector will be (1, 1)
.
In this case, since we're adding a horizontal and a vertical movement, the
player would move faster diagonally than if it just moved horizontally.
We can prevent that if we normalize the velocity, which means we set its
length to 1
, then multiply by the desired speed. This means no more fast
diagonal movement.
Vihje
Jos et ole koskaan käyttänyt vektorimatematiikkaa, tai tarvitset kertausta, voit katsoa selityksen Godotin vektorien käytöstä: Vector math. Se on hyvä osata, mutta ei ole välttämätön oppaan loppuosan kannalta.
We also check whether the player is moving so we can call play()
or
stop()
on the AnimatedSprite.
Vihje
$
is shorthand for get_node()
. So in the code above,
$AnimatedSprite.play()
is the same as
get_node("AnimatedSprite").play()
.
In GDScript, $
returns the node at the relative path from the
current node, or returns null
if the node is not found. Since
AnimatedSprite is a child of the current node, we can use
$AnimatedSprite
.
Now that we have a movement direction, we can update the player's position. We
can also use clamp()
to prevent it from leaving the screen. Clamping a
value means restricting it to a given range. Add the following to the bottom of
the _process
function (make sure it's not indented under the else):
position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)
Position += velocity * delta;
Position = new Vector2(
x: Mathf.Clamp(Position.x, 0, ScreenSize.x),
y: Mathf.Clamp(Position.y, 0, ScreenSize.y)
);
godot::Vector2 position = get_position();
position += velocity * (real_t)p_delta;
position.x = godot::Math::clamp(position.x, (real_t)0.0, _screen_size.x);
position.y = godot::Math::clamp(position.y, (real_t)0.0, _screen_size.y);
set_position(position);
Vihje
delta`parametri `_process() funktiossa viittaa ruudun (animaatiossa) pituuteen -aikaan joka kului edellisen ruudun suorittamiseen. Tätä arvoa käyttämällä varmistutaan että liike on säännönmukainen vaikka ruudun päivitysnopeus muuttuisi.
Click "Play Scene" (F6, Cmd + R on macOS) and confirm you can move the player around the screen in all directions.
Varoitus
If you get an error in the "Debugger" panel that says
Attempt to call function 'play' in base 'null instance' on a null
instance
this likely means you spelled the name of the AnimatedSprite node
wrong. Node names are case-sensitive and $NodeName
must match
the name you see in the scene tree.
Animaatioiden valinta¶
Now that the player can move, we need to change which animation the
AnimatedSprite is playing based on its direction. We have the "walk" animation,
which shows the player walking to the right. This animation should be flipped
horizontally using the flip_h
property for left movement. We also have the
"up" animation, which should be flipped vertically with flip_v
for downward
movement. Let's place this code at the end of the _process()
function:
if velocity.x != 0:
$AnimatedSprite.animation = "walk"
$AnimatedSprite.flip_v = false
# See the note below about boolean assignment.
$AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite.animation = "up"
$AnimatedSprite.flip_v = velocity.y > 0
if (velocity.x != 0)
{
animatedSprite.Animation = "walk";
animatedSprite.FlipV = false;
// See the note below about boolean assignment.
animatedSprite.FlipH = velocity.x < 0;
}
else if (velocity.y != 0)
{
animatedSprite.Animation = "up";
animatedSprite.FlipV = velocity.y > 0;
}
if (velocity.x != 0) {
_animated_sprite->set_animation("walk");
_animated_sprite->set_flip_v(false);
// See the note below about boolean assignment.
_animated_sprite->set_flip_h(velocity.x < 0);
} else if (velocity.y != 0) {
_animated_sprite->set_animation("up");
_animated_sprite->set_flip_v(velocity.y > 0);
}
Muista
The boolean assignments in the code above are a common shorthand for programmers. Since we're doing a comparison test (boolean) and also assigning a boolean value, we can do both at the same time. Consider this code versus the one-line boolean assignment above:
if velocity.x < 0:
$AnimatedSprite.flip_h = true
else:
$AnimatedSprite.flip_h = false
if (velocity.x < 0)
{
animatedSprite.FlipH = true;
}
else
{
animatedSprite.FlipH = false;
}
Play the scene again and check that the animations are correct in each of the directions.
Vihje
A common mistake here is to type the names of the animations wrong. The
animation names in the SpriteFrames panel must match what you type in
the code. If you named the animation "Walk"
, you must also use a
capital "W" in the code.
When you're sure the movement is working correctly, add this line to
_ready()
, so the player will be hidden when the game starts:
hide()
Hide();
hide();
Törmäyksiin valmistautuminen¶
Haluamme, että Player
havaitsee milloin vihollinen on törmännyt siihen, mutta emme ole tehneet vielä vihollisia! Se on OK, koska aiomme laittaa sen toimimaan Godotin signal toiminnallisuudella.
Add the following at the top of the script, after extends Area2D
:
signal hit
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void Hit();
// This code goes in `player.cpp`.
// We need to register the signal here, and while we're here, we can also
// register the other methods and register the speed property.
void Player::_register_methods() {
godot::register_method("_ready", &Player::_ready);
godot::register_method("_process", &Player::_process);
godot::register_method("start", &Player::start);
godot::register_method("_on_Player_body_entered", &Player::_on_Player_body_entered);
godot::register_property("speed", &Player::speed, (real_t)400.0);
// This below line is the signal.
godot::register_signal<Player>("hit", godot::Dictionary());
}
Tämä määrittelee mukautetun signaalin nimeltä "hit", jonka laitamme pelaajamme lähettämään, kun se törmää viholliseen. Käytämme Area2D
solmua tunnistamaan törmäyksen. Valitse Player
solmu ja napsauta "Solmu" välilehteä Tarkastelijan vieressä nähdäksesi listan signaaleista, joita pelaaja voi lähettää:

Notice our custom "hit" signal is there as well! Since our enemies are going to
be RigidBody2D
nodes, we want the body_entered(body: Node)
signal. This
signal will be emitted when a body contacts the player. Click "Connect.." and
the "Connect a Signal" window appears. We don't need to change any of these
settings so click "Connect" again. Godot will automatically create a function in
your player's script.

Note the green icon indicating that a signal is connected to this function. Add this code to the function:
func _on_Player_body_entered(body):
hide() # Player disappears after being hit.
emit_signal("hit")
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred("disabled", true)
public void OnPlayerBodyEntered(PhysicsBody2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(nameof(Hit));
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
}
// This code goes in `player.cpp`.
void Player::_on_Player_body_entered(godot::Node2D *_body) {
hide(); // Player disappears after being hit.
emit_signal("hit");
// Must be deferred as we can't change physics properties on a physics callback.
_collision_shape->set_deferred("disabled", true);
}
Joka kerta kun vihollinen osuu pelaajaan, signaali välitetään. Meidän täytyy poistaa käytöstä pelaajan törmäys ettei se laukaise enää hit
signaalia enempää kuin kerran.
Muista
Disabling the area's collision shape can cause an error if it happens
in the middle of the engine's collision processing. Using
set_deferred()
tells Godot to wait to disable the shape until it's
safe to do so.
The last piece is to add a function we can call to reset the player when starting a new game.
func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false
public void Start(Vector2 pos)
{
Position = pos;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
// This code goes in `player.cpp`.
void Player::start(const godot::Vector2 p_position) {
set_position(p_position);
show();
_collision_shape->set_disabled(false);
}
With the player working, we'll work on the enemy in the next lesson.