I need help improving this helicopter control code

In this project I am using the older body movers like BodyVelocity and BodyGyro; I know they are depreciated, but I have experimented with the constraint replacements for them, and I did not find them to be as flexible or easy to use as the older BodyMovers are. I would greatly appreciate any answers to this question using the new constraint replacements for them in their answer to help me get the hang of them, but it isn’t necessary at this time.

I’m working on a helicopter system that relies on a VehicleSeat’s throttle and steer components to control left/right and forward/backward movement, so that it will be more cross-platform friendly. In addition, I have it so that the helicopter’s orientation is determined by the player’s mouse moving constantly (I will change this for mobile players obviously), and the player will use Left Shift/Left Control to go up/down (again, as this system progresses further along, I will add more cross-platform friendly controls).

So far I have gotten the basic’s down, but I am running into some issues:

  1. I cannot have multiple controls going at once. For example, if I try to combine going up in the helicopter with going forward, going forward with override the going up control and I will stop going up when I start going forward. Here is a gif of the problem.

  2. Controls don’t stop until another control overrides them. If I go forward, the only way to stop would be to choose another control, like going backwards, up or down, or left and right. I know a fix to this but I fear that it will make the issue in #1 even worse. Here is a gif of the problem.

I apologize if those gifs don’t help visualize the issues very well.

The following is the server-sided code used for the mode:

local seat = script.Parent.VehicleSeat
local engine = script.Parent.Engine
local gyro = engine.BodyGyro
local speed = engine.BodyVelocity
local maxSpeed = 100
local minSpeed = 0
local connection = script.Parent.Control
local inc = 10
local height = 0 --- 0 for no height change, -1 for down, 1 for up.
local Mouse = Vector3.new(0,0,0)

seat:GetPropertyChangedSignal("Occupant"):connect(function()
	local Humanoid = seat.Occupant
	local Char = Humanoid and Humanoid.Parent
	local Player = Char and game.Players:GetPlayerFromCharacter(Char)
	local controlla = script.LocalScript:Clone()
	controlla.heli.Value = script.Parent
	if Player then
		controlla.Parent = Char
		controlla.Disabled = false
		speed.MaxForce = Vector3.new(math.huge,math.huge,math.huge)
		gyro.maxTorque=Vector3.new(1e4,1e4,1e4)
		repeat
			if height == 1 then -- we going up
				speed.Velocity = (engine.CFrame.UpVector)*inc
			end
			if height == -1 then -- we going down
				speed.Velocity = (engine.CFrame.UpVector)*-inc
			end
			if seat.Throttle == 1 then --- we are going forward
				speed.Velocity = (engine.CFrame.lookVector)*inc
			end
			if seat.Throttle == -1 then --- we are going backwards
				speed.Velocity = (engine.CFrame.lookVector)*-inc
			end
			if seat.Steer == 1 then --- we are going right
				speed.Velocity = (engine.CFrame.RightVector)*inc
			end
			if seat.Steer == -1 then --- we are going left
				speed.Velocity = (engine.CFrame.RightVector)*-inc
			end
			game["Run Service"].Heartbeat:wait(1)
		until
		not seat.Occupant
	end
end)

connection.OnServerInvoke = (function(player,mouse,heightChange)
	if mouse ~= nil then -- constantly updating the orientation of the model to the player's mouse.
		Mouse = mouse
		gyro.cframe = mouse
	end
	height = heightChange --- constantly checking for changes in height.
end)

Client-sided code for controlling:

local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local inputS=game:getService("UserInputService")

mouse.Move:connect(function() 
	--if inputS.TouchEnabled == false then
	if script.heli.Value ~= nil then
		script.heli.Value.Control:InvokeServer(mouse.Hit)
	end
end)

inputS.InputBegan:connect(function(input)
	local code=input.KeyCode
	if code==Enum.KeyCode.LeftShift or code==Enum.KeyCode.DPadUp then
		script.heli.Value.Control:InvokeServer(mouse.Hit,1)
	elseif code==Enum.KeyCode.LeftControl or code == Enum.KeyCode.DPadDown then
		script.heli.Value.Control:InvokeServer(mouse.Hit,-1)
	end 
end)

inputS.InputEnded:connect(function(input)
	local code=input.KeyCode
	if (code==Enum.KeyCode.LeftShift or code == Enum.KeyCode.DPadUp) or (code==Enum.KeyCode.LeftControl or code == Enum.KeyCode.DPadDown) then
		script.heli.Value.Control:InvokeServer(mouse.Hit,0)
	end
end)
1 Like

Instead of changing the speed for each individual keypress which is both cumbersome and a terrible idea, you can sum up the motion vectors from each key and use that as the collective vector direction.
Here’s the idea: each keypress will add a vector pertaining to that key’s direction. Pressing the W key will add the vector -Vector3.zAxis to the collective one. And any key releases will deduct the same vector. Releasing the W key will subtract -Vector3.zAxis, etc. A combination of keypresses will add up the vectors accordingly and give you the net direction.

Because this collective vector is in world space, you must convert it back into object space to get the appropriate velocity. You can do this by turning the vector into a CFrame (by simply tossing it into the constructor) and then using that to calculate a position on the velocity’s vector, find the normalized displacement, and finally use that as the velocity.

Below is some pseudocode.

local collective: Vector3 = Vector3.zero

on "W" key pressed: collective += -Vector3.zAxis
on "A" key pressed: collective += Vector3.xAxis
--etc

on "W" key release: collective -= -Vector3.zAxis
on "A" key release: collective -= Vector3.xAxis
function updateVelocity()
    local offset: CFrame = helicopter.CFrame * CFrame.new(collective.Unit)
    local direction: Vector3 = offset.Position - helicopter.Position --offset is already normalized so we don't need to do it again
    bodyMover.Velocity = direction * power
end
2 Likes

I use Linearvelocity for some stuff, it’ll work similarly to Bodyvelocity I think. Unsure on a newer counterpart to Bodygyro though.

As I said in my original post, I am trying to keep most of the helicopter’s movement (forward, backwards, right, left) determined by the Throttle and Steer properties of a VehicleSeat within said helicopter - this makes it easier to keep A) cross-platform compatible and B) keeps most of the movement input server-sided so I don’t need to use remotes which could have latency issues. I would prefer this method over tying it to a PC-centric control scheme based on WASD

I am not super familiar with CFrame as I have only really used it to create jets/planes - a pretty straight forward task as I’m only making the jet go constantly forward and move around via the pilot’s cursor. Obviously, with helicopters, they have far more directions of possible motions in flight than planes + they can hover. I have done some helicopter stuff in the past but it rarely came out smooth/exactly how I wanted it to be.

In any case, I tried the following code going off of your psuedo code, but keeping the VehicleSeat as the primary driver of motion. When I enter the pilot seat, it immediately kills my avatar and despawns the helicopter:

--- snippet of code of the edited "repeat until" code from my original post
repeat
			--- changes in height
			local collective = Vector3.zero
			if height == 1 then -- we going up
				collective += Vector3.yAxis
			end
			if height == -1 then -- we going down
				collective += -Vector3.yAxis
			end
			if height == 0 then
				collective -= Vector3.yAxis
			end
			--- changes in forward direction
			if seat.Throttle == 1 then --- we are going forward
				collective += -Vector3.zAxis
			end
			if seat.Throttle == -1 then --- we are going backwards
				collective += Vector3.zAxis
			end
			if seat.Throttle == 0 then --- we are going backwards
				collective -= Vector3.zAxis
			end
			--steering
			if seat.Steer == 1 then --- we are going right
				collective += Vector3.xAxis
			end
			if seat.Steer == -1 then --- we are going left
				collective += -Vector3.xAxis
			end
			if seat.Steer == 0 then --- we are going left
				collective -= Vector3.xAxis
			end
			--- now implement the changes
			local offset = engine.CFrame * CFrame.new(collective.Unit)
			local direction: Vector3 = offset.Position - engine.Position --offset is already normalized so we don't need to do it again
			speed.Velocity = direction * inc
			game["Run Service"].Heartbeat:wait(1)
		until
		not seat.Occupant
1 Like

LinearVelocity was by far the easier BodyMover replacement to work with/the one that replicated its predecessor very well - a lot of the others just did not work the same, or were very finnicky. For example, BodyPosition’s replacement, AlignPosition, just does not replicate it well whatsoever.
That being said, I would rather use BodyVelocity and BodyGyro together than mix an older BodyMover with a newer constrain based one; it just lookes weird to use the newer LinearVelocity but keep using the older BodyGyro, plus IDK if that would cause issues.

1 Like

Yes, I know, and I am basing the potential solution on what you already have in your code. The system that you currently use is basically identical to relying on WASD inputs in terms of mechanics. I just worded it differently.

Probably an unrelated issue, but I noticed that the directional vectors are being continuously summed up with each update period when they should only add/subtract once.
Other than that, I’m sure you can do the troubleshooting yourself. Print out variables, check and stuff. The collective vector should have a maximum magnitude of √3 (i.e. the player is moving in 3 different axes simultaneously), and opposing directions would cancel.

1 Like