Ok I’ll try it. Thanks a lot for the help. Will maybe reply tomorow or the day after
Also, the Vector2.ZERO return is why I placed a guard statement in physics_process that said if not direction: return
Man I can’t get this movement to work at all. Do you perhaps know a code that uses speed instead of move_delay, cause I don’t really like it unless there is like a huge benefit of using it… And I need to feed my animation_tree normalized parameters like 0,1; 0,-1; 1,0; -1,0. I just can’t fix the your code so that the character doesn’t move 2 steps forwards and 1 step back, or direction changing when spamming on tiles, or more issues I had. I’ll still try to fix it, but it’s kind of stressing me, maybe it’s not my day :DDD
Can you post your current move code?
There are a lot of ways to do movement. What are your requirements? It is grid locked right, as in you will only move in increments of your tiles?
Well in my game I want to have click to move movement which I first did and modified off of this tutorial. I made it to also have directional animations and variable speed. I’m thinking of perhaps going back to it and trying to implement it again but in the Body class.
Code right now:
extends Node2D
class_name Body
@export var move_delay: float = 0.2
var moving: bool = false
var direction: Vector2
var anim_direction: Vector2 = Vector2.ZERO
var id_path: Array = []
const reach_threshold := 1.0
func _physics_process(_delta):
if id_path.is_empty() or moving:
anim_direction = Vector2.ZERO
return
direction = id_path.front()
var to_target = direction - global_position
if to_target.length() <= reach_threshold:
id_path.pop_front()
anim_direction = Vector2.ZERO
return
anim_direction = to_target.normalized()
moving = true
var tween: Tween = get_tree().create_tween()
tween.tween_property(self, "global_position", direction, move_delay).set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_IN_OUT)
tween.tween_callback(func ():
moving = false
id_path.pop_front()
)
It works on the walking side, kind of, character starts dashing if spam clicking and sometimes moves diagonally and also the directions for the animation tree aren’t outputing that well
So, I am not sure your issues with your animation. I tested this out with the input that you see, just to see if the input is the issue. That is not the case. So, my guess is what is happening is wherever you are changing the path is not guarded to prevent overwriting the path in the middle of movement. The movement works fine how it is written here in this example. Either the path that you are receiving is off, and giving funky coordinates, or how you have your code set up to initiate the path by clicking is giving you issues, where it is overwriting your current path in the middle of movement or something.
class_name Body extends Node2D
const REACH := 1.0
@export var move_delay: float = 0.2
var moving: bool = false
var direction: Vector2
var anim_direction: Vector2 = Vector2.ZERO
var id_path: Array = []
func _input(event: InputEvent) -> void:
if Input.is_action_just_pressed("east"):
if not moving:
id_path.append(global_position + (Vector2.RIGHT * 100))
id_path.append(global_position + (Vector2.RIGHT * 200))
id_path.append(global_position + (Vector2.RIGHT * 300))
func _physics_process(_delta):
print(id_path)
if moving:
return
if not _can_move():
anim_direction = Vector2.ZERO
return
moving = true
direction = id_path[0]
var to_target = direction - global_position
anim_direction = to_target.normalized()
var tween: Tween = get_tree().create_tween()
tween.tween_property(self, "global_position", direction, move_delay).set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_IN_OUT)
tween.tween_callback(func ():
moving = false
id_path.pop_front()
)
func _can_move() -> bool:
if id_path.is_empty():
return false
if id_path[0] == global_position:
id_path.pop_front()
return false
return true
When you have fixed movement along a grid it can be complicated to use more velocity based movement, you can mess with lerp, but I find tweening honestly easier then lerp, and it sounds like your main problem is animations, and then funky double movement.
This is caused I am guessing in how you are setting your path, you need away to prevent your path from being reset while moving, since that could cause an issue depending on how you are calculating your path and everything. If you share your current code for selecting your path by clicking, and the part of the code that generates the path, I might be able to figure out where the problem is.
Also, out of curiosity why are you using an AnimationTree? I would think AnimationPlayer would be more than sufficient for your needs while working with a 2D game like that? I am just asking since I have never used it myself, and was wondering what benefit it was giving you over an AnimationPlayer.
EDIT: Oh I deleted the bit that was checking length of to_target <= reach_threshhold… I wasn’t sure what that was suppose to be checking for, and it was breaking the code so that movement wasn’t working.
EDIT2: primary difference between using speed with velocity and the move_delay that you currently have is clarity. Speed doesnt really tell you how fast your character is moving between points. With move_delay (you can rename it speed if you want) it is the time in seconds your character will take to move to the next point in your path. So, with a speed of 0.2 in 1 second your character will move 5 points on your astar grid.
Second difference velocity uses the physics calculations, and can be difficult to keep your character locked to a grid when moving.
Ok I though I was over after modifying the code but I guess not. Everything works fine as I want to but when switching walking direction near unwalkable tiles the character still sometimes moves diagonally or even speeds up. I used chatgpt to find out why it could be happening and it said it could be because of tween or imprecise to_target caulculations.
extends Node2D
class_name Body
@export var move_speed: float = 0.2
var moving: bool = false
var anim_direction: Vector2 = Vector2.ZERO
var id_path: Array = []
const reach_threshold := 0.1
func _physics_process(_delta):
if moving:
return
if not _can_move():
anim_direction = Vector2.ZERO
return
moving = true
var target_position = id_path[0]
var to_target = target_position - global_position
if abs(to_target.x) > abs(to_target.y):
if to_target.x > 0:
anim_direction = Vector2(1, 0)
else:
anim_direction = Vector2(-1, 0)
else:
if to_target.y > 0:
anim_direction = Vector2(0, -1)
else:
anim_direction = Vector2(0, 1)
var tween: Tween = get_tree().create_tween()
tween.tween_property(self, "global_position", target_position, move_speed)\
.set_trans(Tween.TRANS_LINEAR)\
.set_ease(Tween.EASE_IN_OUT)
tween.tween_callback(func ():
id_path.pop_front()
moving = false
)
func _can_move() -> bool:
while not id_path.is_empty() and id_path[0].distance_to(global_position) < reach_threshold:
id_path.pop_front()
return not id_path.is_empty()
Getting closer to this god damn mess
Edit: it’s not just happening near unwalkable tiles here’s a clip of that (green tile border is where I click and shows the target tile)
So, two things could be happening, your AStarGrid has diagonal connections. Or like I said before the new path is being calculated before the previous movement is finished. Please show me your clicking code, and your astar/path code otherwise I am unsure how to help you.
The movement code only moves to the coord inputed, so it a problem of input, not in execution.
My tile map code:
extends TileMapLayer
var astar: AStarGrid2D
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:
var 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)
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
And this is the code where it registers inputs / clicking code
func _unhandled_input(event):
if event.is_action_pressed("move") and not input_locked:
id_path = map.find_path(global_position,get_global_mouse_position())
Idk diagonal mode is set to NEVER
Okay, so you could try this. In tween callback add direction = Vector2.ZERO.
In your input func after tge input check add if direction: then place your same line that you have after it, but change global_position to direction. Then place an else: and keep what you have currently using global_position.
Sorry not by computer or I’d write it out. It is harder to debug without being able to check things with print statements to figure exactly what is going on.
ok so i tried to do what you said and this is what i did:
Input:
func _unhandled_input(event):
if event.is_action_pressed("move") and not input_locked:
if direction:
id_path = map.find_path(direction,get_global_mouse_position())
else:
id_path = map.find_path(global_position,get_global_mouse_position())
I also added the vector2.zero callback in the _on_reach_tile func which is being called back but to me it looks like it doesn’t do anything. I’ll just drop the whole Body class code for convenience:
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 = []
const reach_threshold := 0.1
func _physics_process(_delta):
if moving:
return
if not _can_move():
direction = Vector2.ZERO
return
moving = true
var target_position = id_path[0]
var to_target = target_position - global_position
if abs(to_target.x) > abs(to_target.y):
if to_target.x > 0:
direction = Vector2(1, 0)
else:
direction = Vector2(-1, 0)
else:
if to_target.y > 0:
direction = Vector2(0, -1)
else:
direction = Vector2(0, 1)
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
direction = Vector2.ZERO
func _can_move() -> bool:
while not id_path.is_empty() and id_path[0].distance_to(global_position) < reach_threshold:
id_path.pop_front()
return not id_path.is_empty()
Due to the new input code changes when the character is moving and i change to walk to another tile the player is teleported to spawn or close to it.
No worries, man, reply whenever you want I’m just happy someone is helping me.
First off, what are you using reach_threshhold for? What is that used for?
Also, have you used a print() to check if your _reach_tile() is being called?
Sign… you changed up how you were using direction, so now it isn’t working how I had originally suggested.
Edit: Sorry didn’t see you had changed to target_position the last time you posted your move code. That is what you should be using for your check on input.
You are over complicating your code.
func _unhandled_input(event):
if event.is_action_pressed("move") and not input_locked:
if target_position:
id_path = map.find_path(target_position, get_global_mouse_position())
else:
id_path = map.find_path(global_position,get_global_mouse_position())
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
const reach_threshold := 0.1
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()
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
target_position = Vector2.ZERO
func _can_move() -> bool: #why use while loop here? This seems overcomplicated and not used for what it should be.
while not id_path.is_empty() and id_path[0].distance_to(global_position) < reach_threshold:
id_path.pop_front()
return not id_path.is_empty()
So, some questions.
What is the reason for reach_threshold?
Was using normalized now working for you like I have written here?
Advice, before adding a lot of code in the quest for getting your animations working, first get your movement not buggy. You shouldn’t need reach_threshold, you should be able to use global_position == id_path(0). Your character should only ever stop and call _can_move() when he is on a AStar path point.
I tried to fix the issues and the the old problembs changed into other. Now the move_ speed (move_delay) delays time before character starts moving and when starting to move the character looks up before switching to a right direction. I’ll drop the code I have but it’s enough for today or my head will explode
Input:
func _unhandled_input(event):
if event.is_action_pressed("move") and not input_locked:
if target_position:
id_path = map.find_path(target_position, get_global_mouse_position())
else:
id_path = map.find_path(global_position,get_global_mouse_position())
Tile_map_layer:
extends TileMapLayer
var astar: AStarGrid2D
var target_position: Vector2
@onready var tile_map_layer: TileMapLayer = $"."
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:
var 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)
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
Movement (Body.gd):
extends Node2D
class_name Body
@export var move_speed: float = 0.6
var moving: bool = false
var direction: Vector2 = Vector2.ZERO
var id_path: Array = []
var target_position: Vector2 = Vector2.ZERO
func _physics_process(_delta):
if moving or id_path.is_empty():
if id_path.is_empty():
direction = Vector2.ZERO
return
moving = true
target_position = id_path[0]
var to_target = target_position - global_position
if abs(to_target.x) > abs(to_target.y):
if to_target.x > 0:
direction = Vector2(1, 0)
else:
direction = Vector2(-1, 0)
else:
if to_target.y > 0:
direction = Vector2(0, -1)
else:
direction = Vector2(0, 1)
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"))
#testing
print("Global player position:", global_position)
print("Target position:", target_position)
print("Direction:", direction)
func _on_reach_tile():
id_path.pop_front()
moving = false
direction = Vector2.ZERO
Thanks and until tomorow
Okay, so you are being distracted by getting your animations to play. Until your movement works you should just # out all your animation code so you aren’t distracted.
You keep changing drastically how you have things set up. I do slight changes to your code, and you make drastic refactors that leave out important parts.
You deleted _can_move() which if its functionality was transfered is based on your own preference, personally I prefer keeping my physics from getting cluttered if I can have a func outside handle it.
For my sake can you please try this that I have below? Don’t worry if animations are playing correctly, focus on your movement bug, once movement is correct animations can be fixed.
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
const reach_threshold := 0.1
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()
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
target_position = Vector2.ZERO
print(moving, target_position)
func _can_move() -> bool:
if id_path.is_empty(): return false
if id_path[0] == global_position:
id_path.pop_front()
return false
return true
You need to follow the logical flow.
When do you want your character to move? When you have a path.
When don’t you want your character to move? When he is already moving.
Where do you want your character to move? To a new square in a cardinal direction.
Your input takes care of the first along with the id_path.is_empty().
Your moving var takes care of the second.
The issue is the third, the check if global_position == id_path[0] was part of the solution. However, you need to prevent the path from giving the first unique coord as something other than a cardinal direction within 1 tile. That is why I had you change your path getting input to use target_position.
The logic was, if your player isn’t moving the path will be valid. However, if your player is moving his global_position may not = that of his destination, so your path_finding will think he is somewhere he isn’t, thus returning an innaccurate path.
So, if you use target_position and update it between the one being used for movement, and a value of 0 like we have, when it isn’t 0 it will call your path finding func with the from pos == to your characters destination.
Movement works perfectly. Tested all the problems and it seemed like they’re gone. Even tried clicking with an autoclicker at 10ms clicks which is unreal but at that speed the movement does become a bit choppy but it’s no problem. They do seem to appear smooth at around 70ms clicks but it’s whatever for now.
Your troubleshooting print:
I know you said to not pay attention to animations but only the y.axis animations are reversed. Overall very good
I am glad it seems to be working better for you.
Now, id recommend going through the code to make sure you understand everything and why it is there.
Yeah there are some parts I don’t fully understand but now it would be cool to move the enemy. Tried to do something like this which I thought would work but I guess not:
extends Body
class_name Enemy
@onready var player: Player = $"../../Player"
var map: TileMapLayer
func _physics_process(delta):
super._physics_process(delta)
func _on_detection_area_body_entered(body: Node2D) -> void:
print("Detected body:", body.name)
target_position = player.position
if target_position:
id_path = map.find_path(player)
Okay, so lets address what you don’t understand. What parts exactly? It isn’t good to have code you are using that you don’t understand.
So, the problem is your map variable is a nil value. Meaning there is nothing stored. Where do you set the map to = $SomeTileMapHere.
The second part is that even if you had successfully called the func, that func takes 2 args. Start point and end point, but you only put player in there. Player is also not the right type, find_path needs Vector2.