Skip to content

Adding an Enemy

We’ve got a weapon, we’ve got health, we’ve got treasure to collect. What’s missing?

Something to fight!

We’ll be keeping our enemy fairly simple, it’ll operate off a circular detection range, and walk toward the player if it enters that range, without taking into account walls (Though it will of course still collide with walls)

Enemy Scene

Let’s get to making the scene!

  1. It’ll need: a root CharacterBody2D (Called “Enemy”) with three children: a CollisionShape2D, an Area2D (named “range”) and an AnimatedSprite2D

    Additionally we’ll want to give the Area2D a CollisionShape2D as a child.

    Enemy Scene

  2. First, on the CharacterBody2D under the Collision section, we’ll want Layer 2 and 3 selected. With only 3 selected under Mask

  3. Let’s skip down to setting up the AnimatedSprite2D. Create two animations, call the first “idle” and the second “move”. Then, pick something to be your enemy! Selecting both the idle and move animation as we did for the player, using the Add from file button.

    You may want to speed the animations up as they’re fairly slow by default, I found 10 fps to be good for both! You’ll also want to make sure you set the idle animation to be autoplay!

  4. Set the CollisionShape2D that’s a child of the Root node to have a shape that generally matches the sprite.

  5. Set the shape of the CollisionShape2D child of the Area2D to be a circle, making it however large you want the ‘detection’ range of the enemy to be!

  6. Finally, let’s create a new Group and call it “enemy”, and assign it to the root node of the scene.

Enemy Scripting

Now let’s get the enemy moving!

  1. Let’s create some variables:

    1. a boolean to keep track of if the player is in range
    2. a reference to the player
    3. a variable to control our speed

    It should look something like this, again getting the reference to the AnimatedSprite2D the usual way:

    var in_range : bool = false
    var target
    @export var speed : float = 50.0
    @onready var animated_sprite_2d = $AnimatedSprite2D
  2. Then, we’ll want to connect two signals from the “range” Area2D node, we’ll want to connect both the “on_body_entered” and “on_body_exited” signals to the script we created!

    Let’s write those functions. When the player enters the range, we’ll want to set our in_range boolean, and play our move animation

    func _on_range_body_entered(body):
    if(body.is_in_group("player")):
    in_range = true
    target = body
    animated_sprite_2d.play("move")
  3. In the exited function, we’ll want to do the inverse!

    func _on_range_body_exited(body):
    if(body.is_in_group("player")):
    in_range = false
    animated_sprite_2d.play("idle")
  4. Now, in the _process() function, we’ll want to check if our player is in range, if they are, move toward them. We’ll also want to flip our sprite based on where the player is in relation to the enemy.

    func _process(delta):
    if(in_range):
    velocity = (target.global_position - global_position).normalized() * speed
    if(target.global_position.x < global_position.x):
    animated_sprite_2d.flip_h = true
    else:
    animated_sprite_2d.flip_h = false
    move_and_slide()
  5. For a final script that looks like this:

    extends CharacterBody2D
    var in_range : bool = false
    var target
    @export var speed : float = 50.0
    @onready var animated_sprite_2d = $AnimatedSprite2D
    # Called every frame. 'delta' is the elapsed time since the previous frame.
    func _process(delta):
    if(in_range):
    velocity = (target.global_position - global_position).normalized() * speed
    if(target.global_position.x < global_position.x):
    animated_sprite_2d.flip_h = true
    else:
    animated_sprite_2d.flip_h = false
    move_and_slide()
    func _on_range_body_entered(body):
    if(body.is_in_group("player")):
    in_range = true
    target = body
    animated_sprite_2d.play("move")
    func _on_range_body_exited(body):
    if(body.is_in_group("player")):
    in_range = false
    animated_sprite_2d.play("idle")

and that’s it! The last thing we need to do is head over to our Player scene, create a group called “player” and add the root node of the scene to it.

Add an enemy to the scene, and you should notice that if you get close to it, it’ll walk toward you, and take health away whenever it touches you!

Although there’s one problem… We can’t destroy it!

Destroying the enemy!

Head to your Weapon Scene and open the weapon.gd script. We’ll just need to make some slight modifications to check if the sword is colliding with enemies when we attack.

  1. First, let’s get a reference to the Area2D node.

    @onready var area_2d = $Sprite2D/Area2D
  2. Then, in our _process() function, if we’re attacking, we’ll want to get all the bodies we’re colliding with and check if they’re enemies.

    if attacking:
    for body in area_2d.get_overlapping_bodies():
    if body.is_in_group("enemy"):
    body.queue_free()
  3. Leaving the _process(): function looking like this:

    func _process(delta):
    look_at(get_global_mouse_position())
    if Input.is_action_just_pressed("Attack"):
    animation_player.play("Attack")
    if attacking:
    for body in area_2d.get_overlapping_bodies():
    if body.is_in_group("enemy"):
    body.queue_free()

Test it out, and hopefully you’ll find you can now destroy the enemies!

Checklist

  • I’ve created the enemy scene
  • I’ve created the enemy script
  • The enemy damages me
  • The enemy moves toward me
  • I’m able to destroy the enemy