So we’ve properly created our Enemy class, and added some simple Intelligence into it as well. Now it’s time to actually give the new Enemy the ranged attacks ability that we have planned for our RPG. The execution will be similar to how we added the Player’s fireball ability.

Enemy Class

We’re going to be using an interesting concept here called randomized change or “probability”. The problem is that a computer, which runs off logic cannot “guess” or pick a random number. There’s just no logic behind that. The best we can do is create a pseudo-random algorithm.

For this we are going to use a special function from the numpy library. Be sure to include the import numpy import statement at the top of the Python file before trying out the below code.

In the below move function of the enemy, we are adding in a probability of the enemy firing out a bolt. We are going to use the numpy.random.uniform() function, which takes two numbers as parameters, and generates a random number between them.

What makes this different from the random function from the random library is that is more uniform in it’s randomness. If you generate 10 numbers between 1 to 10, there is a equal chance for each number.

Now this is the tricky part. The move function will execute 50 times while the enemy is in the wait position. We want the enemy to generate a bolt within this period.

              rand_num = numpy.random.uniform(0, 50)
              if int(rand_num) == 25:

I have used the above two lines to create a 2% chance of the enemy generating a bolt whenever the move function executes (in wait position). And since it executes 50 times, there is a roughly 100% chance of generating a bolt. Due to the random nature however, there is a chance of the enemy generating no bolts, or even 2 – 3 at once. It’s a nice touch to make things more realistic in my opinion.

      def move(self):

        # Updates positon with new values
        if self.wait > 50:
              self.wait_status = True
        elif int(self.wait) <= 0:
              self.wait_status = False

        if self.wait_status == True:
              rand_num = numpy.random.uniform(0, 50)
              if int(rand_num) == 25:
                    bolt = Bolt(self.pos.x, self.pos.y, self.direction)
              self.wait -= 1

Don’t pay too much attention to the bolt in the above code. We will be creating it in the next section of this tutorial.

Enemy Bolt Class

Here we create the Enemy Bolt Class which takes three parameters in it’s constructor. An X and Y value, for the initial spawn point, and a direction (d).

You see those two lines where add +15 and +20 into the x and y positions? We did this to basically offset the position of the bolt a bit. The x and y variable store the coordinates of the top-left corner of the Enemy. Adding in a little offset like this adjusts the bolt to spawn in the center of the Enemy generating it.

class Bolt(pygame.sprite.Sprite):
      def __init__(self, x, y, d):
            self.image = pygame.image.load("bolt.png")
            self.rect = self.image.get_rect()
            self.rect.x = x + 15
            self.rect.y = y + 20
            self.direction = d

This is the fire method of the Bolt attack. It’s almost identical to the Fireball class’s move method. The difference however, is that it checks for collisions with the Player, not the enemies.

      def fire(self):
            # Runs while the fireball is still within the screen w/ extra margin
            if -10 < self.rect.x < 710:
                  if self.direction == 0:
                        self.image = pygame.image.load("bolt.png")
                        displaysurface.blit(self.image, self.rect)
                        self.image = pygame.image.load("bolt.png")
                        displaysurface.blit(self.image, self.rect)
                  if self.direction == 0:
                        self.rect.move_ip(12, 0)
                        self.rect.move_ip(-12, 0)   

            # Checks for collision with the Player
            hits = pygame.sprite.spritecollide(self, Playergroup, False)
            if hits:

Another change in here can also be observed at the bottom. If the hit (collision check) with the player group was successful, then the player_hit() is called, and the bolt destroys itself.

Currently, I am using the same image for if the Bolt is fired left or right. This is because the Bolt is symmetrical and

Game Loop

Just like how we did the fireballs, we can iterate over the Bolts group (make sure to create one first) and then call the fire method on it.

    for ball in Fireballs:

    for bolt in Bolts:

Demo Video

A short demo video showing off the Enemy Ranged Attacks.

Next Section

And that’s all for now. A short tutorial, but an Important one. In the next we will take a look at adding Music and Sound into our game. This is a massive change that will drastically change the quality of our Pygame RPG and create an immersive experience.

This marks the end of the Pygame RPG Enemy Ranged Attacks Tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.

Notify of
Newest Most Voted
Inline Feedbacks
View all comments