Not often that I will post something, but here's a little something based from a chapter from the book 'Game Programming Patterns (https://gameprogrammingpatterns.com/)'. It's very primitive, and there are no comments, as the code is quite large.
Demo video
(https://www.dropbox.com/scl/fi/9y6ra4650d2r3r0k2q180/example_001.webp?rlkey=gd5p2so7yavbnneh8t794ry87&st=nm4jc9lr&raw=1)
There's also a Cerberus-X (https://www.syntaxboom.com/forum/index.php?topic=131.0) version.
You will need to download the warpsara-nohelmet-anim-sheet-alpha_1.png (https://opengameart.org/sites/default/files/warpsara-nohelmet-anim-sheet-alpha_1.png) file from https://opengameart.org/content/space-sara (https://opengameart.org/content/space-sara) and remain it heroine.png
Enum EACTIONS
NONE = 0,
MOVE_LEFT = 1,
MOVE_RIGHT,
JUMP,
JUMP_LEFT,
JUMP_RIGHT,
DUCK
End Enum
Const FLAG_FACE:UInt = $01
Const FLAG_RUNNING:UInt = $02
Type TFrame
Field _srcX:Int
Field _srcY:Int
Field _srcW:Int
Field _srcH:Int
End Type
Type TAnimation
Field _frames:TFrame[]
Field _totalFrames:Int
Field _frameRate:Float
Method GetFrameRate:Float()
Return _frameRate
End Method
Method GetFrameData:TFrame(currentFrame:Int)
Return _frames[currentFrame]
End Method
Method AddFrames(totalFrames:Int, frameRate:Float, column:Int, row:Int, frameWidth:Int = 48, frameHeight:Int = 48)
Local cfl:Int = 0
If _frames = Null
_frames = New TFrame[totalFrames]
Else
cfl = _frames.Length
_frames = _frames[..(_frames.length + totalFrames)]
EndIf
_frameRate = 1000 / frameRate
_totalFrames = _frames.length
Local i:Int = cfl, p:Int = column * frameWidth
While i < totalFrames
_frames[i] = New TFrame
_frames[i]._srcX = p
_frames[i]._srcY = row * frameHeight
_frames[i]._srcW = frameWidth
_frames[i]._srcH = frameHeight
i :+ 1
p:+frameWidth
Wend
End Method
End Type
Type TControllerInput
Method Update()
Self._inputState = PRESS_NONE
If KeyDown(KEY_S) _inputState :| PRESS_DUCK
If KeyDown(KEY_W) _inputState :| PRESS_JUMP
If KeyDown(KEY_A) _inputState :| PRESS_LEFT
If KeyDown(KEY_D) _inputState :| PRESS_RIGHT
If KeyDown(KEY_SPACE) _inputState :| PRESS_FIRE
End Method
Method GetAction:EACTIONS()
If _inputState & PRESS_JUMP
If _inputState & PRESS_LEFT
Return EACTIONS.JUMP_LEFT
Elseif _inputState & PRESS_RIGHT
Return EACTIONS.JUMP_RIGHT
Else
Return EACTIONS.JUMP
Endif
ElseIf _inputState & PRESS_DUCK
Return EACTIONS.DUCK
ElseIf _inputState & PRESS_LEFT
Return EACTIONS.MOVE_LEFT
ElseIf _inputState & PRESS_RIGHT
Return EACTIONS.MOVE_RIGHT
EndIf
Return EACTIONS.NONE
End Method
Const PRESS_NONE:Int = $00
Const PRESS_LEFT:Int = $01
Const PRESS_RIGHT:Int = $02
Const PRESS_JUMP:Int = $04
Const PRESS_DUCK:Int = $08
Const PRESS_FIRE:Int = $10
Field _inputState:Byte
End Type
Type THeroine
Method New()
_atlas = LoadImage("heroine.png")
_states = THeroineState.CreateStates()
SetState(_states._idle1)
_xSpeed = 0
_ySpeed = 0
_flags:|FLAG_FACE
End Method
Method HandleInput(action:EACTIONS)
_state.HandleInput(Self, action)
End Method
Method Update()
_state.Update(Self)
End Method
Method Render()
Local frame:TFrame = _state.GetCurrentFrameData(Self)
If _flags & FLAG_FACE
DrawSubImageRect(_atlas, _xPos, _yPos, frame._srcW, frame._srcH, frame._srcX, frame._srcY, frame._srcW, frame._srcH, frame._srcW / 2, frame._srcH / 2, 0)
ElseIf _flags & FLAG_FACE = 0
DrawSubImageRect(_atlas, _xPos, _yPos, -frame._srcW, frame._srcH, frame._srcX, frame._srcY, frame._srcW, frame._srcH, frame._srcW / 2, frame._srcH / 2, 0)
EndIf
End Method
Method SetState(state:THeroineState)
_state = state
_totalFrameCount = state.GetTotalFrameCount()
_currentFrame = 0
End Method
Method TotalFrameCount:Int()
Return _totalFrameCount
End Method
Method FrameCountEnd:Int()
Return _totalFrameCount - 1
End Method
Method GetStateName:String()
Return _state._strID
End Method
Method GetLastFrameTime:Float()
Return _lastFrameTime
End Method
Method SetLastFrameTime(value:Float)
_lastFrameTime = value
End Method
Method FaceLeft()
_flags:&~FLAG_FACE
_currentFrame = 0
_xSpeed = -8.0
End Method
Method FaceRight()
_flags:|FLAG_FACE
_currentFrame = 0
_xSpeed = 8.0
End Method
Method EnableRunning()
_flags:|FLAG_RUNNING
End Method
Method DisableRunning()
_flags:&~FLAG_RUNNING
End Method
Global _xPos:Float
Global _yPos:Float
Global _xSpeed:Float
Global _ySpeed:Float
Global _flags:UInt
Global _currentFrame:Int
Field _atlas:TImage
Field _states:THeroineState
Field _state:THeroineState
Field _totalFrameCount:Int
Field _lastFrameTime:Float
End Type
Type THeroineState
Field _strID:String
Field _animationSequence:TAnimation
Field _idle1:THeroineIdleState
Field _startRun:THeroineStartRunState
Field _startRunJump:THeroineStartRunJumpState
Field _runLoop:THeroineRunLoopState
Field _stopRun:THeroineStopRunState
Field _crouch:THeroineCrouchState
Field _stand:THeroineStandUpState
Field _startJump:THeroineStandJumpState
Field _jumpLoop:THeroineJumpLoopState
Field _stopJump:THeroineStopJumpState
Method HandleInput(heroine:THeroine, action:EACTIONS)
End Method
Method Update(heroine:THeroine)
Local tick:Float = MilliSecs()
Local elapsed_time:Float = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
If heroine._currentFrame < heroine.FrameCountEnd()
heroine._currentFrame = heroine._currentFrame + 1
Else
heroine._currentFrame = 0
EndIf
Motion(heroine)
Endif
End Method
Method Motion(heroine:THeroine)
If heroine._xPos < 24 heroine._xPos = 24
If heroine._xPos > GraphicsWidth() - 25 heroine._xPos = GraphicsWidth() - 25
End Method
Method GetCurrentFrameData:TFrame(heroine:THeroine)
Return heroine._state._animationSequence.GetFrameData(heroine._currentFrame)
End Method
Method GetTotalFrameCount:Int()
Return _animationSequence._totalFrames
End Method
Method AddFrames(totalFrames:Int, frameRate:Float, column:Int, row:Int, frameWidth:Int = 48, frameHeight:Int = 48)
If _animationSequence = Null _animationSequence = New TAnimation()
_animationSequence.AddFrames(totalFrames, frameRate, column, row, frameWidth, frameHeight)
End Method
Function CreateStates:THeroineState()
Local state:THeroineState = New THeroineState()
state._idle1 = New THeroineIdleState()
state._crouch = New THeroineCrouchState()
state._stand = New THeroineStandUpState()
state._startRun = New THeroineStartRunState()
state._runLoop = New THeroineRunLoopState()
state._stopRun = New THeroineStopRunState()
state._startJump = New THeroineStandJumpState()
state._jumpLoop = New THeroineJumpLoopState()
state._stopJump = New THeroineStopJumpState()
state._startRunJump = New THeroineStartRunJumpState()
Return state
End Function
End Type
Type THeroineCrouchState Extends THeroineState
Method New()
_strID = "CROUCH DOWN STATE"
AddFrames(5, 16.5, 0, 13)
End Method
Method HandleInput(heroine:THeroine, action:EACTIONS) Override
If Not (action = EACTIONS.DUCK)
If heroine._state = heroine._states._crouch heroine.SetState(heroine._states._stand)
EndIf
End Method
Method Update(heroine:THeroine) Override
Local tick:Int = MilliSecs()
Local elapsed_time:Int = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
If heroine._currentFrame < heroine.FrameCountEnd() heroine._currentFrame = heroine._currentFrame + 1
Endif
End Method
End Type
Type THeroineStandUpState Extends THeroineState
Method New()
_strID = "STANB UP STATE"
AddFrames(4, 16.5, 0, 14)
End Method
Method HandleInput(heroine:THeroine, input:EACTIONS) Override
End Method
Method Update(heroine:THeroine) Override
Local tick:Int = MilliSecs()
Local elapsed_time:Int = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
If heroine._currentFrame < heroine.FrameCountEnd()
heroine._currentFrame = heroine._currentFrame + 1
Else
heroine.SetState(heroine._states._idle1)
EndIf
EndIf
End Method
End Type
Type THeroineIdleState Extends THeroineState
Method New()
_strID = "IDLE STATE ONE"
AddFrames(4, 4.5, 0, 1)
End Method
Method HandleInput(heroine:THeroine, action:EACTIONS) Override
Select action
Case EACTIONS.DUCK
heroine.SetState(heroine._states._crouch)
Case EACTIONS.MOVE_LEFT
heroine.SetState(heroine._states._startRun)
heroine.FaceLeft()
Case EACTIONS.MOVE_RIGHT
heroine.SetState(heroine._states._startRun)
heroine.FaceRight()
Case EACTIONS.JUMP_LEFT
heroine.SetState(heroine._states._startRunJump)
heroine.FaceLeft()
heroine.EnableRunning()
Case EACTIONS.JUMP_RIGHT
heroine.SetState(heroine._states._startRunJump)
heroine.FaceRight()
heroine.EnableRunning()
Case EACTIONS.JUMP
heroine.SetState(heroine._states._startJump)
End Select
End Method
Method Update(heroine:THeroine) Override
Super.Update(heroine)
End Method
End Type
Type THeroineJumpLoopState Extends THeroineState
Method New()
_strID = "JUMP LOOP STATE"
AddFrames(4, 12.5, 0, 8)
End Method
Method HandleInput(heroine:THeroine, action:EACTIONS) Override
End Method
Method Update(heroine:THeroine) Override
Local tick:Int = MilliSecs()
Local elapsed_time:Int = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
Motion(heroine)
If heroine._currentFrame < heroine.FrameCountEnd()
heroine._currentFrame = heroine._currentFrame + 1
DebugLog "FRAME INCREMENT: " + heroine._currentFrame
If heroine._currentFrame > 2
heroine._ySpeed = 8.0
DebugLog "FRAME YSPEED TRIGGERED: "+heroine._ySpeed
EndIf
Else
heroine.SetState(heroine._states._stopJump)
DebugLog "SET FRAME STATE TO STOP JUMP"
EndIf
EndIf
End Method
Method Motion(heroine:THeroine) Override
heroine._yPos:+heroine._ySpeed
If heroine._flags & FLAG_RUNNING
heroine._xPos:+heroine._xSpeed
Super.Motion(heroine)
Endif
End Method
End Type
Type THeroineStartRunJumpState Extends THeroineState
Method New()
_strID = "START RUN JUMP STATE"
AddFrames(4, 12.5, 0, 6)
End Method
Method HandleInput(heroine:THeroine, action:EACTIONS) Override
End Method
Method Update(heroine:THeroine) Override
Local tick:Float = MilliSecs()
Local elapsed_time:Float = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
If heroine._currentFrame < heroine.FrameCountEnd()
heroine._currentFrame = heroine._currentFrame + 1
If heroine._currentFrame > 0 heroine._ySpeed = -8.0
Else
heroine.SetState(heroine._states._jumpLoop)
heroine._ySpeed = 0.0
EndIf
Motion(heroine)
EndIf
End Method
Method Motion(heroine:THeroine) Override
heroine._yPos:+heroine._ySpeed
If heroine._flags & FLAG_RUNNING
heroine._xPos:+heroine._xSpeed
Super.Motion(heroine)
Endif
End Method
End Type
Type THeroineStopJumpState Extends THeroineState
Method New()
_strID = "STOP JUMP STATE"
AddFrames(2, 12.5, 0, 9)
End Method
Method HandleInput(heroine:THeroine, action:EACTIONS) Override
End Method
Method Update(heroine:THeroine) Override
Local tick:Float = MilliSecs()
Local elapsed_time:Float = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
Motion(heroine)
If heroine._currentFrame < heroine.FrameCountEnd()
heroine._currentFrame = heroine._currentFrame + 1
Else
heroine.SetState(heroine._states._idle1)
heroine._ySpeed = 0.0
heroine.DisableRunning()
EndIf
EndIf
End Method
Method Motion(heroine:THeroine) Override
heroine._yPos:+heroine._ySpeed
If heroine._flags & FLAG_RUNNING
heroine._xPos:+heroine._xSpeed
Super.Motion(heroine)
Endif
End Method
End Type
Type THeroineStandJumpState Extends THeroineState
Method New()
_strID = "START STAND JUMP STATE"
AddFrames(6, 12.5, 0, 7)
End Method
Method HandleInput(heroine:THeroine, action:EACTIONS) Override
End Method
Method Update(heroine:THeroine) Override
Local tick:Float = MilliSecs()
Local elapsed_time:Float = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
If heroine._currentFrame < heroine.FrameCountEnd()
heroine._currentFrame = heroine._currentFrame + 1
If heroine._currentFrame > 2 heroine._ySpeed = -8.0
Else
heroine.SetState(heroine._states._jumpLoop)
heroine._ySpeed = 0.0
EndIf
Motion(heroine)
EndIf
End Method
Method Motion(heroine:THeroine) Override
heroine._yPos:+heroine._ySpeed
End Method
End Type
Type THeroineStartRunState Extends THeroineState
Method New()
_strID = "START RUN STATE"
AddFrames(1, 12.5, 0, 3)
End Method
Method HandleInput(heroine:THeroine, action:EACTIONS) Override
Select action
Case EACTIONS.MOVE_LEFT
If (heroine._flags & FLAG_FACE)
heroine.FaceLeft()
Else
heroine.EnableRunning()
EndIf
Case EACTIONS.MOVE_RIGHT
If (heroine._flags & FLAG_FACE) = 0
heroine.FaceRight()
Else
heroine.EnableRunning()
EndIf
Case EACTIONS.JUMP_LEFT
heroine.SetState(heroine._states._startRunJump)
heroine.EnableRunning()
Case EACTIONS.JUMP_RIGHT
heroine.SetState(heroine._states._startRunJump)
heroine.EnableRunning()
Default
heroine.SetState(heroine._states._stopRun)
heroine.DisableRunning()
End Select
End Method
Method Update(heroine:THeroine) Override
Local tick:Int = MilliSecs()
Local elapsed_time:Int = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
If heroine._currentFrame > heroine.FrameCountEnd()
heroine._currentFrame = heroine._currentFrame + 1
Else
heroine.SetState(heroine._states._runLoop)
EndIf
Motion(heroine)
Endif
End Method
Method Motion(heroine:THeroine) Override
If heroine._xPos > 23 And heroine._xPos < GraphicsWidth() - 24 heroine._xPos:+heroine._xSpeed
Super.Motion(heroine)
End Method
End Type
Type THeroineRunLoopState Extends THeroineState
Method New()
_strID = "RUN STATE"
AddFrames(10, 12.5, 0, 4)
End Method
Method HandleInput(heroine:THeroine, action:EACTIONS) Override
Select action
Case EACTIONS.MOVE_LEFT
If (heroine._flags & FLAG_FACE)
heroine.FaceLeft()
Else
heroine._flags:|FLAG_RUNNING
EndIf
Case EACTIONS.MOVE_RIGHT
If (heroine._flags & FLAG_FACE) = 0
heroine.FaceRight()
Else
heroine._flags:|FLAG_RUNNING
EndIf
Case EACTIONS.JUMP_LEFT
heroine.SetState(heroine._states._startRunJump)
heroine.FaceLeft()
Case EACTIONS.JUMP_RIGHT
heroine.SetState(heroine._states._startRunJump)
heroine.FaceRight()
Default
heroine.SetState(heroine._states._stopRun)
heroine.DisableRunning()
End Select
End Method
Method Update(heroine:THeroine) Override
If heroine._flags & FLAG_RUNNING
Local tick:Int = MilliSecs()
Local elapsed_time:Int = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
If heroine._currentFrame < heroine.FrameCountEnd()
heroine._currentFrame = heroine._currentFrame + 1
Else
heroine._currentFrame = 0
EndIf
Motion(heroine)
EndIf
EndIf
End Method
Method Motion(heroine:THeroine) Override
If heroine._xPos > 23 And heroine._xPos < GraphicsWidth() - 24 heroine._xPos:+heroine._xSpeed
Super.Motion(heroine)
End Method
End Type
Type THeroineStopRunState Extends THeroineState
Method New()
_strID = "STOP RUN STATE"
AddFrames(3, 12.5, 0, 5)
End Method
Method HandleInput(heroine:THeroine, action:EACTIONS) Override
End Method
Method Update(heroine:THeroine) Override
Local tick:Int = MilliSecs()
Local elapsed_time:Int = tick - heroine.GetLastFrameTime()
If elapsed_time >= _animationSequence.GetFrameRate()
heroine.SetLastFrameTime(tick)
If heroine._currentFrame > heroine.FrameCountEnd()
heroine._currentFrame = heroine._currentFrame + 1
Else
heroine.SetState(heroine._states._idle1)
heroine.DisableRunning()
EndIf
Motion(heroine)
Endif
End Method
Method Motion(heroine:THeroine) Override
If heroine._xPos > 23 And heroine._xPos < GraphicsWidth() - 24 heroine._xPos:+heroine._xSpeed
Super.Motion(heroine)
End Method
End Type
Type TGameLoop
Method New(width:Int = 800, height:Int = 600, depth:Int = 0, hertz:Int = 60, flags:Int = GRAPHICS_BACKBUFFER)
_graphics = Graphics(width, height, depth, hertz, flags, 400, 400)
player = New THeroine()
controls = New TControllerInput()
player._xPos = GraphicsWidth() / 2
player._yPos = GraphicsHeight() / 2
Run()
End Method
Method Run()
While Not KeyDown(KEY_ESCAPE)
Cls
DrawRect(0, (GraphicsHeight() / 2) + 24, GraphicsWidth(), 8)
DrawText("EXAMPLE 001: ANIMATION STATES", 0, 0)
DrawText("BASED ON STATES CHAPTER FROM: https://gameprogrammingpatterns.com/",0, 12)
DrawText("MEDIA FROM: https://opengameart.org/content/space-sara", 0, 25)
DrawText("HEROINE STATE: " + player.GetStateName(), 0, 38)
DrawText("XPOS: " + player._xPos + " - YPOS: " + player._yPos, 0, 51)
DrawText("KEYS: W,A,D,S AND ESCAPE TO QUIT", 0, 64)
controls.Update()
player.HandleInput(controls.GetAction())
player.Update()
player.Render()
Flip
Wend
End Method
Field _graphics:TGraphics
Field player:THeroine
Field controls:TControllerInput
End Type
New TGameLoop()
Okay. If you want to see a better example of a BlitzMax project.
Then you can download the sources from here (https://www.dropbox.com/scl/fi/cjxlynj4j7s963mom4k31/example_001.zip?rlkey=edn8kkbreshntfrowfl6u29wa&st=kdbx2ptv&dl=0).
You will still need to download the image file linked above.
It's a shame that BlitzMax doesn't support method properties like Cerberus-X. It would make it a lot easier to work with.