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:
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:
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:
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:
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.
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:
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:
Latest version of the code is available in the B4A and B4i examples.
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:
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.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
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: