Ques for people who have made a shooter game

BlitzMax Forums/BlitzMax Beginners Area/Ques for people who have made a shooter game

Zacho(Posted 2011) [#1]
As we all start out programming I know everyone wants to make some shooter/platformer game out there. I know, for I am one of them. I have a question for all of you that have made a shooter (or any game that uses lists for that matter); concerning checks between bullets and the computer. How is it that you can check for collision detection between multiple instances of bullets and multiple instances of CPU? I have been talking with someone (Jesse) in particular and he has helped me. We (he actually, I don't know his level of expertise - I am far from it) came up with a nested for...next loop within another for...next loop.

I know from a programming point of view that you have to check each bullet and see if it hits any of the multiple targets, this is why a nested for...next loop within another makes sense to me (albeit it works with some bugs) but it doesn't seem right. I know there are different ways to approach this question. I would like to hear your input on any ways this is possible (I want to thoroughly understand programming so I don't run before I can walk). Please take note that I don't know too well: methods, polymorphism, oo, lists (I started to learn and I am getting the hang of it) but I do know types, global, local, ifs, elses, while wend loops.

Again thanks for helping :)


H&K(Posted 2011) [#2]
Jesses solution was simple and maintainable.
Its only downside, the amount of stuff you need to loop through, is practicability irrelevant with the speed of today's computers.

However if you think you have too many "cpu" and bullets that the game is slowing down, then there are other ways but these are more complicated and and really not worth it unless you have to. Plus nine time out of ten you would go through all the effort to write a different collision detection, to find its no faster.

Jesses solution works. and ATM its better to keep working on the rest of the game. If in the end it seems the bullet collision detection is a bottle neck go back and improve it then. (When you have more experience if nothing else. Just make sure you have compartmentalised the program so (for example) the rest of the program is just expecting a "Yes hit, Cpunumer x" (Flag:byte,CPU:int) or less if possible.


ima747(Posted 2011) [#3]
There are always tradeoffs between speed and structure (readability in this case). But there are also always ways to optimize things.

For this type of interaction I will typically be operating multiple lists, such as a list of all MOBs (short for mobiles, legacy term for bad guys at the code level) and a list of all bullets (projectiles of any type).

All MOBs are either defined in the same class, or atleast derived from the same super class. As such they typically will have an "Update()" method which is called once per cycle to allow their AI to do it's thing, update their positions, etc. Optionally you can have each MOB check each bullet to see if it's been hit in it's update cycle. This method is nice because you can trigger death animations etc. right away which may be handy depending on your structure.

All Bullets follow the same structure. They are either of the same class or atleast derived from the same super class. They also have an Update() method that is called once per frame and moves them based on their momentum, possibly applies AI in the case of seeking missles, etc. If the mobs don't check to see if they're hit then the bullets have to check to see if they've collided with each mob. This can be handy as the bullets are responsible for deducting health etc. rather than the mob directly (though you still may call some form of mob.WasHitBy(thisBullet) method...) which can be a nice layout.

You have to compare each thing against each other thing to see if it's hit, there' no way around that. However there are higher level optimizations one can do that are generally dependent on the structure of the game. For example:

If you are updating things off the screen as well as on the screen, you may choose to have all off screen entities skip their bullet check loop... you may even group all offscreen mobs and offscreen bullets into seperate lists so you can just ignore them entirely in collision tests...

You may split the screen up into sections and create a list for each section so that you only have to check for things that you know are comparatively close for collisions.

You almost certainly should do some form of bounding box collision test before a pixel perfect collision test, though I think the built in pixel level collision test has a bounding box pass built in (not 100% sure on that...).

You may be able to just get by with bounding box for many types of collision (lots of shmups do this...). Though this may make you approach your MOB and art asset structures differently if you really need the speed.


ImaginaryHuman(Posted 2011) [#4]
You need some spacial partitioning to act as an acceleration structure, such as a simple grid, where each grid cell can contain a linked list of objects which are within the cell. Then you only need to brute-force compare collisions between the objects in that cell and the surrounding cells.


Jesse(Posted 2011) [#5]
look Zacho this is so you will have an idea what I am working on(on and off) this is an older version than what I have. I would post the newer version but it's a mac and you probably have a pc:
http://www.mediafire.com/?g3qtyvruxc3hcnv
it's a shooter that can handle quite a bit of enemies, bullets and player all interacting with each other.

its not complete but the up to date version has a tile scroller in the background and and multiple other extras.

and this is the main loop that control bullet and enemy interaction:
	Method updatePlayerBullets()
		For Local b:Tbullet = EachIn playerbulletList
			If b.update() = False
				b.remove()
				Continue
			EndIf
			For Local e:tbase = EachIn EnemyList
				If b.collided(e,True)
					e.life:-b.damage
					b.remove()
					If e.life <= 0
						e.remove();
						If e.found
							If e.found.found
								e.found.found = Null
							EndIf
							e.found = Null
						EndIf
						If e.name = "BOSS"
							Local ex:TexplosionGroup = e.explode(10)
							ex.link = ExplosionGroupList.addlast(ex)
						Else
							e.explode();
						EndIf
						score :+ e.value
					Else
						b.explode()
					EndIf
					Exit
				EndIf
			Next
		Next
	End Method

these code takes care of all types of enemies.
there are other ways of doing it but all of them use a loop nested in a loop. it may not be so obvious and/or may be quite a bit more complicated but at the core all of them do the same thing. There is no way around it.

what I would suggest is that you don't combine graphics displaying along with logic processing.
You would need to be careful not to repeat display the same image on the screen as that would really deteriorate performance.


a little in on the game:
the game coding is 100% designed by me, the graphics are 90% designed by me, the other 10% are from a graphics library I bought a few years back. I don't have any skills in creating animations and that's why I am using the bought graphics.

Last edited 2011


Matty(Posted 2011) [#6]
As ImaginaryHuman says -
if you have thousands of bullets (possible in some games such as my old multiplayer space combat sim) it is best to subdivide your world/universe into regions, be they a grid or otherwise, and only check for collisions between objects within the grid cells (or sometimes the adjacent grid cells as well). You would create and maintain a list of objects within each grid cell, only updating the grid cells' lists when an object moves between the boundaries of one grid cell to another(remove from one cell, add to the other), or when an object is destroyed or created (add/remove from current cell).

As Jesse says you will still need to do a nested for loop check...it just will be between a smaller number of items which is a good thing.

There is usually a trade off between memory and speed - ie often greater speed is gained at the cost of additional memory, while greater memory savings are gained at the cost of additional processing power...my style is to gain speed at the cost of memory - as mostly memory is cheaper...


Zacho(Posted 2011) [#7]
Thank you for responding everyone. My internet did not want me to come on for quite some time :P but now I am able to get online.

@H&K: I will not be having an immense ammount of bullets (~50 approx.) and I tested my code with my game and it works.

@ima747: I do not know how to do the superclass as you said earlier. It is something with abstract and method, am I right? At the moment I am getting my collision code right (which it is - I had to rewrite my whole main loop and functions) and then I will work at specializing my code

@ImaginaryHuman: I have no idea what you said :D (special partitioning act, acceleration structure) I am a bit more low-key than this, but the concept makes some sense. I just don't know how to code it and that is why I will be saving your sugggesstion for when I am better at programming.

@Jesse: Nice piece of code there. Now, I haven't gotten into methods yet so they are not in use in my code. I also followed your suggestion and didn't combine my graphics/logics, everything is split up, and it works! I have to thank you, a nested for...next for...next loop was what I needed, and I understand it too. It sounds interesting, you're game. yes i do have a pc so I couldn't help ya out by that. I will have to check out your game soon

@Matty:
 You would create and maintain a list of objects within each grid cell, only updating the grid cells' lists when an object moves between the boundaries of one grid cell to another(remove from one cell, add to the other), or when an object is destroyed or created (add/remove from current cell). 
I understand everything but that ^ =D. Um, well.. I suppose I could do checks for x and y and then put whatever is in those in list300,300 or something like that. Yeah I suppose but I don't see why I would need to ever do this unless my code didn't work for me. I appreciate your feedback but this way of programming is a bit foreign to me BUT i will keep it in mind if I should ever use it.


To all of you, here is complete code that is working (constructive criticism :)) I would llike to thank you all for helping me get this far! I appreciate your time:



Annddd to keep this thread alive, my next step in my game is cpu AI. I want to have the ships either meander about in their initial x,y position or follow the player (the obvious reason for the first choice so the player isn't overwhelmed in higher levels). I am curious on how to write the code for this, as I can't have cpu x,y like a jitterbug (updating/changing position at each loop). Do I need a timer (and I haven't worked with timer's before :o) and once this expires then update the cpu x,y? What are your beginner-friendly suggestions?


Jesse(Posted 2011) [#8]
I noticed that you have checkplayer and checkcpu that do the same as checkcollision. just get rid of the checkplayer and checkcpu and remove checkcollision from the createProjectiles and put it in the main loop after updateProjectiles that way you are not doing redundant checks.

methods are really easy and if you want to get an idea on how to use them I'll show you in your code

see how you have this code in your program:
Type bullettype
	Field x:Int
	Field y:Int = 560
	Field yv:Int = 6
	Field frame:Int
EndType

and this:
	For Local Cbullet:bullettype = EachIn CbulletList
		Cbullet.y:+ Cbullet.yv
		Cbullet.frame:+1
			If Cbullet.frame > 3
				Cbullet.frame = 0
			EndIf
	Next 



you can move the bullet type variables into a method:

Type bullettype
	Field x:Int
	Field y:Int = 560
	Field yv:Int = 6
	Field frame:Int
	
	Method update()
		y:+ yv
		frame:+1
		If frame > 3
			frame = 0
		EndIf
		
	End Method
EndType

notice how it doesn't have the cbullet variabel. It changes into a direct access.
and then use it like this:
	For Local Cbullet:bullettype = EachIn CbulletList
			Cbullet.update()
	Next 

it's that simple.

Last edited 2011


Zacho(Posted 2011) [#9]
So calling a method inside of a type is like a function. This function will be specific to only that type. Operations inside of a method don't need to use the type name (Cbullet.y : the Cbullet part) because it is called within that type. And then this method is called similar to a function, using the type name followed by a . And then the method? Did I get this all right?

If this is true then isn't it possible for me to put the collision detection in a method as well?


Jesse(Posted 2011) [#10]
Yes, All true.

Just remember that only fields with in that type can be accessed directly. Different "type" variables can not be accessed directly within the current "type method".

This is the brake down:
Only the fields belonging to the instance can be accessed directly from with in the method.
Methods are only accessible if there is an instance of an object for example creating an object with "New".
A method can not access fields from other objects(Types) unless you pass them as parameters or are declared as global similar to the way functions work.

Any object(type) that is passed to a method must be used with the complete name conventions.
such as "bullet.xpos = 33".

the idea about objects is to try to keep everything related to the object with in the object itself. that include a field for the image. this will make collision a lot easier to process:
Type spaceship
	Field x:Int,y:Int,frame:Int
	Field health:Int = 1, shield:Int
	Field image:TImage '<----------------added this
EndType 

and this:
	
Type bullettype
	Field x:Int
	Field y:Int = 560
	Field yv:Int = 6
	Field frame:Int
	Field image:TImage '<----------------added this
EndType



that way when you have to modify or add code you won't need to remember which image is for what.
but when you instantiate the object(create with "New") you will have to assign the specific image to the instance and is the only time you will have to worry about it:
	If KeyHit(SPACEBAR)
		Local Cbullet:bullettype = New bullettype
		Cbullet.x = Rand(0,700)
		Cbullet.y = cpu.y
		Cbullet.frame = 0
		Cbullet.Image = CbulletImage '<---------------added this
		ListAddLast(CbulletList,Cbullet)
	EndIf


that way when you add the collision to the "player type", it will be a lot simpler:

Type spaceship
	Field x:Int,y:Int,frame:Int
	Field health:Int = 1, shield:Int
	Field image:TImage '<----------------added this

   Method collided:Int(bullet:bulletType)
         If ImagesCollide(image,x,y,0,   bullet.image,bullet.x,bullet.y,bullet.frame)
			health :- 1
			If health < 0
				health = 0
			EndIf
             Return True
        EndIf
        Return False
    End Method

End Type


and use it like this:

	For Local Cbullet:bullettype = EachIn CbulletList
		If player.collided(cbullet)			
			ListRemove (CbulletList,Cbullet)
		EndIf 
	Next


Last edited 2011

Last edited 2011

Last edited 2011


Zacho(Posted 2011) [#11]
Thank you Jesse. Your explanation was superb but I am getting a little confused with

Field image:TImage


inside of the Type spaceship. It doesn't click with me. I will look at it later, I just need some time for the "Method" to sink in with me so I can get familiar with that first. I will look over and reply later. thanks again!


Jesse(Posted 2011) [#12]
I just noticed that I copied the spaceship type twice. this has been corrected in the samples. was that what was confusing you?
other than Timage is a type and image holds the address where the actual Timage instance is stored.

Last edited 2011


Zacho(Posted 2011) [#13]
No, I figured that that was a typo. I don't quite understand drawing the actual image if I put the image in a type in the bullettype. These three lines are particularly confusing me and I don't know if you can help explain them.

Field image:TImage '<----------------added this

   Method collided:Int(bullet:bulletType)
         If ImagesCollide(image,x,y,0,   bullet.image,bullet.x,bullet.y,bullet.frame)


Starting with the first line, you declare a new field in the type of the bullettype named image. I know a colon (:) identifies what the field is, either a number, string or etc. I understand that part. What I don't or never understood was why you have to put TImage there. It is never in yellow font or anything so I didn't think BMax recognized it.

The Second line. You declare a method called "collided" that is a integer. Why is it an integer? Another question I never understood and didn't want to get confused with is why you have "bullet:bulletType" in ( ) after calling the method. If I use F1 when coding over "Method" the help description is no help. Is "bullet:bulletType" after the method supposed to help coding but provide no other purpose. I am just confused as what this is used for

Lastly I don't know where to load the image and stuff with this line. If you put in a field called "image:TImage" do you put and = sign after and then the image url? Is the image created/loaded after? Does this change the way I have to load the types in conjunction with the images? Particularly the "bullet.image" part I am confused with, where do I load it? Within the method? type?


Nate the Great(Posted 2011) [#14]
I just read the first post (not the entire thread) but if you want to switch, the most efficient Nearest Neighbor search I have found is using a grid. Its really fast especially if you have 10's of thousands of bullets on the screen. If I used a nested loop for my fluid engine it would only be able to handle a few hundred particles instead of thousands of particles. (not sure why you would want thousands of bullets on the screen though) ;)


Jesse(Posted 2011) [#15]
note that you still have to load the globals for the images:
Global playerimage:TImage=LoadImage("C:/Users/Zachary/Pictures/Rapture/Player.BMP")

and the spaceship type will now include the image field:
Type spaceship
	Field x:Int,y:Int,frame:Int
	Field health:Int = 1, shield:Int
	Field image:TImage '<----------------added this
EndType 

now when you create the player type, you create it like this:
	global player = new spaceship
	player.x = 10
	player.y = 550
	player.health = 100
	player.shield = 100
	player.image = playerImage '<--------------added this

you asign the image to the player just like you asign the any other variable to the player
now if you want to display it all you have to do is this:
DrawImage player.image,player.x,player.y

now if you want to display it from within the type instead of directly, you first have to create a method with in the type.
remember that player is a spaceship type. so you can do this:
Type spaceship
	Field x:Int,y:Int,frame:Int
	Field health:Int = 1, shield:Int
	Field image:TImage '<----------------added this

	Method display()
		DrawImage image,x,y '<------------note this will display the current object image
	End Method

EndType


so if I want to display it through the type, all I have to do is do this.
player.display()


and so you will know, any type can be assigned to a variable field.
field monster:Tenemy

and is perfectly valid.

about Timage:
Timage is a module in blitzmax. it is highlited in the newst release only. it was not highlighted because of blitzmax inconsistencies.
the reason it don't show or was highlited is because it was not documented by Mark at all by.
you can look through BlitxMax source and you will see a file name "image.bmx" that is where the information about TImage is stored. but unlike any other module by BRL it is not documented.

Last edited 2011

Last edited 2011


Jesse(Posted 2011) [#16]
a method works exactly like a function
this is a function:
function addition()
  print "ok"
end function

that is a basic function and I am sure you know how to use it.

but if I want to send a value to a function I create it like this:
function show(a:int)
   print a
end function


I can call it like tis:
show(20)


and 20 will be assigned to the variable a in the function and when it executes it will display "20"
and that is how you pass values to a function.
now you can also pass values in a variable to a function:
function show(a:int)
    print a
end function

local b:int = 20
show(b)

this code will pass the value in variable "b" to the variable "a" in the function show
and the function wil print "20" to the console

a function can receive and can return values:
this function will return a value:

function get:int()
      local n = 10
      return n
end function

local b:int = get()
print b

the "get:int" tells the function it needs to return anteger.
and the "return" tells the function what to return
now methods work the same way:
Method collided:Int(bullet:bulletType)

in this case the collided method expect to receive the object( of bulletType) and return an integer
1 if there was a collision and 0 if there was none.
0 = false
1 = true
that is what is returned from the test from imagesCollide.


Zacho(Posted 2011) [#17]
This


Global playerimage:TImage=LoadImage("C:/Users/Zachary/Pictures/Rapture/Player.BMP")


	global player = new spaceship
               ....
               ....
               ....
	player.image = playerImage '<--------------added this


made LOADS of sense to me. By creating a new player, and setting the player.image field to the actual image; you can then draw the player through drawing the player.image field. Makes sense now :)

And it makes sense that I can call player.display() and cpu.display() from:
Type spaceship
	Field x:Int,y:Int,frame:Int
	Field health:Int = 1, shield:Int
	Field image:TImage '<----------------added this

	Method display()
		DrawImage image,x,y '<------------note this will display the current object image
	End Method

EndType

to make drawing the enemy, the coding more compacted. Thank you. I can see myself re-doing this whole coding process, re-doing my whole game. I don't know if I should do this or if I should try to finish my game and re-do it after... I don't know what is better for my programming learning proccess. I know I want to get some more practice with an animage and maybe a method or two. I do not think I will implore the use of a function calling an integer (for now). I will save that for later :)