昨天我們探究了多人游戲中的peer_connected信號觸發(fā)規(guī)則, 今天繼續(xù)開發(fā), 包括場景的生成, 每個peer的Player同步生成
場景的切換
先預(yù)加載main場景
const MAIN : PackedScene = preload("uid://yubvfldj7w73")
新版本的Godot更推薦使用uid來表述資源, 按住Ctrl將場景拖入腳本中, 將自動生成上述語句
在ready中我們將對MultiplayerAPI的peer_connected事件監(jiān)聽修改為connected_to_server事件監(jiān)聽, 這個事件僅在Client成功連接到服務(wù)器后觸發(fā), 僅在客戶端peer上觸發(fā)
multiplayer.connected_to_server.connect(_on_connected_to_server)
當(dāng)客戶端成功連接服務(wù)器后, 加載main場景
func _on_connected_to_server() -> void:
get_tree().change_scene_to_packed(MAIN)
我們的服務(wù)器不是單純服務(wù)器, 它是Host服務(wù)器, 也就是它既充當(dāng)一個玩家的peer也充當(dāng)服務(wù)器, 它也需要加載main場景, 服務(wù)器不用等待, 在創(chuàng)建服務(wù)成功后就可以加載
func _on_host_pressed() -> void:
var server_peer := ENetMultiplayerPeer.new()
server_peer.create_server(PORT)
multiplayer.multiplayer_peer = server_peer
get_tree().change_scene_to_packed(MAIN)
各peer的Player同步
當(dāng)peer連接到服務(wù)器后, 所有peer都已經(jīng)加載了main場景, 此時需要在所有peer上創(chuàng)建對應(yīng)的Player, 在多人游戲中我們使用MultiplayerSpawner在所有peer上同步生成節(jié)點
刪除main.tscn場景中的Player子節(jié)點, 添加一個MultiplayerSpawner子節(jié)點, 將其SpawnPath設(shè)置為Main節(jié)點

當(dāng)一個Client加入服務(wù)器后, 在同步生成Player節(jié)點之前, 需要確保Client的場景加載完畢, 因此我們在ready之后才調(diào)用_create_player, 在服務(wù)器上調(diào)用MultiplayerSpawner進行Player的同步生成
extends Node2D
const PLAYER = preload("uid://dgstmloeo60yy")
@onready var multiplayer_spawner: MultiplayerSpawner = $MultiplayerSpawner
func _ready() -> void:
multiplayer_spawner.spawn_function = func(data: Dictionary):
var player = PLAYER.instantiate()
player.name = "Player%s" % [data.player_id]
return player
_create_player.rpc_id(1)
@rpc("any_peer", "call_local", "reliable")
func _create_player() -> void:
var id := multiplayer.get_remote_sender_id()
multiplayer_spawner.spawn({ "player_id" : id })
這里有幾個點需要注意:
-
MultiplayerAPI的
spawn_function指定之后, 當(dāng)authority(節(jié)點的擁有者, 這里就是服務(wù)端)調(diào)用MultiplayerAPI的spawn函數(shù)之后,spawn_function會在所有的peer上對等調(diào)用,spawn函數(shù)的傳入?yún)?shù)會被當(dāng)做spawn_function調(diào)用時的傳入?yún)?shù)spawn****僅在authority上調(diào)用一次,spawn_function****會自動在所有peer上調(diào)用所以我們用
_create_player.rpc_id(1)這種RPC遠程調(diào)用的方式, 讓創(chuàng)建player的邏輯僅在服務(wù)端(authority)運行在Godot編輯器中, 按住Ctrl點擊對應(yīng)的成員, 會直接跳轉(zhuǎn)到詳細的文檔, 可以很方便查看比如
multiplayer,spawn等成員的具體說明 Godot節(jié)點上的rpc調(diào)用要求所有peer上對應(yīng)節(jié)點的路徑必須相同, 因此我們在創(chuàng)建player對象時, 都顯式指定了player節(jié)點的名稱
@rpc的三個參數(shù)含義需要弄明白, 特別是call_local, 因為host服務(wù)器自己ready之后也需要調(diào)用_create_player創(chuàng)建自身的player節(jié)點-
spawn_function需要返回一個沒有加入任何場景樹的節(jié)點, 需要小心Method called on all peers when a custom spawn() is requested by the authority. Will receive the data parameter, and should return a Node that is not in the scene tree.
Note: The returned node should not be added to the scene with Node.add_child(). This is done automatically.
客戶端加入后的問題
為方便調(diào)試, 我們調(diào)整Debug中的多實例數(shù)量為2
運行起來之后, 僅啟動服務(wù)器時, 一切正常, 玩家正常創(chuàng)建, 可以正常移動
但是客戶端加入后, 發(fā)現(xiàn)玩家對象"閃現(xiàn)"一下之后消失了
并且如果服務(wù)器啟動時不移動玩家, 客戶端加入后, 兩個窗口的玩家都會閃現(xiàn)一下消失

通過Remote調(diào)試, 查看場景樹, Player節(jié)點都還在, 但是位置都跑了很遠
原來是碰撞生效了, 在第二個玩家生成的那一刻, 兩個玩家碰撞盒疊在一起, 被一下子排斥開來, 飛出屏幕了, 設(shè)置一下碰撞層級就可以正常運行啦
緊接著又發(fā)現(xiàn)一個問題! 好像只有一個玩家, 但仔細查看Remote場景樹, 發(fā)現(xiàn)其實是兩個玩家疊一起了, 按下移動鍵時, 兩者一起移動了, 看起來好像只有一個, 我們下次來解決該問題