Android Tutorial [B4X] [BitmapCreator] Creating cross platform games

Please use XUI2D for new games: https://www.b4x.com/android/forum/threads/xui2d-example-pack.96454/

BitmapCreator is a relatively new cross-platform class for low level drawings.
The drawing performance when used correctly is good and it can serve as the basis for 2d games:


Requirements:

- BitmapCreator v3.5+ (B4A, B4J, B4i)
- B4A v8.3+, B4i v5.0+ and B4J v6.3+

Before we start it is important to understand the performance characteristics of BitmapCreator:
[B4X] [BitmapCreator] Maximizing Performance with BC

The main advantage of using BitmapCreator for games is that it is cross platform. Together with XUI library you can use the same code in all three platforms.
I recommend starting with B4J and later creating B4A and B4i projects that reference the same classes. It should look like this:

SS-2018-06-07_17.03.57.png


All modules, except of the Main module, are shared between the three projects.

Lets Start

I'll explain the "walking character" example.
1. Nothing prevents you from implementing things in a different way.
2. I will not cover all the code. If you want to better understand it then run the example and go over the code. Overall the code is not too complicated.

The core of the game code is inside MainLoop in the Game class.
The main things that happen every iteration:

- The GameTime counter is incremented by 1.
- The Tick sub of each sprite is called. A GameStep object is passed to these subs. This object can be used to pass information related to the current tick.
- Each of the sprites does whatever it needs to do in the Tick event. This includes: updating the position, changing the current frame, checking for collisions and so on.
- Deleted sprites are removed from the list of sprites.
- Future tasks are checked and executed.
- Each of the visible sprites creates a DrawTask.
- All the drawing tasks are drawn asynchronously.
- The TargetView (ImageView) is updated with the updated bitmap.

Note that nothing should happen outside of this loop. For example input events should only set flags that will later be treated from inside the loop.

Sprites

Each visual element is represented with a custom class instance. This class instance holds a reference to a Sprite object. Sprite.Target points back to the class instance. It is a composition pattern.
The sprite objects are added to the main game list.
The Tick events are called from the main loop like this:
B4X:
For Each Sprite As Sprite In Sprites
   Sprite.Index = Sprite.Index + 1
   Sprite.Tick(gs)
Next

'Sprite.Tick:
Public Sub Tick (gs As GameStep)
   CallSub2(Target, "Tick", gs)
End Sub

The Sprite object holds all kinds of fields and methods that can be useful for working with the sprites.
For example lets see the code of the Tick sub of the Bird class:
B4X:
Public Sub Tick (gs As GameStep)
   If MySprite.Index Mod MySprite.TickInterval = 0 Then
       MySprite.CurrentFrame = (MySprite.CurrentFrame + 2) Mod MySprite.mFrames.Size
   End If
   MySprite.UpdatePosition
   If MySprite.InsideViewRect = False Then
       MySprite.Delete(gs)
       Return
   End If
   MySprite.Frame = MySprite.mFrames.Get(MySprite.CurrentFrame)
End Sub
MySprite.Index is a counter that is incremented automatically every iteration.
MySprite.TickInterval is an optional field that is used here to change the current frame every x ticks.
MySprite.UpdatePosition updates MySprite.Position based on MySprite.Velocity.
Note that the position points to the sprite center.
MySprite.InsideViewRect checks whether the sprite is inside the currently visible rectangle. In this case we want to delete it once the bird goes outside of the screen.
The last line updates the current Frame (it is a BitmapCreator object).

In many cases you can use the DummySprite class instead of creating a custom class. This class is useful for any sprite that doesn't have "special" logic. In this example it is used for the score numbers that appear after a collision with a coin. It could have also been used instead of Bird and Explosion classes (it was created after those classes were written).

Coordinates

By default the reference for the sprites positions is the game virtual coordinates.
MySprite.ViewRect returns the visible rectangle.
It is moved in the Kid class:
B4X:
'check if near edges and change ViewRect as needed
       Dim DistanceToLeft As Int = MySprite.Position.x - MySprite.Frame.mWidth / 2 - MySprite.ViewRect.Left
       Dim DistanteToRight As Int = MySprite.ViewRect.Right - MySprite.Frame.mWidth / 2 - MySprite.Position.x
       If  DistanceToLeft < MinDistanceToEdge Or DistanteToRight < MinDistanceToEdge Then
           MySprite.GameUtils.PushRect(MySprite.ViewRect, MySprite.Velocity.vx, 0)
           MySprite.mGame.ViewRectPushed
       End If

You can however switch to the screen coordinates. Screen coordinates are relative to TargetView. This is used in this example for the score sprites that always move to the top left corner. We don't want their position to be affected by changes to the game ViewRect.
B4X:
'set the score sprite position based on the coin position
ds.MySprite.Position = MySprite.ToScreenPosition
'set ScreenCoordinates to True
ds.MySprite.ScreenCoordinates = True

Future Tasks


There are cases where you want to do something later.
For example 20 ticks before a coin disappears it starts moving up. This is done with this code:
B4X:
MySprite.mGame.AddFutureTask(Me, "Move_Up", MySprite.TimeToLive - 20, Null)

Public Sub Move_Up (ft As FutureTask)
   MySprite.Velocity.vy = -10
End Sub
The underscore in the sub name is important if you want to compile with obfuscation.

Tips

- Avoid loading the same resources multiple times. Load then when the game starts and reuse them.
- jGameViewHelper (B4J) and iGameView (B4i) are used for the low latency sound effects and keyboard events in B4J. Make sure to update to iGameView v1.05+ if you are using a local builder.
- B4i multitouch handling is based on: https://www.b4x.com/android/forum/threads/87027/#content
In this case the moving event is ignored.

Art Credits

The sounds and images were downloaded from: https://opengameart.org

https://opengameart.org/content/pixel-explosion-12-frames
https://opengameart.org/content/coin-animation
https://opengameart.org/content/2d-character-animation-sprite
https://opengameart.org/content/picked-coin-echo-2
https://opengameart.org/content/several-scrolling-backgrounds-and-layerable-runners

The three projects links:
B4A: www.b4x.com/b4j/files/games/B4A_WalkingCharacter.zip
B4i: www.b4x.com/b4j/files/games/B4i_WalkingCharacter.zip
B4J: www.b4x.com/b4j/files/games/B4J_WalkingCharacter.zip

All the classes are identical.

Updates:

- The code in B4A and B4i (Main) was updated. The game size is set to 600 / 400 and the ImageView is scaled based on the screen size.
The code in MainLoop was changed with:
B4X:
mTargetView.SetBitmap(bmp)
#if B4A
'B4XView.SetBitmap sets the gravity in B4A to CENTER. This will prevent the bitmap from being scaled as needed so
'we switch to FILL
Dim iv As ImageView = mTargetView
iv.Gravity = Gravity.FILL
#End If

Latest version of the code is available in the B4A and B4i examples.
 
Last edited:

sorex

Expert
Licensed User
Longtime User
Nice!

I'm getting a crash on this line > gmh.PlayAudioClip(COIN_SOUND, 1) (unknown source error)

in B4J debug mode tho, seems to go away in release mode.

does this mean we should just disable sound in debug mode or is it a bug somewhere?
 

sorex

Expert
Licensed User
Longtime User
It should work in debug mode. Try to rename the audio file name to 1.wav and update the code. Does it work?

Which OS are you using?

that was my first thought aswell that the spaces could be a problem but it works fine for the images.

I renamed it to 1.wav and changed the code but it still crashes.

Even more bizarre is the fact that when I swap the filenames in the 2 lines of code I hear the coin sound when that explode animation appears
and when I touch a coin I also get the crash instead of the explosion sound.


changing the variable from COIN_SOUND to BOMB_SOUND in this line > gmh.PlayAudioClip(COIN_SOUND,1)

makes it work too.

it's like the init of the second sound fails but it's not reported to the debug window.


It was Friday on a win 8.1 box and also happening on this win 7 box.
 

R D Boozer

Member
Licensed User
Thanks for doing this Erel! Just want you to know how much your efforts are appreciated. I will use this class for sure, and not necessarily just for games.
 

Jaames

Active Member
Licensed User
Longtime User
Thank you for sharing this and for BitmapCreator.

Is it too hard to implement pinch-zoom / pan with this framework?
 
Top