TList cycle through objects, not linear.

BlitzMax Forums/BlitzMax Beginners Area/TList cycle through objects, not linear.

Philip7(Posted 2008) [#1]
I'm making a space invaders game. I have the enemies running from right to left and back.

I had to make a Global direction variable and integrate that in the movement of the enemy-objects to make it work because when i tried to change all individual direction variables some were left untouched and some were changed twice. So the global is a solution for this problem.

But now i have the problem of the y movement. I want to move all enemies 20 points down on the y-axle when a (whichever) enemy changes direction on the x-axle. I really don't want to make the y a global because it limits my options with the object.

I tried to do a single change of y for all enemy-objects with EachIn in the Update of the object itself but this means i do a Eachin on a Eachin for the same object so i tried to limited the calls to the number of enemies but it doesn't do what i expected: some objects move down 20 points, some move down 40 points and some dont move down.

I keep all objects in 1 list and call them by type, this works (to my suprise) but is this whats causing the inconsistent object-updates? I really want to keep all objects in the same list for the same reason that some people need to drink coffee after dinner or wash their hands every 30 minutes :)

Sorry for the lack of code but i'm at work and don't have the code here. I really hope to solve this tonight at home.
Anyone any suggestions on how to do a change on all objects of a certain type (in a big list of several types but all extends within same object-tree) as soon as a trigger (one of the objects hits the x margin) is set off?

*Will post the code tonight.


Grey Alien(Posted 2008) [#2]
Sounds like you have added the objects to the list more than once or in some cases not at all... I use lists all the time, lists for aliens, lists for bullets, lists for particles and they work fine so you must have done something crazy :-)


Philip7(Posted 2008) [#3]
No actually, each object has been added once to my GameObjectList.
The regular code works fine, all enemies move from left to right and back again (just like space invaders).

This is done with a simple For EachIn cycle with a Update and Draw method. All fine so far.

Now when one of the enemies hits the x-margin, the direction (a global variable 1/-1) is changed, this works fine. But i want it to also move all enemies down (y) 20 points when the x-margin is hit. The problem is that the trigger happens with the For EachIn loop and i want to update the y of all enemies at that moment. Just a simple :

For Local e:TEnemy EachIn GameObjectList
e.y :+ 20
Next

The hard part is to have it do this once for every object. At this moment it will move all my objects down of the screen. So i cant get it to stop doing +20 or when i restrict the change it leaves the first object (the one that set off the trigger) at the original y position or it moves random objects on y and leaves others alone.


Grey Alien(Posted 2008) [#4]
OK Oh, this is nice and easy. Before your main For loop (the one that moves them all sideways) make a Local variable called MoveDown=0. Then when the x margin is hit, set MoveDown=1 and DON'T loop through the ObjectList yet. After the main loop is complete you can check if MoveDown=1 and if so run the loop which changes the y coord, thus it only happens once! :-)


Philip7(Posted 2008) [#5]
I think i see where you're going but that would be if the movement of the object is in my main gameloop. I placed the movement in my Update Method of the object.

My main loop is this:

While 'NOT ESC'

cls

If Enemies=0; CreateWave()

//Grey Alien solution
If MoveDown= 1
    For Local e:TEnemy EachIn GameObjectList
         e.y :+ 20
    Next
    MoveDown = 0
End if
//-----------------------

For Local o:TGameObject EachIn GameObjectList
o.Update
o.Draw
Next

Flip 

CalculateDelta()

Wend


I think i tried something like this and it didnt work (though it should).
My problem is probably that i want all my gamelogic inside my Type methods, i'll try this tonight at home. Thanks Grey.


Grey Alien(Posted 2008) [#6]
OK make MoveDown a GLOBAL instead. Then you can set it inside your Type methods and process it outside, after your For Local o:TGameObject EachIn GameObjectList loop.


Philip7(Posted 2008) [#7]
Yeah, indeed Global.

In my code the y is updated next frame and then drawn.
If i were to use your way i would have to split Update & Draw and i would have an additional For EachIn loop. This offcourse if with your way you want to update the y the same frame, if this is not the case then i don't see the difference between your way and my way.


Grey Alien(Posted 2008) [#8]
Oh yeah you 100% should separate the logic and draw and later on you should get some timing code in there so that your logic may occur several times before it is drawn...


Philip7(Posted 2008) [#9]
Okay, the CalculateDelta updates a AlphaTime and i multiply all changes on the x and y by AlphaTime.

y :+ (20 * AlphaTime)

I kept it out of the posts to keep it readable.
So i need more timingcode than that?

Just out of curiousity, can you name a situation where i would need to have my logic occur multiple times before i draw?
Am i on the right track with putting all the logic in Type mothods or should i keep the Types simple and slim and have all the gamelogic in the mainloop (or function(s) which i call from the mainloop)? Or is all of this just a personal design preference?

Thanks for all your help so far.


Grey Alien(Posted 2008) [#10]
can you name a situation where i would need to have my logic occur multiple times before i draw?
Yeah if you used fixed rate logic. It won't occur if you are using delta time. Even so you should always process your logic first, and then do the drawing later completely separately (not part of same loop). I agree that keeping the logic in the game types is correct. It's just you if you process all the logic first then you can set global flags like I mentioned to change everything before it's drawn.


Philip7(Posted 2008) [#11]
Grey or anyone else, please help me out.

I have the Y step working but slowly the enemy object are moving out of alignment. I increased the speed-multiplier so you can see what happens very quickly. I dont have an uploadplace (never needed one the last 10 years) but i dont use any real art yet anyway, just 2 random pictures until i have the basic framework up and running. So just select 2 random (small) pictures to use with the game.

What am i doing wrong that results in this de-alignment, shouldn't my DeltaTiming prevent this from happening?

'Space Invaders v0.1
'Philip
'Start: 27 aug 2008

SuperStrict

'Declare files
Incbin "ship.png"
Incbin "missile.png"

'Declare constants
Global ScreenWidth: Int = 1024
Global ScreenHeight: Int = 768
Global ScreenColorDepth: Int = 32

'Screen resolution
Graphics ScreenWidth, ScreenHeight, ScreenColorDepth

'Hide the Mousepointer
HideMouse()

'Declare types

'Default for all Gameobjects
Type TGameObject

	Field x:Float, y:Float
	Field speed:Float
	Field life: Int
	Field img:TImage
	Field xmargin:Int, ymargin: Int

	Method Draw() 	
		DrawImage(img, X, Y)
	End Method
	
	Method Update() Abstract

End Type

'Thats you, the player
Type TPlayer Extends TGameObject
	
	Field FireDelay: Int
	Field TempDelay: Int = 0
	Field Weapon: Int = 0
	Field WeaponSpeed: Int = 6

	Function Create:TPlayer(px:Int, py:Int, pspeed:Int, plife:Int, pdelay:Int, pimg:String)
		Local P:TPlayer = New TPlayer
		P.x = px
		P.y = py
		P.speed = pspeed
		P.life = plife
		P.FireDelay = pdelay
		P.img = LoadImage(pimg)
		P.xmargin = ImageWidth(P.img) / 2
		P.ymargin = ImageHeight(P.img) / 2
		MidHandleImage(P.img)
		ListAddLast GameObjectList, P
		Return P
	End Function	

	Method Update()
		
		If KeyHit(KEY_SPACE)
			Player.Shoot()
		EndIf

		If KeyHit(KEY_UP)
			Player.SwapWeapon()
		EndIf
	
		If KeyDown(KEY_LEFT)
			Player.x :- (Player.speed * AlphaTime)
		EndIf
	
		If KeyDown(KEY_RIGHT)
			Player.x :+ (Player.speed * AlphaTime)
		EndIf
		
		If x < xmargin ; x = xmargin
		If x > ScreenWidth - xmargin ; x = ScreenWidth - xmargin
		TempDelay:- 1
	End Method
	
	Method SwapWeapon() 
		Weapon:+ 1
		If Weapon = 2; Weapon = 0
	End Method
	
	Method Shoot()  
		If TempDelay < 0
			Select Weapon
				Case 0
					WeaponSpeed = 4
					FireDelay = 15
				Case 1
					WeaponSpeed = 2
					FireDelay = 8
			End Select
			Local S:TBullet = TBullet.Create(x , y - ymargin , - 1 , WeaponSpeed , 1 , "incbin::missile.png")
			TempDelay = FireDelay
		EndIf
	End Method	

End Type

'The default for all Enemies
Type TEnemy Extends TGameObject
	Field StepYLocal:Int =0
	
	Function Create:TEnemy(ex:Int, ey:Int, espeed:Float, elife:Int, estepylocal:Int, eimg:String)
		Local E:TEnemy = New TEnemy
		E.x = ex
		E.y = ey
		E.speed = espeed
		E.life = elife
		E.StepYLocal = estepylocal
		E.img = LoadImage(eimg)
		E.xmargin = ImageWidth(e.img) / 2
		E.ymargin = ImageHeight(e.img) / 2
		MidHandleImage(E.img)
		ListAddLast GameObjectList, E
		Return E
	End Function	

	Method Update() 	
		x :+ ( (EnemyDir * speed) * AlphaTime)
		If x < xmargin Or x > (ScreenWidth - xmargin) 
			EnemyDir :* - 1
			StepY = 1
		EndIf
	End Method
	
	Method UpdateY() 	
		y :+ (StepYLocal * AlphaTime) 
		speed:+ ((speed/10) * AlphaTime)
	End Method
	
End Type

'The default Bullet
Type TBullet Extends TGameObject
	Field direction: Int = 0

	Function Create:TBullet(bx:Int, by:Int, bdirection:Int, bspeed:Int, blife:Int, bimg:String)
		Local B:TBullet = New TBullet
		B.x = bx
		B.y = by
		B.direction = bdirection
		B.speed = bspeed
		B.life = blife
		B.img = LoadImage(bimg) 
		B.ymargin = ImageHeight(B.img) / 2 
		B.y :- B.ymargin
		MidHandleImage(B.img)
		ListAddLast GameObjectList, B
		Return B
	End Function

	Method Update() 
		y :+ ( (direction * speed) * AlphaTime) 		
		If Life <= 0 Or x < 0 Or x > ScreenWidth Or y < 0 Or y > ScreenHeight
			GameObjectList.Remove(Self)			
		End If 	
	End Method
	
End Type

'Declare placeholders and globals
Global GameObjectList:TList = CreateList() 
Global Player:TPlayer = TPlayer.Create(ScreenWidth/2, ScreenHeight-30, 4, 3, 3, "incbin::ship.png")

Global LastTime:Int = MilliSecs()
Global AlphaTime:Double

Global Enemies:Int = 0
Global EnemyDir: Int = 1
Global StepY:Int = 0

'Main loop
While Not KeyDown(Key_Escape)

	Local o:TGameObject
	Local e:TEnemy
	
	Cls

	If Enemies = 0; Wave()

	For o = EachIn GameObjectList
		o.Update()  
	Next
	
	'Possible Y update
	If StepY > 0
		For e = EachIn GameObjectList
			e.UpdateY()
		Next
		StepY = 0
	EndIf
	
	'Draw actual	
	For o = EachIn GameObjectList
		o.Draw()
	Next
			
	Flip

	DeltaCalculate()

Wend

GCCollect()
End

Function DeltaCalculate()
	Local CurTime:Int = MilliSecs()
	AlphaTime = (CurTime - LastTime) / 10.0
	LastTime = CurTime
End Function

Function Wave()	
	EnemyDir = 1
	For Local EnemyRow:Int = 1 To 8
		For Local EnemyColumn:Int = 1 To 5
			Local Enemy:TEnemy = TEnemy.Create(150 + (50 * EnemyRow) , 10 + (50 * EnemyColumn) , 0.1, 20, 1, "Incbin::ship.png")
			GameObjectList.Addlast(Enemy) 
			Enemies:+ 1
		Next
	Next
End Function



Philip7(Posted 2008) [#12]
Problem solved!

Okay, sorry. You stare at your code for 3 hours straight to find an error. Then right after you give up and ask for an answer on the forum the solution hits you right in the face.

Because i have 1 object triggering the directional Global all objects after that one are moved with the new direction except offcource the object that triggered the directional change because it has already been updated. I just had to move the: EnemyDir :* - 1


Derron(Posted 2008) [#13]
Just as a general idea and not say something bad about your code:

If all "enemies" change their y-coordinate simultanous there is no need to change each y-field. Just have a global moveY-field within the TEnemy-Type and change it when needed. When drawing/updating/calculating you easily add moveY to the y-field [ drawimage(self.x,self.y+self.moveY) ].

Although this seems unnecessary for a list with 20 objects, but imagine something like "sidecrolling effects",instead of moving each tile of a level you just "displace" the view with changing only one variable.


bye
MichaelB