limit vector2 velocity to 4 directions - (1,0)(-1,0)(0,1)(0,-1)

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By blurymind
:warning: Old Version Published before Godot 3 was released.

Basically I am writing a simple AI movement script and I am using kineticBody2d node with move and slide to move the enemy for while there are no obstructions between the enemy and the player.

What I need to figure out now is a way to limit the movement to 4 directions only somehow

To do that I need the velocity Vector2 value to be one of 4 possible values:
(1,0) or (-1,0) or (0,1) or (0,-1)

But right now because I am getting it like this:
var angle = get_angle_to(targetPos)
var velocity = Vector2(sin(angle), cos(angle))
I get the full float range of x and y

Any suggestions on how to do this with math ? I tried with a bunch of if statements and its just messy and sometimes halves the speed or stops moving

Checking which number is higher(sin or cos) and by that deciding if you should let the X be 0 or the Y be 0.
you can also just check for the player’s position and for the Enemy’s position and manipulate them in order to achieve whatever you need

rustyStriker | 2017-10-23 12:44

The function I gave below does that without using angles or trigonometry, but I believe the problem to solve is how the AI moves for some amount of time, because unlike free movement, 4-directional movement must do an angled path, and that function shouldn’t be called all over again in _process() (or maybe never be used like this in the first place)

Zylann | 2017-10-23 18:05

:bust_in_silhouette: Reply From: Zylann

One problem about moving to a target using only 4 directions is that there are multiple vectors that lead to the same result.

It may always be in two phases: one going horizontally, then vertically, but the other way around also works. If your AI is as far as the target in both X and Y, which direction should it take first? (1,0) or (0,1)?

Now, if you just want a vector to a point but that uses only horizontal or vertical directions, you can isolate the problem into a function, like this:

static func get_direction(from, to):
	var diff = to - from
	if abs(diff.x) > abs(diff.y):
		return Vector2(sign(diff.x), 0)
	else:
		return Vector2(0, sign(diff.y))

But again, you need to think about the effect of this across time, because applying the same approach as a “float” direction will make your AI oscillate weirdly as soon as it reaches the point where X and Y distances are equal.

Hi,
Thank you for the function, but it doesnt work still - if you perfectly align the target position and the from, the node starts moving diagonally towards the target rapidly switching between moving vertically and horizontally

blurymind | 2017-10-23 13:48

This is exactly what I warned you about, read my answer again :stuck_out_tongue:
The function should work in order to give you an immediate 4-direction vector, but it’s not going to solve your problem on its own, you need to think about how your AI behaves across multiple frames. Beware, I also mean this function shouldn’t be used every single frame, because it would cause weird oscillations.

Maybe you need to remember in which direction your AI is going.
For example, you might start going upwards because the target is mostly upwards. You continue going up vertically until it gets horizontally aligned with the target. Then, change direction and go horizontally towards the target.
You may want to refine this algorithm if the target moves though.

That’s a two-phase movement because you want to constrain to 4 directions, and the only way for an AI to go from A to B is to do an angled path.

Zylann | 2017-10-23 17:54

Hi I solved it by introducing grid based movement,so direction is changed only per tile.

Btw is there a way to turn your function into an 8 direction one?

blurymind | 2017-10-23 18:23

Aaah too bad, I came up with something:

extends Sprite

var _dir = null


func _ready():
	set_process(true)


func _process(delta):
	var target = get_global_mouse_pos()
	var pos = get_pos()
	
	if target == pos:
		return
	
	if _dir == null:
		_dir = get_direction(pos, target)
		
	else:
		var diff = target - pos
		diff = Vector2(floor(diff.x), floor(diff.y))
		var is_vertical = _dir.x == 0
		
		if is_vertical:
			if sign(diff.y) != _dir.y or diff.y == 0:
				_dir = get_direction(pos, target)
		else:
			if sign(diff.x) != _dir.x or diff.x == 0:
				_dir = get_direction(pos, target)
	
	var speed = 100.0
	set_pos(pos + _dir * speed * delta)


static func get_direction(from, to):
	var diff = to - from
	if abs(diff.x) > abs(diff.y):
		return Vector2(sign(diff.x), 0)
	else:
		return Vector2(0, sign(diff.y))

For 8 directions I don’t know, unfortunately I don’t have time left to investigate an example right now.
Also keep in mind that the “oscillation” still applies on a turn-based grid, it just means it will pick horizontal and vertical alternatively each turn

Zylann | 2017-10-23 18:27