[SOLVED] How to Create Animations with Smooth Transition

I’m trying to create Animations that gradually blend with the next Animation. I don’t know how to apply smooth transitioning for the Animations.

THIS IS A EXAMPLE:

ANIMATION SCRIPT:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local MODULES = ReplicatedStorage.MODULES
local EVENTS = ReplicatedStorage.EVENTS
local ANIMATIONS = ReplicatedStorage.ANIMATIONS

local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Character:FindFirstChildOfClass("Humanoid")
if not Humanoid or Humanoid.Name == "AI" then return end
local HRP = Character:WaitForChild("HumanoidRootPart")

local SPEED_CONFIGURATION = require(MODULES:WaitForChild("SPEED_CONFIG"))

-- PLAYER ANIMATIONS
local IDLE_ANIMATION = ANIMATIONS.PLAYER.IDLE
local WALK_ANIMATION = ANIMATIONS.PLAYER.WALK
local RUN_ANIMATION = ANIMATIONS.PLAYER.RUN
local JUMP_ANIMATION = ANIMATIONS.PLAYER.JUMP
-- PLAYER HURT ANIMATIONS
local HURT_IDLE__ANIMATION = ANIMATIONS.PLAYER.HURT.HURT_IDLE
local HURT_WALK_ANIMATION = ANIMATIONS.PLAYER.HURT.HURT_WALK
local HURT_RUN_ANIMATION = ANIMATIONS.PLAYER.HURT.HURT_RUN

local UpdatePhase_EVENT = EVENTS.SPEED:WaitForChild("UpdatePhase")

local currentPhase = 1

local currentAnimSpeed = 1
local targetAnimSpeed = 1
local tweenSpeedRate = 3
local Hurt = false
local hurtTimer = nil

local walkTrack, runTrack, idleTrack
local hurtIdleTrack, hurtWalkTrack, hurtRunTrack
local jumpTrack

local function GetTargetSpeed(phase)
	return 1 + (phase - 1) * SPEED_CONFIGURATION.ANIMATION_SPEED_MULTIPLIER
end

local function PlayHurtAnimation()
	if walkTrack.IsPlaying then walkTrack:Stop() end
	if runTrack.IsPlaying then runTrack:Stop() end
	if idleTrack.IsPlaying then idleTrack:Stop() end

	local speed = HRP.Velocity
	local horizontalSpeed = Vector3.new(speed.X, 0, speed.Z).Magnitude
	local STANDSTILL_THRESHOLD = 0.1

	if horizontalSpeed <= STANDSTILL_THRESHOLD then
		if not hurtIdleTrack.IsPlaying then
			hurtIdleTrack.TimePosition = 0
			hurtIdleTrack:Play()
		end
		if hurtWalkTrack.IsPlaying then hurtWalkTrack:Stop() end
		if hurtRunTrack.IsPlaying then hurtRunTrack:Stop() end
	else
		if currentPhase == 1 then
			if not hurtWalkTrack.IsPlaying then
				hurtWalkTrack.TimePosition = 0
				hurtWalkTrack:Play()
			end
			if hurtIdleTrack.IsPlaying then hurtIdleTrack:Stop() end
			if hurtRunTrack.IsPlaying then hurtRunTrack:Stop() end
		else
			if not hurtRunTrack.IsPlaying then
				hurtRunTrack.TimePosition = 0
				hurtRunTrack:Play()
			end
			if hurtIdleTrack.IsPlaying then hurtIdleTrack:Stop() end
			if hurtWalkTrack.IsPlaying then hurtWalkTrack:Stop() end
		end
	end
end

local function UpdateMovementAnimation()
	if not Humanoid or not Humanoid:IsDescendantOf(workspace) then return end
	if Hurt then
		PlayHurtAnimation()
		return
	end
	if not walkTrack or not runTrack or not idleTrack then return end

	targetAnimSpeed = GetTargetSpeed(currentPhase)

	local speed = HRP.Velocity
	local horizontalSpeed = Vector3.new(speed.X, 0, speed.Z).Magnitude

	local STANDSTILL_THRESHOLD = 0.1

	if horizontalSpeed <= STANDSTILL_THRESHOLD then
		if walkTrack.IsPlaying then walkTrack:Stop() end
		if runTrack.IsPlaying then runTrack:Stop() end
		if not idleTrack.IsPlaying then
			idleTrack.TimePosition = 0
			idleTrack:Play()
		end
	else
		if idleTrack.IsPlaying then idleTrack:Stop() end

		if currentPhase == 1 then
			if runTrack.IsPlaying then runTrack:Stop() end
			if not walkTrack.IsPlaying then
				walkTrack.TimePosition = 0
				walkTrack:Play()
			end
		else
			if walkTrack.IsPlaying then walkTrack:Stop() end
			if not runTrack.IsPlaying then
				runTrack.TimePosition = 0
				runTrack:Play()
			end
		end
	end
end

local function SetupAnimator()
	if not Character or not Character:IsDescendantOf(workspace) then return end

	Humanoid = Character:FindFirstChildOfClass("Humanoid")
	if not Humanoid or Humanoid.Name == "AI" then return end
	local Animator = Humanoid:FindFirstChildOfClass("Animator") or Humanoid:WaitForChild("Animator")

	-- PLAYER ANIMATIONS SET-UP
	walkTrack = Animator:LoadAnimation(WALK_ANIMATION)
	runTrack = Animator:LoadAnimation(RUN_ANIMATION)
	idleTrack = Animator:LoadAnimation(IDLE_ANIMATION)
	jumpTrack = Animator:LoadAnimation(JUMP_ANIMATION)
	walkTrack.Looped = true
	runTrack.Looped = true
	idleTrack.Looped = true
	jumpTrack.Looped = false

	-- PLAYER HURT ANIMATIONS SET-UP
	hurtIdleTrack = Animator:LoadAnimation(HURT_IDLE__ANIMATION)
	hurtWalkTrack = Animator:LoadAnimation(HURT_WALK_ANIMATION)
	hurtRunTrack = Animator:LoadAnimation(HURT_RUN_ANIMATION)
	hurtIdleTrack.Looped = true
	hurtWalkTrack.Looped = true
	hurtRunTrack.Looped = true

	UpdateMovementAnimation()

	Humanoid.StateChanged:Connect(function(oldState, newState)
		if newState == Enum.HumanoidStateType.Jumping then
			
			if walkTrack.IsPlaying then walkTrack:Stop() end
			if runTrack.IsPlaying then runTrack:Stop() end
			if idleTrack.IsPlaying then idleTrack:Stop() end
			if hurtIdleTrack.IsPlaying then hurtIdleTrack:Stop() end
			if hurtWalkTrack.IsPlaying then hurtWalkTrack:Stop() end
			if hurtRunTrack.IsPlaying then hurtRunTrack:Stop() end

			if not jumpTrack.IsPlaying then
				jumpTrack.TimePosition = 0
				jumpTrack:Play()
			end

		elseif newState == Enum.HumanoidStateType.Landed or newState == Enum.HumanoidStateType.Running or newState == Enum.HumanoidStateType.RunningNoPhysics then
			if jumpTrack.IsPlaying then jumpTrack:Stop() end

			if Hurt then
				PlayHurtAnimation()
			else
				UpdateMovementAnimation()
			end
		end
	end)

	local lastHealth = Humanoid.Health
	Humanoid.HealthChanged:Connect(function(newHealth)
		if newHealth < lastHealth then
			if hurtTimer then
				hurtTimer:Cancel()
				hurtTimer = nil
			end
			Hurt = true
			PlayHurtAnimation()
			
			hurtTimer = task.delay(SPEED_CONFIGURATION.HURT_DURATION, function()
				Hurt = false
				hurtTimer = nil
				if hurtIdleTrack.IsPlaying then hurtIdleTrack:Stop() end
				if hurtWalkTrack.IsPlaying then hurtWalkTrack:Stop() end
				if hurtRunTrack.IsPlaying then hurtRunTrack:Stop() end
				UpdateMovementAnimation()
			end)
		end
		lastHealth = newHealth
	end)
end


SetupAnimator()

Player.CharacterAdded:Connect(function(char)
	Character = char
	Humanoid = Character:WaitForChild("Humanoid")
	HRP = Character:WaitForChild("HumanoidRootPart")
	SetupAnimator()
end)

RunService.RenderStepped:Connect(function(dt)
	if math.abs(currentAnimSpeed - targetAnimSpeed) > 0.01 then
		currentAnimSpeed = currentAnimSpeed + (targetAnimSpeed - currentAnimSpeed) * math.clamp(tweenSpeedRate * dt, 0, 1)

		if currentPhase == 1 and walkTrack and walkTrack.IsPlaying then
			walkTrack:AdjustSpeed(currentAnimSpeed)
		elseif currentPhase > 1 and runTrack and runTrack.IsPlaying then
			runTrack:AdjustSpeed(currentAnimSpeed)
		end
	end
	UpdateMovementAnimation()
end)

UpdatePhase_EVENT.Event:Connect(function(newPhase)
	currentPhase = newPhase
	UpdateMovementAnimation()
end)
1 Like

You could use inverse kinetics for that but its very hard to implement (i think)

THIS IS A SHOWCASE OF THE ISSUE:

1 Like

I’ve fixed the issue:

ANIMATION SCRIPT:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")

local MODULES = ReplicatedStorage.MODULES
local EVENTS = ReplicatedStorage.EVENTS
local SOUNDS = ReplicatedStorage.SOUNDS
local ANIMATIONS = ReplicatedStorage.ANIMATIONS

local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Character:FindFirstChildOfClass("Humanoid")
if not Humanoid or Humanoid.Name == "AI" then return end
local HRP = Character:WaitForChild("HumanoidRootPart")

local SPEED_CONFIGURATION = require(MODULES:WaitForChild("SPEED_CONFIG"))

-- PLAYER ANIMATIONS
local IDLE_ANIMATION = ANIMATIONS.PLAYER.IDLE
local WALK_ANIMATION = ANIMATIONS.PLAYER.WALK
local RUN_ANIMATION = ANIMATIONS.PLAYER.RUN
local JUMP_ANIMATION = ANIMATIONS.PLAYER.JUMP
-- PLAYER HURT ANIMATIONS
local HURT_IDLE__ANIMATION = ANIMATIONS.PLAYER.HURT.HURT_IDLE
local HURT_WALK_ANIMATION = ANIMATIONS.PLAYER.HURT.HURT_WALK
local HURT_RUN_ANIMATION = ANIMATIONS.PLAYER.HURT.HURT_RUN

local UpdatePhase_EVENT = EVENTS.SPEED:WaitForChild("UpdatePhase")

local Heartbeat_SOUND = SOUNDS.Heartbeat

local currentPhase = 1

local currentAnimSpeed = 1
local targetAnimSpeed = 1
local tweenSpeedRate = 3
local Hurt = false
local hurtTimer = nil

local walkTrack, runTrack, idleTrack
local hurtIdleTrack, hurtWalkTrack, hurtRunTrack
local jumpTrack

local currentPlayingTrack = nil
local currentHurtTrack = nil

local ANIMATION_SPEED = SPEED_CONFIGURATION.ANIMATION_SPEED
local OTHER_ANIMATION_SPEED = SPEED_CONFIGURATION.OTHER_ANIMATION_SPEED

local function CrossfadeAnimation(oldTrack, newTrack)
	if newTrack == idleTrack then
		if oldTrack and oldTrack.IsPlaying then
			oldTrack:Stop(OTHER_ANIMATION_SPEED)
		end
		if newTrack then
			newTrack:Play(OTHER_ANIMATION_SPEED)
		end
	else
		if oldTrack and oldTrack.IsPlaying then
			oldTrack:Stop(OTHER_ANIMATION_SPEED)
		end
		if newTrack then
			newTrack:Play(OTHER_ANIMATION_SPEED)
		end
	end
end

local function PlayHurtAnimation()
	if currentPlayingTrack and currentPlayingTrack.IsPlaying then
		currentPlayingTrack:Stop(ANIMATION_SPEED)
		currentPlayingTrack = nil
	end

	local speed = HRP.Velocity
	local horizontalSpeed = Vector3.new(speed.X, 0, speed.Z).Magnitude
	local STANDSTILL_THRESHOLD = 0.1

	local newHurtTrack = nil

	if horizontalSpeed <= STANDSTILL_THRESHOLD then
		newHurtTrack = hurtIdleTrack
	else
		if currentPhase == 1 then
			newHurtTrack = hurtWalkTrack
		else
			newHurtTrack = hurtRunTrack
		end
	end

	if currentHurtTrack ~= newHurtTrack then
		CrossfadeAnimation(currentHurtTrack, newHurtTrack)
		currentHurtTrack = newHurtTrack
	end
end

local function StopHurtAnimations()
	if currentHurtTrack and currentHurtTrack.IsPlaying then
		currentHurtTrack:Stop(ANIMATION_SPEED)
		currentHurtTrack = nil
	end
end

function TargetSpeed(phase)
	return 1 + (phase - 1) * SPEED_CONFIGURATION.ANIMATION_SPEED_MULTIPLIER
end

local function UpdateMovementAnimation()
	if not Humanoid or not Humanoid:IsDescendantOf(workspace) then return end
	if Hurt then
		PlayHurtAnimation()
		return
	end
	if not walkTrack or not runTrack or not idleTrack then return end

	targetAnimSpeed = TargetSpeed(currentPhase)

	local speed = HRP.Velocity
	local horizontalSpeed = Vector3.new(speed.X, 0, speed.Z).Magnitude

	local STANDSTILL_THRESHOLD = 0.1

	local newTrack = nil

	if horizontalSpeed <= STANDSTILL_THRESHOLD then
		newTrack = idleTrack
	else
		if currentPhase == 1 then
			newTrack = walkTrack
		else
			newTrack = runTrack
		end
	end

	if currentPlayingTrack ~= newTrack then
		CrossfadeAnimation(currentPlayingTrack, newTrack)
		currentPlayingTrack = newTrack
	end
	StopHurtAnimations()
end

local function SetupAnimator()
	if not Character or not Character:IsDescendantOf(workspace) then return end

	Humanoid = Character:FindFirstChildOfClass("Humanoid")
	if not Humanoid or Humanoid.Name == "AI" then return end
	local Animator = Humanoid:FindFirstChildOfClass("Animator") or Humanoid:WaitForChild("Animator")

	-- PLAYER ANIMATIONS SET-UP
	walkTrack = Animator:LoadAnimation(WALK_ANIMATION)
	runTrack = Animator:LoadAnimation(RUN_ANIMATION)
	idleTrack = Animator:LoadAnimation(IDLE_ANIMATION)
	jumpTrack = Animator:LoadAnimation(JUMP_ANIMATION)
	walkTrack.Looped = true
	runTrack.Looped = true
	idleTrack.Looped = true
	jumpTrack.Looped = false

	-- PLAYER HURT ANIMATIONS SET-UP
	hurtIdleTrack = Animator:LoadAnimation(HURT_IDLE__ANIMATION)
	hurtWalkTrack = Animator:LoadAnimation(HURT_WALK_ANIMATION)
	hurtRunTrack = Animator:LoadAnimation(HURT_RUN_ANIMATION)
	hurtIdleTrack.Looped = true
	hurtWalkTrack.Looped = true
	hurtRunTrack.Looped = true

	currentPlayingTrack = nil
	currentHurtTrack = nil

	UpdateMovementAnimation()

	Humanoid.StateChanged:Connect(function(oldState, newState)
		if newState == Enum.HumanoidStateType.Jumping then
			-- Stop all other tracks smoothly
			if currentPlayingTrack and currentPlayingTrack.IsPlaying then
				currentPlayingTrack:Stop(ANIMATION_SPEED)
				currentPlayingTrack = nil
			end
			if currentHurtTrack and currentHurtTrack.IsPlaying then
				currentHurtTrack:Stop(ANIMATION_SPEED)
				currentHurtTrack = nil
			end
			if jumpTrack and not jumpTrack.IsPlaying then
				jumpTrack.TimePosition = 0
				jumpTrack:Play(0.2)
			end

		elseif newState == Enum.HumanoidStateType.Landed or
			newState == Enum.HumanoidStateType.Running or
			newState == Enum.HumanoidStateType.RunningNoPhysics then

			if jumpTrack and jumpTrack.IsPlaying then
				jumpTrack:Stop(0.2)
			end

			if Hurt then
				PlayHurtAnimation()
			else
				UpdateMovementAnimation()
			end
		end
	end)

	local lastHealth = Humanoid.Health
	local currentHeartbeatTween
	local heartbeatStopTask

	Humanoid.HealthChanged:Connect(function(newHealth)
		if newHealth < lastHealth then
			if hurtTimer then
				task.cancel(hurtTimer)
				hurtTimer = nil
			end

			Hurt = true
			PlayHurtAnimation()

			if Heartbeat_SOUND.IsPlaying then
				Heartbeat_SOUND:Stop()
			end
			Heartbeat_SOUND.PlaybackSpeed = 0.8
			Heartbeat_SOUND:Play()

			if currentHeartbeatTween then
				currentHeartbeatTween:Cancel()
			end

			local tweenInfo = TweenInfo.new(10, Enum.EasingStyle.Linear)
			currentHeartbeatTween = TweenService:Create(Heartbeat_SOUND, tweenInfo, { PlaybackSpeed = 1.9 })
			currentHeartbeatTween:Play()

			if heartbeatStopTask then
				task.cancel(heartbeatStopTask)
			end

			heartbeatStopTask = task.delay(10, function()
				if not Hurt and Heartbeat_SOUND.IsPlaying then
					Heartbeat_SOUND:Stop()
				end
			end)

			hurtTimer = task.delay(SPEED_CONFIGURATION.HURT_DURATION, function()
				Hurt = false
				hurtTimer = nil

				if Heartbeat_SOUND.IsPlaying then
					Heartbeat_SOUND:Stop()
				end

				StopHurtAnimations()
				UpdateMovementAnimation()
			end)
		end

		lastHealth = newHealth
	end)
end

SetupAnimator()

Player.CharacterAdded:Connect(function(char)
	Character = char
	Humanoid = Character:WaitForChild("Humanoid")
	HRP = Character:WaitForChild("HumanoidRootPart")
	SetupAnimator()
end)

RunService.RenderStepped:Connect(function(dt)
	if currentPlayingTrack then
		if math.abs(currentAnimSpeed - targetAnimSpeed) > 0.01 then
			currentAnimSpeed = currentAnimSpeed + (targetAnimSpeed - currentAnimSpeed) * math.clamp(tweenSpeedRate * dt, 0, 1)
			currentPlayingTrack:AdjustSpeed(currentAnimSpeed)
		end
	elseif currentHurtTrack then
		if math.abs(currentAnimSpeed - targetAnimSpeed) > 0.01 then
			currentAnimSpeed = currentAnimSpeed + (targetAnimSpeed - currentAnimSpeed) * math.clamp(tweenSpeedRate * dt, 0, 1)
			currentHurtTrack:AdjustSpeed(currentAnimSpeed)
		end
	end
	UpdateMovementAnimation()
end)

UpdatePhase_EVENT.Event:Connect(function(newPhase)
	currentPhase = newPhase
	UpdateMovementAnimation()
end)
1 Like

If you have found a solution, please mark this topic as solved.

1 Like