본문으로 건너뛰기
cccskills
무료GitHub에 공개

godot-online-multiplayer

Godot 4 多人游戏核心模式,包括场景管理、玩家生命周期、死亡复活机制。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md25.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Godot Online Multiplayer

Godot 4 多人游戏核心模式,涵盖场景管理、玩家生命周期、死亡复活等机制。

When to Use This Skill

  • 构建多人在线游戏
  • 实现玩家生成与销毁
  • 处理多人游戏中的死亡与复活
  • 管理游戏流程状态机
  • 同步玩家数据

Core Concepts

多人游戏架构

MultiplayerHost (服务器/主机)
├── MultiplayerSpawner (自动生成节点)
├── MultiplayerSynchronizer (状态同步)
└── PlayerManager (玩家管理)

MultiplayerPeers (客户端/对等端)
├── NetworkedMultiplayerENet (网络连接)
└── Player instances (同步的玩家)

1. 场景结构

主游戏场景

# main.tscn 场景结构
# Main (Node)
# ├── GameManager (Autoload)
# ├── MultiplayerSpawner (自动生成玩家)
# ├── World (Node2D)
# │   ├── SpawnPoints (Node)
# │   └── Enemies (Node)
# └── UI (CanvasLayer)

玩家场景

# player.tscn 场景结构
# Player (CharacterBody2D)
# ├── Sprite2D
# ├── CollisionShape2D
# ├── HealthComponent
# ├── HitboxComponent
# ├── NameLabel (Label)
# └── HealthBar (ProgressBar)

2. MultiplayerSpawner 使用

# PlayerSpawner.gd
class_name PlayerSpawner
extends MultiplayerSpawner

## 自动生成玩家节点
## 当对等端连接时自动生成玩家实例

signal player_spawned(player: Node, peer_id: int)
signal player_despawned(player: Node, peer_id: int)

@export var player_scene: PackedScene

func _ready() -> void:
    # 设置生成路径
    spawn_path = ^"/root/Main/Players"
    
    # 连接信号
    spawned.connect(_on_spawned)
    despawned.connect(_on_despawned)

func _on_spawned(node: Node) -> void:
    var peer_id := multiplayer.get_remote_sender_id()
    print("玩家生成: %s (Peer ID: %d)" % [node.name, peer_id])
    player_spawned.emit(node, peer_id)

func _on_despawned(node: Node) -> void:
    var peer_id := multiplayer.get_remote_sender_id()
    print("玩家销毁: %s (Peer ID: %d)" % [node.name, peer_id])
    player_despawned.emit(node, peer_id)

3. 生成函数

# spawn_manager.gd
class_name SpawnManager
extends Node

## 管理玩家生成逻辑

@export var player_scene: PackedScene
@export var spawn_points: Array[Marker2D] = []

var _spawn_points_container: Node2D

func _ready() -> void:
    _spawn_points_container = $SpawnPoints

func get_spawn_position(peer_id: int) -> Vector2:
    """根据peer_id获取生成位置"""
    if spawn_points.is_empty():
        return Vector2.ZERO
    
    # 使用 peer_id 哈希选择生成点,实现均衡分布
    var index := peer_id % spawn_points.size()
    var spawn_point := spawn_points[index]
    
    if spawn_point is Marker2D:
        return spawn_point.global_position
    
    return spawn_point.global_position if spawn_point.has_node(".") else Vector2.ZERO

func spawn_player(peer_id: int, parent: Node) -> Node:
    """生成玩家节点"""
    var player := player_scene.instantiate()
    
    # 设置玩家名称为 peer_id
    player.name = str(peer_id)
    
    # 设置初始位置
    player.global_position = get_spawn_position(peer_id)
    
    # 配置玩家
    if player.has_method("setup"):
        player.setup(peer_id)
    
    parent.add_child(player)
    
    # 只在服务器上设置网络主人
    if multiplayer.is_server():
        player.set_multiplayer_authority(peer_id)
    
    return player

func despawn_player(peer_id: int, parent: Node) -> void:
    """销毁玩家节点"""
    var player := parent.get_node_or_null(str(peer_id))
    if player:
        player.queue_free()

4. 玩家加入流程

# game_manager.gd (Autoload)
extends Node

signal player_joined(peer_id: int)
signal player_left(peer_id: int)
signal game_started
signal game_ended(winner_id: int)

enum GameState { WAITING, PLAYING, GAME_OVER }

var game_state: GameState = GameState.WAITING
var players: Dictionary = {}  # peer_id -> PlayerData
var _min_players: int = 1
var _max_players: int = 4

func _ready() -> void:
    # 设置网络回调
    multiplayer.peer_connected.connect(_on_peer_connected)
    multiplayer.peer_disconnected.connect(_on_peer_disconnected)
    
    # 如果是服务器,启动游戏循环
    if multiplayer.is_server():
        _check_start_game()

func _on_peer_connected(peer_id: int) -> void:
    print("玩家连接: %d" % peer_id)
    
    # 创建玩家数据
    var player_data := PlayerData.new()
    player_data.peer_id = peer_id
    player_data.name = "Player_%d" % peer_id
    player_data.is_alive = true
    player_data.score = 0
    
    players[peer_id] = player_data
    
    # 通知所有客户端有新玩家加入
    rpc("_sync_player_joined", peer_id, player_data.to_dict())
    
    player_joined.emit(peer_id)
    
    # 检查是否可以开始游戏
    _check_start_game()

func _on_peer_disconnected(peer_id: int) -> void:
    print("玩家断开: %d" % peer_id)
    
    # 清理玩家数据
    var player_data := players.get(peer_id)
    if player_data:
        player_data.free()
    
    players.erase(peer_id)
    
    # 通知所有客户端
    rpc("_sync_player_left", peer_id)
    
    player_left.emit(peer_id)
    
    # 检查游戏是否结束
    if game_state == GameState.PLAYING and players.size() < 2:
        _end_game(0)  # 剩余玩家获胜

@rpc(call_local = true)
func _sync_player_joined(peer_id: int, data: Dictionary) -> void:
    if not players.has(peer_id):
        var player_data := PlayerData.new()
        player_data.from_dict(data)
        players[peer_id] = player_data

@rpc(call_local = true)
func _sync_player_left(peer_id: int) -> void:
    var player_data := players.get(peer_id)
    if player_data:
        player_data.free()
    players.erase(peer_id)

func _check_start_game() -> void:
    if game_state == GameState.WAITING and players.size() >= _min_players:
        start_game()

func start_game() -> void:
    game_state = GameState.PLAYING
    game_started.emit()
    
    # 重置所有玩家状态
    for peer_id in players.keys():
        var data := players[peer_id]
        data.is_alive = true
        data.lives = 3
        data.score = 0
    
    rpc("_sync_game_state", GameState.PLAYING)

@rpc(call_local = true)
func _sync_game_state(state: GameState) -> void:
    game_state = state

func _end_game(winner_id: int) -> void:
    game_state = GameState.GAME_OVER
    game_ended.emit(winner_id)
    rpc("_sync_game_state", GameState.GAME_OVER)
    rpc("_sync_winner", winner_id)

@rpc(call_local = true)
func _sync_winner(winner_id: int) -> void:
    pass  # UI显示胜利者


# PlayerData.gd
class_name PlayerData
extends RefCounted

var peer_id: int
var name: String
var is_alive: bool
var lives: int = 3
var score: int = 0

func to_dict() -> Dictionary:
    return {
        "peer_id": peer_id,
        "name": name,
        "is_alive": is_alive,
        "lives": lives,
        "score": score
    }

func from_dict(data: Dictionary) -> void:
    peer_id = data.get("peer_id", 0)
    name = data.get("name", "")
    is_alive = data.get("is_alive", true)
    lives = data.get("lives", 3)
    score = data.get("score", 0)

5. 断开处理

# connection_manager.gd
class_name ConnectionManager
extends Node

signal connection_failed
signal connection_succeeded
signal server_disconnected

const DEFAULT_PORT := 8765
const MAX_PLAYERS := 8

func _ready() -> void:
    # 显示主菜单UI
    pass

func create_server(port: int = DEFAULT_PORT) -> bool:
    """创建服务器"""
    var peer := ENetMultiplayerPeer.new()
    
    var error := peer.create_server(port, MAX_PLAYERS)
    if error != OK:
        push_error("无法创建服务器: %s" % error)
        return false
    
    multiplayer.multiplayer_peer = peer
    
    # 连接信号
    peer.peer_connected.connect(_on_peer_connected)
    peer.peer_disconnected.connect(_on_peer_disconnected)
    
    print("服务器创建成功,端口: %d" % port)
    connection_succeeded.emit()
    
    return true

func join_server(ip: String, port: int = DEFAULT_PORT) -> bool:
    """加入服务器"""
    var peer := ENetMultiplayerPeer.new()
    
    var error := peer.create_client(ip, port)
    if error != OK:
        push_error("无法连接服务器: %s" % error)
        connection_failed.emit()
        return false
    
    multiplayer.multiplayer_peer = peer
    
    # 连接信号
    peer.connection_succeeded.connect(_on_connection_succeeded)
    peer.connection_failed.connect(_on_connection_failed)
    peer.server_disconnected.connect(_on_server_disconnected)
    
    print("正在连接 %s:%d..." % [ip, port])
    
    return true

func _on_peer_connected(peer_id: int) -> void:
    print("对等端连接: %d" % peer_id)

func _on_peer_disconnected(peer_id: int) -> void:
    print("对等端断开: %d" % peer_id)
    # GameManager 处理玩家离开逻辑

func _on_connection_succeeded() -> void:
    print("连接成功!")
    connection_succeeded.emit()

func _on_connection_failed() -> void:
    print("连接失败!")
    connection_failed.emit()

func _on_server_disconnected() -> void:
    print("服务器断开")
    server_disconnected.emit()
    
    # 返回主菜单
    get_tree().change_scene_to_file("res://scenes/menu.tscn")

func disconnect_from_server() -> void:
    if multiplayer.multiplayer_peer:
        multiplayer.multiplayer_peer.close()
        multiplayer.multiplayer_peer = null

6. 死亡与复活

# player.gd
class_name Player
extends CharacterBody2D

signal died(peer_id: int, killer_id: int)
signal respawned(peer_id: int)

@export var respawn_time: float = 3.0
@export var invincibility_after_spawn: float = 2.0

var peer_id: int = 0
var is_alive: bool = true
var killer_id: int = 0

var _respawn_timer: Timer
var _invincibility_timer: Timer
var _health_component: HealthComponent

func _ready() -> void:
    # 获取生命组件
    _health_component = $HealthComponent
    
    if _health_component:
        _health_component.died.connect(_on_died)
    
    # 创建计时器
    _respawn_timer = Timer.new()
    _respawn_timer.one_shot = true
    _respawn_timer.timeout.connect(_on_respawn_timer_timeout)
    add_child(_respawn_timer)
    
    _invincibility_timer = Timer.new()
    _invincibility_timer.one_shot = true
    _invincibility_timer.timeout.connect(_on_invincibility_timeout)
    add_child(_invincibility_timer)

func setup(p_id: int) -> void:
    peer_id = p_id
    name = str(p_id)
    
    # 设置网络权限
    set_multiplayer_authority(p_id)
    
    # 在所有客户端上设置可见性
   .rpc("_sync_setup", p_id)

@rpc
func _sync_setup(p_id: int) -> void:
    peer_id = p_id
    name = str(p_id)

func _on_died(source_peer_id: int) -> void:
    if not is_alive:
        return
    
    is_alive = false
    killer_id = source_peer_id
    
    died.emit(peer_id, killer_id)
    
    # 通知服务器
    rpc("_notify_death", peer_id, source_peer_id)

@rpc(call_local = true)
func _notify_death(victim_id: int, killer_id: int) -> void:
    died.emit(victim_id, killer_id)
    
    # 服务器处理死亡逻辑
    if multiplayer.is_server():
        GameManager.on_player_died(victim_id, killer_id)
        
        # 开始复活计时
        _start_respawn_timer()

func _start_respawn_timer() -> void:
    _respawn_timer.start(respawn_time)

func _on_respawn_timer_timeout() -> void:
    if is_networked():
        rpc("_request_respawn", peer_id)
    else:
        _do_respawn()

@rpc
func _request_respawn(p_id: int) -> void:
    if multiplayer.is_server():
        _do_respawn_for_player(p_id)

func _do_respawn_for_player(p_id: int) -> void:
    # 服务器检查是否可以复活
    var player_data := GameManager.players.get(p_id)
    if player_data and player_data.lives > 0:
        rpc("_perform_respawn", p_id)

@rpc(call_local = true)
func _perform_respawn(p_id: int) -> void:
    if peer_id != p_id:
        return
    
    # 重置状态
    is_alive = true
    _health_component.current_health = _health_component.max_health
    
    # 移动到生成点
    global_position = SpawnManager.get_spawn_position(p_id)
    
    # 无敌时间
    _invincibility_timer.start(invincibility_after_spawn)
    
    respawned.emit(peer_id)

func _on_invincibility_timeout() -> void:
    # 恢复碰撞
    pass

func take_damage(amount: int, source_peer_id: int) -> void:
    if not is_alive:
        return
    
    if _health_component:
        _health_component.take_damage(amount, get_node_or_null(str(source_peer_id)))

7. 游戏结束检查

# game_manager.gd 继续

func on_player_died(victim_id: int, killer_id: int) -> void:
    """处理玩家死亡"""
    var victim_data := players.get(victim_id)
    var killer_data := players.get(killer_id)
    
    if not victim_data:
        return
    
    # 标记死亡
    victim_data.is_alive = false
    victim_data.lives -= 1
    
    # 给击杀者加分
    if killer_data and killer_id != victim_id:
        killer_data.score += 100
        rpc("_sync_player_scored", killer_id, killer_data.score)
    
    # 检查游戏是否结束
    _check_game_over()
    
    # 检查是否需要回合复活
    _check_round_respawn(victim_id)

func _check_game_over() -> void:
    """检查是否触发游戏结束"""
    var alive_players := players.values().filter(func(p): return p.is_alive)
    
    # 只剩一个玩家
    if alive_players.size() <= 1 and game_state == GameState.PLAYING:
        if alive_players.is_empty():
            _end_game(0)  # 平局
        else:
            _end_game(alive_players[0].peer_id)

func _check_round_respawn(victim_id: int) -> void:
    """检查是否进行回合复活"""
    var player_data := players.get(victim_id)
    if player_data and player_data.lives > 0:
        # 有剩余生命值,准备复活
        rpc("_schedule_round_respawn", victim_id, player_data.lives)

@rpc(call_local = true)
func _schedule_round_respawn(peer_id: int, remaining_lives: int) -> void:
    # UI显示复活倒计时
    pass

@rpc(call_local = true)
func _sync_player_scored(peer_id: int, new_score: int) -> void:
    var player_data := players.get(peer_id)
    if player_data:
        player_data.score = new_score

8. 回合复活

# round_manager.gd
class_name RoundManager
extends Node

signal round_started(round_number: int)
signal round_ended(round_number: int, winner_id: int)
signal all_rounds_ended(final_winner_id: int)

@export var rounds_to_win: int = 3
@export var round_intermission_time: float = 3.0

var current_round: int = 1
var round_state: RoundState = RoundState.INTERMISSION
var player_wins: Dictionary = {}  # peer_id -> wins

enum RoundState { INTERMISSION, FIGHTING, ROUND_END }

func _ready() -> void:
    GameManager.game_started.connect(_on_game_started)
    GameManager.game_ended.connect(_on_game_ended)

func _on_game_started() -> void:
    current_round = 1
    round_state = RoundState.INTERMISSION
    player_wins.clear()
    
    # 初始化所有玩家的胜利数
    for peer_id in GameManager.players.keys():
        player_wins[peer_id] = 0
    
    _start_round_intermission()

func _start_round_intermission() -> void:
    round_state = RoundState.INTERMISSION
    rpc("_sync_round_state", RoundState.INTERMISSION, current_round)
    
    # 3秒后开始回合
    await get_tree().create_timer(round_intermission_time).timeout
    _start_round()

func _start_round() -> void:
    round_state = RoundState.FIGHTING
    rpc("_sync_round_state", RoundState.FIGHTING, current_round)
    round_started.emit(current_round)
    
    # 重置所有玩家位置和状态
    rpc("_reset_all_players_for_round")

@rpc(call_local = true)
func _sync_round_state(state: RoundState, round_num: int) -> void:
    round_state = state
    current_round = round_num

@rpc(call_local = true)
func _reset_all_players_for_round() -> void:
    # 重置玩家状态用于新回合
    for peer_id_str in GameManager.players.keys():
        var peer_id := int(peer_id_str)
        var player := get_tree().get_first_node_in_group("players").get_node_or_null(str(peer_id))
        if player:
            player.global_position = SpawnManager.get_spawn_position(peer_id)
            player.is_alive = true
            if player.has_method("reset_for_round"):
                player.reset_for_round()

func on_player_died_in_round(victim_id: int, killer_id: int) -> void:
    """回合中玩家死亡"""
    if round_state != RoundState.FIGHTING:
        return
    
    var alive_players := _get_alive_players()
    
    if alive_players.size() <= 1:
        _end_round(alive_players[0] if alive_players else 0)

func _get_alive_players() -> Array:
    var alive: Array = []
    for peer_id in GameManager.players.keys():
        var data := GameManager.players[peer_id]
        if data.is_alive:
            alive.append(peer_id)
    return alive

func _end_round(winner_id: int) -> void:
    round_state = RoundState.ROUND_END
    round_ended.emit(current_round, winner_id)
    
    if winner_id != 0:
        player_wins[winner_id] += 1
    
    # 检查是否有人获胜
    for peer_id in player_wins.keys():
        if player_wins[peer_id] >= rounds_to_win:
            _end_game(peer_id)
            return
    
    # 下一回合
    current_round += 1
    _start_round_intermission()

func _end_game(winner_id: int) -> void:
    all_rounds_ended.emit(winner_id)
    GameManager._end_game(winner_id)

func _on_game_ended(winner_id: int) -> void:
    round_state = RoundState.ROUND_END

9. 场景切换

# scene_transition.gd
class_name SceneTransition
extends CanvasLayer

@export var transition_duration: float = 0.5
@export var fade_color: Color = Color.BLACK

var _transition_tween: Tween
var _color_rect: ColorRect

func _ready() -> void:
    # 创建过渡用的 ColorRect
    _color_rect = ColorRect.new()
    _color_rect.color = Color.TRANSPARENT
    _color_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
    add_child(_color_rect)

func fade_to_black() -> void:
    """淡出到黑色"""
    if _transition_tween:
        _transition_tween.kill()
    
    _transition_tween = create_tween()
    _transition_tween.tween_property(_color_rect, "color", fade_color, transition_duration)
    
    await _transition_tween.finished

func fade_from_black() -> void:
    """从黑色淡入"""
    if _transition_tween:
        _transition_tween.kill()
    
    _color_rect.color = fade_color
    _transition_tween = create_tween()
    _transition_tween.tween_property(_color_rect, "color", Color.TRANSPARENT, transition_duration)
    
    await _transition_tween.finished

func change_scene(scene_path: String) -> void:
    """带过渡的场景切换"""
    await fade_to_black()
    get_tree().change_scene_to_file(scene_path)
    await fade_from_black()

# 使用示例
func _on_play_pressed() -> void:
    var transition := $SceneTransition
    await transition.fade_to_black()
    
    get_tree().change_scene_to_file("res://scenes/game.tscn")
    
    await transition.fade_from_black()

10. 游戏流程状态机

# game_state_machine.gd
class_name GameStateMachine
extends Node

## 游戏流程状态机

signal state_changed(from_state: GameStates, to_state: GameStates)

enum GameStates { 
    MAIN_MENU, 
    CONNECTING, 
    WAITING_FOR_PLAYERS, 
    LOADING, 
    PLAYING, 
    PAUSED, 
    ROUND_END, 
    GAME_OVER 
}

@export var initial_state: GameStates = GameStates.MAIN_MENU

var current_state: GameStates
var previous_state: GameStates

func _ready() -> void:
    current_state = initial_state
    _enter_state(current_state)

func transition_to(new_state: GameStates, msg: Dictionary = {}) -> void:
    if new_state == current_state:
        return
    
    previous_state = current_state
    var old_state := current_state
    current_state = new_state
    
    _exit_state(old_state)
    _enter_state(new_state, msg)
    
    state_changed.emit(old_state, new_state)

func _enter_state(state: GameStates, msg: Dictionary = {}) -> void:
    match state:
        GameStates.MAIN_MENU:
            _enter_main_menu()
        GameStates.CONNECTING:
            _enter_connecting()
        GameStates.WAITING_FOR_PLAYERS:
            _enter_waiting()
        GameStates.LOADING:
            _enter_loading()
        GameStates.PLAYING:
            _enter_playing()
        GameStates.PAUSED:
            _enter_paused()
        GameStates.ROUND_END:
            _enter_round_end()
        GameStates.GAME_OVER:
            _enter_game_over()

func _exit_state(state: GameStates) -> void:
    match state:
        GameStates.MAIN_MENU:
            _exit_main_menu()
        GameStates.CONNECTING:
            _exit_connecting()
        GameStates.WAITING_FOR_PLAYERS:
            _exit_waiting()
        GameStates.LOADING:
            _exit_loading()
        GameStates.PLAYING:
            _exit_playing()
        GameStates.PAUSED:
            _exit_paused()
        GameStates.ROUND_END:
            _exit_round_end()
        GameStates.GAME_OVER:
            _exit_game_over()

func _enter_main_menu() -> void:
    get_tree().paused = false
    get_tree().change_scene_to_file("res://scenes/menu.tscn")

func _exit_main_menu() -> void:
    pass

func _enter_connecting() -> void:
    # 显示连接界面
    pass

func _exit_connecting() -> void:
    pass

func _enter_waiting() -> void:
    # 显示等待玩家界面
    GameManager.game_state = GameManager.GameState.WAITING
    RoundManager._start_round_intermission()

func _exit_waiting() -> void:
    pass

func _enter_loading() -> void:
    # 显示加载界面
    pass

func _exit_loading() -> void:
    pass

func _enter_playing() -> void:
    get_tree().paused = false
    GameManager.game_state = GameManager.GameState.PLAYING

func _exit_playing() -> void:
    pass

func _enter_paused() -> void:
    get_tree().paused = true
    # 显示暂停菜单
    pass

func _exit_paused() -> void:
    get_tree().paused = false

func _enter_round_end() -> void:
    # 显示回合结束界面
    pass

func _exit_round_end() -> void:
    pass

func _enter_game_over() -> void:
    GameManager.game_state = GameManager.GameState.GAME_OVER
    # 显示游戏结束界面
    pass

func _exit_game_over() -> void:
    pass

func _process(delta: float) -> void:
    match current_state:
        GameStates.MAIN_MENU:
            _update_main_menu(delta)
        GameStates.WAITING_FOR_PLAYERS:
            _update_waiting(delta)
        GameStates.PLAYING:
            _update_playing(delta)

func _update_main_menu(_delta: float) -> void:
    pass

func _update_waiting(_delta: float) -> void:
    # 检查玩家数量
    if GameManager.players.size() >= GameManager._min_players:
        transition_to(GameStates.LOADING)

func _update_playing(_delta: float) -> void:
    pass

11. 数据管理(玩家字典)

# player_data.gd
class_name PlayerData
extends RefCounted

## 玩家数据结构

var peer_id: int = 0
var name: String = ""
var is_alive: bool = true
var lives: int = 3
var score: int = 0
var kills: int = 0
var deaths: int = 0
var ping: int = 0
var last_position: Vector2 = Vector2.ZERO
var custom_data: Dictionary = {}

func _init() -> void:
    pass

func to_dict() -> Dictionary:
    """序列化为字典"""
    return {
        "peer_id": peer_id,
        "name": name,
        "is_alive": is_alive,
        "lives": lives,
        "score": score,
        "kills": kills,
        "deaths": deaths,
        "ping": ping,
        "last_position": {"x": last_position.x, "y": last_position.y},
        "custom_data": custom_data
    }

func from_dict(data: Dictionary) -> void:
    """从字典反序列化"""
    peer_id = data.get("peer_id", 0)
    name = data.get("name", "")
    is_alive = data.get("is_alive", true)
    lives = data.get("lives", 3)
    score = data.get("score", 0)
    kills = data.get("kills", 0)
    deaths = data.get("deaths", 0)
    ping = data.get("ping", 0)
    
    var pos_dict: Dictionary = data.get("last_position", {})
    last_position = Vector2(pos_dict.get("x", 0), pos_dict.get("y", 0))
    
    custom_data = data.get("custom_data", {})

func reset() -> void:
    """重置玩家数据"""
    is_alive = true
    lives = 3
    score = 0
    kills = 0
    deaths = 0
    last_position = Vector2.ZERO

func add_kill() -> void:
    kills += 1
    score += 100

func add_death() -> void:
    deaths += 1
    lives = maxi(lives - 1, 0)

func update_ping(new_ping: int) -> void:
    ping = new_ping


# player_registry.gd
class_name PlayerRegistry
extends Node

## 玩家注册表,管理所有玩家数据

signal player_registered(peer_id: int, data: PlayerData)
signal player_unregistered(peer_id: int)
signal player_updated(peer_id: int, data: PlayerData)

var _players: Dictionary = {}  # peer_id -> PlayerData

func _ready() -> void:
    # 连接 GameManager 信号
    if GameManager:
        GameManager.player_joined.connect(_on_player_joined)
        GameManager.player_left.connect(_on_player_left)

func register_player(peer_id: int, data: PlayerData) -> void:
    _players[peer_id] = data
    player_registered.emit(peer_id, data)

func unregister_player(peer_id: int) -> void:
    if _players.has(peer_id):
        _players.erase(peer_id)
        player_unregistered.emit(peer_id)

func get_player(peer_id: int) -> PlayerData:
    return _players.get(peer_id)

func get_all_players() -> Array[PlayerData]:
    return Array(_players.values(), TYPE_OBJECT, "", null)

func get_alive_players() -> Array[PlayerData]:
    return get_all_players().filter(func(p): return p.is_alive)

func get_player_count() -> int:
    return _players.size()

func has_player(peer_id: int) -> bool:
    return _players.has(peer_id)

func _on_player_joined(peer_id: int) -> void:
    var data := PlayerData.new()
    data.peer_id = peer_id
    data.name = "Player_%d" % peer_id
    register_player(peer_id, data)

func _on_player_left(peer_id: int) -> void:
    unregister_player(peer_id)

func update_player(peer_id: int, updater: Callable) -> void:
    """使用回调更新玩家数据"""
    var data := get_player(peer_id)
    if data:
        updater.call(data)
        player_updated.emit(peer_id, data)

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Godot 4 Autoload单例模式最佳实践,包含全局管理、事件总线、信号通信。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

925236118/AlphaAgent1032026년 10월 19일 업데이트

Godot 4 Buff系统完整实现,支持状态效果管理、持续时间、循环效果、层叠机制等。使用Resource+Component架构。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

925236118/AlphaAgent1032026년 10월 19일 업데이트

Godot 4 建造系统实现,包含GridMap方块管理、第一人称视角、射线交互。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

925236118/AlphaAgent1032026년 10월 19일 업데이트

Godot 4 ECS实体组件系统模式,包含组件化设计、数据与逻辑分离、组件通信。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

925236118/AlphaAgent1032026년 10월 19일 업데이트

Godot 4 种植系统设计,包含植物数据结构、生长阶段、浇水机制、TileMap应用。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

925236118/AlphaAgent1032026년 10월 19일 업데이트

Master Godot 4 GDScript patterns including signals, scenes, state machines, and optimization. Use when building Godot games, implementing game systems, or learning GDScript best practices.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

925236118/AlphaAgent1032026년 10월 19일 업데이트

925236118의 스킬 모두 보기

이 스킬의 문제 신고