Android Tutorial [B4X] [BitmapCreator] Cross platform Falling Sand game

SS-2018-06-19_11.20.27.png


The main challenge here is the performance. All particles are updated if needed and redrawn every cycle.

The Particles array stores the state of all "game pixels". A value of 0 means that the pixel is empty. Other values mean that there is a particle on that pixel.
Sand particles move down or diagonally if they can.

The RowsState array tracks the rows that are considered dirty. Non-dirty rows are skipped.

Single dimension arrays of bytes can be accessed in an optimized way in B4i with Bit.FastArrayGet / Set methods (https://www.b4x.com/android/forum/threads/92951/#content). I used these methods in some of the cases.

All the important code is implemented in the Game class, which is of course shared between the three projects. No sprites are used in this game. The drawings are done in the MoveParticles sub.
As in the Tetris game the layout is managed with the designer script.

You need to change the GameUtils module that is included in the zip files with the one attached separately.

Dependencies:

- Recent version of XUI.
- BitmapCreator v3.6 (B4J, B4i)

The three projects are attached. I've also uploaded an executable jar. Download and double click.
www.b4x.com/b4j/files/games/FallingSand_ExecutableJar.jar
 

Attachments

  • B4A_FallingSand.zip
    25.6 KB · Views: 864
  • B4i_FallingSand.zip
    177.6 KB · Views: 662
  • B4J_FallingSand.zip
    24.6 KB · Views: 703
  • GameUtils.bas
    6.5 KB · Views: 752
Last edited:

Mark Read

Well-Known Member
Licensed User
Longtime User
Obviously Erel has 36 hours in his day! Otherwise he wouldn't have time for such great things between writing updates and answering questions. Great post! :D
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Projects in the first post were updated with a fix to a bug that caused the game to crash.

The cause of this bug might be a bit interesting as it is a pattern that can be used in many cases.

When the particles move we need to check the status of the nearby pixels. This causes a problem with the pixels near the edge. We can add checks to assure that we don't try to access out of bounds pixels but it can be annoying to always check it when 99.5% of the pixels are known to be inside.
So instead we are creating ROCK borders when the game starts:
B4X:
'create the borders
For y = 0 To Height - 1
   Particles((y + 1) * Width - 1) = ROCK
   Particles(y * Width + 1) = ROCK
Next
For x = 0 To Width - 1
   Particles ((Height - 1) * Width + x) = ROCK
   Particles (x) = ROCK
Next
This way there are less edge cases to deal with.

So where was the bug?

With the Eraser feature. It didn't check for the edges and could have erased the borders themselves. This feature is the only feature that overwrites other types of particles.
This caused the sand particles to fall outside of the BitmapCreator and even outside of the phone in some cases. The fix is to restrict the erasing position. It was added to AddParticles sub.
 

koaunglay

Member
Licensed User
Longtime User
Please! Why can't I test this project?
Help

B4X:
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.DynamicBuilder.build(DynamicBuilder.java:21)
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayoutHelper(LayoutBuilder.java:349)
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayoutHelper(LayoutBuilder.java:454)
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:148)
    at anywheresoftware.b4a.objects.PanelWrapper.LoadLayout(PanelWrapper.java:134)
    at anywheresoftware.b4a.objects.B4XViewWrapper.LoadLayout(B4XViewWrapper.java:247)
    at b4a.example.game._initialize(game.java:298)
    at b4a.example.main._activity_create(main.java:342)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:191)
    at b4a.example.main.afterFirstLayout(main.java:104)
    at b4a.example.main.access$000(main.java:17)
    at b4a.example.main$WaitForLayout.run(main.java:82)
    at android.os.Handler.handleCallback(Handler.java:754)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:163)
    at android.app.ActivityThread.main(ActivityThread.java:6342)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:880)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:770)
Caused by: java.lang.RuntimeException: Font asset not found b4x_fontawesome.otf
    at android.graphics.Typeface.createFromAsset(Typeface.java:206)
    at anywheresoftware.b4a.objects.TextViewWrapper.getTypeface(TextViewWrapper.java:155)
    at anywheresoftware.b4a.objects.TextViewWrapper.build(TextViewWrapper.java:183)
    at anywheresoftware.b4a.objects.LabelWrapper.build(LabelWrapper.java:36)
    ... 21 more
game_initialize (java line: 298)
java.lang.RuntimeException: java.lang.RuntimeException: Object should first be initialized (Label).
Did you forget to call Activity.LoadLayout?
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:170)
    at anywheresoftware.b4a.objects.PanelWrapper.LoadLayout(PanelWrapper.java:134)
    at anywheresoftware.b4a.objects.B4XViewWrapper.LoadLayout(B4XViewWrapper.java:247)
    at b4a.example.game._initialize(game.java:298)
    at b4a.example.main._activity_create(main.java:342)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:191)
    at b4a.example.main.afterFirstLayout(main.java:104)
    at b4a.example.main.access$000(main.java:17)
    at b4a.example.main$WaitForLayout.run(main.java:82)
    at android.os.Handler.handleCallback(Handler.java:754)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:163)
    at android.app.ActivityThread.main(ActivityThread.java:6342)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:880)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:770)
Caused by: java.lang.RuntimeException: Object should first be initialized (Label).
Did you forget to call Activity.LoadLayout?
    at anywheresoftware.b4a.AbsObjectWrapper.getObject(AbsObjectWrapper.java:50)
    at anywheresoftware.b4a.objects.ViewWrapper.innerInitialize(ViewWrapper.java:73)
    at anywheresoftware.b4a.objects.LabelWrapper.innerInitialize(LabelWrapper.java:28)
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayoutHelper(LayoutBuilder.java:435)
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayoutHelper(LayoutBuilder.java:454)
    at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:148)
    ... 16 more
 

Roger Taylor

Member
Licensed User
Longtime User
I hope you don't mind me jumping over here to talk about how this particle demo works. I'm trying to find the elements responsible for binding the Particles() byte array to the "live" bitmap. Are the rows redrawn only when needed? I'm getting an almost steady FPS no matter how many particles I add, so this is nice.
 

Roger Taylor

Member
Licensed User
Longtime User
GameUtils.MainLoop calls Game.Tick every loop and then asynchronously renders the frame with DrawBitmapCreatorsAsync.

Game.Tick calls MoveParticles. Most of the logic is implemented in this sub.
This sub draws the particles with calls to bc.SetARGB.
Should I be worried that the async redraw might queue up, or fall behind after say, a day of running?
 

agraham

Expert
Licensed User
Longtime User
I don't know if it is worth updating these projects but they now won't run 'as is' in B4A or B4J
B4X:
bc.BuildPatternCache (True) ' error :  "Unknown member: buildpatterncache"

Wait For (MainBC.DrawBitmapCreatorsAsync(tasks)) Complete (bmp As B4XBitmap) ' error : ", expected"
I assume that the BuildPatternCache lines can be deleted but maybe I'having a bad day but I can't see how to fix the Wait For. :(
 
Top