Movement tutorial, velocity cap

Hello, i just started working with LUA and Defold, and have been doing the tutorials. When i finished the movement tutorial, there were some extra exercises i wanted to complete. I want to make a movementspeed cap for my object, but i cant seem to solve it.

local acceleration = self.input * 200

local dv = acceleration * dt
local v0 = self.velocity
local v1 = self.velocity + dv
local movement = (v0 + v1) * dt * 0.5

local p = go.get_position()
go.set_position(p + movement)

self.velocity = v1
self.input = vmath.vector3()

I wonder if there is a way to assign a max and min value for my vector v1, which will make the movement varible have a max, and min value? Thanks for help!

Hi and welcome!
It sounds that it should be possible to use the built-in functions math.min and math.max around movement. :slight_smile:

Hey, sorry for bumping but it’s the first answer on google.
I’m new too to Defold and Lua, AND working with vector3.
I don’t get how to use min/max on vectors to limit speed.
I did something different:

if vmath.length_sqr(movement) > max_speed then --max_speed = 120
    movement = vmath.normalize(movement) * max_speed
end

Only problem is that spaceship fly off when it reaches my supposed max_speed when it should have be limited to max_speed.

EDIT:
I noticed that I forgot about delta time multiplication (movement = vmath.normalize(movement) * max_speed * dt)

1 Like

Try using the vmath.length function instead.

1 Like

Got stuck on this myself, it’s still the first result on Google so I wanted to continue the thread.

I’ve already tried the above two methods. Comparing the movement’s length_sqr to the max speed causes the ship to increase in speed drastically, and using vmath.length just slows it down instead.

I’ve also considered using math.min, but I don’t know how you would use it in this context. You can’t use it on movement since it’s a vector, and the only scalar that changes speed is the acceleration, which never reaches max_speed.

How else would you approach this?

Can you please share your movement code?

Here’s my update function, pretty much identical to the tutorial aside from maybe 1 or 2 things:

if vmath.length_sqr(self.input) > 1 then
	self.input = vmath.normalize(self.input)
end

local acceleration = self.input * self.speed * dt
	
local v0 = self.velocity
local v1 = self.velocity + acceleration

local movement = (v0 + v1) * dt * 0.5
	
local p = go.get_position()
go.set_position(p + movement)

self.velocity = v1
self.input = vmath.vector3()

on_input just changes self.input depending on key press.

Not exactly the same code as you had but it should handle direction, speed, acceleration and max speed:

go.property(acceleration, 100)
go.property(max_speed, 1000)

function update(self, dt)
	-- there shouldn't really be any need to do this
	local direction = vmath.normalize(self.input)

	if vmath.length(direction) > 0 then
		-- increase speed if we are moving in any direction
		self.speed = self.speed + self.acceleration * dt
		-- cap speed
		self.speed = math.min(self.speed, self.max_speed)
	else
		-- decrease speed here when no key is pressed
		-- how you want this to happen is up to you
		-- speed = 0 immediately?
		-- speed decreased every frame?
	end

	-- move the game object
	local p = go.get_position()
	p = p + direction * self.speed * dt
	go.set_position(p)

	-- reset input
	self.input = vmath.vector3()
end

function on_input(self, action_id, action)
	-- update self.input based on currently pressed keys
end
3 Likes

That’s much simpler than the tutorial code, thanks. Only thing I had to change was the first line of code, because direction was vmath.vector3(nan, nan, nan) for some reason. self.input was initialized to vmath.vector3().

Here’s my replacement code for that, for future reference:

local direction = vmath.vector3()
if vmath.length(self.input) > 0 then
	direction = vmath.normalize(self.input)
end

I think you see nan because you cannot normalize the zero vector.

Added as a new example: https://defold.com/examples/basics/movement_speed/

The go get stuck after a while I am moving it… Indeed I don’t understand this line:

self.direction = vmath.normalize(self.direction + self.input)

Note that self.direction + self.input may be 0… Perhaps there is something I am missing…

Yeah, I looked at that example recently and found an issue at that line.

If you move left, then immediately try to move right, you get a self.direction of (-1, 0, 0) and a self.input of (1, 0, 0). This means that you end up adding the two together to get a zero vector, and try to normalize it. This sets self.direction to a vector of (nan, nan, nan), which breaks the script. I’d assume this happens with up and down too.

My quick and dirty solution was to check for a nan vector and replace it with a zero vector before it got reused:

-- nan is not equal with itself, so use this for comparison check
if self.direction.x ~= self.direction.x then
    self.direction = vmath.vector3()
end
1 Like

Good point. My bad! The idea was to allow for some inertia in changing direction, but that complicated the example (and introduced the bug you found). I’ve changed the example to directly use self.input as direction.

2 Likes

Here is my attempt to this. Note that probably this is not what you want. My idea is that, the closer to the “real world physics” the more the result is natural. On the other hand you pay this in some hardness in constant tuning.

go.property("speed_decay", 2.0)
go.property("force", 500.0)

function init(self)
	msg.post(".", "acquire_input_focus")
	self.input = vmath.vector3(0.0)
	self.speed = vmath.vector3(0.0)
	self.position = go.get_position(".")
end

function update(self, dt)
	self.speed = self.speed + dt * self.force * self.input
	self.speed = self.speed * (1.0 - self.speed_decay * dt)
	self.position = self.position + dt * self.speed
	go.set_position(self.position)
	self.input = vmath.vector3(0.0)
end

function on_input(self, action_id, action)
	if action_id == hash("up") then
		self.input.y = 1.0
	elseif action_id == hash("down") then
		self.input.y = -1.0
	elseif action_id == hash("left") then
		self.input.x = -1.0
	elseif action_id == hash("right") then
		self.input.x = 1.0
	end
	local length = vmath.length(self.input)
	if length > 0.1 then self.input = self.input / length end
end

EDIT: moved the normalization at the end of on_input; there is no need to check for normalization at each frame.

@britzl Thank you for formatting the code… Still I don’t understand how to do that, really!

1 Like

Hey guys! Noob in Defold here!
This is my first post. Hopefully somebody will find it usefull.
Anyway, i think i figred out the most simple solution. Here’s what the whole code file look likes:

local max_speed = 200

function init(self)
	msg.post(".", "acquire_input_focus")
	self.velocity = vmath.vector3()             -- [1]
	self.input = vmath.vector3()
end

function update(self, dt)
	if vmath.length_sqr(self.input) > 1 then
		self.input = vmath.normalize(self.input)
	end

	local acceleration = self.input * 200       -- [2]

	local dv = acceleration * dt                -- [3]
	local v0 = self.velocity                    -- [4]
	local v1 = self.velocity + dv               -- [5]

	if vmath.length_sqr(v1) > max_speed*max_speed then
		v1 = vmath.normalize(v1) * max_speed
		print(v1)
	end
	
	local movement = (v0 + v1) * dt * 0.5       -- [6]

	local p = go.get_position()
	go.set_position(p + movement)               -- [7]

	self.velocity = v1                          -- [8]
	self.input = vmath.vector3()
end

function on_input(self, action_id, action)
	if action_id == hash("up") then
		self.input.y = 1                     -- [1]
	elseif action_id == hash("down") then
		self.input.y = -1                    -- [1]
	elseif action_id == hash("left") then
		self.input.x = -1                    -- [1]
	elseif action_id == hash("right") then
		self.input.x = 1                     -- [1]
	elseif action_id == hash("click") and action.pressed then
		print("CLICK!")
	end
end

The only things i added to the code in the tuturial are:

local max_speed = 200

--and

	if vmath.length_sqr(v1) > max_speed*max_speed then
		v1 = vmath.normalize(v1) * max_speed
		print(v1)
	end

What i think i noticed is that some people forget that vmath.length_sqr(self.input) returns a squared value of components of vector3 (x^2 + y^2 + z^2). And if we want to compare it to some “max_speed” value, we should square it : max_speed*max_speed

I’m not at all sure if this is by any means an efficient solution and a “good practice”, but it looks rather simple and clean to me. And most importantly it works!!

5 Likes