Sorry if this post has a long description, this is an unconventional game so I want to make sure I describe everything as best I can. A concise description of the problem is at the end.
I am trying to make a client-sided singleplayer 2D fighting game as a fun little project on roblox. Up to this point it has been smooth and fun to make, but I am facing a problem with getting animations to work.
(AttInpt means attack input. The game is planned to be a direct rip off of street fighter, but have simple directional inputs like smash bros where space is for normals and Z is for special attacks. As of right now I am trying to get a proof of concept done before all the fancy polish stuff and only space works with neutral attacks.)
Above is the diagram I made for how I think the animation system should work. There are two kinds of animations: looped (orange) and non-looped / finishing (green). I have gotten the looped animations to work, but my non-looped animations (specifically the attack animations) have strange problems.
Here is a part of my script. This has a couple variables, the PlayAnim() function, and my Update() function which runs every frame. I apologize in advance if the code isn’t the most clean.
local speed = 16 -- Speed in studs per sec
local myHeight = 3 -- How far the center of the character is above the ground
local gravity = GameFolder.Gravity.Value
local grounded = true
local xVelocity = 0
local yVelocity = 0
local curState = nil
local curAnim = nil
local curDir = nil
local animRunning = false -- Keeps track of when a time-based animation is running (i.e. attacks, jump start animations, anything that isn't looped)
-- Stops all animations and plays the input animation. If the animation is looped, return an event that fires when the animation is complete.
local function PlayAnim(animName, fadeTime, is_looped)
local finalName = animName .. ":" .. curDir
if finalName == curAnim then return end
curAnim = finalName
StopAnimations(fadeTime)
loadedAnims[finalName]:Play(fadeTime)
if is_looped ~= true then
coroutine.resume(coroutine.create(function()
loadedAnims[finalName].Stopped:Wait()
AnimEnded:Fire()
end))
return AnimEnded.Event
end
end
-- Runs every frame
local function Update(deltaTime)
local nextState = state.Idle -- The anticipated state after all inputs have been considered (Defaulted to Idle)
-- Update my direction
if grounded and Enemy then
if hrp.Position.X >= Enemy.PrimaryPart.Position.X and curDir ~= direction.R then
curDir = direction.R
elseif hrp.Position.X < Enemy.PrimaryPart.Position.X and curDir ~= direction.L then
curDir = direction.L
end
end
-- Ground Inputs
if grounded and animRunning == false then
xVelocity = 0 -- Frictions stops ground motion, this is realism at its finest
if UserInputService:IsKeyDown(Enum.KeyCode.Right) then
xVelocity += speed
end
if UserInputService:IsKeyDown(Enum.KeyCode.Left) then
xVelocity -= speed
end
if xVelocity ~= 0 then
nextState = state.Walking
end
if UserInputService:IsKeyDown(Enum.KeyCode.Space) then
nextState = state.Attack
end
if nextState ~= state.Attack then
if UserInputService:IsKeyDown(Enum.KeyCode.Up) then
grounded = false
yVelocity = 0.5
nextState = state.Jumping
end
end
end
-- Update position based on velocity
hrp.CFrame = CFrame.new(hrp.Position.X - (xVelocity * deltaTime), hrp.Position.Y, hrp.Position.Z)
if not grounded then
yVelocity -= gravity * deltaTime
if -yVelocity > GetDistanceFromGround() then
grounded = true
script.Land:Play()
hrp.CFrame = CFrame.new(hrp.Position.X, myHeight + GameFolder.GroundLevel.Value, hrp.Position.Z)
else
hrp.CFrame = CFrame.new(hrp.Position.X, hrp.Position.Y + yVelocity, hrp.Position.Z)
end
end
curState = nextState
-- Update animations
if curState == state.Idle and animRunning == false then
if grounded then
PlayAnim("Idle", _, true)
else
PlayAnim("AerialIdle", 0, true)
end
elseif curState == state.Walking and animRunning == false then
if (curDir == direction.R and xVelocity < 0) or (curDir == direction.L and xVelocity > 0) then
PlayAnim("Retreat", _, true)
else
PlayAnim("Advance", _, true)
end
elseif curState == state.Jumping and animRunning == false then
print("Jump request received)")
local jumpAnim = nil
if (curDir == direction.R and xVelocity < 0) or (curDir == direction.L and xVelocity > 0) then
jumpAnim = "BackJump"
else
jumpAnim = "ForwardJump"
end
animRunning = true
local finish = PlayAnim(jumpAnim, 0)
finish:wait()
print(curState)
animRunning = false
elseif curState == state.Attack and animRunning == false then
animRunning = true
local finish = PlayAnim("NPunch", 0)
finish:wait()
print(curState)
animRunning = false
end
end
RunService.RenderStepped:Connect(Update)
What am I trying to achieve: I am trying to make it so when you press the space bar, the attack animation is triggered and the character is locked into this action. He cannot move or jump until after his attack has finished.
The problem:
In the video, right before the script throws an error I hold the spacebar down.
If you hold down the spacebar so an attack is triggered immediately after the last one ends, the event in the PlayAnim() function returns as nil and the script throws an error and stops running.
If I didn’t explain this error properly enough or if I need to send the rest of the code (or even the place file), please comment, but I have been stuck on this problem for awhile and I hope someone is willing to help, thanks in advance!