Files
agame/autoload/net.gd
T
2026-09-22 17:45:29 +08:00

195 lines
5.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends CanvasLayer
## 联机总入口(Autoload 名:Net,指向 res://autoload/net.tscn)
##
## 职责边界:只负责「选传输层 + 开局/加入/断开 + 玩家生成回收」。
## 玩法代码不直接接触 MultiplayerPeer —— 以后要把 ENet 换成 Steam P2P,
## 只需要改本文件的 host_game / join_game 两个函数,RPC 与 Spawner 层不用动。
##
## 界面是**节点配置**:整个 UI 在 res://autoload/net.tscn 里用节点搭好,
## 本脚本只做「改文字、切显隐」这类状态更新,不动态创建任何 Control。
## 按钮的 pressed 信号也在 net.tscn 里连接(打开场景 → 选按钮 → 节点面板 → 信号)。
##
## 本机双开调试(命令行):
## godot --path <项目目录> -- --host
## godot --path <项目目录> -- --join 127.0.0.1
const PORT: int = 7000
const MAX_PLAYERS: int = 8
const DEFAULT_ADDRESS: String = "127.0.0.1"
## 所有玩家节点都会加入这个组,方便整批清场
const PLAYER_GROUP: String = "net_player"
var _spawner: MultiplayerSpawner = null
@onready var _menu: CenterContainer = $Menu
@onready var _address_edit: LineEdit = $Menu/Panel/Box/AddressEdit
@onready var _menu_status: Label = $Menu/Panel/Box/StatusLabel
@onready var _hud: Control = $Hud
@onready var _hud_status: Label = $Hud/Box/StatusLabel
func _ready() -> void:
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.connection_failed.connect(_on_connection_failed)
multiplayer.server_disconnected.connect(_on_server_disconnected)
_show_menu_ui()
call_deferred("_apply_cmdline")
# ---------------- 对外接口 ----------------
## 世界根节点在 _ready 里调用,把 MultiplayerSpawner 托管给 Net。
func bind_spawner(spawner: MultiplayerSpawner) -> void:
_spawner = spawner
## 创建主机。ENet 是客户端-服务器模型,主机同时是服务器与玩家。
func host_game() -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(PORT, MAX_PLAYERS)
if err != OK:
_set_status("创建主机失败:%s" % error_string(err))
return
multiplayer.multiplayer_peer = peer
_show_game_ui()
print("[Net] 主机已启动,端口 %d" % PORT)
# 服务器自己也要一个角色。延后一帧,等各端场景就绪。
call_deferred("_spawn_player", 1)
## 加入主机。address 为主机在局域网内的内网 IP。
func join_game(address: String = DEFAULT_ADDRESS) -> void:
var target := address.strip_edges()
if target.is_empty():
target = DEFAULT_ADDRESS
var peer := ENetMultiplayerPeer.new()
var err := peer.create_client(target, PORT)
if err != OK:
_set_status("连接失败:%s" % error_string(err))
return
multiplayer.multiplayer_peer = peer
_set_status("正在连接 %s:%d …" % [target, PORT])
print("[Net] 正在连接 %s:%d" % [target, PORT])
## 断开并清场,回到主菜单。
func leave_game() -> void:
multiplayer.multiplayer_peer = null
_clear_players()
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_show_menu_ui()
_set_status("已断开")
# ---------------- 网络事件 ----------------
func _on_peer_connected(id: int) -> void:
# 只有服务器生成角色,客户端靠 MultiplayerSpawner 自动收到同一份。
if multiplayer.is_server():
call_deferred("_spawn_player", id)
func _on_peer_disconnected(id: int) -> void:
if multiplayer.is_server():
_despawn_player(id)
func _on_connected_to_server() -> void:
_show_game_ui()
print("[Net] 已连接,等待主机生成角色")
func _on_connection_failed() -> void:
multiplayer.multiplayer_peer = null
_set_status("连接失败:主机无响应")
func _on_server_disconnected() -> void:
multiplayer.multiplayer_peer = null
_clear_players()
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_show_menu_ui()
_set_status("主机已关闭")
# ---------------- 玩家生成与回收 ----------------
func _spawn_player(peer_id: int) -> void:
if _spawner == null or not multiplayer.is_server():
return
var parent := _spawner.get_node_or_null(_spawner.spawn_path)
if parent == null or parent.has_node(str(peer_id)):
return
_spawner.spawn(peer_id)
func _despawn_player(peer_id: int) -> void:
if _spawner == null:
return
var parent := _spawner.get_node_or_null(_spawner.spawn_path)
if parent == null:
return
var node := parent.get_node_or_null(str(peer_id))
if node != null:
node.queue_free()
func _clear_players() -> void:
for node in get_tree().get_nodes_in_group(PLAYER_GROUP):
node.queue_free()
# ---------------- 命令行调试入口 ----------------
func _apply_cmdline() -> void:
var args := OS.get_cmdline_user_args()
if args.has("--host"):
host_game()
elif args.has("--join"):
var index := args.find("--join")
var address := DEFAULT_ADDRESS
if index + 1 < args.size():
address = args[index + 1]
join_game(address)
# ---------------- 界面(节点都在 net.tscn 里,这里只管状态) ----------------
## 两个按钮的 pressed 信号在 net.tscn 里连到这里
func _on_host_button_pressed() -> void:
host_game()
func _on_join_button_pressed() -> void:
join_game(_address_edit.text)
func _unhandled_key_input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and not event.echo \
and event.keycode == KEY_F10 and multiplayer.multiplayer_peer != null:
leave_game()
get_viewport().set_input_as_handled()
func _show_game_ui() -> void:
_menu.visible = false
_hud.visible = true
_update_hud()
func _show_menu_ui() -> void:
_menu.visible = true
_hud.visible = false
func _set_status(text: String) -> void:
_menu_status.text = text
func _update_hud() -> void:
var role := "主机" if multiplayer.is_server() else "客户端"
var players := multiplayer.get_peers().size() + 1
_hud_status.text = "联机中 · %s · 玩家 %d" % [role, players]