Jump to content
  • Advertisement
Sign in to follow this  
jeremyjmwilson

Beginning Game Development with Python

This topic is 800 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

1iQ3TGt.jpg

 

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 theruntime 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:

ho16AlC.png

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:
pygame.quit()
sys.exit()
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):

cc.update()
cc.render()

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

BUFFERSIZE = 512

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])

try:
i.send(pickle.dumps(update))
except Exception:
remove.append(i)
continue

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 = '127.0.0.1'
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 = select.select([s], [], [], 0)
for inm in ins:
gameEvent = pickle.loads(inm.recv(BUFFERSIZE))
if gameEvent[0] == 'id update':
playerid = gameEvent[1]
print(playerid)
if gameEvent[0] == 'player locations':
gameEvent.pop(0)
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:
m.render()

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]
s.send(pickle.dumps(ge))

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:

r3gynp0.gif

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.

Suggest for you:

Zero to Hero with Python Professional Python Programmer Bundle

The Python Mega Course: Build 10 Python Applications

PYTHON Programming Tutorial For Beginners : Learn in 3 hours

The Ultimate Python Programming Tutorial

Start Learning Unity3d by Making 5 Games from Scratch

Mastering HTML5 Game Development

Share this post


Link to post
Share on other sites
Advertisement
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!