extends Body
class_name Enemy
@onready var player: Player = $"../Player"
@onready var enemy: Enemy = $"."
var chasing = false
var map: TileMapLayer
func _physics_process(delta):
move_speed = 0.5
super._physics_process(delta)
#print("Enemy:", global_position)
#print("Player:", player.global_position)
func _on_detection_area_body_entered(body: Node2D) -> void:
chasing = true
id_path = map.find_path(enemy.get_position(), player.get_position())
#test
print("entered")
print("player position: ", player.position)
print("")
pass
Enemy walks but it walks strangely and almost always away from the player even though i set id_path path array from the enemy to the player, unless I’m doing it incorrectly
Player walks into detection area, path is created to the player, using find_path, physics_process calls Body movement code. Although when entering only one path is created and isnt updated so it should probably also be in physics process. At least that’s what my logic says
Hi was busy yesterday with work. Uh yeah sure find path uses a start and an end point. I don’t know which vars are you refering to but I’ll guess the ones in the Enemy.gd.
I use var: player to get info of the player position, var chasing to set the state of the enemy if it started chasing the player when entered the detection zone. I can discribe other vars if ya want but now that I sat down and tried to fix up I got the enemy to move but it doesn’t get right to the player, or it doesn’t collide with it and only chases the player when it tries to leave detection area
extends Body
class_name Enemy
@onready var player: Player = $"../Player"
var chasing = false
var map: TileMapLayer
func _physics_process(delta):
move_speed = 0.4
if chasing == true:
id_path = map.find_path(global_position, player.global_position)
super._physics_process(delta)
#test
print("Player pos: ", player.global_position)
print("Enemy id path: ", id_path)
func _on_detection_area_body_entered(body: Node2D) -> void:
chasing = true
pass
func _on_detection_area_body_exited(body: Node2D) -> void:
chasing = false
pass
I’m glad to see you fixed some of the errors you had previously, like using position rather than global_position, having a var referencing its own node, having id_path not being in physics, and not actually using your chase var.
You will need to do some bug testing to figure out why the path isn’t going to player until he leaves, I’d print exit and enter respectively and see if they behave how you expect.
So, collisions are the thing. So, the reality is your movement doesn’t use the physics engine, so it doesn’t calculate collisions. So, if you want collision detection you’ll either need to convert your tween movement to a move_and_slide() that uses physics collision.
The other option is opting for an occupied/unnoccupied cell setup. For true gridbased movement games this is what I tend to do, since my movement always ends up funky when trying to use physics based movement set to a grid.
So, the unnoccupied/occupied would be turning cells off when you enter, and back on when you exit.
It is up to you which of these two you pursue. I found the second ultimately harder to implement at start, but easier to keep from being buggy.
Added a pop_front() after the id_path so now the enemy follows the player but like you said due to such movement they don’t have collisions so they overlap. I think it would work if its possible to delete the last array value in id_path. Or like you said set tiles to occupied/unoccupied. Thanks
Edit: Nvm pop_front makes enemy move diagonally ;DD
You could experiement with popping last array point id_path.pop_at(id_path.size - 1).
However, this might only be a solution if you ever only have 1 enemy, since in reality you need a way for enemies to know if other enemies are also occupying tiles.
How this is handled can vary. It might be sufficient to have a func set_occupation(start, end) here you would pass the position the body is leaving, and their target destination. You would set on your AStar point disabled false for leaving pos, and true for point entering.
A more advanced approach would be using class to create an internal class within your map script, which you use a dictionary to “attach” to each astar point, and use that dict to handle occupation.
Switched back to move_toward instead of tween. The old move code was just too complex i guess but it was smoother on the walking and animations :DD You’re probably not the expert at this but now i’m back to almost square one. Well not completely because the player and the enemy now move using the same physics process. I noticed 2 problems:
Character sometimes stops moving and returns empty path while moving:
When starting to walk the animations are a bit buggy as if the direction is changed for a split second.
Please do let me know if you want to close this topic as you’ve helped me plenty and basically the topic has been solved as I’ve created a dedicated class for movement
So, the reason you are having issues with the empty path (my best guess) is due to what I mentioned about buggy. That is one of the things that was difficult for me to handle while creating a physics-based movement snapped to a grid.
This is being caused because your player position is most likely not staying exactly at grid dimensions. So, as you are moving it is possible that your map is having difficulty calculating the path from the coords you send it. Without being able to debug your code myself I cannot confirm this. However, if you place a print statement with (start) in your find_path code within your map, and at the end of that code another print that gives you what value it is returning, this will allow you to see what is the start value when it is returning empty.
Ultimately, due to the needs of my game, and the fact that my movement was so buggy this is why I eventually landed on non-physics, since ultimately if your characters always rest at the center of a tile there is no need for physics calculations. They don’t have free form movement, however, that would require you to restructure your code/map setup to implement something like that.
So, print(direction) where it is applicable in your movement code, to check if it is being changed before you would be setting your animations, or slightly after.
Also, use print in your find_path code to try and figure out if it is really the fact that your characters aren’t sitting exactly at the center of your tiles.
Moved back to tween ;DD fixed it and everything works now. Thank you very very much for the help. Gotta go figure out how to make collisions now. Got any ideas? I used some raycasts on the enemy so that when it detects the player it stops moving but that doesn’t reliably work (enemy doesn’t stop or when player changes direction the enemy walks into the player) and the player can pass through the enemy also.
Most reliable way would be to create a dictionary of each point that holds a bool for occupied.
For you since your tiles don’t need to hold other information. I would create a dict occupation in your map script. When you are populating ypur astar, add an entry to the dict with your astar point as the key, and false as the value.
Then create a func in map set_occupied(free, occupy). Here you will set your dict value with key free to false, and with key occupy to true. You have to convert the arg to your astar points before using them.
In your move func in your body class you will call this func right before your tween, you will pass your current position, and your target position.
In your map, you will also have another func check_occupied(point) -> bool: return true/false
You will add a check statement in your move code if map.check_occupied(target) where you will pass your desired destination. If this returns true you will return.
Edit: more simply instead of using the dictionary you could see what happens if you just set the points to disabled true/false, I tried that at one point but it didn’t give me the response I wanted for my game.
Though I think while finding a path you’ll have to set find partial path to true.
extends TileMapLayer
var astar: AStarGrid2D
var tile_position: Vector2i
var occupation: Dictionary = {}
func _ready():
astar = AStarGrid2D.new()
astar.region = get_used_rect()
astar.cell_size = Vector2(32, 32)
astar.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER
astar.update()
for x in get_used_rect().size.x:
for y in get_used_rect().size.y:
tile_position = Vector2i(
x + get_used_rect().position.x,
y + get_used_rect().position.y
)
var tile_data = get_cell_tile_data(tile_position)
if tile_position == null or tile_data.get_custom_data("Walkable") == false:
astar.set_point_solid(tile_position)
else:
occupation[tile_position] = false
This is just the first paragraph of your suggestion but I wanna ask if I’m doing this right. As you can see at first I set non occupied tiles to the one that are walkable (occupation = false). Now going to the next paragarph where you say to change false values to free and true to occupied.
(How you get those positions to be converted to astar points is up to you)
The idea is that you’d use the astar points as keys in the dict, when you move you’d use the bodies location, and their target position to change occupation of tiles. You’d then prevent movement to occupied tiles by having another func that just checks the dict to see if tile is occupied.
Ok so I think i corrected everything like you said and how I understood it. I tested with printing occupation[global_position] and occupation[target_position] and it seems they’re returning well. Global returning false ant target returning true. When I click though the player moves on spawn tile and nowhere else. Here’s some code:
Map:
extends TileMapLayer
var astar: AStarGrid2D
var tile_position: Vector2i
var occupation: Dictionary = {}
var free: String
var occupy: String
func _ready():
astar = AStarGrid2D.new()
astar.region = get_used_rect()
astar.cell_size = Vector2(32, 32)
astar.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER
astar.update()
for x in get_used_rect().size.x:
for y in get_used_rect().size.y:
tile_position = Vector2i(
x + get_used_rect().position.x,
y + get_used_rect().position.y
)
var tile_data = get_cell_tile_data(tile_position)
if tile_position == null or tile_data.get_custom_data("Walkable") == false:
astar.set_point_solid(tile_position)
occupation[tile_position] = occupy
else:
occupation[tile_position] = free
func find_path(start, end) -> Array:
var id_path = astar.get_point_path(
local_to_map(to_local(start)),
local_to_map(to_local(end))
)
for i in id_path.size():
id_path[i] = id_path[i] + Vector2(16, 16)
return id_path
func set_occupied(free, occupy):
occupation[free] = false
occupation[occupy] = true
func check_occupied(point) -> bool:
return true or false
And the Body code:
extends Node2D
class_name Body
@export var move_speed: float = 0.2
var moving: bool = false
var direction: Vector2 = Vector2.ZERO
var id_path: Array = []
var target_position: Vector2
var map: TileMapLayer
func _physics_process(_delta):
if moving:
return
if not _can_move():
target_position = Vector2.ZERO
return
moving = true
target_position = id_path[0]
direction = (target_position - global_position).normalized()
direction.y = -direction.y
map.set_occupied(global_position, target_position)
#test
#print(map.occupation[target_position])
if map.check_occupied(target_position) == true:
return
var tween := get_tree().create_tween()
tween.tween_property(self, "global_position", target_position, move_speed) \
.set_trans(Tween.TransitionType.TRANS_LINEAR) \
.set_ease(Tween.EaseType.EASE_IN_OUT)
tween.tween_callback(Callable(self, "_on_reach_tile"))
func _on_reach_tile():
id_path.pop_front()
moving = false
func _can_move() -> bool:
while not id_path.is_empty() and id_path[0] == (global_position):
id_path.pop_front()
return not id_path.is_empty()
That is because you are checking occupied after you set the tile to occupied. Place if map.check_occupied(id_path[0]): right after if not can_move():
Right now if you follow your logic, you set target_position to be occupied, then you proceed to check if it is occupied.
Often times if you have a bug I find just reading through the logical progression of code can often time reveal a lot. However, you need to understand wjat your code is doing for that to work.
Can move now but changes nothing on collisions. Enemy prints target position is true which should mean the tile is occupied. Idk, I gotta go so I’ll sit down a couple hours later.
Also in “if map.check_occupied(id_path[0]):can't use id_path[0], saysOut of bounds get index ‘0’ (on base: ‘Array’)”
Okay, when you get a moment can you post your code.
id_path[0] shouldn’t return an error if you are checking after your if not can_move(), since that should block an empty array from continuing past that point.
Also, post your map code please too, so I can see your check_occupied() and set_occupied() func.