Jump to content

Simple Car Improved

Charrua

731 views

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.

Enjoy:

--[[
	Autor Juan Ignacio Odriozola (charrua)
	Purpose:
		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"

Script.steerPivot=nil
Script.suspensionPivot=nil

Script.steerJoint=nil
Script.suspensionJoint=nil
Script.wheelJoint=nil

function Script:Start()

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

	if self.chassis ~= nil then

		self.suspensionPivot = Pivot:Create()
		self.suspensionPivot:SetPosition(pos)
		self.suspensionPivot:SetMass(1)
		self.suspensionPivot:SetCollisionType(Collision.None)
		
		self.steerPivot = Pivot:Create()
		self.steerPivot:SetPosition(pos)
		self.steerPivot:SetMass(1)
		self.steerPivot:SetCollisionType(Collision.None)
		
		--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)

		--suspension
		self.suspensionJoint:EnableLimits() 
		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.suspensionJoint:EnableMotor()

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

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

	else
		Debug:Error("no chassis assigned")
	end
end


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

function Script:UpdateWorld()

	if self.motorspeed>0 then
		self.wheelJoint:SetAngle(self.wheelJoint:GetAngle()+100)
	else
		self.wheelJoint:SetAngle(self.wheelJoint:GetAngle()-100)
	end

	if App.window:KeyDown(Key.Space) then
		self:setMotorSpeed(0)
	end

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

	if self.steerAngle>0 then
		local direction=0
		if App.window:KeyDown(Key.Left) then
			direction=-self.steerAngle/2
		end
		if App.window:KeyDown(Key.Right) then
			direction=self.steerAngle/2
		end
		self.steerJoint:SetAngle(direction)
	else
		self.steerJoint:SetAngle(0)
	end
end

 

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!")
			terrain:SetFriction(10,10)
			break
		end
	end

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). 

Juan

  • Like 3


15 Comments


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.

Juan

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.

Guest
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.

  • Blog Entries

    • By Haydenmango in Snowboarding Development Blog 6
      So I've been researching snowboarding lately to get an idea of what animations and mechanics I need to create for my game.  I have learned lots of interesting things since I've only seen snow once or twice in my entire life and have never even tried snowboarding or any other board sports (skateboarding, surfing, etc.) for that matter.
       
      Snowboarding tricks are quite interesting as they are mostly derived from skateboarding.  Snowboarding tricks pay homage to their equivalent skating tricks by sharing many concepts and names.  For example basic grabs in snowboarding share the same concepts and names as skateboarding: indy, mute, method, stalefish, nosegrab, and tailgrab.  Something interesting to note is in snowboarding you can grab Tindy or Tailfish but this is considered poor form since these grabs can't be done on a skateboard (due to the board not being attached to the skaters feet) and grabbing these areas is generally something a novice snowboarder does when failing or "half-assing" a normal grab.  Check out this diagram to see how grabs work -
       
       
      So, after reading lots of text descriptions for tricks I was still confused by what all these terms meant and how they were actually applied.  So my next step was to look up these tricks actually being done and I found some really cool videos showing off how to do various tricks.  This video in particular is the best reference material I've found as it contains nearly every trick back to back with labeled names and some tweaks -
       
      Sadly my rigged model doesn't handle leg animations with the snowboard that well so I can't animate as many tricks as I want to.  Regardless there will still be around 15 total grab/air tricks in the game.  Now it's time for me to stop procrastinating and start animating!  
    • By jen in jen's Blog 3
      I thought I would share my experience on this; if you're working on Multiplayer, you will need to protect your packets. The solution is simple, let's go through how we can achieve this by implementing what Valve calls "challenge codes". (Some reading on the topic from Valve here: https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol#Challenge_response).
      Disclaimer: this doesn't cover other security techniques like authoritative server or encryption.
      So, I've worked on Border Recon last year (I think) and I needed a way to protect my server/client packets. There was no need for me to re-invent the wheel, I just had to copy what Valve has had for a  long time - challenge  codes.
      The idea behind challenge codes is similar to Captcha, but not exactly. Think of it like this: for every packet submitted to the server, it must be verified - how? By requiring the client to solve challenges our server provides.
      To implement this we need to have the following:
      A randomised formula in the server i.e.: a = b * c / d + e or a = b / c + d - e, be creative - it can be any combination of basic arithmetic or some fancy logic you like and can be however long as you want - do consider that the longer the formula, the more work your server has to do to make the computation.  Copy the same formula to the client. A random number generator.  So the idea here is:
      (Server) Generate a random number (see 3 above) of which the result would become the challenge code, (Server) run it through our formula and record the result. (Client) And then, we hand over the challenge code to the client to solve (an authentic client would have the same formula implemented in its program as we have on the server). For every packet received from the player, a new challenge code is created (and the player is notified of this change by the server in response). For every other packet, a new challenge code is created. (Client) Every packet sent to the server by the client must have a challenge code and its answer embedded.  (Server receives the packet) Run the challenge code again to our formula and compare the result to the answer embedded on the client's packet. (Server) If the answers are different, reject the packet, no changes to the player's state. The advantage(s) of this strategy in terms of achieving the protection we need to secure our server:
      - For every packet sent, new challenge code is created. Typically, game clients (especially FPS) will update its state in a matter of ms so even if a cheater is successful at sniffing the answer to a challenge code it would be invalidated almost instantaneously. 
      - Lightweight solution. No encryption needed. 
      Disadvantage(s):
      - The formula to answering the challenge code is embedded to the client, a cheater can de-compile the client and uncover the formula. Luckily, we have other anti-cheat solutions for that; you can implement another anti-cheat solution i.e. checking file checksums to verify the integrity of your game files and more (there are third-party anti cheat solutions out there that you can use to protect your game files).
       
       
       
    • By Josh in Josh's Dev Blog 4
      New commands in Turbo Engine will add better support for multiple monitors. The new Display class lets you iterate through all your monitors:
      for (int n = 0; n < CountDisplays(); ++n) { auto display = GetDisplay(n); Print(display->GetPosition()); //monitor XY coordinates Print(display->GetSize()); //monitor size Print(display->GetScale()); //DPI scaling } The CreateWindow() function now takes a parameter for the monitor to create the window on / relative to.
      auto display = GetDisplay(0); Vec2 scale = display->GetScale(); auto window = CreateWindow(display, "My Game", 0, 0, 1280.0 * scale.x, 720.0 * scale.y, WINDOW_TITLEBAR | WINDOW_RESIZABLE); The WINDOW_CENTER style can be used to center the window on one display.
      You can use GetDisplay(DISPLAY_PRIMARY) to retrieve the main display. This will be the same as GetDisplay(0) on systems with only one monitor.
×
×
  • Create New...