Giving the player the same controls as a vehicleseat

I’m making a racing game where the character spawns as a car with the same controls as a jeep from the default racing template. Here’s a list of differences between humanoid controls and vehicle controls in case you haven’t noticed:

  • Usually much faster than a humanoid.
  • Instead of moving left and right by pressing A and D, you can only turn in those directions by doing that, so you gotta accelerate at the same time to change direction. I’m focusing on this one the most and the other two are kinda obvious.
  • The speedometer, but I don’t really want that, and I know how to disable it

I’ve created my own custom vehicle already that’s gonna be my new StarterCharacter. This isn’t it; it’s just another design that’s more basic so my actual design wouldn’t get stolen:

Of course, if the custom character isn’t a humanoid, so it needs custom controls. I’m having trouble editing a script to make it do the things in the list above.

First, I tried making a custom character out of a humanoid and a vehicle with a vehicleseat, thinking the humanoid would sit in it when I pressed play. That didn’t work, so I ditched the humanoid and resorted to scripting. I found a couple scripts in StarterPlayerScripts after you click play that I thought might work after editing it. I tried editing both of them and nothing worked. Here they are:

BaseCharacterController:

--!strict
--[[
	BaseCharacterController - Abstract base class for character controllers, not intended to be
	directly instantiated.

	2018 PlayerScripts Update - AllYourBlox
--]]

local ZERO_VECTOR3: Vector3 = Vector3.new(0,0,0)

--[[ The Module ]]--
local BaseCharacterController = {}
BaseCharacterController.__index = BaseCharacterController

function BaseCharacterController.new()
	local self = setmetatable({}, BaseCharacterController)
	self.enabled = false
	self.moveVector = ZERO_VECTOR3
	self.moveVectorIsCameraRelative = true
	self.isJumping = false
	return self
end

function BaseCharacterController:OnRenderStepped(dt: number)
	-- By default, nothing to do
end

function BaseCharacterController:GetMoveVector(): Vector3
	return self.moveVector
end

function BaseCharacterController:IsMoveVectorCameraRelative(): boolean
	return self.moveVectorIsCameraRelative
end

function BaseCharacterController:GetIsJumping(): boolean
	return self.isJumping
end

-- Override in derived classes to set self.enabled and return boolean indicating
-- whether Enable/Disable was successful. Return true if controller is already in the requested state.
function BaseCharacterController:Enable(enable: boolean): boolean
	error("BaseCharacterController:Enable must be overridden in derived classes and should not be called.")
	return false
end

return BaseCharacterController

VehicleController:

--[[
	// FileName: VehicleControl
	// Version 1.0
	// Written by: jmargh
	// Description: Implements in-game vehicle controls for all input devices

	// NOTE: This works for basic vehicles (single vehicle seat). If you use custom VehicleSeat code,
	// multiple VehicleSeats or your own implementation of a VehicleSeat this will not work.
--]]
local ContextActionService = game:GetService("ContextActionService")

--[[ Constants ]]--
-- Set this to true if you want to instead use the triggers for the throttle
local useTriggersForThrottle = true
-- Also set this to true if you want the thumbstick to not affect throttle, only triggers when a gamepad is conected
local onlyTriggersForThrottle = false
local ZERO_VECTOR3 = Vector3.new(0,0,0)

local AUTO_PILOT_DEFAULT_MAX_STEERING_ANGLE = 35


-- Note that VehicleController does not derive from BaseCharacterController, it is a special case
local VehicleController = {}
VehicleController.__index = VehicleController

function VehicleController.new(CONTROL_ACTION_PRIORITY)
	local self = setmetatable({}, VehicleController)

	self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY

	self.enabled = false
	self.vehicleSeat = nil
	self.throttle = 0
	self.steer = 0

	self.acceleration = 0
	self.decceleration = 0
	self.turningRight = 0
	self.turningLeft = 0

	self.vehicleMoveVector = ZERO_VECTOR3

	self.autoPilot = {}
	self.autoPilot.MaxSpeed = 0
	self.autoPilot.MaxSteeringAngle = 0

	return self
end

function VehicleController:BindContextActions()
	if useTriggersForThrottle then
		ContextActionService:BindActionAtPriority("throttleAccel", (function(actionName, inputState, inputObject)
			self:OnThrottleAccel(actionName, inputState, inputObject)
			return Enum.ContextActionResult.Pass
		end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonR2)
		ContextActionService:BindActionAtPriority("throttleDeccel", (function(actionName, inputState, inputObject)
			self:OnThrottleDeccel(actionName, inputState, inputObject)
			return Enum.ContextActionResult.Pass
		end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonL2)
	end
	ContextActionService:BindActionAtPriority("arrowSteerRight", (function(actionName, inputState, inputObject)
		self:OnSteerRight(actionName, inputState, inputObject)
		return Enum.ContextActionResult.Pass
	end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Right)
	ContextActionService:BindActionAtPriority("arrowSteerLeft", (function(actionName, inputState, inputObject)
		self:OnSteerLeft(actionName, inputState, inputObject)
		return Enum.ContextActionResult.Pass
	end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Left)
end

function VehicleController:Enable(enable: boolean, vehicleSeat: VehicleSeat)
	if enable == self.enabled and vehicleSeat == self.vehicleSeat then
		return
	end

	self.enabled = enable
	self.vehicleMoveVector = ZERO_VECTOR3

	if enable then
		if vehicleSeat then
			self.vehicleSeat = vehicleSeat

			self:SetupAutoPilot()
			self:BindContextActions()
		end
	else
		if useTriggersForThrottle then
			ContextActionService:UnbindAction("throttleAccel")
			ContextActionService:UnbindAction("throttleDeccel")
		end
		ContextActionService:UnbindAction("arrowSteerRight")
		ContextActionService:UnbindAction("arrowSteerLeft")
		self.vehicleSeat = nil
	end
end

function VehicleController:OnThrottleAccel(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
		self.acceleration = 0
	else
		self.acceleration = -1
	end
	self.throttle = self.acceleration + self.decceleration
end

function VehicleController:OnThrottleDeccel(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
		self.decceleration = 0
	else
		self.decceleration = 1
	end
	self.throttle = self.acceleration + self.decceleration
end

function VehicleController:OnSteerRight(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
		self.turningRight = 0
	else
		self.turningRight = 1
	end
	self.steer = self.turningRight + self.turningLeft
end

function VehicleController:OnSteerLeft(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
		self.turningLeft = 0
	else
		self.turningLeft = -1
	end
	self.steer = self.turningRight + self.turningLeft
end

-- Call this from a function bound to Renderstep with Input Priority
function VehicleController:Update(moveVector: Vector3, cameraRelative: boolean, usingGamepad: boolean)
	if self.vehicleSeat then
		if cameraRelative then
			-- This is the default steering mode
			moveVector = moveVector + Vector3.new(self.steer, 0, self.throttle)
			if usingGamepad and onlyTriggersForThrottle and useTriggersForThrottle then
				self.vehicleSeat.ThrottleFloat = -self.throttle
			else
				self.vehicleSeat.ThrottleFloat = -moveVector.Z
			end
			self.vehicleSeat.SteerFloat = moveVector.X

			return moveVector, true
		else
			-- This is the path following mode
			local localMoveVector = self.vehicleSeat.Occupant.RootPart.CFrame:VectorToObjectSpace(moveVector)

			self.vehicleSeat.ThrottleFloat = self:ComputeThrottle(localMoveVector)
			self.vehicleSeat.SteerFloat = self:ComputeSteer(localMoveVector)

			return ZERO_VECTOR3, true
		end
	end
	return moveVector, false
end

function VehicleController:ComputeThrottle(localMoveVector)
	if localMoveVector ~= ZERO_VECTOR3 then
		local throttle = -localMoveVector.Z
		return throttle
	else
		return 0.0
	end
end

function VehicleController:ComputeSteer(localMoveVector)
	if localMoveVector ~= ZERO_VECTOR3 then
		local steerAngle = -math.atan2(-localMoveVector.x, -localMoveVector.z) * (180 / math.pi)
		return steerAngle / self.autoPilot.MaxSteeringAngle
	else
		return 0.0
	end
end

function VehicleController:SetupAutoPilot()
	-- Setup default
	self.autoPilot.MaxSpeed = self.vehicleSeat.MaxSpeed
	self.autoPilot.MaxSteeringAngle = AUTO_PILOT_DEFAULT_MAX_STEERING_ANGLE

	-- VehicleSeat should have a MaxSteeringAngle as well.
	-- Or we could look for a child "AutoPilotConfigModule" to find these values
	-- Or allow developer to set them through the API as like the CLickToMove customization API
end

return VehicleController

I’m still pretty sure editing one of these scripts is the way to go, but I could be wrong. There wasn’t very many DevForum posts of the same problem. Also please keep in mind that I’m not a very good scripter by the way.

you could use the properties of VehicleSeat known as “Steer” or “SteerFloat”, “Throttle” or “ThrottleFloat”, just expect them to reset to 0 when a player sits in the seat

Throttle sets it’s value to “1” when holding “w” and throttle sets it’s value to -1 when holding “s”

Steer sets it’s value to “1” when holding “d” and steer sets it’s value to -1 when holding “a”

image

Take note that ThrottleFloat acts the same as Throttle and SteerFloat acts the same as Steer

Hope this was helpful ^^