Godot Version
v4.2.2.stable.official [15073afe3]
Question
I cannot seem to get this enemy to move well. The intended behavior of the enemy is that it should rest until its raycasts detect the player, then it jumps at the player, then it rests, then it gets back up and walks around until its cooldown timer runs out, after which it is free to jump at the player again. Instead it hangs in air when it should fall to the ground (collision shape not colliding with anything as far as I can tell). This is pronounced when the enemy is walking or jumping off a ledge, or sometimes when the enemy has been attacked. There may be other problems with the movement, but until the thing lands on the ground like it should, they’ll be hard to diagnose. Thanks!
extends CharacterBody2D
#CONSTANTS
const LEFT = 1
const RIGHT = 0
const REST = 0
const WALK = 1
const ATTACK = 2
const DIE = 3
#VARIABLES
var speed = 50
var enemy_direction = LEFT
var facing_right = false
var direction = -1
var maxhp = 5
var currenthp = maxhp
var dead : bool = false
var knocked_back : bool = false
var movement_suspended : bool = false
var invincible : bool = false
var is_dying = false
var move_type = REST
var target = null
var cooldown = false
var new_bone_pos = null
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
var flipped = false
#REFERENCE THINGS
@onready var game_manager = %GameManager
@onready var ray_cast_right = $RayCastRight
@onready var ray_cast_left = $RayCastLeft
@onready var animated_sprite_2d = $AnimatedSprite2D
@onready var player = $"../../Player"
@onready var knockback_timer = $EnemyHitbox/KnockbackTimer
@onready var enemy_hit_sound = $EnemyHitbox/EnemyHitSound
@onready var after_death_timer = $AfterDeathTimer
@onready var enemy_death_sound = $EnemyDeathSound
@onready var cooldown_timer = $CooldownTimer
@onready var attack_timer = $AttackTimer
@onready var enemy_hitbox = $EnemyHitbox
@onready var flip_timer = $FlipTimer
@export var bone_scene:PackedScene
#DEATH
func death(fell=false):
#print("Panther died!")
dead = true
if fell:
queue_free()
else:
move_type = DIE
print("Play panther death animation.")
after_death_timer.start()
func _on_after_death_timer_timeout():
enemy_death_sound.play()
visible = false
queue_free()
#GETTING ATTACKED
func _on_enemy_hitbox_area_entered(area):
if area.is_in_group("PlayerWeapon"):
if invincible == false:
currenthp = currenthp - 3
enemyknockback()
print("Panther takes damage. HP:" + str(currenthp))
func enemyknockback():
#print ("Panther knockback...")
knocked_back = true
invincible = true
enemy_hit_sound.play()
if knocked_back:
move_type = ATTACK
print ("enemy_direction is " + str(enemy_direction))
print ("enenmy knockback - player direction: " + str(player.player_direction))
velocity.y = -120
velocity.x = -64
if player.player_direction == player.RIGHT:
velocity.x *= -1
knockback_timer.start()
func _on_knockback_timer_timeout():
print("Panther knockback ended.")
knocked_back = false
invincible = false
visible = true
if currenthp <= 0 and !dead:
invincible = true
print ("Check panther death.")
death()
#ATTACKING
func attack_input():
print ("Panther attacks...")
move_type = ATTACK
attack_timer.start()
target = null
velocity.y = -240
velocity.x = 240
if player.player_direction == player.RIGHT:
velocity.x *= -1
cooldown = true
print ("Cooldown switched to:" + str(cooldown))
cooldown_timer.start()
func _on_attack_timer_timeout():
print("Attack timer end.")
move_type = REST
movement_suspended = true
func _on_cooldown_timer_timeout():
print ("Cooldown timer end.")
movement_suspended = false
cooldown = false
move_type = WALK
print("Cooldown switched to:" + str(cooldown))
#PROCESS & PHYSICS
func _physics_process(delta):
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
if movement_suspended == false:
#if is_dying:
# return
if !knocked_back and move_type == REST:
pass
else:
velocity.x = direction * speed
#handle attack
if is_on_floor() and !cooldown and !is_dying and move_type != ATTACK:
if $RayCastAttackFront.is_colliding():
target = $RayCastAttackFront.get_collider().get_parent()
elif $RayCastAttackUp.is_colliding():
target = $RayCastAttackUp.get_collider().get_parent()
elif $RayCastAttackDown.is_colliding():
target = $RayCastAttackDown.get_collider().get_parent()
# Check for target not being null first
if target and target.is_in_group("Player"):
attack_input()
#handle flip
if is_on_floor():
if ray_cast_left.is_colliding() or ray_cast_right.is_colliding():
flip()
#print ("Casting flip ray. Direction: " + str(direction) + "Enemy direction:" + str(enemy_direction) + "Facing Right: " + str(facing_right) + "Movement Suspended: " + str(movement_suspended))
if knocked_back and velocity.y < 10:
velocity.y += 4
if !knocked_back and is_dying:
movement_suspended = true
move_and_slide()
func flip():
if !flipped:
facing_right = !facing_right
flipped = true
flip_timer.start()
scale.x = abs(scale.x) * -1
if facing_right:
enemy_direction = RIGHT
direction = 1
else:
enemy_direction = LEFT
direction = -1
func _on_flip_timer_timeout():
flipped = false
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
if currenthp <= 0:
is_dying = true
#if invincible == true:
# visible = !visible
if move_type == WALK: #and !is_dying:
animated_sprite_2d.play("Walk")
elif move_type == ATTACK:
animated_sprite_2d.play("Attack")
elif move_type == REST:
animated_sprite_2d.play("Rest")
elif move_type == DIE:
animated_sprite_2d.play("Death")