Fly script animations aren't working (Heartbeat Service Issue)

I’m making an angel rpg and have been trying to learn scripting so I can manage to do some things by myself without hiring a team to do everything. So I looked up a tutorial and used a fly script from Suphi Kaner that allowed me to understand the uses behind certain functions. The script works great but I wanted to make my own small adjustments.

Scripting Video Used

Here is my issue

I added my own flying animations, and I made them play based on if they’re Idle or Moving. This is the code that makes that work

--Animations--
local FlyForward = script:WaitForChild("FlyForward")
local animationTrack = character.Humanoid.Animator:LoadAnimation(FlyForward)
animationTrack.Priority = Enum.AnimationPriority.Movement

local FlyIdle = script:WaitForChild("FlyIdle")
local flyIdleAnimTrack = character.Humanoid.Animator:LoadAnimation(FlyIdle)
flyIdleAnimTrack.Priority = Enum.AnimationPriority.Idle

local function FlyAction(actionName, inputState, inputObject)
	if inputState ~= Enum.UserInputState.Begin then return Enum.ContextActionResult.Pass end
	if connection == true then return Enum.ContextActionResult.Pass end
	if connection == nil then
		connection = true
		if character.Humanoid.FloorMaterial ~= Enum.Material.Air then       -- if character is standing on the floor when flying, will make character jump before flight
			character.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			task.wait(0.1)
		end
		--Flight Mode--
		vectorForce.Enabled = true
		alignOrientation.CFrame = primaryPart.CFrame
		alignOrientation.Enabled = true
		flyIdleAnimTrack:Play() --the animation it starts with
		character.Humanoid:ChangeState(Enum.HumanoidStateType.Physics) --this makes it so our humanoid is not in control of the fly physics
		connection = runService.Heartbeat:Connect(function(deltaTime)
			vectorForce.Force = gravityVector * primaryPart.AssemblyMass
			local moveVector = Vector3.new(character.Humanoid.MoveDirection.X, yAxis, character.Humanoid.MoveDirection.Z)
			if moveVector.Magnitude > 0 then
				animationTrack:Play() --switches to flying animation when moving
				moveVector = moveVector.Unit
				vectorForce.Force += moveVector * force * primaryPart.AssemblyMass
				if math.abs(moveVector.Y) == 1 then
					alignOrientation.CFrame = CFrame.lookAt(Vector3.new(0, 0, 0), moveVector -primaryPart.CFrame.LookVector) * CFrame.fromOrientation(-math.pi / 2, 0, 0)  
				else
					alignOrientation.CFrame = CFrame.lookAt(Vector3.new(0, 0, 0), moveVector)  --* CFrame.fromOrientation(-math.pi / 2, 0, 0) <-- Makes character flip forward when flying forward 
				end
				else animationTrack:Stop() --switches back to idle animation (our default)
			end

Now it works as intended- when I stop moving, the flying Idle plays, when I move the flying forward animation plays. But the issue is- the flying forward animation DOESN’T play. It freezes mid animation when I fly around.

Example

I know the animation isn’t the problem, because when I replace the default animation (FlyIdleTrack) to the flying forward animation, the animation plays fine. So I’m guessing its the way I input the code that makes it look laggy and freezes. I tried alternatives, like placing the the animations in different parts of the script, or switching the animations places, but it doesn’t work as intended. I’m stumped on how to fix this.

I also tried asking for help in Suphi’s discord, (a server where programmers help each other and has more tutorials) and even asked Suphi himself but I was just ignored.

3 Likes

found the issue lies within the heartbeat service. The animation will reset with every count of heartbeat. Unfortunately the rest of the script wont function if the heartbeat service is removed from its place. And even worse, the animations won’t play on cue if also moved. Is there a way to keep the heartbeat service from affecting the animations under it?

1 Like

No clue about heartbeat alternatives, but changing you priorities might help at least a little bit. The Idle animations could be set to movement, and the fly to be set to action
Might not be helpful, sorry

are you disconnecting your heartbeat connections when you release a key?

what could be happening here is an older heartbeat connection is running animationTrack:Stop()

edit: nvm the vid you sent disconnected the heartbeat connection

my only idea is to check if the animation track is playing before stopping or starting it

if magnitude > 0 then
  if animationTrack.IsPlaying == false then
     animationTrack:Play()
  end
else
  if animationTrack.IsPlaying == true then
    animationTrack:Stop()
  end
end

I gave it a try and unfortunately nothing changed :frowning: Although I appreciate your efforts to help

I gave your code a try and it ended up breaking the script :frowning:

The animation is definitely playing however the heartbeat ticks will keep resetting the animation instead of allowing it to fully play

Why do you call Play() on the animation every heart beat?

If I skimmed your script correctly as I was searching for another post (which I found btw though no one was asking), since you call :Play() every frame the player is not stationary, it will literally :Play() right at the beginning of the animation every frame, or I think so at least?

So however you want to go about it, you should call :Play() only once, when the player “starts” to “fly forward”. If you want to change how fast the wings flap based on speed, then you should call Animation:AdjustSpeed(speed of flight divided by maximum flight speed) every time the speed/moveVector/whatever you call it changes magnitude, which isn’t necessary every heart beat, though I suppose you could check every heartbeat…

If you need help writing it out or don’t understand, lmk maybe I or someone else can help you further.

2 Likes

I think I understand what you mean, but I might understand better if you could provide an example of what you mean in script form. I’m not very good at scripting so I have a hard time understanding where certain code should be placed for it to work.

Yea well it’s not so much where code should be placed for it to work, but why a certain code functions correctly or not correctly.

So to start, I mentioned you should only call Animation:Play() when you begin to “fly”. There are multiple ways to go about this, but just for simplicity’s sake since I don’t have your full script, let’s instead use when the player changes their move direction. So there’s a nifty roblox method called “GetPropertyChangedSignal” that fires every time the specific property changes.

So whenever “MoveDirection” changes, you should either start the flying animation or stop the flying animation.

local function moveDirectionChanged(newspeed)
	if newspeed > 0 then
		if not Anim.IsPlaying() then -- check if it's already playing, if not, play it
			Anim:Play()
		end
	else -- stop flying
		Anim:Stop()
	end
end


humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(moveDirectionChanged)

You know I realized just now I probably made this too complicated for your specific case anyway, so disregard what I said above. Usually, you don’t want to have a connection that checks every frame since there may be a more efficient/less laggy method, but I don’t have your full script to make that assumption anyway. It’s sort of like the XY problem I was trying to address.

So, in your specific case, all you have to do is check if the animation track is currently playing, and if not, then play it.

if not animationTrack.IsPlaying() then
animationTrack:Play()
end

I tried both methods and none of them work. Since you keep saying you can’t make an assumption since you don’t have the full script, then here is the script.

local runService = game:GetService("RunService")
local contextActionService = game:GetService("ContextActionService")
local connection = nil
local character = script.Parent
local primaryPart = character.PrimaryPart
local gravityVector = Vector3.new(0, game.Workspace.Gravity, 0)
local drag = 0.5
local force = 400
local yAxis = 0



local vectorForce = script:WaitForChild("VectorForce")
vectorForce.Attachment0 = primaryPart.RootRigAttachment

local alignOrientation = script:WaitForChild("AlignOrientation") --prevents us from spinning out of control
alignOrientation.Attachment0 = primaryPart.RootRigAttachment

--Animations--
local FlyForward = script:WaitForChild("FlyForward")
local animationTrack = character.Humanoid.Animator:LoadAnimation(FlyForward)
animationTrack.Priority = Enum.AnimationPriority.Movement

local FlyIdle = script:WaitForChild("FlyIdle")
local flyIdleAnimTrack = character.Humanoid.Animator:LoadAnimation(FlyIdle)
flyIdleAnimTrack.Priority = Enum.AnimationPriority.Idle

local function FlyAction(actionName, inputState, inputObject)
	if inputState ~= Enum.UserInputState.Begin then return Enum.ContextActionResult.Pass end
	if connection == true then return Enum.ContextActionResult.Pass end
	if connection == nil then
		connection = true
		if character.Humanoid.FloorMaterial ~= Enum.Material.Air then       -- if character is standing on the floor when flying, will make character jump before flight
			character.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			task.wait(0.1)
		end
		--Flight Mode--
		vectorForce.Enabled = true
		alignOrientation.CFrame = primaryPart.CFrame
		alignOrientation.Enabled = true
		flyIdleAnimTrack:Play() --the animation it starts with
		character.Humanoid:ChangeState(Enum.HumanoidStateType.Physics) --this makes it so our humanoid is not in control of the fly physics
		connection = runService.Heartbeat:Connect(function(deltaTime:number)
			print ("HB") --HB is heartbeat
			vectorForce.Force = gravityVector * primaryPart.AssemblyMass
			local moveVector = Vector3.new(character.Humanoid.MoveDirection.X, yAxis, character.Humanoid.MoveDirection.Z)
			if moveVector.Magnitude > 0 then 
				animationTrack:Play() --switches to flying animation when moving 
				moveVector = moveVector.Unit
				vectorForce.Force += moveVector * force * primaryPart.AssemblyMass
				if math.abs(moveVector.Y) == 1 then
					alignOrientation.CFrame = CFrame.lookAt(Vector3.new(0, 0, 0), moveVector -primaryPart.CFrame.LookVector) * CFrame.fromOrientation(-math.pi / 2, 0, 0)  
				else
					alignOrientation.CFrame = CFrame.lookAt(Vector3.new(0, 0, 0), moveVector)  --* CFrame.fromOrientation(-math.pi / 2, 0, 0) <-- Makes character flip forward when flying forward 
				end
			else
				animationTrack:Stop()
			end
			if primaryPart.AssemblyLinearVelocity.Magnitude > 0 then
				local dragVector = -primaryPart.AssemblyLinearVelocity.Unit
				local v2 = primaryPart.AssemblyLinearVelocity.Magnitude ^ 1.5
				vectorForce.Force += dragVector * drag *primaryPart.AssemblyMass * v2
			end
			
		end)
	else
		vectorForce.Enabled = false
		alignOrientation.Enabled = false
		flyIdleAnimTrack:Stop()
		character.Humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
		connection:Disconnect()
		connection = nil
	end
	return Enum.ContextActionResult.Pass
end

local function UpAction(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.Begin then yAxis = 1 else yAxis = 0 end
	return Enum.ContextActionResult.Pass
end

local function DownAction(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.Begin then yAxis = -1 else yAxis = 0 end
	return Enum.ContextActionResult.Pass
end

--Actions--

contextActionService:BindAction("Fly", FlyAction, true, Enum.KeyCode.F) -- The key pressed to make us fly
contextActionService:SetTitle("Fly", "Fly") --This is our title for mobile fly button
contextActionService:SetPosition("Fly",UDim2.new(1, -150, 1, -80)) --this is our position of the button

contextActionService:BindAction("Up", UpAction, true, Enum.KeyCode.Space)
contextActionService:SetTitle("Up", "↑")
contextActionService:SetPosition("Up", UDim2.new(1, -55, 1, -145))

contextActionService:BindAction("Down", DownAction, true, Enum.KeyCode.LeftShift)
contextActionService:SetTitle("Down", "↓")
contextActionService:SetPosition("Down", UDim2.new(1, -105, 1, -145))

mb I type these at night so I don’t double check my syntax and might have fat-fingered the code, but the output should have gave it away if you typed it in. IsPlaying is a boolean property value of animation track, not a function, so remove the parentheses at the end. The logic still is in tact. Check if the animation track is playing, and if it isn’t playing, then play it. Otherwise, don’t keep replaying it every frame.

if not animationTrack.IsPlaying then
animationTrack:Play()
end

check out the example script in the api for more information