Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Enemy movement Proof Of Concept
#1
I'm trying to get a POC working where enemies cannot move through each other. This draws on a hidden collision map, but I'm open to better ways of collision detection. 

It works quite well when all have the same low speed, but fails at higher speeds and/or when speeds are random. (Controlled by the 1st constant)

Cursor keys to move, E to show collision map, S for normal view. 

Code: (Select All)

' Enemy movement POC
Randomize Timer

' General constants
Const NME_Speed = 6 ' Range -5 to 6 - Negative for absolute N, positive for random 0 to N-1
Const MAX_NME = 254 ' Max enemies
Const GRID_WIDTH = 39 ' Columns = N + 1
Const GRID_HEIGHT = 24 ' Rows = N + 1
Const CELL_WIDTH = 32 ' X pixels per tile
Const CELL_HEIGHT = 32 ' Y pixels per tile - add separate X and Y Lo and Hi lookups if not square


' Enemy states
Const Dead = 0
Const Still = 1
Const Displayed = 2
Const Mapped = 3
Const Moved = 4

' Cursor Keys - Not QB64 compatible, PE only
Const KeyUp = Chr$(0) + "H"
Const keyDown = Chr$(0) + "P"
Const KeyLeft = Chr$(0) + "K"
Const keyRight = Chr$(0) + "M"

' Player data structure
Type t_plr
    X As Integer ' X Position
    Y As Integer ' Y Position
    D As Integer ' Direcition
End Type

' Enemy data structure
Type t_nme
    X As Integer ' X Position
    Y As Integer ' Y Position
    S As Integer ' State
    D As Integer ' Direction
    M As Integer ' Max Speed
    R As Integer ' Real speed
End Type

' Map data structure
Type t_grid
    I As Integer ' Map item
    N As Integer ' Enemy ID
End Type

' Grid lookup data structure
Type t_lookup
    Lo As Integer ' Left & top
    Hi As Integer ' Right & bottom
End Type

' Direction data structure
Type t_vector
    X As Integer ' Horizontal delta
    Y As Integer ' Vertical detla
End Type

' Shared arrays and variables
Dim Shared PLR As t_plr
Dim Shared NME(1 To MAX_NME) As t_nme
Dim Shared GD(GRID_WIDTH, GRID_HEIGHT) As t_grid
Dim Shared LU(maxint(GRID_WIDTH, GRID_HEIGHT)) As t_lookup
Dim Shared Dir(5) As t_vector
Dim Shared Speed(5) As Integer
Dim Shared Main_Screen As Long, NME_Screen As Long
Dim Shared GFX(4) As Long

' Create bitmaps
Main_Screen = _NewImage((GRID_WIDTH + 1) * CELL_WIDTH, (GRID_HEIGHT + 1) * CELL_HEIGHT, 13)
NME_Screen = _NewImage((GRID_WIDTH + 1) * CELL_WIDTH, (GRID_HEIGHT + 1) * CELL_HEIGHT, 13)

' Setup screen
Screen Main_Screen
' Sleep 1

' Read or calculate data tables
ReadVectors
ReadGFX
CalcLookup


' Reset
ResetGrid
ResetPLR
ResetNme

mode = 0

DisplayGrid
_Display

Do Until k$ = Chr$(27)
    MapCollisions
    MoveNME
    DisplayGrid
    DrawNME
    Line (PLR.X, PLR.Y)-Step(CELL_WIDTH, CELL_HEIGHT), 9, BF ' Draw player
    If mode = 1 Then
        _PutImage (0, 0), NME_Screen, Main_Screen
    End If
    _Limit 120
    _Display

    k$ = InKey$

    Select Case k$
        Case "s", "S"
            mode = 0
        Case "e", "E"
            mode = 1
        Case KeyUp
            UpdatePlayer (1)
        Case keyDown
            UpdatePlayer (3)
        Case KeyLeft
            UpdatePlayer (4)
        Case keyRight
            UpdatePlayer (2)
    End Select
Loop

System

' Direction Vectors
DV_Data:
Data 0,0,0,-1,1,0,0,1,-1,0

' Move speeds
Data 1,2,4,6,8,10

' Enemy image data
EI_Data:
' Still
Data "33000000000000000000000000000033"
Data "31111111111111111111111111111113"
Data "01111111111111111111111111111110"
Data "01111111111111111111111111111110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01110000000000000000000000001110"
Data "01111111111111111111111111111110"
Data "01111111111111111111111111111110"
Data "31111111111111111111111111111113"
Data "30000000000000000000000000000033"

' Up
Data "33000000000000000000000000000033"
Data "30000000000000011000000000000003"
Data "00000000000000111100000000000000"
Data "00000000000001111110000000000000"
Data "00000000000011111111000000000000"
Data "00000000000111111111100000000000"
Data "00000000001111111111110000000000"
Data "00000000011111111111111000000000"
Data "00000000111111111111111100000000"
Data "00000001111111111111111110000000"
Data "00000011111111111111111111000000"
Data "00000111111111111111111111100000"
Data "00001111111111111111111111110000"
Data "00011111111111111111111111111000"
Data "00111111111111111111111111111100"
Data "01111111111111111111111111111110"
Data "01111111111111111111111111111110"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "30000000000011111111000000000003"
Data "33000000000000000000000000000033"

' Right
Data "33000000000000000000000000000033"
Data "30000000000000011000000000000003"
Data "00000000000000011100000000000000"
Data "00000000000000011110000000000000"
Data "00000000000000011111000000000000"
Data "00000000000000011111100000000000"
Data "00000000000000011111110000000000"
Data "00000000000000011111111000000000"
Data "00000000000000011111111100000000"
Data "00000000000000011111111110000000"
Data "00000000000000011111111111000000"
Data "00000000000000011111111111100000"
Data "01111111111111111111111111110000"
Data "01111111111111111111111111111000"
Data "01111111111111111111111111111100"
Data "01111111111111111111111111111110"
Data "01111111111111111111111111111110"
Data "01111111111111111111111111111100"
Data "01111111111111111111111111111000"
Data "01111111111111111111111111110000"
Data "00000000000000011111111111100000"
Data "00000000000000011111111111000000"
Data "00000000000000011111111110000000"
Data "00000000000000011111111100000000"
Data "00000000000000011111111000000000"
Data "00000000000000011111110000000000"
Data "00000000000000011111100000000000"
Data "00000000000000011111000000000000"
Data "00000000000000011110000000000000"
Data "00000000000000011100000000000000"
Data "30000000000000011000000000000003"
Data "33000000000000000000000000000033"

' Down
Data "33000000000000000000000000000033"
Data "30000000000011111111000000000003"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "00000000000011111111000000000000"
Data "01111111111111111111111111111110"
Data "01111111111111111111111111111110"
Data "00111111111111111111111111111100"
Data "00011111111111111111111111111000"
Data "00001111111111111111111111110000"
Data "00000111111111111111111111100000"
Data "00000011111111111111111111000000"
Data "00000001111111111111111110000000"
Data "00000000111111111111111100000000"
Data "00000000011111111111111000000000"
Data "00000000001111111111110000000000"
Data "00000000000111111111100000000000"
Data "00000000000011111111000000000000"
Data "00000000000001111110000000000000"
Data "00000000000000111100000000000000"
Data "30000000000000011000000000000003"
Data "33000000000000000000000000000033"

' Left
Data "33000000000000000000000000000033"
Data "30000000000000011000000000000003"
Data "00000000000000111000000000000000"
Data "00000000000001111000000000000000"
Data "00000000000011111000000000000000"
Data "00000000000111111000000000000000"
Data "00000000001111111000000000000000"
Data "00000000011111111000000000000000"
Data "00000000111111111000000000000000"
Data "00000001111111111000000000000000"
Data "00000011111111111000000000000000"
Data "00000111111111111000000000000000"
Data "00001111111111111111111111111110"
Data "00011111111111111111111111111110"
Data "00111111111111111111111111111110"
Data "01111111111111111111111111111110"
Data "01111111111111111111111111111110"
Data "00111111111111111111111111111110"
Data "00011111111111111111111111111110"
Data "00001111111111111111111111111110"
Data "00000111111111111000000000000000"
Data "00000011111111111000000000000000"
Data "00000001111111111000000000000000"
Data "00000000111111111000000000000000"
Data "00000000011111111000000000000000"
Data "00000000001111111000000000000000"
Data "00000000000111111000000000000000"
Data "00000000000011111000000000000000"
Data "00000000000001111000000000000000"
Data "00000000000000111000000000000000"
Data "30000000000000011000000000000003"
Data "33000000000000000000000000000033"

' Precalculate cell coordinates in a lookup table
Sub CalcLookup
    For i = 0 To maxint(GRID_WIDTH, GRID_HEIGHT)
        LU(i).Lo = i * CELL_WIDTH
        LU(i).Hi = LU(i).Lo + CELL_WIDTH - 1
    Next i
End Sub

' Load direction vectors and speeds
Sub ReadVectors
    For I = 0 To 4
        Read Dir(I).X, Dir(I).Y
    Next I
    For I = 0 To 5
        Read Speed(I)
    Next I
End Sub

' Create enemy images
Sub ReadGFX
    '    _Display
    Restore EI_Data
    For I = 0 To 4
        GFX(I) = _NewImage(32, 32, 13)
        _Dest GFX(I)
        For y = 0 To 31
            Read l$
            For x = 0 To 31
                z = Val(Mid$(l$, x + 1, 1)) * 4
                '                _Title Str$(I) + Str$(x) + Str$(y) + Str$(z)
                PSet (x, y), z
            Next x
        Next y
    Next I
End Sub

' Simple max function
Function maxint (mx As Integer, my As Integer)
    If mx < my Then
        maxint = my
    Else
        maxint = mx
    End If
End Function

' Convert screen x coordinate to grid column
Function GridX (X As Integer)
    GridX = X \ CELL_WIDTH
End Function

' Convert screen y coordinate to grid row
Function GridY (Y As Integer)
    GridY = Y \ CELL_HEIGHT
End Function


' Wipe the map
Sub ResetGrid
    For gx = 0 To GRID_WIDTH ' Loop through columns
        For gy = 0 To GRID_HEIGHT ' Loop through rows
            GD(gx, gy).I = 0 ' Clear item entry
            GD(gx, gy).N = 0 ' Clear enemy ID

            GD(gx, 0).I = 5 ' Set left boarder
            GD(gx, GRID_HEIGHT).I = 5 ' Set right boarder

            GD(0, gy).I = 5 ' Set Left edge
            GD(GRID_WIDTH, gy).I = 5 ' Set right edge

            GD(gy + 3, 7).I = 6 ' Place a horizontal wall - using GY in the column was deliberate to save an If statement
            If gy + 5 < GRID_HEIGHT Then GD(5, gy + 5).I = 6 ' Place  Vertical wall - can't use the above shortcut here 
        Next gy
    Next gx
End Sub

' Position the player in the grid and screen
Sub ResetPLR
    X = 10 ' Grid column
    Y = 10 ' Grid row
    GD(X, Y).N = -1 ' Place the player
    PLR.X = LU(X).Lo ' Set player x position
    PLR.Y = LU(Y).Lo ' Set player y position
    PLR.D = 0 ' Set direction
End Sub

' Randomize enemy locations
Sub ResetNme
    For c = 1 To MAX_NME
        Do
            gx = Int(Rnd(1) * (GRID_WIDTH - 1)) + 1 ' Random X
            gy = Int(Rnd(1) * (GRID_HEIGHT - 1)) + 1 ' Random Y
        Loop Until (GD(gx, gy).I + Abs(GD(gx, gy).N)) = 0 ' Loop if not clear of walls, other enemies and the player
        GD(gx, gy).N = c ' Assign enemy to grid
        NME(c).X = LU(gx).Lo ' Lookup screen X position
        NME(c).Y = LU(gy).Lo ' Lookup screen Y position
        NME(c).S = Still ' Set status
        NME(c).D = Int(Rnd(1) * 5) ' Pick a random direction

        If NME_Speed < 1 Then
            NME(c).M = Abs(NME_Speed) ' Fixed spped for all
        Else
            NME(c).M = Int(Rnd(0) * NME_Speed) ' Random speeds
        End If
    Next c
End Sub

' Draw map and enemies
Sub DisplayGrid
    _Dest Main_Screen ' Set GFX output
    Cls ' Clear output
    For GY = 0 To GRID_HEIGHT ' Loop through rowns
        For GX = 0 To GRID_WIDTH ' Loop through columns
            I = GD(GX, GY).I ' Fetch cell item
            '            N = GD(GX, GY).N ' Fetch cell enemy - Potentially occupying up to 4 cells

            Select Case I ' Process grid items
                Case 5, 6 ' It's a wall
                    Line (LU(GX).Lo, LU(GY).Lo)-(LU(GX).Hi, LU(GY).Hi), I, BF ' Draw walls
                Case Else
            End Select

        Next GX
    Next GY
    '    _Display ' Update screen
End Sub

' Draw Enemies
Sub DrawNME
    _Dest Main_Screen ' Set GFX output

    For N = 1 To MAX_NME ' Loop through enemies
        ES = NME(N).S

        If ES > Dead Then ' Check status
            EX = NME(N).X ' Get x
            EY = NME(N).Y ' Get Y
            ED = NME(N).D ' Get Direction
            _PutImage (EX, EY), GFX(ED) ' Draw enemy sprite
            NME(N).S = Displayed ' Update status

        End If
    Next N
    Line (PLR.X, PLR.Y)-Step(CELL_WIDTH, CELL_HEIGHT), 9, BF ' Draw player
End Sub


' Populate enemy collision map
Sub MapCollisions
    _Dest NME_Screen ' Set GFX output
    Cls ' Clear map image



    ' Add player first
    For X = 0 To 31
        Line (PLR.X + X, PLR.Y)-Step(0, CELL_HEIGHT - 1), 255 - X ' Draw player on collision map
    Next X
    '
    ' Add obsticles
    For GY = 0 To GRID_HEIGHT ' Loop through rowns
        For GX = 0 To GRID_WIDTH ' Loop through columns
            I = GD(GX, GY).I ' Fetch cell item

            Select Case I ' Process grid items
                Case 5, 6 ' It's a wall
                    Line (LU(GX).Lo, LU(GY).Lo)-(LU(GX).Hi, LU(GY).Hi), I, BF ' Draw walls
                Case Else
            End Select

        Next GX
    Next GY

    For N = 1 To MAX_NME ' Loop through enemies
        ES = NME(N).S

        If ES > Dead Then ' Check status
            EX = NME(N).X ' Get x
            EY = NME(N).Y ' Get Y
            Line (EX, EY)-Step(CELL_WIDTH - 1, CELL_HEIGHT - 1), N, BF ' Draw on colision map
            NME(N).S = Mapped ' Update status

            For X = GridX(EX) To GridX(EX + CELL_WIDTH - 1) ' Loop through left and grid coordinates
                For y = GridY(EY) To GridY(EY + CELL_HEIGHT - 1) ' Loop through to and bottom grid coordinates
                    GD(X, y).N = N 'Assign enemy to grid position(s)                                    n
                Next y
            Next X
        End If
    Next N
End Sub

' Check
Sub MoveNME
    _Source NME_Screen ' Set GFX input
    _Dest NME_Screen ' Set GFX output
    CW = CELL_WIDTH - 1
    CH = CELL_HEIGHT - 1

    For N = 1 To MAX_NME ' Loop through enemies
        If NME(N).S = Mapped Then ' Check status
            D = 0 ' Enemy will be still
            EX = NME(N).X ' Get current enemy X
            EY = NME(N).Y ' Get current enemy y
            For TS = NME(N).M To 0 Step -1 ' Loop enemy max speed down to minimum
                ER = Speed(TS) ' Get speed
                If D = 0 Then
                    XD = PLR.X - EX ' Calucate X offset to player
                    If Abs(XD) > ER Then ' Check enemy wont overshoot
                        Select Case XD ' Does it need to go East or West
                            Case Is < 0 ' Enemy needs to go west
                                If CheckNME(EX - ER, EY, EX - 1, EY + CH) = 0 Then D = 4 ' Turn West if clear
                            Case Is > 0 ' Enemy needs to go East        7
                                If CheckNME(EX + CELL_WIDTH, EY, EX + CW + ER, EY + CH) = 0 Then D = 2 ' Turn East if clear
                        End Select
                    End If
                End If

                If D = 0 Then ' Check if blocked horizontally
                    YD = PLR.Y - EY ' Calculate Y offset to player
                    If Abs(YD) > ER - 1 Then ' Check enemy wont overshoot
                        Select Case YD ' North or South?
                            Case Is < 0 ' North
                                If CheckNME(EX, EY - ER, EX + CW, EY - 1) = 0 Then D = 1 ' Turn North
                            Case Is > 0 ' South
                                If CheckNME(EX, EY + CELL_HEIGHT, EX + CW, EY + CH + ER) = 0 Then D = 3 ' Turn South
                        End Select
                    End If
                End If

                If D > 0 Then ' Check for movement
                    EX = EX + Dir(D).X * ER ' Calcuate new X
                    EY = EY + Dir(D).Y * ER ' Cacluate new Y
                    Line (EX, EY)-Step(CW, CH), N, BF ' Update colision map

                    NME(N).X = EX ' Update enemy X
                    NME(N).Y = EY ' Update enemy Y

                End If

                NME(N).D = D ' Update direction              e
                NME(N).S = Moved ' Update status
            Next TS
        End If
    Next N
    '    _Title t$
End Sub

' Check enemy collision map for empty space
Function CheckNME (TX As Integer, TY As Integer, BX As Integer, BY As Integer)
    DefInt P, X, Y

    P = 0 ' Pixel value at x,y
    X = TX ' Left
    Do Until (X > BX) Or (P > 0) ' Loop while in horizontal range and previous pixel is zero
        Y = TY ' Top
        Do Until (Y > BY) Or (P > 0) ' Loop while in vertical range and previous pixel is zero
            P = Point(X, Y) ' Read pixel
            Y = Y + 1 ' Move down
        Loop
        X = X + 1 ' Move right
    Loop
    CheckNME = P ' Return last pixel value
End Function


' Move player and update collision map
Sub UpdatePlayer (d As Integer)
    _Dest NME_Screen ' Set output
    XL = PLR.X ' Caculate left coordinate
    XR = XL + CELL_WIDTH - 1 ' Calculate right coordinate
    YT = PLR.Y ' Calculate top coordinate
    YB = YT + CELL_HEIGHT - 1 ' Calculate bottom coordinate
    SP = Speed(4)
    For i = 0 To -1 Step -1 ' Loop to erase then place
        For x = XL \ CELL_WIDTH To XR \ CELL_WIDTH ' Loop through x locations
            For y = YT \ CELL_HEIGHT To YB \ CELL_HEIGHT ' Loop through y locations
                GD(x, y).N = i ' Update grid
            Next y
        Next x

        If i = 0 Then ' Update player coordinate after erase pass
            H = PLR.X + Dir(d).X * SP ' New player x position
            If (H > SP) And (H < GRID_WIDTH * CELL_WIDTH - SP) Then PLR.X = H ' Update if inside the screen
            V = PLR.Y + Dir(d).Y * SP ' New player Y position
            If (V > SP) And (V < GRID_HEIGHT * CELL_HEIGHT - SP) Then PLR.Y = V
        End If
    Next i
End Sub
Reply
#2
An accurate collision detection would be with: a for loop, an initial position and a final position
where, the for loop loops from the initial position to the final position and checks if there is an obstacle in between...
much similar to raycasting, do optimizing algorithms for this is the DDA.
You can use KeyUp = "W"... and reassign "s" -> "x" (or toggle with "e")
Reply




Users browsing this thread: 1 Guest(s)