Align Rotation behaving weirdly

I’m trying to code a helicopter that can tilt, rotate, and pitch. Currently, the tilt and pitch work, but I cannot rotate. I’m using an align rotation that makes the helicopter copy the movement of a test cube, to simulate smoothness. However, when enabled, the test part rotates weirdly, sort of like a magnet. It sticks to either -90 or 90 on the Y rotation, and stays there and does not move. Once the align rotation is disabled, it rotates freely but it is clamped on the -90 → 90 rotation on the Y. I don’t think the script is doing this, but i’m not too sure.

Video showing weird behavior →

Global script →

local heli = script.Parent
local seat = heli.VehicleSeat
local topHinge = heli.TopHinge
local inSeat = false
local prox = seat.ProximityPrompt
local keyEvent = game.ReplicatedStorage.Key
local plr = nil
local cameraEvent = game.ReplicatedStorage.Camera
local topForce = heli.TopForce
local throttleDown = false
local throttleNegativeDown = false
local throttle = 0

local speed = 2000
local maxSpeed = 150000

local percentage = 0
local tiltRightDown = false
local tiltLeftDown = false
local rotateLeftDown = false
local rotateRightDown = false
local mouseDown = false

local health = 100

local rArray = {}
local mouse = nil
for _,v in pairs(heli:GetDescendants()) do
	if v:IsA("BasePart") then
		table.insert(rArray, v)
	end
end

local function smoke(position, getsRemoved)
	local exp = game.ReplicatedStorage.VFX.Cloud1BBasic5:Clone()
	exp.Parent = workspace
	exp.Position = position
	exp.FireParticles:Emit(1)
	exp.Dist:Play()
	
	if getsRemoved then
		coroutine.wrap(function()
			wait(4)
			exp:Destroy()
		end)()
	end

	
end

local function explode(position, breaks, kills)
	local exp = game.ReplicatedStorage.VFX.ExplosionBasic5:Clone()
	exp.Parent = workspace

	exp.Position = position


	exp.FireParticles.Enabled = true
	exp.SmokeParticles.Enabled = true


	coroutine.wrap(function()
		task.wait(0.1)
		exp.FireParticles.Enabled = false
		exp.SmokeParticles.Enabled = false


	end)()

	exp.Explode:Play()
	exp.Dist:Play()

	local accuracy = 20


	if breaks then
		for lat = -80, 80, accuracy do
			for lon = 0, 350, accuracy do
				local params = RaycastParams.new()
				local cf = CFrame.new(position) * CFrame.Angles(math.rad(lon), math.rad(lat), 0)
				local raycast = workspace:Raycast(position,cf.LookVector.Unit * 40,params)

				if raycast then
					if raycast.Instance:IsA("BasePart") then
						if raycast.Instance.Name ~= "Terrain" then
							raycast.Instance.Anchored = false
						end

					end
				end
			end
		end
	end

	if kills then
		for lat = -80, 80, accuracy do
			for lon = 0, 350, accuracy do
				local params = RaycastParams.new()
				local cf = CFrame.new(position) * CFrame.Angles(math.rad(lon), math.rad(lat), 0)
				local raycast = workspace:Raycast(position,cf.LookVector.Unit * 20,params)

				if raycast then
					if raycast.Instance:IsA("BasePart") then
						if raycast.Instance.Parent then
							if raycast.Instance.Parent:FindFirstChildOfClass("Humanoid") then
								if script:IsA("Script") then
									raycast.Instance.Parent.Humanoid:TakeDamage(100)
								else
									game.ReplicatedStorage.ShotPlayer:FireServer(raycast.Instance.Parent, 100)
								end
							end
						end
					end
				end
			end
		end
	end

	coroutine.wrap(function()
		wait(4)
		exp:Destroy()
	end)()
end

function getMass(model)
	local mass = 0
	for i,v in pairs(model:GetDescendants()) do
		if(v:IsA("BasePart")) then
			mass += v.AssemblyMass
		end
	end
	return mass;
end

game.ReplicatedStorage.Click.OnServerEvent:Connect(function(plr, down)
	if down then
		mouseDown = true
	else
		mouseDown = false
	end
end)

prox.PromptShown:Connect(function()
	print('shown')
end)

prox.PromptHidden:Connect(function()
	print('hidden')
end)

prox.PromptButtonHoldBegan:Connect(function()
	print('holdbegan')
end)

prox.PromptButtonHoldEnded:Connect(function()
	print('holdended')
end)
prox.Triggered:Connect(function(player)
	plr = player
	plr.PlayerGui.Heli.Enabled = true
	cameraEvent:FireClient(plr,true, heli)
	seat:Sit(player.Character.Humanoid)
	player.Character.Humanoid.JumpHeight = 0
	prox.Enabled = false
	mouse = player:GetMouse()
	
	for _,v in pairs(player.Character:GetDescendants()) do
		if v:IsA("BasePart") then
			v.Transparency = 1
			v.CanCollide = false
		end
		if v:IsA("Decal") then
			v.Transparency = 1
		end
	end
	task.wait()
	inSeat = true
end)

keyEvent.OnServerEvent:Connect(function(player, key, down)
	if key == Enum.KeyCode.F then
		if down and inSeat and plr and player.Name == plr.Name then
			plr.Character.Humanoid.Sit = false
			
			seat:WaitForChild('SeatWeld'):Destroy()
			cameraEvent:FireClient(plr,false, nil)
			
			mouse = nil
			plr.Character:PivotTo(seat.Attachment.WorldCFrame)
			plr.Character.Humanoid.JumpHeight = 7.2
			prox.Enabled = true
			plr.PlayerGui.Heli.Enabled = false
			for _,v in pairs(player.Character:GetDescendants()) do
				if v:IsA("BasePart") then
					if v.Name ~= "HumanoidRootPart" then
						v.Transparency = 0
						coroutine.wrap(function()
							task.wait(0.5)
							v.CanCollide = true
						end)()
						
					end
				end
				if v:IsA("Decal") then
					v.Transparency = 0
				end
			end

			cameraEvent:FireClient(plr, false, heli)
			inSeat = false
			plr = nil
		end
	end
	if key == Enum.KeyCode.LeftShift then
		if down then
			throttleDown = true
		else
			throttleDown = false
		end
	end
	if key == Enum.KeyCode.LeftControl then
		if down then
			throttleNegativeDown = true
		else
			throttleNegativeDown = false
		end
	end
	if key == Enum.KeyCode.E then
		if down then
			tiltRightDown = true
		else
			tiltRightDown = false
		end
	end
	if key == Enum.KeyCode.Q then
		if down then
			tiltLeftDown = true
		else
			tiltLeftDown = false
		end
	end
	if key == Enum.KeyCode.D then
		if down then
			rotateRightDown = true
		else
			rotateRightDown = false
		end
	end
	if key == Enum.KeyCode.A then
		if down then
			rotateLeftDown = true
		else
			rotateLeftDown = false
		end
	end

end)


while game:GetService('RunService').Heartbeat:Wait() do
	
	if plr then
		
		if mouseDown then
			local origin = heli.Main.Shoot.WorldPosition
			local dir = heli.Main.CFrame.RightVector * 500
			local params = RaycastParams.new()
			params.FilterType = Enum.RaycastFilterType.Blacklist
			params.FilterDescendantsInstances = rArray
			
			local tracer = heli.tracer:Clone()
			tracer.Parent = heli
			tracer.startA.Attachment.WorldPosition = origin
			tracer.endA.Attachment.WorldPosition = origin + dir
			tracer.startA.light.Transparency = NumberSequence.new(0)
			
			coroutine.wrap(function()
				task.wait(0.1)
				tracer:Destroy()
			end)()
			
			local raycast = workspace:Raycast(origin, dir, params)
			if raycast then
				explode(raycast.Position, false, true)
			end
		end
		
		percentage = math.abs(throttle) / maxSpeed
	
		heli.Top.Transparency = percentage
		heli.TopDecal.t.Transparency = 1 - percentage
		heli.TopDecal.b.Transparency = 1 - percentage
		if throttleDown then
			if throttle < maxSpeed then
				throttle += speed
			end
		end
		if throttleNegativeDown then
			if throttle > -maxSpeed then
				throttle -= speed
			end
		end
		
		
		local rot = workspace.TestPart.Rotation
		local x = rot.X
		local y = rot.Y
		local z = rot.Z
		
		if seat.Throttle > 0.9 then
			--W
			z = -20
		elseif seat.Throttle < -0.9 then
			--S
			z = 20
		else
			--none
			z = 0
		end 
		
		if tiltRightDown then
			x = 20
		elseif tiltLeftDown then
			x = -20
		else
			x = 0
		end
		
		if rotateLeftDown  then
			--heli.LeftForce.Force = Vector3.new(10000,0,0)
			--heli.LeftForce.Force = Vector3.new(-10000,0,0)
			y += 1
		elseif rotateRightDown then
			--heli.LeftForce.Force = Vector3.new(-10000,0,0)
			--heli.RightForce.Force = Vector3.new(10000,0,0)
			y -= 1
		else
			--heli.LeftForce.Force = Vector3.new(0,0,0)
			--heli.RightForce.Force = Vector3.new(0,0,0)
		end
	
		local cf1 = CFrame.Angles(math.rad(x),math.rad(y),math.rad(z))
		local cf2 = heli.PrimaryPart.CFrame.Rotation
		
		local cf4 = cf2:ToWorldSpace(cf1)

		
		workspace.TestPart.CFrame = cf4
		
		topHinge.AngularVelocity = throttle
		heli.RearHinge.AngularVelocity = throttle / 1000
		

		topForce.Force = Vector3.new(workspace.Gravity * heli.PrimaryPart:getMass() * (throttle / maxSpeed) * 1.2,0,0)
	end
end

Local script →

local player = game.Players.LocalPlayer
local keyEvent = game.ReplicatedStorage.Key
local uis = game:GetService('UserInputService')
local cameraEvent = game.ReplicatedStorage.Camera

local mouse = player:GetMouse()

mouse.Button1Down:Connect(function()
	game.ReplicatedStorage.Click:FireServer(true)
end)

mouse.Button1Up:Connect(function()
	game.ReplicatedStorage.Click:FireServer(false)
end)


cameraEvent.OnClientEvent:Connect(function(inseat, heli)
	if inseat then
		workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
		workspace.CurrentCamera.CameraSubject = heli
	else
		workspace.CurrentCamera.CameraType = Enum.CameraType.Custom
		workspace.CurrentCamera.CameraSubject = player.Character.Humanoid
	end
end)

uis.InputBegan:Connect(function(key, down)
	keyEvent:FireServer(key.KeyCode, true)
end)

uis.InputEnded:Connect(function(key, down)
	keyEvent:FireServer(key.KeyCode, false)
end)

Current setup →
image
image

1 Like

Try this:

After getting in the plane, set the NetworkOwner of the part to the player. This must be done within a server script.

If this didn’t solve the problem, I’m curious to see what it looks like through the server.

That didn’t work. Anything else?

Not quite sure. I’m new to using these, but have been getting more experienced at it ever since the deprecation of BodyGyro. What happens when you change RigidityEnabled to true?

This

Try changing the responsiveness. I’m not very experienced with AlignOrientation, but I know that for some reason, changing Responsiveness and PrimaryAxisOnly seem to change a few things.

That did not work. Responsiveness just changes how exact it is to the part, and Rigidity is basically just infinite responsiveness. If that doesn’t work, then responsiveness doesn’t do anything.

I think your rotation values themselves are off, or somehow not being taken into account when the final CFrame gets changed in the .HeartBeat.

I’ll need to experiment more with it. What is the current NetworkOwner of the part?
As a side note, I noticed it was using BodyForce, which is unfortunately now deprecated.

This is also true, but I didn’t realize until you said it.

Yeah, I think it should be this way to make everything smoother. Being ahead is better than being behind, right?

I had to code that in with a few of my projects.

I’m pretty sure @IAmAGreenStickMan is currently using VectorForce.

Maybe. I’ll try disabling the script and checking it out.

I just took all the baseparts in the model and set the network owner the player. Also, thanks for telling me about that, I didn’t know.

They should’ve added a way to check which one is being used. Ok, maybe the VectorForce is configured incorrectly? The configuration with these is tricky.

I’m not using it right now. The only one I’m using is the one that propels it upward.

1 Like