Beginning Game Development with Python

Beginning Game Development with Python

I was asked if I’d be keen to go to Kiwi Pycon (a Python conference in Dunedin, New Zealand). I decided it would be an interesting experience even though my Python knowledge is a bit lacking

A few weeks ago I was asked if I’d be keen to go to Kiwi Pycon (a Python conference in Dunedin, New Zealand). I decided it would be an interesting experience even though my Python knowledge is a bit lacking. So as an after-work project my colleague Yosan and I decided to put together a small game to help us learn the language. This article covers our initial experiences putting everything together.

The plan

As with other languages I’ve learned I typically like to develop an application which involves a handful of functions such as reading files, networking, user input and visuals. This forces me to become familiar with libraries and language functions which gets me up to speed in a way that re-implementing algorithms and completing tutorial projects would not. It also forces me to understand a bit about Python’s environment with regards to installing dependencies and creating releases.

We looked up a few libraries related to game creation and networking and decided to use pygame as that seemed to provide a functionality that would remove a lot of the tedium from development. It also looked like Python had a range of libraries for networking so we decided to figure it out when we got to it.

Installing Python

Python itself was relatively easy to install. We just took the auto installer from the website and had the runtime ready within a minute.

Installing Pygame

Pygame proved to be a bit frustrating to install. It took several attempts before we managed to download the script and install it in the correct way. We had to find the correct version of the library (that matched the version of Python we had installed) on a list of dependencies that wasn’t easily found, then extract that with the Python package install utility pip3.exe. This seemed harder than it should have been, especially due to the number of different versions of the library and the slight differences in what we would have to do if we had a different version of Python installed.

Eventually we got things set up and looked for a tutorial on getting the basics of a game up and running.

Drawing a sprite

The first thing to do when getting started with anything graphical is just to get something (or anything) rendered to the screen. We found a whole bunch of tutorials of varying complexity on this and based on their examples came up with a basic render loop:

import pygame, sys
from pygame.locals import *

WIDTH = 400 HEIGHT = 400

screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption('Hello World!')

clock = pygame.time.Clock()

thing = pygame.image.load('images/TrashPanda/TrashPanda_front.png')

x = 0 y = 0

while True: for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit()

clock.tick(30) screen.fill((0,0,0)) screen.blit(thing, (x, y)) pygame.display.flip()

This code produced this:

After that, we focused on capturing user input to move the character. We also created a class for the player character to internalize some of its logic:

class Minion:
  def init(self, x, y):
    self.x = x
    self.y = y
    self.vx = 0
    self.vy = 0
  def update(self):
    self.x += self.vx
    self.y += self.vy
    #this keeps the player character within the bounds of the screen
    if self.x > WIDTH - 50:
      self.x = WIDTH - 50
    if self.x < 0:
      self.x = 0
    if self.y > HEIGHT - 50:
      self.y = HEIGHT - 50
    if self.y < 0:
      self.y = 0

def render(self): screen.blit(thing, (self.x, self.y))

User input was captured within the game loop:

for event in pygame.event.get():
  if event.type == QUIT:
  if event.type == KEYDOWN:
    if event.key == K_LEFT: cc.vx = -10
    if event.key == K_RIGHT: cc.vx = 10
    if event.key == K_UP: cc.vy = -10
    if event.key == K_DOWN: cc.vy = 10
  if event.type == KEYUP:
    if event.key == K_LEFT and cc.vx == -10: cc.vx = 0
    if event.key == K_RIGHT and cc.vx == 10: cc.vx = 0
    if event.key == K_UP and cc.vy == -10: cc.vy = 0
    if event.key == K_DOWN and cc.vy == 10: cc.vy = 0

And the character’s position was updated and rendered (also in the gameloop):


Now that we had basic character movement working, we wanted to start building some simple multiplayer functionality.

We decided on a very simple data transfer model:

  • Clients would connect to the server and then continually broadcast the position of their own character
  • The server would then broadcast the location of all characters to all clients

We decided to use TCP sockets as they handle things like connects and disconnects easier than UDP. Also this isn’t exactly a performance critical application.

We managed to find a good article covering writing async servers in Python here.

The basic server code started as this:

import socket
import asyncore
import random
import pickle
import time


outgoing = []

#additional logic here...

class MainServer(asyncore.dispatcher): def init(self, port): asyncore.dispatcher.init(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.bind(('', port)) self.listen(10)

def handle_accept(self): conn, addr = self.accept() print ('Connection address:' + addr[0] + " " + str(addr[1])) outgoing.append(conn) playerid = random.randint(1000, 1000000) playerminion = Minion(playerid) minionmap[playerid] = playerminion conn.send(pickle.dumps(['id update', playerid])) SecondaryServer(conn)

class SecondaryServer(asyncore.dispatcher_with_send): def handle_read(self): recievedData = self.recv(BUFFERSIZE) if recievedData: updateWorld(recievedData) else: self.close()

MainServer(4321) asyncore.loop()

This defines a MainServer responsible for accepting new TCP connections which it then creates a SecondaryServer for. The secondary servers handles all incoming data from each client. When an incoming packet is received, the data is passed to updateWorld. This is defined below:

class Minion:
  def init(self, ownerid):
  self.x = 50
  self.y = 50
  self.ownerid = ownerid

minionmap = {}

def updateWorld(message): arr = pickle.loads(message) playerid = arr[1] x = arr[2] y = arr[3]

if playerid == 0: return

minionmap[playerid].x = x minionmap[playerid].y = y

remove = []

for i in outgoing: update = ['player locations']

for key, value in minionmap.items():
  update.append([value.ownerid, value.x, value.y])

except Exception:

for r in remove: outgoing.remove(r)

updateWorld is simply responsible for updating the dictionary containing the location of each player’s character. It then broadcasts the positions to each player by serializing their positions as an array of arrays.

Now that the client was built we could implement the logic in the client to send and receive updates. When the game is started we added some logic to start a simple socket and connect to a server address. This optionally takes an IP address specified by the command line but otherwise connects to localhost:

serverAddr = ''
if len(sys.argv) == 2:
 serverAddr = sys.argv[1]
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((serverAddr, 4321))

We then added some logic to the start of the game loop to read from the socket. We utilized the ‘select’ package to read incoming packages from the socket only when they had data. If we had used ‘socket.recv’ the gameloop would halt if the socket didn’t have any packets to read. Using ‘select’ allows the gameloop to continue executing even if there isn’t anything to read:

ins, outs, ex =[s], [], [], 0)
  for inm in ins: 
    gameEvent = pickle.loads(inm.recv(BUFFERSIZE))
    if gameEvent[0] == 'id update':
      playerid = gameEvent[1]
    if gameEvent[0] == 'player locations':
      minions = []
      for minion in gameEvent:
        if minion[0] != playerid:
          minions.append(Minion(minion[1], minion[2], minion[0]))

The above code handled two of the serialized payloads the server could possibly produce.

1. The initial packet containing the players server assigned identifier

This is used by the client to identify itself to the server on all position updates. It also used to ignore its own player data that server broadcasts so there isn’t a shadowed version of the player character.

2. The player location payload

This contains a set of arrays containing player identifiers and character positions. When this is retrieved the existing Minion objects are cleared and new Minion objects are created for each of the transmitted ones.

The other Minions are then rendered in the game loop:

  for m in minions:

The last thing we had to do was to add some code to the client to tell the server the position of the player. This was done by adding a broadcast at the end of the gameloop to serialize the current players position using ‘pickle‘, then sending this bytestream to the server:

  ge = ['position update', playerid, cc.x, cc.y]

Once this was complete players connected to the same server could see the other players moving around.

Some additional updates such as displaying different avatars based on the playerid were implemented.

When finished, the current iteration with two players looked like this:

The full code for both client and server is available here.

Of course there’s always of room for development and improvement.  If you found this article interesting, let us know in the comments below.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Learn More

Complete Python Bootcamp: Go from zero to hero in Python 3

Machine Learning A-Z™: Hands-On Python & R In Data Science

Python and Django Full Stack Web Developer Bootcamp

Complete Python Masterclass

The Python Bible™ | Everything You Need to Program in Python

Guide from a Python project to an open source package

Top Popular Python open-source projects on GitHub

Python at Netflix

MySQL Databases With Python Tutorial

Build Your First Python and Django Application

Exploring Python Basics (Free eBook

An A-Z of useful Python tricks

A Complete Machine Learning Project Walk-Through in Python

Learning Python: From Zero to Hero

python game-development

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Hire Python Developers

Are you looking for experienced, reliable, and qualified Python developers? If yes, you have reached the right place. At **[]( "")**, our full-stack Python development services...

Hire Python Developers India

Looking to build robust, scalable, and dynamic responsive websites and applications in Python? At **[]( "")**, we constantly endeavor to give you exactly what you need. If you need to...

Top Python Development Companies | Hire Python Developers

After analyzing clients and market requirements, TopDevelopers has come up with the list of the best Python service providers. These top-rated Python developers are widely appreciated for their professionalism in handling diverse projects. When...

Rummy Game Software Development

Rummy Game Software Development- Mobiweb Technologies is India's leading Rummy Game Software provider company having expertise in providing Rummy website and app development solutions.

Mobiweb- A Leading Ludo Game App Development Company

Mobiweb Technologies is a leading [ludo game app developmen]( "ludo game app developmen")t company that provides multiple types of ludo games in a single application. Ludo is one of the...