One of the libraries I wrote for Basic4Android is ABPhysicsEngine. This is a full 2D Newton engine. Games like ‘Angry Birds’ and ‘Cut the rope’ are made with similar engines. This one is very easy to use within B4A.
For this tutorial you must download version 1.1 of the library from the B4A forum!
Here is a little ‘anti-stress’ game I made with the library in B4A. You have to tap very fast anywhere on the screen and all kind of balls will appear and fall down on the two running Garfields. No winning or prices, but this is for learning purposes anyway.
First, we declare some variables. We’ll need an ABPhysicsEngine (ph), some Bitmaps for the graphics, some groups (bal, ground, gar) and a particle (p)
'Activity module Sub Process_Globals 'These global variables will be declared once when the application starts. 'These variables can be accessed from all modules. End Sub Sub Globals 'These global variables will be redeclared each time the activity is created. 'These variables can only be accessed from this module. Dim ph As ABPhysicsEngine Dim tim As Timer Dim c As Canvas Dim Panel1 As Panel Dim bm As Bitmap Dim bm2 As Bitmap Dim bm3 As Bitmap Dim bm4 As Bitmap Dim bm5 As Bitmap Dim bm6 As Bitmap Dim ground As ABGroup Dim p As ABParticle Dim bal As ABGroup Dim gar As ABGroup Dim walk1 As Double Dim walk2 As Double Dim scale As Double End Sub
Next we do some initializations in the Activity_Create() sub.
We initialize our ABPhysicsEngine (ph). A normal value is 1/4 or 1/3. This controls the speed and accuracy of the engine. By default a value of 0.25 will do.
We create a Gravity force (f) and add it to the engine. We also set the damping to 1 (this, together with the initialization value of ph, controls the accuracy and speed). By default set it to 1 and in most cases you will never have to give it another value.
We’ll load our pictures and create our first group of particles: ground
Typical features of the ground are that they are fixed (they don’t fall for example). They are collidable.
Check out the other parameters like position and angle in the function definition.
We use our particle p just to get to the constant values (like p.RECTANGLE).
Our second group ground2 is not added to the first group because we don’t want it to be collidable (the balls have to fall through it).
Then we built our Garfields. They are collidable and have some animation (running around and waving with their arms)
With ‘gar.AddCollidable(ground2)’ we say, unlike the balls, garfield cannot fall through ground2. He has to walk up and down.
We prepare a group bal that can collide with the group ‘ground’ and of course also with the group ‘gar’:
bal.Initialize(“bal”, True)
bal.AddCollidable(ground)
bal.AddCollidable(gar)
ph.AddGroup(bal)
Watch out with addCollidable! Do not collide A with B AND B with A. If you do, all forces will be calculated exponentionally.
Collide A with B or B with A.
Finally, we add our groups to the engine and start the timer.
Sub Activity_Create(FirstTime As Boolean) activity.LoadLayout("1") c.Initialize(panel1) ph.Initialize(0.25) Dim f As ABForce f.Initialize(False, 0, 3, -1, "Gravity") ph.AddGlobalForce(f) ph.Damping = 1 bm.Initialize(File.DirAssets,"WheelBig.png") bm2.Initialize(File.DirAssets,"balk.png") bm4.Initialize(File.DirAssets, "VoetBal.png") bm6.Initialize(File.DirAssets, "BasketBal.png") ' and an animation bm3.Initialize(File.DirAssets,"garrightfight.png") bm5.Initialize(File.DirAssets,"garleftfight.png") ground.Initialize("ground", False) ground.AddParticle(buildParticle(p.RECTANGLE, 75, 150, 150, 10, 0, True, False, 10, 0, 0 , 1, True , "Ground1")) ground.AddParticle(buildParticle(p.RECTANGLE, 300, 250, 150, 10, 0, True, False, 350, 0, 0 , 1, True , "Ground2")) ground.AddParticle(buildParticle(p.RECTANGLE, 100, 450, 200, 10, 0, True, False, 5,2, 0 , 1, True , "Ground3")) ground.AddParticle(buildParticle(p.RECTANGLE, 380, 450, 200, 10, 0, True, False, 175,2, 0 , 1, True , "Ground3")) ground.AddParticle(buildParticle(p.RECTANGLE, 5,400, 800, 10, 0, True, False, 90,0, 0 , 1, True , "Ground3")) ground.AddParticle(buildParticle(p.RECTANGLE, 475,400, 800, 10, 0, True, False, 90,0, 0 , 1, True , "Ground3")) Dim ground2 As ABGroup ground2.Initialize("ground2", False) ground2.AddParticle(buildParticle(p.RECTANGLE, 240, 720, 480, 10, 0, True, False, 0, 0, 0 , 1, True , "Ground3")) p = buildParticle(p.CIRCLE, 30, 530, 60, 60, 30, True, True, 0, 0, 0 ,1, False, "garfield1") ' give the particle an animation p.addAnimation(bm3, p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_RIGHT, p.ANIM_ACTION_A,7, 16, p.ANIM_BEHAVIOUR_CONTINIOUS) p.addAnimation(bm5, p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_LEFT, p.ANIM_ACTION_A,7, 16, p.ANIM_BEHAVIOUR_CONTINIOUS) ' start the animation p.StartAnimation(p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_RIGHT, p.ANIM_ACTION_A) ' add the particle gar.AddParticle(p) p = buildParticle(p.CIRCLE, 300, 530, 60, 60, 30, True, True, 0, 0, 0 ,1, False, "garfield2") ' give the particle an animation p.addAnimation(bm3, p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_RIGHT, p.ANIM_ACTION_A,7, 16, p.ANIM_BEHAVIOUR_CONTINIOUS) p.addAnimation(bm5, p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_LEFT, p.ANIM_ACTION_A,7, 16, p.ANIM_BEHAVIOUR_CONTINIOUS) ' start the animation p.StartAnimation(p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_LEFT, p.ANIM_ACTION_A) ' add the particle gar.AddParticle(p) gar.AddCollidable(ground2) walk1 = 3 walk2 = -4 bal.Initialize("bal", True) bal.AddCollidable(ground) bal.AddCollidable(gar) ph.AddGroup(bal) Scale = Activity.Width / 480 addgroups(Array As ABGroup(ground, ground2, gar)) ' start the timer tim.Initialize("tim", 16) tim.Enabled = True End Sub
We make sure our timer stops and restarts when we leave the program.
Sub Activity_Resume tim.Enabled = True End Sub Sub Activity_Pause (UserClosed As Boolean) tim.Enabled = False End Sub
In our timer_tick sub we draw everything. We check if the Garfields have to turn around and change the animation. I think this code is not difficult to understand.
One thing to notice is the removal of the balls. I first add the balls I want to remove to a temporary list and after the loop has finished, I’ll then remove them. This is because I do not want to interrupt the loop.
For j = 0 To remove.Size – 1
bal.RemoveParticle(remove.Get(j))
Next
Sub tim_Tick ph.Step() Dim r As Rect r.Initialize(0,0,Activity.Width,Activity.Height) p = gar.GetParticleByName("garfield1") p.CenterX = p.CenterX + walk1 If walk1 > 0 Then If p.CenterX > 450 Then walk1 = -3 p.StartAnimation(p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_LEFT, p.ANIM_ACTION_A) End If Else If p.CenterX < 30 Then walk1 = 3 p.StartAnimation(p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_RIGHT, p.ANIM_ACTION_A) End If End If p = gar.GetParticleByName("garfield2") p.CenterX = p.CenterX + walk2 If walk2 > 0 Then If p.CenterX > 450 Then walk2 = -4 p.StartAnimation(p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_LEFT, p.ANIM_ACTION_A) End If Else If p.CenterX < 30 Then walk2 = 4 p.StartAnimation(p.ANIM_STATE_MOVE, p.ANIM_DIRECTION_RIGHT, p.ANIM_ACTION_A) End If End If Dim l As List Dim l2 As List Dim i As Int Dim p As ABParticle Dim jo As ABJoint Dim g As ABGroup Dim l3 As List Dim srcR As Rect Dim tgtR As Rect Dim remove As List remove.Initialize c.DrawRect(r, Colors.Black, True, 10dip) l = ph.GetGroups() For i = 0 To l.Size - 1 g = l.Get(i) ' if you want to draw the joints, do it like this l2 = g.GetJoints() For j = 0 To l2.Size - 1 jo = l2.get(j) c.DrawLine(jo.Part1.CenterX*scale, jo.Part1.CenterY*scale, jo.Part2.CenterX*scale, jo.Part2.CenterY*scale, Colors.gray, 2dip) Next ' draw the particles l2 = g.GetParticles() For j = 0 To l2.Size - 1 p = l2.get(j) If p.Type = p.CIRCLE Then If p.isAnimated = False Then srcR.Initialize(0,0, 60, 60) tgtR.Initialize((p.CenterX - p.Width / 2 ) * Scale, (p.CenterY - p.Height / 2 ) * Scale, (p.CenterX + p.Width / 2 ) * Scale, (p.CenterY + p.Height / 2 ) * Scale ) Select p.Name Case "p1" c.DrawBitmapRotated(bm, srcR, tgtR, P.Angle) Case "p2" c.DrawBitmapRotated(bm4, srcR, tgtR, P.Angle) Case "p3" c.DrawBitmapRotated(bm6, srcR, tgtR, P.Angle) End Select Else srcR.Initialize(0,0, p.Width, p.height) tgtR.Initialize((p.CenterX - p.Width / 2 ) * Scale, (p.CenterY - p.Height / 2 ) * Scale, (p.CenterX + p.Width / 2 ) * Scale, (p.CenterY + p.Height / 2 ) * Scale) c.DrawBitmapRotated(p.GiveCurrentAnimBitmap, srcR, tgtR, P.Angle) End If ' if its having a collision, mark it as red l3 = p.GetHits() If l3.size > 0 Then c.DrawCircle(p.CenterX*Scale, p.CenterY*Scale, p.Radius*Scale, Colors.Red, True, 1dip) End If Else srcR.Initialize(0,0, 700, 10) tgtR.Initialize((p.CenterX - p.Width / 2 ) * Scale, (p.CenterY - p.Height / 2 ) * Scale, (p.CenterX + p.Width / 2 ) * Scale, (p.CenterY + p.Height / 2 ) * Scale) c.DrawBitmapRotated(bm2, srcR, tgtR, P.Angle) End If If p.CenterY > 800 Then remove.Add(p) End If Next For j = 0 To remove.Size - 1 bal.RemoveParticle(remove.Get(j)) Next Next panel1.Invalidate End Sub
When we touch the panel, we do want to add a new ball. By using the Rnd function, we’ll make sure we get another type of ball. Once created, we add them to the group ‘bal’:
bal.AddParticle(p1)
Sub panel1_Touch (Action As Int, X As Float, Y As Float) If Action = Activity.ACTION_UP Then Dim p1 As ABParticle Dim rand As Int rand = Rnd(0,3) Select rand Case 0 p1 = buildParticle(p.CIRCLE, x*(1/Scale), 15, 30, 30, 15, True, True, 0, 0.1, 0 ,1, False, "p1") p1.AutoRotate = True Case 1 p1 = buildParticle(p.CIRCLE, x*(1/Scale), 15, 30, 30, 15, True, True, 0, 0.5, 0 ,1, False, "p2") p1.AutoRotate = True Case 2 p1 = buildParticle(p.CIRCLE, x*(1/Scale), 15, 40, 40, 20, True, True, 0, 0.7, 0 ,1, False, "p3") p1.AutoRotate = True End Select bal.AddParticle(p1) End If End Sub
Here are some help functions to make it easier to add groups and joints.
Sub buildParticle(TypeP As Int,centerX As Double, centerY As Double, Width As Double, Height As Double, Radius As Double, Collidable As Boolean,seeCollisionAsHit As Boolean, rotation As Double, elasticity As Double, friction As Double, mass As Double, isFixed As Boolean, Name As String) As ABParticle Dim p As ABParticle p.Initialize(TypeP,centerX,centerY,width,height,radius,collidable, seeCollisionAsHit, rotation,name) p.Elasticity = elasticity p.Friction = friction p.isFixed = isfixed p.Mass = mass Return p End Sub Sub buildJoint(p1 As ABParticle, p2 As ABParticle, name As String, stiffness As Double) As ABJoint Dim j As ABJoint j.Initialize(p1,p2, name, stiffness) Return j End Sub Sub addGroups(a() As ABGroup) Dim l As List l.Initialize2(a) ph.AddListOfGroups(l) End Sub
And that’s it! We have our first game. You can download the full B4A project from the link at the end of this article. I would suggest you play around with it and maybe, just maybe you’ll the one that makes the new ‘Angry Birds! 🙂
Good luck and have fun programming!
Full source code and pictures: http://www.gorgeousapps.com/Game2.zip
Thanks a lot.
You rule!
Would this work along with the gameview library to give accelerated 2D graphics drawing using this engine?
This would then work really well for a smooth 2d physics game using Android 3.0 upwards.
It’s a long time since I played with it, but I think it could. From what I recall, the engine just does physics calculations, no drawing stuff. Once the ‘ph.Step()’ is called in the ‘tim_Tick’ sub, you can then use whatever graphics system you want. e.g. In the above example I used standaard B4A commands to do the drawing.
Hi, I only came across B4A yesterday. I have VB experience but no C etc so was a bit daunted by Eclipse. I can more or less follow the code but couple of things escape me.
1) when setting up the ground AddParticle, which is ground1?
2) how is the game started? and where are the pause, resume (and exit?) buttons located.
Sorry is this is very basic but I haven’t downloaded the B4A software yet and so haven’t tried your game out.
Thanks in advance.
David
Hi David,
It has been a long time since I worked on this project, but I think I splitted Ground1 and Ground2 up because some ‘yellow/gray’ lines where solid and others not. (Check out the buildParticle() function to locate every part).
There are no start/resume/exit buttons. A timer starts listening for a touch on the screen. This happens in the Activity_Create. The Subs Activity_Resume() and Activity_Pause (UserClosed As Boolean) are used to stop the timer if a user uses the Android Home button. You will exit when you press the Android Back button.
I remember it had a logic when I wrote it at the time but it may take some time getting stated with this library. 🙂
Thanks for the prompt reply, I’ve been looking through the code again its kind of making sense. Not sure I understand the collision steps yet. I won’t bother you with loads of questions as I will get the software and try the program. One last question though, when I download the code from your link, save it to C: drive and want to load it into B4A, which file do I select (anlagous to File, Open, **.doc in Word) to load all the code? (You can see I’m new to this and a bit blind without the software!)
Thanks again, think I’m going to have a lot of fun with this! 🙂
It is the .b4a file 🙂
Oh that’s just plain embarrassing! Guess the clue was in my question really! Sorry for asking such a stuupid question! Hopefully I’ll ask the Forum more intelligent ones in the future! Thanks for taking pity! 🙂