The turret CFrame seems correct when the vehicle spawns (image 1), but as soon as the vehicle moves to a different orientation, the CFrame becomes incorrect (image 2). It looks like it’s an issue of object space vs. world space, but I’ve tried converting aimCFrame both ways at every step of the process and it’s only flipped the polarity I have to turn the axes. It hasn’t actually solved the issue of the welds’ orientations being based on the vehicle being at standstill.
character.Humanoid.Seated:Connect(function(active, seatPart)
--...
--...
elseif seatPart.Name == "TurretSeat" then
local vehicle = seatPart.Parent
local turretBarrel = vehicle.TurretBarrel
local turretBody = vehicle.TurretBody
local aimer = turretBarrel.Aimer
weldUpDown = turretBody.WeldUpDown
weldLeftRight = turretBody.WeldLeftRight
local offsetUD = weldUpDown.C0
local offsetLR = weldLeftRight.C0
local seats = getSeats(vehicle:GetChildren())
queryVehicleInfo:FireServer(vehicle)
inputStart = UIS.InputBegan:Connect(function(input, GPE)
if GPE then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 and firing == false then
firing = true
while firing do
fire(vehicle, FIRE_SPEED, FIRE_RANGE, getOccupants(seats))
fireTurret:FireServer(vehicle)
task.wait(1 / FIRE_RATE)
end
end
end)
inputEnd = UIS.InputEnded:Connect(function(input, GPE)
if GPE then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
firing = false
end
end)
sitConnection = run.RenderStepped:Connect(function()
local aimCFrame
local mousePosition = UIS:GetMouseLocation()
local mouseRay = workspace.Camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
raycastParams.FilterDescendantsInstances = {vehicle, getOccupants(seats)}
local raycastResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 1000, raycastParams)
if raycastResult then
aimCFrame = CFrame.lookAt(aimer.WorldPosition, raycastResult.Position)
else
aimCFrame = CFrame.new(Vector3.new(0, 0, 0), mouseRay.Direction)
end
local x, y, z = aimCFrame:ToEulerAnglesYXZ()
weldUpDown.C0 = offsetUD * CFrame.Angles(-x, 0, 0)
weldLeftRight.C0 = offsetLR * CFrame.Angles(0, y, 0)
aimTurret:FireServer(vehicle, weldUpDown.C0, weldLeftRight.C0)
end)
end
end
end)
Since the turret now uses welds, both movement and firing are now done on the client side via remote events. However, it’s tough to properly record bullet hits because the bullet gets copied to all clients, causing multiple hits to be registered depending on the number of players. For example, a bullet that deals 50 damage server-side, with 2 players, would deal 100 damage, with 4 players 200 damage, etc. One idea I had for fixing this was to track the number of clients that a specific bullet registered hits on and only deal damage if it’s above a specific number, but I’m told this would create thousands of problems regarding different pings and StreamingEnabled. To me, the obvious, somewhat lazy solution would be only to register the bullet hit by the player who fired it, but I imagine this could be easily exploited. I would appreciate any pointers in finding a solution here; even if they’re completely abstract, I could probably translate them to code.
Please let me know if I need to provide any more information.
I set up a little bit of a showcase to demonstrate more clearly why I think the first problem has to do with object space vs. world space:
In this first image, I’ve backed the truck up (without turning) from its original spawn point to have it sit in between two colored blocks. I then sat in the turret seat and aimed at the red block. The turret aims directly at it, as expected.
In this next image, I turned the truck around to face the opposite direction and positioned it between the two blocks again. This time, when I aim at the red block, the turret aims next to the blue block, in what appears to be the complete opposite direction of the red block relative to the turret.
Finally, I reoriented the truck to face the direction it was originally facing (for the most part) and positioned it one last time between the two blocks. Aiming at the red block, the turret has returned to normal, mostly.
As I mentioned in the first post, I’ve already tried converting aimCFrame to both object space and world space at every step in the process and it hasn’t solved the problem. Is there something I’m missing? Or is this not even an issue with object space vs. world space?
Using a hinge constraint would prevent me from being able to have another person drive the vehicle while someone is manning the turret. That was the first thing I tried before I started using welds.
This topic explains it well. You can’t split network ownership of an assembly between people; one person can have complete ownership and that’s it. That means in order to allow for more than one person to control the behavior of an assembly, you need to make a workaround. This workaround involves manipulating the orientations of two welds and then replicating that across all other clients through remote events.
It doesn’t matter if the parts of the turret and the parts of the rest of the vehicle are in separate models. Network ownership applies to assemblies. If any part of them is connected in any way (which they’re going to be because they’re ultimately different parts of the same vehicle) via constraints or other means, then network ownership of both the turret and the vehicle is going to belong to the same person. There’s no way to split it between different people because only one person’s inputs can replicate to the server. I don’t know what “PhisicsConstraints” are, they don’t appear in documentation. If you’re talking about Mechanical Constraints, I already tried making the turret use hinges and that not working led me to where I am right now.
I’m still completely stuck and desperately need some insight. I recently tried adding the orientation of the vehicle’s body to the angles I get out of :ToEulerAnglesYXZ() and it made no difference and might’ve broken things even further. I had to add 180 degrees to the X value to even get it to aim in the right direction when situated at the origin.
-- the vehicle/turret driving function, what runs whenever someone sits down
character.Humanoid.Seated:Connect(function(active, seatPart)
--...
--...
-- turret portion
elseif seatPart.Name == "TurretSeat" then
local vehicle = seatPart.Parent
local turretBarrel = vehicle.TurretBarrel
local turretBody = vehicle.TurretBody
local aimer = turretBarrel.Aimer
weldUpDown = turretBody.WeldUpDown
weldLeftRight = turretBody.WeldLeftRight
local offsetUD = weldUpDown.C0
local offsetLR = weldLeftRight.C0
local seats = getSeats(vehicle:GetChildren())
queryVehicleInfo:FireServer(vehicle)
-- input starts (shooting)
inputStart = UIS.InputBegan:Connect(function(input, GPE)
if GPE then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
firing = true
while firing do
if tick() - (1 / FIRE_RATE) >= lastShot then
local aimCFrame
local mousePosition = UIS:GetMouseLocation()
local mouseRay = workspace.Camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
raycastParams.FilterDescendantsInstances = {vehicle, getOccupants(seats)}
local raycastResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 1000, raycastParams)
if raycastResult then
aimCFrame = CFrame.lookAt(aimer.WorldPosition, raycastResult.Position)
else
aimCFrame = CFrame.new(Vector3.new(0, 0, 0), mouseRay.Direction)
end
local x, y, z = aimCFrame:ToEulerAnglesYXZ()
x -= vehicle.PrimaryPart.Orientation.X + math.rad(180)
y -= vehicle.PrimaryPart.Orientation.Y
weldUpDown.C0 = offsetUD * (CFrame.Angles(x, 0, 0)):ToObjectSpace()
weldLeftRight.C0 = offsetLR * (CFrame.Angles(0, -y, 0)):ToObjectSpace()
aimTurret:FireServer(vehicle, weldUpDown.C0, weldLeftRight.C0)
lastShot = tick()
fire(vehicle, FIRE_SPEED, FIRE_RANGE, getOccupants(seats))
fireTurret:FireServer(vehicle)
end
task.wait()
end
end
end)
-- input ends (shooting)
inputEnd = UIS.InputEnded:Connect(function(input, GPE)
if GPE then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
firing = false
end
end)
end
end
end)
Does it matter which BasePart I use for the first and fifth steps?
Does :ToOrientation() replace :ToEulerAnglesYXZ() in my script?
Why clamp the orientation? What does that do?
EDIT: The method you outlined turned out to be the solution (I didn’t clamp anything because I think that function is unique to your module) but I’d still like answers on these just in case anyone else is having the same problem.