Creating a game with Godot Engine - Ep.5 - Player's character
In this fifth article we will talk about creating a character for the player and different Godot features you can use to create your gameplay. First, we will see how you can move a character around using the keyboard. Then, we will talk about lights. We will finish by looking at camera animation. (source code)
Disclaimer: the following article represents one way to do it and, it may not be the best way. Please adapt this technique using your best judgement.
This is probably one of the first thing you will do when creating a game. You have to choose what the player's character will be and what it will be able to do. Taking the time to do so is important. The character is the way the player gets the feel of the gameplay.
As established before, each entity of the game is represented by a scene. Here is what constitute the character scene:
- root: this is a KinematicBody2D node. It is the best way to create a movable entity which is able to interact with the physic engine.
- player: this is a Sprite node. It is simply the image representing the character.
- collision: this is a CollisionShape2D node. It is the shape which represents the character in the physic engine. In games, most of the time the physical shape is different from the graphical representation. Mostly because it is faster for the physic engine to work with basic shape, like box or circle. Also, because it is easier for the developer to adjust the gameplay to such shape too.
- flash_light: this is a Light2D node. It is directional light used as an extra gameplay feature.
- surrounding_light: this is a Light2D node. It is used to create a halo of light around the character to increase the visibility of the immediate environment.
- camera: this is a Camera2D node. It is the camera used to follow the player.
- camera_anim: this is a AnimationPlayer node. It is used to animate the camera.
# Contains the player logic extends KinematicBody2D const MOTION_SPEED = 250 # Pixels/second signal killed() var flash_light var player var camera_anim var killed = false func _ready(): flash_light = find_node("flash_light") player = find_node("player") camera_anim = find_node("camera_anim") set_fixed_process(true) set_process_input(true) func killed(): if(!killed): killed = true flash_light.set_enabled(false) player.set_modulate(Color(1, 0, 0)) camera_anim.play("shaking") set_fixed_process(false) set_process_input(false) emit_signal("killed") func _fixed_process(delta): var motion = Vector2() if (Input.is_action_pressed("move_up")): motion += Vector2(0, -1) if (Input.is_action_pressed("move_down")): motion += Vector2(0, 1) if (Input.is_action_pressed("move_left")): motion += Vector2(-1, 0) if (Input.is_action_pressed("move_right")): motion += Vector2(1, 0) motion = motion.normalized()*MOTION_SPEED*delta move(motion) if(is_colliding()): var collider = get_collider() if("box" in collider.get_groups()): collider.move(get_collision_normal()*-1) else: var n = get_collision_normal() motion = n.slide(motion) move(motion) _update_flash_light_rot() func _input(event): if(event.type == InputEvent.MOUSE_MOTION): _update_flash_light_rot() func _update_flash_light_rot(): var mouse_vec = get_local_mouse_pos().normalized() flash_light.set_rot(Vector2(-mouse_vec.y, mouse_vec.x).angle())
As established before, we attach a script to the root node of the scene to create the logic. We will go through the code in details in the corresponding sections below.
In the _ready function we simply get the nodes that we will need in the rest of the script. Also we tell Godot that we want to have access to the fixed process and the input events, more details later.
Contrary to what you might believe, moving the character is not done on input events but in the fixed process. This is because we want a physically based interaction, and the _fixed_process function is where it is the most suitable to update entity existing in the physic engine. Hopefully, with Godot, it does not make it more difficult, I would even say it makes it easier.
At the beginning of the script, it might be a good idea to use a constant to determine the speed of character. You can even want a to make it a variable, if you want the speed to change on certain conditions. In _fixed_process, we start by declaring a Vector2 variable which will store the final motion of the character. Then we check for each of the four directions which one is activated, and add the corresponding motion to the final motion. Please note that you have to use if conditions and not else if conditions, otherwise the character will not be able to move in two directions at the same time, like up and left.
The final calculation of the motion is going like this:
- We normalize the input vector to avoid the character to move quicker diagonally.
- We multiply by the desired speed we want.
- We multiply by the _fixed_process delta to make the speed dependent of time (pixels/seconds) and not the framerate.
To get the character to move, we call the move function using the final motion vector.
The rest of _fixed_process has to do with two things which involve a collision:
- As a gameplay feature, we want the character to be able to move box around. If there is a collision, we check if the collider is of the group "box". If true, we move the collider using the opposite vector of the collision vector, resulting, visually, as if the character is pushing the box.
- If there is a collision with other elements of the level, you might don't want your character to be stuck in them. In this case we call the slide function using the collision normal and the final motion. This results as if the character is sliding against walls and other elements.
Adding lighting with Godot is easy but it requires some preparatory work. You have to create an image in the shape of the light you want to create, like the example above for the flash light. I would suggest to create a transparent PNG with the light in white, using some gradient or blur effects to create light fading. Using white makes it easier later to change color of the light directly into Godot.
The good thing with Godot is when you place your light as a child of another, the light will automatically follow the movement. If you want to direct the light using the mouse requires writing a bit of code, but is quite simple. Just take a look at the _update_flash_light_rot function. The first line we get the position of the mouse relative to the character. Then we just have to use this vector with the set_rot and the angle functions. However, there is two tricks to make it work properly:
- You have to offset the light so the center of rotation is not centered on the light but centered on the character, look in the node Inspector for the Offset property of Light2D.
- You have to reverse the mouse vector and negate the y axis to match the coordinate system of the angle function.
Finally, we have to call the _update_flash_light_rot function in the appropriate cases. The first one is in case of a mouse motion event, look into the _input function. The second case is _fixed_process at the end to make sure the direction is correctly updated when the character moves.
The first case of camera animation is to follow the character. Hopefully, when you place the camera as a child of the character, the camera will automatically follow the character. On thing you might want to look into is the Drag Margin properties of the Camera2D. You probably want to enable this feature and use a value like 0.2. What it does, it makes the camera movement a lot smoother as it does not follow the character exactly, but only when it moves more than the given margin.
The second is that I added a camera shaking animation upon death, to give a better feedback to the player. For this we use an AnimationPlayer node. Using the Godot's Animation tool, we add a new animation and call it "shaking". Then we add a track for the offset property of the camera. We want this animation to be very short, so we set the length to 0.5, and the step to to 0.1 to have a sufficient number of key values. Then for each key value, we move the offset randomly. To play the animation, we have to call the play function in the killed function.
Note: if your camera does not appear to follow the character, make sure the Current of Camera2D property is enabled.
What is next?
In the next article we will speak about creating floors and walls.
Written by Olivier on Thursday November 10, 2016