Godot游戲練習(xí)01-第4節(jié)-多人控制與玩家位置同步(翻車)

昨天我們使用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)選中

image.png

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變量

image.png

在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ù)器管理

image.png

腳本中的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作為同步屬性

image.png

看看效果

我們暫時(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è)置

image.png

這是教程之外的問(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ā)生了變化

image.png

排查之后發(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!

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

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

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