Basic4Android: A first look at ABPlay, a game engine library

ABPlay Screenshot
ABPlay Screenshot

It has been a while since I’ve written a new article on this blog because it have been busy months both at work and in my personal life. One of the things I wanted to do was writing an easy to use Game Engine for B4A. I have written the ABPhysics engine in the past and recently Informatix pointed out I had started another engine (ABGameEngine) before that. Development on ABGameEngine was stopped early for several reasons: time was one of them, but also because several other developers were working on an engine themselves. It seemed a little bit pointless to continue.

However, the other engines were not further developed either. Until recently Erel came up with the GameView. This is an excellent View that will cover the needs of a lot of beginning programmers. I definitely would like to urge starting game developers to take a look at this. Registered users of B4A can download the GameView lib from here.

But still, I wanted something more. I looked at ABgameEngine again, as it had some great ideas like layers, animated sprites, gamepads etc. But it was also very old code and not written very well. And it crashed all the time 🙂

I decided to restart from scratch a couple of weeks ago. Some weekends and evenings later ABPlay was born! And with a lot of goodies!

Here is a small demo video of what I got so far. it demonstrates the following:

1. Layers
     (Layer 1) the moving background with the Odies as animated sprites
     (Layer 2) the black foreground layer with a weird dancing creature that passes now and then
2. Animated sprites, not bound to a layer
     (our hero Garfield is back!)
3. A sprite can have different animations.
     (like one for standing, one for walking, one for fighting, etc)
4. A sprite can have different predefined 'Walks'
     (A walk can be build like you would build a path. It's a sequence of lines, bezier curves, wait periods etc. It's like a simple flash movie)
5. Gamepad controls
     (the 'joystick' pad on the left)
     (the 'action' pad on the right)
     (the 'direction' pad as an alternative to the joystick, not shown in this video)
6. In the demo I cannot demonstrate it with a mouse, but it is completely multi touch
     (You can control Garfield AND press the Action button X AND do a swipe anywhere at the same time)

But let’s have a look (beware this is running on the Emulator. On a real device it is much smoother):

I’m actually very pleased with the result. The graphics and handling is very smooth.
Above all, it’s still very easy to program. Here is the whole code for the demo app:

#Region  Project Attributes
	#ApplicationLabel: ABPlayTest
	#VersionCode: 1
	#VersionName:
	'SupportedOrientations possible values: unspecified, landscape or portrait.
	#SupportedOrientations: landscape
	#CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
	#FullScreen: true
	#IncludeTitle: false
#End Region

Sub Process_Globals

End Sub

Sub Globals
	Dim myPlay As ABPlay
	' the panel that will hold the ABPlay
	Dim myPanel As Panel

	' a background and foregroud layer
	Dim bgLayer As ABLayer
	Dim fgLayer As ABLayer

	' some colors to show the multi touch points in the demo
	Dim myColors() As Int = Array As Int(Colors.Red, Colors.Green, Colors.Blue, Colors.Cyan, Colors.Yellow, Colors.Gray, Colors.White, Colors.Magenta, Colors.LightGray, Colors.DarkGray)

	' our hero
	Dim Hero As ABSprite
	' our enemies
	Dim Enemies As List

	' param to set the set the speed
	Dim Speed As Float = 0.2

	' some variables to hold the current state of the hero
	Dim currentAction As String
	Dim currentDirection As String
	Dim currentIsStanding As Boolean
End Sub

Sub Activity_Create(FirstTime As Boolean)
	' initialize ABPlay with myPanel
	myPanel.Initialize("")
	Activity.AddView(myPanel, 0,0,100%x,100%y)
	myPlay.Initialize(myPanel, "myPlay")

	'////////////// BEGIN loading sprite sequences
	' Load Sprite Sequences
	myPlay.LoadSpriteSequence("GarStandingLeft",LoadBitmap(File.DirAssets, "garleftstill.png"),6, 1, 1000)
	myPlay.LoadSpriteSequence("GarStandingLeft",LoadBitmap(File.DirAssets, "garleftstill.png"),6, 1, 1000)
	myPlay.LoadSpriteSequence("GarStandingRight",LoadBitmap(File.DirAssets, "garrightstill.png"),6, 1, 1000)
	myPlay.LoadSpriteSequence("GarWalkingLeft",LoadBitmap(File.DirAssets, "garleft.png"),8, 1, 1000)
	myPlay.LoadSpriteSequence("GarWalkingRight",LoadBitmap(File.DirAssets, "garright.png"),8, 1, 1000)
	myPlay.LoadSpriteSequence("GarStandingLeftFight",LoadBitmap(File.DirAssets, "garleftstillfight.png"),2, 1, 250)
	myPlay.LoadSpriteSequence("GarStandingRightFight",LoadBitmap(File.DirAssets, "garrightstillfight.png"),2, 1, 250)
	myPlay.LoadSpriteSequence("GarWalkingLeftFight",LoadBitmap(File.DirAssets, "garleftfight.png"),7, 1, 1000)
	myPlay.LoadSpriteSequence("GarWalkingRightFight",LoadBitmap(File.DirAssets, "garrightfight.png"),7, 1, 1000)
	' and the ones for Odie
	myPlay.LoadSpriteSequence("OdieStandingLeft", LoadBitmap(File.DirAssets, "odieleftstill.png"),7,1,Rnd(800,1200))
	myPlay.LoadSpriteSequence("OdieStandingRight", LoadBitmap(File.DirAssets, "odierightstill.png"),7,1,Rnd(800,1200))
	myPlay.LoadSpriteSequence("OdieWalkingLeft", LoadBitmap(File.DirAssets, "odieleft.png"),5,1,Rnd(800,1200))
	myPlay.LoadSpriteSequence("OdieWalkingRight", LoadBitmap(File.DirAssets, "odieright.png"),5,1,Rnd(800,1200))
	' and the one for the creature
	myPlay.LoadSpriteSequence("CreaturePassToRight", LoadBitmap(File.DirAssets, "creature.png"), 3, 4, 1200)
	'////////////// END loading sprite sequences

	'////////////// BEGIN building the Hero Garfield
	' initialize the hero
	Hero.Initialize("Hero", 50%x, 50%y)
	' add sprite sequences
	Hero.AddSpriteSequence("GarStandingLeft")
	Hero.AddSpriteSequence("GarStandingRight")
	Hero.AddSpriteSequence("GarWalkingLeft")
	Hero.AddSpriteSequence("GarWalkingRight")
	Hero.AddSpriteSequence("GarStandingLeftFight")
	Hero.AddSpriteSequence("GarStandingRightFight")
	Hero.AddSpriteSequence("GarWalkingLeftFight")
	Hero.AddSpriteSequence("GarWalkingRightFight")
	Hero.StartSpriteSequence("GarStandingLeft", True)
	currentDirection="LEFT"
	'//////////////  END building the Hero Garfield

	'////////////// BEGIN building the background layer with Odies
	' initalize a background layer
	bgLayer.Initialize("background", 0,0)
	bgLayer.SetBackground(LoadBitmap(File.DirAssets, "bga.jpg"), 1.0)
	myPlay.AddLayer(bgLayer)

	' initialize some enemies
	Enemies.Initialize
	Dim a As Int
	For a = 1 To 10
		Dim Odie As ABSprite

		' initialize an Odie with some animation sequences
		Odie.Initialize("Odie" & a, Rnd(10%x,90%x), Rnd(10%y, 90%y))
		' add multiple sprite sequences
		Odie.AddSpriteSequences(Array As String("OdieStandingLeft","OdieStandingRight","OdieWalkingLeft","OdieWalkingRight"))

		' create a random walk to the right
		Dim WalkRight As ABSpriteWalk
		WalkRight.Initialize("ToTheRight", True)
		Dim newX As Int = Odie.x+Rnd(20%x, 80%x)
		WalkRight.AddLine("OdieWalkingRight",Odie.x, Odie.y, newX, Odie.y, Rnd(90,100))
		WalkRight.AddWait("OdieStandingRight",newX, Odie.y, Rnd(10,50))
		WalkRight.AddLine("OdieWalkingLeft",newX, Odie.y, Odie.x, Odie.y, Rnd(90,100))
		WalkRight.AddWait("OdieStandingLeft",Odie.x, Odie.y, Rnd(10,50))
		Odie.AddWalk(WalkRight)

		' create a random walk to the left
		Dim WalkLeft As ABSpriteWalk
		WalkLeft.Initialize("ToTheLeft", True)
		Dim newX As Int = Odie.x-Rnd(20%x, 80%x)
		WalkLeft.AddLine("OdieWalkingLeft",Odie.x, Odie.y, newX, Odie.y , Rnd(90,100))
		WalkLeft.AddWait("OdieStandingLeft",newX, Odie.y, Rnd(10,50))
		WalkLeft.AddLine("OdieWalkingRight",newX, Odie.y, Odie.x, Odie.y, Rnd(90,100))
		WalkLeft.AddWait("OdieStandingRight",Odie.x, Odie.y, Rnd(10,50))
		Odie.AddWalk(WalkLeft)

		' pick random a walk, left or right
		Dim GoLeft As Int = Rnd(0,2)
		If GoLeft = 0 Then
			Odie.StartWalk("ToTheRight")
		Else
			Odie.StartWalk("ToTheLeft")
		End If
		bgLayer.AddSprite(Odie)
	Next
	'////////////// END building the background layer with Odies

	'////////////// BEGIN building the foreground layer with creature
	' initalize a foreground layer
	Dim fgLayer As ABLayer
	fgLayer.Initialize("foreground", 0,0)
	fgLayer.SetBackground(LoadBitmap(File.DirAssets, "fga.png"), 100%y/400)
	myPlay.AddLayer(fgLayer)

	' and a weird creature...
	Dim creature As ABSprite
	creature.Initialize("creature", -100%x, 100%y-240)
	creature.AddSpriteSequence("CreaturePassToRight")
	' with a walk
	Dim pass As ABSpriteWalk
	pass.Initialize("DanseToTheRight", True)
	pass.AddLine("DansingRight",creature.x, creature.y, 200%x, creature.y, 300)
	creature.AddWalk(pass)
	' and start the walk
	creature.StartWalk("DanseToTheRight")
	' add the creature to the foreground layer
	fgLayer.AddSprite(creature)
	'////////////// END building the foreground layer with creature

	'////////////// BEGIN Add game pads
	' initialize and start a Joystick Gamepad
	myPlay.InitializeJoystickPad(18, 100%y-210, 192, 192 , LoadBitmap(File.DirAssets, "joystick_bg.png"),LoadBitmap(File.DirAssets, "joystick.png"))
	myPlay.ShowJoystickPad(True)	

	' initialize and start a Action Gamepad
	myPlay.InitializeActionPad(100%x-210, 100%y-210, 192, 192 , LoadBitmap(File.DirAssets, "action_active.png"),LoadBitmap(File.DirAssets, "action_inactive.png"),LoadBitmap(File.DirAssets, "action_mask.png"),True, False, True, False)
	myPlay.ShowActionPad(True)
	'////////////// END Add game pads
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)
	' NEEDED FOR THE MOMENT TO CATCH THE HOME KEY, RESUME/PAUSE NOT YET SUPPORTED!
	myPlay.StopAndRecycle
	Activity.Finish
End Sub

Sub Activity_KeyPress (KeyCode As Int) As Boolean 'Return True to consume the event
	Select Case KeyCode
		Case KeyCodes.KEYCODE_BACK
			' NEEDED: stop the drawing thread and recycle stuff
			myPlay.StopAndRecycle
			Activity.Finish
			Return True
		Case KeyCodes.KEYCODE_HOME
			Return True
	End Select
End Sub

Sub myPlay_Draw(c As Canvas, State As ABState)
	' the actual drawing, do NOT set a debug stop in here!

	'////////////// BEGIN Calculation stuff
	' calculate hero stuff depending on what buttons we pressed on the gamepad
	currentAction = ""
	If State.UsingActionPad Then
		' go into fight state
		If State.ACTION_X Then
			currentAction = "X"
		End If
		' go back to center
		If State.ACTION_Y Then
			Hero.SetPostition(50%x,50%y)
		End If
	End If

	Dim XMovement As Int
	Dim YMovement As Int
	If State.UsingJoystickPad Then
		XMovement = State.JOYSTICK_X*Speed
		YMovement = State.JOYSTICK_Y*Speed
		' update the hero's position and direction
		If XMovement<0 Then
			currentDirection = "LEFT"
		Else
			currentDirection = "RIGHT"
		End If
		Hero.SetPostition(Hero.x+XMovement, Hero.y+YMovement)
		currentIsStanding = False
	Else
		currentIsStanding = True
	End If

	' set the animation type
	Select Case currentDirection
		Case "LEFT"
			Select Case currentAction
				Case "X"
					If currentIsStanding Then
						Hero.StartSpriteSequence("GarStandingLeftFight", False)
					Else
						Hero.StartSpriteSequence("GarWalkingLeftFight", False)
					End If
				Case Else
					If currentIsStanding Then
						Hero.StartSpriteSequence("GarStandingLeft", False)
					Else
						Hero.StartSpriteSequence("GarWalkingLeft", False)
					End If
			End Select
		Case "RIGHT"
			Select Case currentAction
				Case "X"
					If currentIsStanding Then
						Hero.StartSpriteSequence("GarStandingRightFight", False)
					Else
						Hero.StartSpriteSequence("GarWalkingRightFight", False)
					End If
				Case Else
					If currentIsStanding Then
						Hero.StartSpriteSequence("GarStandingRight", False)
					Else
						Hero.StartSpriteSequence("GarWalkingRight", False)
					End If
			End Select
	End Select	

	' update the hero animation
	Hero.Update

	' move our backgrounds, does not make sense but shows the possibilities
	Dim newX, newY As Int
	Dim newMovementX, newMovementY As Int
	newMovementX=Min(Abs(XMovement),1)
	newMovementY=Min(Abs(YMovement),1)
	If currentIsStanding = False Then
		If currentDirection = "LEFT" Then
			newX = bgLayer.ViewX
			newY = bgLayer.ViewY
			If newX-newMovementX >= 0 Then
				newX = newX-newMovementX
			End If
			If newY-newMovementY >= 0 Then
				newY = newY-newMovementY
			End If
			bgLayer.SetLayerPostion(newX, newY)
		Else
			newX = bgLayer.ViewX
			newY = bgLayer.ViewY
			If newX+newMovementX <= bgLayer.OuterWidth - myPlay.Width Then
				newX = newX+newMovementX
			End If
			If newY+newMovementY <= bgLayer.OuterHeight - myPlay.Height Then
				newY = newY+newMovementY
			End If
			bgLayer.SetLayerPostion(newX, newY)
		End If
	End If

	'////////////// END Calculation stuff

	'////////////// BEGIN Drawing stuff
	'ok, all the calulations are done, let's draw!

	' draw the background layer with all its sprites on it and advance all sprite animations and walks
	myPlay.DrawLayer("background", c)

	Dim a As Int
	' draw the multitouch points that are not on the gamepad, no action here but just to show the possibilities
	For a = 0 To State.touchPoints.Size - 1
		Dim tmpP As ABTouchPoint
		tmpP = State.touchPoints.GetValueAt(a)
		c.DrawCircle(tmpP.X, tmpP.Y, 50dip, myColors(tmpP.id), True, 1dip)
	Next

	' draw the hero
	Hero.Draw(c)

	' draw the foreground layer with all its sprites on it and advance all sprite animations and walks
	myPlay.DrawLayer("foreground", c)
	'////////////// END Drawing stuff

	' NEEDED: very last line of the Draw event. Let ABPlay know it may process touches again!
	myPlay.DrawDone()
End Sub

I’m not there yet, but it’s a good start. It needs a lot more testing and a lot of new features.

Until next time!
Alwaysbusy

Click here to Donation if you like my work