Moving a game object to a target position (SOLVED)

Hey everyone, I’m new to Defold so I’m just working my way through basic stuff before moving onto actual projects.

Right now I’m trying to move a “player” game object to a target position defined in self.target. Basically I’m trying to make the player move to where I tap.

Both the position and self.target are vectors, and I don’t know much vector math or trigonometry at all, so I’m struggling to find any kind of possible solution to this. I’m aware of go.animate(), but I need the player to move at a constant speed rather than defining a duration for a tween, hence why I’ve turned to vector math.

What would be the solution to this? Do I need to use vector math, or does Defold have a function I can use for this? If the answer is vector math, can you explain how the math works?

Any help is appreciated.

https://www.pkeod.com/defold/2D-FOV-Field-of-View/

Do you want the movement to be similar to this?

That’s almost exactly what I want, all I’d change is to make the player move to the target position on a single tap instead of needing to hold.

I’m taking a break for now, but I might be able to figure it out just by looking at that code, so thanks.

Yes, you need to learn a bit of vector math. (There are plenty of easy going resources on this online, here’s one)

In this case, you have two points (which are represented as vector3), and to figure out how to move from one point to the other, you need the difference between the two.

local diff = self.target - go.get_position()

This new vector is not really a new position but a direction, and the length of that vector is the distance. If you add a bit of that direction to your position each frame, you will end up at the target position.

local oldpos = go.get_position()
local diff = self.target - oldpos
-- move 10% of the remaining distance each frame
local newpos = go.get_position() + diff * 0.1
go.set_position(newpos)

This will move the object in a non linear speed (not a constant speed), and figuring out these animations can be tricky. So we have a helper function for this.

In Defold, we have the function go.animate(), which we use a lot.

go.animate(".", "position", go.PLAYBACK_ONCE_FORWARD, self.target, go.EASING_LINEAR, 1.0)

This will result in a smooth linear animation of your game object during one second.

2 Likes

Here’s a dead simple example leveraging go.animate():

And you can calculate the duration based on the distance to get a constant speed:

-- distance from own position to target
local from = go.get_position()
local to = self.target
local distance = vmath.length(from - to)
-- 200 pixels/s
local duration = distance / 200
go.animate(".", "position", to, go.EASING_LINEAR, duration)
2 Likes

That’s clever! I didn’t know tweens could be used this way, this might be the solution I use for now. That said I won’t be ignoring math and I’ll be making good use of the link Mathias posted.

There’s still a problem though, if I try to touch somewhere to set a new self.target, I always move towards the bottom left of the screen, no matter where I touch.

Here’s my code:

function on_input(self, action_id, action)
	if action_id == hash("touch") and action.pressed then
		-- get target position at touch position
		self.target = vmath.vector3(math.floor(action.screen_x), math.floor(action.screen_y), go.get_position().z)
		print(self.target)
		
		-- calculate the distance and duration, then use go.animate to move to that position
		local distance = vmath.length(go.get_position() - self.target)
		local duration = distance / self.speed	-- handy way to maintain a consistent speed with tweens
		go.animate(".", "position", go.PLAYBACK_ONCE_FORWARD, self.target, go.EASING_LINEAR, duration)
	end
end

I have tried both x and screen_x, with no luck. Any ideas?

Are you using a camera? If that is the case then you need to translate from screen to world space.

Nope. This is a blank mobile template with a tile map, atlas, and a couple of tile sources. The only script in the game is player.script:

function init(self)
	msg.post(".", "acquire_input_focus")
	self.speed = 100
	self.target = go.get_position()
    msg.post("@render:", "use_fixed_projection", { zoom = 1 })
end

function on_input(self, action_id, action)
	if action_id == hash("touch") and action.pressed then
		-- get target position at touch position
		self.target = vmath.vector3(math.floor(action.screen_x), math.floor(action.screen_y), go.get_position().z)
		print(action.screen_x .. " " .. action.screen_y)
		print(self.target)
		
		-- calculate the distance and duration, then use go.animate to move to that position
		local distance = vmath.length(go.get_position() - self.target)
		local duration = distance / self.speed	-- handy way to maintain a consistent speed with tweens
		go.animate(".", "position", go.PLAYBACK_ONCE_FORWARD, self.target, go.EASING_LINEAR, duration)
	end
end

-- If the game looks weird on iOS, change the settings back.
-- Use 180 x 320 for screen width and height, and set zoom to 4.

I did try removing the line that posts use_fixed_projection, but that didn’t change anything.

I want to try converting screen space to world space, but the only results I can find on Google are solutions using projection and view matrices, which might be far beyond my knowledge at the moment…

Is the game object with the script a child of another object?

And what does self.target print?

That game object is a child of the bootstrap collection. There are no other collections in game, and the only other game objects are also children of the bootstrap. self.target prints a vector3 which appears to print the correct coordinates, the bottom left is (0, 0).

After a bit more digging I found Rendercam from the asset portal, which has a handy screen_to_world_2d() function in it. I’ve set it up based on its instructions, but after adding the following code, it hasn’t changed a thing:

	-- convert screen coords to world coords
	local mouseworldpos = rc.screen_to_world_2d(action.screen_x, action.screen_y)
	print("Screen: " .. tostring(self.target) .. ", World: " .. tostring(mouseworldpos))
	
	-- calculate the distance and duration, then use go.animate to move to that position
	local distance = vmath.length(go.get_world_position() - mouseworldpos)
	local duration = distance / self.speed	-- handy way to maintain a consistent speed with tweens
	go.animate(".", "position", go.PLAYBACK_ONCE_FORWARD, mouseworldpos, go.EASING_LINEAR, duration)

mouseworldpos prints a vector3 with (0, 0) when I click (0, 0) in the collection, so Rendercam seems to be functioning correctly, but I still don’t have anything to go on.

Just in case it’s important information, the player doesn’t move directly towards the bottom left, it just moves in that general direction depending on where I click.

I’m going to sleep now, but I’ll be able to continue with this in the morning.

Zip the project and share it. There’s something you’re overlooking.

Here’s that project file (the player object is just off the top right of the screen in game:

retro-top-down.zip (263.8 KB)

It’s actually working just fine, but the sprite on your “player” game object is offset by quite a lot, so it moves off screen. The sprite’s position should probably be (0, 0, 0).

Another little thing, about your Z positions. Your “player” game object is at Z = 1, so it draws above the tilemap, but the position you get from rendercam.screen_to_world_2d will have Z = 0, so when it finishes animating to that position it may be underneath the tilemap. You could change your camera settings so screen_to_world_2d gives you Z = 1, or you could modify the vector after you get it, but I always find it easier to keep my game objects at Z = 0 and use the sprite Z position to get the draw order I want. (So in your case, set the “player” game object to Z = 0 and the sprite to Z = 1.)

3 Likes

I knew that was going to mess me up someday. Thanks for pointing that out, it works now.

Is there a correct way to move around game objects without having to worry about its components, or do you just have to be very careful?

Well, once your game gets more complicated, you’ll definitely want to put your player in its own collection (or in a game object file if it’s really simple). It will be a lot harder to make this sort of mistake if you do it that way.

2 Likes

I know this topic has been solved…but, if you’d like to see a video that walks through how to move your game objects around the screen…I’d recommend the following:

7 Likes