Godot 101 - Part 5: Player-controlled Sprite

Tags: godot tutorial gamedev

This is part 5 of “Godot 101”. In this installment, we’ll learn how to use player input to move a sprite around the screen. If you haven’t already read through the previous parts, please start with Part 1.

About this series

Godot 101 is an introduction to the Godot game engine and how it works. If you’ve never used a game engine before, or if you’re just new to Godot, this is the place to start. If you’re new here, a quick note about this website: we’re called KidsCanCode because we teach programming and game development to kids, but if you’re an adult you’re welcome here, too. We don’t believe in dumbing-down the material for kids, and game development is hard - so it will probably be challenging no matter what your age.

You can watch a video version of this lesson here:

Player Scene

Click here to download a zip of the art we’re going to use for this lesson. Unpack it in your project folder.

First let’s create a scene for our player. What node should we choose for our root node? It depends on what kind of collisions and physics we need. If you click on Add Node and search for “collision” you will see there is a CollisionObject2D that has a few children:

These nodes are all for implementing different kinds of collision and physics in your game - which one you choose depends on your goal. A brief summary:

This node is a general purpose area detection node. It can detect when another object enters or leaves its area but doesn’t have any physics properties. This is great for when you simply need to know when one object overlaps another.

This node is for what is sometimes called “arcade physics” (hence the little Space Invaders icon). It is not affected by physics (gravity, etc.) but will collide with other bodies. This is good for player controlled bodies that need collision - for example, a platform character that doesn’t fall through the ground.

This is the node to use for full-on physics simulation. Objects of this type will behave realistically when interacting with the environment. You would use this, for example, if you were making an Angry Birds style game and wanted stacks of blocks that would tumble down when hit.

This is a static physics body, meaning it is designed to represent non-moving objects like walls, trees, etc. - anything that will not be moving around.

For this particular project, we’re going to stick with the Area2D for our player. Add one to the scene and name it “player”.

By itself, collision nodes are not visible, so we need to add a Sprite to display our character’s image. In the “art/Player” folder are the images for our character. Drag the standing image into the node’s Texture field:

Click to enlarge

Now let’s scale the sprite down a little and make sure it’s centered on the parent node by setting the following values in the Inspector:

    Scale: (0.6, 0.6)
    Offset: (0, -64)

Collision Shapes

If we stopped here, we wouldn’t actually have any collision detetion, because any CollisionObject2D must have a collision shape defined. We’re going to use a CollisionShape2D. Add it as a child of player and select a RectangleShape2D

Click to enlarge

Now we need to adjust our collision shape to define the collision area. Important: do not drag the scale handles. Scaling a physics object will not work and may break things. Use the inner handles to adjust the extents of the rectangle:

Click to enlarge

Input Mapping

We are going to use the arrow keys to move our player. In Godot, all the player input functions are described under Scene -> Project Settings -> Input Map:

Click to enlarge

In this window, you can set up whatever input actions you want to use, and assign various controls to them - keys, mouse buttons, game pads, etc. For this demo, we’re going to stick with the already-defined actions for the four arrow keys: "ui_up", "ui_left", "ui_right", and "ui_down".

Player Script

Now, let’s add a script to the player node:

extends Area2D

export var speed = 400

var vel = Vector2()

func _ready():
	set_fixed_process(true)

func _fixed_process(delta):
	set_pos(get_pos() + vel * delta)
 

First, we’re creating a variable to set our player’s speed. The export keyword does something special: save this script, then click on the player node and look at the Inspector. Now we can change the value of speed directly in the Inspector!

The movement is going to be similar to how we did it in the previous lesson - we move the position by adding vel * delta. But our sprite won’t move, because we need to detect the keys:

func _fixed_process(delta):
	var input = Vector2(0, 0)
	input.x = Input.is_action_pressed("ui_right") - Input.is_action_pressed("ui_left")
	input.y = Input.is_action_pressed("ui_down") - Input.is_action_pressed("ui_up")
	vel = input.normalized() * speed
	set_pos(get_pos() + vel * delta)
 

Here, we’re creating an input vector to hold the direction the player is holding - a combination of whatever keys are pressed. Input.is_action_pressed() will return 1 or 0, so we can get the total movement in each axis by subtracting the negative direction key from the positive one.

Once we have the input vector, we must normalize() it so that (1, 1) is not faster than (1, 0).

extends Area2D

export var speed = 400

var screensize
var extents
var vel = Vector2()

func _ready():
        set_fixed_process(true)
        screensize = get_viewport_rect().size
        extents = get_node("collision").get_shape().get_extents()
        set_pos(screensize / 2)

func _fixed_process(delta):
        var input = Vector2(0, 0)
        input.x = Input.is_action_pressed("ui_right") - Input.is_action_pressed("ui_left")
        input.y = Input.is_action_pressed("ui_down") - Input.is_action_pressed("ui_up")
        vel = input.normalized() * speed
        var pos = get_pos() + vel * delta
        pos.x = clamp(pos.x, extents.width, screensize.width - extents.width)
        pos.y = clamp(pos.y, extents.height, screensize.height - extents.height)
        set_pos(pos)
 

Wrapping Around

Finally, we’ve copied-and-pasted from the last lesson the _ready() code to grab the sizes of the screen and collision shape. Then we’re using those to limit the movement of the sprite to a minimum and maximum value (this is what clamp() does).

Wrapping Up

That will do it for our player’s movement. In the next part, we’ll put some items on the screen for our player to collect and explore how to detect collisions with an Area2D node.

Full code for this part

Godot 101 - Part 6

Comments