Jump to content

Simple Car Improved



Now, I wrote a single script that creates suspension, steer and traction

You have to create the chassis, 4 wheels, set this script to each one of the wheels and then paly with the configurable parameters.


	Autor Juan Ignacio Odriozola (charrua)
		A script that facilitates the making of a simple car
		all you need is a chassis and 4 wheels
		assing this scrip to each wheel and set the object chassis
		then adjust some of the configurable parameters

	Parent: chassis
	entity : wheel
	3 joints and 2 auxiliary entities are created the chain is:
	Parent      Slider            Pivot          Hinge       Pivot        Hinge
	chassis -suspensionJoint- suspensionPivot -steerJoint- steerPivot -wheelJoint- wheel
	suspension uses pin 010		(Y axis)
	steer      uses pin 010		(Y axis)
	wheel      pin 				(must be set depends of wheel orientation)
	up/down keys are defaults for forward and backward
	left/right keys are defaults for steer left/right
	space key is default for brakes
	steer velocity and start/end angle must be set
	suspension lenght must be set
	wheel friction must be set

	steerAngle set both limits to +/- steerAngle/2
	if no key (left/right) is pressed then, target angle is 0 : straight
	suspensionLenght set both limits to +/- suspensionLength/2 and target distance is set to 0

	suspension strength defaults to 1000
		which is too much strenght for a light weight car (20 of mass) and not to much for a 200 car of mass
	each joint is created with a mass of 1, which should be taking into accoung
	(so for a 4 wheels car, you have a mass of 8 on the 8 joints).

	there are so many other parameters that may be adjusted: Spring, Strength, Stiffness ... not too much documented :)


Script.currspeed = 0

Script.chassis = nil--Entity "chassis"
Script.pin = Vec3(0,0,1) --Vec3 "wheel Pin"
Script.motorspeed=500--float "max motor speed"
Script.velcontrolled=false--bool "velControl"
Script.suspensionLength=0.2--float "suspension"

Script.steerAngle=90--float "steer angle"
Script.steerSpeed=100--float "steer velocity"

Script.friction=1--float "wheel friction"



function Script:Start()

	local pos = self.entity:GetPosition(false)	--true for global

	if self.chassis ~= nil then

		self.suspensionPivot = Pivot:Create()
		self.steerPivot = Pivot:Create()
		--joints creation
		self.suspensionJoint = Joint:Slider(pos.x, pos.y, pos.z, 0, 1, 0, self.chassis, self.suspensionPivot)
		self.steerJoint = Joint:Hinge(pos.x, pos.y, pos.z, 0, -1, 0, self.suspensionPivot, self.steerPivot)
		self.wheelJoint = Joint:Hinge(pos.x, pos.y, pos.z, self.pin.x, self.pin.y, self.pin.z, self.steerPivot, self.entity)

		self.suspensionJoint:SetLimits(-self.suspensionLength/2,self.suspensionLength/2)	--steerAngle=0 means no steer
		self.suspensionJoint:SetTargetAngle(0)	--at the middle
		self.suspensionJoint:SetMotorSpeed(1)	-- 1 m/s
		self.suspensionJoint:SetStrength(100)	--defatul is 1000

		self.steerJoint:SetLimits(-self.steerAngle/2,self.steerAngle/2)	--steerAngle=0 means no steer

		self.entity:SetFriction(self.friction, self.friction)

		Debug:Error("no chassis assigned")

function Script:setMotorSpeed(speed)
	if self.velcontrolled then
		--System:Print("setMotorSpeed: "..speed)
		self.currspeed = speed
		if speed~=0 then

function Script:UpdateWorld()

	if self.motorspeed>0 then

	if App.window:KeyDown(Key.Space) then

	if self.velcontrolled then
		if App.window:KeyDown(Key.Up) then
			self.currspeed = self.currspeed + 10
			if self.currspeed>self.motorspeed then
			if self.currspeed == 10 then self.wheelJoint:EnableMotor() end
		if App.window:KeyDown(Key.Down) then
			self.currspeed = self.currspeed - 10
			if self.currspeed<-self.motorspeed then

	if self.steerAngle>0 then
		local direction=0
		if App.window:KeyDown(Key.Left) then
		if App.window:KeyDown(Key.Right) then


In the other maps i was using a box as a floor to which I set the desired friction, testing this new script i use a terrain and have to figure it out how to set the friction to the terrain...

Did some searches and ended with:

local n
	for n=0,self.world:CountEntities()-1 do
		local entity = self.world:GetEntity(n)
		if entity:GetClassName()=="Terrain" then
			terrain = entity
			System:Print("terrain found!")

insert this in the app.lua (after load map) and then you can play with terrain friction, the video shows how the car behaves with the defaul terrain friction and then whit a friction of 10,10

Always learning something new 


A word about some parameters::)

If you are using a hinge, when you specity speed (SetMotorSpeed) the number means degrees per second. So if you use 3600 as max speed you get 10 revoluions per second.

If your tire has, 64cm then d*pi aprox 2 meters per revolution, 10 revolutions per secon aprox 20 meters per second... and if you are lucky 72Km/h

If you are using a slider, then speed is un meters per second. 

Other parameter which is very important is the hinge/slider "pin" which is a vector that tells the direction of movement of the slider or over which plane de hinges open/close

For a common door, we need to use the Y axis, so the pin is 0,1,0

I use this pin for the suspension and for the steer but for this script, you must tell the traction pin, which sould be 1,0,0 or 0,0,1 depending on the orientation of your tires

If your tires are not facing X nor Z axis, then you have to do some math to get the proper x,z component of the pin

In the script I use a Strenght of 100, instead of the 1000 which is default, my car is light weight : 5 + 4*3 = 17 of Mass

chassis has 5, each wheel has 1 and the 2 auxiliary pivots has 1 each one

whith a friction of 10 on each tire and with a friction of 10 on the terrain looks ok for me (better than I spected at first). 


  • Like 3


Recommended Comments

When you use a motor on the slider joint, you are using a constant amount of force for the suspension. A spring will increase the force as it gets further from the resting point.

Share this comment

Link to comment

I could not get anything running with SetSpring() at high speed 🤔

Because  chassis with little mass tends to sink to floor (backward or forward) as speed goes up, that's why big mass required and then I could not find a strong value enough to the string ?

Josh, is SetSpring() compatible with SetStrength() and SetTargetAngle() ?

Share this comment

Link to comment
Just now, Marcousik said:

I could not get anything running with SetSpring() at high speed 🤔

Because  chassis with little mass tends to sink to floor (backward or forward) as speed goes up, that's why big mass required and then I could not find a strong value enough to the string ?

Josh, is SetSpring() compatible with SetStrength() and SetTargetAngle() ?

No, those are motor commands. The motor should not be enabled on the slider joint.

Share this comment

Link to comment

Thanks, I will try it, I was applying scaled strength based on speed in my last (not published still) tests..

Also doing not so logical tests :), but with good resultsresults :)


Share this comment

Link to comment

Yes I did...Mass and suspension force are directly bound.

I used: SetSpring(2000) forward and SetSpring(1000) backward.

As I can't get it better to work, I still prefer with motor enabled 😅 and high mass.

Maybe it is possible better but I just do not find the values 🤷‍♂️

Share this comment

Link to comment

About krazy tests...

video captures are so glitchy, i guess is the combination of leadwerks and the video capture sofware.. 

There are 4 lines of text, the first two are what is the speed aplied to the wheels and so, the estimates speed, based on diameter*PI in m/s and km/s

The last two lines are based on real distance travelled (taking chassis positions each second and calclating distance, with vec3 distance to point function)

I placed the chassis underground and it behaves ok, but beaware of not jump!

The white sphere (is a fake chassis) is placed by hand doing an average of the position of the 4 wheels.

if you set the property Hidden on the editor, then jumps are ok :)

but, behavior is not much as realistic as it should, when you stop, the chassis seems to do a pendulus movement.. 

now, the following two test are with the cassis in the correct place, seen/not seen.

When chassis is correctly placed, as marcosuik says it tends to touch the floor and seems to try to fly

I am using strength scaled (increased) by car speed.


Share this comment

Link to comment

It looks definivly better with hidden() ...

So the sphere is not a child of the chassis ? 

Whatever, replace the sphere with a nice car model and it is ok for a little race game, isn't it ?

Share this comment

Link to comment

Sphere is placed by math at the center of the 4 wheels, so, as you say, any shape is ok. The camera uses the orientation of the real chassis, for that reason it has this strange behavior. I still use a 100 mass car, an strength is scaled as speed rise.

The chassis hits the ground and has 0 friction, I guess is better to hide it and let the fake chassis look nice and slow, perhaps I reduce the movement using some average values to reflect or simulate a more realistic one...


Share this comment

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Create New...