VBfx / Tile tutorial / Step 5

Random movement

To bring life into the sample code I added a small function that provides random movement for the units. You don't have to understand the code but it's really easy. Basically it moves all units in their current direction and changes the direction from time to time. Here's the code:

Public Sub MoveRandomly()
    Dim A as Long
    
    Dim MoveX as Long
    Dim MoveY as Long
    
    For A = 2 To UnitCount
        'Reset
        MoveX = 0
        MoveY = 0
        
        With Unit( A )
            'Set random direction
            If 100 * Rnd < 10 Then: .Direction = CLng( 3 * Rnd )
            
            'Get movement
            Select Case .Direction
                Case 0: MoveY = -1 'Up
                Case 1: MoveX = 1 'Right
                Case 2: MoveY = 1 'Down
                Case 3: MoveX = -1 'Left
            End Select
            
            'Set new position
            MoveUnit A, ( .X + MoveX ), ( .Y + MoveY )
        End With
    Next
End Sub

Collision checks

Now that we have some movement it's time to implement the collision checks. Since we already made a MoveUnit function we can just extend it to check the requested position before moving the unit. Note that we have to do 2 different collision checks, unit-to-map and unit-to-unit. The first one checks if the tile is walkable and the second checks if there's already a unit registered. If one of the checks fails the function simply exits without moving the unit at all.

Here again I'll first show you the code, then go through it step by step:

Public Sub MoveUnit( Index as Long, NewX as Long, NewY as Long )
    Dim TempIndex as Long
    
    'Check boundaries
    If NewX < 0 Then: NewX = 0
    If NewY < 0 Then: NewY = 0
    If NewX > ( ( Map.W - 1 ) * TileSize ) Then: NewX = ( ( Map.W - 1 ) * TileSize )
    If NewY > ( ( Map.H - 1 ) * TileSize ) Then: NewY = ( ( Map.H - 1 ) * TileSize )
    
    'Get new map index
    TempIndex = ( CLng( NewY / TileSize ) * Map.W ) + CLng( NewX / TileSize )
    
    With Unit( Index )
        If Not TempIndex = .MapIndex Then
            'Unit-to-map collision check
            If Not Tile( Map.Floor( TempIndex ) ).Walkable Then: Exit Sub
            
            'Unit-to-unit collision check
            If Map.Units( TempIndex ) > 0 Then: Exit Sub
        End If
        
        'Unregister from map
        If .MapIndex > -1 Then: Map.Units( .MapIndex ) = 0
        
        'Apply new position
        .X = NewX
        .Y = NewY
        .MapIndex = TempIndex
        
        'Register to map
        Map.Units( .MapIndex ) = Index
    End With
End Sub

Code discussion

Here we go.

Public Sub MoveUnit( Index as Long, NewX as Long, NewY as Long )
    Dim TempIndex as Long
    
    'Check boundaries
    If NewX < 0 Then: NewX = 0
    If NewY < 0 Then: NewY = 0
    If NewX > ( ( Map.W - 1 ) * TileSize ) Then: NewX = ( ( Map.W - 1 ) * TileSize )
    If NewY > ( ( Map.H - 1 ) * TileSize ) Then: NewY = ( ( Map.H - 1 ) * TileSize )

This all stayed the same except that we need a tempoary index this time. Explanation following below.

    'Get new map index
    TempIndex = ( CLng( NewY / TileSize ) * Map.W ) + CLng( NewX / TileSize )

Instead of just moving the unit and setting the new index we get the requested map index here. This is because we need to check that position first before unregistering or moving anything.

    With Unit( Index )
        If Not TempIndex = .MapIndex Then

This line checks if the unit moved to another map position (tile). Othewise we don't have to double-check the tile and can skip the following collision tests.

            'Unit-to-map collision check
            If Not Tile( Map.Floor( TempIndex ) ).Walkable Then: Exit Sub

As the comment says this line checks the tile if it's walkable or not. If not we just exit the code without moving the unit at all.

            'Unit-to-unit collision check
            If Map.Units( TempIndex ) > 0 Then: Exit Sub

This second check uses the units map to see if the new position already contains another unit. This is exactly the line what we made this map for.

If the unit didn't yet leave it's current tile, this check would fail returning the unit's index because at this point it's still registered to that tile! This is another reason for checking if the unit moved on to another tile or not.

        End If
        
        'Unregister from map
        If .MapIndex > -1 Then: Map.Units( .MapIndex ) = 0
        
        'Apply new position
        .X = NewX
        .Y = NewY
        .MapIndex = TempIndex

Now that the new position is confirmed we can unregister the unit from the current position and set the new coodinates. Also we can now store the new map index and use it in the following line to register the unit at the new position:

        'Register to map
        Map.Units( .MapIndex ) = Index
    End With
End Sub

Finally the unit is re-registered at the new position.

Conclusion

Now all the work gets paid and we can use only 1 line to perform the whole unit-to-unit collision check. And since the tile map has the same size as the unit map we can do the unit-to-map collision check without even re-calculating the map index. At this point your engine provides more or less all the collisions you need so run the sample code to see it working.

Navigation