Games XUI2D - Collisions, or not..

eps

Expert
Licensed User
Longtime User
Okay, I've been trying to combine part of the Space Invaders example with the Space Shooter example and it's just not triggering the collisions between the space ship and the barriers and I just can't see why. I've looked at Tiled and am now happy that the bit mask and so on are correct and I've even set them manually to try to force them but for some reason it just isn't acting as expected.

I'm sure it's probably an obvious issue, but I just cannot fathom why right now and I've been looking at it for far too long!

Any thoughts?

Some of the code below and I've attached some of the source code, which is literally Space Shooter with the barrier code from Space Invaders placed in there. Including the json file for the objects..

B4X:
Public Sub Start
    If X2.IsRunning Then Return
    'Make sure that start was not called while the message appeared (this can happen in edge cases where the app moves to the background
        'and then quickly to the foreground several times).
    StartIndex = StartIndex + 1
    Dim MyIndex As Int = StartIndex
    Wait For (ShowMessage("Get Ready...")) Complete (Success As Boolean)
    If MyIndex <> StartIndex Then Return
    X2.Reset
    Multitouch.ResetState
    Dim ratio As Float = ivForeground.Width / ivForeground.Height
    WorldWidth = 6
    WorldHeight = WorldWidth / ratio
    X2.ConfigureDimensions(X2.CreateVec2(WorldWidth / 2, WorldHeight / 2), WorldWidth)
    'comment to disable debug drawing
'    X2.EnableDebugDraw
    TileMap.Initialize(X2, File.DirAssets, "space shooter.json", Null)
    'square tiles. The base size is the world width.
    TileMap.SetSingleTileDimensionsInMeters(WorldWidth / TileMap.TilesPerRow, WorldWidth / TileMap.TilesPerRow)
    TileMap.PrepareObjectsDef(ObjectLayer)
       
    lblMessages.Visible = False
    LeftBorder = TileMap.CreateObject2ByName(ObjectLayer, "left border")
    TileMap.CreateObject2ByName(ObjectLayer, "right border")
    Ship = TileMap.CreateObject2ByName(ObjectLayer, "ship")
    If xui.IsB4J Then
        Ship.Body.SetTransform(X2.CreateVec2(Ship.Body.Position.X, 0.5), 0)
    End If
    Ship.Body.LinearDamping = 4 'stop quickly
    ShipWidth = X2.GetShapeWidthAndHeight(Ship.Body.FirstFixture.Shape).X
    ScoreLabel1.SetValueNow(0)   
   
    CreateShields
   
    SetLives(3)
    ivForeground.Visible = True
    'motor is used in B4A and B4i to move the ship to the touch position.
    Dim motor As B2MotorJointDef
    motor.Initialize(LeftBorder.Body, Ship.Body)
    motor.MaxMotorForce = 0
    motor.CollideConnected = True
    ShipMotor = world.CreateJoint(motor)
    ShipMotor.CorrectionFactor = 0.2
    X2.Start
    InputsDisabled = False
End Sub

Sub World_BeginContact (Contact As B2Contact)
   
    Dim bc As X2BodiesFromContact = X2.GetBodiesFromContact(Contact, "fire")
   
    If bc <> Null And bc.OtherBody.Name.StartsWith("asteroid") Then
        X2.AddFutureTask2(Me, "Destroy_Asteroid", 0, bc, True)
    Else
        Log("fire")
        bc = X2.GetBodiesFromContact(Contact, "ship")
        If bc <> Null Then
            If bc.OtherBody.Name = "power" Then
                X2.AddFutureTask(Me, "Power_Up", 0, bc.OtherBody)
'            Else If bc.OtherBody.Name.StartsWith("asteroid") And IsProtected = False Then
'                X2.AddFutureTask(Me, "Destroy_Ship", 0, Null)
            End If
        End If
    End If
   
    If bc <> Null Then
        If bc.OtherBody.Name = "left edge" Then
            X2.AddFutureTask(Me, "change_direction", 0, True)
        Else If bc.OtherBody.Name = "right edge" Then
            X2.AddFutureTask(Me, "change_direction", 0, False)
        Else If bc.OtherBody.Name = "ship laser" Then
            X2.AddFutureTask(Me, "EnemyHit_WithMissle", 0, bc)
        Else If bc.OtherBody.Name = "ship" Then
            X2.AddFutureTask(Me, "ShipHit_WithEnemy", 0, bc)
        End If
    End If
'    bc = X2.GetBodiesFromContact(Contact, "enemy laser")
'    If bc <> Null And bc.OtherBody.Name = "ship" Then
'        X2.AddFutureTask(Me, "ShipHit_WithMissile", 0, bc)
'    End If

    bc = X2.GetBodiesFromContact(Contact, "shield")
   
    Log("shieldcontact1")
       
    If bc <> Null Then
        X2.AddFutureTask(Me, "Shield_Hit", 0, bc)
    End If

End Sub

Private Sub Shield_Hit (ft As X2FutureTask)
    Log("Shield_Hit")
    Dim bodies As X2BodiesFromContact = ft.Value
    If bodies.OtherBody.Name <> "enemy" Then
        bodies.OtherBody.Delete(X2.gs) 'delete the missile
    End If
    Dim shld As Shield = bodies.ThisBody.DelegateTo
    shld.Hit(bodies.ThisFixture)
End Sub

Shield Code Module

B4X:
Sub Class_Globals
    Public bw As X2BodyWrapper
    Private x2 As X2Utils 'ignore
    Private bc As BitmapCreator
    Private ShapeSize As B2Vec2
    Private cbc As CompressedBC
    Private HalfWidth, HalfHeight As Float
    Private xui As XUI
End Sub

Public Sub Initialize (wrapper As X2BodyWrapper)
    bw = wrapper
    x2 = bw.X2
    bw.DelegateTo = Me
    ShapeSize = x2.GetShapeWidthAndHeight(bw.Body.FirstFixture.Shape)
    bc.Initialize(x2.MetersToBCPixels(ShapeSize.X), x2.MetersToBCPixels(ShapeSize.Y))
    bc.FillGradient(Array As Int(0xFF006016, 0xFF92F7AA), bc.TargetRect, "RECTANGLE")
    'remove the large shape and create smaller blocks
    bw.Body.DestroyFixture(bw.Body.FirstFixture)
    Dim fd As B2FixtureDef
    Dim rect As B2PolygonShape
    rect.Initialize
    fd.Shape = rect
    Dim BlocksPerRow = 6, BlocksPerColumn = 3 As Int
    HalfWidth = ShapeSize.X / BlocksPerRow / 2
    HalfHeight = ShapeSize.Y / BlocksPerColumn / 2
    fd.Friction = 0
    fd.IsSensor = True
   
    For y = 0 To BlocksPerColumn - 1
        For x = 0 To BlocksPerRow - 1
            Dim position As B2Vec2 = x2.CreateVec2(-ShapeSize.X / 2 + (x * 2 + 1) * HalfWidth, -ShapeSize.Y / 2 + (y * 2 + 1) * HalfHeight)
            rect.SetAsBox2(HalfWidth, HalfHeight, position, 0)
            Dim fixture As B2Fixture = bw.Body.CreateFixture(fd)
'            bw.Body.FirstFixture.SetFilterBits(4,65535)
            fixture.Body.FirstFixture.SetFilterBits(4,65535)
            'we save the x and y indices. Later they will be used to clear the broken fixtures.
            fixture.Tag = Array As Float(x / BlocksPerRow, y / BlocksPerColumn)
        Next
    Next
    UpdateCBC
End Sub

Public Sub Hit(Fixture As B2Fixture)
    Log("shield hit")
    Dim xy() As Float = Fixture.Tag
    bw.Body.DestroyFixture(Fixture)
    Dim rect As B4XRect
    Dim width As Int = x2.MetersToBCPixels(HalfWidth * 2)
    Dim height As Int = x2.MetersToBCPixels(HalfHeight * 2)
    'need to turn the y position upside down.
    rect.Initialize(xy(0) * bc.mWidth - 1, bc.mHeight - height - xy(1) * bc.mHeight, 0, 0)
    rect.Width = width + 2
    rect.Height = height + 2
    bc.FillRect(xui.Color_Transparent, rect)
    UpdateCBC
End Sub

Private Sub UpdateCBC
    cbc = bc.ExtractCompressedBC(bc.TargetRect, x2.GraphicCache.CBCCache)
End Sub

Public Sub Tick (GS As X2GameStep)
    If GS.ShouldDraw Then
        GS.DrawingTasks.Add(x2.CreateDrawTaskFromCompressedBC(cbc, x2.WorldPointToMainBC(bw.Body.Position.X, bw.Body.Position.Y), cbc.TargetRect))
    End If
End Sub

If I bang my head any more on this I'll pass out!!

ETA : I've already looked at this https://www.b4x.com/android/forum/threads/xui2d-to-collide-or-not-to-collide.97144/
 

Attachments

  • Space Shooter.zip
    147 KB · Views: 176

eps

Expert
Licensed User
Longtime User
Better to upload the full project. Use the comment link at the top of B4XMainPage.

I recommend you to start with the B4J implementation. It is easier to debug.
Hi Erel, thanks for responding. I did try to upload the full project but it was too big for the site, but was only 3MB or so..

I'll give B4J a go and see if the issue is more obvious via that route
 

eps

Expert
Licensed User
Longtime User

Erel

B4X founder
Staff member
Licensed User
Longtime User
There are several issues here:
1. "Collisions happen between dynamic bodies and other bodies (including other dynamic bodies)."
If the laser is kinematic and the shield is static then no collision will happen.
Change the laser type to dynamic. Set its density to 100 to give it a lot of energy.

2. The shields are created with multiple fixtures. This is done in Shield.Initialize. You want to set the category bit to 4:
B4X:
fd.SetFilterBits(4, 0xffff)

3. If you want the laser to hit everything except of the ship (category = 1) then its mask should be: 65535 - 1 = 65534

1646290734330.png
 

eps

Expert
Licensed User
Longtime User
Thanks Erel - as always you're right on the mark with this, thank you for explaining it in simple enough terms for me to understand!!

I'll make some changes later on and hopefully get moving forward on this :)
 

ilan

Expert
Licensed User
Longtime User
Set its density to 100 to give it a lot of energy
why erel?
he can set the laser body to .isSensor = true and no need to set high density. like this, it does not matter how much the body weight and it will always move as expected.

what density does it gives bodies a lot of weight(mass) and like this if a dynamic body with very high density hits another dynamic body with low density it will not be affected by the collision. you can think of it as a tank driving through a wall made of paper.

i would also consider setting isBullet = true to get a more precise collision.

EDIT: I need to mention that I don't like that the drawing still appears after the bullet hits the shield for another few frames. i guess it has something to do with the
AddFutureTask method. but I am not sure.
in my games, I normally follow these steps:
- move bodies
- check for collision
- remove bodies if hit/out of frame
- draw sprites

like this, I don't get frames drawn after the body has been destroyed
 

Attachments

  • SpaceShooterChangedB4Jonlyv2.zip
    310.3 KB · Views: 163
Last edited:
  • Like
Reactions: eps

ilan

Expert
Licensed User
Longtime User
The laser body type was set to kinematic. I suggested to change it to dynamic, so collisions will work and make it heavy so it will behave similar to a kinematic body.
yes, i understand what you wanted to do and i agree that setting the laser body to dynamic is correct. you can also set the gravity scale to 0 so it is not affected by gravity.
about setting the density it depends on the game behavior you want to archive.
if the bullet the hits should move other bodies like a cannon ball hitting a wall then you are right setting the density to high will give you that effect. but in this game it only should hit another body and get destroyed so in my opinion it is better to set the bullet to isSensor=true + isBullet=true and this should be better then setting the density.
normally a dynamic body is moved via forces and not velocity. setting the density to high will require apply more force on the body. kinematic bodies are moved via velocity.

i just want to mention that it is not a critic erel. it is just another approach. your suggestion will work too.

this is worth a read: https://rotatingcanvas.com/arrows-and-bullets-in-box2d/
 
  • Like
Reactions: eps
Top