From 79bc956b71a44f6df1efbced260e4dce1c3662ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=A8=E8=90=BD=E5=9F=BA=E5=9B=B4=E8=99=BE?= <3161880837@qq.com> Date: Sun, 9 Nov 2025 17:00:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=A4=9A=E4=BA=BA=E6=B8=B8=E6=88=8F):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=9F=BA=E7=A1=80=E5=A4=9A=E4=BA=BA=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加玩家位置同步功能 - 实现服务器和客户端连接管理 - 添加玩家名称输入和生成逻辑 - 完善多人游戏UI界面 - 移除单机模式下的预设玩家角色 --- .../Scenes/FullscreenPanels/Starter.tscn | 63 +++++++++++++++++-- components/Scenes/World.tscn | 15 ++--- scripts/Contents/Panels/Starter.gd | 28 +++++++++ scripts/Statemachine/EntityBase.gd | 24 ++++++- scripts/Tools/Managers/MultiplayerState.gd | 7 +-- 5 files changed, 117 insertions(+), 20 deletions(-) diff --git a/components/Scenes/FullscreenPanels/Starter.tscn b/components/Scenes/FullscreenPanels/Starter.tscn index e7a0682..cfae34b 100644 --- a/components/Scenes/FullscreenPanels/Starter.tscn +++ b/components/Scenes/FullscreenPanels/Starter.tscn @@ -65,13 +65,36 @@ unique_name_in_owner = true layout_mode = 2 text = "0 ∈ [0, 0]" -[node name="startBtn" type="Button" parent="content/wrapper/starter/singleplayer" index="1"] +[node name="start" type="HBoxContainer" parent="content/wrapper/starter/singleplayer" index="1"] +layout_mode = 2 +alignment = 1 + +[node name="playerNameInput" type="LineEdit" parent="content/wrapper/starter/singleplayer/start" index="0"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 4 +theme = ExtResource("4_lfxcn") +text = "公鸡" +placeholder_text = "角色名" +expand_to_text_length = true +virtual_keyboard_type = 7 +select_all_on_focus = true + +[node name="startBtn" type="Button" parent="content/wrapper/starter/singleplayer/start" index="1"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 4 theme = ExtResource("4_lfxcn") text = "单人游戏" +[node name="startMultiplayerBtn" type="Button" parent="content/wrapper/starter/singleplayer/start" index="2"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +theme = ExtResource("4_lfxcn") +disabled = true +text = "多人游戏" + [node name="multiplayer" type="VBoxContainer" parent="content/wrapper/starter" index="1"] layout_mode = 2 theme_override_constants/separation = 15 @@ -153,26 +176,31 @@ size_flags_horizontal = 4 disabled = true text = "断开连接" -[node name="serverConfig" type="VBoxContainer" parent="content/wrapper/starter/multiplayer" index="1"] +[node name="HBoxContainer" type="HBoxContainer" parent="content/wrapper/starter/multiplayer" index="1"] layout_mode = 2 +theme_override_constants/separation = 30 alignment = 1 -[node name="title" type="Label" parent="content/wrapper/starter/multiplayer/serverConfig" index="0"] +[node name="serverConfig" type="VBoxContainer" parent="content/wrapper/starter/multiplayer/HBoxContainer" index="0"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="title" type="Label" parent="content/wrapper/starter/multiplayer/HBoxContainer/serverConfig" index="0"] layout_mode = 2 size_flags_horizontal = 4 text = "服务器配置" -[node name="maxPlayer" type="HBoxContainer" parent="content/wrapper/starter/multiplayer/serverConfig" index="1"] +[node name="maxPlayer" type="HBoxContainer" parent="content/wrapper/starter/multiplayer/HBoxContainer/serverConfig" index="1"] layout_mode = 2 alignment = 1 -[node name="title" type="Label" parent="content/wrapper/starter/multiplayer/serverConfig/maxPlayer" index="0"] +[node name="title" type="Label" parent="content/wrapper/starter/multiplayer/HBoxContainer/serverConfig/maxPlayer" index="0"] layout_mode = 2 size_flags_horizontal = 4 text = "最大玩家数" label_settings = SubResource("LabelSettings_i7qv0") -[node name="maxPlayerInput" type="LineEdit" parent="content/wrapper/starter/multiplayer/serverConfig/maxPlayer" index="1"] +[node name="maxPlayerInput" type="LineEdit" parent="content/wrapper/starter/multiplayer/HBoxContainer/serverConfig/maxPlayer" index="1"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 4 @@ -182,3 +210,26 @@ expand_to_text_length = true emoji_menu_enabled = false virtual_keyboard_type = 2 select_all_on_focus = true + +[node name="VBoxContainer" type="VBoxContainer" parent="content/wrapper/starter/multiplayer/HBoxContainer" index="1"] +layout_mode = 2 + +[node name="Label" type="Label" parent="content/wrapper/starter/multiplayer/HBoxContainer/VBoxContainer" index="0"] +layout_mode = 2 +text = "玩家名" + +[node name="Label2" type="Label" parent="content/wrapper/starter/multiplayer/HBoxContainer/VBoxContainer" index="1"] +layout_mode = 2 +text = "玩家名" + +[node name="Label3" type="Label" parent="content/wrapper/starter/multiplayer/HBoxContainer/VBoxContainer" index="2"] +layout_mode = 2 +text = "玩家名" + +[node name="Label4" type="Label" parent="content/wrapper/starter/multiplayer/HBoxContainer/VBoxContainer" index="3"] +layout_mode = 2 +text = "玩家名" + +[node name="Label5" type="Label" parent="content/wrapper/starter/multiplayer/HBoxContainer/VBoxContainer" index="4"] +layout_mode = 2 +text = "玩家名" diff --git a/components/Scenes/World.tscn b/components/Scenes/World.tscn index 69dd1ea..c3015c7 100644 --- a/components/Scenes/World.tscn +++ b/components/Scenes/World.tscn @@ -1,10 +1,9 @@ -[gd_scene load_steps=10 format=3 uid="uid://dmxi1ikn6avig"] +[gd_scene load_steps=9 format=3 uid="uid://dmxi1ikn6avig"] -[ext_resource type="Script" path="res://scripts/Tools/Managers/WorldManager.gd" id="1_lxsxj"] +[ext_resource type="Script" uid="uid://d2oyyyg0b4qqd" path="res://scripts/Tools/Managers/WorldManager.gd" id="1_lxsxj"] [ext_resource type="PackedScene" uid="uid://dfwg750a47ggx" path="res://components/Scenes/UI.tscn" id="2_04cdd"] -[ext_resource type="PackedScene" uid="uid://bm7ymrri6pykb" path="res://components/Characters/Rooster.tscn" id="3_5ui6q"] [ext_resource type="Texture2D" uid="uid://c33c8mtm4x3e3" path="res://resources/maps/Galaxy.png" id="4_oy4jj"] -[ext_resource type="Script" path="res://scripts/Tools/Managers/CameraManager.gd" id="5_mk7bv"] +[ext_resource type="Script" uid="uid://bs45p8w83d4b4" path="res://scripts/Tools/Managers/CameraManager.gd" id="5_mk7bv"] [sub_resource type="Animation" id="Animation_ykpvi"] length = 0.001 @@ -60,8 +59,8 @@ tracks/1/keys = { [sub_resource type="AnimationLibrary" id="AnimationLibrary_44ixa"] _data = { -"RESET": SubResource("Animation_ykpvi"), -"bigLaser": SubResource("Animation_kii8h") +&"RESET": SubResource("Animation_ykpvi"), +&"bigLaser": SubResource("Animation_kii8h") } [sub_resource type="CircleShape2D" id="CircleShape2D_4hkht"] @@ -85,7 +84,7 @@ script = ExtResource("5_mk7bv") [node name="animator" type="AnimationPlayer" parent="camera"] unique_name_in_owner = true libraries = { -"": SubResource("AnimationLibrary_44ixa") +&"": SubResource("AnimationLibrary_44ixa") } [node name="map" type="StaticBody2D" parent="." groups=["map"]] @@ -103,5 +102,3 @@ shape = SubResource("CircleShape2D_4hkht") [node name="shan2" type="CollisionPolygon2D" parent="map"] polygon = PackedVector2Array(-2419, 1803, 2429, 1825, 2392, -366, 2867, -318, 2723, 2241, -2879, 2193, -2797, -2582, 2959, -2528, 2858, -347, 2420, -337, 2441, -1834, -2438, -1792) - -[node name="rooster" parent="." groups=["players"] instance=ExtResource("3_5ui6q")] diff --git a/scripts/Contents/Panels/Starter.gd b/scripts/Contents/Panels/Starter.gd index 9c5b74c..d42b0e1 100644 --- a/scripts/Contents/Panels/Starter.gd +++ b/scripts/Contents/Panels/Starter.gd @@ -3,6 +3,7 @@ extends FullscreenPanelBase @onready var diffEdit: HSlider = $"%diffEdit" @onready var startBtn: Button = $"%startBtn" +@onready var startMultiplayerBtn: Button = $"%startMultiplayerBtn" @onready var levelShow: Label = $"%levelShow" @onready var hostInput: LineEdit = $"%hostInput" @onready var portInput: LineEdit = $"%portInput" @@ -11,15 +12,30 @@ extends FullscreenPanelBase @onready var maxPlayerInput: LineEdit = $"%maxPlayerInput" @onready var connectionState: Label = $"%connectionState" @onready var disconnectBtn: Button = $"%disconnectBtn" +@onready var playerNameInput: LineEdit = $"%playerNameInput" +@onready var serverConfig: VBoxContainer = $"%serverConfig" func _ready(): diffEdit.min_value = GameRule.difficultyRange.x diffEdit.max_value = GameRule.difficultyRange.y + multiplayer.connection_failed.connect( + func(): + setState(MultiplayerState.ConnectionState.DISCONNECTED) + ) + multiplayer.peer_connected.connect( + func(): + setState(MultiplayerState.ConnectionState.CONNECTED_CLIENT) + ) startBtn.pressed.connect( func(): + EntityBase.generatePlayer(playerNameInput.text) Wave.next() UIState.closeCurrentPanel() ) + startMultiplayerBtn.pressed.connect( + func(): + pass + ) maxPlayerInput.text_changed.connect( func(text): MultiplayerState.maxPlayer = int(text) @@ -27,6 +43,16 @@ func _ready(): launchBtn.pressed.connect( func(): MultiplayerState.launchServer(int(portInput.text)) + setState(MultiplayerState.ConnectionState.CONNECTED_HOST) + ) + connectBtn.pressed.connect( + func(): + multiplayer.multiplayer_peer = MultiplayerState.connectClient(hostInput.text, int(portInput.text)) + setState(MultiplayerState.ConnectionState.CONNECTING) + ) + disconnectBtn.pressed.connect( + func(): + setState(MultiplayerState.ConnectionState.DISCONNECTED) ) setState(MultiplayerState.ConnectionState.DISCONNECTED) func _physics_process(_delta): @@ -38,3 +64,5 @@ func setState(state: MultiplayerState.ConnectionState): connectionState.text = "状态:%s" % MultiplayerState.stateTextMap[state] connectionState.modulate = MultiplayerState.stateColorMap[state] disconnectBtn.disabled = not MultiplayerState.isConnected() + startMultiplayerBtn.disabled = not MultiplayerState.isConnected() + serverConfig.visible = MultiplayerState.state == MultiplayerState.ConnectionState.CONNECTED_HOST diff --git a/scripts/Statemachine/EntityBase.gd b/scripts/Statemachine/EntityBase.gd index 0a75371..5170b2e 100644 --- a/scripts/Statemachine/EntityBase.gd +++ b/scripts/Statemachine/EntityBase.gd @@ -175,6 +175,7 @@ func _physics_process(_delta: float) -> void: move_and_slide() storeEnergy(randf_range(0.01, 0.05 + fields.get(FieldStore.Entity.ENERGY_REGENERATION) - 1), true) trailParticle.emitting = trailing + rpc("syncPosition", displayName, position) # 通用方法 func rebuildWeaponIcons(): @@ -393,11 +394,26 @@ func summon(who: PackedScene, syncFields: bool = true, lockValue: bool = true) - get_parent().add_child(instance) return instance +# 多人游戏数据同步 +@rpc("any_peer") +func syncPosition(player: String, newPosition: Vector2): + if player == displayName: + position = newPosition +@rpc("any_peer") +func syncHealth(player: String, newHealth: float): + if player == displayName: + health = newHealth + healthChanged.emit(health) +@rpc("any_peer") +func syncAttack(player: String, index: int): + if player == displayName: + tryAttack(index) + # 关于追踪 func getTrackingAnchor() -> Vector2: return hurtbox.get_node("hitbox").global_position -# 关于分组 +# 关于实体类型 func isPlayer() -> bool: return is_in_group("players") func isSummon() -> bool: @@ -430,6 +446,10 @@ func enterStage(_stage: int): func kill(): pass +static func generatePlayer(playerName: String): + var player = generate(ComponentManager.getCharacter("Rooster"), Vector2.ZERO, false, false, true) + player.displayName = playerName + return player static func generate( entity: PackedScene, spawnPosition: Vector2, @@ -443,6 +463,8 @@ static func generate( instance.level = clamp((round(Wave.current * (1 + GameRule.entityLevelOffsetByWave * randf_range(-1, 1)))), 1, INF) if isMob: instance.add_to_group("mobs") + else: + instance.add_to_group("players") if addToWorld: WorldManager.rootNode.add_child(instance) return instance diff --git a/scripts/Tools/Managers/MultiplayerState.gd b/scripts/Tools/Managers/MultiplayerState.gd index 79ecda5..5e19ac4 100644 --- a/scripts/Tools/Managers/MultiplayerState.gd +++ b/scripts/Tools/Managers/MultiplayerState.gd @@ -9,7 +9,7 @@ enum ConnectionState { } static var stateTextMap = { ConnectionState.DISCONNECTED: "未连接到服务器。", - ConnectionState.CONNECTING: "连接中...", + ConnectionState.CONNECTING: "正在连接到服务器...", ConnectionState.CONNECTED_HOST: "服务器启动成功!", ConnectionState.CONNECTED_CLIENT: "已连接到服务器!", } @@ -21,18 +21,17 @@ static var stateColorMap = { } static var state: ConnectionState = ConnectionState.DISCONNECTED -static var isMultiplayer: bool = false static var maxPlayer: int = 10 static func isConnected(): return [ConnectionState.CONNECTED_HOST, ConnectionState.CONNECTED_CLIENT].has(state) -static func launchServer(port: int): +static func launchServer(port: int) -> ENetMultiplayerPeer: var peer = ENetMultiplayerPeer.new() peer.create_server(port, maxPlayer) state = ConnectionState.CONNECTED_HOST return peer -static func connectClient(host: String, port: int): +static func connectClient(host: String, port: int) -> ENetMultiplayerPeer: var peer = ENetMultiplayerPeer.new() peer.create_client(host, port) state = ConnectionState.CONNECTED_CLIENT