想學習Python不知從哪開始,來看我和同事通過遊戲開發學習Python

燕大俠v發表於2018-04-20

學習python?說得通。它擁有大量穩定的機器學習和資料操作庫。在本文中,將您的Python研究帶到下一個層次。

我的同事和我決定一起做一個小遊戲來幫助我們學習語言。這篇文章介紹了我們最初的經驗,把一切都放在一起。

這個計劃

就像我學過的其他語言一樣,我通常喜歡開發一個應用程式,它涉及到一些功能,比如讀取檔案、網路、使用者輸入和視覺化。這迫使我熟悉了庫和語言的功能,這讓我加快了重新實現演算法和完成教程專案的速度。它還迫使我瞭解Python的環境,以安裝依賴關係和建立發行版。

我們查閱了一些與遊戲建立和網路相關的庫,並決定使用pygame,因為它似乎提供了一種功能,可以從開發中刪除許多單調乏味的內容。它看起來也像Python裡有一系列的網路庫,所以我們決定在使用它的時候把它弄清楚。

安裝Python

Python本身相對容易安裝。我們剛剛從網站上下載了自動安裝程式,並在一分鐘內將執行時準備好。

安裝Pygame

事實證明,安裝Pygame有點令人沮喪。在我們設法下載指令碼並以正確的方式安裝它之前,我們嘗試了好幾次。我們必須找到這個庫的正確版本(它與我們安裝的Python版本相匹配)在一個不容易找到的依賴項列表上,然後用Python包安裝實用程式pip3.exe來提取它。這看起來比實際要困難得多,特別是由於庫的不同版本的數量,以及如果我們安裝了不同版本的Python,我們需要做些什麼。

最終,我們建立了一些東西,並尋找了一個關於如何獲得遊戲基礎的教程。

在這裡還是要推薦下我自己建的Python開發學習群:483546416,群裡都是學Python開發的,如果你正在學習Python ,小編歡迎你加入,大家都是軟體開發黨,不定期分享乾貨(只有Python軟體開發相關的),包括我自己整理的一份2018最新的Python進階資料和高階開發教程,歡迎進階中和進想深入Python的小夥伴

Drawing a Sprite

在開始使用任何圖形的時候,首先要做的事情就是將某些東西(或任何東西)呈現在螢幕上。我們發現了一大堆關於這方面的複雜的教程,並基於他們的例子提出了一個基本的渲染迴圈:

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

whileTrue:

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

這段程式碼產生:

在那之後,我們將重點放在捕獲使用者輸入以移動字元。我們還為玩家角色建立了一個類來將其邏輯內部化:

classMinion:

def__init__(self, x, y):

self.x = x

self.y = y

self.vx = 0

self.vy = 0

defupdate(self):

self.x += self.vx

self.y += self.vy

#this keeps the player character within the bounds of the screen

ifself.x > WIDTH - 50:

self.x = WIDTH - 50

ifself.x < 0:

self.x = 0

ifself.y > HEIGHT - 50:

self.y = HEIGHT - 50

ifself.y < 0:

self.y = 0

defrender(self):

screen.blit(thing, (self.x, self.y))

使用者輸入在遊戲迴圈中被捕獲:

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

角色的位置被更新和渲染(同樣在遊戲中):

cc.update()

cc.render()

現在我們有了基本的字元移動工作,我們想開始構建一些簡單的多人遊戲功能。

我們決定採用一個非常簡單的資料傳輸模型:

· 客戶端連線到伺服器,然後不斷廣播自己的字元的位置

伺服器將所有字元的位置廣播給所有的客戶

我們決定使用TCP套接字,因為它們處理諸如連線和斷開連線比UDP更容易。另外,這並不是一個效能關鍵的應用程式。

我們成功地找到了一篇關於在Python中使用Python編寫非同步伺服器的好文章。

基本的伺服器程式碼是這樣開始的:

import socket

import asyncore

import random

import pickle

import time

BUFFERSIZE = 512

outgoing = []

#additional logic here...

classMainServer(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)

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

classSecondaryServer(asyncore.dispatcher_with_send):

defhandle_read(self):

recievedData = self.recv(BUFFERSIZE)

if recievedData:

updateWorld(recievedData)

else: self.close()

MainServer(4321)

asyncore.loop()

這定義了一個負責接受新的TCP連線的主伺服器,然後它建立一個二級伺服器。輔助伺服器處理來自每個客戶機的所有傳入資料。接收到傳入的資料包時,將資料傳遞給updateWorld。這是定義如下:

classMinion:

def__init__(self, ownerid):

self.x = 50

self.y = 50

self.ownerid = ownerid

minionmap = {}

defupdateWorld(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只是負責更新包含每個玩家角色位置的字典。然後,它通過將它們的位置序列化為陣列的陣列,向每個播放器廣播這些位置。

現在客戶端已經構建好了,我們可以實現客戶端傳送和接收更新的邏輯。當遊戲開始時,我們新增了一些邏輯來啟動一個簡單的套接字並連線到一個伺服器地址。這可選地獲取命令列指定的IP地址,但其他方式連線到本地主機:

serverAddr = '127.0.0.1'

iflen(sys.argv) == 2:

serverAddr = sys.argv[1]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((serverAddr, 4321))

然後,我們在遊戲迴圈的開始部分新增了一些邏輯,以便從套接字讀取。我們使用“select”包只在有資料的情況下從套接字讀取傳入的包。如果我們使用了插座。如果套接字沒有讀取的包,則遊戲程式將停止。使用“select”允許gameloop繼續執行,即使沒有什麼可讀的:

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

上面的程式碼處理了伺服器可能生成的兩個序列化的有效負載。

1. 包含玩家伺服器分配識別符號的初始包。

客戶端使用此方法在所有位置更新中標識自己到伺服器。它還用於忽略伺服器廣播的自己的播放器資料,因此沒有一個帶有陰影版本的玩家角色。

2. 球員的位置有效載荷

這包含一組包含玩家識別符號和字元位置的陣列。當檢索到現有的Minion物件時,將為每個傳輸的物件建立新的Minion物件。

其他的小黃人則在遊戲迴圈中呈現:

for m in minions:

m.render()

我們要做的最後一件事是向客戶端新增一些程式碼,告訴伺服器玩家的位置。這是通過在gameloop的結尾新增一個廣播來序列化當前播放器的位置,然後使用“pickle”,然後將這個bytestream傳送到伺服器:

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

s.send(pickle.dumps(ge))

一旦這是完整的玩家連線到同一個伺服器可以看到其他玩家移動。

一些額外的更新,例如顯示基於playerid的不同的化身被實現。

當完成時,當前的迭代有兩個參與者是這樣的:

 

相關文章