This is the eight part of the tutorial creating platformer game on Godot Engine. In this tutorial, we will create interaction between player and enemy.

  1. Part 1 : Preparation
  2. Part 2 : Player Creation
  3. Part 3 : Player Creation 2
  4. Part 4 : Tilemap and Camera
  5. Part 5 : Player Animation
  6. Part 6 : Parallax Background and Level Bounds
  7. Part 7 : Character Controller and Enemy
  8. Part 8 : Interaction Between Player and Enemy

Finding the Enemy

First of all, we need the collision information of the player, whether the collision between enemy or the other. To know if the collision is between enemy, we can use layer mask as a sign that the collision is between enemy. Layer 1 will be a layer for the enemy node. Change the layer mask value of enemy into like this

Enemy Layer Mask

Next, change the collision mask value of player into like this

Player Collision Mask

Layer Mask : Like a grouping system for Collision Node Type. You can edit from inspector by changing the Layers value.

Collision Mask : Which mask will interact with the node. You can edit from inspector by changing the Mask value.

How they work? You have 2 Node, Player and Enemy. Player Layer Mask = 1, and Enemy Layer Mask = 2. If Player Collision Mask number 2 is active, then Player can collide with Enemy, if number 2 non-active, Player can’t collide the Enemy. Got it? Between Layer and Collision Mask using bit system, for example

1
2
3
4
- 0 active = 1 = 0001
- 1 active = 2 = 0010
- 0 and 1 active = 3 = 0011
- 0, 2, 3 active = 13 = 1101

Detecting the Enemy

The enemy layer is in the 1 (starting index 0), now we just need to check the collision between player and enemy. Open player.gd and modify it

1
2
3
4
5
6
7
8
9
10
11
12
#other code
func _fixed_process(delta):
#other code
var remaining_movement = controller.move(right_input, left_input, jump_input, delta)
if is_colliding():
#check if collider is KinematicBody
if get_collider().get_type() == "KinematicBody2D":
#check if collider layer inside layer 1
if get_collider().get_layer_mask_bit(1):
#then enemy detected
print("Enemy")
#other code

If you run the scene and make a collision between player and enemy, the Output will print “Enemy”.

Taking Damage

Talking about damage, we also need health right? Basically, character type node has health property, because of that, I will using character_controller.gd to put the health property. Open character_controller and add two new variable export var MAX_HEALTH = 10 and onready var current_health = MAX_HEALTH. Then add a new function to taking damage

1
2
3
4
5
#other code
func take_damage(value):
current_health -= value
if current_health < 0:
current_health = 0

Next, open enemy.gd and add one variable export var damage_given = 1. Next open the player.gd and modify into like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# path enemy script
var enemy_script = preload("res://enemy.gd")
#other code
func _fixed_process(delta):
#other code
if is_colliding():
#check if collider is KinematicBody
if get_collider().get_type() == "KinematicBody2D":
#check if collider layer inside layer 1
if get_collider().get_layer_mask_bit(1):
# make sure if collider is enemy
if get_collider() extends enemy_script:
take_damage(get_collider().damage_given)
#othercode

Try it and make a collision betwen enemy and see the Output console.

If the result same as above, then your implementation is success!!

Invisible

When a character taking damage, the character will become Invisible and take no damage during specific time. Most games indicate that a character is invisible is by flickering the sprite (renderer). Flickering can be achieve by shifting the alpha. Add 3 variables export var INVISIBLE_TIME = 2.0, const flicker_step = 0.1, var invisible_time_done = 0.0, var is_invisible = false and onready var timer = Timer.new() at character_controller.gd. INVISIBLE_TIME is for storing how long character will be at invisible state, flicker_step is time needed to toggle the alpha, timer is for counting the flickering and invisible time and invisible_time_done is for storing how long time has been done. Then, it seems we need to access the Sprite Node, and since we have it inside player.gd and slime.gd, delete the onready var sprite = get_node("Sprite") inside player.gd and slime.gd. Then add a new variable onready var sprite = get_node("Sprite") inside character_controller.gd. So far, what you need to add is

1
2
3
4
5
6
export var INVISIBLE_TIME = 2.0
const flicker_step = .1
var invisible_time_done = 0.0
var is_invisible = false
onready var timer = Timer.new()
onready var sprite = get_node("Sprite")

Next, create the logic flickering, the comment explain it all.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#other code
func _ready():
#othercode
create_timer()
# Creating timer
func create_timer():
timer = Timer.new()
add_child(timer)
timer.set_timer_process_mode(Timer.TIMER_PROCESS_FIXED)
timer.set_one_shot(false)
timer.connect("timeout", self, "_flickering")
# Flickering, only caled when timeout fired
func _flickering():
if invisible_time_done == INVISIBLE_TIME:
#set the sprite alpha = 1
sprite.set_modulate(Color(1,1,1,1))
# reset invisible_time_done
invisible_time_done = 0
# now character can take damage again
is_invisible = false
# stop the timer because flickering process done
timer.stop()
# When invisible_time_done + flicker_step is more than INVISIBLE_TIME
elif invisible_time_done + flicker_step > INVISIBLE_TIME:
# we need to calculate the time left, because time left is not equal than flicker time
var t = INVISIBLE_TIME - invisible_time_done
# set this to catch the condition above (invisible_time_done == INVISIBLE_TIME)
invisible_time_done = INVISIBLE_TIME
# stop the timer since the flicker_step not the right value
timer.stop()
# re-start the flickering with proper time
start_flickering(t)
if sprite.get_modulate().a == 0:
sprite.set_modulate(Color(1,1,1,1))
else:
sprite.set_modulate(Color(1,1,1,0))
else:
# this region is for flickering, nothing special
if sprite.get_modulate().a == 0:
sprite.set_modulate(Color(1,1,1,1))
else:
sprite.set_modulate(Color(1,1,1,0))
invisible_time_done += flicker_step
func start_flickering(t):
timer.set_wait_time(t)
timer.start()
func take_damage(value):
if is_invisible:
return
current_health -= value
if current_health < 0:
current_health = 0
else:
is_invisible = true
start_flickering(flicker_step)
#othercode

Try it and make a collision to slime.

Invisible

Bounce

The next section we will make it bounce back while taking damage. While bouncing back, the character can’t be controlled for a while. So let’s do this, modify the character_controller.gd code into like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#other code
# store if is bouncing
var is_bouncing = false
# bouncing direction, x will be automatically change depending on the damage factor and this character
export var bounce_dir = Vector2(3, -5)
# can't control character while taking damage
export(float, 0.0, 1.0, .01) var uncontrolable_invisible = 0.5
#other code
func _flickering():
if invisible_time_done > INVISIBLE_TIME * uncontrolable_invisible and is_bouncing:
is_bouncing = false
#other code
func calculate_movement(right_input, left_input, jump_input, delta):
# make a Vector2 variable movement and add gravity into y axis
var movement = Vector2(velocity.x, velocity.y + GRAVITY * delta)
#Apply the horizontal movement
if right_input and !is_bouncing:
movement.x += move_step * delta
elif left_input and !is_bouncing:
movement.x -= move_step * delta
#other code
func collision_handling(remaining_movement):
if is_bouncing:
return
#other code
func take_damage(value):
if is_invisible:
return
current_health -= value
if current_health < 0:
current_health = 0
else:
is_bouncing = true
var dir = sign(get_center_pos().x - get_collider().get_center_pos().x)
velocity = bounce_dir * Vector2(dir, 1)
is_invisible = true
start_flickering(flicker_step)
#other code
Bounce

Take Damage Animation

Open the player.tscn and make a new GetDamage Animation Clip. Again, I’m will not explaining process creating animation clip, not different as the previous part. After you’ve done creating animation, now modify player.gd script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#other code
func _fixed_process(delta):
#other code
var new_anim = "Idle"
if is_bouncing:
new_anim = "GetDamage"
elif last_frame_grounded:
if velocity.x != 0:
new_anim = "Move"
else:
new_anim = "Jump"
#other code
Bounce with Animation

This part ended here, next part we will creating the UI to showing player health.

Download Project Here