I did this test the last weekend,
I'm using sprites as waypoints to change the direction of the "enemy" sprite, so emulating some AI where the mobs change the direction not only if they collide with the wall -checking the current tile +/- 1- but decide to change the direction pressing the waypoint sprite.
Some code as example, it has a little state machine
// Project: OhNoes
// Created: 2018-03-28
// UDT
Type tCell
x as integer
y as integer
value as integer // Walkable = 0
// Wall = 1
// Room = 2
// Waypoint = 3
EndType
Type tLevel
treasures as tTreasure[MAX_TREASURES_BOXES]
enemies as integer
EndType
Type tTreasure
id as integer
name as string
points as integer
treasureType as integer
EndType
Type tEnemy
spriteId as integer
direction as integer
group as integer
count as integer
collided as integer
debugSprite as integer
x as integer
y as integer
EndType
Type tPlayer
spriteId as integer
direction as integer
x as integer
y as integer
EndType
#constant KEY_C 67
#constant MAX_TIME 30
#constant MAX_LEVELS 10
#constant MAX_LIFES 6
#constant CELL_SIZE 16
#constant MAX_TREASURES 20
#constant MAX_TREASURES_BOXES 3
#constant WORLD_SIZE_X 22 // Must match with the static data atm
#constant WORLD_SIZE_Y 14 // TODO: Retrieve dinamically
#constant MAX_ENEMIES = MAX_LEVELS
#constant STATIC_FILE = "static.txt"
#constant MIN_TREASURE_POINTS 10
#constant MAX_TREASURE_POINTS 50
#constant ENEMIES_GROUP 1
#constant WALLS_GROUP 2
#constant TREASURES_GROUP 3
#constant WAYPOINTS_GROUP 4
#constant ENEMIES_START_X 1
#constant ENEMIES_START_Y 6
#constant PLAYER_START_X 9
#constant PLAYER_START_Y 1
#constant STARTING_ENEMIES 4 // CHANGE THIS ;)
// The values coming from DATA file
#constant WALL_CELL_TYPE_ID = 1
#constant TREASURE_CELL_TYPE_ID = 2
global gameStatus as integer // 0 = Stopped / 1 = Started / 2 = Ended
global currentPoints as integer
global currentTime as integer
global currentLevel as integer
global currentLifes as integer
global currentScene as integer
global currentEnemies as integer
global worldX as integer
global worldY as integer
global player as tPlayer
global Enemies as tEnemy[]
global staticMap as tCell[WORLD_SIZE_X, WORLD_SIZE_Y]
global levels as tLevel[MAX_LEVELS]
global Treasures as tTreasure[]
global textMenuId as integer
// show all errors
SetErrorMode(2)
// set window properties
SetWindowTitle( "OhNoes" )
SetWindowSize( WORLD_SIZE_X * CELL_SIZE, WORLD_SIZE_Y * CELL_SIZE , 0 )
SetWindowAllowResize( 1 )
SetOrientationAllowed( 1, 1, 1, 1 )
SetSyncRate( 30, 0 )
SetScissor( 0,0,0,0 )
SetClearColor(255, 125, 0)
worldX = WORLD_SIZE_X * CELL_SIZE
worldY = WORLD_SIZE_Y * CELL_SIZE
SetVirtualResolution( worldX + CELL_SIZE * 2, worldY + CELL_SIZE * 2)
// TODO UI
//AddVirtualButton(1,CELL_SIZE, CELL_SIZE, CELL_SIZE)
//AddVirtualButton(2,CELL_SIZE * 2, CELL_SIZE, CELL_SIZE)
//AddVirtualButton(3,CELL_SIZE * 3, CELL_SIZE, CELL_SIZE)
LoadMusic(1,"Maf464.mp3")
do
PrintStats()
CheckButtons()
// State Machine
select currentScene:
case 0:
// Background
LoadStaticMap()
DrawStaticMap()
currentScene = 1
endcase
case 1:
GetStartMenu()
//TODO DrawBackgroundDemo()
currentScene = 2
endcase
case 2:
if GetRawKeyPressed(KEY_C)
DeleteAllText()
currentScene = 3
endif
endcase
case 3:
// Level loading
currentTime = MAX_TIME
GenerateLevel(currentLevel)
if not currentEnemies
currentEnemies = STARTING_ENEMIES
endif
SpawnEnemies()
//TODO SpawnPlayer()
currentScene = 4
endcase
case 4:
UpdateLevel()
in = GetUserInput()
HandleUserInput(in)
endcase
case 4:
if (currentLevel > MAX_LEVELS)
GameEnds()
else
GoNextLevel()
endif
endcase
endselect
Sync()
loop
Function ToggleWaypoints()
Endfunction
Function CheckButtons()
// TODO UI
Remstart
if GetVirtualButtonPressed(1)
PlayMusic(1)
elseif GetVirtualButtonPressed(2)
StopMusic()
elseif GetVirtualButtonPressed(3)
ToggleWaypoints()
endif
Remend
EndFunction
Function UpdateLevel()
UpdateEnemies()
UpdatePlayer()
Endfunction
Function GameEnds()
Endfunction
Function GoNextLevel()
currentLevel = currentLevel + 1
Endfunction
Function UpdatePlayer()
//TODO
EndFunction
Function UpdateEnemies()
for i=0 to currentEnemies - 1
UpdateEnemy(i)
next
EndFunction
Function SpawnEnemies()
for i=1 to currentEnemies
enemyId = CreateSprite(0)
startX = Round(CELL_SIZE * ENEMIES_START_X)
startY = Round(CELL_SIZE * ENEMIES_START_Y)
SetSpriteColor(enemyId, 255, 255, 0, 255)
SetSpriteSize(enemyId, CELL_SIZE, CELL_SIZE)
SetSpritePosition(enemyId, startX, startY )
SetSpriteGroup(enemyId, ENEMIES_GROUP)
//For debug purposes
//markSpriteId = CreateSprite(0)
//SetSpriteColor(markSpriteId, 155, 155, 155, 255)
//SetSpriteSize(markSpriteId, CELL_SIZE, CELL_SIZE)
//SetSpritePosition(markSpriteId,startX,startY)
// Creates an enemy
enemy as tEnemy
enemy.spriteId = enemyId
enemy.group = ENEMIES_GROUP // Todo different groups
enemy.count = 0
enemy.collided = 0
enemy.x = startX
enemy.y = startY
enemy.direction = 0 // Up (clockwise 0-3)
//For debug purposes
//enemy.debugSprite = markSpriteId
Enemies.insert(enemy)
next i
EndFunction
Function SpawnPlayer()
playerId = CreateSprite(0)
xp = CELL_SIZE * PLAYER_START_X
yp = CELL_SIZE * PLAYER_START_Y
SetSpriteColor(playerId, 255, 255, 0, 255)
SetSpriteSize(playerId, CELL_SIZE, CELL_SIZE)
SetSpritePosition(playerId, xp, yp )
// Popuplate the global player variable
player.spriteId = playerId
player.x = xp
player.y = yp
player.direction = 2 // Down (clockwise 0-3)
EndFunction
Function GenerateLevel(currentLevel)
level as tLevel
level.enemies = currentLevel
level.treasures = GenerateRandomTreasures()
EndFunction
Function GenerateRandomTreasures()
//Add Treasures
levelTreasures As tTreasure[]
// Adds MAX_TREASURES treasures
for i=1 to MAX_TREASURES_BOXES
commonTreasure As tTreasure
commonTreasure.treasureType = 1
commonTreasure.points = Random(MIN_TREASURE_POINTS, MAX_TREASURE_POINTS)
levelTreasures.insert(commonTreasure)
next i
// Adds a key
keyTreasure As tTreasure
keyTreasure.treasureType = 2
keyTreasure.points = MAX_TREASURE_POINTS
levelTreasures.insert(keyTreasure)
// Adds a tomb
tombTreasure As tTreasure
tombTreasure.treasureType = 3
tombTreasure.points = MAX_TREASURE_POINTS
levelTreasures.insert(tombTreasure)
// Adds a scroll
scrollTreasure As tTreasure
scrollTreasure.treasureType = 4
scrollTreasure.points = MAX_TREASURE_POINTS
levelTreasures.insert(scrollTreasure)
// Adds an extra enemy
enemyTreasure As tTreasure
enemyTreasure.treasureType = 5
enemyTreasure.points = 0
levelTreasures.insert(enemyTreasure)
EndFunction levelTreasures
Function LoadStaticMap()
delimiter$ = ","
dataFile = OpenToRead(STATIC_FILE)
dataLine$ = ReadLine(dataFile)
y as integer = 0
while not FileEOF(dataFile)
for x = 0 to WORLD_SIZE_X
cell as tCell
cell.x = x
cell.y = y
cell.value = Val(GetStringToken(dataLine$, delimiter$, x + 1))
staticMap[x,y] = cell
next x
if y < WORLD_SIZE_Y then inc y else y = 0
dataLine$ = ReadLine(dataFile)
endwhile
EndFunction
Function DrawStaticMap()
for i=0 to WORLD_SIZE_X
for j=0 to WORLD_SIZE_Y
spriteId = CreateSprite(0)
select staticMap[i,j].value
case 0:
SetSpriteColor(spriteId, 0,0,0,255)
endcase
case 1:
SetSpriteColor(spriteId, 255, 125, 0, 255)
SetSpriteGroup(spriteId, WALLS_GROUP)
endcase
case 2:
SetSpriteColor(spriteId, 255, 155, 0, 255)
SetSpriteGroup(spriteId, TREASURES_GROUP)
endcase
case 3:
SetSpriteColor(spriteId, 155, 155, 0, 255)
SetSpriteGroup(spriteId, WAYPOINTS_GROUP)
endcase
endselect
x = i * CELL_SIZE
y = j * CELL_SIZE
SetSpritePosition(spriteId, x, y)
SetSpriteSize(spriteId, CELL_SIZE, CELL_SIZE)
next j
next i
// For debug purposes
for i=0 to WORLD_SIZE_X
for j=0 to WORLD_SIZE_Y
textId = CreateText(Str(staticMap[i,j].value))
select staticMap[i,j].value
case 1:
SetTextColor(textId, 0,255,255,255)
endcase
case 0:
SetTextColor(textId, 255,255,0,255)
endcase
endselect
x = i * CELL_SIZE
y = j * CELL_SIZE
SetTextPosition(textId, x, y)
SetTextSize(textId, CELL_SIZE)
SetTextAlignment(textId, 0)
next j
next i
EndFunction
Function PrintStats()
PrintC ( "SCORE : " + Str(currentPoints) + " " )
PrintC ( "MEN : " + Str(currentLifes) + " " )
Print ( "FPS : " + Str(Round(ScreenFPS())))
EndFunction
Function GetStartMenu()
textMenuId = CreateText("Press C to Continue")
SetTextPosition(textMenuId, (worldX / 4), worldY / 2)
SetTextColor(textMenuId, 0, 255, 255, 255)
SetTextSize(textMenuId, 18)
if GetRawKeyPressed(KEY_C)
DeleteAllText()
gameStatus = 1
endif
EndFunction
Function UpdateEnemy(i as integer)
MoveEnemy(i)
//OnSeeCreature(enemy, player)
EndFunction
Function MoveEnemy(enemyIndex as integer)
// Get the enemy coordinates
enemyX = GetSpriteX(Enemies[enemyIndex].spriteId)
enemyY = GetSpriteY(Enemies[enemyIndex].spriteId)
// ATM we need to review the outbounds to prevent the walls
if enemyX < CELL_SIZE then enemyX = enemyX + CELL_SIZE
if enemyY < CELL_SIZE then enemyY = enemyY + CELL_SIZE
if enemyX > worldX then enemyX = worldX - CELL_SIZE
if enemyY > worldY then enemyY = worldY - CELL_SIZE
// In every step we need to "see" what cell type is in front of us
x1 = enemyX / CELL_SIZE
y1 = enemyY / CELL_SIZE
//Outbounds nullcheck
if x1 < 1 then x1 = 1
if y1 < 1 then y1 = 1
if x1 > WORLD_SIZE_X then x1 = WORLD_SIZE_X
if y1 > WORLD_SIZE_Y then y1 = WORLD_SIZE_Y
collisionData = staticMap[x1,y1].value
//Print ("CollisionData : " + Str(collisionData))
Enemies[enemyIndex].collided = collisionData
if (Enemies[enemyIndex].x = (x1 * CELL_SIZE)) && (Enemies[enemyIndex].y = (y1 * CELL_SIZE))
select Enemies[enemyIndex].collided
case 3:
repeat
if Random(0, 100) <= 33 // 33% chance to move the direction
Enemies[enemyIndex].direction = Random(0,3)
endif
select(Enemies[enemyIndex].direction)
case 0: //up
y1 = y1 - 1
endcase
case 1: //right
x1 = x1 + 1
endcase
case 2: //down
y1 = y1 + 1
endcase
case 3: //left
x1 = x1 - 1
endcase
endselect
//Outbounds nullchecks again to be safe
if x1 < 1 then x1 = 1
if y1 < 1 then y1 = 1
if x1 > WORLD_SIZE_X then x1 = WORLD_SIZE_X
if y1 > WORLD_SIZE_Y then y1 = WORLD_SIZE_Y
collisionData = staticMap[x1,y1].value
until collisionData = 0 // Ready to go
endcase
endselect
endif
//For debug purposes, uncomment the other debug lines to see it in action
//SetSpritePosition(Enemies[enemyIndex].debugSprite, x1 * CELL_SIZE, y1 * CELL_SIZE)
// Adds an step to the enemy
select(Enemies[enemyIndex].direction)
case 0: //up
enemyY = enemyY - 1
endcase
case 1: //right
enemyX = enemyX + 1
endcase
case 2: //down
enemyY = enemyY + 1
endcase
case 3: //left
enemyX = enemyX - 1
endcase
endselect
Enemies[enemyIndex].x = enemyX
Enemies[enemyIndex].y = enemyY
// Useless atm
if Enemies[enemyIndex].x < CELL_SIZE then Enemies[enemyIndex].x = Enemies[enemyIndex].x + CELL_SIZE
if Enemies[enemyIndex].y < CELL_SIZE then Enemies[enemyIndex].y = Enemies[enemyIndex].x + CELL_SIZE
if Enemies[enemyIndex].x > worldX then Enemies[enemyIndex].x = worldX - CELL_SIZE
if Enemies[enemyIndex].y > worldY then Enemies[enemyIndex].y = worldY - CELL_SIZE
// Finally after a few checs we can move our enemy
SetSpritePosition(Enemies[enemyIndex].spriteId, Enemies[enemyIndex].x, Enemies[enemyIndex].y)
//Print ("EnemyD " + Str(Enemies[enemyIndex].direction))
//Print ("EnemyX " + Str(x1))
//Print ("EnemyY " + Str(y1))
//Print ("EnemyRealX: " + Str(enemyX))
//Print ("EnemyRealY: " + Str(enemyY))
EndFunction
function GetUserInput()
result = 0
if GetPointerPressed()
result = 1
endif
endfunction result
function HandleUserInput(in as integer)
select in
case 0:
exitfunction
endcase
case 1:
//TODO
endcase
endselect
endfunction
// Theme song credits
// https://soundcloud.com/maf464/2006-oh-mummy-theme-maf464-remake