This is tutorial number 8 in our Pygame RPG series.
Now that we are done with our Player class, we can finally move on to developing the Enemy class. We’re not going for anything fancy, just simple barebones with the required concepts needed to build a proper game.
You can always customize the enemy class later on using different images and adding other features.
The Enemy Class
__init__ method is going to be pretty big, so we’ll divide it into three parts and examine each one individually.
class Enemy(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image = pygame.image.load("Enemy.png") self.rect = self.image.get_rect() self.pos = vec(0,0) self.vel = vec(0,0)
The above code follows the trend of previous classes. It loads an image, and retrieves the
rect object from it using
get_rect(). Furthermore, it creates two vectors for position and velocity with two components each.
An enemy with the same repeating patterns in really boring. One great way to create randomized elements is to use Python random library. One of it’s many functions is
randint() which takes two numbers as arguments and generates a random integer between them.
self.direction = random.randint(0,1) # 0 for Right, 1 for Left self.vel.x = random.randint(2,6) / 2 # Randomized velocity of the generated enemy
self.direction takes a random integer between 0 and 1, which is to say “either 0 or 1”. We have decided that 0 will represent the right direction and 1 will represent the left.
self.vel.x (the velocity) will take a value between 2 and 6, which we will then divide by 2. We do this to avoid the enemy going too fast, while keeping a wide range of possible values for enemies.
The final section of the
__init__ method, where we determine the starting position of the enemy. Now in this game we want them to come in from both the right and left. You may choose to remove one side if that’s how your game is designed.
# Sets the intial position of the enemy if self.direction == 0: self.pos.x = 0 self.pos.y = 235 if self.direction == 1: self.pos.x = 700 self.pos.y = 235
If the direction of the enemy is towards the right then we will spawn the enemy at the left end of the screen and vice versa.
Next up is adding the move functionality into the Enemy.
The below code is incharge of changing the direction of the Enemy once it has reached the end of the screen. If not for this code, the enemy will keep on going once he reached the edge of window, until he is no longer visible.
def move(self): # Causes the enemy to change directions upon reaching the end of screen if self.pos.x >= (WIDTH-20): self.direction = 1 elif self.pos.x <= 0: self.direction = 0
The reason why we are comparing to
WIDTH - 20 instead of
WIDTH is to keep a little margin between the enemy and the screen. If we were to use
WIDTH, the enemy would be midway out of the screen before changing directions. (This is because
pos.x returns the x-coordinate of the middle of the enemy)
The reason why we have two if statements here is because the Enemy is only assigned the magnitude of the velocity, not the direction. In other words, it’s just speed. It doesn’t specify which direction he is going in, left or right.
# Updates position with new values if self.direction == 0: self.pos.x += self.vel.x if self.direction == 1: self.pos.x -= self.vel.x self.rect.center = self.pos # Updates rect
The above code will either subtract the velocity or add it into the position “x” based on the direction of the enemy. This effectively handles any possible issues.
Lastly of course, we update the
rect property with the new position. If not for this, the “rect” of the enemy would be left behind at the initial spawn point and collisions would not occur accurately. The image would render as normal though, so identifying the problem can actually be pretty hard.
Lastly we’ll add the render method and create the object for an enemy. We’re just creating one enemy right now just for testing purposes to see if our code worked.
def render(self): # Displayed the enemy on screen displaysurface.blit(self.image, (self.pos.x, self.pos.y)) enemy = Enemy()
Next we have to actually call the
enemy.render() method. It doesn’t matter much whether the player renders first or the enemy, but both must render after the background.
# Display and Background related functions background.render() ground.render() # Rendering Sprites displaysurface.blit(player.image, player.rect) enemy.render()
You’ve probably already noticed, but compared to our Player class the Enemy class is much smaller and simpler. Some of the things our enemy objects is missing is a gravity system, a jump mechanic and attack mechanics.
Since our goal in this tutorial is to mainly teach game concepts, we have decided to omit such features (for now) to avoid repetition. We’ve already explained these concepts (using the Player), which you can easily add into the Enemy class. We will however be discussing unique concepts related to enemies such as collision detection so stay tuned.
Here’s a short 5 second clip demonstrating the movement of warping of the Enemy we just created.
Later on in this tutorial series we’ll create an actual Sprite group for enemy objects. This will allow us to simultaneously render all of them, regardless of how many they are. With the current implementation we would have to add 100 render method calls for 100 enemies.
Our next Pygame RPG tutorial brings together everything we’ve done in the last 2 – 3 tutorials in the form of collision detection between the Player and the Enemy. Click the button below to find out more!
This marks the end of the Pygame RPG Tutorial – Enemy Class. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.