8-way movement to target?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Kaizu

I want the node to move in a fashion of four arrow keys movement towards set point.
I started with something like this, but this creates obvious problem with twitching, and the node will never achieve “0” in the if statement. Is there a workaround or should I approach it differently?

if Input.is_action_pressed("click"):
	mps = get_global_mouse_pos()
dire = (mps-get_global_pos()).normalized()

if get_global_pos().distance_to(mps) > 10:
	if dire.x>0:
		set_pos(get_pos()+Vector2(1,0)*delta*speed)
	if dire.x<0:
		set_pos(get_pos()+Vector2(-1,0)*delta*speed)
	if dire.y>0:
		set_pos(get_pos()+Vector2(0,1)*delta*speed)
	if dire.y<0:
		set_pos(get_pos()+Vector2(0,-1)*delta*speed)

Just to give more info - I’m on a quest to recreate a simple pathfinding in style of Warcraft game, without using navigation nodes. I don’t know if it’s relevant.

Kaizu | 2018-12-03 21:12

If you need pathfinding and not just simple movement from start to end see A*: https://www.youtube.com/watch?v=Ad6Us73smNs

pospathos | 2018-12-04 07:54

:bust_in_silhouette: Reply From: pospathos

In my opinion you should devide space in grid cells to simulate 8 dir movement, and move from cell to cell (not actual TileMap just virtual grid space for movement, if you already dont use TileMap for level, if you do then things are easier). Cell size is dependent of how granular you want 8dir movement to be (or if you use TileMap in game you should use cell size of TileMap). Devide your start and goal position by cell size so you get start and goal cell positions, and use some of line algorithms ( Line drawing on a grid ) to store grid points of a line deffned by start and goal in array. Then you need to move from one array index to another (multiply cell positions by cell size to get actual pixel position) until you reach the last one. If you need to land exactly on goal point you should replace last cell position from line array with your goal position and move there. Hope it helps.

Two line algorithms:

static func WalkGrid( p0 : Vector2, p1 : Vector2) -> Array:
	var dx = p1.x - p0.x
	var dy = p1.y - p0.y
	var nx = abs(dx)
	var ny = abs(dy)
	var signX = 1 if dx > 0 else -1
	var signY = 1 if dy > 0 else -1
	var p : Vector2 = p0 # Point to walk on grid. set it to Start point
	var points : Array = [p] # Add Start position to array
	
	var ix : int = 0
	var iy : int = 0
	
	while ix < nx || iy < ny:
		if ( ( 1 + ( ix << 1) ) * ny < ( 1 + ( iy << 1) ) * nx ):
			p.x += signX
			ix +=1
		else:
			p.y += signY
			iy += 1
		points.append(p)
	return points

static func BresenhamLine(p0 : Vector2, p1 : Vector2):
	var s = Vector2()
	var dx = abs(p1.x-p0.x);  s.x = 1 if p0.x<p1.x else -1
	var dy = abs(p1.y-p0.y);  s.y = 1 if p0.y<p1.y else -1
	var err = (dx if dx>dy else -dy)/3
	var e2
	var points = []
	
	while true:
		points.append(p0)
		if p0.x == p1.x && p0.y == p1.y:
			break
		e2 = err
		if e2 > -dx:
			err -= dy
			p0.x += s.x
		if e2 < dy:
			err +=dx
			p0.y += s.y
	return points

pospathos | 2018-12-04 07:48

Thanks for the suggestions!
The TileMap solved half of my problems. With grid on board I was able to smooth the moving.
You and the commenter suggested using line/pathfinding algorithms and that’s the thing I want to avoid.
What I came with is:
var target_pos = Vector2()
var unit_pos = Vector2()
var speed = 100

func _input(event):
	if event.is_action_released("rmb"):
		target_pos = get_global_mouse_pos().snapped(Vector2(64,64))
func _process(delta):
	
	if target_pos.x-unit_pos.x < 0:
		set_pos(get_pos()+Vector2(-1,0)*speed*delta)
		if target_pos.x-unit_pos.x > -1:
			set_pos(get_global_pos().snapped(Vector2(64,0)))
	if target_pos.x-unit_pos.x > 0:
		set_pos(get_pos()+Vector2(1,0)*speed*delta)
		if target_pos.x-unit_pos.x < 1:
			set_pos(get_global_pos().snapped(Vector2(64,0)))
	if target_pos.y-unit_pos.y < 0:
		set_pos(get_pos()+Vector2(0,-1)*speed*delta)
		if target_pos.y-unit_pos.y > -1:
			set_pos(get_global_pos().snapped(Vector2(0,64)))
	if target_pos.y-unit_pos.y > 0:
		set_pos(get_pos()+Vector2(0,1)*speed*delta)
		if target_pos.y-unit_pos.y < 1:
			set_pos(get_global_pos().snapped(Vector2(0,64)))
			
	if abs(target_pos.x-unit_pos.x) < 1 and abs(target_pos.y-unit_pos.y) < 1:
		set_pos(target_pos)	
	
	unit_pos = get_global_pos()

Now, if someone could tell if it’s Godot-proper?

Kaizu | 2018-12-05 10:37

It seem to be ok, at least your idea. One question, I am curious: why do you need to simulate 8 dir movement? You can just change appropriate sprite animation direction depending of vector of movement and use Godot’s Navigation2D/NavigationPolygon for movement of units (even on individual tiles you can set NavigationPolygon).

pospathos | 2018-12-05 18:10

Warcraft didn’t used anything as elaborate as Navigation nodes provides.
It’s AI was because of this really bad, and I want to achieve something similar in terms of clunkiness.
Also I don’t want to use pre-calculated lines, paths etc, to make it even more stupid.

Kaizu | 2018-12-06 09:09