06-02-2025, 10:55 PM
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.
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