Let’s get physical with Basic4Android

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

8 thoughts on “Let’s get physical with Basic4Android

  1. 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.

    1. 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.

  2. 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

    1. 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. 🙂

      1. 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! 🙂

          1. 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! 🙂

Leave a comment