Section 2: Player Movement

In this section, we reviewed some fundamental C# concepts while setting up the player for our game.

Intro - Player Movement

This lecture does not contain notes.

Adding Player Animations

In this lecture, we added animations to the player. During this process, we established a few rules regarding scenes.

  1. Node names will be Pascal cased.

  2. Scene files will remain snake-cased as recommended by Godot.

  3. All scenes will be saved in a folder called Scenes.

Other than that, we also added animations. Refer to the video on how animations are set up for the player.

Even though characters will be using 2D sprites, it's recommended to use 3D nodes for the sprites to be able to interact with 3D objects. DO NOT use the CharacterBody2D node. Otherwise, the game will not behave as expected.

Attaching Scripts to Nodes

In this lecture, we learned how to attach a C# script to a node in Godot. There are a few conventions we'll be following in this course.

  1. All C# scripts will be saved in a folder called Scripts.

  2. C# files will be Pascal cased.

  3. If a C# script is attached to a scene, the paths should reflect one another. For example, if a scene saved in the Scenes/Characters/Player has a script, the script should be saved in the Scripts/Characters/Player folder. Not required, but recommended.

We have two scenes. One is for the level called Main, and another is for the player called Player. The Main scene uses a Node3D node, and the Player scene should use the CharacterBody3D node since we'll be using physics to move the player around.

Configuring Visual Studio Code

In this lecture, we configured Visual Studio Code by installing an extension called. A link for this extension can be found here: https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit

In addition, we installed a theme called Tokyo Night, which can also be found here: https://marketplace.visualstudio.com/items?itemName=enkia.tokyo-night

Of course, you may prefer a different flavor of theme. Check out the resource section of this lecture for a site that can make it easier to browse various themes available for Visual Studio Code.

Resources

Overriding Methods

In this lecture, we learned about overriding methods. Godot's classes define methods that we can override, such as the _PhysicsProcess or _Input method. You can determine is overridable by checking where the method was originally defined and checking if it has the word Virtual next to it like in the screenshot below:

When using Godot's methods or properties, the documentation uses the GDScript naming convention. However, for C# users, names are converted into pascal case. So, _physics_process is actually written as _PhysicsProcess in C#. Keep that in mind as Godot doesn't provide many of its C# counterparts.

Overriding a method can be done by adding the override keyword after the accessor like so:

public override void _PhysicsProcess(double delta)
{
    GD.Print("Player physics process");
}

In addition to overriding methods, we learned about the GD class, which acts as a container for any other functions that don't belong to a node, such as the Print() method for printing text onto the output of the Godot editor.

Resources

Moving the Player

In this lecture, we learned how to move the player with the CharacterBody3D methods. Firstly, we had to set up the actions for our project. The following keys were bound in the input map:

  • MoveForward = W

  • MoveLeft = A

  • MoveBackward = S

  • MoveRight = D

Next, we had to generate a vector for determining where the player should be moved. Luckily, Godot has a method called Input.GetVector() which uses our actions to determine the vector value.

public override void _Input(InputEvent @event)
{
    direction = Input.GetVector(
        "MoveLeft",
        "MoveRight",
        "MoveForward",
        "MoveBackward"
    );
}

The GetVector() method accepts the action for moving the player left, right, forward and backward in that order. This method returns the value as a Vector2 type, which we store in a class variable called direction.

Something to keep in mind is that the _Input() method gets called when a key is pressed AND released. So, the vector will get reset to a vector zero when the player releases a key, which is what we want to stop the player from moving when the key is no longer being pressed.

Once we have the direction to player, we updated the _PhysicsProcess method by changing the Velocity property defined on the CharacterBody3D node. During this process, we have to convert the Vector2 type to Vector3. The Vector2.X is mapped to Vector3.X and the Vector2.Y is mapped to Vector3.Z. As for Vector3.Y, that is set to 0, as that'll move the player up and down in the air.

public override void _PhysicsProcess(double delta)
{
    Velocity = new(direction.X, 0, direction.Y);
    Velocity *= 5;

    MoveAndSlide();
}

Lastly, we multipled the velocity by 5 so that the player moves faster and then called the MoveAndSlide() method to tell the CharacterBody3D node to begin moving the player. Simply setting the velocity does not move the player.

During this process, we do not need to normalize the Vector2 from the GetVector() method since it's already normalized, and we don't have to apply delta to the velocity since the MoveAndSlide() method already does so for us, too.

Exporting Fields with Attributes

In this lecture, we decided to export fields from the Player class so that we can reference child nodes in the Player scene. Fields can be exported by using the Export attribute. Attributes are a feature in C# to communicate mainly with external programs, such as Godot. In the case of the Export attribute, we are telling it which fields/properties can be edited directly from the godot editor.

[Export] private AnimationPlayer animPlayerNode;
[Export] private Sprite3D spriteNode;

In the example above, we exported fields for storing the AnimationPlayer and Sprite3D nodes. The class for a node can be found by hovering your mouse over the Node like so:

After adding those fields, we must build our scripts before they'll appear in the editor by pressing the Build button in the top right corner.

Playing Animations

In this lecture, we learned how to play animations with the AnimationPlayer class. We can use the Play() method and provide the name of the animation (case-sensitive). For example, to play the idle animation, we would use the following code:

animPlayerNode.Play("Idle");

We're playing the idle animation when the player is ready from the _Ready method. However, we want to change the animation when the player moves or stops moving. We updated the _Input method by adding the following conditional statements:

if (direction == Vector2.Zero)
{
    animPlayerNode.Play("Idle");
}
else
{
    animPlayerNode.Play("Move");
}

In this example, we're checking if the player is moving by checking the direction variable. If it's Vector2.Zero, this means there is no movement. In that instance, we play the idle animation. Otherwise, we play the move animation.

Game Constants

In this lecture, we created a class for holding strings to reduce the likelihood of typos throughout our program. We outsourced the strings for our input actions and animations called GameConstants like so:

public class GameConstants
{
    // Animations
    public const string ANIM_IDLE = "Idle";
    public const string ANIM_MOVE = "Move";

    // Input
    public const string INPUT_MOVE_LEFT = "MoveLeft";
    public const string INPUT_MOVE_RIGHT = "MoveRight";
    public const string INPUT_MOVE_FORWARD = "MoveForward";
    public const string INPUT_MOVE_BACKWARD = "MoveBackward";
}

Constants are a feature in C# for variables that once they've been initialized with a value, they can never be modified. One of the benefits of using a constant is that we don't have to have an instance of a class to use the value. So, we can update the Player.cs file to use our constants instead of plain strings like so:

animPlayerNode.Play(GameConstants.ANIM_IDLE);

We also used these constants in the GetVector method.

direction = Input.GetVector(
  GameConstants.INPUT_MOVE_LEFT,
  GameConstants.INPUT_MOVE_RIGHT,
  GameConstants.INPUT_MOVE_FORWARD,
  GameConstants.INPUT_MOVE_BACKWARD
);

Flipping the Player

In this lecture, we learned how to flip the player through their sprite node. Sprites have a property called Flip H for flipping a sprite horizontally. We want to flip the sprite based on the direction the player is facing. We defined a method called Flip in the Player.cs to tackle this problem.

private void Flip()
{
    bool isNotMovingHorizontally = Velocity.X == 0;

    if (isNotMovingHorizontally) { return; }

    bool isMovingLeft = Velocity.X < 0;

    spriteNode.FlipH = isMovingLeft;
}

We're doing three things in this snippet of code.

  1. We're checking if the player is moving horizontally to begin with. If they're not, we shouldn't bother flipping them.

  2. If they are moving horizontally, we check if the Velocity.X property is negative or positive. Negative means they're moving left. Positive means they're moving right.

  3. Based on that information, we set the FlipH property to the isMovingLeft variable.

After defining this method, we're calling this method from the _PhysicsProcess method after changing the velocity since we only want to flip the player after their velocity has changed.

Optimizing the Sprites

In this lecture, we learned how to optimize the sprites in Godot. There are no notes for this lecture. It's meant to be watched via video.

Partial Classes

In this lecture, we learned why it's necessary to add the partial keyword to our classes. If we ever inherit from any of Godot's classes, such as the CharacterBody3D class, the derived class must be marked as partial. This is because Godot performs code analysis on our code and injects additional code. The partial keyword permits Godot to inject more code into our classes.

Partial classes are also helpful in splitting large classes into multiple classes. However, it's typically recommended to use inheritance or composition. In most cases, partial classes are mostly used with source generation.

Last updated