2D player sprite animation freaks out when switching keys

So, I have made a script that allows a frame to move with WASD and when it moves, an image will cycle through various images to perceive movement. But when I go back and forth and WASD, the animation goes all weird and moves funny. Additionally, when the player stops moving, the animation still continues.

I used a function, an If statement, and a while loop to make the sprite work (Shown below)

local Loop = false

function Animate()
	if Loop == true then
		while Loop == true do
			wait(0.001)
			plr.Image = f1
			wait(0.001)
			plr.Image = f2
			wait(0.001)
			plr.Image = f3
			wait(0.001)
			plr.Image = f4
			wait(0.001)
			plr.Image = f5
			wait(0.001)
			plr.Image = f6
			wait(0.001)
			plr.Image = f7
			wait(0.001)
			plr.Image = f8
			wait(0.001)
			plr.Image = f9
			wait(0.001)
			plr.Image = f10
			wait(0.001)
			plr.Image = f11
			wait(0.001)
			plr.Image = f12
			wait(0.001)
			plr.Image = f13
			wait(0.001)
			plr.Image = f14
			wait(0.001)
			plr.Image = f15
			wait(0.001)
			plr.Image = f16
			wait(0.001)
			plr.Image = f17
			wait(0.001)
			plr.Image = f18
			wait(0.001)
			plr.Image = f19
			wait(0.001)
			plr.Image = f20
			wait(0.001)
			plr.Image = f21
			wait(0.001)
			plr.Image = f22
		end	
	end
	if Loop == false and plr.Image == f22 or plr.Image == f21 or plr.Image == f20 or plr.Image == f19 or plr.Image == f18 or plr.Image == f17 or plr.Image == f16 or plr.Image == f15 or plr.Image == f14 or plr.Image == f13 or plr.Image == f12 or plr.Image == f11 or plr.Image == f10 or plr.Image == f9 or plr.Image == f8 or plr.Image == f7 or plr.Image == f6 or plr.Image == f5 or plr.Image == f4 or plr.Image == f3 or plr.Image == f2 or plr.Image == f1 then
		plr.Image = Idle
	end
end

(How the function gets called )
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

--When WASD is pressed
	if keys[key] then
		pressed[key] = keys[key]
		Loop = true
		Animate()
	end 

--When WASD is not pressed
	if keys[key] then
		pressed[key] = nil
		Loop = false
		Animate()
	end 

Here’s footage of what I am referring to.
robloxapp-20210710-0247487.wmv (1.3 MB)

Well, first of all. The minimun the wait() function can wait is .03 seconds, your code is a little bit messy, and I think you could have made it a lot more effecient. In the infinite loop, there is nothing to check if it should break out of it, so that’s why it continues.

I would suggest reading about loops.

The problem is that the while loop is still going when pressing another key.
You make a new thread which will also be changing the Image because it doesn’t have any command to stop in between all those waits.

You can put the frames in a table like:

local Frames = {f1,f2}

then have a loop which can break if Loop has been set to false.

function Animate()
	for i,v in ipairs(Frames) do -- i is the position of the frame in the table and v is the frame itself
		if Loop == false then break end-- Stops the loop
		plr.Image = v
		wait(0.02)-- Probably 5fps animation
	end
end

This is meant to be more of an example so I don’t know if it will actually work or be efficient.

Some API references:
https://developer.roblox.com/en-us/articles/Loops
https://developer.roblox.com/en-us/articles/Table

1 Like

I read some posts and it doesn’t seem to say anywhere that the lowest time is 0.03. Only thing I found is that if you don’t provide an argument by just doing wait() it will wait for 0.03 by default.

I’m guessing it’s cause you’re never breaking the loop, so it continues even after it stops animating/being called.

You can use break for that, but I also have something else. Instead of manually animating the player image, you can also use for i = 1, 22 since you have 22 images.

Here’s how to break the loop. (Check the last line before the function ends)

function Animate()
	if Loop == true then
		while Loop == true do
			wait(0.001)
			plr.Image = f1
			wait(0.001)
			plr.Image = f2
			wait(0.001)
			plr.Image = f3
			wait(0.001)
			plr.Image = f4
			wait(0.001)
			plr.Image = f5
			wait(0.001)
			plr.Image = f6
			wait(0.001)
			plr.Image = f7
			wait(0.001)
			plr.Image = f8
			wait(0.001)
			plr.Image = f9
			wait(0.001)
			plr.Image = f10
			wait(0.001)
			plr.Image = f11
			wait(0.001)
			plr.Image = f12
			wait(0.001)
			plr.Image = f13
			wait(0.001)
			plr.Image = f14
			wait(0.001)
			plr.Image = f15
			wait(0.001)
			plr.Image = f16
			wait(0.001)
			plr.Image = f17
			wait(0.001)
			plr.Image = f18
			wait(0.001)
			plr.Image = f19
			wait(0.001)
			plr.Image = f20
			wait(0.001)
			plr.Image = f21
			wait(0.001)
			plr.Image = f22
		end	
	end
	if Loop == false and plr.Image == f22 or plr.Image == f21 or plr.Image == f20 or plr.Image == f19 or plr.Image == f18 or plr.Image == f17 or plr.Image == f16 or plr.Image == f15 or plr.Image == f14 or plr.Image == f13 or plr.Image == f12 or plr.Image == f11 or plr.Image == f10 or plr.Image == f9 or plr.Image == f8 or plr.Image == f7 or plr.Image == f6 or plr.Image == f5 or plr.Image == f4 or plr.Image == f3 or plr.Image == f2 or plr.Image == f1 then
		plr.Image = Idle
        break
	end
end

The frames idea is very good. The way I would do it is have a global index variable like so:

local frameIdx = 1
local Frames = {f1, f2}
function Animate()
	plr.Image = Frames[frameIdx % #Frames]
    frameIdx += 1
end

Then call the function By binding to RenderStepped

--When WASD is pressed
	if keys[key] then
		pressed[key] = keys[key]
		RunService:BindToRenderStep("animateSprite", 1, Animate)
	end 

--When WASD is not pressed
	if keys[key] then
		pressed[key] = nil
		RunService:UnbindFromRenderStep("animateSprite")
        frameIdx = 1
	end

Here the animation will run at the framerate that the client has. Ideally you would want to use the step to set you framerate appropriately. More here RunService:BindToRenderStep

Just by reading posts, you can’t prove it. The wait has a minimun of 29 milliseconds to wait (0.03), yes it’s right the default would be 0.03, but that’s also the minimum, why wouldn’t it be the minimun wait by default also? Makes no sense. Roblox Globals Scroll down until you find the wait() function :smiley:

It’s saying that plr.Image = Frames[frameIdx % #Frames] is an invalid argument.
image

You probably don’t want the animation speed to depend on the frame rate. This code should set the correct image regardless of framerate.

local RunService = game:GetService("RunService")

local ANIM_DURATION = .5 -- Change to whatever you want.

local frames = {f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, f22}

local frameNum = #frames
local frameDuration = ANIM_DURATION / frameNum

local moveAnimLoopRunning = false

local function animateMovement()
    moveAnimLoopRunning = true
    local animStartTime = os.clock()
    while moveAnimLoopRunning do
        plr.Image = frames[1 + math.floor((os.clock() - animStartTime) % ANIM_DURATION / frameDuration)]
        RunService.RenderStepped:Wait()
    end
end

local function stopMovement()
    moveAnimLoopRunning = false
end

local function animateIdle()
    plr.Image = Idle
end
--When WASD is pressed
if keys[key] then
	pressed[key] = keys[key]
	if not moveAnimLoopRunning then
		animateMovement()
	end
end 

--When WASD is not pressed
if keys[key] then
	pressed[key] = nil
	stopMovement()
	animateIdle()
end

Hmmm, that error doesn’t sound quite right. Maybe you can DM me a bigger chunk of the code so I can check it? Invalid Argument #3 implies there is a function called on that line, but there is none.

One idea that I have is to change

function Animate()

to

function Animate(step)

Even you don’t use the step. That argument is passed and it might be breaking something there.

Setting ImageLabel.Image to nil gives that error. There’s a problem in the way you calculate the index. If frameIdx is equal to #Frames, frameIdx % #Frames is 0. The code below might work better.

plr.Image = Frames[(frameIndex - 1) % #Frames + 1]
1 Like