arrested (gaming) development: pick a bullet, any bullet

please note in order to see the code and the videos, you’ll need to go to the full post. feedburner doesn’t appear to play nice with my embedded videos.

last week we animated our moving sprites exploding the individual frames in an animated gif, placing them in separate files, and flicking through them rapidly.

this week we’re looking at how to make the game a bit more interesting by getting the boss to fire demons at us. to avoid this becoming some dodging bullets matrix-style game, our dragon in turn has mastered the art of hadoken, so can return fire, er, balls. to spice things up a bit (read: game play improvement) the boss can also fire baby dragons at us. shooting these guys will incur a scoring penalty (scoring will feature in a later post).

the dragon’s fireball is fairly trivial, so we’ll skip it for now and go straight to the boss and discuss the properties of his munitions. the boss will fire two types of bullets, either a demon or a baby. the game maker’s apprentice defines that there’s a 1 in 50 chance of a demon being fired.

if random.randint(1,50) == 50:
	# create a new demon
    demon.x = boss.x - 10
    demon.y = boss.y - 10

the demon has a possible starting direction of sw, w or se.

	# which direction is demon going nw(1), w(2), sw(3)?
    demonDir = random.randint(1,3)
    if demonDir == 1: # nw x-,y-
		demon.dy = 1
    if demonDir == 3: # sw x-,y+
        demon.dy = 0
    # no check for 2, because at the end of the day we're always going west

the demon will move along the screen at rate of 10 pixels per frame going towards the rightmost side of the screen. because the demon can veer towards the top or bottom of the screen, we need to make sure when it reaches the edge of the screen it changes direction (bounces).

	demon.x -= 10

    if demon.dy:
        demon.y += 10
    else:
        demon.y -= 10

    if demon.y > 290:
        demon.dy = 0
    if demon.y < -10:
        demon.dy = 1

so how does our demo look now?

looks good eh? or does it? upon closer inspection you will notice that some of the demons seem to vanish before they even get close to the dragon. it didn't take me long to realise that the demon object was being reused everytime it was being fired! this makes for great ammo economy for the military, but a crap gaming experience for us.

realising the error in my code, i needed to find a way to spawn multiple bullets from the same class. luckily our demons aren't too complicated; they have a fixed speed and their x-axis starting position is always the same. this means they move at a constant speed one after the other. at the moment (we've not yet broached the topic of collision with the dragon or his fireballs) they move in an orderly queue, but to avoid confusion with the python term "queue", here we'll use the term "list".

i'm not going to teach you about python lists, queues and whatnots. there are loads of people who do this better than i ever could. instead i'll tell you what you need to know coming from a structure language background like perl, batch and c.

i should probably point out that by now i'd stopped looking at the linux format code for pyinvaders, and whilst the code may look similar it isn't.

whilst i don't think you need to initialise an array (pythonically known as a list) i think it makes for easier reading. you just need to create an empty list i.e. demons = []

to add an item to the list, call the append method i.e. demons.append(demon_object). yes dorothy, you can append objects with no fancy incantation in python as you would a scalar (simple) variable type.

you can get the number of items in a list using the len keyword i.e. len(demons).

finally, to delete an item from a list you can use the del keyword and specify the index (position) of the item in the list. i.e. del demons[5]. before someone carps about the fact that you can delete an item by specifying its name i.e. ldell list['x'] i haven't found out how you do this with an object. so if you know please post a comment and share the knowledge.

also i've refrained from using the pop function (fifo) or push(?) pop(-1) (lifo) because whilst the current code's behaviour is a queue, when we enable collisions we'll start to see random removal of items from the list i.e. items will disappear out of sequence.

now, i could leave it at that, for most coders. you'd be able to track the current index by incrementing a counter as you looped through the list. but, i must share this code i found. it allows me to walk list whilst tracking the index. check out the test code below (it's a proof of concept script) to see what i'm harping on about:

for x in range(0,30):
	num = random.randint(1,3)
    if num == 3:
		print "%d: ADD bullet" % x
        bullets.append(Bullet())

    for index, item in enumerate(bullets):
        if item.x < 10:
        	item.x += 1
        else:
			print "%d: DEL bullet" % x
            del bullets[index]

        print "%d: bullets: idx:%d -> val:%d " % (x,index,item.x)

don't get too tied up in the specifics of the bullet class, its only got one method that initialises the single variable called x. instead, look at the for/in loop and the enumerate keyword. basically the for/in loop is the same as the the foreach in perl and php. enumerate returns two items, the first is the current index and the second is the object reference.

i remembered the nightmare of trying to learn passing object references and values in java when i was in uni. in python its seems so much more straightforward, all you need to know is that when you've obtained an object from the list through the for/in loop you are now dealing with that object. you can tweak it to your heart's content, and then when you step to the next item in the list, you have packed it away (along with its mods) until you go through the list again.

coincidentally, the proof of concept code i've given above is pretty much the same code i used for the demons. there's the testing code to see if we need to add a new demon to the list. then there's the for/in loop which walks through the list, checking to see if demons have to be removed from the list because they've gone off screen. if they haven't then their x axis is updated.

the directional code to determine y starting position w, sw, nw is placed with the testing code. and the change direction code is placed within the for/in loop.

let's have a brain break here and see how our video looks now.

first off for reference the game maker version

and now pygame version, hopefully this is an improvement from the previous.

the baby dragons are a rarer event and only occur with a 1 in 100 chance. they only move in one direction from right to left, and their offset is based on the boss's y position. finally to add the trickiness element, we make them marginally slower than the demons by making them move 8 pixels per frame (compared to the demons' 10 pixels per frame.)

the dragon's fireball is almost identical to the baby dragon's in so far as it moves in one direction towards the right of the screen and is triggered by the spacebar. the speed is the same as the demon's.

rather than waste valuable reading inches i'll add a link to the full source code here. you'll also find the earlier version of this code where we have a single demon being fired repeatedly.

so whilst i'm happy with the progress, the game itself is not very playable at the moment. the keyboard events stop as soon as you press a key. and there's a distinct lack of collision detection. as with the slight jittery animation last week, i'm going to park the lack of non-blocking keyboard control. i can see the seasoned games developers shaking their heads and tutting alot, but for now the lack of collision detection is preventing this from being a decent game.

so tune in next week for our next installment!

prev (animation) | about | next (collisions)


About this entry