Enemies - acbarker19/Godot-Action-RPG GitHub Wiki
How to Create the Enemy Bat
- Add a new KinematicBody2D
- Rename to Bat
- Add an AnimatedSprite as a child
- Create the sprite animation
- Move the Y offset to -12
- Add a Sprite for the shadow using a shadow texture
- Add a CollisionShape2D on the shadow of the bat
- Click on the Bat parent node
- Add an Enemy layer
- In the right menu, open Collision, set the Layer to Enemy, and set the Mask to World
Allow Enemy Bat to be Attacked
- Open the Bat scene
- [Add a hurtbox)(https://github.com/acbarker19/Godot-Action-RPG/wiki/Interactions#add-a-hitbox-or-hurtbox-to-an-object)
- Set the hurtbox's Layer and Mask
- Add a function when the bat is hurt
Add Knockback When the Enemy Bat is Attacked
Add the following code to the Bat script:
var knockback = Vector2.ZERO
func _physics_process(delta):
knockback = knockback.move_toward(Vector2.ZERO, 200 * delta)
knockback = move_and_slide(knockback)
func _on_Hurtbox_area_entered(area):
knockback = area.knockback_vector * 120
Add a script to any hitbox that will knock back the bat with the following code (in this project, this is applied to the SwordHitbox):
var knockback_vector = Vector2.ZERO
Add the following code to any object that will control the hitbox (in this project, this is applied to the Player):
onready var objectHitbox = $DesiredHitboxForObject
func _ready():
objectHitbox.knockback_vector = roll_vector
func move_state(delta):
var input_vector = Vector2.ZERO
// code to set input_vector...
if input_vector != Vector2.ZERO:
roll_vector = input_vector
objectHitbox.knockback_vector = input_vector
How to Create Enemy Stats
- Create a new scene using the + icon near the top middle of the screen
- In the left menu, select Other Node and add a Node to the scene
- Save the scene as Stats.tscn
- Add a script to the Node
- Add the following code and anything additional that would apply to all enemies:
export(int) var max_health = 1
onready var health = max_health // onready will make sure health is equal to whatever value is set for the scene that implements Stats rather than the initial max_health value of 1
Look in General Godot Information to see the explanation for export var
How to Add Stats to the Bat Enemy
- Open the Bat scene and select the Bat parent node
- In the left menu, click the Instance a scene file as a Node button (Looks like a chain)
- Select Stats
- Inside the Bat script, add the following:
onready var stats = $Stats
- In the scene editor, select the Stats node
- In the right menu, increase Script Variables > Max Health to the desired amount
How to Update Bat Health When Hit
- In the Bat script, add the following:
func _on_Hurtbox_area_entered(area):
stats.health -= area.damage
- In the Stats script, add the following:
onready var health = max_health setget set_health
signal no_health
func set_health(value):
health = value
if (health <= 0):
emit_signal("no_health")
func _onready():
self.health = max_health
- Click on the Stats node withing the Bat scene
- In the right menu, open the Node tab and double click Stats.gd > no_health()
- In the popup, click Connect
- In the Bat script, add the following to the newly created signal function:
func _on_Stats_no_health():
queue_free()
Visit the General Godot Information page for an explanation of setget
Add an Enemy State Machine
In the Bat script, add the following (only relevant code is shown):
export var ACCELERATION = 300
export var MAX_SPEED = 50
export var FRICTION = 200
enum {
IDLE,
WANDER,
CHASE
}
var velocity = Vector2.ZERO
var state = CHASE
func _physics_process(delta):
// knockback code
match state:
IDLE:
pass
WANDER:
pass
CHASE:
pass
Make the Bat Detect and Chase the Player
- Create a PlayerDetectionZone
- In the Bat scene in the left menu, select Instance a scene file as a Node (looks like a chain)
- Select PlayerDetectionZone
- In the left menu, right click the PlayerDetectionZone and check Editable Children
- Select the PlayerDetectionZone's child CollisionShape2D
- In the right menu, choose a shape and set its size
- In the Bat script, add the following (only relevant code is shown):
onready var playerDetectionZone = $PlayerDetectionZone
func _physics_process(delta):
// knockback code
match state:
IDLE:
velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
seek_player()
WANDER:
// wander code
CHASE:
var player = playerDetectionZone.player
if player != null:
var direction = global_position.direction_to((player.global_position))
velocity = velocity.move_toward(direction * MAX_SPEED, ACCELERATION * delta)
else:
state = IDLE
velocity = move_and_slide(velocity)
func seek_player():
if playerDetectionZone.can_see_player():
state = CHASE
Make the Bat Face the Correct Direction
- Open the Bat script
- Add the following:
onready var sprite = $AnimatedSprite
func _physics_process(delta):
// knockback code
match state:
IDLE:
// idle code
WANDER:
// wander code
CHASE:
// chase code
sprite.flip_h = velocity.x < 0
Allow the Bat to Hurt the Player
- Open the Bat scene
- Add a Hitbox to the Bat
- In the right menu, select Collision > Mask > PlayerHurtbox
Signal to Parent Node as Health Changes
In Stats, add the following:
signal health_changed(value)
func set_health(value):
health = value
emit_signal("health_changed", health)
Signal to Parent Node as Max Health Changes
In Stats, add the following:
export(int) var max_health = 1 setget set_max_health
signal max_health_changed(value)
func set_max_health(value):
max_health = value
self.health = min(health, max_health) // if hearts is greater than max_hearts, hearts is lowered
emit_signal("max_health_changed", max_health)
Make an Enemy Wander
- Open the Bat scene
- Add a new Node2D called WanderController
- Right click on Wander Controller and select Save Branch as Scene
- Save the scene within the Enemies folder
- Open the WanderController scene and add a new Timer node
- In the right menu, enable Inspector > One Shot and Inspector > Autostart
- Attach a new script to the WanderController
- In the right menu, add a Node > Timer > timeout()
- In WanderController, add the following code:
export(int) var wander_range = 32
onready var start_position = global_position
onready var target_position = global_position
onready var timer = $Timer
func _ready():
update_target_position()
func update_target_position():
var target_vector = Vector2(rand_range(-wander_range, wander_range), rand_range(-wander_range, wander_range))
target_position = start_position + target_vector
func get_time_left():
return timer.time_left
func start_wander_timer(duration):
timer.start(duration)
func _on_Timer_timeout():
update_target_position()
- In Bat, add the following code:
onready var wanderController = $WanderController
func _ready():
state = pick_random_state([IDLE, WANDER])
func _physics_process(delta):
// code
match state:
IDLE:
//code
if wanderController.get_time_left() == 0:
state = pick_random_state([IDLE, WANDER])
wanderController.start_wander_timer(rand_range(1, 3))
WANDER:
seek_player()
if wanderController.get_time_left() == 0:
state = pick_random_state([IDLE, WANDER])
wanderController.start_wander_timer(rand_range(1, 3))
var direction = global_position.direction_to((wanderController.target_position))
velocity = velocity.move_toward(direction * MAX_SPEED, ACCELERATION * delta)
sprite.flip_h = velocity.x < 0
// code
func pick_random_state(state_list):
state_list.shuffle()
return state_list.pop_front()
Prevent Wobbling Motion When Wandering
Enemies will sometimes wander, reach a position, and then seem to wobble in place for a moment. To prevent this, add the following code to Bat:
export var WANDER_TARGET_RANGE = 4
func _physics_process(delta):
// code
match state:
//code
WANDER:
// code
if global_position.distance_to(wanderController.target_position) <= WANDER_TARGET_RANGE:
state = pick_random_state([IDLE, WANDER])
wanderController.start_wander_timer(rand_range(1, 3))