In my BallZ game, I have some exploding spikes, which generate other spikes. I found that when calling the initialisation routine using a value return from a passed array, the value is invalid - ie corrupted.
The routine is something like the following :
FOREACH loop in self.shapes[]
AddStuff(loop)
DELETE loop
NEXT
FUNCTION AddStuff:item as TBall
LOCAL temp as TBall
temp.Initialise(item.ReturnSomeValue()) <--- Return value from ReturnSomeValue is corrupt (a large negative number)
DIMPUSH self.shapes[],temp
ENDFUNCTION
I think what is happening is that the initial loop position is getting changed due to the DIMPUSH and then DELETE...
Exaclty. FOREACH and ALIAS offer a reference (pointer) to an element in an array. That's very fast.
However, if you change the array (only really harmful way is adding elements or removing them all), the reference might get corrupt. Even worse: There's no way to check this. It might crash, if you're lucky. It might just overwrite some bytes on other game-data in worst case.
Every time you have a reference, be sure _not_ to add something to the array containing this referenced object. Nor the array containing the array containing the object. You get the idea.
Yes, thought it was the cause. :)
I'm also working on a game that uses a array of enemies and a custom type. It is an Asteroids type game, so when a big enemy (=asteroid) is destroyed, it will spawn 2 smaller ones.
While the program is looping the Enemies[] array with a FOREACH loop, it also adds new enemies into the Enemies[] array. This works and doesn't crash, but I suspect this isn't the correct way to do it. Anyone have suggestions for the structure of the program?
Here is my example code:
CONSTANT SCREEN_XSIZE = 640
CONSTANT SCREEN_YSIZE = 480
CONSTANT SCREEN_XSIZE_H = 320
CONSTANT SCREEN_YSIZE_H = 240
TYPE TEnemy
x% = 0 // position
y% = 120
iSpeed% = 1 // x-axis speed
iSize = 64 // 16=small 40=medium 64=big
iHealth% = 3
FUNCTION Update%:
// move
self.x = self.x + self.iSpeed
IF (self.x < 0) THEN self.x = SCREEN_XSIZE
IF (self.x > SCREEN_XSIZE) THEN self.x = 0
// if no health left
IF (self.iHealth <= 0)
RETURN FALSE // enemy is dead
ELSE
RETURN TRUE // enemy lives
ENDIF
ENDFUNCTION
FUNCTION Draw%:
PRINT self.iHealth, self.x, self.y-16
DRAWRECT self.x, self.y, self.iSize, self.iSize, RGB((self.iSize*4)-1,256-(self.iSize*4),0)
ENDFUNCTION
ENDTYPE
GLOBAL Enemies[] AS TEnemy
// -------------------------------------
// main program
// -------------------------------------
RunTestGame()
END
// -------------------------------------
// main test game loop
// -------------------------------------
FUNCTION RunTestGame%:
// variables
LOCAL tmpEnemy AS TEnemy
LOCAL bFire%
// initialise big enemies
DIMPUSH Enemies[], tmpEnemy
tmpEnemy.x = 100
DIMPUSH Enemies[], tmpEnemy
tmpEnemy.x = 200
tmpEnemy.y = 160
tmpEnemy.iSpeed = -1
DIMPUSH Enemies[], tmpEnemy
tmpEnemy.x = 300
DIMPUSH Enemies[], tmpEnemy
WHILE KEY(1) = FALSE
// very simple player fire
IF (INKEY$() = " ")
DRAWRECT SCREEN_XSIZE_H, 0, 1, SCREEN_YSIZE, RGB(255, 0, 0)
bFire = TRUE
ELSE
bFire = FALSE
ENDIF
// update all enemies
FOREACH e IN Enemies[]
// check if enemy hit
IF (bFire = TRUE) AND (e.x < SCREEN_XSIZE_H) AND (e.x+e.iSize > SCREEN_XSIZE_H)
e.iHealth = e.iHealth - 1
ENDIF
// update all enemies
IF (e.Update() = FALSE)
// spawn new enemies?
LOCAL newEnemy AS TEnemy
SELECT e.iSize
CASE 64
// big enemy destroyed, spawn 2 medium enemies
newEnemy.x = e.x
newEnemy.y = e.y
newEnemy.iSize = 40
DIMPUSH Enemies[], newEnemy
newEnemy.iSpeed = -1
DIMPUSH Enemies[], newEnemy
CASE 40
// medium enemy destroyed, spawn 2 small enemies
newEnemy.x = e.x
newEnemy.y = e.y
newEnemy.iSize = 16
DIMPUSH Enemies[], newEnemy
newEnemy.iSpeed = -1
DIMPUSH Enemies[], newEnemy
CASE 16
// small enemy destroyed, do nothing
ENDSELECT
// remove current enemy
DELETE e
ENDIF
NEXT // e
// draw all enemies
FOREACH e IN Enemies[]
e.Draw()
NEXT // e
// showall
SHOWSCREEN
WEND
ENDFUNCTION
In BallZ I have a list of objects that need to be added, which is then deleted as each is added.