This is Pause Button tutorial (18) in the pygame RPG series
Regardless of how much pre-planning is done, there are certain issues that only come to light when you’re in the middle of developing the game in question. Similarly, as we developed this game, I noticed a lack of several features.
Most notably, we currently lack the ability to pause the game mid-way, neither is there any way for us to return back to the original “home” area of our game, once we have entered a dungeon.
In this Pygame RPG tutorial we’ll introduce the concept of a Pause button, which we can use the solve the above two issues. Fair warning, this is going to be a really long tutorial.
Button Class
We’ll start off with the main feature, the Button. The init
function is fairly straightforward, setting the position of the button to the coordinates (620, 300)
. The imgdisp
variable will keep track of the button mode, and will have a value of either 0 or 1.
class PButton(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.vec = vec(620, 300)
self.imgdisp = 0
Our goal is to create a single button that swaps between three modes. The main mode is “home button”, which is displayed normally, when not in combat. The second and third modes are only activated if the Player is in battle. (Because it doesn’t make much sense to have a pause button outside of battle. In this game anyway.)
This brings us to the render function of the PButton Class. If takes an extra parameter, called num
, which is used to determine which image to display. Another value, cursor.wait
is also used to determine which image is to be displayed. More on this later though, when we begin making the Cursor class.
def render(self, num):
if (num == 0):
self.image = pygame.image.load("home_small.png")
elif (num == 1):
if cursor.wait == 0:
self.image = pygame.image.load("pause_small.png")
else:
self.image = pygame.image.load("play_small.png")
displaysurface.blit(self.image, self.vec)
Creating a Button in Pygame is hard work. To learn more about Button Creation and Management in Pygame, follow this link.
Cursor Class
This tutorial is about a button, so why are we making a Cursor class? The reason is simple. Pygame doesn’t have any inbuilt way of creating buttons or detecting buttons. For this reason, we have make everything ourselves, even the hover animation that appears when you move the cursor over the button. And this is exactly why we are making a cursor class.
The first thing to do is create the cursor class, and load up an image into it. Like we’ve been doing uptil now, we’ll also extract a rect from it.
class Cursor(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load("cursor.png")
self.rect = self.image.get_rect()
self.wait = 0
We’ve also added the self.wait
variable here, which we used earlier in the Button Class. The cursor is the class that is going to hold this variable.
The next function is pause
. Remember, that when you click a Play button, the video/music begins playing, and the button image switches to a Pause symbol. When you click the Pause button, it stops playing and the symbol goes back to being Play.
def pause(self):
if self.wait == 1:
self.wait = 0
else:
self.wait = 1
Similarly, the above code will turn self.wait
to 0, if it has the value 1 and vice versa.
This is the function responsible for the change in the cursor when hovering over the button. The if statement as line 2 is in charge of determining whether the mouse is hovering over the button.
def hover(self):
if 620 <= mouse[0] <= 670 and 300 <= mouse[1] <= 345:
pygame.mouse.set_visible(False)
cursor.rect.center = pygame.mouse.get_pos() # update position
displaysurface.blit(cursor.image, cursor.rect)
else:
pygame.mouse.set_visible(True)
Remember that we put the button at coordinates (620, 300). Those aren’t the center coordinates, rather they are the topleft coordinates. Since the width is 50, it’s obvious that it’s located in between 620 and 670 (620 + 50). Same concept goes for the height.
Once the condition has been passed successfully, we’ll set the current cursor’s visibility to “off” (Line 3). We’ll then replace it with our own cursor, by putting it at the same location as the regular cursor, and then drawing it to that location (Line 4 and 5).
If the condition is false (meaning the mouse isn’t over the button), the visibility will be set to True.
Home Function
Below is the code for the home function. It’s pretty well commented, so you should be able to understood most of it without explanation.
One interesting new thing is at Line 3. The easiest way to disable a time (to stop enemies from generating), is to set the timer to 0. This effectively stops the enemies from generating.
def home(self):
# Reset Battle code
pygame.time.set_timer(self.enemy_generation, 0)
self.battle = False
self.enemy_count = 0
self.dead_enemy_count = 0
self.stage = 1
# Destroy any enemies or items lying around
for group in Enemies, Items:
for entity in group:
entity.kill()
# Bring back normal backgrounds
castle.hide = False
background.bgimage = pygame.image.load("Background.png")
ground.image = pygame.image.load("Ground.png")
Most of the code in this function is just returning things to their regular state. Turning battle to False, killing any existing enemies and leftover Items, switching the backgrounds (though this has no effect right now) etc.
Game Loop
It’s pretty obvious that we’ll need the coordinates of the mouse before we proceed any further. Luckily we can easily manage this by using the get_pos()
function.
while True:
player.gravity_check()
mouse = pygame.mouse.get_pos()
The variable mouse
, now stores a list of two values, the first being the x-coordinate, and the second being the y-coordinate. The way we’ve positioned this line, it will refresh every with every iteration of the game loop, and before any of the other code has a chance to execute. (allowing for the latest values to be used).
Below is how we’ll actually be using the mouse coordinates. It’s the same thing that we discussed earlier in the Cursor class.
# For events that occur upon clicking the mouse (left click)
if event.type == pygame.MOUSEBUTTONDOWN:
if 620 <= mouse[0] <= 670 and 300 <= mouse[1] <= 345:
if button.imgdisp == 1:
cursor.pause()
elif button.imgdisp == 0:
handler.home()
The only difference, is the code within the condition. If the button is on mode 1, it will cause the pause()
function, otherwise it will call the home()
function. Mode 1 represents the fact that the Player is currently in a battle situation. Mode 2 represents that he’s currently idle, and not in battle.
# Display and Background related functions
background.render()
ground.render()
button.render(button.imgdisp)
cursor.hover()
Remember where we added the background and ground render codes? Well, we have to add the cursor and button render codes there too.
We’ve been adding alot of things, but don’t worry. We’re almost done.
Event Handler
When moving to the next stage, we want to switch the button to mode 1. This is because we’re beginning the next level, so we obviously want to switch to the Pause/Play mode.
def next_stage(self): # Code for when the next stage is clicked
button.imgdisp = 1
self.stage += 1
Similarly, we’ll add the button.imgdisp = 1
line, in all the world code blocks as well. This ensures that the button is converted to Pause/Play mode when a world begins. (Next stage only covers stage 2 and onwards)
def world1(self):
self.root.destroy()
pygame.time.set_timer(self.enemy_generation, 2000)
button.imgdisp = 1
castle.hide = True
self.battle = True
def world2(self):
self.battle = True
button.imgdisp = 1
def world3(self):
self.battle = True
button.imgdisp = 1
StageDisplay
Similarly, just like how we want the button to switch to Pause/Play mode at the start of a battle, we want it to switch back to “Home” mode after a battle is over.
def stage_clear(self):
self.text = headingfont.render("STAGE CLEAR!", True , color_dark)
button.imgdisp = 0
Hence we’ll switch the mode to 0, as shown in the image above.
Other Misc. Changes:
We still have some minor changes to be making, without which our system is not complete. Uptil now, we’ve fully developed the Pause + Cursor + Home features. However, we haven’t actually specified “how” exactly everything is supposed to be paused. If you were to run the code right now, the Pause button would have no effect.
The reason for this, is because we haven’t made any connection between the Enemy/Player and the pause code. This is something we’ll achieve with the cursor.wait
variable, which tracks whether the game is paused or not.
def update(self):
if cursor.wait == 1: return
In the above code, you can see that the function does not run if the cursor.wait variable
has the value of 1. It simply returns itself, without doing anything. This is how we intend to achieve the “pause” effect. By disabling the required functions.
So which are the functions in which we have to add this line? Here’s a list of them.
Change 1: (Game Loop, where key presses are detected)
# Event handling for a range of different key presses
if event.type == pygame.KEYDOWN and cursor.wait == 0:
Change 2: (Enemy Class, move function)
def move(self):
if cursor.wait == 1: return
Change 3: (Player Class, attack function)
def attack(self):
if cursor.wait == 1: return
Change 4: (Player Class, update function)
def update(self):
if cursor.wait == 1: return
Showcase
Next Section
This marks the end of our Part 2 to our Pygame Tutorial Series. You can find a Button that will lead you to Part 3 at the end of the article.
Here you can find the link to Code Review 3, as well as a download link for the code + materials.
The Link to the next tutorial:
This marks the end of the Pygame RPG Tutorial – Pause Button. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.