5

I want to add colors to dynamic objects in Box2D. It seems like some properties of dynamic objects have to be assigned in the rendering of the scene. I have played around with the Box2D demo but can't figure out how to add properties like colors (and bitmaps) ?

Flemming
  • 694
  • 7
  • 22

1 Answers1

4

The Box2d demo that ships with SmartMS uses the "DebugDraw" feature of Box2d. This feature just paints a representation of the object with some internal colors for "static object", "moving object" and "still object". I'm actually not sure if it is possible to override these...

What you need to do, is to replace FWorld.DrawDebugData (in the PaintView method) with the custom painting you'd like to use.

You can traverse all object like this:

  Body := FWorld.GetBodyList;  //Will give you first object in list

  while Assigned(Body) do
  begin
    //Do something with "Body" here...

    Body := Body.GetNext;  //Will give you next object in list
  end;

For instance:

  i := 0;
  Canvas.Font := '10pt verdana';
  Canvas.FillStyle := 'rgb(255,255,255)';
  Canvas.FillTextF('Count:' + IntToStr(FWorld.GetBodyCount), 30, 40, MAX_INT);

  Body := FWorld.GetBodyList;
  while Assigned(Body) do
  begin
    Canvas.FillTextF('i:' + IntToStr(i),                    30, 60+(i*12), MAX_INT);
    Canvas.FillTextF('Y:' + FloatToStr(Body.Position.Y, 2), 70, 60+(i*12), MAX_INT);

    Body := Body.GetNext;
    Inc(i);
  end;

To paint a sprite for various objects, you should use a TW3SpriteSheets class to handle that.

Add the sprite sheet image as a resource file to the project, and it will be extracted to the res folder when you compile.

Load the image like this:

  FSpriteSheet := TW3SpriteSheet.Create;
  FSpriteSheet.LoadImage('res/MySpriteSheet.png');

And use the image like this:

  if FSpriteSheet.Ready then
  begin
    FSpriteSheet.Draw(Canvas, 100, 100, 1); // draw tile #1
    FSpriteSheet.Draw(Canvas, 133, 100, 2); // draw tile #2
    FSpriteSheet.Draw(Canvas, 166, 100, 3); // draw tile #3
  end;

Edit:

In the sprite sheet, you have all the rotations need of your object.

enter image description here
(Image from: http://gameonaut.com/wordpress/2011/11/flixel-demo-creating-animated-and-rotated-sprites-from-an-un-rotated-animated-images/)

enter image description here
(Image from; http://windowsphone7developerguide.blograby.com/more-sprite-transforms-rotation-and-scaling/)

The Angle you get from Box2d is in radians. Use the build int function RadToDeg to get the angle as 0-360.

Canvas.FillTextF('A:' + FloatToStr(Body.Angle, 2),               140, 60+(i*12), MAX_INT);
Canvas.FillTextF('A:' + FloatToStr(RadToDeg(Body.Angle), 0),     220, 60+(i*12), MAX_INT);

The angle can be more than 360 degrees, and you would only like to know the value from 0 to 360. Thus use MOD:

 SpriteAngle := Round(RadToDeg(Body.Angle)) MOD 360;

Further the angle can be negative, so you would need to turn -1 into 359:

 if SpriteAngle < 0 then
    SpriteAngle := SpriteAngle + 360;

Finally. Since 360 degrees is the same as 0 degrees, you would like to use 0:

 if SpriteAngle = 360 then
    SpriteAngle := 0;

Now we have an angle from 0 to 359 degrees, and we would like to pick the right sprite based on the angle. If you have 32 sprites in your sprite sheet, then you will have 360/32 = 11.25 degrees per sector. So, for the first sector (0 to 11.25) we would like to use sprite 0, for the next sector (11.25 to 22.5) we would use the second sprite etc. By using Trunc we will get the sprite number we are looking for. 359/11.25 => 31.911 => 31

  FSpriteSheet.Draw(Canvas, X, Y, Trunc(SpriteAngle/11.25)); 

In this screenshot I used the sprite sheet from this article (http://www.codeproject.com/Articles/9012/Rotating-Sprite-Objects-on-DirectDraw-Wrapper-for). It has 101 sprites, and it is very smooth.
(Debugdata: i, Body.Position.Y, Body.Angle, RadToDeg(Body.Angle), Final 0-359 angle, SpriteID)

enter image description here
(Click to play screen cast)


Edit 2:

A few notes about X,Y and using the second half of the sprite sheet...

To avoid a mess, I left out X and Y from the previous code line.

Setting X and Y is basically:

X := Body.Position.X * CScale;
Y := Body.Position.Y * CScale;

However! If you run this with debugdraw, you'll notice that Body.Position.X and Body.Position.Y refers to the center of the Box2d object, while FSpriteSheet.Draw(Canvas, X, Y, ... refers to the top, left corner. You will therefor need to make a minor adjustment:

X := Round(Body.Position.X * CScale) - FSheet.SpriteHalfWidth;
Y := Round(Body.Position.Y * CScale) - FSheet.SpriteHalfHeight;

enter image description here enter image description here


An how to pick a sprite from the second half of the sprite sheet?

Well, it's pretty easy ;-)

If you want to use images from the second half, you need to start the 0-indexed counting from the first sprite in the second set. There are 112 images in each set. First set = 0..111, second set = 112..223. So, when you want to reach the first set, use 0 + SpriteId. When you want to pick sprites from the second set, use 112 + SpriteId. And so on if you have more sets.

In my example, I used the second set for every second object. By using i MOD 2, every odd number would be 1, and every even number would be 0.

Thus, this code would result in 0 for every even number and 112 for every odd number:

SpriteSetStart := (i MOD 2) * 112;

Then we add the reference to the sprite id. In my example with 101 sprites (and 11 blank spites), each sprite would cover a sector of 3.564 degrees. 360/101 => 3.564.

SpriteId := Trunc(SpriteAngle/3.564);

Finally:

FSpriteSheet.Draw(Canvas, X, Y, SpriteSetStart + SpriteId); 
Jørn E. Angeltveit
  • 3,029
  • 3
  • 22
  • 53
  • It works but the canvas drawing is extremely slow. The sprite drawing on the contrary is very fast, but it doesn't draw rotated ? – Flemming Mar 13 '14 at 07:42
  • What's the framerate? Full redraw of screen can be cumbersome if you have a large canvas area. In these situations you'd need to redraw just the portions of the screen that needs to be repainted. The current demo starts with a full clear-screen command, and redraws everything. It the FPS-rate still slow if you switch to iPhone layout? – Jørn E. Angeltveit Mar 13 '14 at 10:28
  • Rotating object: The objects are rotated in the sprite sheet, thus you'd only need to select the right sprite to get the correct rotation. (http://windowsphone7developerguide.blograby.com/more-sprite-transforms-rotation-and-scaling/). I'll add some more code to the answer... – Jørn E. Angeltveit Mar 13 '14 at 10:32
  • Thank you for walking me through this :-) The spritesheet you use contains two different objects. How do you select from the second part of the sheet ? Btw. you missed the X:=Body.Position.X*CScale; Y:=Body.Position.Y*CScale; – Flemming Mar 13 '14 at 15:32
  • Damn ! I got a "ReferenceError, Box2D is not defined" error when trying to run it on external server or as iphone app. I got the error with the featured demo, even without making any changes to the demo version !? – Flemming Mar 23 '14 at 23:27
  • Have you copied the box2d JS Library into Your RES folder? – Jon Lennart Aasenden Jan 07 '15 at 12:41
  • I should add, it would be a lot faster to NOT Draw stuff, but rather use the values directly on real elements (DIV's), applying x,y position and rotation. Mixing box2d With sprite2d (unit) should produce some spectacular results – Jon Lennart Aasenden Jan 07 '15 at 12:42