Main Menu

News:

SyntaxBoom, now with pwetty syntax highlighted code boxes! \o/ 

https://www.syntaxboom.com/forum/index.php?topic=96

Shoutbox

Jackdaw

Today at 20:00:40
Going to have to try bourbon and beans. That should be an explosive combination.

Baggey

Today at 13:15:42
I sometimes mix a chicken vindaloo and a Tika Masala together. Awesome  :P

Dabzy

Today at 05:49:34
And doing the act was the realisation I went for an indian when out... 20mins I was in the thunderbox waiting for me back chaff to cool down!

Dabzy

Today at 05:48:11
When I was on my "Year On The Blur", aka drinking after getting divorced, I was minging one night, couldnt remember getting home. Anyway, next day, went to work, and needed a poo...

GfK

2025-10-15, 21:39:05
I overdosed on jelly babies once and my arse was like the shooty gun bit at the start of James Bond

Amon

2025-10-15, 20:16:38
lol

Jackdaw

2025-10-15, 19:40:48
Never had a Phall or a tinderloo. But I have heard that your backside feels like that map at the start of every episode of Bonanza.

GfK

2025-10-15, 19:22:25
Never confuse phall with phallus

Baggey

2025-10-15, 18:30:58
My mate ate a phall one night. Even that was to hot for me. I bet he suffered in the morning!  :-[

Dabzy

2025-10-15, 18:02:52
I like nice and toasty bit of fire in my bait as well, I used to eat really red hot gear, but nowadays if I do... Heartburn happens! :(

Members
Stats
  • Total Posts: 1,811
  • Total Topics: 224
  • Online today: 26
  • Online ever: 232 (Oct 08, 2025, 09:18 AM)
Users Online
  • Users: 2
  • Guests: 6
  • Total: 8
  • ando
Welcome to SyntaxBoom. Please login or sign up.

Recent

Game Object Movement and Animation States

Started by Jackdaw, Jul 19, 2025, 03:31 PM

Previous topic - Next topic

Jackdaw

Not often that I will post something, but here's a little something based from a chapter from the book 'Game Programming Patterns'. It's very primitive, and there are no comments, as the code is quite large.

There's also a BlitzMax version.

Demo video


You will need to download the warpsara-nohelmet-anim-sheet-alpha_1.png file from https://opengameart.org/content/space-sara and remain it heroine.png, and place it in a directory called heroine.data the has to be created at the same level as the code below.

Save the code below as heroine.cxs.
Code  cerberus Select
Strict
Import mojo

Enumerate
	MOVE_NONE = 0,
	MOVE_LEFT = 1,
	MOVE_RIGHT,
	MOVE_JUMP,
	MOVE_JUMP_LEFT,
	MOVE_JUMP_RIGHT,
	MOVE_DUCK

Const FLAG_FACE:Int = $01
Const FLAG_RUNNING:Int = $02

Class CControllerInput
	Method Update:Void()
		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 GetAction:Int()
		If _inputState & PRESS_JUMP
			If _inputState & PRESS_LEFT
				Return MOVE_JUMP_LEFT
			Elseif _inputState & PRESS_RIGHT
				Return MOVE_JUMP_RIGHT
			Else
				Return MOVE_JUMP
			Endif
		ElseIf _inputState & PRESS_DUCK
			Return MOVE_DUCK
		ElseIf _inputState & PRESS_LEFT
			Return MOVE_LEFT
		ElseIf _inputState & PRESS_RIGHT
			Return MOVE_RIGHT
		EndIf
		Return MOVE_NONE	
	End
	
	Private
		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:Int
End

Class CFrame
	Field _srcX:Int
	Field _srcY:Int
	Field _srcW:Int
	Field _srcH:Int
End

Class CAnimation
	Field _frames:CFrame[]
	Field _totalFrames:Int
	Field _frameRate:Float
	
	Method GetFrameRate:Float()
		Return _frameRate
	End
	
	Method GetFrameData:CFrame(currentFrame:Int)
		Return _frames[currentFrame]
	End
	
	Method AddFrames:Void(totalFrames:Int, frameRate:Float, column:Int, row:Int, frameWidth:Int = 48, frameHeight:Int = 48)
		Local cfl:Int = 0
		If _frames.Length() = 0
			_frames = _frames.Resize(totalFrames)
		Else
			cfl = _frames.Length()
			_frames = _frames.Resize(_frames.Length() + totalFrames)
		EndIf
		_frameRate = 1000 / frameRate
		_totalFrames = _frames.Length()
		Local i:Int = cfl, p:Int = column * frameWidth
		While i < totalFrames
			_frames[i] = New CFrame
			_frames[i]._srcX = p
			_frames[i]._srcY = row * frameHeight
			_frames[i]._srcW = frameWidth
			_frames[i]._srcH = frameHeight
			i += 1
			p += frameWidth
		Wend
	End
End

Class CHeroineState
	Field _strID:String
	Field _animationSequence:CAnimation
	Field _idle1:CHeroineIdleState

	Field _startRun:CHeroineStartRunState
	Field _startRunJump:CHeroineStartRunJumpState
	Field _runLoop:CHeroineRunLoopState
	Field _stopRun:CHeroineStopRunState

	Field _crouch:CHeroineCrouchState
	Field _stand:CHeroineStandUpState
	
	Field _startJump:CHeroineStandJumpState
	Field _jumpLoop:CHeroineJumpLoopState
	Field _stopJump:CHeroineStopJumpState

	Method HandleInput:Void(heroine:CHeroine, action:Int)
	End Method

	Method Update:Void(heroine:CHeroine)
		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:Void(heroine:CHeroine)
		If heroine._xPos < 24 heroine._xPos = 24
		If heroine._xPos > DeviceWidth() - 25 heroine._xPos = DeviceWidth() - 25
	End Method

	Method GetCurrentFrameData:CFrame(heroine:CHeroine)
		Return heroine._state._animationSequence.GetFrameData(heroine._currentFrame)
	End Method
	
	Method GetTotalFrameCount:Int()
		Return _animationSequence._totalFrames
	End Method
	
	Method AddFrames:Void(totalFrames:Int, frameRate:Float, column:Int, row:Int, frameWidth:Int = 48, frameHeight:Int = 48)
		If _animationSequence = Null _animationSequence = New CAnimation()
		_animationSequence.AddFrames(totalFrames, frameRate, column, row, frameWidth, frameHeight)
	End Method

	Function CreateStates:CHeroineState()
		Local state:CHeroineState = New CHeroineState()
		state._idle1 = New CHeroineIdleState()
		state._crouch = New CHeroineCrouchState()
		state._stand = New CHeroineStandUpState()
		state._startRun = New CHeroineStartRunState()
		state._runLoop = New CHeroineRunLoopState()
		state._stopRun = New CHeroineStopRunState()
		state._startJump = New CHeroineStandJumpState()
		state._jumpLoop = New CHeroineJumpLoopState()
		state._stopJump = New CHeroineStopJumpState()
		state._startRunJump = New CHeroineStartRunJumpState()
		Return state
	End Function
End

Class CHeroine
	Method New()
#If CONFIG="debug"
			_atlas = LoadImage("heroine_debug.png")
#Else
			_atlas = LoadImage("heroine.png")
#Endif
		_states = CHeroineState.CreateStates()
		SetState(_states._idle1)
		_xSpeed = 0
		_ySpeed = 0
		_flags|=FLAG_FACE
	End
	
	Method HandleInput:Void(action:Int)
		_state.HandleInput(Self, action)
	End

	Method Update:Void()
		_state.Update(Self)
	End

	Method Render:Void()
		Local frame:CFrame = _state.GetCurrentFrameData(Self)
		PushMatrix()
		If _flags & FLAG_FACE
			Translate(-24,-24)
			DrawImageRect(_atlas, _xPos, _yPos, frame._srcX, frame._srcY, frame._srcW, frame._srcH)
		ElseIf _flags & FLAG_FACE = 0
			Translate(24,-24)
			DrawImageRect(_atlas, _xPos, _yPos, frame._srcX, frame._srcY, frame._srcW, frame._srcH, 0, -1,1)
		EndIf
		PopMatrix
	End
	
	Method SetState:Void(state:CHeroineState)
		_state = state
		_totalFrameCount = state.GetTotalFrameCount()
		_currentFrame = 0
	End
	
	Method TotalFrameCount:Int()
		Return _totalFrameCount
	End
	
	Method FrameCountEnd:Int()
		Return _totalFrameCount - 1
	End
	
	Method GetStateName:String()
		Return _state._strID
	End
	
	Method GetLastFrameTime:Float()
		Return _lastFrameTime
	End
	
	Method SetLastFrameTime:Void(value:Float)
		_lastFrameTime = value
	End
	
	Method FaceLeft:Void()
		_flags&=~FLAG_FACE
		_currentFrame = 0
		_xSpeed = -8.0
	End
	
	Method FaceRight:Void()
		_flags|=FLAG_FACE
		_currentFrame = 0
		_xSpeed = 8.0
	End
	
	Method EnableRunning:Void()
		_flags|=FLAG_RUNNING
	End
	
	Method DisableRunning:Void()
		_flags&=~FLAG_RUNNING
	End
	
	Global _xPos:Float
	Global _yPos:Float
	Global _xSpeed:Float
	Global _ySpeed:Float
	Global _flags:Int
	Global _currentFrame:Int
	Field _atlas:Image
	Field _states:CHeroineState
	Field _state:CHeroineState
	Field _totalFrameCount:Int
	Field _lastFrameTime:Float
End

Class CHeroineCrouchState Extends CHeroineState
	Method New()
		_strID = "CROUCH DOWN STATE"
		AddFrames(5, 16.5, 0, 13)
	End

	' Override the HandleInput to capture only the crouch action.
	Method HandleInput:Void(heroine:CHeroine, action:Int)
		' If the action input for ducking is no longer being pressed, set the action state to idle.
		If Not (action = MOVE_DUCK)
			If heroine._state = heroine._states._crouch heroine.SetState(heroine._states._stand)
		EndIf
	End

	' Update any action for the crouch state.
	' As this action state is not looped. It has to stop at the crouched position.
	Method Update:Void(heroine:CHeroine)
		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
End Class

' This is that animation for leaving the crouched state. 
Class CHeroineStandUpState Extends CHeroineState
	Method New()
		_strID = "STANB UP STATE"
		AddFrames(4, 16.5, 0, 14)
	End

	Method HandleInput:Void(heroine:CHeroine, action:Int)
	End

	Method Update:Void(heroine:CHeroine)
		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
End

Class CHeroineIdleState Extends CHeroineState
	Method New()
		_strID = "IDLE STATE ONE"
		AddFrames(4, 4.5, 0, 1)
	End

	' Override the HandleInput to capture all actions.
	Method HandleInput:Void(heroine:CHeroine, action:Int)
		' The player is idling, so all inputs need to be checked.
		Select action
			Case MOVE_DUCK
				heroine.SetState(heroine._states._crouch)	
			Case MOVE_LEFT
				heroine.SetState(heroine._states._startRun)
				heroine.FaceLeft()
			Case MOVE_RIGHT
				heroine.SetState(heroine._states._startRun)
				heroine.FaceRight()
			Case MOVE_JUMP_LEFT
				heroine.SetState(heroine._states._startRunJump)
				heroine.FaceLeft()
				heroine.EnableRunning()
			Case MOVE_JUMP_RIGHT
				heroine.SetState(heroine._states._startRunJump)
				heroine.FaceRight()
				heroine.EnableRunning()
			Case MOVE_JUMP
				heroine.SetState(heroine._states._startJump)
		End Select
	End

	' Update any action for the idle state.
	Method Update:Void(heroine:CHeroine)
		Super.Update(heroine)
	End
End

Class CHeroineJumpLoopState Extends CHeroineState
	Method New()
		_strID = "JUMP LOOP STATE"
		AddFrames(4, 12.5, 0, 8)
	End

	Method HandleInput:Void(heroine:CHeroine, action:Int)
	End
	
	Method Update:Void(heroine:CHeroine)
		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
				If heroine._currentFrame > 2
				 	heroine._ySpeed = 8.0
				EndIf
			Else
				heroine.SetState(heroine._states._stopJump)
			EndIf
		EndIf
	End
	
	Method Motion:Void(heroine:CHeroine)
		heroine._yPos += heroine._ySpeed
		If heroine._flags & FLAG_RUNNING
			heroine._xPos += heroine._xSpeed
			Super.Motion(heroine)
		Endif
	End
End

Class CHeroineStartRunJumpState Extends CHeroineState
	Method New()
		_strID = "START RUN JUMP STATE"
		AddFrames(4, 12.5, 0, 6)
	End
	
	Method HandleInput:Void(heroine:CHeroine, action:Int)
	End
	
	Method Update:Void(heroine:CHeroine)
		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 Motion:Void(heroine:CHeroine)
		heroine._yPos += heroine._ySpeed
		If heroine._flags & FLAG_RUNNING
			heroine._xPos += heroine._xSpeed
			Super.Motion(heroine)
		Endif
	End
End

Class CHeroineStartRunState Extends CHeroineState
	Method New()
		_strID = "START RUN STATE"
		AddFrames(1, 12.5, 0, 3)
	End

	Method HandleInput:Void(heroine:CHeroine, action:Int)
		Select action
			Case MOVE_LEFT
				If (heroine._flags & FLAG_FACE)
					heroine.FaceLeft()
				Else
					heroine.EnableRunning()
				EndIf			
			Case MOVE_RIGHT
				If (heroine._flags & FLAG_FACE) = 0
					heroine.FaceRight()
				Else
					heroine.EnableRunning()
				EndIf	
			Case MOVE_JUMP_LEFT
				heroine.SetState(heroine._states._startRunJump)
				heroine.EnableRunning()
			Case MOVE_JUMP_RIGHT
				heroine.SetState(heroine._states._startRunJump)
				heroine.EnableRunning()
			Default
				heroine.SetState(heroine._states._stopRun)
				heroine.DisableRunning()
		End Select
	End
	
	Method Update:Void(heroine:CHeroine)
		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 Motion:Void(heroine:CHeroine)
		If heroine._xPos > 23 And heroine._xPos < DeviceWidth() - 24 heroine._xPos += heroine._xSpeed
		Super.Motion(heroine)
	End
End

Class CHeroineRunLoopState Extends CHeroineState
	Method New()
		_strID = "RUN STATE"
		AddFrames(10, 12.5, 0, 4)
	End

	' the HandleInput to capture all actions.
	Method HandleInput:Void(heroine:CHeroine, action:Int)
		Select action
			Case MOVE_LEFT
				If (heroine._flags & FLAG_FACE)
					heroine.FaceLeft()
				Else
					heroine._flags |= FLAG_RUNNING
				EndIf
			Case MOVE_RIGHT
				If (heroine._flags & FLAG_FACE) = 0
					heroine.FaceRight()
				Else
					heroine._flags |= FLAG_RUNNING
				EndIf
			Case MOVE_JUMP_LEFT
				heroine.SetState(heroine._states._startRunJump)
				heroine.FaceLeft()				
			Case MOVE_JUMP_RIGHT
				heroine.SetState(heroine._states._startRunJump)
				heroine.FaceRight()
			Default
				heroine.SetState(heroine._states._stopRun)
				heroine.DisableRunning()
		End Select
		
	End

	Method Update:Void(heroine:CHeroine)
		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 Motion:Void(heroine:CHeroine)
		If heroine._xPos > 23 And heroine._xPos < DeviceWidth() - 24 heroine._xPos += heroine._xSpeed
		Super.Motion(heroine)
	End
End

Class CHeroineStopRunState Extends CHeroineState
	 Method New()
		_strID = "STOP RUN STATE"
		AddFrames(3, 12.5, 0, 5)
	 End
	 
	 Method HandleInput:Void(heroine:CHeroine, action:Int)	
	 End
	 
	 Method Update:Void(heroine:CHeroine)
		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 Motion:Void(heroine:CHeroine)
		If heroine._xPos > 23 And heroine._xPos < DeviceWidth() - 24 heroine._xPos += heroine._xSpeed
		Super.Motion(heroine)
	End
End


Class CHeroineStandJumpState Extends CHeroineState
	Method New()
		_strID = "START STAND JUMP STATE"
		AddFrames(6, 12.5, 0, 7)
	End
	
	Method HandleInput:Void(heroine:CHeroine, action:Int)
	End
	
	Method Update:Void(heroine:CHeroine)
		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 Motion:Void(heroine:CHeroine)
		heroine._yPos += heroine._ySpeed
	End
	
End

Class CHeroineStopJumpState Extends CHeroineState
	Method New()
		_strID = "STOP JUMP STATE"
		AddFrames(2, 12.5, 0, 9)
	End
	
	Method HandleInput:Void(heroine:CHeroine, action:Int)
	End
	
	Method Update:Void(heroine:CHeroine)
		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 Motion:Void(heroine:CHeroine)
		heroine._yPos += heroine._ySpeed
		If heroine._flags & FLAG_RUNNING
			heroine._xPos += heroine._xSpeed
			Super.Motion(heroine)
		Endif
	End
End

#If TARGET="html"
#HTML5_CANVAS_WIDTH=800
#HTML5_CANVAS_HEIGHT=600
#Endif

Function Main:Int()
	New CGame()
	Return 0
End

Class CGame Extends App
	Method OnCreate:Int()
#If TARGET<>"html"
		SetDeviceWindow(800, 600, 4)
#Endif
		SetUpdateRate(60)
		player = New CHeroine()
		player._xPos = DeviceWidth() / 2
		player._yPos = DeviceHeight() / 2
		
		controls = New CControllerInput()
		Return 0
	End
	
	Method OnUpdate:Int()
#If TARGET="glfw"
		If KeyDown(KEY_ESCAPE) EndApp()
#Endif
		controls.Update()
		player.HandleInput(controls.GetAction())
		player.Update()
		Return 0
	End
	
	Method OnRender:Int()
		Cls
		DrawRect(0, (DeviceHeight() / 2) + 24, DeviceWidth(), 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)
		player.Render()
		Return 0
	End

	Field player:CHeroine
	Field controls:CControllerInput
End
If you've dug yourself into a hole. Just keep digging. You're bound to come out the other side eventually.

Jackdaw

#1
Okay. If you want to see a better example of a Cerberus-X project.
Then you can download the sources from here.

You will still need to download the image file linked above.

It's a shame that BlitzMax doesn't support method properties. It would make it a lot easier to work with.
If you've dug yourself into a hole. Just keep digging. You're bound to come out the other side eventually.