bumping bc hour has passed; experimenting w/ linear velocity but still no good solution. My current collision-handling code now shoots raycasts top-to-bottom along the scooter’s velocity to cover edge cases. However, if the scooter is tilted enough, the raycasts won’t hit but I don’t want the raycasts to be too long because otherwise the collisions will be treated improperly in “normal” cases where the scooter hits the obstacle head-on. Current code:
local function handleCollisions()
-- get current velocity in world space
local velocity = scooterPhysicsBox.CFrame.LookVector * linearVelocity.PlaneVelocity.X
-- shoot raycasts top-to-bottom
for y = -1, 1, 1 do
-- calculate raycast origin depending on offset
local origin = scooterPhysicsBox.Position + Vector3.new(0, y * scooterPhysicsBox.Size.Y * 0.5 * 0.9, 0)
-- get shoot raycast just past the edge of the scooter
-- (hence the 0.55 which should be interpreted as 55%)
-- maxDimension is just the maximum dimension of the scooter size (X, Y, or Z)
local result = workspace:Raycast(origin, velocity.Unit * maxDimension * 0.55, raycastParams)
-- Ensure result exists and surface is steep enough to be an obstacle
if result ~= nil and result.Instance ~= nil and result.Normal.Y <= 0.5 then
-- calculate reflected velocity along surface normal
local reflectedWorldVelocity = scooterPhysicsBox.CFrame:PointToObjectSpace(velocity - 2 * velocity:Dot(result.Normal) * result.Normal)
-- plane velocity is a two-dimensional vector always parallel with the scooter look vector
linearVelocity.PlaneVelocity = Vector2.new(reflectedWorldVelocity.X, reflectedWorldVelocity.X)
return
end
end
end
RunService.Heartbeat:Connect(handleCollisions)
How about trying to raycast in multiple different directions? I am assuming the only raycast in that code won’t hit the wall if angled.
Something similar like this:
local function handleCollisions()
local velocity = scooterPhysicsBox.CFrame.LookVector * linearVelocity.PlaneVelocity.X
local origin = scooterPhysicsBox.Position
local directions = {
velocity.Unit,
(velocity + scooterPhysicsBox.CFrame.RightVector).Unit,
(velocity - scooterPhysicsBox.CFrame.RightVector).Unit,
}
for _, direction in ipairs(directions) do
-- multiply by 0.55 to make ray go a little past the scooter
local rayDirection = direction * maxDimension * 0.55
local result = workspace:Raycast(origin, rayDirection, raycastParams)
if result ~= nil and result.Instance ~= nil then
local incidentVector = velocity.Unit
local normalVector = result.Normal
local speed = linearVelocity.PlaneVelocity.X
local dampingFactor = 0.5
local reflectedVector = incidentVector - 2 * incidentVector:Dot(normalVector) * normalVector
local reflectedVelocity = reflectedVector * math.abs(speed) * dampingFactor
linearVelocity.PlaneVelocity = Vector2.new(reflectedVelocity.X, reflectedVelocity.Z)
break
end
end
end
Thanks for your reply! Great thinking! I tweaked it to fit with my code and that almost got me the result I am looking for. Although it does handle collisions correctly when going at a sufficient speed, it does not resolve the overall issue when colliding at a very low speed and causes the scooter to get stuck for some reason. Essentially what’s happening is when the scooter is already in contact with the wall and it just starts moving against the wall, the reflected velocity does not reflect much and this causes the scooter to eventually touch the wall and start having a “lesser” version of the original behavior; it just stays stuck to the wall as if there is friction. And btw I made sure to have zero friction on the physics box via custom physical properties. Video of scooter physics box getting stuck on wall
Here’s the current code:
local function handleCollisions()
local velocity = scooterPhysicsBox.CFrame.LookVector * linearVelocity.PlaneVelocity.X
local dampingFactor = 0.5
-- Shoot raycasts in multiple directions
for x = -1, 1, 1 do
for y = -1, 1, 1 do
-- Offset and cast ray as appropriate
local origin = scooterPhysicsBox.Position + Vector3.new(x * scooterPhysicsBox.Size.X * 0.5 * 1.1, y * scooterPhysicsBox.Size.Y * 0.5 * 0.9, 0)
local result = workspace:Raycast(origin, velocity.Unit * maxDimension * 0.55, raycastParams)
-- Ensure result exists and surface is steep enough to be an obstacle
if result ~= nil and result.Instance ~= nil and result.Normal.Y <= 0.5 then
-- Get unit vector representing direction of reflected vector
local reflectedVector = velocity.Unit - 2 * velocity.Unit:Dot(result.Normal) * result.Normal
-- Apply a magnitude with damping to reflected vector to get reflected velocity
local reflectedVelocity = reflectedVector * velocity.Magnitude * dampingFactor
linearVelocity.PlaneVelocity = Vector2.new(reflectedVelocity.X, reflectedVelocity.Z)
return
end
end
end
end
Maybe try using a push factor that always applies no matter the speed?
local function handleCollisions()
local velocity = scooterPhysicsBox.CFrame.LookVector * linearVelocity.PlaneVelocity.X
local dampingFactor = 0.5
local pushFactor = 0.1 -- your push factor
for x = -1, 1, 1 do
for y = -1, 1, 1 do
local origin = scooterPhysicsBox.Position + Vector3.new(x * scooterPhysicsBox.Size.X * 0.5 * 1.1, y * scooterPhysicsBox.Size.Y * 0.5 * 0.9, 0)
local result = workspace:Raycast(origin, velocity.Unit * maxDimension * 0.55, raycastParams)
if result ~= nil and result.Instance ~= nil and result.Normal.Y <= 0.5 then
local reflectedVector = velocity.Unit - 2 * velocity.Unit:Dot(result.Normal) * result.Normal
local reflectedVelocity = reflectedVector * velocity.Magnitude * dampingFactor
reflectedVelocity = reflectedVelocity + (-result.Normal * pushFactor) -- apply the small push factor here
linearVelocity.PlaneVelocity = Vector2.new(reflectedVelocity.X, reflectedVelocity.Z)
return
end
end
end
end
Although that did make it take longer, the scooter still eventually made its way to the wall. I added a ternary operator that checks if the magnitude of the current velocity is below some threshold and if so, it just reflects the entirety of the velocity rather than damping:
local function handleCollisions()
local velocity = scooterPhysicsBox.CFrame.LookVector * linearVelocity.PlaneVelocity.X
local velocityThreshold = 5
local dampingFactor = 0.5
for x = -1, 1, 1 do
for y = -1, 1, 1 do
local origin = scooterPhysicsBox.CFrame:PointToWorldSpace(Vector3.new(x * scooterPhysicsBox.Size.X * 0.5 * 1.1, y * scooterPhysicsBox.Size.Y * 0.5 * 0.9, 0))
local result = workspace:Raycast(origin, velocity.Unit * maxDimension * 0.525, raycastParams)
-- Ensure result exists and surface is steep enough to be an obstacle
if result ~= nil and result.Instance ~= nil and result.Normal.Y <= 0.5 then
local reflectedVector = velocity.Unit - 2 * velocity.Unit:Dot(result.Normal) * result.Normal
local reflectedVelocity = reflectedVector * velocity.Magnitude * (if velocity.Magnitude <= velocityThreshold then 1 else dampingFactor)
linearVelocity.PlaneVelocity = Vector2.new(reflectedVelocity.X, reflectedVelocity.Z).Unit * reflectedVelocity.Magnitude
return
end
end
end
end
;-; Overall this does resolve the issue… But I found a case in which this does not work. When the scooter bumps into the wall backward, it for some reason still gets stuck. And even sometimes when going in forward… Video of scooter getting stuck
Adjusting the code a little, I implemented a check to see if the scooter is driving backwards into the wall. And I also added a velocityThreshold to check if the scooter is too slow, and if so, we will just reverse the velocity.
local function handleCollisions()
local velocity = scooterPhysicsBox.CFrame.LookVector * linearVelocity.PlaneVelocity.X
local velocityThreshold = 5
local dampingFactor = 0.5
for x = -1, 1, 1 do
for y = -1, 1, 1 do
local origin = scooterPhysicsBox.CFrame:PointToWorldSpace(Vector3.new(x * scooterPhysicsBox.Size.X * 0.5 * 1.1, y * scooterPhysicsBox.Size.Y * 0.5 * 0.9, 0))
local result = workspace:Raycast(origin, velocity.Unit * maxDimension * 0.525, raycastParams)
if result ~= nil and result.Instance ~= nil and result.Normal.Y <= 0.5 then
local reflectedVector = velocity.Unit - 2 * velocity.Unit:Dot(result.Normal) * result.Normal
local reflectedVelocity
local angle = math.acos(velocity.Unit:Dot(scooterPhysicsBox.CFrame.LookVector))
if angle > math.pi / 2 then
reflectedVelocity = -reflectedVector * velocity.Magnitude
elseif velocity.Magnitude <= velocityThreshold then
reflectedVelocity = -velocity
else
reflectedVelocity = reflectedVector * velocity.Magnitude * dampingFactor
end
linearVelocity.PlaneVelocity = Vector2.new(reflectedVelocity.X, reflectedVelocity.Z).Unit * reflectedVelocity.Magnitude
return
end
end
end
end
Nice! It works as expected! Thank you very much! Hopefully in the future, this won’t be an issue with Linear Velocity Constraints; it doesn’t seem very natural to slide up walls . I really appreciate your time and effort