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