Programming: Projectiles

Our Red_Guy can move around in 8-directions within the window boundaries. For this series, I'll create this game into a basic, Smash TV-style shoot 'em up game. So for that, we'll need some bullets!

Projectile Class

I'll first create a "Projectile" class that will be used for creating the bullet objects. This will look very similar to the "Characters" class from the previous post, with some added attributes.
There's also the member function "Move" within the class. 
class Projectiles(object):
    def __init__(self, x, y, direction, color, speed):
        self.x = x
        self.y = y
        self.direction = direction
        self.color = color
        self.speed = speed
        self.radius = 2
Within the "Projectile" class, I'll add the member function "Move". A member function is exclusive to the objects within the class. The "self" input is automatically input into the function, which allows for all object attributes to be used. 
    def Move(self, win):
        if self.direction == 'Up Left':
            self.x -= self.speed * 0.707
	    self.y -= self.speed * 0.707
	elif self.direction == 'Up':
	    self.y -= self.speed
	elif self.direction == 'Up Right':
	    self.x += self.speed * 0.707
	    self.y -= self.speed * 0.707
	elif self.direction == 'Right':
            self.x += self.speed
	elif self.direction == 'Down Right':
	    self.x += self.speed * 0.707
	    self.y += self.speed * 0.707
	elif self.direction == 'Down':
	    self.y += self.speed
	elif self.direction == 'Down Left':
	    self.x -= self.speed * 0.707
	    self.y += self.speed * 0.707
	elif self.direction == 'Left':
	    self.x -= self.speed
	pygame.draw.circle(win, self.color, (round(self.x), round(self.y)), self.radius)        

In orange, the directions should be self-explanatory based on the previous post on directions. The syntax is a bit different since it's a member function, and there's the interesting "self.direction" conditional that I'll get into. 

In yellow, the "pygame.draw.circle" function is called to draw a circle at the object's coordinate of the game surface. The object's color is used and an arbitrary radius is given (2 pixels). Note the function does not accept floating numbers (decimals), so a round function needs to be used. 

Shoot the Bullet

Now I have the bullet class created, we need a way to shoot it. I first need to make an adjustment to the "Character" class to add the "self.bullets" list and "self.direction" attribute
class Characters(object):
	def __init__(self, x, y, color):
		self.x = x
		self.y = y
		self.color = color
		self.bullets = []
		self.direction = 'Left' 
I'll also update the "Move_Object" function to update the "self.direction" attribute based on which direction the character is moving.
def Move_Object(obj):
	speed = 1
	if pygame.key.get_pressed()[pygame.K_w]:
		if pygame.key.get_pressed()[pygame.K_a]: #Move Up-Left
			obj.x -= speed * 0.707
			obj.y -= speed * 0.707
			obj.direction = 'Up Left'
		elif pygame.key.get_pressed()[pygame.K_d]: #Move Up-Right
			obj.x += speed * 0.707
			obj.y -= speed * 0.707
			obj.direction = 'Up Right'
		else: #Move Up 
			obj.y -= speed
			obj.direction = 'Up' 
	elif pygame.key.get_pressed()[pygame.K_s]:
		if pygame.key.get_pressed()[pygame.K_a]: #Move Down-Left
			obj.x -= speed * 0.707 
			obj.y += speed * 0.707
			obj.direction = 'Down Left' 
		elif pygame.key.get_pressed()[pygame.K_d]: #Move Down-Right
			obj.x += speed * 0.707
			obj.y += speed * 0.707
			obj.direction = 'Down Right' 
		else: #Move Down
			obj.y += speed 
			obj.direction = 'Down' 
	elif pygame.key.get_pressed()[pygame.K_a]: #Move Left
		obj.x -= speed
		obj.direction = 'Left' 
	elif pygame.key.get_pressed()[pygame.K_d]: #Move Right
		obj.x += speed
		obj.direction = 'Right' 

We'll create another function called "Shoot", which is triggered once the player presses the "J" key. Once triggered, the character's attributes (coordinates & direction) are used to create an object from the "Projectile" class. The bullet object is created and appended to the "self.bullets" list created earlier. 
def Shoot(obj):
    if pygame.key.get_pressed()[pygame.K_j]:
	obj.bullets.append(Projectiles(obj.x, obj.y, obj.direction, (255,255,255), 5))

Lastly, I'll add the While loop to call the "Shoot" function and member function "Projectile.Move(self, win)" to draw and move the bullet. 
  • For the "Shoot" function, I'll input the "Red_Guy" object. 
  • For the "Projectile.Move" function, I'll use a "For" loop to iterate over the "Red_Guy.bullets" list. 
Now look at that, I can shoot a whole lot of bullets!


Collision 

However, like a player moving out of the game window, the bullets will continue moving off the screen infinitely. Since I only care about them within the screen, I'll need to add the same boundary conditions: Once the bullets contact the boundary, I'll delete them from the list.

I'll create the "Collision" function using the same "If" statement, but with a different "Then" statement. The line "obj.bullets.pop(obj.bullets.index(proj))" performs the following:
  1. Finds the location of the projectile object (proj) in the bullets list (obj.bullets.index(proj))
  2. Deletes the item in the bullet list at that location (obj.bullets.pop( ))
def Collision(obj, proj):
	if proj.x < 0: #If object exceeds left boundary
		obj.bullets.pop(obj.bullets.index(proj)) 
	elif proj.x > 400-proj.radius: #If object exceeds right boundary
		obj.bullets.pop(obj.bullets.index(proj)) 
	if proj.y < 0: #If object exceeds top boundary
		obj.bullets.pop(obj.bullets.index(proj)) 
	elif proj.y > 300-proj.radius: #If object exceeds bottom boundary
		obj.bullets.pop(obj.bullets.index(proj)) 

The function then gets added right after the "bullet.Move()" function, since after each movement we need to check if  the bullets should get deleted or not. 

Cooldown 

One last thing: A single key stroke creates dozens of bullets. Since the cycle time of the code is so fast, it's creating a bullet for the duration the "J" key is depressed. How many times is that? I added a line to count each time the "Shoot" function creates a bullet:

Just a tap of the "J" key creates 115 bullets! This is clearly not right, so the solution is add an additional requirement prior to creating the bullet: a cooldown

class Characters(object):
	def __init__(self, x, y, color):
		self.x = x
		self.y = y
		self.color = color
		self.bullets = []
		self.direction = 'Left'
                self.cooldown = 0 
def Shoot(obj):
	if pygame.key.get_pressed()[pygame.K_j]:
		if obj.cooldown == 0:
			obj.bullets.append(Projectiles(obj.x, obj.y, obj.direction, (255,255,255), 0.05))
			obj.cooldown = 10
	if obj.cooldown>0:
		obj.cooldown -= 1

The modifications adds the "Cooldown" attribute to the object. The "Shoot" function checks if the object's "Cooldown" is equal to zero before creating the bullet. If the bullet is created, then the "Cooldown" value is set to 10, preventing any more bullets from being created. The next line ticks down the "Cooldown" value if it's greater than zero. Once ticked down to zero, the next bullet can be created, etc. 

Next Steps 

So now the bullets get can fire from different directions and are destroyed when they collide with the game window. But there's something missing, right? They need enemies to hit!




Comments