How do i make the ai's attack each other

Godot Version

godot 4

Question

i am trying to make my AI attack each other, but i dont know how to do it so an ai will recieve damage based on the other ai’s damage variable.

the ai has a box in front of it that will make it stop once it gets in attack range but thats all ive been able to set up

func _on_area_2d_area_entered(area):
if area.has_method(“enemy”):
move = 0
dino_in_attack_range = true

func _on_area_2d_area_exited(area):
if area.has_method(“enemy”):
move = 2
dino_in_attack_range = false

func attack_enemy():
if dino_in_attack_range and dino_attack_cooldown == true:
#health -= 1
dino_attack_cooldown = false
$attack_cooldown.start()
print(health)

func _on_attack_cooldown_timeout():
dino_attack_cooldown = true

this is all i have. I’ma not really sure what to do and i cant find any tutorials on this. if someone could help lead me in the right direction and give me an idea of how to make the ai take damage. in my game there is no player there is only ai battling .

the code you shared shows how to make a cooldown attack of the ai instead, it should work now, no?

Create two nodes: AttackComponent, HitboxComponent

Create a Hit(int Damage) signal in HitboxComponent.
And connect it to the same node.

Afterwards, when you need to attack, for example if the enemy is in the attack area (track this by AreaEntered signal from Area2D), give each entity a Hit signal.
For example:

var enemys = area2d.get_overlapping_areas
if enemys.count != 0:
    for i in range(enemys.count):
         enemys.EmitSignal("Hit", damage)

Now you can transfer damage between AIs, but to store this health you can either also create a HealthComponent and set up a link between it and the HitboxComponent, or use your own way.

The principle I described is called Composition:
The most detailed of all the Godot videos on this topic that I’ve seen is Firebelley’s video: https://youtu.be/rCu8vQrdDDI?si=-WYD_EnS5hpd2z08

2 Likes

tysm i was very confused on this

Yes, I realise it’s quite difficult to present. I was counting on you knowing OOP :slight_smile:

Composition is an OOP principle that uses the creation of small bricks to create a house.

To give you an example, let me try to give you an idea of hierarchy in composition.
You have a CharacterBody2D character, it needs: Health, ability to move, attack, be attacked.
To do this you can allocate 4 nodes:
HealthComponent (Node2/3D)- stores current and maximum health
MovementComponent (Node2/3D) - stores speed, handles movement.
HitboxComponent (Area2/3D) - has only Hit signal, when called this node transfers damage value to HealthComponent.
AttackComponent (Area2/3D) - stores damage, sends a signal to the opponent’s HitboxComponent under the conditions required for an attack (you set it yourself).

So you have 4 components and the advantage of this approach is that if the code is written correctly, the game can easily scale, i.e. you can add these components to any other entity and everything will work. I advise you to watch the video I’ve attached and also to understand the OOP

1 Like

I watched the video, and it makes a lot of sense.
I am a bit confused now about how the signals work because I haven’t used custom-made signals before. so i emit the signal and then how do i connect the nodes and make a node receive a signal

There are two ways to connect your signals to the node: Through the code and through the inspector (your signal will appear there). If you are going to use my way, create a hit signal:
signal hit(damage)
(If I’m not mistaken).
Now your signal will appear in the inspector of the HitboxComponent, connect it to it and you can process your logic.

1 Like

Now in HealthComponent: you can create literally two variables to start with: maximum and current health. Also create func Damage(value). Now in HitboxComponent, connect your node with HealthComponent using @onready var HealthComponent = …
Now the HitboxComponent has access to the HealthComponent.
Then in the Hit(damage) signal function just write HealthComponent.Damage(damage) and everything will work

1 Like



Godot_v4.2.1-stable_mono_win64_Y0xIhJI5II

1 Like

so ive been trying some stuff and messing around with it but im still having trouble getting it to work

in full, the error says
emit_signalp: Error calling from signal ‘area_entered’ to callable: ‘Node2D(AttackComponent.gd)::_on_hitbox_component_area_entered’: The method expected 2 arguments but called for 1.
The second image is attack component

3rd image is health component

The 4th image is a hitbox component.

I already added all the features to the enemy, so that shouldn’t be the problem.
I was a bit confused on what to put in the attack component, but I messed around with moving and changing stuff, but it wouldn’t really work, so I’m unsure what the problem is. I would really appreciate your help on this. You’ve been a lot of help to me already, and its really encouraging.

i guess i cant embed more than one image so i gotta add them all real quick


i had to cut off a lot of stuff sorry if it looks messy

hello? are you able to help?

Sorry, don’t sit on the forum often, only when I need help myself.
In AttackComponent name the signal Attack() and in HitboxComponent name Hit(value), it will be easier to understand.
The Attack signal doesn’t need to be connected somewhere, the point of it is that you call it when needed, for example:
You pressed the left mouse button and call the Attack signal on the AttackComponent node. The damage dealt does not need to be transferred to the signal, because it is stored in the AttackComponent node. That’s cleared up, let’s move on.
In HitboxComponent connect the Hit(value) signal to itself. The code in HitboxComponent in the hit function is correct, but you have connected the hit signal from the wrong node. That is, connect hit to HitboxComponent and write the code HealthComponent.damage(value) and that’s it

1 Like
# AttackComponent code
extends Area2D
signal Attack()

@export var Damage: int

func OnAttack():
     var Enemys = self.get_overlapping_areas()
     if Enemys.count != 0:
          for i in range(Enemys.Count())
               Enemys[i].EmitSignal("Hit", Damage)

func _on_area_entered(area): 
# Signal from the same node, i.e. from AttackComponent (it is an Area2D node).
    self.EmitSignal("Attack")
1 Like
# HitboxComponent code
extends Area2D
signal Hit(Damage)

@onready var HealthComponent: Node2D = "path to node"

func OnHit(Damage):
    HealthComponent.damage(Damage)

The instructions are written above, the code is written, if you have any questions write, I will try to answer when I have time

1 Like

Okay, so I followed everything, and I was very confused, but it all finally started to make sense, and I understand the signaling now. The only problem i think i have now is the OnAttack function

if I write it as you said, I get an error because it cant use the Enemys.count because its not an area or something.

“Invalid get index ‘count’ (on base: ‘Array[Area2D]’).”

and then if i include area on the function like
func OnAttack(area):
then i get same error as before
E 0:00:11:0204 AttackComponent.gd:17 @ _on_area_entered(): Error calling from signal ‘Attack’ to callable: ‘Area2D(AttackComponent.gd)::OnAttack’: Method expected 1 argument, but called with 0.

this is the same error as before so its still a bit confusing. I continued to mess with the signal and change stuff but cant find out what is causing this do you have any idea. i will provide some pictures of the code

i can show anything else if needed thanks

and then the collision shape is a bit of a mess but i think the collisions and other stuff is okay
image

if you’re reading this, then you don’t need to help anymore. I fixed it. all it took was some fixing of the code. When you wrote it, you forgot just a few simple things, and I just had to go in and fix some stuff. its understandable that you forgot some things, though, because you arent making the game and you dont have access to all the code I do. Thanks alot lot. You really helped me with this and i think its finally going to work

all i had to do was this

extends Area2D
signal Attack()
signal Hit(Damage)
@export var Damage: int

func OnAttack(area):
var Enemys = self.get_overlapping_areas()
print(“1”)
for i in range(Enemys.count(area)):
Enemys[i].emit_signal(“Hit”, Damage)
print(“2”)

func _on_area_entered(area):

self.emit_signal("Attack",area)

Well, that’s very good, glad it worked out for you. I’m glad the principle of composition is being promoted among the godot community.

1 Like