Car / bumper car physic help

You can write your topic however you want, but you need to answer these questions:
Hello,

I am attempting to create a WMMT style game, focusing on the ‘brake blocking’ physics. I’ve created a system for slowing down vehicle against walls, ‘zero-stopping’ on certain walls/areas, and a proper rubber-banding system. However, I am struggling with getting the physics right for ‘shoving’ other players while in a vehicle. I more so run into an issue where player A will be in front of player B, and player A will lean into player B’s car to shove them into a wall, however player A’s vehicle will get shoved instead and sometimes flung back. I also notice when testing if player B’s vehicle is static, and player A slowly taps player B (just to simulate a brake block) player A’s vehicle gets pushed back instead of player B’s. Any suggestions on how I can tackle this issue would be greatly appreciated!

Here is a video explaining ‘brake blocking’ for those unfamiliar.

Here is my current code for handling the brake blocking:


local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")

local blockerVehicle = script.Parent
local primaryPart = blockerVehicle.PrimaryPart


local VEHICLE_TAG = "PlayerVehicle"
local BRAKE_FACTOR = 0.7 
local PUSH_FORCE = 40 -- high value just for testin
local COOLDOWN = 1.0 
local SIDE_IMPACT_THRESHOLD = 0.5 
local ATTACKER_VELOCITY_THRESHOLD = -0.5 


local blockIsActive = false
local lastBlockTime = 0
local velocityBeforeHit = Vector3.new()
local impactNormalForPush = Vector3.new()
local targetVehicle = nil
local velocityOfTargetBeforeHit = Vector3.new()


CollectionService:AddTag(blockerVehicle, VEHICLE_TAG)

local function findAncestorWithTag(instance, tag)
	local current = instance
	while current do
		if CollectionService:HasTag(current, tag) then
			return current
		end
		current = current.Parent
	end
	return nil
end


local function onTouched(otherPart)
	if os.clock() - lastBlockTime < COOLDOWN then return end

	local otherModel = findAncestorWithTag(otherPart, VEHICLE_TAG)
	if not otherModel or otherModel == blockerVehicle or not otherModel.PrimaryPart then
		return
	end

	local targetVelocity = otherModel.PrimaryPart.AssemblyLinearVelocity

	if targetVelocity.Magnitude < 1 then
		return
	end

	local origin = otherModel.PrimaryPart.Position
	local direction = primaryPart.Position - origin
	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = {otherModel}
	raycastParams.FilterType = Enum.RaycastFilterType.Exclude
	local result = workspace:Raycast(origin, direction, raycastParams)

	if not (result and result.Instance:IsDescendantOf(blockerVehicle)) then return end

	local impactNormal = result.Normal

	local sideImpactDot = primaryPart.CFrame.LookVector:Dot(impactNormal)
	if math.abs(sideImpactDot) > SIDE_IMPACT_THRESHOLD then
		return
	end

	local targetVelocityDot = targetVelocity.Unit:Dot(impactNormal)
	if targetVelocityDot > ATTACKER_VELOCITY_THRESHOLD then
		return
	end

	velocityBeforeHit = primaryPart.AssemblyLinearVelocity
	velocityOfTargetBeforeHit = targetVelocity
	impactNormalForPush = impactNormal
	targetVehicle = otherModel
	blockIsActive = true
	lastBlockTime = os.clock()
end

RunService.Stepped:Connect(function(gameTime, deltaTime)
	if blockIsActive then
		if not (targetVehicle and targetVehicle.PrimaryPart and targetVehicle.Parent) then
			blockIsActive = false
			return
		end

		local currentTargetCFrame = targetVehicle:GetPrimaryPartCFrame()
		targetVehicle:SetPrimaryPartCFrame(currentTargetCFrame + impactNormalForPush * 0.1)

		local n = impactNormalForPush 
		local v = velocityOfTargetBeforeHit

		local closingSpeed = v:Dot(n)
		local closingVelocity = closingSpeed * n

		local parallelVelocity = v - closingVelocity

		local pushVelocity = n * PUSH_FORCE

		local finalTargetVelocity = (parallelVelocity * BRAKE_FACTOR) + pushVelocity

		targetVehicle.PrimaryPart.AssemblyLinearVelocity = finalTargetVelocity
		targetVehicle.PrimaryPart.AssemblyAngularVelocity = Vector3.new(0, 0, 0) -- Prevent spinouts maybe???

		primaryPart.AssemblyLinearVelocity = velocityBeforeHit * BRAKE_FACTOR
		primaryPart.AssemblyAngularVelocity = Vector3.new(0, 0, 0) -- Prevent spinouts for own vehicle

		blockIsActive = false
		targetVehicle = nil
	end
end)

local bodyContainer = blockerVehicle:FindFirstChild("Body")
if bodyContainer and bodyContainer:IsA("PVInstance") then
	for _, bodyPart in ipairs(bodyContainer:GetDescendants()) do
		if bodyPart:IsA("BasePart") then
			bodyPart.Touched:Connect(onTouched)
		end
	end
else
	primaryPart.Touched:Connect(onTouched)
end

blockerVehicle.Destroying:Connect(function()
	if CollectionService:HasTag(blockerVehicle, VEHICLE_TAG) then
		CollectionService:RemoveTag(blockerVehicle, VEHICLE_TAG)
	end
end)

Truly I am unsure if this would even be possible with the physics and network involved, but any suggestions would be appreciated!

So a little update on this. I ended up getting some of the physics better, and made only the front end of the vehicle ‘detectable’ for the pushing physics. I think im moreso going for a ‘bumper kart’ type instead of full on brake blocking cause im still not sure how accurate the physics can get



local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")

local blockerVehicle = script.Parent

blockerVehicle:SetAttribute("CanBrakeBlock", true)
local primaryPart = blockerVehicle.PrimaryPart


local VEHICLE_TAG = "PlayerVehicle"
local PUSH_FORCE_MULTIPLIER = 150
local SHOVE_DURATION = 0.25
local COOLDOWN = 1.0

local blockState = "Inactive"
local lastBlockTime = 0
local blockStartTime = 0
local targetVehicle = nil
local impactNormal = Vector3.new()
local pushForceInstance = nil

CollectionService:AddTag(blockerVehicle, VEHICLE_TAG)

local ignoreParts = {}
for _, part in ipairs(blockerVehicle:GetDescendants()) do
	if part:IsA("BasePart") then
		ignoreParts[part] = true
	end
end


local function findTaggedVehicle(instance)
	local current = instance
	while current and current ~= workspace do
		if CollectionService:HasTag(current, VEHICLE_TAG) then
			return current
		end
		current = current.Parent
	end
	return nil
end

local function findSeatVehicle(instance)
	local current = instance
	while current and current ~= workspace do
		if current:IsA("Model") and current.PrimaryPart and current.PrimaryPart:IsA("VehicleSeat") then
			return current
		end
		current = current.Parent
	end
	return nil
end

local function onTouched(otherPart)

	if not blockerVehicle:GetAttribute("CanBrakeBlock") then
		return
	end

	if ignoreParts[otherPart] then
		return
	end

	if blockState ~= "Inactive" or os.clock() - lastBlockTime < COOLDOWN then
		return
	end


	local otherModel = findTaggedVehicle(otherPart) or findSeatVehicle(otherPart)
	if not otherModel or otherModel == blockerVehicle or not otherModel.PrimaryPart then
		return
	end

	if not otherModel:GetAttribute("CanBrakeBlock") then
		return
	end

	impactNormal = (otherModel.PrimaryPart.Position - primaryPart.Position).Unit


	targetVehicle = otherModel
	blockState = "Starting"
	lastBlockTime = os.clock()
	blockerVehicle:SetAttribute("CanBrakeBlock", false)
	otherModel:SetAttribute("CanBrakeBlock", false)
	print("Push initiated by", blockerVehicle.Name, "against", otherModel.Name)
end

RunService.Heartbeat:Connect(function()
	if blockState == "Starting" then
		if not (targetVehicle and targetVehicle.PrimaryPart) then
			blockState = "Inactive"

			blockerVehicle:SetAttribute("CanBrakeBlock", true)
			if targetVehicle and targetVehicle.Parent then
				targetVehicle:SetAttribute("CanBrakeBlock", true)
			end
			return
		end

		local tp = targetVehicle.PrimaryPart
		local tMass = tp.AssemblyMass
		local forceMag = tMass * PUSH_FORCE_MULTIPLIER
		local attach = Instance.new("Attachment", tp)
		pushForceInstance = Instance.new("VectorForce", attach)
		pushForceInstance.RelativeTo = Enum.ActuatorRelativeTo.World
		pushForceInstance.Attachment0 = attach
		pushForceInstance.Force = impactNormal * forceMag

		blockStartTime = os.clock()
		blockState = "Active"

	elseif blockState == "Active" then
		if os.clock() - blockStartTime >= SHOVE_DURATION then
			blockState = "Ending"
		end

	elseif blockState == "Ending" then

		if pushForceInstance and pushForceInstance.Parent then
			pushForceInstance.Parent:Destroy()
			pushForceInstance = nil
		end

		blockerVehicle:SetAttribute("CanBrakeBlock", true)
		if targetVehicle and targetVehicle.Parent then
			targetVehicle:SetAttribute("CanBrakeBlock", true)
		end
		targetVehicle = nil
		blockState = "Inactive"
	end
end)

local brakePoints = blockerVehicle:FindFirstChild("BrakeBlockPoints")
if brakePoints then
	for _, part in ipairs(brakePoints:GetDescendants()) do
		if part:IsA("BasePart") then
			part.Touched:Connect(onTouched)
		end
	end
else
	local bodyFolder = blockerVehicle:FindFirstChild("Body")
	if bodyFolder then
		for _, part in ipairs(bodyFolder:GetDescendants()) do
			if part:IsA("BasePart") then
				part.Touched:Connect(onTouched)
			end
		end
	else
		primaryPart.Touched:Connect(onTouched)
	end
end


blockerVehicle.Destroying:Connect(function()
	CollectionService:RemoveTag(blockerVehicle, VEHICLE_TAG)
	blockState = "Ending"
end)

BUT I did want to ask, running a few play test I notice there’s a pretty big desync in placement of 2 vehicles. So for example on player A’s screen player A and player B may be close next to each other, but on player B’s screen player A could be further in front or further behind. I’m wondering whats the best way to approach the desyncing, or is that something that cannot be addressed?

actually just thought about this. would utilizing SetNetworkOwner() on the vehicle’s, or maybe vehicle seat help with that desync?

Thats a great idea! I’ll see if i can set the networkownership for the attacking player of the target car :slight_smile:

I’ve made more progress on this, ofc items are place holders for now until i get the physics and system to a good point. I’m even using the racing template roblox provided, mostly because its a good platform to test on.

I’ve switch up to instead make it so instead of ‘pushing’ the other vehicles, i’ve added a stabilizer that helps keep the vehicle ‘stiff’, even if its at an angle and colliding with another object. the idea came to me yesterday and i’ve seemed to got it mostly working. its not perfect, but its a lot better. The ‘brake blocking’ now is set by the front end and a bit of the side of the vehicle detecting if its hit the other vehicles front end, and instead of ‘pushing’ it decreases the maxspeed for a second and then restores the original max speed. I may make it so the player has to ‘brake’ for this to connect, but im not sure yet cause that’d be really hard on mobile.

Heres a ‘slow’ version of me and my wife testing the brake blocking

Heres areal good example of the ‘brake blocking’ in action.

I also made it so you cannot ‘brake block’ the person in front of you. its a bit janky but im makin progress!

(and ofc she wrecks herself at the end after doing so well :broken_heart:)

Will do some more research on the networkownership, during the playtest I could see our vehicles where quite desynced which i think it going to cause more problems later on

1 Like

Not sure what I was thinking in that lost post … /facepalm
Just try setting networkownership from a server script..

1 Like

sorry for late reply

I actually did this, but I got an annoying effect where the other player would ‘freeze’ or ‘jitter’ really bad when hitting them or attempting to hit them, so it made performing the ‘brake block’ nearly impossible. not sure if i set something up wrong or if its just networking being silly

So i found this wonderful thread as I am running into this exact issue

I see as of May 19 a staff member stated they are working on a server-authority system that should address this exact issue I am having with the desync.

So brake block handling and stabilization is completed. I will await for the server-authority system to come out to address this. I think that’ll overall help the desyncing issue

EDIT: of course if anyone has any suggestions please feel free to reply :slight_smile:

I’ve tried >

  1. setting network ownership nil (to server) but the input delay was quite bad
  2. setting network ownership to player when brake blocking / colliding, but this doesn’t really work since the position(s) of the vehicles on each client side will be different so its quite hard to tell when your colliding with someone
  3. setting network ownership to player w/ lowest ping. this rlly only ‘works’ for the player with the lowest ping bc other players will get input lag like in #1

A little update on this. I whipped together a game using these physics. Im moreso going to wait for the server-authority system to come out to complete this but here is a little ‘demo’