Godot Version
4.4
Question
I am trying to create an algorithm to detect if any enemy is above or below the hero(player). To do this I am using Raycast2D below and above the player to make enemy do something when it is colliding, for now I am just doing the enemy stop and it is working. The problem is when I have more enemies all of them stop too. I think it is happening because of Raycast2D collision is at to layer “enemies” where the enemies are setter. I would like that just the enem who is colliding stopped, and the others goes working to attack the player
I would like to know how could I do this, using or not RayCast2D ?
Are there a better way to do ?
if(_player_ref.is_player_upraycast_colliding() or _player_ref.is_player_downraycast_colliding()):
set_idle_animation()
return
You should put the player and enemies on separate physics layers and set the raycasts to only look at the other layer
Is there a reason not to just check based on coordinates? It looks to me like you’re essentially doing axis-aligned box collisions, which are pretty simple to build even by hand.
I’m guessing you’re making an ARPG or brawler, and are trying to determine when the mobs should shift from “chase” mode to “fight” mode. Given it’s 2D and the visuals you’ve posted, I’m assuming you’re doing a classic brawler view.
Instead of a raycast scheme like this, you might consider a simple 2D distance check. Presumably you have an origin point at which the sprite is anchored (either centered, or centered horizontally and at the feet vertically). If it’s the same on all your sprites (let’s say it’s the feet, so it’s the ground position of the object), then you can do a simple range check:
const MELEE_RANGE: float = 30.0 ** 2 # Or whatever, note we're squaring this...
const Y_SCALE: Vector2 = Vector2(1.0, 0.5)
enum ActionMode = { chase, melee }
var mode: ActionMode = ActionMode.chase
func _process(delta: float):
# Scale the vectors to match the foreshortening of the vertical axis; you'll
# probably want to tune the Y value for your game...
var pos: Vector2 = Y_SCALE * global_position
var plyr: Vector2 = Y_SCALE * player.global_position
# Using distance squared because it's cheaper.
var diff = pos.distance_squared_to(plyr)
# Check whether we need to switch modes.
if diff <= MELEE_RANGE && mode != ActionMode.melee:
mode = ActionMode.melee
_enter_melee() # Any initial setup...
elif diff > MELEE_RANGE && mode != ActionMode.chase:
mode = ActionMode.chase
_enter_chase() # Any initial setup...
match(mode):
ActionMode.chase: _chase(delta)
ActionMode.melee: _melee(delta)
We’re using distance squared here because calculating the distance between two Vector2
values requires a square root, and squared distance lets us skip that somewhat expensive step if all we care is in/out of range. If you want the actual distance you can use distance_to()
, or you can keep using the squared version but take the square root of it yourself when you need to know the actual distance.
We’re also scaling the positions vertically, in order to compress the distance; this means in screen coordinates, the collision area will be an oval rather than a circle, and the y
value in Y_SCALE
controls how far the oval is from a circle.
2 Likes
I’ve used your suggestion and it works well. Thanks a lot!
# Utils Class
static func is_enemy_distance_achieved_top(player_gpos:Vector2,
enemy_gpos:Vector2,
distance:float)->bool:
var Y_SCALE:Vector2 = Vector2(1.0, 0.5)
var enemy_global_position:Vector2 = Y_SCALE * enemy_gpos
var player_global_position:Vector2 = Y_SCALE * player_gpos
var diff = enemy_global_position.distance_squared_to(player_global_position)
if(diff <= distance):
print("Diff <= Distance Top: ", diff <= distance)
return true
return false
static func is_enemy_distance_achieved_bottom(player_gpos:Vector2,
enemy_gpos:Vector2,
distance:float)->bool:
var Y_SCALE:Vector2 = Vector2(1.0, 0.5)
var enemy_global_position:Vector2 = Y_SCALE * enemy_gpos
var player_global_position:Vector2 = Y_SCALE * player_gpos
var diff = enemy_global_position.distance_squared_to(player_global_position)
if(diff <= distance):
print("Diff <= Distance Bottom: ", diff <= distance)
return true
return false
# Enemy Class
func _physics_process(delta: float) -> void:
if(check_enemy_acchiachieved_top() or check_enemy_acchiachieved_bottom()):
set_idle_animation()
return
func check_enemy_acchiachieved_top() -> bool:
var distance:float = 100 ** 2
return Utils.is_enemy_distance_achieved_top(_player_ref.get_raycast_updown_up().global_position,
self.global_position,
distance)
func check_enemy_acchiachieved_bottom() -> bool:
var distance:float = 100 ** 2
return Utils.is_enemy_distance_achieved_bottom(_player_ref.get_raycast_updown_down().global_position,
self.global_position,
distance)