The Tb2Body has a property called UserData which is a Pointer. You can assign your own objects or data to the UserData property (like a TRectangle) and then access them later. Here is a simple example of how to do that in code:
var
sd: Tb2PolygonShape;
bd: Tb2BodyDef;
body: Tb2Body;
begin
[...]
body := m_world.CreateBody(bd, False);
body.CreateFixture(sd, 5.0, False);
body.UserData := TRectangle.Create(nil);
TRectangle(body.UserData).Parent := WorldObject;
TRectangle(body.UserData).Position.X := body.GetPosition.x;
TRectangle(body.UserData).Position.Y := body.GetPosition.y;
TRectangle(body.UserData).Width := 22;
TRectangle(body.UserData).Height := 22;
What this allows you to do is mirror the position of a Box2d body as a Firemonkey object like a TRectangle. I used a bit of a hack and use the OnPaint event of the TPaintBox in the project to move all of the objects like the TRectangles to keep them in sync with the Box2d bodies. You should probably use a game loop instead however. The drawing of the TPaintBox is actually turned off (change Settings.drawShapes to True if you want to see the Box2d bodies drawn).
For detecting collisions and the strength of collisions between Box2d objects I followed this tutorial and some source code from the other Box2d Firemonkey demos to create a TMyb2ContactListener class that has two event methods. The first one is BeginContact(var contact: Tb2Contact) and the second one is PostSolve(var contact: Tb2Contact; const impulse: Tb2ContactImpulse). BeginContact works if you just need to know when collisions happened and I originally started with this function. However, if you need to know the strength of collisions you use PostSolve(). First I check to see if the two bodies that collided were bodies that I want to take action on and then I use the TagFloat property to record how many times the bodies have collided. In my game loop (in this case the OnPaint event) is where I count up the damage each body has and remove it. Here is the PostSolve() event code (you will notice how I use the UserData property here to interface with my Firemonkey objects):
procedure TMyb2ContactListener.PostSolve(var contact: Tb2Contact; const impulse: Tb2ContactImpulse);
const MAX_IMPULSE:Integer = 100;
var
bodyA, bodyB: Tb2Body;
begin
if (impulse.normalImpulses[0]>MAX_IMPULSE) then
begin
bodyA := contact.m_fixtureA.GetBody;
bodyB := contact.m_fixtureB.GetBody;
if Assigned(bodyA) AND Assigned(BodyB) then
if (bodyB.UserData<>nil) AND (bodyA.UserData<>nil) then
begin
if (TFmxObject(bodyB.UserData) is TCircle) AND (TFmxObject(bodyA.UserData) is TRectangle) then
begin
if TRectangle(bodyA.UserData).Tag=2 then
TRectangle(bodyA.UserData).TagFloat := TRectangle(bodyA.UserData).TagFloat+5;
end
else if (TFmxObject(bodyB.UserData) is TRectangle) AND (TFmxObject(bodyA.UserData) is TRectangle) then
begin
if (TRectangle(bodyA.UserData).Tag=2) then
TRectangle(bodyA.UserData).TagFloat := TRectangle(bodyA.UserData).TagFloat + 1;
end;
end;
end;
end;
Finally, I used TFrames to create the start screen, instruction screen, and game over screen. They are set to design time visible false. This keeps them separate which makes them much easier to use. For the sound I use the Windows API but it has a TMediaPlayer for other platforms (or if you want to use each platform’s native play sound API you could). For the music I used a TMediaPlayer. The popping sound and the music are from http://www.freesfx.co.uk/.
I set this project up to use Win64 as that has the best speed at handling the Box2d Firemonkey calculations but Win32 will also work fine. I built this demo for Windows and OSX desktop but it would run on Android and IOS with some changes to the layout of the project. On a tablet it would probably run fine without any layout changes.
Download the full source code for the Box2d Firemonkey Topple The Tower Prototype Demo.