MotorVehicle - Chassis Programming Made Easy

MotorVehicle

Chassis Programming Made Easy

About

Programming chassis in video games is a very overwhelming, difficult, as well as tedious process that is hard to get it just right, this library was written to solve this problem. It’s also a great place to dissect the code to learn on how to write your own chassis.

If you are planning on making a realistic car racing game, it’s probably best in your interest to avoid this library as it is not meant to be used in racing games. But if you are making a game like Bloxburg, Emergency Response: Liberty County, or something that doesn’t primarily focus on motor vehicles and you want to save yourself some time, this is probably a library for you.

Showcase

Getting Started

You will need to get the library and initialize a new instace of the class.

Source Code: GitHub
Roblox Model: Roblox
Example Place:
MotorVehicle Example Project.rbxl (764.4 KB)

local bus = MotorVehicle.new({
	root = VehicleSeat,
	wheels = {Chassis.RL, Chassis.RR},
	torque = 1000,
	maxSteerAngle = 45,
	turnSpeed = 3,
	gearRatio = {2.97, 2.07, 1.43, 1.00, 0.84, 0.56},
})

And whenever you are ready, you can compute properties via the method below:

local output = bus:compute(deltaTime, {
	steerFloat = -VehicleSeat.SteerFloat,
	throttleFloat = VehicleSeat.ThrottleFloat
})
Documentation

You will need to know how to rig a vehicle before doing this, I highly recommend watching this tutorial.

Initialization Configuration

root

Type: BasePart


The most important part of the vehicle, it will be used to determinate certain things like direction, speed, etc.

wheels

Type: {BasePart}


An array of wheels that will be driven (aka “powered”) by the motor, those will be used to determinate certain states, the library will not drive them, that will be your job to do so.

torque

Type: number (preferablly an integer)


The measurement of your car’s ability to do work, meaning that the more torque, the greater amount of power an engine can produce. If your engine has a lot of torque, your car can accelerate more quickly when the vehicle is beginning to start (explanation taken from kia website).

maxSteerAngle

Type: number (preferablly an integer)


Determinates how far the wheel can be turned (angle in degrees).

turnSpeed

Type: number


Arbitrary number which defines the top speed at which the wheels can turn.

gearRatio

Type: {number} (preferablly a float)


Ordered array of floats that determinate ratio per gear (their order matters as the system takes ratio from the left side and as the gear goes up the index which is used to take the gear ratio will also increase, move to the right).

image

Image source: https://www.theengineerspost.com/gear-ratio/

Methods

getPitch

Returns: number


Computes pitch for the engine sound. Make sure to perform extra math operations on the returned value to suits your specific audio (eg. pitch + 0.5). It automatically clamps the number to prevent large spike when vehicle falls or glitches out (aka you won’t go deaf when the car glitches out at 3000 studs per second).

getSpeed

Returns: number


Determinates the vehicle speed and returns it. The returned value is always positive as your day should be :) (no pun intended)

getThrottleState

Returns: number


Determinates whether the vehicle is in neutral, breaking, or accelerating. You can take the returned value and compare it against an ThrottleState enum included in Enums module script.

eg.

getThrottleState() == Enums.ThrottleState.Breaking
getDirection

Returns: number


Determinates whether the vehicle is going forward, backwards, or is resting in place.

  • 1 moving forward
  • 0 resting in place
  • -1 moving backwards
compute

Returns:

{
	angularVelocity: number, -- Angular velocity to be applied to vehicle's motor(s)
	angle: number, -- Turn angle for the wheels
	motorMaxTorque: number, -- Maximum torque that the vehicle's motor(s) can run at
	motorMaxAngularAcceleration: number -- Maximum angular acceleration that the vehicle's motor(s) can have
}

Parameters:

deltaTime: number,
config: { steerFloat: number, throttleFloat: number }?

Computes values required to make the vehicle react to a given input.

eg:

for _, wheelAtt in wheelAtts do
	wheelAtt.Orientation = Vector3.new(0, output.angle, 90)
end

for _, motor in motors do
	motor.MotorMaxAngularAcceleration = output.motorMaxAngularAcceleration
	motor.MotorMaxTorque = output.motorMaxTorque
	motor.AngularVelocity = output.angularVelocity
end
27 Likes

This looks cool! I’ll try it out once I find the time :slight_smile:

1 Like

Great to hear! I am also planning on adding example project but I need to clean up some of my old messy testing code.

1 Like

This is something I need. Thanks for making it!

1 Like

Whats the benefit over other existing chassis such as A-Chassis or AG-Chassis which is what some of the games you mention use(modified versions at least).

  • It’s simple; therefore, easy to dissect.
  • It’s lightweight.
  • It’s open-source, anyone can contribute.
  • It’s a library, not a full working out-of-box system that you need to configure and write plugins for. Therefore, you have the full control.

A-Chassis is advanced system, feature-rich, works out-of-box with lengthy configuration, you have to follow it’s rules to make it work.

On other hand, MotorVehicle is a library, needs to be interacted with code and it doesn’t force anything upon your chassis, so you can make your chassis structure how you like it. You simply feed it with information, and it spits out information to you, then can use the output how you wish to.

1 Like

You should add this into your post, they are all good selling points.

2 Likes

looks interesting!
i may try to add this to my game :+1:

Release 2.0.0


What’s New?

  1. Made wheel retract their turn angle faster when player stops turning.
  2. Renamed and added some API methods to make it more consistant.
  3. Added documentation comments.
  4. Added example project with 3 vehicles.
  5. Added chassis stats to the example project.

API Changes:

  1. getPitch() has been renamed to getRevPlaybackSpeed()
  2. getSpeed() has been renamed to getAssemblyVelocity()
  3. getThrottleState() has been renamed to getAssemblyVelocityState()
  4. getDirection() has been renamed to getAssemblyDirection()
  5. gear has been replaced with getGear()
  6. engineRPM has been replaced with getMotorRPM()
  7. getMotorHorsepower() has been added
  8. torque has been replaced with getMotorTorque()

What’s next?

I am working on 10th generation of the chassis where constraints will be replaced with pure math and raycasting, it should enable greater control of the chassis feel without it tilting/rolling over at high speeds.

1 Like

How well does this function with fluctuating or low FPS? There have been times whilst working with chassis that a user’s FPS will affect how well the chassis functions.

Are you talking about the current chassis? If yes, then FPS should not affect how the chassis behaves because it’s relying on Roblox’s constraints.

On the other side, the upcoming chassis will be using raycasting, which so far is unfortunately affected by FPS because Roblox doesn’t give us frame rate independent loop with fixed delta like Unity does with it’s fixed update function. I am not sure how to solve it yet but I am leaving it for later since it’s not important at the moment.

But, here it how the raycast chassis looks so far:

It still needs gearbox, and all that fancy stuff that the current chassis has.

Looks awesome!

Since the upcoming system will be using raycasting.
Does this mean that this system also can be used to create vehicles without wheels like hover cars or vehicles with two wheels?

Since I’m assuming you are using raycasting to adjust the height and balance of the car and not the actual physical wheels?

Yes.

Yes.


Here is more progress:

5 Likes

API v3 Design Draft


Example:

Server
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local CarController = require(packages.CarController)

local model = script.Parent
local driverSeat = model.DriverSeat

local car = CarController.new(model.Config)

local heartbeat

local function serverControl()
	-- Remove network control from the player, let server do it
	driverSeat:SetNetworkOwner(nil)

	-- Handle the wheels
	heartbeat = RunService.Heartbeat:Connect(function(deltaTime)
		local steerFloat = 0
		local throttleFloat = 0

		car:simulate({
			steerFloat = steerFloat,
			throttleFloat = throttleFloat,
			deltaTime = deltaTime
		})
	end)
end

local function clientControl(player: Player)
	-- Give network control to the player
	driverSeat:SetNetworkOwner(player)

	local client = script.Client:Clone()
	client.Parent = player.PlayerGui
	client.Enabled = true

	-- Revoke server control, if any
	if heartbeat then
		heartbeat:Disconnect()
	end
end

car.onOccupantJoinedSeat(driverSeat, function(humanoid)
	local character = humanoid.Parent
	local player = Players:GetPlayerFromCharacter(character)

	-- We only want players to use the car, not NPC's, etc.
	if not player then
		return
	end

	clientControl(player)
end)

car.onOccupantLeftSeat(driverSeat, function()
	serverControl()
end)

-- By default the server will control the car
serverControl()
Client
local RunService = game:GetService("RunService")

local CarController = require(packages.CarController)

local model = script.Vehicle.Value
local driverSeat = model.VehicleSeat

local config = require(model.Config)

local car = CarController.new(config)

model.Root.Engine.Rev:Play()

-- Handle the wheels
local heartbeat
heartbeat = RunService.Heartbeat:Connect(function(deltaTime)
	car:simulate({
		steerFloat = driverSeat.SteerFloat,
		throttleFloat = driverSeat.ThrottleFloat,
		deltaTime = deltaTime
	})

	model.Root.Engine.Rev.PlaybackSpeed = car.engine:getRevPlaybackSpeed() + 0.75
end)
Config
local CarController = require(packages.CarController)
local Wheel = CarController.Wheel

local model = script.Parent

return {
	root = model.Root,
	engine = {
		torque = 3,
		gearRatios = {3.587, 2.022, 1.384, 1.000}
	},
	suspension = {
		height = 1.5,
		stiffness = 200,
		damping = 4
	},
	wheel = {
		radius = 1.25,
		friction = 5.5,
		maxSteerAngle = 30
	},
	wheels = {
		Wheel.new({
			part = model.Wheels.FL,
			isSteer = true,
		}),
		Wheel.new({
			part = model.Wheels.FR,
			isSteer = true,
		}),
		Wheel.new({
			part = model.Wheels.RL,
			isSteer = false,
		}),
		Wheel.new({
			part = model.Wheels.RR,
			isSteer = false,
		})
	}
}

I want to know what you guys think about this API design, things like:

  • What to improve
  • What to add
  • What to remove

And any other thoughts you might have.

4 Likes

Whoa, you’ve just singlehandedly shot down my single biggest excuse for not revamping my own NGChassis project from a few years back: transmission math. You good with me integrating this into that project and publishing it?

Yes, because It’s under the MIT license.