Games [XUI2D] Units and Scales

Discussion in 'Game Development' started by Erel, Jul 19, 2018.

  1. Erel

    Erel Administrator Staff Member Licensed 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:
    Code:
    X2.ConfigureDimensions(world.CreateVec2(03.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:

    [​IMG]

    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:

    [​IMG]

    Code:
    BoxSize = X2.CreateVec2(0.51)
    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:
    Code:
    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:
    Code:
    Dim r As B4XRect
    r.Initialize(
    00, X2.MetersToBCPixels(BoxSize.X) + 1, X2.MetersToBCPixels(BoxSize.Y) + 1)
    Cvs.ClearRect(r)
    Cvs.DrawRect(r, xui.Color_Black , 
    True0)
    Cvs.DrawCircle(r.CenterX, r.CenterY, X2.MetersToBCPixels(
    0.15), xui.Color_White, True0)
    Again we use MetersToBCPixels to convert the lengths.

    3. Get the bitmap and add it to the cache:
    Code:
    Dim sb As X2ScaledBitmap
    sb.Bmp = Cvs.CreateBitmap.Crop(
    00, 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:
    Code:
    sb.Bmp = X2.CreateImmutableBitmap(Cvs.CreateBitmap)
    [​IMG]

    Code:
    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(
    00, BoxSize.X * scale, BoxSize.Y * scale)
    Cvs.ClearRect(r)
    Cvs.DrawRect(r, xui.Color_Black , 
    True0)
    Cvs.DrawCircle(r.CenterX, r.CenterY, 
    0.15 * scale, xui.Color_White, True0)
    Dim sb As X2ScaledBitmap
    sb.Bmp =  Cvs.CreateBitmap.Crop(
    00, r.Right, r.Bottom)
    sb.Scale = X2.BmpSmoothScale 
    '<-----
    X2.GraphicCache.PutGraphic2("block"Array(sb), True5)
    [​IMG]


    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: Jul 19, 2018
  2. Erel

    Erel Administrator Staff Member Licensed 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).
    Code:
    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.
     

    Attached Files:

    Last edited: Jul 19, 2018
  3. sorex

    sorex Expert Licensed 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?

    Code:
    'works
    X2.ConfigureDimensions(world.CreateVec2(2.50),5)
    Log(X2.BCPixelsToMeters(32)*10)

    LOG3.3333334922790527

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

    LOG: infinite
     
  4. Erel

    Erel Administrator Staff Member Licensed User

    It cannot work before you call ConfigureDimensions as this call determines the dimensions.

    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.
    Code:
    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).
     
  5. sorex

    sorex Expert Licensed 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.
     
  6. Erel

    Erel Administrator Staff Member Licensed User

    It should be very similar with X2.ReadSprites. You pass the Width and Height of each of the sprites.
     
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice