├── .gitignore ├── README.md ├── game.py ├── images ├── BlueThing │ └── BlueThing_front.png ├── Ladette │ └── Ladette_front.png ├── TrashPanda │ └── TrashPanda_front.png └── Tubby │ └── Tubby_front.png └── server.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Very Basic Python Multiplayer game prototype. 2 | 3 | Currently allows players to run around a screen together. 4 | 5 | Developed for Python 3.5.2 using pygame. 6 | 7 | start the server by running 8 | 9 | `py server.py` 10 | 11 | then in a separate terminal start the client with 12 | 13 | `py game.py` 14 | 15 | foreign clients can join by specifying your ipaddress eg: 16 | 17 | `py game.py 192.10.1.50` 18 | -------------------------------------------------------------------------------- /game.py: -------------------------------------------------------------------------------- 1 | import pygame, sys 2 | from pygame.locals import * 3 | import pickle 4 | import select 5 | import socket 6 | 7 | WIDTH = 400 8 | HEIGHT = 400 9 | BUFFERSIZE = 2048 10 | 11 | screen = pygame.display.set_mode((WIDTH, HEIGHT)) 12 | pygame.display.set_caption('Game') 13 | 14 | clock = pygame.time.Clock() 15 | 16 | serverAddr = '127.0.0.1' 17 | if len(sys.argv) == 2: 18 | serverAddr = sys.argv[1] 19 | 20 | sprite1 = pygame.image.load('images/BlueThing/BlueThing_front.png') 21 | sprite2 = pygame.image.load('images/Ladette/Ladette_front.png') 22 | sprite3 = pygame.image.load('images/TrashPanda/TrashPanda_front.png') 23 | sprite4 = pygame.image.load('images/Tubby/Tubby_front.png') 24 | 25 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 26 | s.connect((serverAddr, 4321)) 27 | 28 | playerid = 0 29 | 30 | sprites = { 0: sprite1, 1: sprite2, 2: sprite3, 3: sprite4 } 31 | 32 | class Minion: 33 | def __init__(self, x, y, id): 34 | self.x = x 35 | self.y = y 36 | self.vx = 0 37 | self.vy = 0 38 | self.id = id 39 | 40 | def update(self): 41 | self.x += self.vx 42 | self.y += self.vy 43 | 44 | if self.x > WIDTH - 50: 45 | self.x = WIDTH - 50 46 | if self.x < 0: 47 | self.x = 0 48 | if self.y > HEIGHT - 50: 49 | self.y = HEIGHT - 50 50 | if self.y < 0: 51 | self.y = 0 52 | 53 | if self.id == 0: 54 | self.id = playerid 55 | 56 | def render(self): 57 | screen.blit(sprites[self.id % 4], (self.x, self.y)) 58 | 59 | 60 | #game events 61 | #['event type', param1, param2] 62 | # 63 | #event types: 64 | # id update 65 | # ['id update', id] 66 | # 67 | # player locations 68 | # ['player locations', [id, x, y], [id, x, y] ...] 69 | 70 | #user commands 71 | # position update 72 | # ['position update', id, x, y] 73 | 74 | class GameEvent: 75 | def __init__(self, vx, vy): 76 | self.vx = vx 77 | self.vy = vy 78 | 79 | cc = Minion(50, 50, 0) 80 | 81 | minions = [] 82 | 83 | while True: 84 | ins, outs, ex = select.select([s], [], [], 0) 85 | for inm in ins: 86 | gameEvent = pickle.loads(inm.recv(BUFFERSIZE)) 87 | if gameEvent[0] == 'id update': 88 | playerid = gameEvent[1] 89 | print(playerid) 90 | if gameEvent[0] == 'player locations': 91 | gameEvent.pop(0) 92 | minions = [] 93 | for minion in gameEvent: 94 | if minion[0] != playerid: 95 | minions.append(Minion(minion[1], minion[2], minion[0])) 96 | 97 | for event in pygame.event.get(): 98 | if event.type == QUIT: 99 | pygame.quit() 100 | sys.exit() 101 | if event.type == KEYDOWN: 102 | if event.key == K_LEFT: cc.vx = -10 103 | if event.key == K_RIGHT: cc.vx = 10 104 | if event.key == K_UP: cc.vy = -10 105 | if event.key == K_DOWN: cc.vy = 10 106 | if event.type == KEYUP: 107 | if event.key == K_LEFT and cc.vx == -10: cc.vx = 0 108 | if event.key == K_RIGHT and cc.vx == 10: cc.vx = 0 109 | if event.key == K_UP and cc.vy == -10: cc.vy = 0 110 | if event.key == K_DOWN and cc.vy == 10: cc.vy = 0 111 | 112 | clock.tick(60) 113 | screen.fill((255,255,255)) 114 | 115 | cc.update() 116 | 117 | for m in minions: 118 | m.render() 119 | 120 | cc.render() 121 | 122 | pygame.display.flip() 123 | 124 | ge = ['position update', playerid, cc.x, cc.y] 125 | s.send(pickle.dumps(ge)) 126 | s.close() 127 | -------------------------------------------------------------------------------- /images/BlueThing/BlueThing_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlainSight/pygameblog/656350020ced0fe5ce58dd7f23cb46ef86d29384/images/BlueThing/BlueThing_front.png -------------------------------------------------------------------------------- /images/Ladette/Ladette_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlainSight/pygameblog/656350020ced0fe5ce58dd7f23cb46ef86d29384/images/Ladette/Ladette_front.png -------------------------------------------------------------------------------- /images/TrashPanda/TrashPanda_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlainSight/pygameblog/656350020ced0fe5ce58dd7f23cb46ef86d29384/images/TrashPanda/TrashPanda_front.png -------------------------------------------------------------------------------- /images/Tubby/Tubby_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlainSight/pygameblog/656350020ced0fe5ce58dd7f23cb46ef86d29384/images/Tubby/Tubby_front.png -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import asyncore 3 | import random 4 | import pickle 5 | import time 6 | 7 | BUFFERSIZE = 512 8 | 9 | outgoing = [] 10 | 11 | class Minion: 12 | def __init__(self, ownerid): 13 | self.x = 50 14 | self.y = 50 15 | self.ownerid = ownerid 16 | 17 | minionmap = {} 18 | 19 | def updateWorld(message): 20 | arr = pickle.loads(message) 21 | print(str(arr)) 22 | playerid = arr[1] 23 | x = arr[2] 24 | y = arr[3] 25 | 26 | if playerid == 0: return 27 | 28 | minionmap[playerid].x = x 29 | minionmap[playerid].y = y 30 | 31 | remove = [] 32 | 33 | for i in outgoing: 34 | update = ['player locations'] 35 | 36 | for key, value in minionmap.items(): 37 | update.append([value.ownerid, value.x, value.y]) 38 | 39 | try: 40 | i.send(pickle.dumps(update)) 41 | except Exception: 42 | remove.append(i) 43 | continue 44 | 45 | print ('sent update data') 46 | 47 | for r in remove: 48 | outgoing.remove(r) 49 | 50 | class MainServer(asyncore.dispatcher): 51 | def __init__(self, port): 52 | asyncore.dispatcher.__init__(self) 53 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 54 | self.bind(('', port)) 55 | self.listen(10) 56 | def handle_accept(self): 57 | conn, addr = self.accept() 58 | print ('Connection address:' + addr[0] + " " + str(addr[1])) 59 | outgoing.append(conn) 60 | playerid = random.randint(1000, 1000000) 61 | playerminion = Minion(playerid) 62 | minionmap[playerid] = playerminion 63 | conn.send(pickle.dumps(['id update', playerid])) 64 | SecondaryServer(conn) 65 | 66 | class SecondaryServer(asyncore.dispatcher_with_send): 67 | def handle_read(self): 68 | recievedData = self.recv(BUFFERSIZE) 69 | if recievedData: 70 | updateWorld(recievedData) 71 | else: self.close() 72 | 73 | MainServer(4321) 74 | asyncore.loop() --------------------------------------------------------------------------------