This is tutorial number 9 in our Pygame RPG series.
We’ve succeeded in creating both the Player and Enemy classes. The next step is make these two entities interactable with each other. The goal is simple; Players must be able to attack Enemies and vice versa.
In the current version of our game we can attack the enemy all we want (and vice versa) but there will be no effect on either. The reason is because we haven’t implemented collision detection between them, which is what we’ll be doing in this pygame tutorial.
Why do we need this? Because if left unchecked, Pygame could record upto 60 collisions between the Player and Enemy within a second. This because the game loop will check for a collision 60 times within a second with our current implementation.
We’re going to add the
cooldown variable to the player later, but first we’re going to create an “Event”. Uptil now we’ve only been using the default events in pygame. Pygame actually allows us to have our own events which we can look out for in the game loop.
hit_cooldown = pygame.USEREVENT + 1
The above code creates an event called
hit_cooldown by adding +1 into the current index of pygame events. This creates a new and unique event. We’re going to use timers later in this tutorial to begin broadcasting this event signal. Follow this link for a more detailed tutorial on UserEvents in Pygame.
Often in games you’ll see an “invulnerability” period after your character is hit. In other words, there is a cooldown period after the player is hit before he can be hit again. We are going to do the same thing, giving the player an invulnerability period of 1 second before he can be hit again.
Enemy Collision Check
This is the main code responsible for detecting the collision and acting accordingly. We could have placed the same function within the player with little difference, but decided that the Player class already had too many.
def update(self): # Checks for collision with the Player hits = pygame.sprite.spritecollide(self, Playergroup, False) # Activates upon either of the two expressions being true if hits and player.attacking == True: self.kill() #print("Enemy killed") # If collision has occured and player not attacking, call "hit" function elif hits and player.attacking == False: player_hit()
The main thing to note here is that once the collision has been detected, there are two possible outcomes. If the player is currently in an attack mode, it means that the collision has occurred due to the player’s attack. Thus, we kill off the enemy sprite using the
On the other hand, if the collision occurred and the Player was not in a state of attack, we take this to mean that the enemy has hit the player. This will call the
player.hit() method to let the player know that he has been hit.
Player Collision Handling
Now it’s time to create code for the Player’s side of the collision detection. First thing we need to do is create the Player group. If you noticed earlier, we called
spritecollide() between each enemy and a sprite group called Player group.
player = Player() Playergroup = pygame.sprite.Group() Playergroup.add(player)
It’s a bit weird to have only sprite in the group, but it makes sense when you realize there can be more than one player.
We’re going to add a new variable in the Player
__init__ method to track the hit cooldown (we explained this earlier).
#Combat self.attacking = False self.cooldown = False self.attack_frame = 0
And here is the method that’s called by the enemy once it has hit the Player successfully.
def player_hit(self): if self.cooldown == False: self.cooldown = True # Enable the cooldown pygame.time.set_timer(hit_cooldown, 1000) # Resets cooldown in 1 second print("hit") pygame.display.update()
“If” the cooldown is over (a.k.a “False”) then we activate the cooldown and register a hit with the player. We once again use a timer to ensure that the cooldown is set back to False after 1 second.
We don’t have a health bar system yet, and we don’t want to kill our Player after a single hit. Due to this we simply print out a “hit” message for now.
This is the code that resets the cooldown once a second has past after being hit. It’s important to call the timer again with a time duration of 0. This automatically disables it. Otherwise it could keep calling itself every second wasting resources.
for event in pygame.event.get(): if event.type == hit_cooldown: player.cooldown = False pygame.time.set_timer(hit_cooldown, 0)
Finally, in order to get our Enemy up and running we need to call all three functions in the game loop. Right now we’re just testing things out, so where you place these doesn’t matter much.
enemy.update() enemy.move() enemy.render()
Now, there is a slight bug over here which will cause the enemy to continue being displayed even after dying. The reason is that calling the
render() method will display the enemy, whether he is “killed” or not. Due to the lack of visual output, we’ve included a print function that will display a message once the enemy is killed.
We can easily fix this using a state tracking variable which prevents the render function from activating after the enemy is dead. However, we are going to wait for the next two tutorials, where we introduce the concept of Sprite groups for a more proper system of enemy handling.
The below output is from a version where we did add Sprite groups into our code. You can still run the current version, but instead of enemy dying, a message will simply be displayed on the console.
You can take a quick look at the Sprite groups concept from the our enemy generation tutorial if you really want to see this work on your PC a.s.a.p.
In the next two sections we will finally create a proper system of stage and enemy generation in our game. No more random enemies popping up out of nowhere once you start up the game. By the end of the next two tutorials, we will have an actual game with different stages which you can travel between.
This marks the end of the Pygame RPG Tutorial – Collision Detection. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial can be asked in the comments section below.