Let’s get physical with Basic4Android (original) (raw)

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

Click here to Donation if you like my work