Godot Version 4
gdscript
Question
Hi, I’m currently learning Godot 4 and I make a little top-down rpg. At first I started by putting all of my code in one file for both my player
and my ennemy
but this morning I began to create classes from which my ennemy
and my player
could inherit :
The structure is like this:
Entity
|
IEntity
___|___
| |
Ennemy Player
So I just finished implementing all this stuff but it seam that all signals doesn’t work even if I connect them in my _ready()
Here’s the code for IEntity.gd
:
extends Entity
class_name IEntity
#signals
signal hit(dmg: float, knockback : Vector2)
#stats
@export var health = 100
@export var damage = 10
@export var knock_back = 100
# cooldowns variables
@export_group("Cooldowns")
@export var respawn_cooldown : float = 10
@export var basic_attack_cooldown : float = 1
@export_group("Movements")
#movement-related variables
var speed = 0.0 # current_speed
@export var max_speed = 8000.0
@export var acceleration = 2000.0
@export var friction = 1500.0
@export_range(0.0, 1.0) var walk_limit := 0.75
var direction := Vector2.ZERO
#attack variables
var ennemies_in_range = {}
#state
var alive = true
var iframe = false
# respawn variables
@export_group("Respawn")
@export var start_position : Vector2
@export_group("")
#references to nodes
#cooldowns
@onready var basic_attack_cooldown_node : Timer = $BasicAttackCooldown
@onready var respawn_cooldown_node : Timer = $RespawnCooldown
@onready var animator : AnimatedSprite2D = $Animator
@onready var attackRange_node : Area2D = $AttackRange
# TODO : finish attack system, take care of multiples attacks/weapons
# TODO : adding a setting sysem file and a constructor
# built-in methods
func _ready():
# bind all signals
attackRange_node.body_entered.connect(_on_attack_range_body_entered)
attackRange_node.body_exited.connect(_on_attack_range_body_exited)
hit.connect(take_damage)
basic_attack_cooldown_node.timeout.connect(_on_basic_attack_cooldown_timeout)
respawn_cooldown_node.timeout.connect(_on_respawn_cooldown_timeout)
#set cooldowns
basic_attack_cooldown_node.wait_time = basic_attack_cooldown
respawn_cooldown_node.wait_time = respawn_cooldown
func _physics_process(delta):
if health > 1:
movement(delta)
animation()
move_and_slide()
elif alive: # the entity needs to be alive in order to die
die()
# movement-related method
func movement(delta) -> void:
print("Create your Movement function")
# flip when going to the left
# @param left : default to true. If set to false, the sprite flip when going to the right
func enable_flip(left :bool = true) -> void:
if velocity.x < 0:
animator.flip_h = left
elif velocity.x > 0:
animator.flip_h = !left
func animation() -> void:
enable_flip()
if velocity.length():
if speed > walk_limit * max_speed:
animator.play("run")
else :
animator.play("walk")
else:
animator.play("idle")
# life-related methods
func die() -> void:
animator.play("die")
basic_attack_cooldown_node.stop()
respawn_cooldown_node.start()
alive = false
func respawn() -> void:
alive = true
health = 40
position = start_position
show()
# interactions-related methods
func attack() -> void:
for target in ennemies_in_range.keys():
target.emit_signal("hit", damage, dir_to(target.position) * knock_back)
#signal functions
# hitbox
func _on_attack_range_body_entered(body) -> void:
if body.is_in_group("ennemy"):
ennemies_in_range[body] = body.name
func _on_attack_range_body_exited(body) -> void:
if body.is_in_group("ennemy"):
ennemies_in_range.erase(body)
# Cooldowns
func _on_basic_attack_cooldown_timeout() -> void:
attack()
func _on_respawn_cooldown_timeout() -> void:
respawn()
respawn_cooldown_node.stop()
# Animator
func _on_animator_animation_finished() -> void:
if !alive:
hide() # TODO: delete self (queue_free())
# own signals
func take_damage(dmg: float, knockback : Vector2) -> void:
health -= dmg
print(name + ".health : " + str(health))
if knockback.length():
velocity += knockback
move_and_slide()
And here’s for Player.gd
:
extends IEntity
class_name Player
var attacking = false;
# TODO : finish attack system
# player's exclusive methods
# .......
# overrided methods
#built-in methods
func _physics_process(delta):
if health > 1:
movement(delta)
animation()
move_and_slide()
if Input.is_action_just_pressed("fire") && attacking:
attack()
elif alive: # play the animation once so set player_alive here
die()
# movements-related methods
func movement(delta) -> void:
var direction = Vector2(Input.get_axis("right", "left"), Input.get_axis("up", "down"))
if direction.y:
velocity.y = direction.y
else:
velocity.y = lerpf(velocity.y, 0.0, friction/max_speed) # TODO : where to add delta ?
if direction.x:
velocity.x = direction.x
else:
velocity.x = lerpf(velocity.x, 0.0, friction/max_speed) # TODO : where to add delta ?
if direction.x && !direction.y:
velocity.y = 0
elif !direction.x && direction.y:
velocity.x = 0
# Normalize the velocity / direction
velocity = velocity.normalized()
# checking is there are inputs
var isInputs = direction.length() > 0
# Manage the speed
# if we get Inputs, we accelerate
if isInputs:
speed = lerpf(speed, max_speed, acceleration/max_speed)
else:
speed = lerpf(speed, 0, friction/max_speed)
if speed < 10:
speed = 0
velocity *= speed * delta
func animation() -> void:
enable_flip()
if Input.is_action_just_pressed("fire") && !attacking:
animator.play("attack")
attacking = true;
elif velocity.length():
if speed > walk_limit * max_speed:
animator.play("run")
else :
animator.play("walk")
elif !attacking:
animator.play("idle")
# signals methods
# Animator
func _on_animator_animation_finished() -> void:
if attacking:
attacking = false;
elif !alive:
hide() # TODO: delete self (queue_free())
print("animation finished")
And here’s the one for Ennemy.gd
:
extends IEntity
class_name Ennemy
@export var test = 0
var chasing : bool = false
var target = null
# TODO : create a respawner scene
# TODO : rewrite all methods to match multiples targets
# Ennemy exclusive methods
func set_aggro(new_target) -> void:
target = new_target
# signals methods
#built-in signals
func _on_area_2d_body_entered(body) -> void:
if body == self:
return
set_aggro(get_node("../" + body.name))
chasing = true
# overrided methods
# built-in methods
func _physics_process(delta):
if health > 1:
# if we have a target, make sure that he is alive
if target:
if !target.alive:
chasing = false
set_aggro(null)
if chasing:
movement(delta)
animation()
move_and_slide()
else:
animator.play("idle")
elif alive:
die()
# movements-related methods
func movement(delta) -> void:
if dist_to(target.position) > 1 :
direction = dir_to(target.position)
velocity = direction * speed
else:
velocity = Vector2.ZERO
velocity *= delta
func animation() -> void:
if dist_to(target.position) > 15:
animator.play("move")
enable_flip(false)
else:
animator.play("attack")
# life-related methods
func die() -> void:
animator.play("die")
basic_attack_cooldown_node.stop()
respawn_cooldown_node.start()
chasing = false
target = null
alive = false
# signals methods
#built-in signals
# own signals
func ennemy_attack(dmg: float, knockback : Vector2) -> void:
health -= dmg
print(name + ".health : " + str(health))
if knockback.length():
velocity += knockback
move_and_slide()
animator.play("hurt")