It’s been a while since my last post, so I figured I’d get back onto the horse and do a little more on the development side. I’ve added a new class into the codebase now to begin implementing the main game, it currently doesn’t do much more than show the main playfield at present, but it’s in there now as a base upon which we can build.
In order to implement the new state, and make it loadable, we have to perform the usual mechanics in the main pong.py file and the MainMenuState.py file to (a) load the module and (b) provide the code hook by which it can be activated. This is all code which we’ve seen before when we’ve implemented the Credits state.
The new class itself is, like the other states, an extension of the base State class. At present, the __init__ method sets up a few instance variables, and the startup method simply loads a static PNG image which forms the static backdrop for the playing area. This is simply blitted to the display surface at the start of the display method at the coordinates (0, 0). The PNG image has the dimensions 1024×768 pixels, which is the same as our drawing canvas, so we don’t need to worry about scaling before we draw it. Everything else will be drawn on top of that surface.
The handle_events method has code present to process keyboard inputs for both players and sets internal flags accordingly so we can handle the physics updates later. At present, the keybindings are fixed. Again, something we’ll want to look into fixing at a later date.
So, the code in the MainGameState.py file currently looks like this:
#!/usr/bin/python3
# MainGameState.py
# A state class for the game logic itself. Called from the main menu state
#
# Author: Mark Edwards
# Date: 05/07/2021
# Version: 0.01 - Initial version
#
from State import State
from Graphics import *
import math
class MainGame(State):
def __init__(self, game):
"""Performs any on-load initialisation of the state. e.g. preloading
of resources, set persistent buffer lengths, etc."""
# Call the parent class init method (just in case there's setup in
# there we need to also use.
super().__init__(game)
# Do our custom initialisation here
self.score_p1 = 0
self.score_p2 = 0
self.num_players = 0
self.game_is_running = False
self.p1_down = False
self.p1_up = False
self.p2_down = False
self.p2_up = False
self.playfield = None
def handle_events(self):
"""Run the 'event pump' for this particular state. Note that we
don't call the superclass handler here as each state should
individually process the state messages"""
for event in pygame.event.get():
# First check to see if the game is quitting
if event.type == pygame.QUIT:
self.game_is_running = False
# Check to see if the user has pressed a key, if so, set the
# appropriate flags
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.game_is_running = False
if event.key == pygame.K_a:
self.p1_up = True
if event.key == pygame.K_z:
self.p1_down = True
if event.key == pygame.K_COMMA:
self.p2_down = True
if event.key == pygame.K_l:
self.p2_up = True
# End handle_events
def update(self, game_time, dt):
"""Update the simulation"""
if not self.game_is_running:
self.game.state_manager.pop()
# End update
def display(self):
"""Draw the current frame"""
# We don't need to worry about clearing the previous frame since the
# playfield will obscure everything
self.display_surface.blit(self.playfield, (0, 0))
# Display the drawing canvas on the game window...
self.game.display_window.blit(self.display_surface, (0, 0))
# End display
def startup(self):
"""Perform any state specific initialisation each time the state
becomes current (e.g. resetting scores, setting player positions,
etc."""
super().startup()
# Load in the background playfield and assign to a surface so we can
# simply blit it as needed
self.playfield = pygame.image.load("playfield.png")
self.playfield.convert()
self.game_is_running = True
def cleanup(self):
"""Perform any cleanup of resources once the state is no longer
current (e.g. clearing buffers, deallocating resources, etc."""
super().cleanup()
Next on the agenda is how to implement the ball and the player’s bats. We could simply code them directly into the update and display methods, but this would be bad practice. Since each of these objects will contain similar methods for updating their position, checking for collisions, drawing themselves, it makes sense that we model them using a standardised framework similar to how we have done so with the various State objects we have in the game.
To this end, we’ll look at adding an Actor class to the project along with some control logic so that actors can be processed as needed. This should allow for some level of code reuse and standardisation between objects within the game, which will become far more important if we ever extend this framework out to a larger, more complex implementation.
As ever, code is up in GitHub:
When you want to win a game, you have to teach. When you lose a game, you have to learn.
Tom Landry