Games [XUI2D] Units and Scales

Erel

B4X founder
Staff member
Licensed User
Longtime User
There are all kinds of units and scales in XUI2D. It is important to understand them and use them consistently.

XUI2D (like Box2D) is based on the metric system.
- Length is measured in meters.
- Velocity is measured in meters / second.
- Mass is measured in kilograms.
- Acceleration is measured in meters / second ^ 2

No pixels and no DIP units.


The worlds coordinates go from left to right and from the bottom to the top, unlike the screen coordinates that go from the top to the bottom.

The screen dimensions are configured in Game.Initialize:
B4X:
X2.ConfigureDimensions(world.CreateVec2(0, 3.5), 8)
The first parameter is the coordinate of the screen center. X = 0 meters and y = 3.5 meters.
The second parameter is the width length. It is 8 meters here.

If we run the hello world example with width set to 30 meters then it will look like this:

SS-2018-07-19_10.53.16.png


Note that changing the dimensions will not affect the already loaded graphics.

You can change the world center with X2.UpdateWorldCenter.

When you load image files with X2.LoadBmp you set their width and height (in meters). This is the simplest way and in many cases the best way to create the bodies graphics.

Two other common ways to create the graphics are with B4XCanvas or BitmapCreator.

Creating the a box with specific size based on BitmapCreator:

SS-2018-07-19_11.05.54.png


B4X:
BoxSize = X2.CreateVec2(0.5, 1)
Dim bc As BitmapCreator
bc.Initialize(X2.MetersToBCPixels(BoxSize.X), X2.MetersToBCPixels(BoxSize.Y))
bc.FillGradient(Array As Int(xui.Color_White, xui.Color_Black), bc.TargetRect, "TL_BR")
Dim sb As X2ScaledBitmap
sb.Bmp = bc.Bitmap
sb.Scale = 1
X2.GraphicCache.PutGraphic("block", Array(sb))
The important method here is X2.MetersToBCPixels. It converts the length measured in meters to "BCPixels".

Creating the box with B4XCanvas:
To avoid creating a new B4XCanvas we can get one of the canvases used by GraphicCache.
These canvases are square sized.

1. Get the canvas:
B4X:
Dim CvsMinSize As Int = X2.MetersToBCPixels(Max(BoxSize.X, BoxSize.Y))
Dim Cvs As B4XCanvas = X2.GraphicCache.GetCanvas(CvsMinSize / X2.BmpSmoothScale)
We need to divide the requested size with X2.BmpSmoothScale. This scale is used internally for antialiasing of rotated graphics.

2. Draw:
B4X:
Dim r As B4XRect
r.Initialize(0, 0, X2.MetersToBCPixels(BoxSize.X) + 1, X2.MetersToBCPixels(BoxSize.Y) + 1)
Cvs.ClearRect(r)
Cvs.DrawRect(r, xui.Color_Black , True, 0)
Cvs.DrawCircle(r.CenterX, r.CenterY, X2.MetersToBCPixels(0.15), xui.Color_White, True, 0)
Again we use MetersToBCPixels to convert the lengths.

3. Get the bitmap and add it to the cache:
B4X:
Dim sb As X2ScaledBitmap
sb.Bmp = Cvs.CreateBitmap.Crop(0, 0, r.Right, r.Bottom)
sb.Scale = 1
X2.GraphicCache.PutGraphic("block", Array(sb))
Cvs.CreateBitmap returns the internal mutable bitmap in B4A. As call Crop here it doesn't really matter as we get a copy of the bitmap. However if we weren't calling Crop then we should have made a copy with:
B4X:
sb.Bmp = X2.CreateImmutableBitmap(Cvs.CreateBitmap)

SS-2018-07-19_11.25.50.png


B4X:
Dim CvsMinSize As Int = X2.MetersToBCPixels(Max(BoxSize.X, BoxSize.Y))
Dim Cvs As B4XCanvas = X2.GraphicCache.GetCanvas(CvsMinSize) 'not divided by X2.BmpSmoothScale
Dim scale As Float = X2.mBCPixelsPerMeter * X2.BmpSmoothScale
Dim r As B4XRect
r.Initialize(0, 0, BoxSize.X * scale, BoxSize.Y * scale)
Cvs.ClearRect(r)
Cvs.DrawRect(r, xui.Color_Black , True, 0)
Cvs.DrawCircle(r.CenterX, r.CenterY, 0.15 * scale, xui.Color_White, True, 0)
Dim sb As X2ScaledBitmap
sb.Bmp =  Cvs.CreateBitmap.Crop(0, 0, r.Right, r.Bottom)
sb.Scale = X2.BmpSmoothScale '<-----
X2.GraphicCache.PutGraphic2("block", Array(sb), True, 5)
SS-2018-07-19_11.34.01.png


Time Units

It takes some time to get used to thinking in meters instead of pixels. The same is true to time units. It is tempting to count frames or game loop iterations. This is a mistake as both these measurements are not consistent.

The game time is measured in milliseconds. X2.TimeStepMs returns the duration of the last iteration.

The value of X2.TimeStepMs is determined by X2.TargetFPS. By default X2.TargetFPS is set to 60 in release mode and 30 in debug mode. Note that it isn't affected by the actual number of frames per second (when needed the engine make drawings every two iterations).

You can also slow down the game or make it run faster by setting X2.SlowDownPhysicsScale. Make sure to call X2.UpdateTimeParameters after you change it.

The physical engine works with seconds.
 
Last edited:

Erel

B4X founder
Staff member
Licensed User
Longtime User
At the end of each iteration a bitmap is created and is set to the ImageView (ivForeground). The bitmap size can be different from the ImageView size.
In most cases you don't need to be bothered about it and just let ImageView do its work and scale it.

One case where it is important is when you want to convert the user touch point or mouse point to the world coordinates.
Fortunately it is very simple to do:

Tip: Pane(l) Touch event was added in B4J v6.30 so you can use this event in all platforms (the action constants are available in B4XView).
B4X:
Sub Panel1_Touch (Action As Int, X As Float, Y As Float)
   Dim vec As B2Vec2 = X2.ScreenPointToWorld(X, Y)
   Label1.Text = $"($1.1{vec.X},$1.1{vec.y})"$
End Sub

An example is attached. The label shows the current mouse position coordinates. It also shows how to check for bodies in that position.
 

Attachments

  • HelloWorld2.zip
    35.8 KB · Views: 425
Last edited:

sorex

Expert
Licensed User
Longtime User
BCPixelsToMeters is handy but it doesn't seems to work as long as you didn't define your world yet? (see below)

Let's say I have a 32x32px image and want to place 10 of them next to each other to cover full screen width how can you calculate the optimal world width?

Or should I scale them as full screen world meters / 10 in some way?

B4X:
'works
X2.ConfigureDimensions(world.CreateVec2(2.5, 0),5)
Log(X2.BCPixelsToMeters(32)*10)

LOG: 3.3333334922790527

'fails and logs infinite
X2.ConfigureDimensions(world.CreateVec2(2.5, 0),X2.BCPixelsToMeters(32*10))
Log(X2.BCPixelsToMeters(32)*10)

LOG: infinite
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
It cannot work before you call ConfigureDimensions as this call determines the dimensions.

Let's say I have a 32x32px image and want to place 10 of them next to each other to cover full screen width how can you calculate the optimal world width?
Set the width to any value that makes sense to your app. It doesn't really matter.

Use X2.LoadBmp to load the images and set their size based on the screen dimensions.
B4X:
X2.LoadBmp(..., ..., X2.ScreenAABB.Width / 10, X2.ScreenAABB.Height / 10)

You can later get the image dimensions with X2.GraphicCache.GetGraphicSizeMeters. Or calculate it with X2.mBCPixelsPerMeter (see GetGraphicSizeMeters code).
 

sorex

Expert
Licensed User
Longtime User
too bad the x2.loadBmp uses a different type then the one used for that loadSprites routine.

I used that routine from your example and set the frame count to select the right image (it's a 7x1 tile sheet)

I'll do some more research.
 
Top