Games [x2][solved] Help needed regarding creating object clones

fredo

Well-Known Member
Licensed User
This is a follow-up request to https://www.b4x.com/android/forum/threads/x2-solved-help-needed-regarding-object-collision.108455/

The enclosed test project is supposed to be a turn-based top down RPG board game for 2 characters.
Planned course: Players shall roll their dice alternately and advance the corresponding number of fields, sometimes choosing which way to go.

Since the interaction of the X2 framework components has not yet been sufficiently understood, concrete references to the required "best practices" would be needed.

The x2 examples were used to create the basic structures and load the graphics.

B4X:
'
' Java source --> file:///.\Objects\src\b4j\gametest\sn\game.java
'
' ------- -------------- ------
'#CustomBuildAction: folders ready, %WINDIR%\System32\Robocopy.exe,"..\..\Shared Files" "..\Files"
' This is required to copy all files from the "Shared Files" folder to the "Files" folder of the respective platform.
'     Erel --> https://www.b4x.com/android/forum/threads/xui2d-cross-platform-tips.96815/#content
' ------- -------------- ------
' ------- -------------- ------
' X2Test1 "Basic structures"
'     x2notes    --> https://www.b4x.com/android/forum/threads/some-notes-to-better-understand-the-xui2d-examples.107702/
'     x2examples --> https://www.b4x.com/android/forum/threads/xui2d-example-pack.96454/
'   @Gunter’s roadmap into the game dev world --> https://www.b4x.com/android/forum/threads/video-tutorial.99172/#post-624377
' ------- -------------- ------
#if B4A
'ignore DIP related warnings as they are not relevant when working with BitmapCreator.
#IgnoreWarnings: 6
#end if
Sub Class_Globals
    '
    Private ISDEBUG As Boolean = False ' It's easier to change it here instead of find it further down
    '
    ' Collision filtering
    ' Gunther --> https://www.b4x.com/android/forum/threads/tool-collision-bits-and-masks-calculator-collision-filtering.103320/
    Public const COLL_CATEGORY_PLAYER As Int  = 0x0001 ' 0000000000000001 in binary
    Public const COLL_CATEGORY_MONSTER As Int = 0x0002 ' 0000000000000010 in binary
    Public const COLL_CATEGORY_SCENERY As Int = 0x0004 ' 0000000000000100 in binary
 
    Public const COLL_MASK_PLAYERHITS_MONSTERORSCENERY As Int  = 0xFFFE ' 1111111111111110 in binary
    Public const COLL_MASK_MONSTERHITS_PLAYER As Int           = 0xFFF9 ' 1111111111111001 in binary
    Public const COLL_MASK_SCENERYHITS_PLAYER As Int           = 0xFFF9 ' 1111111111111001 in binary
 
    ' The group index can be used to override the category/mask settings for a given set of fixtures.
    ' --> https://www.iforce2d.net/b2dtut/collision-filtering
    Public const COLL_GROUP_PLAYER As Int = -1
    Public const COLL_GROUP_MONSTER As Int = -2
    Public const COLL_GROUP_SCENERY As Int = 1
 
    ' Basic x2 libraries that are needed in every game
    Public  xui As XUI 'ignore
    Public X2 As X2Utils
    Public world As B2World
 
    ' TileMap specific definitions
    Public TileMap As X2TileMap
    Public Const ObjectLayer As String = "Object Layer 1"
 
    ' ExtraClasses
    Private mChar1 As Figure
    Private mChar2 As Figure
 
    ' Parameters
    Public const MotorCorrFacInitVal As Float = 0.03
    Public const MotorCorrFacNormal As Float = 0.07
    Public const MotorCorrFacEnd As Float = 0.06
 
    ' Gamestate
    Private GameOverState As Boolean = False
    Private CharIsMoving As Boolean = False
    Private ActiveCharIndex As Int = 1
    Private LastDiceRollVal As Int = 0
    Private CharLastPositionIndex(3) As Int
    Type PointXy(x As Float, y As Float)
    Private Char1Points As List
    Private Char2Points As List
 
    ' Bodies and joints
    Private Border As X2BodyWrapper
    Private MotorChar1 As B2MotorJoint
    Private MotorChar2 As B2MotorJoint
 
    Private PathMainBCForward As BCPath
    Private PathMainBCBackwards As BCPath
    Private PathWorld As List
'    Private BrushRed As BCBrush
 
 
    ' Templates
    Type FigureTemplates(Template As X2TileObjectTemplate, XPosition As Float)
    Private FigureTemplatesList As List
 
    Type PointTemplates(Template As X2TileObjectTemplate, XPosition As Float)
    Private PointTemplatesList As List
    Private PointTemplatesListLoop0 As List
 
    Type TextTemplates (Template As X2TileObjectTemplate, XPosition As Float)
    Private TextTemplatesList As List
 
    Type LocationTemplates(Template As X2TileObjectTemplate, XPosition As Float)
    Private LocationTemplatesList As List
 
    ' Layout elements
    Private ivForeground As B4XView
    Private ivBackground As B4XView
    Public lblStats As B4XView
    Private pnlTouch As B4XView
    Private RadioButton1 As RadioButton' ####testingonly
    Private RadioButton2 As RadioButton' ####testingonly
    Private Button1 As Button' ####testingonly
    Private Label2 As Label' ####testingonly
    Private Pane1 As Pane' ####testingonly
    Private Pane2 As Pane' ####testingonly
    Private fx As JFX' ####testingonly
    Private TempTestPane As Pane' ####testingonly
    Private Label5 As Label' ####testingonly
'    Private TextArea1 As TextArea ' Restitution
'    Private TextArea3 As TextArea ' Friction
'    Private TextArea4 As TextArea ' Density
'    Private TextArea5 As TextArea ' Category bits
'    Private TextArea6 As TextArea ' Mask bits
'    Private TextArea7 As TextArea ' mcfA
'    Private TextArea8 As TextArea ' mcfB
    Private Label4 As Label ' coll pl1
    Private Label12 As Label ' coll pl2
End Sub
Public Sub Initialize (Parent As B4XView)
    '
    ' ──────────────────────────────────
    ' Preparations for Layout and Views
    ' ──────────────────────────────────
    Parent.LoadLayout("GameLayout")
    lblStats.TextColor = xui.Color_Black
    lblStats.Color = 0x88ffffff
    lblStats.Font = xui.CreateDefaultBoldFont(20)
 
    CSSUtils.SetBackgroundColor(Pane1, fx.Colors.LightGray)' ####testingonly
    CSSUtils.SetBackgroundColor(Pane2, fx.Colors.LightGray)' ####testingonly
 
    ' ──────────────────────────────────
    ' Preparations for World
    ' ──────────────────────────────────
    world.Initialize("world", world.CreateVec2(0, 0))
    X2.Initialize(Me, ivForeground, world)
 
    Dim WorldWidth As Float = 36 'meters
    Dim WorldHeight As Float = WorldWidth / 1 'same ratio as in the designer script (in GameLayout.bjl)!!!
    X2.ConfigureDimensions(world.CreateVec2(WorldWidth / 2, WorldHeight / 2), WorldWidth)
 
 
'    TextArea8.Text = MotorCorrFacNormal ' ####testingonly
'    TextArea7.Text = MotorCorrFacEnd ' ####testingonly
 
    ' ──────────────────────────────────
    ' Preparations for movements
    ' ──────────────────────────────────
    PathMainBCForward.Initialize(0, 0)
    PathMainBCBackwards.Initialize(0, 0)
    PathWorld.Initialize
'    BrushRed = X2.MainBC.CreateBrushFromColor(xui.Color_Red)
    CharLastPositionIndex(0) = -1 ' not used
    CharLastPositionIndex(1) = 0 ' for Char1
    CharLastPositionIndex(2) = 0 ' for Char2
 
    Char1Points.Initialize
    Char2Points.Initialize
 
    ' ──────────────────────────────────
    ' Preparations for graphics
    ' ──────────────────────────────────
    GraphicCache_Put_Objects
    GraphicCache_Put_Characters
 
 
    ' ──────────────────────────────────
    ' Preparations for screen
    ' ──────────────────────────────────
    'SetBackground
    CreateStaticBackground
    'CreateBorder
 
 
    ' ──────────────────────────────────
    ' Preparations for sounds
    ' ──────────────────────────────────
    X2.SoundPool.AddSound("rolldice", File.DirAssets, "dice-4.wav")
    X2.SoundPool.AddSound("stepstone_1", File.DirAssets, "stepstone_1.wav")
    'X2.SoundPool.AddSound("metal-clash", File.DirAssets, "metal-clash.wav")
    X2.SoundPool.AddSound("Pickup__008", File.DirAssets, "Pickup__008.wav")
 
 
    ' ──────────────────────────────────
    ' Debug settings
    ' ──────────────────────────────────
    If ISDEBUG Then
        X2.EnableDebugDraw    ' Comment out to disable debug drawing
    End If
 
End Sub
Private Sub SetWorldCenter
    ' The map size will not be identical to the screen size.
    ' This happens because the tile size in (bc) pixels needs to be a whole number.
    ' So we need to update the world center and move the map to the center.
    X2.UpdateWorldCenter(TileMap.MapAABB.Center)
End Sub
Public Sub WorldCenterUpdated (gs As X2GameStep)
    'CreateEnemies
End Sub
private Sub SetBackground
    'X2.SetBitmapWithFitOrFill(ivBackground, xui.LoadBitmapResize(File.DirAssets, "mybackgroundimage.jpg", ivBackground.Width / 2, ivBackground.Height / 2, False))
End Sub
Private Sub GraphicCache_Put_Objects
    Log("#-Sub game.GraphicCache_Put_Objects")
    '
    Dim RowWidthPixel As Int = 32
    Dim RowHeightPixel As Int = 32
    Dim CollGraphHeightMeters As Int = 2
 
    ' -------------------------------
    ' Step-pads
    ' -------------------------------
    Dim bc1 As BitmapCreator = X2.BitmapToBC(xui.LoadBitmap(File.DirAssets, "dungeon_tilesetmodrollo.png"), 1)
    Dim bmp As B4XBitmap = bc1.Bitmap
    Dim CollGraph1 As B4XBitmap = bmp.Crop(1 * 32, 18 * RowHeightPixel, 2 * RowWidthPixel, 2 * RowHeightPixel)
    Dim CollGraph1List As List = X2.ReadSprites(CollGraph1, 1, 1, CollGraphHeightMeters, CollGraphHeightMeters)
    X2.GraphicCache.PutGraphic("steppad_collision", Array(CollGraph1List.Get(0)) )
 
    Dim bc2 As BitmapCreator = X2.BitmapToBC(xui.LoadBitmap(File.DirAssets, "dungeon_tilesetmodrollo.png"), 1)
    Dim bmp As B4XBitmap = bc2.Bitmap
    Dim CollGraph1 As B4XBitmap = bmp.Crop(5 * 32, 18 * RowHeightPixel, 2 * RowWidthPixel, 2 * RowHeightPixel)
    Dim CollGraph1List As List = X2.ReadSprites(CollGraph1, 1, 1, CollGraphHeightMeters, CollGraphHeightMeters)
    X2.GraphicCache.PutGraphic("steppad_normal", Array(CollGraph1List.Get(0)) )
     
End Sub
Private Sub GraphicCache_Put_Characters
    Log("#-Sub game.GraphicCache_Put_Characters")
    ' -------------------------------
    ' Characters
    ' -------------------------------
    ' Use bitmap without the transparency placeholdercolor:
    Dim bc As BitmapCreator = X2.BitmapToBC( xui.LoadBitmap(File.DirAssets, "RPGCharacterSprites32x32.png"), 1)
    RemovePseudoTransparentColor(bc, "#ff00ff") ' pink, magenta
    Dim bmp As B4XBitmap = bc.Bitmap
    Dim NumberOfSprites As Int = 12
    Dim RowWidth As Int = 32
    Dim RowHeight As Int = 32
    Dim CharHeightMeters As Int = 3
    '
    Dim RowOfChar1 As Int = 2
    Dim character1 As B4XBitmap = bmp.Crop(0, RowHeight * RowOfChar1, NumberOfSprites * RowWidth, RowHeight)
    Dim AllChar1 As List = X2.ReadSprites(character1, 1, NumberOfSprites, CharHeightMeters, CharHeightMeters)
    X2.GraphicCache.PutGraphic("character1 front walking", Array(AllChar1.Get(0), AllChar1.Get(1), AllChar1.Get(2), AllChar1.Get(3)))
    X2.GraphicCache.PutGraphic("character1 front standing", Array(AllChar1.Get(3)))
    X2.GraphicCache.PutGraphic("character1 back walking", Array(AllChar1.Get(4), AllChar1.Get(5), AllChar1.Get(6), AllChar1.Get(7)))
    X2.GraphicCache.PutGraphic("character1 back standing", Array(AllChar1.Get(7)))
    X2.GraphicCache.PutGraphic("character1 side walking", Array(AllChar1.Get(8), AllChar1.Get(9), AllChar1.Get(10)) )
    X2.GraphicCache.PutGraphic("character1 side standing", Array(AllChar1.Get(9)) )
    '
    Dim RowOfChar2 As Int = 3
    Dim character2 As B4XBitmap = bmp.Crop(0, RowHeight * RowOfChar2, NumberOfSprites * RowWidth,  RowHeight)
    Dim AllChar2 As List = X2.ReadSprites(character2, 1, NumberOfSprites, CharHeightMeters, CharHeightMeters)
    X2.GraphicCache.PutGraphic("character2 front walking", Array(AllChar2.Get(0), AllChar2.Get(1), AllChar2.Get(2), AllChar2.Get(3)))
    X2.GraphicCache.PutGraphic("character2 front standing", Array(AllChar2.Get(3)))
    X2.GraphicCache.PutGraphic("character2 back walking", Array(AllChar2.Get(4), AllChar2.Get(5), AllChar2.Get(6), AllChar2.Get(7)))
    X2.GraphicCache.PutGraphic("character2 back standing", Array(AllChar2.Get(7)))
    X2.GraphicCache.PutGraphic("character2 side walking", Array(AllChar2.Get(8), AllChar2.Get(9), AllChar2.Get(10)) )
    X2.GraphicCache.PutGraphic("character2 side standing", Array(AllChar2.Get(9)) )
End Sub
Private Sub RemovePseudoTransparentColor(TilesBC As BitmapCreator, clrstring As String)
    ' Erel --> https://www.b4x.com/android/forum/threads/x2-how-to-use-transparent-color.108173/#post-676534
    Dim clr As Int = 0xff000000 + Bit.ParseInt(clrstring.SubString(1), 16)
    Dim ptranspm As PremultipliedColor
    Dim trans As PremultipliedColor
    Dim pm As PremultipliedColor
    Dim argb As ARGBColor
    TilesBC.ColorToARGB(clr, argb)
    TilesBC.ARGBToPremultipliedColor(argb, ptranspm)
    For y = 0 To TilesBC.mHeight - 1
        For x = 0 To TilesBC.mWidth - 1
            TilesBC.GetPremultipliedColor(x, y, pm)
            If Bit.And(0xff, pm.r) = ptranspm.r And Bit.And(0xff, pm.g) = ptranspm.g And Bit.And(0xff, pm.b) = ptranspm.b And Bit.And(0xff, pm.a) = ptranspm.a Then
                TilesBC.SetPremultipliedColor(x, y, trans)
            End If
        Next
    Next
End Sub
Private Sub PositionObjects
    '    For Each LocationTemplateX As LocationTemplates In LocationTemplatesList
    '        ' Initial position on location-objects (works ok)
    '        Select Case True
    '            Case LocationTemplateX.Template.Name.ToLowerCase.EndsWith("c1")
    '                mChar1.bw.Body.SetTransform(LocationTemplateX.Template.Position, 0)
    '            Case LocationTemplateX.Template.Name.ToLowerCase.EndsWith("c2")
    '                mChar2.bw.Body.SetTransform(LocationTemplateX.Template.Position, 0)
    '        End Select
    '    Next
End Sub
private Sub CreateObjects
    Log("#-Sub game.CreateObjects")
    FigureTemplatesList.Initialize
    PointTemplatesList.Initialize
    PointTemplatesListLoop0.Initialize
    TextTemplatesList.Initialize
    LocationTemplatesList.Initialize
    TempTestPane.RemoveAllNodes    ' ####testingonly
    Dim tempcounter_pt0 As Int = 0'####temp
    Dim ol As X2ObjectsLayer = TileMap.Layers.Get(ObjectLayer)
    For Each TileMapTemplateX As X2TileObjectTemplate In ol.ObjectsById.Values
        'Log("#-    x203, TileMapTemplateX.Name = " & TileMapTemplateX.Name)
     
        If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("p") Then
            Dim pt As PointTemplates
            pt.Template = TileMapTemplateX
            pt.XPosition = TileMapTemplateX.Position.X
            pt.Template.FixtureDef.SetFilterBits(COLL_CATEGORY_SCENERY, COLL_MASK_SCENERYHITS_PLAYER)
            PointTemplatesList.Add(pt)
         
            Dim bwobj As X2BodyWrapper = TileMap.CreateObject(TileMapTemplateX)
            bwobj.Graphicname = "steppad_normal"
            If Not(TileMapTemplateX.Name.Contains(".")) And Not(TileMapTemplateX.Name.Contains("x")) Then
                'Log("#-    x250, " & $"${TileMapTemplateX.Name} --> PointTemplatesListLoop0(${tempcounter_pt0})= $1.0{pt.Template.Position.X}, $1.0{pt.Template.Position.Y}  "$)
                Dim xy0 As PointXy
                xy0.Initialize
                xy0.x = pt.Template.Position.X
                xy0.y = pt.Template.Position.y
                PointTemplatesListLoop0.Add(xy0)
                tempcounter_pt0 = tempcounter_pt0 +1
            End If
            ' ####testingonly
            ' Show the labels of points
            Dim lblx As Label
            lblx.Initialize("")
            lblx.Text = TileMapTemplateX.Name
            lblx.TextColor = fx.Colors.Yellow
            lblx.Alignment = "CENTER"
            Dim bp As B2Vec2 = X2.WorldPointToMainBC( TileMapTemplateX.Position.X, TileMapTemplateX.Position.Y)
            Private DebugScale As Float = 1.1 ' found by trial and error
            bp.MultiplyThis(DebugScale)
            TempTestPane.AddNode(lblx, bp.X, bp.Y, 40dip, 40dip)
            ' /####testingonly
         
         
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("textval") Then
            Dim tt As TextTemplates
            tt.Template = TileMapTemplateX
            tt.XPosition = TileMapTemplateX.Position.X
            tt.Template.FixtureDef.SetFilterBits(COLL_CATEGORY_SCENERY, COLL_MASK_SCENERYHITS_PLAYER)
            TextTemplatesList.Add(tt)
            TileMap.CreateObject(TileMapTemplateX )
         
            ' ####testingonly
            ' Show the labels of text
            Dim lblx As Label
            lblx.Initialize("")
            lblx.Text = TileMapTemplateX.Name.Replace("TextVal","")
            If TileMapTemplateX.Name.ToLowerCase.StartsWith("textval") Then
                lblx.TextSize = 20
            Else
                lblx.TextSize = 10
            End If
            lblx.TextColor = fx.Colors.Magenta
            'lblx.Alignment = "CENTER_RIGHT"
            lblx.Alignment = "CENTER"
            Dim bp As B2Vec2 = X2.WorldPointToMainBC( TileMapTemplateX.Position.X, TileMapTemplateX.Position.Y)
            Private DebugScale As Float = 1.1 ' found by trial and error
            bp.MultiplyThis(DebugScale)
            TempTestPane.AddNode(lblx, bp.X, bp.Y, 40dip, 40dip)
            ' /####testingonly
         
     
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("char") Then
            Dim ft As FigureTemplates
            ft.Template = TileMapTemplateX
            ft.XPosition = TileMapTemplateX.Position.X
            ft.Template.FixtureDef.SetFilterBits(COLL_CATEGORY_PLAYER, COLL_MASK_PLAYERHITS_MONSTERORSCENERY)
            FigureTemplatesList.Add(ft)
         
            Dim bwChX As X2BodyWrapper = TileMap.CreateObject(TileMapTemplateX)
            bwChX.Body.BodyType = bwChX.Body.TYPE_DYNAMIC   ' not "TYPE_KINEMATIC": You cannot use forces and motors with kinematic types.
         
            If TileMapTemplateX.Name.ToLowerCase.EndsWith("1") Then
                mChar1.Initialize(bwChX) ' Set the delegate (the class "Figure")
                mChar1.FigureNameAndId = TileMapTemplateX.Name & "~" & TileMapTemplateX.id
                mChar1.bw.Body.LinearDamping = 0.01
                mChar1.bw.Body.SleepingAllowed = False
                MotorChar1 = CreateMotor(mChar1)
            else If TileMapTemplateX.Name.ToLowerCase.EndsWith("2") Then
                mChar2.Initialize(bwChX) ' Set the delegate (the class "Figure")
                mChar2.FigureNameAndId = TileMapTemplateX.Name & "~" & TileMapTemplateX.id
                mChar2.bw.Body.LinearDamping = 0.01
                mChar2.bw.Body.SleepingAllowed = False
                MotorChar2 = CreateMotor(mChar2)
             
            End If
     
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("loc") Then
            Dim lt As LocationTemplates
            lt.Template = TileMapTemplateX
            lt.XPosition = TileMapTemplateX.Position.X
            tt.Template.FixtureDef.SetFilterBits(COLL_CATEGORY_SCENERY, COLL_MASK_SCENERYHITS_PLAYER)
            LocationTemplatesList.Add(lt)
            TileMap.CreateObject(TileMapTemplateX)
         
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase = "border" Then
            'nothing to do here. It is created in StartGame()
     
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("area") Then
            TileMap.CreateObject(TileMapTemplateX)
         
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("coin") Then
            ' Creates the 1 and only object with the animated coin
            Dim bwobjcoin1 As X2BodyWrapper = TileMap.CreateObject(TileMapTemplateX )
         
        Else
            Log("#-  x313, " & $"Unhandled object --> id=${TileMapTemplateX.Id}, ${TileMapTemplateX.Name}"$)
        End If
    Next
 
    FigureTemplatesList.SortType("XPosition", True)
    PointTemplatesList.SortType("XPosition", True)
    TextTemplatesList.SortType("XPosition", True)
    LocationTemplatesList.SortType("XPosition", True)
 
    ' After all positions of the "TextVal" objects are determined:
    ' CLONE the 1 animated "coin" object to all postions of "TextVal" objects
    For Each tt As TextTemplates In TextTemplatesList
        CreateCloneFromBw(bwobjcoin1, tt.Template.Position.CreateCopy)
    Next
 
End Sub
Sub CreateCloneFromBw(obj0 As X2BodyWrapper, TargetPosition As B2Vec2)
'    Dim bwclone As X2BodyWrapper = obj0.Body.
'    TileMap.CreateObject(obj0.Body.)
 
    ' ??? 
 
End Sub
Private Sub CreateStaticBackground
    Dim bc As BitmapCreator
    bc.Initialize(ivBackground.Width / xui.Scale / 2, ivBackground.Height / xui.Scale / 2)
    'bc.FillGradient(Array As Int(0xFF001AAC, 0xFFC5A400), bc.TargetRect, "TOP_BOTTOM")
    bc.FillGradient(Array As Int(0xFF61584C, 0xFF61584C), bc.TargetRect, "TOP_BOTTOM") ' brownish
    X2.SetBitmapWithFitOrFill(ivBackground, bc.Bitmap)
End Sub
Private Sub CreateBorder
    TileMap.CreateObject(TileMap.GetObjectTemplateByName(ObjectLayer, "border"))
End Sub
private Sub CreateMotor(character As Figure) As B2MotorJoint
    ' Erel --> https://www.b4x.com/android/forum/threads/x2-help-needed-regarding-object-movement.108390/#post-677540
    Dim MotorDefForChar As B2MotorJointDef
    MotorDefForChar.Initialize(Border.Body, character.bw.Body)
    MotorDefForChar.MaxMotorForce = 1500
    MotorDefForChar.CollideConnected = True 'let the Char collide with the borders
    Dim mc As B2MotorJoint = X2.mWorld.CreateJoint(MotorDefForChar)
    'mc.CorrectionFactor = 0.02
    mc.CorrectionFactor = MotorCorrFacInitVal
    Return mc
End Sub
' ────────────────────────────────────────────────────────────────────────────────────────────────
Public Sub Resize
    X2.ImageViewResized
End Sub
Public Sub DrawingComplete
    TileMap.DrawingComplete
End Sub
'Return True to stop the game loop.
Public Sub BeforeTimeStep (GS As X2GameStep) As Boolean
    If GameOverState Then
        Return True
    End If
    Return False
End Sub
Public Sub Tick (GS As X2GameStep)
    'Log("#-Sub game.Tick, gs.GameTimeMs = " & GS.GameTimeMs)
    TileMap.DrawScreen(Array("Tile Layer 1"), GS.DrawingTasks)
    #region ---Move active character by motor
'    Dim voff As B2Vec2 = X2.CreateVec2(0, 1.1) ' offset, so that the char is slightly beneath the pad
    Dim voff As B2Vec2 = X2.CreateVec2(0, 0) ' offset, so that the char is slightly beneath the pad
    Select Case ActiveCharIndex
        Case 1
            If Char1Points.Size > 0 Then
                Dim NextPoint As B2Vec2 = Char1Points.Get(0)
                NextPoint = NextPoint.CreateCopy
                If Char1Points.Size < 2 Then
                    MotorChar1.CorrectionFactor = MotorCorrFacEnd
                    NextPoint.AddToThis(voff)
                Else
                    MotorChar1.CorrectionFactor = MotorCorrFacNormal
                End If
             
                Dim vec As B2Vec2 = mChar1.bw.Body.Position.CreateCopy
                vec.SubtractFromThis(NextPoint)
                If vec.Length < 1 Then
                    Char1Points.RemoveAt(0)
                    If Char1Points.Size = 0 Then
                        PlayerFinished
                    End If
                Else
                    MoveCharByMotorTo(ActiveCharIndex , NextPoint)
                End If
            End If
         
        Case 2
            ' ~~  ~~  ~~  ~~  ~~  ~~  ~~
            ' duplicate code from "case 1" in order to avoid a timeconsuming call to a separate sub
            ' ~~  ~~  ~~  ~~  ~~  ~~  ~~
            If Char2Points.Size > 0 Then
                Dim NextPoint As B2Vec2 = Char2Points.Get(0)
                NextPoint = NextPoint.CreateCopy
                Dim vec As B2Vec2 = mChar2.bw.Body.Position.CreateCopy
                If Char2Points.Size < 2 Then
                    MotorChar2.CorrectionFactor = MotorCorrFacEnd
                    NextPoint.AddToThis(voff)
                Else
                    MotorChar2.CorrectionFactor = MotorCorrFacNormal
                End If
                vec.SubtractFromThis(NextPoint)
                If vec.Length < 2 Then
                    Char2Points.RemoveAt(0)
                    If Char2Points.Size = 0 Then
                        PlayerFinished
                    End If
                Else
                    MoveCharByMotorTo(ActiveCharIndex , NextPoint)
                End If
            End If
    End Select
 
#end region
End Sub
private Sub PlayerFinished
    CharIsMoving = False
    SwapPlayer
End Sub
' ────────────────────────────────────────────────────────────────────────────────────────────────
Public Sub GameOver
    X2.SoundPool.StopMusic
'    X2.SoundPool.PlaySound("gameover")
    X2.AddFutureTask(Me, "Set_GameOver", 3500, Null)
End Sub
Private Sub Set_GameOver (ft As X2FutureTask)
    GameOverState = True
    Sleep(500)
    StartGame
End Sub
Public Sub StopGame
    X2.SoundPool.StopMusic
    X2.Stop
End Sub
Public Sub StartGame
    If X2.IsRunning Then Return
    X2.Reset
    X2.UpdateWorldCenter(X2.CreateVec2(X2.ScreenAABB.Width / 2, X2.ScreenAABB.Height / 2))
    GameOverState = False
    ' ──────────────────────────────────
    ' Preparations for Tilemap
    ' ──────────────────────────────────
    TileMap.Initialize(X2, File.DirAssets, "TiledMapFile_proj01.json", ivBackground)
    Dim TileSizeMeters As Float = X2.ScreenAABB.Height / TileMap.TilesPerColumn
    TileMap.SetSingleTileDimensionsInMeters(TileSizeMeters, TileSizeMeters)
    SetWorldCenter    ' Update the world center based on the map size
    TileMap.PrepareObjectsDef(ObjectLayer)
    Border = TileMap.CreateObject2ByName(ObjectLayer, "border")
 
    ' ──────────────────────────────────
    ' Draw Tilemap
    ' ──────────────────────────────────
    Dim tasks As List
    tasks.Initialize
    TileMap.Draw(Array("Tile Layer 1"), TileMap.MapAABB, tasks)
    For Each dt As DrawTask In tasks
        If dt.IsCompressedSource Then
            TileMap.CurrentBC.DrawCompressedBitmap(dt.Source, dt.SrcRect, dt.TargetX, dt.TargetY)
        End If
    Next
     
    ' ──────────────────────────────────
    ' Preparations for bodies
    ' ──────────────────────────────────
    CreateObjects
    PositionObjects
'    ActiveCharacterFigure = mChar1
 
    ' ──────────────────────────────────
    ' Preparations for Players
    ' ──────────────────────────────────
    mChar1.CollectedItemsMap.Initialize
    mChar2.CollectedItemsMap.Initialize
     
    ' ──────────────────────────────────
    ' Start the Main loop
    ' ──────────────────────────────────
    X2.Start
 
End Sub
private Sub pnlTouch_Touch (Action As Int, X As Float, Y As Float)
    ' Click is on a "FormPoint": x and y are the form-coordinates topleft=0,0 and bottomright=formheight,formwidth
    '
    If Action = pnlTouch.TOUCH_ACTION_MOVE_NOTOUCH Then Return
    Log("#-Sub pnlTouch_Touch, Action=" & Action & ", x=" & x & ", y=" & y)
 
    Dim WorldPoint As B2Vec2 = X2.ScreenPointToWorld(X, Y)
    Dim MainBCPoint As B2Vec2 = X2.WorldPointToMainBC(WorldPoint.X, WorldPoint.Y)
 
'    If Action = pnlTouch.TOUCH_ACTION_DOWN Then
'        Dim FirstPointBC As B2Vec2 = X2.WorldPointToMainBC(ActiveCharacterFigure.bw.Body.Position.X, ActiveCharacterFigure.bw.Body.Position.Y)
'     
'        ' CLONE the paths before modifying them.
'        PathMainBCForward = PathMainBCForward.Clone
'        PathMainBCForward.Reset(FirstPointBC.X, FirstPointBC.Y)
'        PathMainBCBackwards = PathMainBCBackwards.Clone
'        PathMainBCBackwards.Reset(FirstPointBC.X, FirstPointBC.Y)
'        PathWorld.Clear
'     
'    End If
 
    If PathWorld.Size > 0 Then
        Dim PrevPoint As B2Vec2 = PathWorld.Get(PathWorld.Size - 1)
        Dim distance As B2Vec2 = PrevPoint.CreateCopy
        distance.SubtractFromThis(WorldPoint)
        'to improve performance we skip very close points.
        If distance.LengthSquared < 0.1 Then
            Return
        End If
    End If
 
    PathMainBCForward = PathMainBCForward.Clone
    PathMainBCForward.LineTo(MainBCPoint.X, MainBCPoint.Y)
    PathWorld.Add(WorldPoint)
 
End Sub
private Sub MoveCharByMotorTo (CharIndex As Int, vec As B2Vec2)
    CharIsMoving = True
    vec.SubtractFromThis(Border.Body.Position)
    Select Case CharIndex
        Case 1
            MotorChar1.LinearOffset = vec
        Case 2
            MotorChar2.LinearOffset = vec
    End Select
End Sub
private Sub FindAngleToTarget(Body As B2Body, Target As B2Vec2) As Float
    If Abs(Body.Angle) > 2 * cPI Then
        'make sure that the current angle is between -2*cPI to 2*cPI
        Body.SetTransform(Body.Position, X2.ModFloat(Body.Angle, 2 * cPI))
    End If
    Dim angle As Float = ATan2(Target.Y - Body.Position.Y, Target.X - Body.Position.X) + cPI / 2
    Dim CurrentAngle As Float = Body.Angle
    'find the shortest direction
    Dim anglediff As Float = angle - CurrentAngle
    If anglediff > cPI Then
        angle = -(2 * cPI - angle)
    Else If anglediff < -cPI Then
        angle = angle + 2 * cPI
    End If
    Return angle
End Sub
Private Sub World_BeginContact (Contact As B2Contact)
'must handle this event if we want to handle the PreSolve event.
'    Log("#-")
'    Log("#-Sub World_BeginContact")
    If ActiveCharIndex = 1 Then
        Dim bodies As X2BodiesFromContact = X2.GetBodiesFromContact(Contact, "char1")
    else If ActiveCharIndex = 2 Then
        Dim bodies As X2BodiesFromContact = X2.GetBodiesFromContact(Contact, "char2")
    End If
 
    If bodies <> Null Then
        Log("#-  x614, World_BeginContact, bodies.OtherBody.Name = " & bodies.OtherBody.Name)
        'If bodies.OtherBody.Name.ToLowerCase.StartsWith("p") And bodies.OtherBody.GraphicName = "" Then
        If bodies.OtherBody.Name.ToLowerCase.StartsWith("p")  Then
            Log("#-    x679, stepstone_1")
            X2.SoundPool.PlaySound("stepstone_1")
            bodies.OtherBody.GraphicName = "steppad_collision"
            bodies.OtherBody.SwitchFrameIntervalMs = 100
            X2.AddFutureTask2(Me, "FuTask_StopPoint_Highlight", 1000, bodies.OtherBody, True)
     
        else if bodies.OtherBody.Name.ToLowerCase.StartsWith("textval") Then
            X2.SoundPool.PlaySound("Pickup__008")
            X2.AddFutureTask2(Me, "FuTask_TextVal_Hit", 1000, bodies.OtherBody, True)
         
        End If
    End If
 
End Sub
Sub FuTask_TextVal_Hit (ft As X2FutureTask)
    Dim bwhit As X2BodyWrapper = ft.Value
    Dim HitValue As Int = bwhit.Name.SubString(7)
    Log("#-Sub gm.FuTask_TextVal_Hit, bwhit.Name=" & bwhit.Name)
    TextValScore(bwhit, HitValue)
    If ActiveCharIndex = 1 Then
        mChar1.ItemAddValue("collectedvalues", HitValue)
        mChar2.ItemAddValue("collectedvalues", "-" & (HitValue/2))
             
    else If ActiveCharIndex = 2 Then
        mChar2.ItemAddValue("collectedvalues", HitValue)
        mChar1.ItemAddValue("collectedvalues", "-" & (HitValue/2))
             
    End If
    UI_RefreshPlayerStates
 
End Sub
Sub FuTask_StopPoint_Highlight (ft As X2FutureTask)
    Log("#-Sub gm.FuTask_StopPoint_Highlight")
   Dim point As X2BodyWrapper = ft.Value
   point.GraphicName = "steppad_normal"
End Sub
Public Sub TextValScore (bw As X2BodyWrapper, Score As Int)
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 1.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 2.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 3.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 4.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 5.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 6.png
 
End Sub 
 
 
Private Sub World_PreSolve (Contact As B2Contact, OldManifold As B2Manifold)
'    Log("#-Sub World_PreSolve")
'    Dim BodyA As X2BodyWrapper = Contact.FixtureA.Body.Tag
'    Dim BodyB As X2BodyWrapper = Contact.FixtureB.Body.Tag
'    If BodyA.IsVisible = False Or BodyB.IsVisible = False Then Return
'    Log("#-  x637, BodyA.Name=" & BodyA.Name &", BodyB.Name=" & BodyB.Name)
    '    CheckMarioCollisions (Contact, X2.GetBodiesFromContact(Contact, "mario"))
    '    CheckEnemyCollisions(Contact, X2.GetBodiesFromContact(Contact, "enemy bug"))
    '    CheckEnemyCollisions(Contact, X2.GetBodiesFromContact(Contact, "enemy turtle"))
End Sub
Private Sub World_PostSolve (Contact As B2Contact, Impulse As B2ContactImpulse)
'    Log("#-Sub World_PostSolve")
'    Dim BodyA As X2BodyWrapper = Contact.FixtureA.Body.Tag
'    Dim BodyB As X2BodyWrapper = Contact.FixtureB.Body.Tag
'    If BodyA.IsVisible = False Or BodyB.IsVisible = False Then Return
'    Log("#-  x649, BodyA.Name=" & BodyA.Name &", BodyB.Name=" & BodyB.Name)
End Sub
private Sub ActiveCharacterSelect_SelectedChange(Selected As Boolean) ' ####testingonly
    LastDiceRollVal = 0
    If Not(Selected) Then Return
    Dim rbx As RadioButton = Sender
    Dim tagx As String = rbx.Tag
    Select Case True
        Case tagx.ToLowerCase = "rbc1"
            ActiveCharIndex = 1
        Case tagx.ToLowerCase = "rbc2"
            ActiveCharIndex = 2
    End Select
End Sub
private Sub SwapPlayer
    Log("#-Sub game.SwapPlayer")
    If CharIsMoving Then Return
 
    If ActiveCharIndex =1 Then
        RadioButton2.Selected = True
    Else
        RadioButton1.Selected = True
    End If
 
'    ' ####testingonly
'    mChar1.bw.Body.FirstFixture.SetFilterBits(TextArea5.Text, TextArea6.Text)
'    mChar1.bw.Body.FirstFixture.Density = TextArea4.Text
'    mChar1.bw.Body.FirstFixture.Friction = TextArea3.Text
'    mChar1.bw.Body.FirstFixture.Restitution = TextArea1.Text
'    mChar2.bw.Body.FirstFixture.SetFilterBits(TextArea5.Text, TextArea6.Text)
'    mChar2.bw.Body.FirstFixture.Density = TextArea4.Text
'    mChar2.bw.Body.FirstFixture.Friction = TextArea3.Text
'    mChar2.bw.Body.FirstFixture.Restitution = TextArea1.Text
'    '/####testingonly
 
End Sub
private Sub Button1_Click ' ####testingonly
    Log("#-")
    Log("#-  x708, ROLL DICE ~~~~~ # ~~~~~ # ~~~~~ # ~~~~~ # ~~~~~ # ~~~~~ O")
    If CharIsMoving Then Return
    X2.SoundPool.PlaySound("rolldice")
    Sleep(200)
    LastDiceRollVal = Rnd(3, 7)
    Label2.Text = LastDiceRollVal
    ActiveCharAdvance(LastDiceRollVal)
End Sub
Private Sub UI_RefreshPlayerStates
    Log("#-Sub game.UI_RefreshPlayerStates")
    Label4.Text  = mChar1.ItemGet("collectedvalues", "0")
    Label12.Text = mChar2.ItemGet("collectedvalues", "0")
'    Log("#-  x747, " &  fcn.logm(mChar1.CollectedItemsMap))
'    Log("#-  x748, " &  fcn.logm(mChar2.CollectedItemsMap))
End Sub
private Sub ActiveCharAdvance(NumberOfSteps As Int)
    Log("#-Sub ActiveCharAdvance")
    ' Fill the CharPoints list that is used in Tick()
 
    Dim NewPointTemplatesListIndex As Int =    CharLastPositionIndex(ActiveCharIndex) +NumberOfSteps
    Dim GotToPosIndex As Int = NewPointTemplatesListIndex Mod PointTemplatesListLoop0.Size
    '####temp 
    Dim xy0 As PointXy = PointTemplatesListLoop0.Get(GotToPosIndex)
    Label5.Text = $"ActiveCharIndex=${ActiveCharIndex}, ${CRLF}DiceRollVal=${LastDiceRollVal}, ${CRLF}MoveCharTo p${(GotToPosIndex +1)}  = $1.0{xy0.x}, $1.0{xy0.y}  "$    '####test
    Log("#-  x593, Label5.Text = " & Label5.Text.Replace(CRLF, TAB))
    '/####temp 
   
    Dim TempList As List
    TempList.Initialize
    For i = CharLastPositionIndex(ActiveCharIndex) To NewPointTemplatesListIndex
        Dim GotToPosIndex As Int = i Mod PointTemplatesListLoop0.Size
        Dim xy0 As PointXy = PointTemplatesListLoop0.Get(GotToPosIndex)
        TempList.Add(X2.CreateVec2(xy0.x, xy0.y) )
    Next
    Select Case ActiveCharIndex
        Case 1
            Char1Points.Initialize2(TempList)
        Case 2
            Char2Points.Initialize2(TempList)
    End Select
    CharLastPositionIndex(ActiveCharIndex) = GotToPosIndex
End Sub
'private Sub Touch_Object_P(ft As X2FutureTask)
'    Dim Object_px As X2BodyWrapper = ft.Value
'    Log("#-Sub game.Touch_Object_P, Object_px.Name = " & Object_px.Name)
'    If Object_px.Name.ToLowerCase.StartsWith("p") Then
'        X2.SoundPool.PlaySound("stepstone_1")
'    Else If Object_px.Name.ToLowerCase.StartsWith("text") Then
'        X2.SoundPool.PlaySound("metal-clash")
'    End If
'End Sub

2019-08-28_17-39-03.jpg 2019-08-28_17-42-28.jpg

The animated graphic ("coin") should appear in several places on the playing field. The positions should correspond to those of certain tilemap objects ("TextVal...").

It is intended to create several clones from a single object ("coin") in Sub CreateObjects after traversing the templates. These clones should be placed next to the objects whose names begin with "TextVal".

B4X:
private Sub CreateObjects
...
    Dim ol As X2ObjectsLayer = TileMap.Layers.Get(ObjectLayer)
    For Each TileMapTemplateX As X2TileObjectTemplate In ol.ObjectsById.Values

        If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("p") Then

...
      
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("textval") Then
            Dim tt As TextTemplates
            tt.Template = TileMapTemplateX
            tt.XPosition = TileMapTemplateX.Position.X
            TextTemplatesList.Add(tt)
            TileMap.CreateObject(TileMapTemplateX )

...

        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("coin") Then

            ' Creates the 1 and only object with the animated coin
            Dim bwobjcoin1 As X2BodyWrapper = TileMap.CreateObject(TileMapTemplateX )
          
...

    Next

...
  
    ' After all positions of the "TextVal" objects are determined:
    ' CLONE the 1 animated "coin" object to all postions of "TextVal" objects
    For Each tt As TextTemplates In TextTemplatesList
        CreateCloneFromBw(bwobjcoin1, tt.Template.Position.CreateCopy)
    Next
  
End Sub

Sub CreateCloneFromBw(obj0 As X2BodyWrapper, TargetPosition As B2Vec2)
'    Dim bwclone As X2BodyWrapper = obj0.Body.
'    TileMap.CreateObject(obj0.Body.)
 
    ' ??? 
 
End Sub
How can I create multiple clones from an object defined in the tilemap?

(A relevant test project is attached.)
 

Attachments

Last edited:

Erel

Administrator
Staff member
Licensed User
upload_2019-8-29_8-45-4.png


Several ways to do it. The simplest one is to use the 'copy from id' property.

Just add points objects, set their name to 'coins' and set the 'copy from id' field in Tiled to 99. 99 is the id of the original coin.

Code:
B4X:
Else If TileMapTemplateX.Name = "coins" Then
           TileMap.CreateObject(TileMapTemplateX)
The way to do it programmatically is by creating multiple bodies from the same template in different positions:
B4X:
Sub CreateCloneFromBw(template As X2TileObjectTemplate, TargetPosition As B2Vec2)
   template.BodyDef.Position = TargetPosition
   TileMap.CreateObject(template)
End Sub
And:
B4X:
else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("coin") Then
           ' Creates the 1 and only object with the animated coin
           Dim CoinTemplate As X2TileObjectTemplate = TileMapTemplateX
           TileMap.CreateObject(TileMapTemplateX )
 

fredo

Well-Known Member
Licensed User
... do it programmatically is by creating multiple bodies from the same template in different positions ...
Thank you very much.

I think this is the best solution in this case, because I only have to move the "TextVal" objects in the tilemap if necessary. The coins will then appear next to them automatically.

2019-08-29_17-45-51.png

B4X:
'
' Java source --> file:///.\Objects\src\b4j\gametest\sn\game.java
'
' ------- -------------- ------
'#CustomBuildAction: folders ready, %WINDIR%\System32\Robocopy.exe,"..\..\Shared Files" "..\Files"

' This is required to copy all files from the "Shared Files" folder to the "Files" folder of the respective platform.
'     Erel --> https://www.b4x.com/android/forum/threads/xui2d-cross-platform-tips.96815/#content
' ------- -------------- ------

' ------- -------------- ------
' X2Test1 "Basic structures"
'     x2notes    --> https://www.b4x.com/android/forum/threads/some-notes-to-better-understand-the-xui2d-examples.107702/
'     x2examples --> https://www.b4x.com/android/forum/threads/xui2d-example-pack.96454/
'   @Gunter’s roadmap into the game dev world --> https://www.b4x.com/android/forum/threads/video-tutorial.99172/#post-624377
' ------- -------------- ------

#if B4A
'ignore DIP related warnings as they are not relevant when working with BitmapCreator.
#IgnoreWarnings: 6
#end if

Sub Class_Globals
    '
    Private ISDEBUG As Boolean = False ' It's easier to change it here instead of find it further down
    '
    ' Collision filtering
    ' Gunther --> https://www.b4x.com/android/forum/threads/tool-collision-bits-and-masks-calculator-collision-filtering.103320/
    Public const COLL_CATEGORY_PLAYER As Int  = 0x0001 ' 0000000000000001 in binary
    Public const COLL_CATEGORY_MONSTER As Int = 0x0002 ' 0000000000000010 in binary
    Public const COLL_CATEGORY_SCENERY As Int = 0x0004 ' 0000000000000100 in binary
    
    Public const COLL_MASK_PLAYERHITS_MONSTERORSCENERY As Int  = 0xFFFE ' 1111111111111110 in binary
    Public const COLL_MASK_MONSTERHITS_PLAYER As Int           = 0xFFF9 ' 1111111111111001 in binary
    Public const COLL_MASK_SCENERYHITS_PLAYER As Int           = 0xFFF9 ' 1111111111111001 in binary
    
    ' The group index can be used to override the category/mask settings for a given set of fixtures.
    ' --> https://www.iforce2d.net/b2dtut/collision-filtering
    Public const COLL_GROUP_PLAYER As Int = -1
    Public const COLL_GROUP_MONSTER As Int = -2
    Public const COLL_GROUP_SCENERY As Int = 1
    
    ' Basic x2 libraries that are needed in every game
    Public  xui As XUI 'ignore
    Public X2 As X2Utils
    Public world As B2World
    
    ' TileMap specific definitions
    Public TileMap As X2TileMap
    Public Const ObjectLayer As String = "Object Layer 1"
    
    ' ExtraClasses
    Private mChar1 As Figure
    Private mChar2 As Figure
    
    ' Parameters
    Public const MotorCorrFacInitVal As Float = 0.03
    Public const MotorCorrFacNormal As Float = 0.07
    Public const MotorCorrFacEnd As Float = 0.06
    
    ' Gamestate
    Private GameOverState As Boolean = False
    Private CharIsMoving As Boolean = False
    Private ActiveCharIndex As Int = 1
    Private LastDiceRollVal As Int = 0
    Private CharLastPositionIndex(3) As Int
    Type PointXy(x As Float, y As Float)
    Private Char1Points As List
    Private Char2Points As List
    
    ' Bodies and joints
    Private Border As X2BodyWrapper
    Private MotorChar1 As B2MotorJoint
    Private MotorChar2 As B2MotorJoint
    
    Private PathMainBCForward As BCPath
    Private PathMainBCBackwards As BCPath
    Private PathWorld As List
'    Private BrushRed As BCBrush
    
    
    ' Templates
    Type FigureTemplates(Template As X2TileObjectTemplate, XPosition As Float)
    Private FigureTemplatesList As List
    
    Type PointTemplates(Template As X2TileObjectTemplate, XPosition As Float)
    Private PointTemplatesList As List
    Private PointTemplatesListLoop0 As List
    
    Type TextTemplates (Template As X2TileObjectTemplate, XPosition As Float)
    Private TextTemplatesList As List
    
    Type LocationTemplates(Template As X2TileObjectTemplate, XPosition As Float)
    Private LocationTemplatesList As List
    

    ' Layout elements
    Private ivForeground As B4XView
    Private ivBackground As B4XView
    Public lblStats As B4XView
    Private pnlTouch As B4XView
    Private RadioButton1 As RadioButton' ####testingonly
    Private RadioButton2 As RadioButton' ####testingonly
    Private Button1 As Button' ####testingonly
    Private Label2 As Label' ####testingonly
    Private Pane1 As Pane' ####testingonly
    Private Pane2 As Pane' ####testingonly
    Private fx As JFX' ####testingonly
    Private TempTestPane As Pane' ####testingonly
    Private Label5 As Label' ####testingonly
'    Private TextArea1 As TextArea ' Restitution
'    Private TextArea3 As TextArea ' Friction
'    Private TextArea4 As TextArea ' Density
'    Private TextArea5 As TextArea ' Category bits
'    Private TextArea6 As TextArea ' Mask bits
'    Private TextArea7 As TextArea ' mcfA
'    Private TextArea8 As TextArea ' mcfB
    Private Label4 As Label ' coll pl1
    Private Label12 As Label ' coll pl2
End Sub

Public Sub Initialize (Parent As B4XView)
    '
    ' ──────────────────────────────────
    ' Preparations for Layout and Views
    ' ──────────────────────────────────
    Parent.LoadLayout("GameLayout")
    lblStats.TextColor = xui.Color_Black
    lblStats.Color = 0x88ffffff
    lblStats.Font = xui.CreateDefaultBoldFont(20)
    
    CSSUtils.SetBackgroundColor(Pane1, fx.Colors.LightGray)' ####testingonly
    CSSUtils.SetBackgroundColor(Pane2, fx.Colors.LightGray)' ####testingonly
    
    ' ──────────────────────────────────
    ' Preparations for World
    ' ──────────────────────────────────
    world.Initialize("world", world.CreateVec2(0, 0))
    X2.Initialize(Me, ivForeground, world)
    
    Dim WorldWidth As Float = 36 'meters
    Dim WorldHeight As Float = WorldWidth / 1 'same ratio as in the designer script (in GameLayout.bjl)!!!
    X2.ConfigureDimensions(world.CreateVec2(WorldWidth / 2, WorldHeight / 2), WorldWidth)
    
    
'    TextArea8.Text = MotorCorrFacNormal ' ####testingonly
'    TextArea7.Text = MotorCorrFacEnd ' ####testingonly
    
    ' ──────────────────────────────────
    ' Preparations for movements
    ' ──────────────────────────────────
    PathMainBCForward.Initialize(0, 0)
    PathMainBCBackwards.Initialize(0, 0)
    PathWorld.Initialize
'    BrushRed = X2.MainBC.CreateBrushFromColor(xui.Color_Red)
    CharLastPositionIndex(0) = -1 ' not used
    CharLastPositionIndex(1) = 0 ' for Char1
    CharLastPositionIndex(2) = 0 ' for Char2
    
    Char1Points.Initialize
    Char2Points.Initialize
    
    ' ──────────────────────────────────
    ' Preparations for graphics
    ' ──────────────────────────────────
    GraphicCache_Put_Objects
    GraphicCache_Put_Characters
    
    
    ' ──────────────────────────────────
    ' Preparations for screen
    ' ──────────────────────────────────
    'SetBackground
    CreateStaticBackground
    'CreateBorder
    
    
    ' ──────────────────────────────────
    ' Preparations for sounds
    ' ──────────────────────────────────
    X2.SoundPool.AddSound("rolldice", File.DirAssets, "dice-4.wav")
    X2.SoundPool.AddSound("stepstone_1", File.DirAssets, "stepstone_1.wav")
    'X2.SoundPool.AddSound("metal-clash", File.DirAssets, "metal-clash.wav")
    X2.SoundPool.AddSound("Pickup__008", File.DirAssets, "Pickup__008.wav")
    
    
    ' ──────────────────────────────────
    ' Debug settings
    ' ──────────────────────────────────
    If ISDEBUG Then
        X2.EnableDebugDraw    ' Comment out to disable debug drawing
    End If
    
End Sub

Private Sub SetWorldCenter
    ' The map size will not be identical to the screen size.
    ' This happens because the tile size in (bc) pixels needs to be a whole number.
    ' So we need to update the world center and move the map to the center.
    X2.UpdateWorldCenter(TileMap.MapAABB.Center)
End Sub
Public Sub WorldCenterUpdated (gs As X2GameStep)
    'CreateEnemies
End Sub
private Sub SetBackground
    'X2.SetBitmapWithFitOrFill(ivBackground, xui.LoadBitmapResize(File.DirAssets, "mybackgroundimage.jpg", ivBackground.Width / 2, ivBackground.Height / 2, False))
End Sub

Private Sub GraphicCache_Put_Objects
    Log("#-Sub game.GraphicCache_Put_Objects")
    '
    Dim RowWidthPixel As Int = 32
    Dim RowHeightPixel As Int = 32
    Dim CollGraphHeightMeters As Int = 2
    
    ' -------------------------------
    ' Step-pads
    ' -------------------------------
    Dim bc1 As BitmapCreator = X2.BitmapToBC(xui.LoadBitmap(File.DirAssets, "dungeon_tilesetmodrollo.png"), 1)
    Dim bmp As B4XBitmap = bc1.Bitmap
    Dim CollGraph1 As B4XBitmap = bmp.Crop(1 * 32, 18 * RowHeightPixel, 2 * RowWidthPixel, 2 * RowHeightPixel)
    Dim CollGraph1List As List = X2.ReadSprites(CollGraph1, 1, 1, CollGraphHeightMeters, CollGraphHeightMeters)
    X2.GraphicCache.PutGraphic("steppad_collision", Array(CollGraph1List.Get(0)) )
    
    Dim bc2 As BitmapCreator = X2.BitmapToBC(xui.LoadBitmap(File.DirAssets, "dungeon_tilesetmodrollo.png"), 1)
    Dim bmp As B4XBitmap = bc2.Bitmap
    Dim CollGraph1 As B4XBitmap = bmp.Crop(5 * 32, 18 * RowHeightPixel, 2 * RowWidthPixel, 2 * RowHeightPixel)
    Dim CollGraph1List As List = X2.ReadSprites(CollGraph1, 1, 1, CollGraphHeightMeters, CollGraphHeightMeters)
    X2.GraphicCache.PutGraphic("steppad_normal", Array(CollGraph1List.Get(0)) )
        
End Sub
Private Sub GraphicCache_Put_Characters
    Log("#-Sub game.GraphicCache_Put_Characters")

    ' -------------------------------
    ' Characters
    ' -------------------------------
    ' Use bitmap without the transparency placeholdercolor:
    Dim bc As BitmapCreator = X2.BitmapToBC( xui.LoadBitmap(File.DirAssets, "RPGCharacterSprites32x32.png"), 1)
    RemovePseudoTransparentColor(bc, "#ff00ff") ' pink, magenta
    Dim bmp As B4XBitmap = bc.Bitmap
    Dim NumberOfSprites As Int = 12
    Dim RowWidth As Int = 32
    Dim RowHeight As Int = 32
    Dim CharHeightMeters As Int = 3
    '
    Dim RowOfChar1 As Int = 2
    Dim character1 As B4XBitmap = bmp.Crop(0, RowHeight * RowOfChar1, NumberOfSprites * RowWidth, RowHeight)
    Dim AllChar1 As List = X2.ReadSprites(character1, 1, NumberOfSprites, CharHeightMeters, CharHeightMeters)
    X2.GraphicCache.PutGraphic("character1 front walking", Array(AllChar1.Get(0), AllChar1.Get(1), AllChar1.Get(2), AllChar1.Get(3)))
    X2.GraphicCache.PutGraphic("character1 front standing", Array(AllChar1.Get(3)))
    X2.GraphicCache.PutGraphic("character1 back walking", Array(AllChar1.Get(4), AllChar1.Get(5), AllChar1.Get(6), AllChar1.Get(7)))
    X2.GraphicCache.PutGraphic("character1 back standing", Array(AllChar1.Get(7)))
    X2.GraphicCache.PutGraphic("character1 side walking", Array(AllChar1.Get(8), AllChar1.Get(9), AllChar1.Get(10)) )
    X2.GraphicCache.PutGraphic("character1 side standing", Array(AllChar1.Get(9)) )
    '
    Dim RowOfChar2 As Int = 3
    Dim character2 As B4XBitmap = bmp.Crop(0, RowHeight * RowOfChar2, NumberOfSprites * RowWidth,  RowHeight)
    Dim AllChar2 As List = X2.ReadSprites(character2, 1, NumberOfSprites, CharHeightMeters, CharHeightMeters)
    X2.GraphicCache.PutGraphic("character2 front walking", Array(AllChar2.Get(0), AllChar2.Get(1), AllChar2.Get(2), AllChar2.Get(3)))
    X2.GraphicCache.PutGraphic("character2 front standing", Array(AllChar2.Get(3)))
    X2.GraphicCache.PutGraphic("character2 back walking", Array(AllChar2.Get(4), AllChar2.Get(5), AllChar2.Get(6), AllChar2.Get(7)))
    X2.GraphicCache.PutGraphic("character2 back standing", Array(AllChar2.Get(7)))
    X2.GraphicCache.PutGraphic("character2 side walking", Array(AllChar2.Get(8), AllChar2.Get(9), AllChar2.Get(10)) )
    X2.GraphicCache.PutGraphic("character2 side standing", Array(AllChar2.Get(9)) )

End Sub

Private Sub RemovePseudoTransparentColor(TilesBC As BitmapCreator, clrstring As String)
    ' Erel --> https://www.b4x.com/android/forum/threads/x2-how-to-use-transparent-color.108173/#post-676534
    Dim clr As Int = 0xff000000 + Bit.ParseInt(clrstring.SubString(1), 16)
    Dim ptranspm As PremultipliedColor
    Dim trans As PremultipliedColor
    Dim pm As PremultipliedColor
    Dim argb As ARGBColor
    TilesBC.ColorToARGB(clr, argb)
    TilesBC.ARGBToPremultipliedColor(argb, ptranspm)
    For y = 0 To TilesBC.mHeight - 1
        For x = 0 To TilesBC.mWidth - 1
            TilesBC.GetPremultipliedColor(x, y, pm)
            If Bit.And(0xff, pm.r) = ptranspm.r And Bit.And(0xff, pm.g) = ptranspm.g And Bit.And(0xff, pm.b) = ptranspm.b And Bit.And(0xff, pm.a) = ptranspm.a Then
                TilesBC.SetPremultipliedColor(x, y, trans)
            End If
        Next
    Next
End Sub

Private Sub PositionObjects
    '    For Each LocationTemplateX As LocationTemplates In LocationTemplatesList
    '        ' Initial position on location-objects (works ok)
    '        Select Case True
    '            Case LocationTemplateX.Template.Name.ToLowerCase.EndsWith("c1")
    '                mChar1.bw.Body.SetTransform(LocationTemplateX.Template.Position, 0)
    '            Case LocationTemplateX.Template.Name.ToLowerCase.EndsWith("c2")
    '                mChar2.bw.Body.SetTransform(LocationTemplateX.Template.Position, 0)
    '        End Select
    '    Next
End Sub
private Sub CreateObjects
    Log("#-Sub game.CreateObjects")
    FigureTemplatesList.Initialize
    PointTemplatesList.Initialize
    PointTemplatesListLoop0.Initialize
    TextTemplatesList.Initialize
    LocationTemplatesList.Initialize

    TempTestPane.RemoveAllNodes    ' ####testingonly

    Dim tempcounter_pt0 As Int = 0'####temp
    Dim ol As X2ObjectsLayer = TileMap.Layers.Get(ObjectLayer)
    For Each TileMapTemplateX As X2TileObjectTemplate In ol.ObjectsById.Values
        'Log("#-    x203, TileMapTemplateX.Name = " & TileMapTemplateX.Name)
        
        If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("p") Then
            Dim pt As PointTemplates
            pt.Template = TileMapTemplateX
            pt.XPosition = TileMapTemplateX.Position.X
            pt.Template.FixtureDef.SetFilterBits(COLL_CATEGORY_SCENERY, COLL_MASK_SCENERYHITS_PLAYER)
            PointTemplatesList.Add(pt)
            
            Dim bwobj As X2BodyWrapper = TileMap.CreateObject(TileMapTemplateX)
            bwobj.Graphicname = "steppad_normal"
            If Not(TileMapTemplateX.Name.Contains(".")) And Not(TileMapTemplateX.Name.Contains("x")) Then
                'Log("#-    x250, " & $"${TileMapTemplateX.Name} --> PointTemplatesListLoop0(${tempcounter_pt0})= $1.0{pt.Template.Position.X}, $1.0{pt.Template.Position.Y}  "$)
                Dim xy0 As PointXy
                xy0.Initialize
                xy0.x = pt.Template.Position.X
                xy0.y = pt.Template.Position.y
                PointTemplatesListLoop0.Add(xy0)
                tempcounter_pt0 = tempcounter_pt0 +1
            End If

            ' ####testingonly
            ' Show the labels of points
            Dim lblx As Label
            lblx.Initialize("")
            lblx.Text = TileMapTemplateX.Name
            lblx.TextColor = fx.Colors.Yellow
            lblx.Alignment = "CENTER"
            Dim bp As B2Vec2 = X2.WorldPointToMainBC( TileMapTemplateX.Position.X, TileMapTemplateX.Position.Y)
            Private DebugScale As Float = 1.1 ' found by trial and error
            bp.MultiplyThis(DebugScale)
            TempTestPane.AddNode(lblx, bp.X, bp.Y, 40dip, 40dip)
            ' /####testingonly
            
            
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("textval") Then
            Dim tt As TextTemplates
            tt.Template = TileMapTemplateX
            tt.XPosition = TileMapTemplateX.Position.X
            tt.Template.FixtureDef.SetFilterBits(COLL_CATEGORY_SCENERY, COLL_MASK_SCENERYHITS_PLAYER)
            TextTemplatesList.Add(tt)
            TileMap.CreateObject(TileMapTemplateX )
            
            ' ####testingonly
            ' Show the labels of text
            Dim lblx As Label
            lblx.Initialize("")
            lblx.Text = TileMapTemplateX.Name.Replace("TextVal","")
            If TileMapTemplateX.Name.ToLowerCase.StartsWith("textval") Then
                lblx.TextSize = 20
            Else
                lblx.TextSize = 10
            End If
            lblx.TextColor = fx.Colors.Magenta
            'lblx.Alignment = "CENTER_RIGHT"
            lblx.Alignment = "CENTER"
            Dim bp As B2Vec2 = X2.WorldPointToMainBC( TileMapTemplateX.Position.X, TileMapTemplateX.Position.Y)
            Private DebugScale As Float = 1.1 ' found by trial and error
            bp.MultiplyThis(DebugScale)
            TempTestPane.AddNode(lblx, bp.X, bp.Y, 40dip, 40dip)
            ' /####testingonly
            
        
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("char") Then
            Dim ft As FigureTemplates
            ft.Template = TileMapTemplateX
            ft.XPosition = TileMapTemplateX.Position.X
            ft.Template.FixtureDef.SetFilterBits(COLL_CATEGORY_PLAYER, COLL_MASK_PLAYERHITS_MONSTERORSCENERY)
            FigureTemplatesList.Add(ft)
            
            Dim bwChX As X2BodyWrapper = TileMap.CreateObject(TileMapTemplateX)
            bwChX.Body.BodyType = bwChX.Body.TYPE_DYNAMIC   ' not "TYPE_KINEMATIC": You cannot use forces and motors with kinematic types.
            
            If TileMapTemplateX.Name.ToLowerCase.EndsWith("1") Then
                mChar1.Initialize(bwChX) ' Set the delegate (the class "Figure")
                mChar1.FigureNameAndId = TileMapTemplateX.Name & "~" & TileMapTemplateX.id
                mChar1.bw.Body.LinearDamping = 0.01
                mChar1.bw.Body.SleepingAllowed = False
                MotorChar1 = CreateMotor(mChar1)

            else If TileMapTemplateX.Name.ToLowerCase.EndsWith("2") Then
                mChar2.Initialize(bwChX) ' Set the delegate (the class "Figure")
                mChar2.FigureNameAndId = TileMapTemplateX.Name & "~" & TileMapTemplateX.id
                mChar2.bw.Body.LinearDamping = 0.01
                mChar2.bw.Body.SleepingAllowed = False
                MotorChar2 = CreateMotor(mChar2)
                
            End If
        
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("loc") Then
            Dim lt As LocationTemplates
            lt.Template = TileMapTemplateX
            lt.XPosition = TileMapTemplateX.Position.X
            tt.Template.FixtureDef.SetFilterBits(COLL_CATEGORY_SCENERY, COLL_MASK_SCENERYHITS_PLAYER)
            LocationTemplatesList.Add(lt)
            TileMap.CreateObject(TileMapTemplateX)
            
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase = "border" Then
            'nothing to do here. It is created in StartGame()
        
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("area") Then
            TileMap.CreateObject(TileMapTemplateX)
            
        else If TileMapTemplateX.FirstTime And TileMapTemplateX.Name.ToLowerCase.StartsWith("coin") Then
            ' Creates the 1 and only object with the animated coin
            ' It will be moved out of sight after all "coin" clones are redy
            Dim CoinTemplate As X2TileObjectTemplate = TileMapTemplateX
            Dim CoinBodywrapper As X2BodyWrapper = TileMap.CreateObject(TileMapTemplateX)
            
        Else
            Log("#-  x313, " & $"Unhandled object --> id=${TileMapTemplateX.Id}, ${TileMapTemplateX.Name}"$)
        End If
    Next
    
    FigureTemplatesList.SortType("XPosition", True)
    PointTemplatesList.SortType("XPosition", True)
    TextTemplatesList.SortType("XPosition", True)
    LocationTemplatesList.SortType("XPosition", True)
    
    ' After all positions of the "TextVal" objects are determined:
    ' CLONE the 1 animated "coin" object to all postions of "TextVal" objects
    For Each tt As TextTemplates In TextTemplatesList
        CreateCloneFromBw(CoinTemplate, tt.Template.Position.CreateCopy)
    Next
    CoinBodywrapper.Body.SetTransform(X2.CreateVec2(-1, -1), 0) ' This object is no longer needed
    
    
End Sub

Sub CreateCloneFromBw(template As X2TileObjectTemplate, TargetPosition As B2Vec2)
   template.BodyDef.Position = TargetPosition
   TileMap.CreateObject(template)
End Sub

Private Sub CreateStaticBackground
    Dim bc As BitmapCreator
    bc.Initialize(ivBackground.Width / xui.Scale / 2, ivBackground.Height / xui.Scale / 2)
    'bc.FillGradient(Array As Int(0xFF001AAC, 0xFFC5A400), bc.TargetRect, "TOP_BOTTOM")
    bc.FillGradient(Array As Int(0xFF61584C, 0xFF61584C), bc.TargetRect, "TOP_BOTTOM") ' brownish
    X2.SetBitmapWithFitOrFill(ivBackground, bc.Bitmap)
End Sub
Private Sub CreateBorder
    TileMap.CreateObject(TileMap.GetObjectTemplateByName(ObjectLayer, "border"))
End Sub
private Sub CreateMotor(character As Figure) As B2MotorJoint
    ' Erel --> https://www.b4x.com/android/forum/threads/x2-help-needed-regarding-object-movement.108390/#post-677540
    Dim MotorDefForChar As B2MotorJointDef
    MotorDefForChar.Initialize(Border.Body, character.bw.Body)
    MotorDefForChar.MaxMotorForce = 1500
    MotorDefForChar.CollideConnected = True 'let the Char collide with the borders
    Dim mc As B2MotorJoint = X2.mWorld.CreateJoint(MotorDefForChar)
    'mc.CorrectionFactor = 0.02
    mc.CorrectionFactor = MotorCorrFacInitVal
    Return mc
End Sub

' ────────────────────────────────────────────────────────────────────────────────────────────────
Public Sub Resize
    X2.ImageViewResized
End Sub
Public Sub DrawingComplete
    TileMap.DrawingComplete
End Sub

'Return True to stop the game loop.
Public Sub BeforeTimeStep (GS As X2GameStep) As Boolean
    If GameOverState Then
        Return True
    End If

    Return False
End Sub
Public Sub Tick (GS As X2GameStep)
    'Log("#-Sub game.Tick, gs.GameTimeMs = " & GS.GameTimeMs)
    TileMap.DrawScreen(Array("Tile Layer 1"), GS.DrawingTasks)

    #region ---Move active character by motor
'    Dim voff As B2Vec2 = X2.CreateVec2(0, 1.1) ' offset, so that the char is slightly beneath the pad
    Dim voff As B2Vec2 = X2.CreateVec2(0, 0) ' offset, so that the char is slightly beneath the pad
    Select Case ActiveCharIndex
        Case 1
            If Char1Points.Size > 0 Then
                Dim NextPoint As B2Vec2 = Char1Points.Get(0)
                NextPoint = NextPoint.CreateCopy
                If Char1Points.Size < 2 Then
                    MotorChar1.CorrectionFactor = MotorCorrFacEnd
                    NextPoint.AddToThis(voff)
                Else
                    MotorChar1.CorrectionFactor = MotorCorrFacNormal
                End If
                
                Dim vec As B2Vec2 = mChar1.bw.Body.Position.CreateCopy
                vec.SubtractFromThis(NextPoint)
                If vec.Length < 1 Then
                    Char1Points.RemoveAt(0)
                    If Char1Points.Size = 0 Then
                        PlayerFinished
                    End If
                Else
                    MoveCharByMotorTo(ActiveCharIndex , NextPoint)
                End If
            End If
            
        Case 2
            ' ~~  ~~  ~~  ~~  ~~  ~~  ~~
            ' duplicate code from "case 1" in order to avoid a timeconsuming call to a separate sub
            ' ~~  ~~  ~~  ~~  ~~  ~~  ~~
            If Char2Points.Size > 0 Then
                Dim NextPoint As B2Vec2 = Char2Points.Get(0)
                NextPoint = NextPoint.CreateCopy
                Dim vec As B2Vec2 = mChar2.bw.Body.Position.CreateCopy
                If Char2Points.Size < 2 Then
                    MotorChar2.CorrectionFactor = MotorCorrFacEnd
                    NextPoint.AddToThis(voff)
                Else
                    MotorChar2.CorrectionFactor = MotorCorrFacNormal
                End If
                vec.SubtractFromThis(NextPoint)
                If vec.Length < 2 Then
                    Char2Points.RemoveAt(0)
                    If Char2Points.Size = 0 Then
                        PlayerFinished
                    End If
                Else
                    MoveCharByMotorTo(ActiveCharIndex , NextPoint)
                End If
            End If
    End Select
    
#end region
End Sub
private Sub PlayerFinished
    CharIsMoving = False
    SwapPlayer
End Sub
' ────────────────────────────────────────────────────────────────────────────────────────────────
Public Sub GameOver
    X2.SoundPool.StopMusic
'    X2.SoundPool.PlaySound("gameover")
    X2.AddFutureTask(Me, "Set_GameOver", 3500, Null)
End Sub
Private Sub Set_GameOver (ft As X2FutureTask)
    GameOverState = True
    Sleep(500)
    StartGame
End Sub
Public Sub StopGame
    X2.SoundPool.StopMusic
    X2.Stop
End Sub
Public Sub StartGame
    If X2.IsRunning Then Return
    X2.Reset
    X2.UpdateWorldCenter(X2.CreateVec2(X2.ScreenAABB.Width / 2, X2.ScreenAABB.Height / 2))
    GameOverState = False

    ' ──────────────────────────────────
    ' Preparations for Tilemap
    ' ──────────────────────────────────
    TileMap.Initialize(X2, File.DirAssets, "TiledMapFile_proj01.json", ivBackground)
    Dim TileSizeMeters As Float = X2.ScreenAABB.Height / TileMap.TilesPerColumn
    TileMap.SetSingleTileDimensionsInMeters(TileSizeMeters, TileSizeMeters)
    SetWorldCenter    ' Update the world center based on the map size
    TileMap.PrepareObjectsDef(ObjectLayer)

    Border = TileMap.CreateObject2ByName(ObjectLayer, "border")
    
    ' ──────────────────────────────────
    ' Draw Tilemap
    ' ──────────────────────────────────
    Dim tasks As List
    tasks.Initialize
    TileMap.Draw(Array("Tile Layer 1"), TileMap.MapAABB, tasks)
    For Each dt As DrawTask In tasks
        If dt.IsCompressedSource Then
            TileMap.CurrentBC.DrawCompressedBitmap(dt.Source, dt.SrcRect, dt.TargetX, dt.TargetY)
        End If
    Next
        
    ' ──────────────────────────────────
    ' Preparations for bodies
    ' ──────────────────────────────────
    CreateObjects
    PositionObjects
'    ActiveCharacterFigure = mChar1
    
    ' ──────────────────────────────────
    ' Preparations for Players
    ' ──────────────────────────────────
    mChar1.CollectedItemsMap.Initialize
    mChar2.CollectedItemsMap.Initialize
        
    ' ──────────────────────────────────
    ' Start the Main loop
    ' ──────────────────────────────────
    X2.Start
    
End Sub
 
private Sub pnlTouch_Touch (Action As Int, X As Float, Y As Float)
    ' Click is on a "FormPoint": x and y are the form-coordinates topleft=0,0 and bottomright=formheight,formwidth
    '
    If Action = pnlTouch.TOUCH_ACTION_MOVE_NOTOUCH Then Return
    Log("#-Sub pnlTouch_Touch, Action=" & Action & ", x=" & x & ", y=" & y)
    
    Dim WorldPoint As B2Vec2 = X2.ScreenPointToWorld(X, Y)
    Dim MainBCPoint As B2Vec2 = X2.WorldPointToMainBC(WorldPoint.X, WorldPoint.Y)
    
'    If Action = pnlTouch.TOUCH_ACTION_DOWN Then
'        Dim FirstPointBC As B2Vec2 = X2.WorldPointToMainBC(ActiveCharacterFigure.bw.Body.Position.X, ActiveCharacterFigure.bw.Body.Position.Y)
'       
'        ' CLONE the paths before modifying them.
'        PathMainBCForward = PathMainBCForward.Clone
'        PathMainBCForward.Reset(FirstPointBC.X, FirstPointBC.Y)
'        PathMainBCBackwards = PathMainBCBackwards.Clone
'        PathMainBCBackwards.Reset(FirstPointBC.X, FirstPointBC.Y)
'        PathWorld.Clear
'       
'    End If
    
    If PathWorld.Size > 0 Then
        Dim PrevPoint As B2Vec2 = PathWorld.Get(PathWorld.Size - 1)
        Dim distance As B2Vec2 = PrevPoint.CreateCopy
        distance.SubtractFromThis(WorldPoint)
        'to improve performance we skip very close points.
        If distance.LengthSquared < 0.1 Then
            Return
        End If
    End If
    
    PathMainBCForward = PathMainBCForward.Clone
    PathMainBCForward.LineTo(MainBCPoint.X, MainBCPoint.Y)
    PathWorld.Add(WorldPoint)
    
End Sub

private Sub MoveCharByMotorTo (CharIndex As Int, vec As B2Vec2)
'    Log("#-Sub MoveCharByMotorTo, CharIndex=" & CharIndex & ", vec.X=" & vec.X & ", vec.y=" & vec.y)
    CharIsMoving = True
    vec.SubtractFromThis(Border.Body.Position)
    Select Case CharIndex
        Case 1
            MotorChar1.LinearOffset = vec
        Case 2
            MotorChar2.LinearOffset = vec
    End Select
End Sub
private Sub FindAngleToTarget(Body As B2Body, Target As B2Vec2) As Float
    If Abs(Body.Angle) > 2 * cPI Then
        'make sure that the current angle is between -2*cPI to 2*cPI
        Body.SetTransform(Body.Position, X2.ModFloat(Body.Angle, 2 * cPI))
    End If
    Dim angle As Float = ATan2(Target.Y - Body.Position.Y, Target.X - Body.Position.X) + cPI / 2
    Dim CurrentAngle As Float = Body.Angle
    'find the shortest direction
    Dim anglediff As Float = angle - CurrentAngle
    If anglediff > cPI Then
        angle = -(2 * cPI - angle)
    Else If anglediff < -cPI Then
        angle = angle + 2 * cPI
    End If
    Return angle
End Sub

Private Sub World_BeginContact (Contact As B2Contact)
'must handle this event if we want to handle the PreSolve event.
'    Log("#-")
'    Log("#-Sub World_BeginContact")
    If ActiveCharIndex = 1 Then
        Dim bodies As X2BodiesFromContact = X2.GetBodiesFromContact(Contact, "char1")
    else If ActiveCharIndex = 2 Then
        Dim bodies As X2BodiesFromContact = X2.GetBodiesFromContact(Contact, "char2")
    End If
  
    If bodies <> Null Then
        Log("#-  x614, World_BeginContact, bodies.OtherBody.Name = " & bodies.OtherBody.Name)
        'If bodies.OtherBody.Name.ToLowerCase.StartsWith("p") And bodies.OtherBody.GraphicName = "" Then
        If bodies.OtherBody.Name.ToLowerCase.StartsWith("p")  Then
            Log("#-    x679, stepstone_1")
            X2.SoundPool.PlaySound("stepstone_1")
            bodies.OtherBody.GraphicName = "steppad_collision"
            bodies.OtherBody.SwitchFrameIntervalMs = 100
            X2.AddFutureTask2(Me, "FuTask_StopPoint_Highlight", 1000, bodies.OtherBody, True)
        
        else if bodies.OtherBody.Name.ToLowerCase.StartsWith("textval") Then
            X2.SoundPool.PlaySound("Pickup__008")
            X2.AddFutureTask2(Me, "FuTask_TextVal_Hit", 1000, bodies.OtherBody, True)
            
        End If
    End If
    
End Sub
Sub FuTask_TextVal_Hit (ft As X2FutureTask)
    Dim bwhit As X2BodyWrapper = ft.Value
    Dim HitValue As Int = bwhit.Name.SubString(7)
    Log("#-Sub gm.FuTask_TextVal_Hit, bwhit.Name=" & bwhit.Name)
    TextValScore(bwhit, HitValue)
    If ActiveCharIndex = 1 Then
        mChar1.ItemAddValue("collectedvalues", HitValue)
        mChar2.ItemAddValue("collectedvalues", "-" & (HitValue/2))
                
    else If ActiveCharIndex = 2 Then
        mChar2.ItemAddValue("collectedvalues", HitValue)
        mChar1.ItemAddValue("collectedvalues", "-" & (HitValue/2))
                
    End If
    UI_RefreshPlayerStates
    
End Sub
Sub FuTask_StopPoint_Highlight (ft As X2FutureTask)
    Log("#-Sub gm.FuTask_StopPoint_Highlight")
   Dim point As X2BodyWrapper = ft.Value
   point.GraphicName = "steppad_normal"
End Sub
Public Sub TextValScore (bw As X2BodyWrapper, Score As Int)

    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 1.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 2.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 3.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 4.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 5.png
    ' D:\_Rollo\rolloApps\_SchatzJäger\Shared Files\star coin rotate 6.png
    
End Sub   
    
    
Private Sub World_PreSolve (Contact As B2Contact, OldManifold As B2Manifold)
'    Log("#-Sub World_PreSolve")
'    Dim BodyA As X2BodyWrapper = Contact.FixtureA.Body.Tag
'    Dim BodyB As X2BodyWrapper = Contact.FixtureB.Body.Tag
'    If BodyA.IsVisible = False Or BodyB.IsVisible = False Then Return
'    Log("#-  x637, BodyA.Name=" & BodyA.Name &", BodyB.Name=" & BodyB.Name)
    '    CheckMarioCollisions (Contact, X2.GetBodiesFromContact(Contact, "mario"))
    '    CheckEnemyCollisions(Contact, X2.GetBodiesFromContact(Contact, "enemy bug"))
    '    CheckEnemyCollisions(Contact, X2.GetBodiesFromContact(Contact, "enemy turtle"))
End Sub
Private Sub World_PostSolve (Contact As B2Contact, Impulse As B2ContactImpulse)
'    Log("#-Sub World_PostSolve")
'    Dim BodyA As X2BodyWrapper = Contact.FixtureA.Body.Tag
'    Dim BodyB As X2BodyWrapper = Contact.FixtureB.Body.Tag
'    If BodyA.IsVisible = False Or BodyB.IsVisible = False Then Return
'    Log("#-  x649, BodyA.Name=" & BodyA.Name &", BodyB.Name=" & BodyB.Name)
End Sub

private Sub ActiveCharacterSelect_SelectedChange(Selected As Boolean) ' ####testingonly
    LastDiceRollVal = 0
    If Not(Selected) Then Return
    Dim rbx As RadioButton = Sender
    Dim tagx As String = rbx.Tag
    Select Case True
        Case tagx.ToLowerCase = "rbc1"
            ActiveCharIndex = 1
        Case tagx.ToLowerCase = "rbc2"
            ActiveCharIndex = 2
    End Select
End Sub

private Sub SwapPlayer
    Log("#-Sub game.SwapPlayer")
    If CharIsMoving Then Return
    
    If ActiveCharIndex =1 Then
        RadioButton2.Selected = True
    Else
        RadioButton1.Selected = True
    End If
    
'    ' ####testingonly
'    mChar1.bw.Body.FirstFixture.SetFilterBits(TextArea5.Text, TextArea6.Text)
'    mChar1.bw.Body.FirstFixture.Density = TextArea4.Text
'    mChar1.bw.Body.FirstFixture.Friction = TextArea3.Text
'    mChar1.bw.Body.FirstFixture.Restitution = TextArea1.Text
'    mChar2.bw.Body.FirstFixture.SetFilterBits(TextArea5.Text, TextArea6.Text)
'    mChar2.bw.Body.FirstFixture.Density = TextArea4.Text
'    mChar2.bw.Body.FirstFixture.Friction = TextArea3.Text
'    mChar2.bw.Body.FirstFixture.Restitution = TextArea1.Text
'    '/####testingonly
    
End Sub
private Sub Button1_Click ' ####testingonly
    Log("#-")
    Log("#-  x708, ROLL DICE ~~~~~ # ~~~~~ # ~~~~~ # ~~~~~ # ~~~~~ # ~~~~~ O")
    If CharIsMoving Then Return
    X2.SoundPool.PlaySound("rolldice")
    Sleep(200)
    LastDiceRollVal = Rnd(3, 7)
    Label2.Text = LastDiceRollVal
    ActiveCharAdvance(LastDiceRollVal)
End Sub

Private Sub UI_RefreshPlayerStates
    Log("#-Sub game.UI_RefreshPlayerStates")
    Label4.Text  = mChar1.ItemGet("collectedvalues", "0")
    Label12.Text = mChar2.ItemGet("collectedvalues", "0")
'    Log("#-  x747, " &  fcn.logm(mChar1.CollectedItemsMap))
'    Log("#-  x748, " &  fcn.logm(mChar2.CollectedItemsMap))
End Sub

private Sub ActiveCharAdvance(NumberOfSteps As Int)
    Log("#-Sub ActiveCharAdvance")
    ' Fill the CharPoints list that is used in Tick()
    
    Dim NewPointTemplatesListIndex As Int =    CharLastPositionIndex(ActiveCharIndex) +NumberOfSteps
    Dim GotToPosIndex As Int = NewPointTemplatesListIndex Mod PointTemplatesListLoop0.Size

    '####temp   
    Dim xy0 As PointXy = PointTemplatesListLoop0.Get(GotToPosIndex)
    Label5.Text = $"ActiveCharIndex=${ActiveCharIndex}, ${CRLF}DiceRollVal=${LastDiceRollVal}, ${CRLF}MoveCharTo p${(GotToPosIndex +1)}  = $1.0{xy0.x}, $1.0{xy0.y}  "$    '####test
    Log("#-  x593, Label5.Text = " & Label5.Text.Replace(CRLF, TAB))
    '/####temp   
    
    Dim TempList As List
    TempList.Initialize
    For i = CharLastPositionIndex(ActiveCharIndex) To NewPointTemplatesListIndex
        Dim GotToPosIndex As Int = i Mod PointTemplatesListLoop0.Size
        Dim xy0 As PointXy = PointTemplatesListLoop0.Get(GotToPosIndex)
        TempList.Add(X2.CreateVec2(xy0.x, xy0.y) )
    Next
    Select Case ActiveCharIndex
        Case 1
            Char1Points.Initialize2(TempList)
        Case 2
            Char2Points.Initialize2(TempList)
    End Select
    CharLastPositionIndex(ActiveCharIndex) = GotToPosIndex
End Sub

'private Sub Touch_Object_P(ft As X2FutureTask)
'    Dim Object_px As X2BodyWrapper = ft.Value
'    Log("#-Sub game.Touch_Object_P, Object_px.Name = " & Object_px.Name)
'    If Object_px.Name.ToLowerCase.StartsWith("p") Then
'        X2.SoundPool.PlaySound("stepstone_1")
'    Else If Object_px.Name.ToLowerCase.StartsWith("text") Then
'        X2.SoundPool.PlaySound("metal-clash")
'    End If
'End Sub


' The project questions & solution is continued here: https://www.b4x.com/android/forum/threads/x2-help-needed-regarding-creating-object-clones.109094/

Now I have to come up with something that triggers a fascinating animation on the coin on contact.
 

Attachments

Top