2D RTS movement

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

Hello,

I’ve been trying to develop different kind of games
Started from breakout clone, infinite side scroller, vertical scrolling shooter, etc all these are controlled using button presses.

I’m new to Godot and my next project I want to go ahead in is RTS.
The issue I’m having is that I cant get my units to “animate” towards their goals.

So what I am trying to accomplish is:
After selecting a unit I want the unit to move to the clicked empty spot.
I can get it to the destination in an instant but not by “animation”(tweening? not sure if I’m calling it correct)

I have tinkered for a while and noticed I need to make use of vectors.
Even though I found (and learned) quite a bit about them, I still have a hard time to make proper use of it.

Looking for a guide/tutorial didn’t have the success I hoped.
Hopefully one of you can aid me in the right direction.

Thank you for your time reading trough this!

Cheers,
Santouryuu

Edit: I should have added I’m focused on 2D only at the moment

:bust_in_silhouette: Reply From: PMurrayDesign

I think what you need to look at is linear interpolation (lerping). This should give you the movement from their unit’s current Vector2 to the new one.

Thanks for your reply!
I have looked into lerping.
I got the sprite moving sadly enough not the way I want.
Clicking on the destination creates multiple point at which the sprite needs to move along.
Every time I click I can see the path plotted.
But the sprite doesn’t move past the first point.
I have tried multiple ways but with no success.
I have the following at the moment:

  func _input(event):
	if event.type == InputEvent.MOUSE_BUTTON and event.button_index == BUTTON_LEFT:
		mPos = event.global_pos
		var uPos = get_global_pos()
		var spriteRect = Rect2(uPos.x, uPos.y, tsize.x, tsize.y)
		if spriteRect.has_point(mPos):
			selectorNode.set_opacity(1)
		elif selectorNode.get_opacity():
			destination = mPos
			
func _fixed_process(delta):
	if destination != null:
		if get_node("../Navigation2D").path.size():
			var path = get_node("../Navigation2D").path
			for i in range(path.size()):
				print(i)
				set_global_pos(get_global_pos().linear_interpolate(path[i], delta))

Santouryuu | 2017-06-21 10:13

:bust_in_silhouette: Reply From: Kamil Lewan

Maybe Path2D/Curve2D 'll help you. After pathfinding (by a* or custom pathfinder), you should have var with list of vectors with position of each tile(?) or point (in global, or
fictitious units (like tiles), if 2nd option, you must multiple vector in set_pos() by size of tile).
When you have it, you should do (in animation, or (i think better) in _process() an animation.
If it don’t work on tiles, you may use Path2D and get_baked_points(), like

_process(delta):
   set_pos(your_path_2d.get_baked_points()[number_of_frame])

EDIT: Final code:
GitHub demo
Map code:

tool
extends Polygon2D

# map size in pxs
export var map_size_x = 500
export var map_size_y = 500
# move map from left top corner
export var init_pos_x = 150
export var init_pos_y = 150

func _ready():
	# create polygon
	set_pos(Vector2(init_pos_x,init_pos_y))
	set_polygon(Vector2Array([
	Vector2(0,0),
	Vector2(map_size_x,0),
	Vector2(map_size_x,map_size_y),
	Vector2(0,map_size_y)]
	))
	# move Nav2D poly to true pos (with position of map node)
	var _buff_poly = Vector2Array([])
	for i in get_polygon():
		_buff_poly.append(i+get_pos())
	# set polygon to Nav2D
	var _nav_poly = NavigationPolygon.new()
	_nav_poly.add_outline(_buff_poly)
	_nav_poly.make_polygons_from_outlines()
	get_node(\"Navigation2D\").navpoly_create(_nav_poly,Matrix32(0,Vector2(1,1)))

Unit code:

extends Sprite

var movement_path = Curve2D.new()
var speed = 250.0
var pos_on_path = 0

func _ready():
	# that's in pixels (probably)
	movement_path.set_bake_interval(1)
	set_process_unhandled_input(true)
	set_fixed_process(true)

func _unhandled_input(event):
	if event.type == InputEvent.MOUSE_BUTTON and event.pressed:
		if event.button_index == BUTTON_RIGHT:
			movement_path.clear_points()
			movement_path.add_point(get_pos())
			movement_path.add_point(event.global_pos)
			var _buffer = get_node(\"../A passable map/Navigation2D\").get_simple_path(movement_path.get_point_pos(0),movement_path.get_point_pos(1))
			movement_path.clear_points()
			for i in _buffer:
				movement_path.add_point(i)
			pos_on_path = 0

func _fixed_process(delta):
	
	if round(pos_on_path+speed*delta) < movement_path.get_baked_points().size():
		pos_on_path += speed*delta
		set_pos(movement_path.get_baked_points()[round(pos_on_path)])

Thanks for your reply!
If tried using the function get_baked_points, since I m not using tilemaps.
but it keep giving me an error.
I have the following:

func _fixed_process(delta):
	if get_node("../Navigation2D").path.size():
		set_pos(get_node("../Navigation2D").path.get_baked_points())

I get the errror:

Invalid call. Nonexistent function ‘get_baked_points’ in base
‘Vector2Array’.

I have printed the path it does return some values I have an example at the bottom)

What do you mean by [number_of_frame], am I missing something?

if I print get_node("../Navigation2D").path I receive:

[(81.000305, 125.136993), (413, 382)]

Santouryuu | 2017-06-21 10:09

The problem is get_baked_points return array of points, so you can’t just use set_pos(). You must use set_pos(get_baked_points()[a]). That a is number of backed point, like pixel by pixel. So if you want move 1px/frame you should have something like this:

act_pos_in_array = 0
func _process(delta):
   set_pos(get_baked_points()[act_pos_in_array])
   if act_pos_in_array < get_baked_points().size()
      act_pos_in_array += 1

Other thing, what’s path in your code?

Kamil Lewan | 2017-06-23 17:04

First of all thanks for sticking with me!
And I Apologize for my late reply.

I kinda got it to work sprite moves now but not in a smooth motion.
It goes to the first point than waits a little then goes to the next and again halts for a fraction of a second.

func _input(event):
if event.type == InputEvent.MOUSE_BUTTON and event.pressed:
	if event.button_index == BUTTON_RIGHT:
		end = event.global_pos
		start = get_node("Tank").get_pos()
		transition = 0

func _process(delta):

path = get_node("Navigation2D").get_simple_path(start, end)
if path.size() < path_points:
	transition = 0

path_points = path.size()

if path_points < 2: return
var sprite = get_node("Tank")

sprite.set_global_pos(sprite.get_global_pos().linear_interpolate(path[1], transition))
start = sprite.get_global_pos()
transition += speed * delta
if transition > 1: transition = 0

This is currently the code.
Tank is a rigidbody2d object.

I would very much appreciate it if you were able to tell me a way to make the motion smoother

Santouryuu | 2017-07-03 13:52

I think, i can’t explain that easly. I pushed demo project to GitHub, hope it will be helpful.
It’s not best way to do that, but it works, so it’s good to prototyping…

Kamil Lewan | 2017-07-04 11:36

Again thank you very much!!
At least I get the logic behind it now.
Cheers buddy

Santouryuu | 2017-07-04 13:28