How-To: Easily Create PyGame User Interface & Heads Up Display Elements — Part II
In my previous entry, How-To: Easily Create PyGame user interface & Heads Up Display Elements, I introduced you to the new open source library pyguix that enables you to create desired user interface elements during game design / game run time for python based pygames.
In this post I will continue the dive into how easy it is to implement such user interface elements as MessageBoxes or Heads Up Displays using pyguix. In order to showcase the content in this post, as well as future post, a new GitHub open source project now exists that houses the different instances of the game I’ve named ‘Mars’. You can read more about the PyGame-Mars specific project from its README.
What you see in the above image is a snippet of the game running, with a pyguix.ui.elements.MessageBox (ui.MessageBox) instance asking if the user really wants to quit the game. Further you see a ‘Mars Rover’ sprite, and a pyguix.ui.elements.SnapHUD (ui.SnapHUD) heads up display showing ‘Rover Details’ about in game information that is needed for interaction with the player.
How did we get to displaying the ui.MessageBox and ui.SnapHUD user interface elements?
I — Getting Started:
1. First you will need to clone the repo of pyguix, from its home on GitHub.
2. Next, you will need to copy/paste the pyguix folder as a ‘sub-folder’ of the desired game instance in which you will be developing your game. You can see a similar action has taken place for the ‘PyGames-Mars’ pygame.
Once this is in place then you have what you need to start implementing and using user interface elements like ui.MessageBox and ui.SnapHUD.
Now lets break down the steps needed to implement both the ui.MessageBox and ui.SnapHUD, like the examples found in he ‘PyGames-Mars/01_Intro’ game instance.:
II — pyguix.ui.elements.MessageBox (ui.MessageBox):
1. Open the main.py file, and create an import line that will import the pyguix.ui.elements as ui.
2. Create a function on the Game object that is being used to house the Game instance and specifically for init() of all other elements (ie: Level, Player, etc.) as well as the run() bound functin. An example of such a function is shown in the image below, called ‘game_quit() → bool:’
3. Further, in the above image you see the second red arrow pointing to specific code example of ui.MessageBox(…) instance being created as ‘msgbox’ variable. Further you can see, in being paired with the third red arrow, that the msgbox.canceled() and *.clicked() bound functions are being used to set the return value of the ‘game_quit()’ function to the Game.run(). This is completed using an inline ‘if’ statement of setting the ret variable, True or False.
msgbox = ui.MessageBox(
window = self.level.display_surface,
message_text="Would you like to quit the game?",
title="Quit?",
buttons=("Yes","No"),
width=300,
event_list=pygame.event.get()
)
ret = False if msgbox.canceled() or msgbox.clicked() == "No" else True
That is it! That is all the code needed to generate the MessageBox when the Game.run() code detects a pygame.QUIT event, or KEYDOWN, event.KEY == K_ESC or K_q.
if event.type == pygame.QUIT:
if self.game_quit():
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q or event.key == pygame.K_ESCAPE:
if self.game_quit():
pygame.quit()
sys.exit()
You can see from the python code used to create a new instance of ui.MessageBox(…) that several variables are used to shape the MessageBox itself. From title, to message text, as well as buttons used. Notice how the user can cancel the MessageBox as well as you can have code to operate off of which button was clicked by the player.
Finally for the MessageBox, the default settings are built in with allowing for the ‘return’ or ‘Enter’ key to act upon the first button in the button array. (ie: Yes in the example) Further the same default settings are set so as too allow the player to press the ‘Esc’ key and have it act upon the ‘cancel button’ (ie: ‘X’ in top right hand corner, which equates to ui.MessageBox.canceled() returning a True value.)
III — pyguix.ui.elements.globaltheme() (ui.globaltheme()):
It is worth pointing out that as part of this demonstration of the pyguix speed and flexibility, that a global theme is in place for this game instance of ‘PyGame-Mars/01_Intro’. The theme being used is ‘ex_red.json’. This is one of the several JSON themes currently ‘shipped’ with pyguix.
In order to implement the global theme, which is used by all pyguix elements when set, simply call the unbound function globaltheme(), like in the following example, which is found in the Game.init() bound function:
# Set pyguix ui elements global theme:
ui.globaltheme("ex_red.json")
Once this is set, then every pyguix element will make use of that supplied theme.
IV — pyguix.ui.elements.SnapHUD (ui.SnapHUD):
Contained in this next section of the article, you will find details that help you dive into the ui.SnapHUD element that is in use for the ‘PyGames-Mars/01_Intro’ game instance.
In the above image you see in action the ui.SnapHUD with the black arrow pointed towards its ‘Closed’ state. Further the image shown below is the ui.SnapHUD in its ‘Open’ state, as highlighted by the black arrow.
This is the reason the element was named SnapHUD, in the fact that it is a easy to configure heads up display area, which can ‘snap’ open or closed. Allowing great flexibility in design as well as game play interaction with the player.
Having completed the ‘I — Getting Started’ section, you will need to:
1. First target a pyguix.ui.context.*.json file for the SnapHUD element. In the image below, the ‘SnapHUD_default.json’ file is used to configure the ui.SnapHUD instance shown throughout the game instance and images in this article.
Note: You can find this exact JSON file from the following GitHub PyGames-Mars/01_Intro game instance.: SnapHUD_Default.json
2. Once you have a targeted context.json file, you will need to save it in the (game_instance)/pyguix/ui/context folder location.
Note: In the above image you see both the context and settings files for the SnapHUD instance.
3. Now that the context.json file is in place, when need to implement the details of the file that was created. Specifically pyguix.ui.elements.SnapHUD instances are configured with a settings/*.json file and a context/*.json file.
In the ‘SnapHUD_default.json’ example we list a specific ‘SnapHUD.details.infoclass’ value for the class instance of the SnapHUDPartInfo typed class that will house the parts.(part_name).function which each part will call when rendered for update() and draw() events of the element.
Since the example list this class location type from the __main__ module, the info class code should be placed in the main.py python file location. You can see an example of this in the following image.
Finally from the above, you can see that we create a single instance of ‘RoverDetailsInfo(ui.SnapHUDPartInfo)’ class as part of the Game.init() call. Both highlighted by red arrows.
# Rover Details pyguix.ui.elements.SnapHUDPartInfo class:
class RoverDetailsInfo(ui.SnapHUDPartInfo):
def center_pos(self,v=None):
return self.partinfo("center_pos",v)
def rover_direction(self,v=None):
return self.partinfo("rover_direction",v)
def last_sample(self,v=None,md=None):
self.partinfo("mineral_detected",md)
return self.partinfo("last_sample",v)
def mineral_detected(self):
# NOTE: lookup call only, set with sample code above.
return self.partinfo("mineral_detected")
4. Next are focus will change to a game class called Level, which becomes a single instance created by the game and is has its .run() function called by the game. For the ‘PyGames-Mars/01_Intro’ game instance the Level class lives in in a python file named level.py.
In the above image you see highlighted the import, the focus of the Level.__init__(self) function, and then the creating of Level(self).rover_snap_hud = ui.SnapHud(…) instance. Further you also see variables being set for window, context and rg.:
a. window = active pygame display surface
b. context = JSON context file to use for configuration and mapping for parts to infoclass.functions()
c. rg = render group to use to render the updates() for the ui.SnapHUD instance.
# Create UI Elements:
# 01 - SnapHUD for Mars Rover Details:
self.rover_snap_hud = ui.SnapHUD(
window=self.display_surface,
context='SnapHUD_default.json',
rg=self.visible_sprites
)
5. Finally updates to the main.py python game file need to be made. Specifically adding an updatehud(self) bound function to the Game class, code for calling level.rover_snap_hud.clicked() on pygame.MOUSEBUTTONDOWN/pygame.BUTTON_LEFT event.type/event.button, then Game.run() call, after level.run() call for self.updatehud().
You can see these examples in the following image:
When we look at the code:
def updatehud(self):
""" update SnapHUD Rover Details parts """
self.rover_info.center_pos(str(self.level.player.rect.center))
self.rover_info.rover_direction(str(self.level.player.nav_direction.name))
# NOTE: Check to see if last sample produced a valid mineral found in sample taken:
if self.level.player.mine_cords.__contains__(self.level.player.last_sample):
m = self.level.player.mine_cords[self.level.player.last_sample]
else:
m = None
self.rover_info.last_sample(str(self.level.player.last_sample),m)
We see that the Game.rover_info.center_pos() bound function is called for example. This is updating the ui.globalcontext() (__global_cache__) value for that stored key, representing that function value. Through the use of reflection, the context.json file (ie: SnapHUD_default.json) mapping tells the python code elements what class instance, and function to call in order to retrieve the value for rendering of the configured heads up display parts.
Since the ui.SnapHUD element is designed in such a way, it is easy to add more parts, remove parts, start Open or Closed, just by updating the context.json file, and a little bit of code.
Finally the */pyguix/ui/settings/SnapHUD.json is used in the game instance being shown ‘PyGames-Mars/01_Intro’. You can change SnapHUD element properties like width of Parts, width of the SnapHUD element, font size, etc.
V — Conclusion
My goal with this article is to implement the concepts introduced in my previous article, How-To: Easily Create PyGame User Interface & Heads Up Display Elements, so you could start implementing globally themed, easy to use, yet robust and flexible user interface and heads up displays in your python, pygames.
We covered the steps for creating instances of the pyguix.ui.elements.MessageBox and pyuix.ui.elements.SnapHUD python/pygame classes for game interaction needs. With these simple examples, you can now have powerful, easy to implement, yet flexible in-game user interactions that use only core python and pygame libraries.
More entries are on the way, however any feedback, comments, thoughts, or request are most welcome!
J. Brandon George
twitter: @PyFryDay
email: darth.data410@gmail.com
github: DarthData410
GitHub Project Source: https://github.com/DarthData410/PyGames-Mars