python遊戲開發實戰:網路遊戲Demo(客戶端)
一.執行效果
二.介紹
原始碼github:https://github.com/zxf20180725/pygame-online-demo.git
這只是一個簡單的聯網程式Demo,程式碼有很多不嚴謹的地方,僅當拋磚引玉了。
執行客戶端程式,會隨機取一個名字進入遊戲。使用wsad移動頭像(藍葵~)。
三.程式碼解析
注意,這裡只會貼出部分核心程式碼,完整程式碼請在上面的github連結中下載。
全域性部分的程式碼,這些都有註釋了,具體作用,後面會講到。foxyball.cn是我的伺服器域名,這一年內應該都是有效的。
import random
import sys
import time
from random import randint
from threading import Thread
import pygame
import socket # 匯入 socket 模組
from base import Protocol
ADDRESS = ('127.0.0.1', 8712) # ('foxyball.cn', 8712) # 如果服務端在本機,請使用('127.0.0.1', 8712)
WIDTH, HEIGHT = 640, 480 # 視窗大小
g_font = None
g_screen = None # 視窗的surface
g_sur_role = None # 人物的role
g_player = None # 玩家操作的角色
g_other_player = [] # 其他玩家
g_client = socket.socket() # 建立 socket 物件
看一個程式的程式碼,應該從它的入口開始看。
if __name__ == '__main__':
# 初始化
init_game()
# 遊戲迴圈
main_loop()
入口很簡單,就呼叫了兩個函式,那麼我們先看看init_game()做了什麼。
def init_game():
"""
初始化遊戲
"""
global g_screen, g_sur_role, g_player, g_font
# 初始化pygame
pygame.init()
pygame.display.set_caption('網路遊戲Demo')
g_screen = pygame.display.set_mode([WIDTH, HEIGHT])
g_sur_role = pygame.image.load("./role.png").convert_alpha() # 人物圖片
g_font = pygame.font.SysFont("fangsong", 24)
# 初始化隨機種子
random.seed(int(time.time()))
# 建立角色
# 隨機生成一個名字
last_name = ['趙', '錢', '孫', '李', '周', '吳', '鄭', '王', '馮', '陳', '褚', '衛',
'蔣', '沈', '韓', '楊', '朱', '秦', '尤', '許', '何', '呂', '施', '張',
'孔', '曹', '嚴', '華', '金', '魏', '陶', '姜', '戚', '謝', '鄒', '喻', ]
first_name = ['夢琪', '憶柳', '之桃', '慕青', '問蘭', '爾嵐', '元香', '初夏', '沛菡',
'傲珊', '曼文', '樂菱', '痴珊', '孤風', '雅彤', '宛筠', '飛鬆', '初瑤',
'夜雲', '樂珍']
name = random.choice(last_name) + random.choice(first_name)
print("你的暱稱是:", name)
g_player = Role(randint(100, 500), randint(100, 300), name)
# 與伺服器建立連線
g_client.connect(ADDRESS)
# 開始接受服務端訊息
thead = Thread(target=msg_handler)
thead.setDaemon(True)
thead.start()
# 告訴服務端有新玩家
send_new_role()
從與伺服器建立連線開始講吧(28行)。如果對python的socket不太熟悉的話,可以先看看這兩篇文章:https://blog.csdn.net/qq_39687901/article/details/81531101,https://blog.csdn.net/qq_39687901/article/details/81536641,g_client是一個socket物件,與指定的服務端建立連線。
接收服務端訊息部分,我新開了一個執行緒進行處理(因為recv是阻塞執行緒的)。處理服務端訊息的函式是msg_handler,這個函式稍後再講,我們繼續往下看send_new_role函式。
def send_new_role():
"""
告訴服務端有新玩家加入
"""
# 構建資料包
p = Protocol()
p.add_str("newrole")
p.add_int32(g_player.x)
p.add_int32(g_player.y)
p.add_str(g_player.name)
data = p.get_pck_has_head()
# 傳送資料包
g_client.sendall(data)
Protocol是我們自定義的遊戲資料包協議,關於Protocol的設計思路都在這篇文章:https://blog.csdn.net/qq_39687901/article/details/81541967
這裡,我們構造了一個名字叫“newrole”的資料包,並且加入了玩家的資訊(座標和暱稱),最後把這個資料包傳送給服務端。這個“newrole”的作用就是告訴服務端有一個新玩家加入遊戲啦,然後服務端又會告訴其他玩家有個新玩家加入了(這就實現了可以在視窗裡看到其他玩家的功能)。我會在下一篇文章詳細的講解服務端的設計,這裡就不多說了。
回到我們的程式入口來,接下來就該執行main_loop啦。
def main_loop():
"""
遊戲主迴圈
"""
while True:
# FPS=60
pygame.time.delay(32)
# 邏輯更新
update_logic()
# 檢視更新
update_view()
每個遊戲都必不可少的遊戲主迴圈。迴圈裡很簡單,就呼叫了3個函式。pygame.time.delay(32)讓每次迴圈間隔32毫秒,也就是說每秒迴圈執行60次左右。然後就是邏輯更新和檢視更新了,這兩個函式請儘可能的解耦。
那我們繼續看邏輯更新,這個demo的遊戲邏輯很簡單,就是用wasd控制角色移動。
def update_logic():
"""
邏輯更新
"""
# 事件處理
handler_event()
def handler_event():
# 事件處理
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
g_player.y -= 5
elif event.key == pygame.K_s:
g_player.y += 5
elif event.key == pygame.K_a:
g_player.x -= 5
elif event.key == pygame.K_d:
g_player.x += 5
send_role_move() # 告訴伺服器,自己移動了
角色每次移動之後,要把最新的座標告訴給服務端,服務端再把這個角色的最新座標傳送給其他客戶端,這樣其他客戶端就能看到你在移動了。send_role_move函式就是把當前座標傳送給服務端。
def send_role_move():
"""
傳送角色的座標給服務端
"""
# 構建資料包
p = Protocol()
p.add_str("move")
p.add_int32(g_player.x)
p.add_int32(g_player.y)
data = p.get_pck_has_head()
# 傳送資料包
g_client.sendall(data)
回到我們的遊戲主迴圈,繼續看檢視更新。
def update_view():
"""
檢視更新
"""
g_screen.fill((0, 0, 0))
# 畫角色
g_screen.blit(g_player.sur_name, (g_player.x, g_player.y - 20))
g_screen.blit(g_sur_role, (g_player.x, g_player.y))
# 畫其他角色
for r in g_other_player:
g_screen.blit(r.sur_name, (r.x, r.y - 20))
g_screen.blit(g_sur_role, (r.x, r.y))
# 重新整理
pygame.display.flip()
其中,g_other_player是一個存著其他線上玩家的列表。那麼這個列表中的內容是從哪裡來的呢?內容當然是從服務端發過來的。還記得本文最開始提到的新開一個執行緒處理服務端訊息嗎?就是那個msg_handler函式,現在來研究研究它。
def msg_handler():
"""
處理服務端返回的訊息
"""
while True:
bytes = g_client.recv(1024)
# 以包長度切割封包
while True:
# 讀取包長度
length_pck = int.from_bytes(bytes[:4], byteorder='little')
# 擷取封包
pck = bytes[4:4 + length_pck]
# 刪除已經讀取的位元組
bytes = bytes[4 + length_pck:]
# 把封包交給處理函式
pck_handler(pck)
# 如果bytes沒資料了,就跳出迴圈
if len(bytes) == 0:
break
外層的while迴圈是用來接收服務端的訊息,內層的while迴圈是用來切割資料包的(tcp粘包分包瞭解一下)。但這裡還有個問題,是我在程式碼裡沒有去處理的。那就是tcp分包問題,這裡內層while迴圈只解決了粘包。分包的問題,在以後的文章中會講。內層while迴圈的邏輯為什麼要這麼寫,大家還是去看看我之前發的那篇文章吧。https://blog.csdn.net/qq_39687901/article/details/81541967
資料包切割好了之後,就呼叫pck_handler函式處理資料包。
def pck_handler(pck):
p = Protocol(pck)
pck_type = p.get_str()
if pck_type == 'playermove': # 玩家移動的資料包
x = p.get_int32()
y = p.get_int32()
name = p.get_str()
for r in g_other_player:
if r.name == name:
r.x = x
r.y = y
break
elif pck_type == 'newplayer': # 新玩家資料包
x = p.get_int32()
y = p.get_int32()
name = p.get_str()
r = Role(x, y, name)
g_other_player.append(r)
elif pck_type == 'logout': # 玩家掉線
name = p.get_str()
for r in g_other_player:
if r.name == name:
g_other_player.remove(r)
break
我們這個小demo一共就設計了三個協議型別,"playermove"、"newplayer"和"logout"。"playermove"是在其他玩家移動的時候,服務端給我們的,讓我們更新其他玩家的位置(這樣就能看到其他玩家的移動效果了)。剩下的兩個就不用多說了吧。
四.總結
網路流程:
登入流程:
1.客戶端登入,傳送"newrole"資料包給服務端
2.服務端收到"newrole"資料包,然後傳送"newplayer"資料包給其他客戶端
3.其他客戶端收到"newplayer",向g_other_player列表中新增一個玩家
移動流程:
1.客戶端移動,傳送"move"資料包給服務端
2.服務端收到"move"資料包,然後傳送"playermove"資料包給其他客戶端
3.其他客戶端收到"playermove",更新g_other_player的相關資料
下線流程:
1.服務端檢測到有客戶端掉線,傳送"logout"資料包給其他線上客戶端
2.其他客戶端收到"playermove",刪除g_other_player中掉線的玩家
相關文章
- Qt實現網路聊天室(客戶端,服務端)QT客戶端服務端
- 網頁SSH客戶端的實現網頁客戶端
- 實戰:Windows防火牆保護客戶端安全Windows防火牆客戶端
- 網路開發基礎客戶端001客戶端
- Python socket的客戶端Python客戶端
- java版gRPC實戰之四:客戶端流JavaRPC客戶端
- 《golang筆記》第四篇-網路客戶端Golang筆記客戶端
- 客戶端網路配置上的一點說明客戶端
- Solaris網路管理:DNS客戶端的設定(轉)DNS客戶端
- 網頁搜尋客戶端網頁客戶端
- 實戰Flash遊戲開發遊戲開發
- ③SpringCloud 實戰:使用 Ribbon 客戶端負載均衡SpringGCCloud客戶端負載
- python 實現 TCP、UDP 客戶端最簡流程PythonTCPUDP客戶端
- python實現douban.fm簡易客戶端Python客戶端
- python 編寫遊戲測試機器人客戶端 (一)Python遊戲機器人客戶端
- python 編寫遊戲測試機器人客戶端 (二)Python遊戲機器人客戶端
- python 編寫遊戲測試機器人客戶端 (三)Python遊戲機器人客戶端
- 客戶端骨架屏實現客戶端
- 用 Unity 做個遊戲(八) - 客戶端邏輯結構和網路同步機制Unity遊戲客戶端
- 如何建立一個Java遊戲客戶端Java遊戲客戶端
- Python網路爬蟲實戰專案大全 32個Python爬蟲專案demoPython爬蟲
- Spring Cloud實戰系列(二) - 客戶端呼叫Rest + RibbonSpringCloud客戶端REST
- python建立tcp服務端和客戶端PythonTCP服務端客戶端
- 網狐客戶端-win32客戶端Win32
- oracle取客戶端網路卡地址Oracle客戶端
- Python MQTT客戶端 paho-mqttPythonMQQT客戶端
- OpenWrt安裝CIFS客戶端掛載網路驅動器客戶端
- jQuery實現客戶端CheckAll功能jQuery客戶端
- 獲取客戶端真實IP客戶端
- Rest Post示例(java服務端、python客戶端)RESTJava服務端Python客戶端
- Spring Cloud實戰系列(三) - 宣告式客戶端呼叫FeignSpringCloud客戶端
- Python網路爬蟲實戰Python爬蟲
- dubbo客戶端客戶端
- Pulsar客戶端客戶端
- mqtt 客戶端MQQT客戶端
- python爬蟲實踐: 豆瓣小組命令列客戶端Python爬蟲命令列客戶端
- 客戶端爬取-答網友問客戶端
- 客戶端,服務端客戶端服務端