2D Character Sprite Animation goes too fast when pressing two keys (W and A)

  1. What do you want to achieve? Keep it simple and clear!

I am trying to make a 2.5D RPG game.

  1. What is the issue? Include screenshots / videos if possible!

The issue is that when I press two keys (W and A) the character animation becomes weird and goes faster and also sometimes never even animates. Also, when you let go of one key (A) and keep holding another key like W the game thinks the side (one of my variables) is right even though I am holding W which is supposed to make the side up.

  1. What solutions have you tried so far? Did you look for solutions on the Developer Hub?

I tried to look for answers, but literally no one has done something like what I have done.
I’m pretty sure this has to do with coroutines but I’m not sure how to deal with it. I’m also not really used to scripting with events as soon as you press a key.

Video of glitch:

The error mainly has to do with this part of the script:
It basically deletes the old frame and clones a new one to be added in.

function move(key,off)
	local oldside = side
	local oldpos = ppart.Position
	while uis:IsKeyDown(key) do
		if uis:IsKeyDown(key) then
			overideside = false
			oldpos = ppart.Position
			oldside = side
			ppart.Position = ppart.Position + Vector3.new(off.x/8,off.y/8,off.z/8)
		else
			overideside = false
			break
		end
		rs.RenderStepped:Wait()
	end
end


function movement(key)
	moving = true
	if side == "Up" then
		lastkey = "up"
		move(key,Vector3.new(0,0,-speed))
	elseif side == "Down" then
		lastkey = "down"
		move(key,Vector3.new(0,0,speed))
	elseif side == "Left" then
		lastkey = "left"
		move(key,Vector3.new(-speed,0,0))
	elseif side == "Right" then
		lastkey = "right"
		move(key,Vector3.new(speed,0,0))
	end
end

function controls(key)
	if key == Enum.KeyCode.W then
		side = "Up"
	end
	if key == Enum.KeyCode.A then
		side = "Left"
	end
	if key == Enum.KeyCode.S then
		side = "Down"
	end
	if key == Enum.KeyCode.D then
		side = "Right"
	end
	movement(key)
end

uis.InputBegan:Connect(function(i,p)
	if p then return end
	local key = i.KeyCode
	if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then
		local ctrd = coroutine.create(controls)
		coroutine.resume(ctrd,key)
		moving = true
		animate(key)
	end
	if key == Enum.KeyCode.E then
		setframe("DownIdle")
	end
end)

uis.InputEnded:Connect(function(i,p)
	if p then return end
	local key = i.KeyCode
	if check_pressed() == "" then
		moving = false
		setframe(side.."Idle")
	end
end)
1 Like

Think of this, when you press the W key your speed increases by your normal speed. Now, if you were to press 2 keys at once, your basically adding the speed all together. Consider putting a limit on double key presses.

local MOVEMENTSpeed = 3

function move(key,off)
	local oldside = side
	local oldpos = ppart.Position
	while uis:IsKeyDown(key) do
		if uis:IsKeyDown(key) then
			overideside = false
			oldpos = ppart.Position
			oldside = side
			ppart.Position = ppart.Position + Vector3.new(off.x/MOVEMENTSpeed,off.y/MOVEMENTSpeed,off.z/MOVEMENTSpeed)
		else
			overideside = false
			break
		end
		rs.RenderStepped:Wait()
	end
end


function movement(key)
	moving = true
	if side == "Up" then
		lastkey = "up"
		move(key,Vector3.new(0,0,-speed))
	elseif side == "Down" then
		lastkey = "down"
		move(key,Vector3.new(0,0,speed))
	elseif side == "Left" then
		lastkey = "left"
		move(key,Vector3.new(-speed,0,0))
	elseif side == "Right" then
		lastkey = "right"
		move(key,Vector3.new(speed,0,0))
	end
end

function controls(key)
	if key == Enum.KeyCode.W then
		side = "Up"
	end
	if key == Enum.KeyCode.A then
		side = "Left"
	end
	if key == Enum.KeyCode.S then
		side = "Down"
	end
	if key == Enum.KeyCode.D then
		side = "Right"
	end
	movement(key)
end

uis.InputBegan:Connect(function(i,p)
	if p then return end
	local key = i.KeyCode
	if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then
		local ctrd = coroutine.create(controls)
		coroutine.resume(ctrd,key)
		moving = true
		animate(key)
	end
	if key == Enum.KeyCode.E then
		setframe("DownIdle")
	end
end)

uis.InputEnded:Connect(function(i,p)
	if p then return end
	local key = i.KeyCode
	if check_pressed() == "" then
		moving = false
		setframe(side.."Idle")
	end
end)
1 Like

I tried this by making the time waited for the frame to be changed get multiplied by the amount of keys pressed, but it ended up making the character not animate at all.

I was talking about the animation speed.

lower the “speed” value, or send full script

Also keep in mind that the 2D speed going up and down needs to be throttled in 3D space (north and south). That plane he is walking on is not flat to the screen.

Here is the full script:

wait()

local ts = game:GetService("TweenService")
local uis = game:GetService("UserInputService")
local rs = game:GetService("RunService")
local player = game:GetService("Players").LocalPlayer
local char = player.Character
local cam = workspace.CurrentCamera

while not char do
	char = player.Character
	wait()
end

local debugui = player.PlayerGui:WaitForChild("Debug")
local pchar = char:WaitForChild("Player")
local spritesheet = pchar:WaitForChild("SpriteSheet")
local pbb = pchar:WaitForChild("PlayerBillBoard")
local ppart = pchar.PrimaryPart
local hum = char:WaitForChild("Humanoid")
local hrp = char:WaitForChild("HumanoidRootPart")
hrp.Anchored = true

local keys = uis:GetKeysPressed()

local speed = 1
local lastkey = "down"
local olastkey = lastkey
local moving = false
local overideside = false
local side = "Down"
local animstep = 1
local frame = 1
local cammode = 1

local frames = spritesheet:GetChildren()
local wparts = workspace:GetDescendants()
local cparts = {}
local c = 0

for i,child in ipairs(wparts) do
	c = c+1
	if child.Name:match("Cutscene") then
		table.insert(cparts,c,child)
	end
end
c = 0

ppart.Position = workspace.Spawn.Position + Vector3.new(0,1,0)

function check_inside(a,b)
	if a.Position.x - b.Position.x <= b.Size.X/2 and a.Position.x - b.Position.x >= b.Size.X/-2 and a.Position.z - b.Position.z <= b.Size.z/2 and a.Position.z - b.Position.z >= b.Size.z/-2 then
		return true
	end
end

function check_pressed()
	local yss = {{Enum.KeyCode.W,"Up"},{Enum.KeyCode.A,"Left"},{Enum.KeyCode.S,"Down"},{Enum.KeyCode.D,"Right"}}
	local direction = ""
	for i,keyinfo in pairs(yss) do
		if uis:IsKeyDown(keyinfo[1]) then
			direction = direction..keyinfo[2]
		end
	end
	return direction
end

function setframe(f)
	local oldf = pbb:FindFirstChildOfClass("Frame")
	local newf = spritesheet:FindFirstChild(f):Clone()
	if newf then
		oldf:Destroy()
		newf.Parent = pbb
	end
end

function animate(key)
	olastkey = lastkey
	while moving and uis:IsKeyDown(key) do
		if moving and uis:IsKeyDown(key) and olastkey == lastkey then
			animstep = animstep+1
			if animstep == 1 then
				setframe(side.."Walk1")
			elseif animstep == 2 then
				setframe(side.."Idle")
			elseif animstep == 3 then
				setframe(side.."Walk2")
			end
			if animstep > 3 then
				animstep = 0
				setframe(side.."Idle")
			end
		else
			olastkey = lastkey
			return
		end
		wait(0.15)
	end
end

function move(key,off)
	local oldside = side
	local oldpos = ppart.Position
	while uis:IsKeyDown(key) do
		if uis:IsKeyDown(key) then
			overideside = false
			oldpos = ppart.Position
			oldside = side
			ppart.Position = ppart.Position + Vector3.new(off.x/8,off.y/8,off.z/8)
		else
			overideside = false
			break
		end
		rs.RenderStepped:Wait()
	end
end


function movement(key)
	moving = true
	if side == "Up" then
		lastkey = "up"
		move(key,Vector3.new(0,0,-speed))
	elseif side == "Down" then
		lastkey = "down"
		move(key,Vector3.new(0,0,speed))
	elseif side == "Left" then
		lastkey = "left"
		move(key,Vector3.new(-speed,0,0))
	elseif side == "Right" then
		lastkey = "right"
		move(key,Vector3.new(speed,0,0))
	end
end

function controls(key)
	if key == Enum.KeyCode.W then
		side = "Up"
	end
	if key == Enum.KeyCode.A then
		side = "Left"
	end
	if key == Enum.KeyCode.S then
		side = "Down"
	end
	if key == Enum.KeyCode.D then
		side = "Right"
	end
	movement(key)
end

uis.InputBegan:Connect(function(i,p)
	if p then return end
	local key = i.KeyCode
	if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then
		local ctrd = coroutine.create(controls)
		coroutine.resume(ctrd,key)
		moving = true
		animate(key)
	end
	if key == Enum.KeyCode.E then
		setframe("DownIdle")
	end
end)

uis.InputEnded:Connect(function(i,p)
	if p then return end
	local key = i.KeyCode
	if check_pressed() == "" then
		moving = false
		setframe(side.."Idle")
	end
end)

wait()

rs.RenderStepped:Connect(function()
	keys = uis:GetKeysPressed()
	debugui.Keys.W.BackgroundColor = BrickColor.new("Institutional white")
	debugui.Keys.A.BackgroundColor = BrickColor.new("Institutional white")
	debugui.Keys.S.BackgroundColor = BrickColor.new("Institutional white")
	debugui.Keys.D.BackgroundColor = BrickColor.new("Institutional white")
	for i,child in pairs(keys) do
		if child.KeyCode == Enum.KeyCode.W then
			debugui.Keys.W.BackgroundColor = BrickColor.Red()
		elseif child.KeyCode == Enum.KeyCode.A then
			debugui.Keys.A.BackgroundColor = BrickColor.Red()
		elseif child.KeyCode == Enum.KeyCode.S then
			debugui.Keys.S.BackgroundColor = BrickColor.Red()
		elseif child.KeyCode == Enum.KeyCode.D then
			debugui.Keys.D.BackgroundColor = BrickColor.Red()
		end
	end
	
	debugui.Side.Text = "Current Side: "..side
	debugui.Moving.Text = "Moving: "..tostring(moving)
	debugui.AnimStep.Text = "Steps: "..animstep
	c=0
	for i,child in pairs(cparts) do
		c = c+1
		if child.Name:match("Cutscene") and check_inside(ppart,child) then
			cammode = child.CamType.Value
		end
	end
	if cammode == 1 then
		cam.CFrame = CFrame.new(ppart.Position + Vector3.new(0,10,6.5)) * CFrame.Angles(math.rad(-60),0,0)
	end
	if cammode == 2 then
		cam.CFrame = CFrame.new(ppart.Position + Vector3.new(0,3,6.5)) * CFrame.Angles(math.rad(-10),0,0)
	end
end)

I don’t understand how lowering the player’s speed will fix the speed of the animation.

Try This:

wait()

local ts = game:GetService("TweenService")
local uis = game:GetService("UserInputService")
local rs = game:GetService("RunService")
local player = game:GetService("Players").LocalPlayer
local char = player.Character
local cam = workspace.CurrentCamera

while not char do
	char = player.Character
	wait()
end

local debugui = player.PlayerGui:WaitForChild("Debug")
local pchar = char:WaitForChild("Player")
local spritesheet = pchar:WaitForChild("SpriteSheet")
local pbb = pchar:WaitForChild("PlayerBillBoard")
local ppart = pchar.PrimaryPart
local hum = char:WaitForChild("Humanoid")
local hrp = char:WaitForChild("HumanoidRootPart")
hrp.Anchored = true

local keys = uis:GetKeysPressed()

local speed = 1
local lastkey = "down"
local olastkey = lastkey
local moving = false
local overideside = false
local side = "Down"
local animstep = 1
local frame = 1
local cammode = 1

local frames = spritesheet:GetChildren()
local wparts = workspace:GetDescendants()
local cparts = {}
local c = 0

for i,child in ipairs(wparts) do
	c = c+1
	if child.Name:match("Cutscene") then
		table.insert(cparts,c,child)
	end
end
c = 0

ppart.Position = workspace.Spawn.Position + Vector3.new(0,1,0)

function check_inside(a,b)
	if a.Position.x - b.Position.x <= b.Size.X/2 and a.Position.x - b.Position.x >= b.Size.X/-2 and a.Position.z - b.Position.z <= b.Size.z/2 and a.Position.z - b.Position.z >= b.Size.z/-2 then
		return true
	end
end

function check_pressed()
	local yss = {{Enum.KeyCode.W,"Up"},{Enum.KeyCode.A,"Left"},{Enum.KeyCode.S,"Down"},{Enum.KeyCode.D,"Right"}}
	local direction = ""
	for i,keyinfo in pairs(yss) do
		if uis:IsKeyDown(keyinfo[1]) then
			direction = direction..keyinfo[2]
		end
	end
	return direction
end

function setframe(f)
	local oldf = pbb:FindFirstChildOfClass("Frame")
	local newf = spritesheet:FindFirstChild(f):Clone()
	if newf then
		oldf:Destroy()
		newf.Parent = pbb
	end
end

function animate(key)
	olastkey = lastkey
	while moving and uis:IsKeyDown(key) do
		if moving and uis:IsKeyDown(key) and olastkey == lastkey then
			animstep = animstep+1
			if animstep == 1 then
				setframe(side.."Walk1")
			elseif animstep == 2 then
				setframe(side.."Idle")
			elseif animstep == 3 then
				setframe(side.."Walk2")
			end
			if animstep > 3 then
				animstep = 0
				setframe(side.."Idle")
			end
		else
			olastkey = lastkey
			return
		end
		wait(0.25)
	end
end

function move(key,off)
	local oldside = side
	local oldpos = ppart.Position
	while uis:IsKeyDown(key) do
		if uis:IsKeyDown(key) then
			overideside = false
			oldpos = ppart.Position
			oldside = side
			ppart.Position = ppart.Position + Vector3.new(off.x/8,off.y/8,off.z/8)
		else
			overideside = false
			break
		end
		rs.RenderStepped:Wait()
	end
end


function movement(key)
	moving = true
	if side == "Up" then
		lastkey = "up"
		move(key,Vector3.new(0,0,-speed))
	elseif side == "Down" then
		lastkey = "down"
		move(key,Vector3.new(0,0,speed))
	elseif side == "Left" then
		lastkey = "left"
		move(key,Vector3.new(-speed,0,0))
	elseif side == "Right" then
		lastkey = "right"
		move(key,Vector3.new(speed,0,0))
	end
end

function controls(key)
	if key == Enum.KeyCode.W then
		side = "Up"
	end
	if key == Enum.KeyCode.A then
		side = "Left"
	end
	if key == Enum.KeyCode.S then
		side = "Down"
	end
	if key == Enum.KeyCode.D then
		side = "Right"
	end
	movement(key)
end

uis.InputBegan:Connect(function(i,p)
	if p then return end
	local key = i.KeyCode
	if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then
		local ctrd = coroutine.create(controls)
		coroutine.resume(ctrd,key)
		moving = true
		animate(key)
	end
	if key == Enum.KeyCode.E then
		setframe("DownIdle")
	end
end)

uis.InputEnded:Connect(function(i,p)
	if p then return end
	local key = i.KeyCode
	if check_pressed() == "" then
		moving = false
		setframe(side.."Idle")
	end
end)

wait()

rs.RenderStepped:Connect(function()
	keys = uis:GetKeysPressed()
	debugui.Keys.W.BackgroundColor = BrickColor.new("Institutional white")
	debugui.Keys.A.BackgroundColor = BrickColor.new("Institutional white")
	debugui.Keys.S.BackgroundColor = BrickColor.new("Institutional white")
	debugui.Keys.D.BackgroundColor = BrickColor.new("Institutional white")
	for i,child in pairs(keys) do
		if child.KeyCode == Enum.KeyCode.W then
			debugui.Keys.W.BackgroundColor = BrickColor.Red()
		elseif child.KeyCode == Enum.KeyCode.A then
			debugui.Keys.A.BackgroundColor = BrickColor.Red()
		elseif child.KeyCode == Enum.KeyCode.S then
			debugui.Keys.S.BackgroundColor = BrickColor.Red()
		elseif child.KeyCode == Enum.KeyCode.D then
			debugui.Keys.D.BackgroundColor = BrickColor.Red()
		end
	end
	
	debugui.Side.Text = "Current Side: "..side
	debugui.Moving.Text = "Moving: "..tostring(moving)
	debugui.AnimStep.Text = "Steps: "..animstep
	c=0
	for i,child in pairs(cparts) do
		c = c+1
		if child.Name:match("Cutscene") and check_inside(ppart,child) then
			cammode = child.CamType.Value
		end
	end
	if cammode == 1 then
		cam.CFrame = CFrame.new(ppart.Position + Vector3.new(0,10,6.5)) * CFrame.Angles(math.rad(-60),0,0)
	end
	if cammode == 2 then
		cam.CFrame = CFrame.new(ppart.Position + Vector3.new(0,3,6.5)) * CFrame.Angles(math.rad(-10),0,0)
	end
end)
1 Like

Thank you it works much better now.

on line 97 u can change the wait time

You just needed to add a debounce to prevent both connected functions from being executed too frequently.