昨天我們使用MultiplayerSpawner實(shí)現(xiàn)了所有peer上的玩家生成, 但是最后碰到兩個(gè)問(wèn)題, 今天來(lái)解決昨天的問(wèn)題, 并實(shí)現(xiàn)玩家的分開控制與位置同步
玩家生成在一起后彈開的問(wèn)題
昨天其實(shí)已經(jīng)指出原因了 -- Player場(chǎng)景上的碰撞體發(fā)生了碰撞
我們?cè)谇捌谙炔惶幚砼鲎? 暫時(shí)關(guān)閉碰撞, 具體的操作方式如下
在Player場(chǎng)景中的CharacterBody2D節(jié)點(diǎn)中的Collision部分, 取消Mask層的默認(rèn)選中

Layer 的含義是指碰撞體在哪些層, 可以觸發(fā)其他檢測(cè)對(duì)應(yīng)層的碰撞體的碰撞事件
Mask 的含義是當(dāng)前碰撞體檢測(cè)哪些層的碰撞, 當(dāng)其他碰撞體的Layer在當(dāng)前碰撞體的Mask中, 那么其他碰撞體就可以觸發(fā)當(dāng)前碰撞體的碰撞事件
我們?nèi)サ袅薓ask層的默認(rèn)值1, 也就是Player的碰撞體不再檢測(cè)任何層的碰撞事件, 但是它本身還在第1層(Layer), 如果有其他CollisionObject檢測(cè)1層的碰撞, 則可以被Player觸發(fā)
玩家控制
昨天的第二個(gè)問(wèn)題是, 在任意一個(gè)窗口中控制玩家移動(dòng)時(shí), 會(huì)同時(shí)移動(dòng)所有的玩家, 控制沒(méi)有區(qū)分
讓我們新建一個(gè)PlayerInputComponent場(chǎng)景類, 只有一個(gè)MultiplayerSychronizer根節(jié)點(diǎn), 用于同步authority對(duì)等端(peer)的輸入到其他所有對(duì)等端
掛載腳本內(nèi)容如下
class_name PlayerInputMultiplayerSynchronizerComponent
extends MultiplayerSynchronizer
var move_vector : Vector2 = Vector2.ZERO
func _process(_delta: float) -> void:
if is_multiplayer_authority():
move_vector = Input.get_vector("move_left", "move_right", "move_up", "move_down")
配置root_path屬性為根節(jié)點(diǎn)(同步屬性的相對(duì)參考節(jié)點(diǎn)), 在需要同步的屬性(replication)中添加腳本中的move_vector變量

在Player場(chǎng)景下添加該Component節(jié)點(diǎn)(場(chǎng)景實(shí)例化), 獲取引用, 在ready回調(diào)中設(shè)置組件節(jié)點(diǎn)的authority, 在process中使用輸入組件提供的move_vector
注意: 這里僅設(shè)置了Player場(chǎng)景中的輸入組件(PlayerInputMultiplayerSychronizer)的authority, Player本身未設(shè)置, 未設(shè)置的節(jié)點(diǎn)默認(rèn)authority都是1, 也就是默認(rèn)節(jié)點(diǎn)都由服務(wù)器管理

腳本中的input_peer_id由外部輸入, 我們?cè)趍ain.gd中生成Player時(shí)處理 player.input_peer_id = data.peer_id
func _ready() -> void:
multiplayer_spawner.spawn_function = func(data: Dictionary):
var player = PLAYER.instantiate()
player.name = "Player%s" % [data.peer_id]
player.input_peer_id = data.peer_id
return player
_create_player.rpc_id(1)
@rpc("any_peer", "call_local", "reliable")
func _create_player() -> void:
var sender_id := multiplayer.get_remote_sender_id()
multiplayer_spawner.spawn({ "peer_id" : sender_id })
注意: 對(duì)節(jié)點(diǎn)設(shè)置調(diào)用set_multiplayer_authority時(shí), 對(duì)應(yīng)的節(jié)點(diǎn)authority信息不會(huì)自動(dòng)同步到其余peer上, 想要所有peer保持相同的authority設(shè)置, 必須開發(fā)者自己保證, 這里我們?cè)?code>spawn_function中設(shè)置, 而spawn_function會(huì)在所有peer上自動(dòng)調(diào)用, 保證了同步
以下為set_multiplayer_authority函數(shù)的官方文檔說(shuō)明
Warning: This does not automatically replicate the new authority to other peers. It is the developer's responsibility to do so. You may replicate the new authority's information using MultiplayerSpawner.spawn_function, an RPC, or a MultiplayerSynchronizer. Furthermore, the parent's authority does not propagate to newly added children.
玩家位置同步
這里我們只做使用MultiplayerSychronizer做簡(jiǎn)單的位置同步, 暫時(shí)不考慮插值/預(yù)測(cè)等概念
在Player場(chǎng)景中再新增一個(gè)MultiplayerSychronizer節(jié)點(diǎn), 添加global_position作為同步屬性

看看效果
我們暫時(shí)在Debug中開啟3個(gè)實(shí)例, 看看同步效果
不開不知道, 一開嚇一跳, 當(dāng)場(chǎng)翻車了!
調(diào)試時(shí)又碰到一個(gè)問(wèn)題, 在Godot4.6.1版本中, 提示不應(yīng)該在ready回調(diào)中改變節(jié)點(diǎn)的authority值, 應(yīng)該在enter_tree回調(diào)中設(shè)置

這是教程之外的問(wèn)題, 這里有些糾結(jié)的點(diǎn), 在enter_tree回調(diào)中, 我們的@onready還未生效, 也就是還拿不到輸入組件的引用, 我們?cè)囋噀nter_tree中動(dòng)態(tài)創(chuàng)建輸入組件
var player_input_multiplayer_synchronizer_component: PlayerInputMultiplayerSynchronizerComponent
func _enter_tree() -> void:
player_input_multiplayer_synchronizer_component = PlayerInputMultiplayerSynchronizerComponent.new()
player_input_multiplayer_synchronizer_component.set_multiplayer_authority(input_peer_id)
player_input_multiplayer_synchronizer_component.name = "PlayerInputMultiplayerSynchronizer"
add_child(player_input_multiplayer_synchronizer_component)
但是緊接著, 我又發(fā)現(xiàn)一個(gè)問(wèn)題...
當(dāng)客戶端peer加入服務(wù)器后, 報(bào)錯(cuò)數(shù)字狂飆, 一分鐘就飆到10000+了, 壓迫力拉滿
我又確認(rèn)了一遍, 按照教程實(shí)現(xiàn)的沒(méi)錯(cuò), 但是效果不一樣, 可能Godot版本升級(jí)后, 有一些特性發(fā)生了變化

排查之后發(fā)現(xiàn), 當(dāng)客戶端peer加入服務(wù)器后, 服務(wù)器之前生成的服務(wù)器的Player未被同步生成到客戶端peer中
我們?cè)趕pawn_function中添加打印, 并調(diào)試
[peer 1] Spawn player: 1
[peer 1] Spawn player: 1892877996
[peer 1892877996] Spawn player: 1892877996
--- Debugging process stopped ---
第一行: 第一個(gè)實(shí)例點(diǎn)擊host, 創(chuàng)建服務(wù)器, 并創(chuàng)建了自己的Player實(shí)例(peer_id為1)
第二行: 客戶端peer加入服務(wù)器, 在服務(wù)端創(chuàng)建了屬于客戶端peer的Player實(shí)例(peer_id為1892877996)
第三行: 客戶端peer加入服務(wù)器后, 在客戶端創(chuàng)建了屬于客戶端peer的Player實(shí)例(peer_id為1892877996)
然后就沒(méi)了! 客戶端只有一個(gè)Player實(shí)例, 缺少屬于服務(wù)端的Player實(shí)例!
真是翻車的一天, 但是今天太晚了, 幸好我們找出了問(wèn)題, 明天來(lái)解決這個(gè)bug!