Arena Shooter! "Arena BMX"

BlitzMax Forums/BlitzMax Beginners Area/Arena Shooter! "Arena BMX"

Matt McFarland(Posted 2009) [#1]
Hey!

I've been working on my next 24 hour project, "Arena BMX" which is an arena style shooter with different weapon types. You control your guy by using the WASD keys, R to reload, number-keys to change weapons, and SPACE to create random powerups on the playfield.

I'd like to share with you what I'm working with roughly 4 hours into development because the code is getting big and I want to make sure I'm coding correctly.

This game is still in the works so there are no enemies as of yet. Feel free to take a look and play around with it, and let me know what you think!

You don't have to download any content, just copy/paste because all the graphics were made with code.




Czar Flavius(Posted 2009) [#2]
That's quite good!

Try something like this (untested)
Type TWeapon
	Field HasThis:Int
	Field Ammo:Int
	Field MaxAmmo:Int
	Field Chamber:Int
	Field MaxChamber:Int

	Method Shoot() Abstract
	Method Reload() <- probably doesn't need to be abstract
	......
End Type

Type TPistol Extends TWeapon
	Method Shoot()
		'code here to update ammo, create blasts etc
	End Method
	..........other methods
End Type TPistol

Type TShotgun Extends TWeapon
and so on

Type TPlayer
	...........
	Field CurrentWeapon:TWeapon
	Field Pistol:TPistol = New TPistol
	Field Shotgun:TShotgun = New TShotgun

	Method ChangeWeapon(weaponid)
		Select weaponid
			Case PISTOLID
				If Pistol.HasThis
					CurrentWeapon = Pistol
				End If
		......
		End Select
	End Method

	Method Shoot()
		CurrentWeapon.Shoot()
	End Method
End Type


This is just a conceptual suggestion rather than concrete code, but perhaps will give you some ideas?

An advantage of this method is it shouldn't be too hard to expand the code to allow multiplayer battles (same computer or network) as weapons are stored on a per player basis now!!


Matt McFarland(Posted 2009) [#3]
That's fascinating.. I did not know you could put a Type in a Type as a Field! I think I'm going to rewrite the weapon code with a concept like this, that is, if I can! It will take some time, but I feel it's worth it!


Czar Flavius(Posted 2009) [#4]
We learn something new every day :D
Yep fields can be anything, objects, arrays..!


Matt McFarland(Posted 2009) [#5]
Awesome..

I've managed to have time to put another hour into this project. I've completely reworked the weapon code to fit the concept Czar Flavius has helped me realize!

I'm also using Regions to better organize the code with BLIDE.

I don't have the other weapons in yet, nor enemies, but I wanted to share the code thus far as I think it's quite an achievement. I'll have to plugin the shotgun and minigun again, and I'm not 100% sure how I'm going to go about that, but this is the code thus far.



Now I feel freedom to add as many different weapons as I'd like!! Hmm and maybe multiplayer one day!?!? My main focus of this project is improving my coding skills so feel free to critique!


Czar Flavius(Posted 2009) [#6]
Hi. How are you planning to add more than one weapon? With only current weapon, if you switch to a new weapon you'll lose the info on the old one. In the code I posted above, you'll see that there is a field in player for each weapon type, so the ammo etc for each weapon is remembered.

Do you know about type referencing? To begin with you imagine that each type variable contains its type, just like an Int variable contains an integer. But, type variables are actually signposts to their objects. So far you've just had a single signpost for each object, so there's been no difference. But the key difference, is you can have multiple signposts pointing to ("referencing") the same object.

This is quite a tricky thing to get your head around, and I probably haven't explained it very well. In my example code, CurrentWeapon isn't for storing its own weapon type, it exists only to point to (reference) another weapon type. When you tell CurrentWeapon to fire, it sends the fire command to whatever weapon it is currently pointing to (referencing).

When the player needs to change weapon, CurrentWeapon is simply told to point to another weapon object. The handy thing of this trick is, the original weapon is not lost! - because it is still pointed to by another variable. It went from having 2 signposts, to 1. So long as you still keep 1 signpost for an object, it isn't lost and can be reused.

Another implication of this, is you can add the same object to multiple TLists, if you wanted to. Any action or update performed to the object in one list, would update the object in all the other lists too. Or, more correctly, the entry in each list references the same object, so sending a command to any of the references updates the same object that each entry signposts to.

Here is a bit more beefed up example code.

Const PistolID:Int = 1, ShotgunID:Int = 2, MachinegunID:Int = 3

Type TPlayer
	...........
	Field CurrentWeapon:TWeapon
	Field Pistol:TPistol
	Field Shotgun:TShotgun
	Field Machinegun:TMachinegun

	Method New()
		Pistol = New TPistol
		Pistol.HasThis = True

		Shotgun = New TShotgun
		Shotgun.HasThis = False

		Machinegun = New TMachinegun
		Machinegun.HasThis = False

		ChangeWeapon(PistolID) 'so the player spawns with a weapon already selected
		'or it will crash if he tries to fire
	End Method

	Method ChangeWeapon(weaponid:Int)
		Select weaponid
			Case PistolID
				If Pistol.HasThis
					CurrentWeapon = Pistol
				End If
			Case ShotgunID
				If Shotgun.HasThis
					CurrentWeapon = Shotgun
				End If
			Case MachinegunID
				If Machinegun.HasThis
					CurrentWeapon = Machinegun
				End If
		End Select
	End Method

	Method Shoot()
		CurrentWeapon.Shoot()
	End Method
End Type


I added ID on the end of the constants (PistolID), to prevent confusion with the fields (Pistol). (Of course you can call them whatever you wish, so long as the names are unique!)

About your code, something I noticed. You have a few SetColors on the end of methods and functions. This is a bad idea as it forces you to call your draw functions in a specific order. It is better for any drawing routine to assume the incoming set colour could be anything, and set the colour itself as needed, rather than hoping it gets the right colour coming in.


TommyH(Posted 2009) [#7]
In my opinion it would make more sense to have an array of TWeapon as a field of the player.
Every weapon that is "picked up" is added to the array. The current weapon is just an index into the weapons array.
The size of the array could/would limit the amount of weapons the player can carry. Or make it a TList of TWeapons if you want the player to carry as many weapons as possible.

This array/list approach has two advantages:
first you get rid of the "HasThis" field in TWeapon. The player owns all the weapons inside the array. That's it.
Second you can add different weapons later as they only have to be subclasses of TWeapon. But you don't need to change the TPlayer type to add a field for the new weapon type (field Pistol, field Shotgun, field BFG...). This is unnecessary and not very "OO" ;-)

I hope you got the point from my germish explanations ;-)

Tommy


Matt McFarland(Posted 2009) [#8]
Thanks for your replies.

@ Czar: I wasn't' sure how I was planning on adding other weapons, I was gonna wing it and add a Tshotgun and go from there :P

So instead of creating a new weapon in the player new() function, I create the new weapons in the player field. Also, on Player New() I use the Changeweapon method to set the weapon to the pistol. Finally, I use WeaponID to figure if I have a weapon or not. Since all weapons are actually fields within the type, I can reference each weapon on the fly, and shoot "currentweapon" when the fire button is pressed.

Just making sure I get what you're saying Czar. I'm not 100% on the signpost thing, but maybe if you could point out exactly what lines of code are signpost receive and signpost send I'd better understand.

@ TommyH: Thanks, I'll consider using arrays sometime someday, but not with this project as of yet.


Czar Flavius(Posted 2009) [#9]
Germish?? Do you mean Germanish? Germish = bacteria :P

As for your suggestion, I was assuming that the total number of weapons would be small (less than 10 once complete?) and a player could only carry one of each, so it would be simpler just to hardcode them as fields. If the player tries to select the shotgun for example, in my opinion it would be simpler and quicker just to say shotgun.hasthis rather than go through a list one by one looking for it. But if the game were more complicated, with many weapons, I might prefer a different way.

Of course it all depends on you Matt. It is your game after all :D

Edit: haha we posted at the same time! Ok, new reply incoming..


Matt McFarland(Posted 2009) [#10]
@ Czar: Well I'd like to code it in a way that it would be easily expandable as far as the weapons go, so maybe going through a list would be a good idea for this project despite the short(or maybe not by the 24th hour) weapon list? Also, the player should be able to carry all weapons if it has them, but cant carry more than one of each weapon.


Czar Flavius(Posted 2009) [#11]
Ready? :)

First, as to the expandability, to add a new weapon to my idea, all you need to do is add a new field to the player type, and a new case to the select sequence. That seems fairly easy to me :) The advantage of this way is, by virtue of its design, it is impossible for a player to carry more than one of the same weapon. The trouble with a list is it can contain repeated objects, so every time the player picks up a new weapon you'll need to make checks that it isn't already in the list, and when the player changes weapon you'll need to search through the list for the weapon. I just don't think it is an elegant solution.

I hope this code is informative to you.
Type Elephant
	Field name:String
	Field age:Int
	
	Method speak()
		Print "I am " + name + " and " + age + " years old."
	End Method
	
	Function Create:Elephant(name:String, age:Int)
		Local temp:Elephant = New Elephant
		temp.name = name
		temp.age = age
		Return temp
	End Function
End Type

'let's create some elephants!
'i have put the ages in numerical order, to help you keep track
Local first:Elephant = Elephant.Create("Dumbo", 1)
Local second:Elephant = Elephant.Create("Big Nose", 2)
Local third:Elephant = Elephant.Create("George Bush", 3)

'Dumbo 1
first.speak()
'Big Nose 2
second.speak()
'George Bush 3
third.speak()

'now let's use a signpost, and point it at the first elephant
Local signpost:Elephant = first

'this will print out the details: Dumbo and 1!
signpost.speak()

signpost = second

'what will this say?
signpost.speak()
'answer: Big Nose and 2

'we can use the signpost to change another variable's object
signpost.name = "Changed"

'this will say Changed and 2
signpost.speak()

'what about second? has it been updated too?? yes it has!
'Changed 2
second.speak()

'the key concept is, we're not just dealing with copies of the objects
'second and signpost both point to the same object
'there is no special signposting variable. signpost could be called anything
'let's use second as a signpost

second.name = "I've changed again"
signpost.speak()
'this says I've changed again and 2!

'let's make second point to the first elephant
second = first
second.name = "Jumbo"
first.speak()
'will it say Jumbo or Dumbo?? (Jumbo!)

signpost = third
'signpost no longer points to I've changed again. what has happened to him?
'can we access him anymore? no we cannot. we've killed him. he will be erased from memory!
'an object dies when it no longer has any signposts to it

first = signpost
second = signpost
'now we have only one elephant left. what is his name?
'will these all say the same thing?
first.speak()
second.speak()
third.speak()
signpost.speak()


Here's another example: a board game with multiple players to be played on the same computer. You'll have player1:TPlayer, player2:TPlayer etc. and you can have a signposting variable currentplayer:TPlayer, that is made to point to whoever's go it is. Then the code to make a move just needs to be currentplayer.makemove(blah). To change to player 2's turn, just say currentplayer = player2

I think I'll leave it at that for now, and wait to see if I'm helping or just making you even more confused first :P

Note: "signpost" is just my personal word for it; the technical term is "reference". So when you talk about it with other programmers, call it that :P

So instead of creating a new weapon in the player new() function, I create the new weapons in the player field.
You can do it either way; it's entirely equivalent. I've edited the code above to that way if you prefer it. I also forgot to tell it which weapons we start with or not.

Also, on Player New() I use the Changeweapon method to set the weapon to the pistol.
Yes, if you don't, CurrentWeapon won't point to anything in the beginning and if the player fires without selecting a weapon, what should happen?

Finally, I use WeaponID to figure if I have a weapon or not. Since all weapons are actually fields within the type, I can reference each weapon on the fly, and shoot "currentweapon" when the fire button is pressed.
Well you use the HasThis field to figure out if the weapon is in his possession or not. If you wanted to take a shortcut, you could change the IDs of the weapons to the keycodes of the number keys, as the key codes are just numbers themselves. IE PistolID = KEY_1. This might make deciding what weapon to use based on the key press easier.


TommyH(Posted 2009) [#12]
@Czar: I didn't know "germish" was indeed an english noun...I thought it was the common expression for "german english".

@Matt: No matter if you use arrays, lists or fields to store the weapons: Keep on going and have fun ;-)


Czar Flavius(Posted 2009) [#13]
Maybe it is, and I just don't know about it :P But germ means bacteria.


Matt McFarland(Posted 2009) [#14]
Ahh Czar I see what you mean, you're not even using a list! I was putting my weapons in a list so I could reference them in that way. The reason is because I can easily use list.count to show the player how many weapons he has But your solution is STILL better! I really like not using a list, and I didnt even know that was possible; I thought you always had to use a list because I had no idea you could add other references. I'd like to somehow tell the player what weapons he has available; like "you have 3 weapons" or something and I'm not sure how to go about that. But thanks alot for the reference lesson.

EDIT: Understand the reference thing.


Czar Flavius(Posted 2009) [#15]
The simplist way is to have a WeaponCount:Int field which you increase by one every time a player picks up a weapon. You should be careful when using list.count() as it counts each individual object in the list, and will take more time for longer lists.

Perhaps you can add a PickupWeapon method to the player, which takes the ID of the weapon to give. This sets the HasThis field of the weapon to true, and increases the weapon count by one (after checking the weapon is not already held). To avoid repeating the Select Case, it is better to create a function that takes a weapon ID and returns a reference to that weapon in the player's arsenal. Here is some example code:

SuperStrict

Const PistolID:Int = 1, ShotgunID:Int = 2, MachinegunID:Int = 3

Type TWeapon
	Field HasThis:Int = False
	Field ammo:Int = 0
	Method Shoot()
		Print "Bang"
	End Method
End Type

Type TPistol Extends TWeapon
End Type

Type TShotgun Extends TWeapon
End Type

Type TMachinegun Extends TWeapon
End Type

Type TPlayer
	Field CurrentWeapon:TWeapon
	Field WeaponCount:Int = 0
	Field Pistol:TPistol = New TPistol
	Field Shotgun:TShotgun = New TShotgun
	Field Machinegun:TMachinegun = New TMachinegun
	
	Method New()
		'the player starts the game with a pistol, selected as his weapon
		PickupWeapon(PistolID)
		ChangeWeapon(PistolID)
		Assert CurrentWeapon, "The attempt to spawn the player with a weapon failed for some reason."
	End Method
	
	Method GetWeapon:TWeapon(weaponID:Int)
		Select weaponID
			Case PistolID
				Return Pistol
			Case ShotgunID
				Return Shotgun
			Case MachinegunID
				Return Machinegun
			Default
				'we've been asked for a weapon ID that doesn't exist, so let's cause an error
				Assert False, "Tried to access weapon id " + weaponID + "but it does not exist!"
				Return Null
		End Select
	End Method
	
	Method PickupWeapon(weaponID:Int)
		Local weapon:TWeapon = GetWeapon(weaponID)
		If Not weapon.HasThis
			weapon.HasThis = True
			WeaponCount :+ 1
		End If
	End Method
	
	Method LoseWeapon(weaponID:Int)
		Local weapon:TWeapon = GetWeapon(weaponID)
		If weapon.HasThis
			weapon.HasThis = False
			'do you want them to lose the ammo too?
			weapon.ammo = 0
			WeaponCount :- 1
		End If
	End Method
	
	Method PickupAmmo(weaponID:Int, amount:Int)
		Assert amount >= 0, "We were told to pick up negative ammo!? amount = " + amount + "."
		Local weapon:TWeapon = GetWeapon(weaponID)
		'if you want to let them pick up ammo for weapons they don't have yet, remove this check
		If weapon.HasThis
			weapon.ammo :+ amount
		End If
	End Method
	
	Method ChangeWeapon(weaponID:Int)
		Local weapon:TWeapon = GetWeapon(weaponID)
		If weapon.HasThis
			CurrentWeapon = weapon
		End If
	End Method
	
	Method Shoot()
		If CurrentWeapon.HasThis
			CurrentWeapon.Shoot()
		End If
	End Method
	
	Method HowManyWeapons()
		Print "I have " + WeaponCount + " weapons."
	End Method
End Type


Note that the code is not fully tested. Asserts provide you with checks to help you track down any errors or bugs in your code, and provide you with a custom error message. I suggest you use them often, as it can make tracking down an obscure error much easier, before allowing it to propagate through the entire game and becoming impossible to catch!

For example, if you attempt to shoot CurrentWeapon, but this variable has not been assigned the reference of a weapon yet, it will cause the game to crash as it won't be able to find any object to give the Shoot() command to. (Remember that CurrentWeapon isn't given a "New" when defined, so it starts empty) Important: Asserts are only checked when compiled in Debug mode.

I really like not using a list, and I didnt even know that was possible
I'd just like to point out, I am not anti-list :D It's just important to know there are alternatives, so you can pick the best tool for the job. Like I said in an earlier post, if the number of weapons was very large, you could have more than one etc, lists (or arrays, or even a map!) would become more suitable for this problem..


Matt McFarland(Posted 2009) [#16]
This is fascinating.. The GetWeapon:Tweapon(Wep_ID) method was the first method I've ever used to return a Tweapon!! Very good stuff!

Now, I realize that when changing a weapon to a null id we want a special error. Especially if down the road I'm doing something I haven't yet planned out. Here's the thing however, I'm using the mouse wheel to change weapons as well as numerical keys.

Here's the entire code so far, roughly 8 hours into the project! 552 lines!


Now to show you how I go to the mouse wheel:

in Method Control:
	Method Control()		
		'Move
		If KeyDown(KEY_W) And y > 2 y:-2
		If KeyDown(KEY_A) And x > 2 x:-2
		If KeyDown(KEY_S) And y < 390 y:+2
		If KeyDown(KEY_D) And x < 638 x:+2
		'Shoot
		If MouseDown(1) And CurrentWeapon.ShotDelay <= 0 Then CurrentWeapon.Shoot(x, y)
		'Reload
		If KeyHit(KEY_R) CurrentWeapon.Reload(90)
		'ChangeWeapon
		If KeyHit(KEY_1) Then ChangeWeapon(PISTOL_ID)
		If KeyHit(KEY_2) Then ChangeWeapon(SHOTGUN_ID)
		If KeyHit(KEY_3) Then ChangeWeapon(MINIGUN_ID)
		If MouseZ() <> LastMouseZ
			If MouseZ() > LastMouseZ
				GetNextWeapon()
				LastMouseZ = MouseZ()
			End If
			If MouseZ() < LastMouseZ
				GetPrevWeapon()
				LastMouseZ = MouseZ()
			End If
		End If
					

	End Method


Here are the two methods called depending on the mouse wheel change:
	Method GetNextWeapon()
		Local LastID:Int = CurrentWeapon.WepID
		For Local IDSearch:Int = CurrentWeapon.WepID To 9 Step 1
			ChangeWeapon(IDSearch)
			If CurrentWeapon.WepID <> LastID Then Return
		Next

	End Method
	
	Method GetPrevWeapon()
		Local LastID:Int = CurrentWeapon.WepID
		For Local IDSearch:Int = CurrentWeapon.WepID To 0 Step - 1
			ChangeWeapon(IDSearch)
			If CurrentWeapon.WepID <> LastID Then Return
		Next
		
	End Method


As you can see, I move through a list of say, 10 weapons, and since the Change Weapon Method will only change a weapon if the weapon.hasthis = true then bingo I will always change to the next weapon. This is helpful if I only have weapons 1 and 5 in my position for example. The problem is that now I return Null weapons with the new getweapon method. Previously I was assigning the weapon info in the tpickup object, but I found getweapon() to be so cool and use less redundant code that was in the tpickup object.

Now a workaround is to default the Wep_ID to pistol so I'll always return a pistol if in the For..next loop I can't find a weapon (until the loop finds the next weapon in the player's inventory)

The funny thing is, if I'm using an assert to help debugging I can't force the weapon to be a pistol because the assert will close the application. While I think using asserts are a great idea, in this case it just causes problems. I was wondering though if you had another solution, or if you recommend I keep it this way.

EDIT: Also, I'd like to point out I've made massive improvements to the TPlayer code as a whole! You can also copy/paste this into BLIDE and use the parser and Fold all and everything is neatly organized into regions, etc.


Jesse(Posted 2009) [#17]
I am going to throw a curve to you regarding lists. I hope I can explain it clear enough for you.

when you create an object and add it to a list, there is a way to access it directly from there. First a Tlist is just a link list. there are ways to access them directly and remove them the same way. if you know how link lists work, you know that every object is connected to its previous and next object by its link.
when you add an an object to a list it returns the direct position in the list and it is done like this:
local list:Tlist = new Tlist
type Tcar
   field x:float
   field y:float
   field link :Tlink ' this is going to be used to acces the object directly from the list.
end type

local car:Tcar = new Tcar
car.x = 10
car y = 20
car.link = list.addlast(car) ' this stores car in the list and assigns its positon in the list to the link variable.
local van:Tcar = new Tcar
van.x = 30
van.y = 50
van.link = list.addlast(van) ' this stores van in the list and assigns its position in the list to the link variable.

at this point you know that car knows its position in the list because it's saved in its link variable. if you need to access the next item in the list you can do it like this:
local link:Tlink  = car.link.nextlink()
if link <> null 
    van = Tcar(link.value())
else
    went past the last item in the list.
    process accordingly
    you can wrap it around like this:
    van = Tcar(list.first())
endif

if there is an object in the list before the current one, it can be acces with link.prevlink()

if you want to remove the objects from the list you can do it like this:
car.link.remove()
van.link.remove()
Note I am just giving you and alternative so you won't need to go through the whole list just to access the next object also for this to work the objects have to be in the same list but its obvious. correct?


Matt McFarland(Posted 2009) [#18]
Ooh! I think that using a list would be most beneficial for the mouse-wheel function to work properly. I think using nextlink and assuming something like prevlink would make a fast and easy way to cycle through the weapons when the mouseZ changes. I'm thinking I could even put the weapon list inside tplayer too. Thanks for the response! I've learned a lot more than I ever thought I would with this thread and I appreciate it!


Jesse(Posted 2009) [#19]

I think using nextlink and assuming something like prevlink would make a fast and easy way to cycle through the weapons when the mouseZ changes.


that's why I posted. I am glad you got it.


Matt McFarland(Posted 2009) [#20]
There's only a minor problem I'm dealing with using a list to cycle through the weapons. I'd like to make sure that Minigun is always the 3rd item in the list, even if the 2nd item isn't obtained yet. To do this, I have the objects created when the player is, so they're all assigned to the list properly. However, then I run into another problem where if I just use NextLink() I end up pulling weapons I haven't picked up yet because their hasthis value is set to false.


Matt McFarland(Posted 2009) [#21]
Ahh, So I'm doing it this way:



Only problem is I get an unhandled exception error with "CurrentWeapon = tweapon(list.last())" (or list.first respectivly)


Jesse(Posted 2009) [#22]
just make sure there are weapons in the list before accessing items in the list.
if list.IsEmpty() return



Czar Flavius(Posted 2009) [#23]
The funny thing is, if I'm using an assert to help debugging I can't force the weapon to be a pistol because the assert will close the application.
Try replacing the Assert and Return Null with Return Pistol. Then if it's ask for a weapon it can't find, it'll always return the pistol.


Matt McFarland(Posted 2009) [#24]
Czar, then it returns a pistol when it shouldn't.
Jesse the list shouldn't be empty because it is generated right when the player is..

Problem solved this way:


Except it's really sensitive. Also, the reason why the list was null is because tweapon.list is completely different than currentweapon.list as currentweapon has a list while tweapon does not.


Matt McFarland(Posted 2009) [#25]
Ok I fixed it!! Sorry for posting so quickly..

I've figured out how to get the first weapon and last weapon correctly, and I also learned that I need a delay to check the mouseZ change otherwise it is just way too sensitive.

Works PERFECT: