Better way to handle this movement?

Hi all,

I’m trying to get a snake like movement working in my game which I cannot solve. The movement I’m after is exactly like Nimble Quest:

I’m pretty close I think here is what I’ve got:

function M.update(self, dt, mself)
	local position = go.get_position()

	if self.current_direction == MOVEMENT_DIRECTION.up then
		position.y = position.y + self.movement_speed * dt
	elseif self.current_direction == MOVEMENT_DIRECTION.down then
		position.y = position.y - self.movement_speed * dt
	elseif self.current_direction == MOVEMENT_DIRECTION.left then
		position.x = position.x - self.movement_speed * dt
	elseif self.current_direction == MOVEMENT_DIRECTION.right then
		position.x = position.x + self.movement_speed * dt
	end

	position.z = -position.y / 100
	go.set_position(position)

	if self.leader ~= hash('/nil') and #self.turning_points > 0 then
		local pos = go.get_position()
		local turn_point = self.turning_points[1]

		if defmath.dist2d(pos.x, pos.y, turn_point.position.x, turn_point.position.y) < 0.055 then
			go.set_position(turn_point.position)
			change_direction(self, turn_point.direction, mself)
			table.remove(self.turning_points, 1)
		end
	end
end

So the character continue to move forward until it hits a turning point, which is just position and direction stored in a table. The problem I think is because position.y + speed * dt sets the position to a floating number the character either misses the point or continues to get a little closer/further away to the leader character and eventually ends up on top of each other.

Here is how I add a turning point:

function change_direction(self, new_direction, mself)
	if self.current_direction == new_direction then
		return
	end
	
	self.current_direction = new_direction
	
	local turn_point = {
		position = go.get_position(),
		direction = self.current_direction
	}

	if self.follower ~= hash('/nil') then
		msg.post(self.follower, 'leader_turned', { turning_point = turn_point })
	end
end

Tried just doing position.y = position.y + 2 so I get whole numbers on the movement but it’s too fast and I’d like different characters to have different movement speeds. Another way I tried was too add

self.t = self.t + dt

if self.t >= 0.333 then

end

Which slowed the movement down using position.y + 2 but made the movement really choppy. Finally I used defmath.round(position) and stored it in self.rounded_position and did my checks with that but had the same issue of following characters getting to close to each other / missing the turning point.

Could anyone give me any advice?

Thanks.

I guess the problem is that you only move the character up to the next turning point and never further than that. Imagine a very high dt of say 2 (yes, unrealistic I know) and a movement speed of 100 pixels per second. That means that the character should move 200 pixels right? Now, what if the next turning point is only 20 pixels away? In your code you’d first of all completely miss the turning point since you check that the distance is close to zero (0.055). And even if you did solve this some other way you’d still only set the character position to the position of the turning point and call it a day. What happens with the other 180 pixels that the character was supposed to move? In your solution you simply ignore the rest of the distance after passing the turning point and the character would fall behind the leader by quite a bit.

The solution is to do something like this (untested):

local function move_in_direction(self, distance)
	local position = go.get_position()

	if self.current_direction == MOVEMENT_DIRECTION.up then
		position.y = position.y + distance
	elseif self.current_direction == MOVEMENT_DIRECTION.down then
		position.y = position.y - distance
	elseif self.current_direction == MOVEMENT_DIRECTION.left then
		position.x = position.x - distance
	elseif self.current_direction == MOVEMENT_DIRECTION.right then
		position.x = position.x + distance
	end

	position.z = -position.y / 100
	go.set_position(position)
end

function M.update(self, dt, mself)
	local distance_to_move = self.movement_speed * dt

	if self.leader ~= hash('/nil') then
		-- move to turning points as long as we have any and as long as
		-- we still have some distance to move this frame
		while #self.turning_points > 0 and distance_to_move > 0 do
			-- get next turning point and calculate the distance to it
			local turn_point = self.turning_points[1]
			local pos = go.get_position()
			local distance_to_turn_point = defmath.dist2d(pos.x, pos.y, turn_point.position.x, turn_point.position.y)
			-- will we reach or go beyond the turning point?
			-- if yes, then move to the turning point, turn, and reduce
			-- the distance we have left to move this frame
			if distance_to_move >= distance_to_turn_point then
				move_in_direction(self, distance_to_turn_point)
				change_direction(self, turn_point.direction, mself)
				table.remove(self.turning_points, 1)
				distance_to_move = distance_to_move - distance_to_turn_point
			end
		end
	end

	-- move the rest of the way
	move_in_direction(self, distance_to_move)
end
4 Likes

@britzl I had to change the while statement to an if statement as it was crashing but that works perfectly. Thank you for your help and taking the time to post!

Oh, ok, well, the code was untested so I’m not surprised. What kind of crash did you get? Changing it to an if-statement should in general not have any effect, unless the distance to move is very long or the distance between two control points is very short

I’m assuming it’s an infinite loop as I don’t get any errors it just freezes when I turn, I’ve attached the debugger and it’s stuck in the loop.

Ah, yes. There should be a break in there:

function M.update(self, dt, mself)
	local distance_to_move = self.movement_speed * dt

	if self.leader ~= hash('/nil') then
		-- move to turning points as long as we have any and as long as
		-- we still have some distance to move this frame
		while #self.turning_points > 0 do
			-- get next turning point and calculate the distance to it
			local turn_point = self.turning_points[1]
			local pos = go.get_position()
			local distance_to_turn_point = defmath.dist2d(pos.x, pos.y, turn_point.position.x, turn_point.position.y)
			-- will we reach or go beyond the turning point?
			-- if yes, then move to the turning point, turn, and reduce
			-- the distance we have left to move this frame
			if distance_to_move >= distance_to_turn_point then
				move_in_direction(self, distance_to_turn_point)
				change_direction(self, turn_point.direction, mself)
				table.remove(self.turning_points, 1)
				distance_to_move = distance_to_move - distance_to_turn_point
			-- we will not reach the turning point, jump out and move the rest
			-- of the way
			else
				break
			end
		end
	end

	-- move the rest of the way
	move_in_direction(self, distance_to_move)
end
3 Likes