voorloopnul

About Me

My name is Ricardo Pascal, I'm a developer and sysadmin who lives in Florianopolis/Brazil. I enjoy work with projects related to Linux, Python, Science, Web, Django and Network.

g+ A Russian scientist who was struck by a particle accelerator beam | I pictured professor Smith from PHD Comics! | | A kickstarter for the next version of Django Rest Framework. | A empresa Neoprospecta está contratando 2 desenvolvedores python para trabalhar em projetos de bioinformática... | A empresa Neoway está contratando desenvolvedor Python para trabalhar num projeto web com Django em ... | I wrote a small article showing how to display a form, and create objects from them using views in five... | | A Pixeon está com várias vagas abertas. (Inclusive programador Python). | "When deciding which server architecture to use for your environment, there are many factors to consider... | | Segue uma proposta de lei feita pelo DEPUTADO Missionário José Olimpio. É uma lei que visa nos proteger... | By +Ruben Bolling    http://gocomics.typepad.com/tomthedancingbugblog/ THU MAY 29, 2014 AT 06:50 AM ... | Let's send also some cancer, radioactive material, ebola, Justin Bieber and Brazilian Funk  ¬¬ | "The most extreme case is David Whitlock, the M.I.T.-trained chemical engineer who invented AO+. He ... | If I had 8k euros to spare, I would definitely buy one :) | | Esse link eu salvei nos favoritos. Vale a pena! | Estamos procurando Jedi Back-end Engineer We are looking for a engaged and dedicated software engineer... | django-auth-imap is a package that let you use a IMAP server as backend for authentication in your django... |

How (not) to create a network game

One of the most important thing that you should keep in mind while developing a real time network game is that latency is a bitch.

Considerations

  • For the sake of simplicity the code will be focused on latency without any attempt to fix that.
  • Our sample code is constructed based on a previous post about proxy, since the server code is 99% the same, a explanation about how the server works can be retrieved from that post.

Overall architecture

The architecture of our game is really really simple, in a raw way it will emulate one of the most common conception of how a network communication in a game can occur:

1) there is a server; 2) each client connect to this server and send his current position; 3) as response, the server send back a dictionary that holds the position of all objects; 4) with this data the client draw all the objects at their respective positions.

spaceship.png

spaceship.png

spaceship-server.py

#!/usr/bin/python
import socket
import select
import time
import sys
import simplejson

buffer_size = 2000
delay = 0
ships = {}

class TheServer:

    def __init__(self, host, port):
        self.input_list = []
        self.channel = {}

        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server.bind((host, port))
        self.server.listen(200)

    def main_loop(self):
        self.input_list.append(self.server)
        while 1:
            time.sleep(delay)
            inputr, outputr, exceptr = select.select(self.input_list, [], [])
            for self.s in inputr:
                if self.s == self.server:
                    self.on_accept()
                    break
                else:
                    self.data = self.s.recv(buffer_size)
                if len(self.data) == 0:
                    self.on_close()
                else:
                    self.on_recv()

    def on_accept(self):
        clientsock, clientaddr = self.server.accept()
        print clientaddr, "has connected"
        ships[clientaddr[1]] = {}
        self.input_list.append(clientsock)

    def on_close(self):
        clientaddr = self.s.getpeername()
        print " %s has disconnected" % clientaddr[0]
        del(ships[clientaddr[1]])
        #remove objects from input_list
        self.input_list.remove(self.s)

    def on_recv(self):
        _id = self.s.getpeername()[1]
        ships[_id] = simplejson.loads(self.data)
        self.s.send(simplejson.dumps(ships))

if __name__ == '__main__':
        server = TheServer('0.0.0.0', 50090)
        try:
            server.main_loop()
        except KeyboardInterrupt:
            print "Ctrl C - Stopping server"
            sys.exit(1)

spaceship.py

#!/usr/bin/env python
import pyglet
import math
import socket
import simplejson

resolution = (800, 600)
keys = pyglet.window.key.KeyStateHandler()
image = pyglet.image.load('ship.png')
image.anchor_x = image.width // 2
image.anchor_y = image.height // 2

def forward():
    conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    conn.connect(('200.200.200.200', 50090))
    _id = str(conn.getsockname()[1])
    return [_id, conn]

def create_ship(_id=False):
    ship = pyglet.sprite.Sprite(image)
    ship.x = resolution[0] / 2
    ship.y = resolution[1] / 2
    if _id:
        ship.opacity = 64
        ship._id = _id
    return ship

class MainWindow(pyglet.window.Window):
    def __init__(self, *args, **kwargs):
        pyglet.window.Window.__init__(self, *args, **kwargs)
        self.push_handlers(keys)
        self.ship = create_ship()
        self.me, self.conn = forward()
        self.players = []
        self.players_id = []

    def on_draw(self):
        self.clear()
        self.ship.draw()
        x, y = self.ship.position
        a = self.ship.rotation
        data = simplejson.dumps([x, y, a])
        self.conn.send(data)
        data = simplejson.loads(self.conn.recv(2000))

        for shipid in data.keys():
            if shipid != self.me and shipid not in self.players_id:
                self.players_id.append(shipid)
                self.players.append(create_ship(shipid))

        for player in self.players:
            if player._id in data:
                player.x, player.y, player.rotation = data[player._id]
            else:
                player.x, player.y, player.rotation = [0, 0, 0]
            player.draw()

        if keys[pyglet.window.key.LEFT]:
            self.ship.rotation -= 2

        if keys[pyglet.window.key.RIGHT]:
            self.ship.rotation += 2

        if keys[pyglet.window.key.UP]:
            diff = math.cos(math.radians(self.ship.rotation))
            self.ship.y += 6 * diff
            increase = math.sin(math.radians(self.ship.rotation))
            self.ship.x += 6 * increase

if __name__ == "__main__":
    MainWindow(width=resolution[0], height=resolution[1])
    pyglet.app.run()

Client explanation

function forward()

returns a communication channel with the server, using this socket the client will send the spaceship position, and retrieve the global position map.

function create_ship()

returns a spaceship object (a pyglet sprite); set the initial position, if that is not your ship define a _id and put a little of opacity;

class MainWindow() init()

Start the pyglet screen; bind the keyboard as input; create your ship; start the communication channel

class MainWindow() on_draw()

(1) Clear screen and draw your spaceship. (2) Stores your spaceship position/orientation, them send this data encoded as json to the server. (3) Receive and decode the global position data. (4) Iterate over the returned data to create a list of ships id and ships objects. (5) Iterate over the list of objects; define their new position and draw them all. (6) Check for keyboard input and move your ship.

What happens when you run the code?

If you use 3 similar computers on a local network, one as server and two as clients, you will have a close to smooth experience. But if you run this over Internet things will go wrong. Do you realize why?

The bitch latency! Over a LAN you will have less than 1ms of latency, in one second close to 500 interchanges between the server and client can occur. (Since we need a response from server, each action will take twice the latency time: 1000ms / (1ms + 1ms) ) But between my home broadband and my vps only 4 happens.

Let's imagine a scenario where 3 people from different parts of the world connect to my game server running at this VPS(in Germany). How the spaceship speed will be seen from the watcher perspective?

  • People A Joe from Haarlem (NL) with 100ms of latency
  • People B Mika from Hamburg (DE) with 50ms of latency
  • People C Me from Florianópolis (BR) with 250ms of latency

That's what will be seen:

Spaceship speed

Each spaceship will have a different speed, and each client will see a different pattern of lag which will warp all the others spaceships. At this point you probably realize that writing a network game is not so simple.

That's all folks!


by Ricardo Pascal on Sep 10, 2012

comments powered by Disqus