46 lines
2.3 KiB
GDScript
46 lines
2.3 KiB
GDScript
extends Node3D
|
||
## 世界根节点。
|
||
##
|
||
## 只做一件事:把玩家场景注册给 MultiplayerSpawner,并把 spawner 托管给 Net 单例。
|
||
## 玩家的生成/回收时机完全由 Net 决定,本文件不主动调用 spawn。
|
||
@export
|
||
var player_scene: PackedScene
|
||
|
||
const PLAYER_GROUP: String = "net_player"
|
||
## 用节点路径而不是全局标识符访问单例:
|
||
## autoload 的全局名只在引擎启动时注册,这样写不依赖注册时机。
|
||
const NET_PATH: String = "/root/Net"
|
||
|
||
@onready var spawner: MultiplayerSpawner = $MultiplayerSpawner
|
||
@onready var player_spawn_pos: Node3D = $PlayerSpawnPos
|
||
|
||
|
||
func _ready() -> void:
|
||
spawner.spawn_function = _create_player
|
||
var net := get_node_or_null(NET_PATH)
|
||
if net != null:
|
||
net.bind_spawner(spawner)
|
||
else:
|
||
push_error("Net 单例未注册,联机不可用。请确认 project.godot 的 [autoload] 里有 Net")
|
||
|
||
|
||
## MultiplayerSpawner 会在**每一端**都调用这个函数,因此 authority 必须在这里设置:
|
||
## 这样「谁是本地玩家」在各端结论一致,玩家脚本里的 is_multiplayer_authority() 才可靠。
|
||
## recursive=true 很关键 —— 节点 authority 不自动继承,Sync 子节点必须一起设。
|
||
func _create_player(peer_id: int) -> Node:
|
||
var player := player_scene.instantiate()
|
||
# ⚠ 这里只能写**局部** position,绝对不能写 global_position:
|
||
# spawn_function 返回的节点还没入树(spawner 之后才 add_child),而 Node3D 的
|
||
# global_transform 在未入树时是无效的 —— get_global_transform() 会 ERR_FAIL 并回退成
|
||
# 单位矩阵,于是 set_global_transform() 把 Player 根节点的基连同**缩放一起换成单位阵**:
|
||
# player.tscn 根节点那层 0.096096(老鼠 0.15 m 的关键)被静默抹掉,
|
||
# 实测后果 —— 老鼠从 0.15 m 涨到 1.56 m,第一人称相机从脚底 0.1116 m 抬到 1.161 m。
|
||
# Main 与 PlayerSpawnPos 同级、且 Main 本身无变换,所以局部坐标就等于世界坐标。
|
||
player.position = player_spawn_pos.position
|
||
player.name = str(peer_id)
|
||
player.add_to_group(PLAYER_GROUP)
|
||
player.set_multiplayer_authority(peer_id, true)
|
||
var here := multiplayer.get_unique_id()
|
||
print("[Net] 本端 %d 生成玩家 %d%s" % [here, peer_id, "(本地操控)" if peer_id == here else "(远端回放)"])
|
||
return player
|