主题
玩家系统(Player System)
管理游戏中的玩家槽位、用户连接、队伍关系和控制权。
定义位置:GameCore.PlayerAndUsers
核心概念
Player vs User
| 概念 | 类型 | 生命周期 | 用途 |
|---|---|---|---|
Player | 槽位 | 游戏全程存在 | 控制单位、队伍关系、指令权限 |
User | 平台用户 | 连接时绑定,断线后可能变化 | 持久化数据(CloudData)、显示名 |
Player.Id(int)= 临时槽位索引,每局不同User.UserId(long)= 平台持久 ID,用于 CloudData
AI 玩家没有 User(Player.User == null)。
Player 类
属性
| 属性 | 类型 | 端 | 说明 |
|---|---|---|---|
Id | int | 双端 | 槽位 ID |
Team | Team | 双端(服务端可写) | 所属队伍 |
IsNeutral | bool | 双端(服务端可写) | 中立标志 |
ControllerSetting | ControllerType | 双端 | 配置的控制器类型(来自 GameDataPlayerSettings) |
Controller | ControllerType | 双端 | 实际控制器类型 |
MainUnit | Unit? | 双端(服务端可写) | 主控单位 |
Units | IReadOnlyCollection<Unit> | 双端 | 拥有的所有单位 |
Scene | Scene | 双端 | 当前场景 |
User | User? | 双端 | 绑定的平台用户;AI 玩家为 null |
IsOnline | bool | 服务端 | Connected 或 Unresponsive 状态;AI 始终在线 |
IsConnectionUnresponsive | bool | 服务端 | 当前连接是否处于暂时无响应状态 |
SlotController | SlotController | 服务端 | PlayerController(人类)或 AIController(AI) |
MainUnit 绑定与移动能力无关:未启用 UpdateFlags.Walkable 或 AllowMover 的静态单位也可以作为主控单位,用于默认镜头跟随。移动摇杆和移动指令仍要求单位具备对应的移动能力。设置无效单位时会清空绑定并记录警告;单位应属于该玩家,并与玩家处于同一场景。
静态成员
| 成员 | 类型 | 说明 |
|---|---|---|
AllPlayers | static List<Player> | 所有玩家 |
DefaultPlayer | static Player | 系统玩家(Id = 0) |
LocalPlayer | static Player | 本地玩家(仅客户端) |
GetById(int id) | static Player? | 按 ID 查找 |
方法
csharp
// 关系查询
PlayerRelationShip GetRelationShip(Player other);
bool CanControlTo(Entity entity, bool isRequest = false);
// 属性
TValue? GetProperty<TValue>(PropertyPlayer property);
void SetProperty<TValue>(PropertyPlayer property, TValue value); // 服务端
void AddProperty(PropertyPlayer property, Fixed value); // 服务端
TValue? GetUIProperty<TValue>(PropertyPlayerUI property);
// 场景(服务端)
bool SwitchScene(Scene scene, bool withMainUnit = false);
// 控制权共享(服务端)
void SetShareControlTo(Player player, bool on);
bool IsShareControlTo(Player player);
void SetShareSightTo(Player player, bool on);
bool IsShareSightTo(Player player);
// 消息广播(服务端)
static void BroadcastClientMessage<T>(ref T protocol, Func<Player, bool>? filter = null);切场景与主控绑定
SwitchScene(scene) 默认 withMainUnit = false,只请求切换玩家当前视图,不负责携带现有英雄。需要框架携带当前主控时可用 SwitchScene(scene, true);若项目已通过 hero.MoveTo(...) 显式迁移英雄,则使用 SwitchScene(scene, false),在服务端玩家与英雄同场后重新设置 MainUnit。绑定前核对原玩家槽位与英雄归属,避免因时序问题误换角色或玩家。
返回 true 不代表客户端地图、英雄副本和主控都已就绪。客户端应结合场景加载状态、本地英雄副本及 Player.LocalPlayer.MainUnit 确认完成;其他玩家的 Scene 在客户端不一定可靠,不能用于判断远端玩家是否迁移成功。迁移顺序、库存校验、失败恢复及双端验收见 携带已有英雄跨场景。
客户端事件
csharp
event Action<bool>? OnNeutralChanged;
event Action<Team>? OnTeamChanged;
event Action<Unit?>? OnMainUnitChanged;User 类
属性
| 属性 | 类型 | 端 | 说明 |
|---|---|---|---|
UserId | long | 双端 | 平台持久 ID |
Name | string? | 双端 | 显示名(需先 Fetch) |
NameFetchStatus | UserNameFetchStatus | 服务端 | NotStarted/InProgress/Succeeded/Failed/NotFound |
方法
csharp
// 获取平台用户名(服务端)
async Task<bool> FetchNameAsync();
static async Task<UserCloudDataResult> FetchNameAsyncForAllLivingUsers();
// 事件
event Action<User, string>? OnNameFetched; // 服务端ControllerType
| 值 | 说明 |
|---|---|
User | 人类槽位,有 User,支持连接/断线 |
Computer | AI 槽位,无 User,始终在线 |
队伍与关系
Team
csharp
int Id;
LocalizedString? DisplayName;
static Team? GetById(int id);
Player[] GetAllPlayers();PlayerRelationShip
| 值 | 条件 |
|---|---|
Player | 同一玩家 |
Ally | 同队不同玩家 |
Neutral | 任一方 IsNeutral |
Enemy | 不同队且不中立 |
用法
csharp
var rel = player1.GetRelationShip(player2);
if (rel == PlayerRelationShip.Enemy)
{
Game.Logger.LogInformation("玩家 {P1} 和 {P2} 是敌对关系", player1.Id, player2.Id);
}玩家事件
通过 Trigger<T> 订阅,在 OnGameTriggerInitialization 中注册。
服务端事件
| 事件类型 | 属性 | 触发时机 |
|---|---|---|
EventPlayerUserConnected | Player, User, OldUser? | 用户连接 |
EventPlayerUserDisconnected | Player, User | 用户断线 |
EventPlayerConnectionUnresponsive | Player, User | 连接进入 paused / 暂时失联 |
EventPlayerConnectionRestored | Player, User | 连接从 paused / 暂时失联恢复 |
EventPlayerUserActivelyQuit | Player, User | 用户主动退出 |
EventPlayerUserAbandonReconnect | Player, User | 放弃重连 |
EventPlayerUserNameFetched | Player, User, Name | 名称获取完成 |
EventPlayerStatusChanged | Player, Status, OldStatus | 状态变化 |
EventPlayerPropertyChange | Player, Property | 属性变化 |
EventPlayerConnectionUnresponsive / EventPlayerConnectionRestored 对应底层连接 paused / resume。常见来源包括客户端切后台、少数加载流程主动 pause,或 Host 在一段时间没有收到客户端包后自动把连接标为 paused。默认配置下,这个无包窗口通常约 10 秒;若 paused 状态继续超过地图配置 connection_timeout_when_paused,默认 120 秒,才会进入真正断线流程并触发 EventPlayerUserDisconnected。
客户端事件
| 事件类型 | 属性 | 触发时机 |
|---|---|---|
EventPlayerMainUnitChanged | Player, Unit? | 主控单位变化 |
EventPlayerUIPropertyChange | Player, PropertyPlayerUI | UI 属性变化 |
订阅示例
csharp
public static void OnRegisterGameClass()
{
Game.OnGameTriggerInitialization += OnGameTriggerInitialization;
}
#if SERVER
private static void OnGameTriggerInitialization()
{
if (Game.GameModeLink != ScopeData.GameDataGameMode.MapGameMode) return;
var connectTrigger = new Trigger<EventPlayerUserConnected>(OnPlayerConnected);
connectTrigger.Register(Game.Instance);
var disconnectTrigger = new Trigger<EventPlayerUserDisconnected>(OnPlayerDisconnected);
disconnectTrigger.Register(Game.Instance);
}
private static void OnPlayerConnected(EventPlayerUserConnected e)
{
Game.Logger.LogInformation("玩家 {PlayerId} 连接,用户 {UserId}", e.Player.Id, e.User.UserId);
}
private static void OnPlayerDisconnected(EventPlayerUserDisconnected e)
{
Game.Logger.LogInformation("玩家 {PlayerId} 断线", e.Player.Id);
}
#endifPlayerStatus
| 值 | 说明 |
|---|---|
Open | 槽位开放 |
Connected | 已连接 |
Unresponsive | 连接 paused / 暂时失联 |
Disconnected | 已断线 |
Left | 已离开 |
常用模式
遍历玩家
csharp
// 所有玩家
foreach (var player in Player.AllPlayers) { ... }
// 按 ID 查找
var player = Player.GetById(playerId);
// 队友
var teammates = myPlayer.Team.GetAllPlayers();
// 在线玩家(服务端)
#if SERVER
var online = Player.AllPlayers.Where(p => p.IsOnline);
#endifCloudData 与 User
csharp
#if SERVER
// 正确:使用 User.UserId
var user = player.User;
if (user != null)
{
var result = await CloudData.ForUser(user.UserId).QueryUserDataAsync(...);
}
// 错误:不要用 Player.Id 做持久化标识
// Player.Id 是临时槽位索引,每局可能不同
#endif控制权检查
csharp
if (player.CanControlTo(unit))
{
unit.CastAbility(abilityLink, target);
}相关文档
- 玩家会话状态语义 — 登录态、断线、主动退出、顶号、重连与随进随出重新加入的区别
- 单位系统 — Unit 与 Player 的所有权关系
- CloudData 系统 — 使用 User.UserId 持久化数据
- 触发器系统 — 事件订阅机制