Game Development using Pyglet - PongPong - Part II
What We Learned So Far?
- We know what pyglet is and how to design the PongPong game.
- We have the project structure and created main
- We created the Walls, Paddle and Ball classes, initialized with some very important variables (which we will use here).
- We learned the use of those variables and why are they important.
Let’s create our window and load the game objects:
# ./PongPong/pongpong.py # Variables, Considering a vertical oriented window for game WIDTH = 600 # Game Window Width HEIGHT = 600 # Game Window Height BORDER = 10 # Walls Thickness/Border Thickness RADIUS = 12 # Ball Radius PWIDTH = 120 # Paddle Width PHEIGHT = 15 # Paddle Height ballspeed = (-2, -2) # Initially ball will be falling with speed (x, y) paddleacc = (-5, 5) # Paddle Acceleration on both sides - left: negative acc, right: positive acc, for x-axis
We will see what is happening line-by-line:
# ./PongPong/pongpong.py import pyglet from pong import load class PongPongWindow(pyglet.window.Window): def __init__(self, *args, **kwargs): super(PongPongWindow, self).__init__(*args, **kwargs) self.win_size = (WIDTH, HEIGHT) self.paddle_pos = (WIDTH/2-PWIDTH/2, 0) self.main_batch = pyglet.graphics.Batch() self.walls = load.load_rectangles(self.win_size, BORDER, batch=self.main_batch) self.balls = load.load_balls(self.win_size, RADIUS, speed=ballspeed, batch=self.main_batch) self.paddles = load.load_paddles(self.paddle_pos, PWIDTH, PHEIGHT, acc=paddleacc, batch=self.main_batch) def on_draw(self): self.clear() self.main_batch.draw() game_window = PongPongWindow(width=WIDTH, height=HEIGHT, caption='PongPong') game_objects = game_window.balls + game_window.paddles for paddle in game_window.paddles: for handler in paddle.event_handlers: game_window.push_handlers(handler)
First, we import
loadmodule (we will look at this in a few points later).
class PongPongWindow(pyglet.window.Window):this defines the game window class, that inherits the
Windowclass functionality from
pyglet.window(it is used to create still window in pyglet). After inheriting this, we have all its methods like
PongPongWindowwe initialize the base class using
superfunction. What is super?
We are using
def __init__(self, *args, **kwargs):, here
**kwargsare used to unpack any passed arguments and keyword arguments respectively. They are useful as we don’t know which arguments we will be using later on while creating a window and initializing it using
self.win_size = (WIDTH, HEIGHT)a class variable to be used for creating elements on the window to hold their positions.
self.paddle_pos = (WIDTH/2-PWIDTH/2, 0)paddle’s position. Here,
WIDTH/2will return the center of the window but the paddle’s coordinate starts at bottom-left, which means, if we only set paddle’s position to
WIDTH/2then its bottom-left would be at
WIDTH/2but we don’t want that (because it would feel like the paddle is not in the center). To rectify that, we need to subtract
PWIDTH/2(half of paddle’s width) from
WIDTH/2, since we need to shift the paddle to left by half to make it in center, so that paddle’s center would be at
WIDTH/2(If it seems tricky, get a pen and a paper and draw window and paddle and see how this makes sense, but don’t forget everything in pyglet space starts from bottom-left).
self.main_batch = pyglet.graphics.Batch(), we create a batch now. Batch is something that groups different elements that needs to be drawn and draw then in a single call. For example, if we need to draw a rectangle and a circle, so during window creation and loading of objects, we would need to call a method like
draw2 times, 1 for rectangle and 1 for circle, but using a batch makes it even simpler, so if we club that rectangle and that circle in same batch, then just calling that batch’s
drawwill draw both rectangle and circle in a single call. It is helpful to limit the code we write and make the code scalable.
self.walls = load.load_rectangles(self.win_size, BORDER, batch=self.main_batch),
self.balls = load.load_balls(self.win_size, RADIUS, speed=ballspeed, batch=self.main_batch)and
self.paddles = load.load_paddles(self.paddle_pos, PWIDTH, PHEIGHT, acc=paddleacc, batch=self.main_batch), all these create and load the objects on game window (but still not drawn). Here, walls and ball creation takes window size
self.win_sizeand paddle takes paddle position
self.paddle_pos, followed by
RADIUSfor ball and
paddleaccfor paddle (to know more about these constants variable please follow part 1). Then we pass the batch
self.main_batch, this batch will contain all these walls, ball and paddle, we passed same batch to all 3 load methods. All these load functions will return a list containing
nnumber of walls, balls and paddles (in our case it will be 3 walls, 1 ball and 1 paddle, but we can scale it to have
nnumber of these). We will see later how these load methods work with all these passed arguments.
def on_draw(self):this is a method present in the super class, we override it to draw the things we want.
self.clear()clears anything and everything present in memory of window creation if present. May be helpful in simultaneous window creation, but a good practice to use this.
self.main_batch.draw()then we draw the batch that contains all loaded objects (only 1 call to draw and everything will be available).
game_window = PongPongWindow(width=WIDTH, height=HEIGHT, caption='PongPong')now we create the game window we defined. We pass the width and height parameters with a caption to the window.
game_objects = game_window.balls + game_window.paddleswe define game objects that needs to be moved or that involves some position changes throughout the game. Ball and Paddle created are in a list returned by load functions.
In this for loop, for every paddle in-game window, we push its event handlers to game window to let it know that whenever some particular event occurs please know that it belongs to a certain element in window. Secondly, why are we using a for loop to push event handlers when we have only 1 paddle ? Its because maybe in future if there is a case where we want another paddle to be made, then just adding that paddle in load function will be enough and hence promotes the scalability, as there will be no further change in the main file. Well, so much we have covered, now let’s move on to creating load functions that are used to load the objects.
for paddle in game_window.paddles: for handler in paddle.event_handlers: game_window.push_handlers(handler)
Load FunctionsThe load functions are the ones that we used in
PongPongWindowclass to help load the objects and store those objects in main batch. Let’s start its code.
- Importing required modules,
rectangle, these have the required classes.
# ./PongPong/pong/load.py from . import ball, paddle, rectangle from typing import Tuple
- We will create
load_ballsfunction first. Code would look something like this:
def load_balls(win_size : Tuple, radius : float, speed : Tuple, batch=None): balls =  ball_x = win_size/2 ball_y = win_size/2 new_ball = ball.BallObject(x=ball_x, y=ball_y, radius=radius, batch=batch) new_ball.velocity_x, new_ball.velocity_y = speed, speed balls.append(new_ball) return balls
Here, first we create a list
ballsthat will contain
nnumber of balls, in this case it will have only 1 ball.
ball_ydefines the (x, y) coordinate of ball on the window, this point will be the point of ball’s origin, that will be bottom-left of ball.
BallObjectinstance, that takes
(x, y, radius, batch), all these are the arugments of
__init__method of class
pyglet.shapes.Circlethat was inherited by
ycontains the position values of (x, y) coordinate of ball, that would be bottom-left (I don’t know how they calculate bottom-left of a circle !), then there is
radiusof the ball and the
batchargument to specify that which batch it belongs to (remember we passed
self.main_batchin batch in
velocity_y(to know more go through part 1), here we assign them their initial value, that is,
ballspeed = (-2, -2)from
pongpong.py, that means, it will be falling along a line that intersects at point
Finally, we append the created ball to the list
ballsand return that list.
- Let’s create a similar load function for the paddle.
def load_paddles(paddle_pos : Tuple, width : float, height : float, acc : Tuple, batch=None): paddles =  new_paddle = paddle.Paddle(x=paddle_pos, y=paddle_pos, width=width, height=height, batch=batch) new_paddle.rightx = new_paddle.x + width new_paddle.acc_left, new_paddle.acc_right = acc, acc paddles.append(new_paddle) return paddles
paddleslist to contain all paddles created.
new_paddlecontains instance of class
(x, y, width, height, batch), these arguments are defined in inherited class
ydefines bottom-left coordinate of rectangle/paddle,
heightof paddle and
batchcontains batch object in which it will reside (that is,
self.main_batch). To know more about
Paddleclass structure, read through part 1.
new_paddle.rightxcontains the right most x-coordinate of paddle, that we will use to detect collision with right wall.
new_paddle.acc_rightboth defines the amount of points they will move whenever left and right arrow keys are pressed respectively.
Finally, we append the created paddle to the list and return the
def load_rectangles(win_size : Tuple, border : float, batch=None): rectangles =  top = rectangle.RectangleObject(x=0, y=win_size-border, width=win_size, height=border, batch=batch) left = rectangle.RectangleObject(x=0, y=0, width=border, height=win_size, batch=batch) right = rectangle.RectangleObject(x=win_size - border, y=0, width=border, height=win_size, batch=batch) rectangles.extend([left, top, right]) return rectangles
rectangleslist to contain all the rectangles created (in this case they will act as walls).
rightvariables, that will show the respective walls.
Each wall variable is assigned to instance of
(x, y, width, height, batch), these arguments are passed to the class inherited
RectangleObject. These variables are same as defined for paddle.
After instantiating all 3 walls, we append them to
rectangleslist and return that.
pongpong.pyfile. Let’s revisit main
pongpong.pyfile to run the app. Add the following at the end of
Run the file and the output would look something like this: See, the ball is in the center, paddle is in the center and walls are looking good! Hence, so far we have done awesomely amazingly well !
# ./PongPong/pongpong.py if __name__ == '__main__': pyglet.app.run()
Well that was it, in this part we learned:
- How to load our game window.
- How to load elements in that game window and how to code those load functions.
- How to push event handlers to game window if there are any for the elements presents.
- How to run pyglet app, that is, using
Just starting your Open Source Journey ? Don’t forget to check out Hello Open Source Want to
++your GitHub Profile README ? Check out Quote - README Till next time ! Namaste 🙏
Please share your Feedback:
Did you enjoy reading or think it can be improved? Don’t forget to leave your thoughts in the comments section below! If you liked this article, please share it with your friends, and read a few more!