用python3從零開始開發(fā)一款燒腦射擊游戲#6

歲月不居,時節(jié)如流,轉(zhuǎn)眼已來到了這個系列的最后一篇
上回說到采用socket+多線程的網(wǎng)絡(luò)模型來實(shí)現(xiàn)服務(wù)器與客戶端的通信,今天我們就在這個基礎(chǔ)上完整的實(shí)現(xiàn)多人在線的游戲版本
我們會把之前的1~5節(jié)所講的內(nèi)容全都用上,見證一款可以玩的游戲的誕生

20190131_123401.gif

再回顧一下服務(wù)器與客戶端的邏輯分工:
服務(wù)器:
1.與客戶端建立網(wǎng)絡(luò)連接,并為該玩家創(chuàng)建一個robot(副線程)
2.接收客戶端陸續(xù)發(fā)過來的消息,將消息解析成玩家的具體操作(副線程)
3.在自己的游戲世界中模擬robot與子彈的變化(主線程)
4.將游戲世界中發(fā)生的事時刻同步給所有的客戶端(主線程)

客戶端:
1.與服務(wù)器建立連接(主線程)
2.捕捉玩家的鍵盤行為,描述成一組消息發(fā)送給服務(wù)器(主線程)
3.接收服務(wù)器發(fā)送的同步消息,修改自己存儲的游戲世界(副線程)
4.將游戲世界在屏幕上繪制出來(主線程)

我們可以與第4篇的“單機(jī)版”對比一下
單機(jī)版是相當(dāng)于是“客戶端”捕捉玩家鍵盤行為后自己模擬世界的變化
網(wǎng)絡(luò)版是客戶端將行為發(fā)給服務(wù)器,由服務(wù)器模擬,再由服務(wù)器告訴客戶端世界的變化

從上面的描述中可以看到客戶端與服務(wù)器之間有兩種消息交互:
1.客戶端到服務(wù)器:
玩家的移動指令、發(fā)射指令
2.服務(wù)器到客戶端:
所有玩家的位置信息、所有子彈的位置信息

為了區(qū)分不同的玩家,服務(wù)器在創(chuàng)建robot時會對應(yīng)生成一個id來標(biāo)志這個玩家,并把這個id回傳給客戶端
客戶端在之后的所有指令消息中會附帶上這個id

下面我們來看關(guān)鍵部分的代碼實(shí)現(xiàn)

服務(wù)器與客戶端建立網(wǎng)絡(luò)連接,并為該玩家創(chuàng)建一個robot

def accept_client():
    global total_robot_num,lock,g_need_syna
    while True:
        client, _ = g_socket_server.accept()# 阻塞,等待客戶端連接
        g_conn_pool.append(client)
        thread = Thread(target=message_handle, args=(client,))#創(chuàng)建線程接收后續(xù)指令消息
        thread.setDaemon(True)
        thread.start()
        lock.acquire()#線程鎖加鎖
        total_robot_num += 1
        robot = Robot(total_robot_num)#創(chuàng)建玩家控制的robot
        robot_list.append(robot)
        g_need_syna = 1#告訴主線程世界發(fā)生了變化,需要同步
        res_msg = "accept "+str(total_robot_num)
        client.send(bytes(res_msg,'utf-8'))#將robot的id發(fā)送給客戶端
        lock.release()#線程鎖釋放

我們在這個函數(shù)中加入了一個新的東西“lock”,這是一個線程鎖。
這是由于游戲世界(所有的robot,所有的子彈)只有一份,是公用的。而主線程和副線程都會對游戲世界做修改,為了避免幾個線程同時修改世界(可以類比幾個人都在搶一張火車票),在一個線程獲得了修改權(quán)限時,需要把游戲世界鎖起來,別的線程只有等它修改結(jié)束了才能繼續(xù)修改。

而客戶端收到這個“accep xxx"之后,就會把xxx作為自己的id記下來

def RecvMsg(client):
    global lock,robot_list,bullet_list,self_robot_id
    while True:
        data = client.recv(8192)
        data = data.decode()
        str_list = data.split('\n')
        if(len(str_list)):
            first_line = str_list[0]
            fl = first_line.split(' ')
            if(len(fl)):
                if(fl[0]=="accept"):#連接成功
                    self_robot_id = int(fl[1])

再看客戶端發(fā)送操作指令的消息:

key_press = pygame.key.get_pressed()
if(key_press[K_LEFT]):#按下方向鍵左
    msg = str(self_robot_id) + " 3"#與服務(wù)器約定3表示向左移動一次
    client.send(msg.encode("utf-8"))

以及服務(wù)器接收到指令后的處理:

def message_handle(client):
    global lock,g_need_syna,total_bullet,RobotSize,BulletSize
    while True:
        data = client.recv(8192)
        data = data.decode('utf-8')
        if len(data) == 0:
            client.close()
            g_conn_pool.remove(client)#刪除連接
        else:
            str_list = data.split(' ')
            if(len(str_list)>=2):
                robot_id = int(str_list[0])#id
                move_dir = int(str_list[1])#移動移動指令
                lock.acquire()
                for rbt in robot_list:
                    if rbt.id == robot_id:#找到對應(yīng)的機(jī)器人
                        rbt.Move(move_dir)#控制機(jī)器人移動
                        
                g_need_syna = 1
                lock.release()

最后是服務(wù)器對整個游戲世界的同步:

syna_msg = []
syna_msg.append("robots\n")
for rbt in robot_list:#所有玩家的位置
    syna_msg.append(str(rbt.id)+" ")
    syna_msg.append(str(rbt.x)+" ")
    syna_msg.append(str(rbt.y)+" ")
    syna_msg.append(str(rbt.z)+" ")
    syna_msg.append(str(rbt.dir)+"\n")
    
syna_msg.append("bullets\n")
for bul in bullet_list:#所有子彈的位置
    syna_msg.append(str(bul.id)+" ")
    syna_msg.append(str(bul.fa)+" ")
    syna_msg.append(str(bul.x)+" ")
    syna_msg.append(str(bul.y)+" ")
    syna_msg.append(str(bul.z)+"\n")
syna_str = ''.join(syna_msg)

for clt in g_conn_pool:#發(fā)送給所有客戶端
    clt.sendall(bytes(syna_str,'utf-8'))

以及客戶端收到同步的消息后需要對應(yīng)修改自己記錄的世界(代碼太長就不貼了)

完整的代碼可以從這里獲取(代碼細(xì)節(jié)還是挺多的,就不一一說明了。而且這只是我自己實(shí)現(xiàn)的方式,我對python也不是很熟,湊合著看吧)
服務(wù)器
客戶端

至此整個游戲的初版已經(jīng)完成。
后續(xù)可以對游戲進(jìn)行貼圖(美術(shù)換皮),魔改子彈和技能,以及玩家視野上做一些處理。目前已經(jīng)在0.1版本中加入了追蹤導(dǎo)彈的元素。
如果有時間的話會在GitHub上繼續(xù)更新。也歡迎鐵子們跟我一起交流嗷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容