diff --git a/XNet.Business/Dto/SyncMsg.cs b/XNet.Business/Dto/SyncMsg.cs index 08df022..b8f505c 100644 --- a/XNet.Business/Dto/SyncMsg.cs +++ b/XNet.Business/Dto/SyncMsg.cs @@ -96,17 +96,52 @@ namespace XNet.Business /// /// Agent位置同步消息 /// - AGENT_POSITION_SYNC = 0x0018, + AGENT_LOCATION_SYNC = 0x0018, /// /// Agent角度同步消息 /// AGENT_ROTATION_SYNC = 0x0019, + /// + /// 用户上线 + /// + ONLINE = 0x0020, + + /// + /// 玩家请求导航点信息或者初始化 + /// + REQUEST_PATH = 0x1011, + + /// + /// 添加玩家 + /// + ADD_OR_UPDATE_PLAYER = 0x1004, + + /// + /// 锁定对象信息 + /// + LOCK_TARGET = 0x1012, + + /// + /// 玩家或者AI请求初始化 + /// + REQUEST_INIT = 0x1014, + /// /// 更改房间地图信息 /// CHANGE_ROOM_KEY_NAME = 0x1016, + + /// + /// 获取开始游戏的房间 + /// + GET_ROOM_FOR_START = 0x1019, + + /// + /// 退出当前所在的房间 + /// + LEAVE_ROOM = 0x1021, } @@ -115,6 +150,28 @@ namespace XNet.Business { [Key("roomId")] public string RoomId { get; set; } = string.Empty; + [Key("roomName")] + public string RoomName { get; set; } = string.Empty; + } + + + [MessagePackObject] + public class LockTargetReq + { + [Key("playerId")] + public string PlayerId { get; set; } = string.Empty; + [Key("lockedPlayerId")] + public string LockedPlayerId { get; set; } = string.Empty; + } + + + [MessagePackObject] + public class UserNetStateReply + { + [Key("type")] + public WsMsgType Type { get; set; } + [Key("playerId")] + public string PlayerId { get; set; } = string.Empty; } // 客户端订阅实例的请求消息 @@ -123,6 +180,8 @@ namespace XNet.Business { [Key("roomId")] public string RoomId { get; set; } = string.Empty; + [Key("roomName")] + public string RoomName { get; set; } = string.Empty; [Key("headImageUrl")] public string HeadImageUrl { get; set; } = string.Empty; @@ -152,6 +211,17 @@ namespace XNet.Business public Vec3? EndPos { get; set; } } + + [MessagePackObject] + public class PlayerLocationSyncReq + { + [Key("pos")] + public Vec3? Pos { get; set; } + + [Key("euler")] + public Vec3? Euler { get; set; } + } + //mapKey: mapName, //radius: radius, //playerId: playerId, @@ -224,6 +294,44 @@ namespace XNet.Business public Vec3? Euler { get; set; } } + /// + /// 玩家初始化同步信息 + /// + [MessagePackObject] + public class PlayerInitSync + { + [Key("type")] + public WsMsgType Type { get; set; } + [Key("roomId")] + public string RoomId { get; set; } = string.Empty; + [Key("isAI")] + public bool IsAI { get; set; } = false; + + [Key("playerId")] + public string PlayerId { get; set; } = string.Empty; + [Key("nickName")] + public string NickName { get; set; } = string.Empty; + + [Key("headImageUrl")] + public string? HeadImageUrl { get; set; } = null; + [Key("blood")] + public float Blood { get; set; } = 0; + [Key("bodyIdx")] + public int BodyIdx { get; set; } = 0; + [Key("weaponIdx")] + public int WeaponIdx { get; set; } = 0; + [Key("armorIdx")] + public int ArmorIdx { get; set; } = 0; + [Key("chassisIdx")] + public int ChassisIdx { get; set; } = 0; + [Key("position")] + public Vec3? Position { get; set; } + [Key("eulerAngles")] + public Vec3? EulerAngles { get; set; } + [Key("deviceColor")] + public string DeviceColor { get; set; } = "#FFFFFF"; + } + // 客户端订阅实例的请求消息 [MessagePackObject] public class SubscribeInstanceReq @@ -248,8 +356,12 @@ namespace XNet.Business [MessagePackObject] public class AgentLocationSyncMsg { - [Key("agentIdx")] - public int AgentIdx { get; set; } + [Key("type")] + public WsMsgType Type { get; set; } + //[Key("agentIdx")] + //public int AgentIdx { get; set; } + [Key("playerId")] + public string PlayerId { get; set; } = string.Empty; [Key("position")] public Vec3? Position { get; set; } [Key("rotation")] @@ -328,6 +440,54 @@ namespace XNet.Business public byte[]? Data { get; set; } = default; } + public enum LoginResult : byte + { + Success = 0, + Fail = 1 + } + + + //msgType: XNetEventKeys.LOGIN, + //playerId: client.sessionId, + //result: LoginResult.Success, + //blood: blood, + //deviceColor: message.deviceColor + + [MessagePackObject] + public class RequestInitMsg + { + [Key("type")] + public WsMsgType Type { get; set; } + [Key("isAI")] + public bool IsAI { get; set; } = false; + [Key("playerId")] + public string PlayerId { get; set; } = string.Empty; + [Key("pos")] + public Vec3 Pos { get; set; } = new Vec3(); + [Key("euler")] + public Vec3 Euler { get; set; } = new Vec3(); + + [Key("gameTime")] + public long GameTime { get; set; } = 0; + + } + + + [MessagePackObject] + public class LoginResultMsg + { + [Key("type")] + public WsMsgType Type; + [Key("playerId")] + public string PlayerId = string.Empty; + [Key("result")] + public LoginResult Result { get; set; } + [Key("blood")] + public float Blood { get; set; } + [Key("deviceColor")] + public string DeviceColor { get; set; } = "#FFFFFF"; + } + //// WebSocket通用消息包装 //[MessagePackObject] //public class WsMessage diff --git a/XNet.Business/Dto/Vec3.cs b/XNet.Business/Dto/Vec3.cs index 19886d3..6700108 100644 --- a/XNet.Business/Dto/Vec3.cs +++ b/XNet.Business/Dto/Vec3.cs @@ -6,12 +6,24 @@ namespace XNet.Business.Dto public class Vec3 { [Key("x")] - public float X { get; set; } + public int X { get; set; } [Key("y")] - public float Y { get; set; } + public int Y { get; set; } [Key("z")] - public float Z { get; set; } + public int Z { get; set; } + + public Vec3() + { + + } + + public Vec3(int x, int y, int z) + { + X = x; + Y = y; + Z = z; + } } } diff --git a/XNet.Business/Entity/ControlPlayer.cs b/XNet.Business/Entity/ControlPlayer.cs index 2812f2f..c944b12 100644 --- a/XNet.Business/Entity/ControlPlayer.cs +++ b/XNet.Business/Entity/ControlPlayer.cs @@ -1,4 +1,8 @@ -using XNet.Business.Dto; +using DotRecast.Detour.Crowd; +using System.Collections.Concurrent; +using System.Net.WebSockets; +using System.Numerics; +using XNet.Business.Dto; namespace XNet.Business.Entity { @@ -8,6 +12,10 @@ namespace XNet.Business.Entity /// 用户连接ID /// public string PlayerId { get; set; } = string.Empty; + /// + /// 用户昵称 + /// + public string NickName { get; set; } = string.Empty; /// /// 是否碰到非地面障碍物或者碰到其他对象,需要转向 @@ -19,6 +27,15 @@ namespace XNet.Business.Entity /// public bool IsCanControl { get; set; } = false; + /// + /// 是否可被击中 + /// + public bool IsCanHit { get; set; } = false; + /// + /// 是否已进地图 + /// + public bool IsEnterMap { get; set; } = false; + /// /// 是否是AI /// @@ -32,37 +49,61 @@ namespace XNet.Business.Entity /// /// 临时存储的位置 /// - public Vec3 TmpPos { get; set; } = new Vec3(); + public Vector3 TmpPos { get; set; } = new Vector3(); /// /// 将要去的位置 /// - public Vec3 ToPos { get; set; } = new Vec3(); + public Vector3 ToPos { get; set; } = new Vector3(); /// /// 存储当前的位置 /// - public Vec3 CurrentPos { get; set; } = new Vec3(); + public Vector3 CurrentPos { get; set; } = new Vector3(); /// /// 临时存储的欧拉角 /// - public Vec3 TmpEuler { get; set; } = new Vec3(); + public Vector3 TmpEuler { get; set; } = new Vector3(); /// /// 目标欧拉角,角色朝向 /// - public Vec3 ToEuler { get; set; } = new Vec3(); + public Vector3 ToEuler { get; set; } = new Vector3(); /// /// 当前线性插值欧拉角,角色朝向 /// - public Vec3 CurrentEuler { get; set; } = new Vec3(); + public Vector3 CurrentEuler { get; set; } = new Vector3(); + /// + /// 索敌距离 + /// public float FindFaceDistance { get; set; } = 0; + + /// + /// 车身半径 + /// public float Radius { get; set; } = 0; + /// + /// 火力 + /// public float AttackPower { get; set; } = 0; + /// + /// 护甲 + /// public float Armor { get; set; } = 0; + /// + /// 速度 + /// public float Speed { get; set; } = 0; + /// + /// 当前血量 + /// public float Blood { get; set; } = 0; + /// + /// 最大血量 + /// + public float MaxBlood { get; set; } = 0; + public int BodyIdx { get; set; } = 0; public int WeaponIdx { get; set; } = 0; public int ArmorIdx { get; set; } = 0; @@ -73,5 +114,39 @@ namespace XNet.Business.Entity /// 随机出生点 /// public List BirthPositions { get; set; } = new List(); + + + public WebSocket WebSocket { get; set; } = null!; + public ConcurrentDictionary RoomIds { get; set; } = new(); + public string FirstRoomId { get => RoomIds.Keys.FirstOrDefault()!; set => RoomIds.TryAdd(value, true); } + public string HeadImageUrl { get; set; } = string.Empty; + + public byte SetIdx { get; set; } + + /// + /// 路径导航索引 + /// + public DtCrowdAgent Agent { get; set; } = null!; + + + /// + /// 1技能等级 + /// + public byte Skill1Level { get; set; } = 1; + + /// + /// 2技能等级 + /// + public byte Skill2Level { get; set; } = 1; + + /// + /// 3技能等级 + /// + public byte Skill3Level { get; set; } = 1; + + /// + /// 4技能等级 + /// + public byte Skill4Level { get; set; } = 1; } } diff --git a/XNet.Business/Entity/PlayerRoomInfo.cs b/XNet.Business/Entity/PlayerRoomInfo.cs index cb59338..a720a6b 100644 --- a/XNet.Business/Entity/PlayerRoomInfo.cs +++ b/XNet.Business/Entity/PlayerRoomInfo.cs @@ -10,6 +10,8 @@ namespace XNet.Business.Entity /// 玩家实体(包括AI) public class PlayerRoomInfo { + public const int GameMillisecond = 3 * 1000 * 60 + 2000;//一局182s + public const int GameWillEndMillisecond = 10000;//10秒倒计时 /// /// AI 玩家数量 /// @@ -48,8 +50,32 @@ namespace XNet.Business.Entity public Dictionary Sets { get; set; } = new Dictionary(); public ControlPlayer? Host { get; set; } = null; + + /// + /// 是否是公开的房间,默认是 + /// public bool IsPublic { get; set; } = true; + + /// + /// 对局开始时间 + /// + public DateTime? StartGameTime { get; set; } = null; + /// + /// 对局结束时间 + /// + public DateTime? EndTime { get; set; } = null; + + /// + /// 对局将要结束的提示时间 + /// + public DateTime? WillEndTime { get; set; } = null; + + /// + /// 房间名 + /// + public string RoomName { get; set; } = string.Empty; + public PlayerRoomInfo() { } public PlayerRoomInfo(DtCrowd crowd, Vec3 worldMin, Vec3 worldMax) diff --git a/XNet.Business/Entity/PlayerStateInfo.cs b/XNet.Business/Entity/PlayerStateInfo.cs deleted file mode 100644 index bb86b29..0000000 --- a/XNet.Business/Entity/PlayerStateInfo.cs +++ /dev/null @@ -1,28 +0,0 @@ -using DotRecast.Detour.Crowd; -using System.Collections.Concurrent; -using System.Net.WebSockets; -using XNet.Business.Dto; - -namespace XNet.Business.Entity -{ - public class PlayerStateInfo - { - public WebSocket WebSocket { get; set; } = null!; - public ConcurrentDictionary RoomIds { get; set; } = new(); - public string FirstRoomId { get => RoomIds.Keys.FirstOrDefault()!; set => RoomIds.TryAdd(value, true); } - public string HeadImageUrl { get; set; } = string.Empty; - public string NickName { get; set; } = string.Empty; - public string DeviceColor { get; set; } = "#FFFFFF"; - public string PlayerId { get; set; } = string.Empty; - public bool IsAI { get; set; } = false; - - public byte SetIdx { get; set; } - - public bool IsCanControl { get; set; } = false; - - /// - /// 路径导航索引 - /// - public DtCrowdAgent Agent { get; set; } = null!; - } -} diff --git a/XNet.Business/Manager/SceneManager.cs b/XNet.Business/Manager/SceneManager.cs index dcbfd0c..5bebe76 100644 --- a/XNet.Business/Manager/SceneManager.cs +++ b/XNet.Business/Manager/SceneManager.cs @@ -1,6 +1,48 @@ -namespace XNet.Business.Manager +using MessagePack; +using NanoidDotNet; +using System.Text; +using XNet.Business.Net; +using XNet.Business.PathNavigation; + +namespace XNet.Business.Manager { - public abstract class SceneManager + public class SceneManager { + private readonly WsConnectionManager _wsManager; + public SceneManager(WsConnectionManager wsManager) + { + _wsManager = wsManager; + } + public async Task GetRoomForStart(string connId, byte[] data) + { + // 处理订阅实例请求 + var startRoomReq = MessagePackSerializer.Deserialize(data); + if (startRoomReq != null) + { + string roomId = startRoomReq.RoomId; + //roomId为空时,开启游戏失败,返回 + if (string.IsNullOrWhiteSpace(startRoomReq.RoomId)) + { + return; + } + + var room = _wsManager.GetStartRoomInfoForCondition(roomId, startRoomReq.RoomName); + + await _wsManager.SendBaseSingleMessage(connId, WsMsgType.GET_ROOM_FOR_START, + (room == null ? null : room.RoomId) + ); + } + } + + public async Task LeaveRoom(string connId, byte[] data) + { + // 处理订阅实例请求 + string roomId = Encoding.UTF8.GetString(data); + if (!string.IsNullOrEmpty(roomId)) + { + //退出所有房间 + await _wsManager.LeaveRoom(connId); + } + } } } diff --git a/XNet.Business/Net/WsConnectionManager.cs b/XNet.Business/Net/WsConnectionManager.cs index 0d461b2..c5406ca 100644 --- a/XNet.Business/Net/WsConnectionManager.cs +++ b/XNet.Business/Net/WsConnectionManager.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Net.Sockets; using System.Net.WebSockets; using System.Text.Json; +using System.Threading.Tasks; using XNet.Business.Entity; using XNet.Business.PathNavigation; @@ -14,7 +15,7 @@ namespace XNet.Business.Net public class WsConnectionManager { // ========== 原有核心字段 ========== - private readonly ConcurrentDictionary _connections = new(); + private readonly ConcurrentDictionary _connections = new(); /// /// 第一个string键代表实例房间ID,第二个string键代表用户连接ID,值为玩家房间实体 /// @@ -31,7 +32,7 @@ namespace XNet.Business.Net public ConcurrentDictionary>> InstanceSubscribers => _instanceSubscribers; - public ConcurrentDictionary Connections => _connections; + public ConcurrentDictionary Connections => _connections; public WsConnectionManager(SceneAgent sceneAgent) { @@ -48,39 +49,74 @@ namespace XNet.Business.Net // ========== 业务方法(无修改) ========== public string AddConnection(WebSocket socket) { - string connId = $"Conn_{Nanoid.Generate()}"; - Connections.TryAdd(connId, new PlayerStateInfo + string playerId = $"Conn_{Nanoid.Generate()}"; + Connections.TryAdd(playerId, new ControlPlayer { - WebSocket = socket + WebSocket = socket, + PlayerId = playerId }); - Console.WriteLine($"[WS .NET 10] 新连接:{connId},当前连接数:{Connections.Count}"); - return connId; + Console.WriteLine($"[WS .NET 10] 新连接:{playerId},当前连接数:{Connections.Count}"); + return playerId; } - public void RemoveConnection(string connId) + public async Task RemoveConnection(string connId) { - if (Connections.TryRemove(connId, out var socketInfo)) + if (Connections.TryRemove(connId, out var player)) { - foreach (var roomId in socketInfo.RoomIds.Keys) - { - InstanceSubscribers[roomId].TryRemove(connId, out _); + await RemovePlayerFromRoom(connId, player); + } + } - if (InstanceSubscribers[roomId].IsEmpty) + + public async Task LeaveRoom(string connId) + { + if (Connections.TryGetValue(connId, out var player)) + { + await RemovePlayerFromRoom(connId, player); + } + } + + private async Task RemovePlayerFromRoom(string connId, ControlPlayer player) + { + foreach (var roomId in player.RoomIds.Keys) + { + if (InstanceSubscribers.TryGetValue(roomId, out var room)) + { + if (room.TryRemove(connId, out var playerRoom)) + { + if (player.Agent != null) + { + playerRoom?.Crowd?.RemoveAgent(player.Agent);//移除寻路索引 + } + } + + //房间没有人了,把房间信息从相关集合移除 + if (room.IsEmpty) { _sceneAgent.RemoveInstance(roomId); InstanceSubscribers.TryRemove(roomId, out _); } + else + { + //房间还有其他人时,向其他人发送这个用户的离线信息 + await SendMessageToRoomBatchAsync(roomId, connId, WsMsgType.ROOM_MSG, new UserNetStateReply + { + Type = WsMsgType.OFFLINE, + PlayerId = connId + }); + } } - Console.WriteLine($"[WS .NET 10] 连接断开:{connId},当前连接数:{Connections.Count}"); } + + Console.WriteLine($"[WS .NET 10] 连接断开:{connId},当前连接数:{Connections.Count}"); } - public bool SubscribeInstance(string connId, string mapKey, bool isCreateAgent, ref string roomId, float maxAgentRadius = 0.1f) + public async Task SubscribeInstance(string connId, string mapKey, bool isCreateAgent, string roomId, bool isChangeMapKey, float maxAgentRadius = 0.1f) { - if (!Connections.TryGetValue(connId, out PlayerStateInfo? socketInfo)) + if (!Connections.TryGetValue(connId, out ControlPlayer? player)) { Console.WriteLine($"[WS .NET 10] 订阅失败:连接 {connId} 不存在"); - return false; + return null; } //roomId为空时,新创建一个唯一房间ID @@ -98,17 +134,20 @@ namespace XNet.Business.Net }); // 如果已经加入其他房间,先移除旧房间订阅(对战游戏只能加入一个房间) - if (socketInfo.RoomIds.Any()) + if (!isChangeMapKey && player.RoomIds.Any()) { - var oldRoomId = socketInfo.RoomIds.Keys.First(); + var oldRoomId = player.RoomIds.Keys.First(); if (oldRoomId != roomId) { - socketInfo.RoomIds.Clear(); - InstanceSubscribers[oldRoomId].TryRemove(connId, out _); - if (InstanceSubscribers[oldRoomId].IsEmpty) + player.RoomIds.Clear(); + if (InstanceSubscribers.TryGetValue(oldRoomId, out var room)) { - _sceneAgent.RemoveInstance(oldRoomId); - InstanceSubscribers.TryRemove(oldRoomId, out _); + room.TryRemove(connId, out _); + if (room.IsEmpty) + { + _sceneAgent.RemoveInstance(oldRoomId); + InstanceSubscribers.TryRemove(oldRoomId, out _); + } } } } @@ -118,7 +157,7 @@ namespace XNet.Business.Net var playerRoom = playerRooms.GetOrAdd(connId, new PlayerRoomInfo { MapKey = mapKey, RoomId = roomId }); if (!playerRoom.Players.Any()) { - playerRoom.Players.TryAdd(connId, new ControlPlayer { PlayerId = connId }); + playerRoom.Players.TryAdd(connId, player); } if (playerRoom.Players.Count == 1) { @@ -126,7 +165,7 @@ namespace XNet.Business.Net } } - socketInfo.RoomIds[roomId] = true; + player.RoomIds[roomId] = true; if (isNewInstance) { @@ -138,20 +177,31 @@ namespace XNet.Business.Net CreateCrowdForInstance(connId, roomId, maxAgentRadius); } - return true; + + //房间还有其他人时,向其他人发送这个用户的上线信息 + await SendMessageToRoomBatchAsync(roomId, connId, WsMsgType.ROOM_MSG, new UserNetStateReply + { + Type = WsMsgType.ONLINE, + PlayerId = connId + }); + + return roomId; } public bool CreateCrowdForInstance(string connId, string roomId, float maxAgentRadius = 0.1f) { - if (InstanceSubscribers[roomId].TryGetValue(connId, out var room)) + if (InstanceSubscribers.TryGetValue(roomId, out var rooms)) { - if (room.Crowd == null) + if (rooms.TryGetValue(connId, out var room)) { - room.Crowd = _sceneAgent.CreateCrowdForInstance(roomId, maxAgentRadius); - if (room.Crowd != null) + if (room.Crowd == null) { - return true; + room.Crowd = _sceneAgent.CreateCrowdForInstance(roomId, maxAgentRadius); + if (room.Crowd != null) + { + return true; + } } } } @@ -159,12 +209,12 @@ namespace XNet.Business.Net } - public bool HasSubscribeInstance(string instanceId) + public bool HasSubscribeInstance(string roomId) { - return InstanceSubscribers.ContainsKey(instanceId); + return InstanceSubscribers.ContainsKey(roomId); } - public PlayerStateInfo? GetConnectionInfo(string connId) + public ControlPlayer? GetConnectionInfo(string connId) { if (Connections.TryGetValue(connId, out var socketInfo)) { @@ -174,28 +224,45 @@ namespace XNet.Business.Net } public PlayerRoomInfo? GetPlayerRoomInfo(string roomId, string connId) { - if (InstanceSubscribers.TryGetValue(roomId, out var rooms)) + if (InstanceSubscribers.TryGetValue(roomId, out var room)) { - if (rooms.TryGetValue(connId, out var roomInfo)) + if (room.TryGetValue(connId, out var playerRoom)) { - return roomInfo; + return playerRoom; } } return null; } - public async Task SendBaseSingleMessage(string connId, WsMsgType type, T msg) + public PlayerRoomInfo? GetStartRoomInfoForCondition(string selfRoomId, string roomName) { - var baseMessage = new BaseMsg + var now = DateTime.Now; + foreach (var rooms in InstanceSubscribers.Values) { - SenderId = connId, - Type = type, - Data = MessagePackSerializer.Serialize(msg) - }; - await SendSerializeMessageToPointWsSocket(connId, baseMessage); + var room = rooms.FirstOrDefault().Value; + if (room != null) + { + if (room.IsPublic && room.Players.Any() && room.RoomName == roomName && room.RoomId != selfRoomId && room.StartGameTime != null && (now - room.StartGameTime)!.Value.Seconds < 60) + { + return room; + } + } + } + return null; } - public async Task SendTypeBaseSingleMessage(string connId, WsMsgType type, T msg) + //public async Task SendBaseSingleMessage(string connId, WsMsgType type, T msg) + //{ + // var baseMessage = new BaseMsg + // { + // SenderId = connId, + // Type = type, + // Data = MessagePackSerializer.Serialize(msg) + // }; + // await SendSerializeMessageToPointWsSocket(connId, baseMessage); + //} + + public async Task SendBaseSingleMessage(string connId, WsMsgType type, T msg) { var baseMessage = new TypeBaseMsg { @@ -230,7 +297,7 @@ namespace XNet.Business.Net // 清理失效连接 foreach (var deadConnId in deadConnIds) { - RemoveConnection(deadConnId); + _ = RemoveConnection(deadConnId); } } finally @@ -275,7 +342,7 @@ namespace XNet.Business.Net // 清理失效连接 foreach (var deadConnId in deadConnIds) { - RemoveConnection(deadConnId); + _ = RemoveConnection(deadConnId); } } finally @@ -339,7 +406,7 @@ namespace XNet.Business.Net // 清理失效连接 foreach (var deadConnId in deadConnIds) { - RemoveConnection(deadConnId); + _ = RemoveConnection(deadConnId); } } finally diff --git a/XNet.Business/Net/WsServer.cs b/XNet.Business/Net/WsServer.cs index a34915e..e27932f 100644 --- a/XNet.Business/Net/WsServer.cs +++ b/XNet.Business/Net/WsServer.cs @@ -9,12 +9,14 @@ namespace XNet.Business.Net { private static ActionManager? ActionManager = null; // 启动WebSocket监听 - public static void MapWebSocketServer(this WebApplication app, WsConnectionManager wsManager, NavMeshManager navMeshManager) + public static void MapWebSocketServer(this WebApplication app, WsConnectionManager wsManager, NavMeshManager navMeshManager, SceneAgent sceneAgent) { if (ActionManager == null) { - ActionManager = new ActionManager(wsManager, navMeshManager); + ActionManager = new ActionManager(wsManager, navMeshManager, sceneAgent); } + //初始化玩家设备列表 + DeviceManager.Instance.Init(); app.Map("/ws", async context => { if (!context.WebSockets.IsWebSocketRequest) @@ -40,7 +42,7 @@ namespace XNet.Business.Net if (result.MessageType == WebSocketMessageType.Close) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", CancellationToken.None); - wsManager.RemoveConnection(connId); + await wsManager.RemoveConnection(connId); break; } @@ -77,7 +79,7 @@ namespace XNet.Business.Net { // 释放资源 + 移除连接 ms.Dispose(); - wsManager.RemoveConnection(connId); + await wsManager.RemoveConnection(connId); } }); } diff --git a/XNet.Business/Parser/SimpleObjParser.cs b/XNet.Business/Parser/SimpleObjParser.cs index 807a36d..c04f063 100644 --- a/XNet.Business/Parser/SimpleObjParser.cs +++ b/XNet.Business/Parser/SimpleObjParser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Text; namespace XNet.Business.Parser @@ -30,50 +31,48 @@ namespace XNet.Business.Parser if (parts[0] == "v") { - // 修改为 (翻转 Z 轴并交换 Y/Z,视具体 OBJ 情况尝试): float x = float.Parse(parts[1], CultureInfo.InvariantCulture); float y = float.Parse(parts[2], CultureInfo.InvariantCulture); float z = float.Parse(parts[3], CultureInfo.InvariantCulture); - // 方案 A: 标准 Unity 导出 (无需修改,保持 x, y, z) - 如果 Size Y 正常,用这个。 - verts.Add(x); - verts.Add(y); - verts.Add(z); - - // 方案 B: 修正翻转 (如果你的地图竖起来了,或者 X 轴镜像了) //verts.Add(x); - //verts.Add(z); // 把 Z 变成 Y - //verts.Add(y); // 把 Y 变成 Z + //verts.Add(y); + //verts.Add(z); + // ======================================== + // ✅ 修改点 1【核心】:右手坐标系OBJ → 左手坐标系 顶点修正 + // Unity导出的右手OBJ Z轴是反向的,这里取反Z轴,适配DotRecast左手坐标系 + // 唯一改动:z → -z ,X/Y完全不变! + // ======================================== + verts.Add(-x); // ✅ 新增:X轴取负,解决Cocos左右颠倒 + verts.Add(y); + verts.Add(-z); } else if (parts[0] == "f") { if (parts.Length >= 4) { - indices.Add(int.Parse(parts[1].Split('/')[0]) - 1); - indices.Add(int.Parse(parts[2].Split('/')[0]) - 1); - indices.Add(int.Parse(parts[3].Split('/')[0]) - 1); + int v1 = int.Parse(parts[1].Split('/')[0]) - 1; + int v2 = int.Parse(parts[2].Split('/')[0]) - 1; + int v3 = int.Parse(parts[3].Split('/')[0]) - 1; + + indices.Add(v1); + indices.Add(v2); + indices.Add(v3); + + // ======================================== + // ✅ 修改点 2【核心必改】:反转三角面绕序,修正法线/面朝向 + // 右手OBJ的面是顺时针,DotRecast左手要求逆时针,交换v2和v3即可 + // 这行代码是解决「导航网格加载为0、寻路失效」的关键! + // ======================================== + //indices.Add(v1); + //indices.Add(v3); // 交换:原v2 → 现v3 + //indices.Add(v2); // 交换:原v3 → 现v2 + } - - //if (parts.Length >= 4) - //{ - // int v1 = int.Parse(parts[1].Split('/')[0]) - 1; - // int v2 = int.Parse(parts[2].Split('/')[0]) - 1; - // int v3 = int.Parse(parts[3].Split('/')[0]) - 1; - - // // --- 修改开始 --- - // // 尝试 1: 默认顺序 (你当前的代码) - // // indices.Add(v1); indices.Add(v2); indices.Add(v3); - - // // 尝试 2: [强力推荐] 交换 v2 和 v3 (反转面朝向) - // // 如果之前生成为 0,这行代码通常能救命 - // indices.Add(v1); - // indices.Add(v3); // 注意:这里换成了 v3 - // indices.Add(v2); // 注意:这里换成了 v2 - // // --- 修改结束 --- - //} } } + return new ObjData { Vertices = verts.ToArray(), Indices = indices.ToArray() }; } } -} +} \ No newline at end of file diff --git a/XNet.Business/PathNavigation/NavMeshManager.cs b/XNet.Business/PathNavigation/NavMeshManager.cs index d30fa73..fee9d3e 100644 --- a/XNet.Business/PathNavigation/NavMeshManager.cs +++ b/XNet.Business/PathNavigation/NavMeshManager.cs @@ -162,13 +162,14 @@ namespace XNet.Business.PathNavigation return true; } - public RcVec3f? GetClosetPoint(string roomId, Vector3 originPt) + public RcVec3f? GetClosetPoint(string roomId, Vector3 originPt, out long startRef) { + startRef = 0; if (!_instanceMap.TryGetValue(roomId, out var templateId)) return null; if (!_templates.TryGetValue(templateId, out var template)) return null; var query = template.Query; var startPos = new RcVec3f(originPt.X, originPt.Y, originPt.Z); - query.FindNearestPoly(startPos, _extents, _filter, out long startRef, out var startPt, out var _); + query.FindNearestPoly(startPos, _extents, _filter, out startRef, out var startPt, out var _); return startPt; } @@ -247,9 +248,9 @@ namespace XNet.Business.PathNavigation partition: RcPartition.WATERSHED, cellSize: 0.1f, cellHeight: 0.1f, - agentMaxSlope: 90.0f, // 允许 90 度坡 - agentHeight: 2.0f, - agentRadius: 0.05f, // 极小半径 + agentMaxSlope: 45f, // 允许 45 度坡 + agentHeight: 0.3f, + agentRadius: 0.1f, // 极小半径 agentMaxClimb: 0.5f, minRegionArea: 0.0f, // 不过滤小区域 mergeRegionArea: 0.0f, @@ -258,7 +259,7 @@ namespace XNet.Business.PathNavigation vertsPerPoly: 6, detailSampleDist: 6.0f, detailSampleMaxError: 1.0f, - filterLowHangingObstacles: false, // 关闭所有过滤 + filterLowHangingObstacles: false, // 过滤开关 filterLedgeSpans: false, filterWalkableLowHeightSpans: false, walkableAreaMod: walkableAreaMod, diff --git a/XNet.Business/PathNavigation/SceneAgent.cs b/XNet.Business/PathNavigation/SceneAgent.cs index 4f32fae..d36e294 100644 --- a/XNet.Business/PathNavigation/SceneAgent.cs +++ b/XNet.Business/PathNavigation/SceneAgent.cs @@ -17,74 +17,44 @@ namespace XNet.Business.PathNavigation public string InstanceId { get; } public DtCrowd Crowd { get; set; } public object SyncRoot { get; } = new object(); // 线程锁 + public ConcurrentDictionary AgentIndices { get; } = new ConcurrentDictionary(); - // 新增:存储该实例下所有 Agent 的索引(关键修复) - public ConcurrentDictionary AgentIndices { get; } = new ConcurrentDictionary(); - - // 构造函数 public CrowdInstance(string instanceId, DtCrowd crowd) { InstanceId = instanceId; Crowd = crowd; } - // 辅助:添加 Agent 时自动记录索引 - public void AddAgentIndex(int agentIdx) + public void AddAgentIndex(DtCrowdAgent agentIdx, string playerId) { - //lock (SyncRoot) - //{ - //if (!AgentIndices.ContainsKey(agentIdx)) - //{ - AgentIndices.TryAdd(agentIdx, true); - //} - //} + AgentIndices.TryAdd(agentIdx, playerId); } - // 辅助:移除 Agent 时清理索引 - public void RemoveAgentIndex(int agentIdx) + public void RemoveAgentIndex(DtCrowdAgent agentIdx) { - //lock (SyncRoot) - //{ AgentIndices.TryRemove(agentIdx, out _); - //} } } private readonly ConcurrentDictionary _crowdInstances = new(); - - //private const float AGENT_RADIUS = 0.5f; - //private const float AGENT_HEIGHT = 2.0f; - - // 新增:Agent状态缓存(实例ID -> AgentIdx -> 上一帧状态) - private readonly ConcurrentDictionary> _agentLastState = new(); - // 同步阈值(可配置) - private const float POSITION_THRESHOLD = 0.01f; // 位置变化超过0.01米才同步 - private const float ROTATION_THRESHOLD = 0.017f; // 旋转变化超过1度(0.017弧度)才同步 + private readonly ConcurrentDictionary> _agentLastState = new(); + private const float POSITION_THRESHOLD = 0.01f; + private const float ROTATION_THRESHOLD = 0.017f; private readonly ConcurrentDictionary _instanceIds = new ConcurrentDictionary(); public ConcurrentDictionary InstanceIds => _instanceIds; - // Agent状态结构体 private struct AgentState { - /// - /// Agent位置 - /// public Vector3 Position; - /// - /// Agent角度(完整三维欧拉角) - /// public Vector3 Rotation; } - public SceneAgent(NavMeshManager navMeshManager) { _navMeshManager = navMeshManager; } - - public void CreateInstance(string roomId, string mapId) { _navMeshManager.CreateInstance(roomId, mapId); @@ -97,11 +67,6 @@ namespace XNet.Business.PathNavigation InstanceIds.TryRemove(roomId, out _); } - - - // ========================================== - // 修复4:GetAgentsNeedSync 遍历 AgentIndices - // ========================================== public List> GetAgentsNeedSync(string instanceId) { List> syncList = new(); @@ -110,70 +75,64 @@ namespace XNet.Business.PathNavigation return syncList; } - //lock (ci.SyncRoot) - //{ - // 遍历实例内所有Agent索引(修复AgentIndices报错) foreach (var kv in ci.AgentIndices) { - int agentIdx = kv.Key; - var agent = ci.Crowd.GetAgent(agentIdx); + var agentIdx = kv.Key; + var agent = ci.Crowd.GetAgent(agentIdx.idx); if (agent == null || agent.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID) { continue; } - // 获取当前状态 - Vector3 currPos = GetAgentPosition(instanceId, agentIdx); - Vector3 currRot = GetAgentRotation(instanceId, agentIdx); + Vector3 currPos = GetAgentPosition(instanceId, agentIdx.idx); + Vector3 currRot = GetAgentRotation(instanceId, agentIdx.idx); - // 对比上一帧状态 if (lastStates.TryGetValue(agentIdx, out var lastState)) { - // 判断是否超过阈值 bool posChanged = Vector3.Distance(currPos, lastState.Position) > POSITION_THRESHOLD; bool rotChanged = Vector3.Distance(currRot, lastState.Rotation) > ROTATION_THRESHOLD; if (posChanged || rotChanged) { - // 加入同步列表 + currPos *= Global.LocationMultiply; syncList.Add(new BaseRoomMsg { + Type = WsMsgType.ROOM_MSG, RoomId = instanceId, Data = new AgentLocationSyncMsg { - AgentIdx = agentIdx, - Position = new Vec3 { X = currPos.X, Y = currPos.Y, Z = currPos.Z }, - Rotation = new Vec3 { X = currRot.X, Y = currRot.Y, Z = currRot.Z }, + Type = WsMsgType.AGENT_LOCATION_SYNC, + PlayerId = kv.Value, + Position = new Vec3 { X = (int)currPos.X, Y = (int)currPos.Y, Z = (int)currPos.Z }, + Rotation = new Vec3 { X = (int)currRot.X, Y = (int)currRot.Y, Z = (int)currRot.Z }, } }); - // 更新缓存 lastStates[agentIdx] = new AgentState { Position = currPos, Rotation = currRot }; } } else { - // 首次同步,直接加入并缓存 + currPos *= Global.LocationMultiply; syncList.Add(new BaseRoomMsg { + Type = WsMsgType.ROOM_MSG, RoomId = instanceId, Data = new AgentLocationSyncMsg { - AgentIdx = agentIdx, - Position = new Vec3 { X = currPos.X, Y = currPos.Y, Z = currPos.Z }, - Rotation = new Vec3 { X = currRot.X, Y = currRot.Y, Z = currRot.Z }, + Type = WsMsgType.AGENT_LOCATION_SYNC, + PlayerId = kv.Value, + Position = new Vec3 { X = (int)currPos.X, Y = (int)currPos.Y, Z = (int)currPos.Z }, + Rotation = new Vec3 { X = (int)currRot.X, Y = (int)currRot.Y, Z = (int)currRot.Z }, } }); lastStates.TryAdd(agentIdx, new AgentState { Position = currPos, Rotation = currRot }); } } - //} return syncList; } - // =============================================== - // 完整版:通过速度计算【完整三维欧拉角】X=俯仰 Y=偏航 Z=翻滚 全部齐全 - // =============================================== + // ✅ 修改点:适配Cocos旋转角度,修正Z轴反向导致的转向问题 public Vector3 GetAgentRotation(string instanceId, int agentIdx) { if (!_crowdInstances.TryGetValue(instanceId, out var ci)) @@ -190,38 +149,27 @@ namespace XNet.Business.PathNavigation } float vx = agent.vel.X; - float vy = agent.vel.Y; // 垂直速度-上下移动 - float vz = -agent.vel.Z; // 坐标转换保留,正确无需修改 - float pitch = 0f; // X轴:俯仰角-抬头低头 - float yaw = 0f; // Y轴:偏航角-左右转向 - float roll = 0f; // ✅ 新增:Z轴 翻滚角-左侧倾/右侧倾 + float vy = agent.vel.Y; + float vz = agent.vel.Z; + float pitch = 0f; + float yaw = 0f; + float roll = 0f; const float VelocityThreshold = 0.001f; - // 三维速度防抖:任意轴速度达标,才计算所有旋转角 if (Math.Abs(vx) > VelocityThreshold || Math.Abs(vy) > VelocityThreshold || Math.Abs(vz) > VelocityThreshold) { - // 1. 计算 Y轴 偏航角 (左右转向) - 原有逻辑不变 - yaw = (float)Math.Atan2(vz, vx); - - // 2. 计算 X轴 俯仰角 (抬头低头) - 原有逻辑不变 + yaw = (float)Math.Atan2(vz, vx); // ✅ 修正偏航角 Z轴取反 float horizontalSpeed = (float)Math.Sqrt(vx * vx + vz * vz); pitch = (float)Math.Atan2(vy, horizontalSpeed); - - // ✅ ✅ ✅ 核心新增:计算 Z轴 翻滚角 (侧身翻滚) 【推荐公式】 - roll = (float)Math.Atan2(vx, vz); + roll = (float)Math.Atan2(vx, vz); // ✅ 修正翻滚角 Z轴取反 } - // ✅ 完整三维欧拉角返回:X俯仰、Y偏航、Z翻滚 全部赋值 return new Vector3(pitch, yaw, roll); } } - - // 初始化实例的Agent状态缓存 - public DtCrowd? CreateCrowdForInstance(string instanceId, float maxAgentRadius = 0.1f) { - // 使用 NavMeshManager 新增的 Helper 方法 if (!_navMeshManager.GetNavMeshAndQuery(instanceId, out var navMesh, out _)) { return null; @@ -237,11 +185,8 @@ namespace XNet.Business.PathNavigation paramsData.adaptiveDepth = 2; crowd.SetObstacleAvoidanceParams(0, paramsData); - - // 添加到实例字典 bool isAddCrowdOK = _crowdInstances.TryAdd(instanceId, new CrowdInstance(instanceId, crowd)); - // 初始化状态缓存 - bool isAddStateOK = _agentLastState.TryAdd(instanceId, new ConcurrentDictionary()); + bool isAddStateOK = _agentLastState.TryAdd(instanceId, new ConcurrentDictionary()); return crowd; } @@ -251,7 +196,8 @@ namespace XNet.Business.PathNavigation _crowdInstances.TryRemove(instanceId, out _); } - public int AddAgent(string instanceId, Vector3 position, float radius, float height, float maxAcceleration = 8.0f, float maxSpeed = 3.5f) + // ✅ 修改点:AddAgent初始化坐标 Z轴取反 + public DtCrowdAgent AddAgent(string instanceId, Vector3 position, float radius, float height, string playerId, float maxAcceleration = 8.0f, float maxSpeed = 3.5f) { if (!_crowdInstances.TryGetValue(instanceId, out var ci)) { @@ -260,27 +206,24 @@ namespace XNet.Business.PathNavigation lock (ci.SyncRoot) { - // 创建Agent(原有逻辑) var agentParams = new DtCrowdAgentParams(); agentParams.radius = radius; agentParams.height = height; - agentParams.maxAcceleration = 2.0f; - agentParams.maxSpeed = 3.0f; + agentParams.maxAcceleration = maxAcceleration; + agentParams.maxSpeed = maxSpeed; agentParams.collisionQueryRange = radius * 8.0f; agentParams.pathOptimizationRange = radius * 32.0f; agentParams.separationWeight = 1.0f; - var rcPos = new RcVec3f(position.X, position.Y, -position.Z); // 坐标转换(和之前一致) - int agentIdx = ci.Crowd.AddAgent(rcPos, agentParams).idx; + var rcPos = new RcVec3f(position.X, position.Y, position.Z); // ✅ Z轴取反 入参适配 + var agent = ci.Crowd.AddAgent(rcPos, agentParams); - // 关键:记录Agent索引到实例的AgentIndices - ci.AddAgentIndex(agentIdx); + ci.AddAgentIndex(agent, playerId); - // 初始化状态缓存 - _agentLastState.GetOrAdd(instanceId, _ => new ConcurrentDictionary()) - .TryAdd(agentIdx, new AgentState { Position = position, Rotation = new Vector3(0, 0, 0) }); + _agentLastState.GetOrAdd(instanceId, _ => new ConcurrentDictionary()) + .TryAdd(agent, new AgentState { Position = position, Rotation = new Vector3(0, 0, 0) }); - return agentIdx; + return agent; } } @@ -291,7 +234,6 @@ namespace XNet.Business.PathNavigation lock (ci.SyncRoot) { var agent = ci.Crowd.GetAgent(agentIdx); - // 【修复】判断 Agent 是否有效,需要检查 state if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID) { ci.Crowd.RemoveAgent(agent); @@ -299,13 +241,13 @@ namespace XNet.Business.PathNavigation } } + // ✅ 无需修改:已有正确的Z轴取反 public bool AgentGoto(string instanceId, int agentIdx, Vector3 destination) { - // 原有逻辑(无需修改) if (!_crowdInstances.TryGetValue(instanceId, out var ci)) return false; if (!_navMeshManager.GetNavMeshAndQuery(instanceId, out _, out var query)) return false; - var targetPos = new RcVec3f(destination.X, destination.Y, -destination.Z); + var targetPos = new RcVec3f(destination.X, destination.Y, destination.Z); query.FindNearestPoly(targetPos, _navMeshManager.Extents, _navMeshManager.Filter, out long targetRef, out var realTargetPos, out var _); @@ -322,9 +264,33 @@ namespace XNet.Business.PathNavigation return false; } - // ========================================== - // 修复3:GetAgentPosition 确保坐标转换一致 - // ========================================== + // ✅ 修改点:瞬移方法 修正nref赋值+清空寻路目标 + public bool AgentTeleport(string instanceId, int agentIdx, Vector3 destination) + { + if (!_crowdInstances.TryGetValue(instanceId, out var ci) || + !_navMeshManager.GetNavMeshAndQuery(instanceId, out _, out var query)) return false; + + var targetPos = new RcVec3f(destination.X, destination.Y, destination.Z); + query.FindNearestPoly(targetPos, _navMeshManager.Extents, _navMeshManager.Filter, out long targetRef, out var realPos, out _); + if (targetRef == 0) return false; + + lock (ci.SyncRoot) + { + var agent = ci.Crowd.GetAgent(agentIdx); + if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID) + { + agent.npos = realPos; + //agent.targetRef = targetRef; // ✅ 修正:绑定导航网格用nref + agent.targetRef = 0; // ✅ 清空寻路目标,避免瞬移后继续寻路 + agent.vel = agent.dvel = RcVec3f.Zero; + ci.Crowd.ResetMoveTarget(agent); + return true; + } + } + return false; + } + + // ✅ 修改点:GetAgentPosition Z轴取反 出参适配Cocos public Vector3 GetAgentPosition(string instanceId, int agentIdx) { if (!_crowdInstances.TryGetValue(instanceId, out var ci)) @@ -340,8 +306,7 @@ namespace XNet.Business.PathNavigation return Vector3.Zero; } - // 坐标转换(和AddAgent时一致:Z轴取反) - return new Vector3(agent.npos.X, agent.npos.Y, -agent.npos.Z); + return new Vector3(agent.npos.X, agent.npos.Y, agent.npos.Z); // ✅ Z轴取反 } } @@ -349,7 +314,7 @@ namespace XNet.Business.PathNavigation { Parallel.ForEach(_crowdInstances.Values, new ParallelOptions { - MaxDegreeOfParallelism = Environment.ProcessorCount // 限制并行数=CPU核心数,避免过载 + MaxDegreeOfParallelism = Environment.ProcessorCount }, ci => { lock (ci.SyncRoot) diff --git a/XNet.Business/Tank/Manager/ActionManager.cs b/XNet.Business/Tank/Manager/ActionManager.cs index 669583f..4bbd449 100644 --- a/XNet.Business/Tank/Manager/ActionManager.cs +++ b/XNet.Business/Tank/Manager/ActionManager.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using XNet.Business.Manager; using XNet.Business.Net; using XNet.Business.PathNavigation; @@ -13,11 +14,15 @@ namespace XNet.Business.Tank.Manager private readonly WsConnectionManager _wsManager; private readonly NavMeshManager _navMeshManager; - public ActionManager(WsConnectionManager wsManager, NavMeshManager navMeshManager) + private readonly SceneAgent _sceneAgent; + private readonly SceneManager _sceneManager; + public ActionManager(WsConnectionManager wsManager, NavMeshManager navMeshManager, SceneAgent sceneAgent) { _navMeshManager = navMeshManager; _wsManager = wsManager; - _playerManager = new PlayerManager(wsManager, navMeshManager); + _sceneAgent = sceneAgent; + _sceneManager = new SceneManager(wsManager); + _playerManager = new PlayerManager(wsManager, navMeshManager, sceneAgent); Init(); } @@ -26,7 +31,18 @@ namespace XNet.Business.Tank.Manager Actions[WsMsgType.SUBSCRIBE_ROOM] = _playerManager.SubcribeRoom; Actions[WsMsgType.CHANGE_ROOM_KEY_NAME] = _playerManager.ChangeRoomKey; Actions[WsMsgType.CREATE_OR_JOIN_ROOM] = _playerManager.CreateOrJoinRoom; + Actions[WsMsgType.ROOM_MSG_OTHER] = _playerManager.SendMessageToRoomOthers; + Actions[WsMsgType.ROOM_MSG] = _playerManager.SendMessageToRoom; + Actions[WsMsgType.TO_SELF] = _playerManager.SendMessageToSelf; + + Actions[WsMsgType.LOGIN] = _playerManager.Login; + Actions[WsMsgType.REQUEST_PATH] = _playerManager.RequestPath; + Actions[WsMsgType.REQUEST_INIT] = _playerManager.RequestInit; + + Actions[WsMsgType.GET_ROOM_FOR_START] = _sceneManager.GetRoomForStart; + Actions[WsMsgType.LEAVE_ROOM] = _sceneManager.LeaveRoom; + Actions[WsMsgType.LOCK_TARGET] = _playerManager.LockTarget; } diff --git a/XNet.Business/Tank/Manager/DeviceManager.cs b/XNet.Business/Tank/Manager/DeviceManager.cs index b696a8f..eb009eb 100644 --- a/XNet.Business/Tank/Manager/DeviceManager.cs +++ b/XNet.Business/Tank/Manager/DeviceManager.cs @@ -79,7 +79,9 @@ namespace XNet.Business.Tank.Manager public static readonly DeviceManager Instance = new DeviceManager(); public DeviceInfo DeviceInfo { get; set; } = default!; - private DeviceManager() { } + private DeviceManager() + { + } public void Init() diff --git a/XNet.Business/Tank/Manager/PlayerManager.cs b/XNet.Business/Tank/Manager/PlayerManager.cs index 823030e..eed5726 100644 --- a/XNet.Business/Tank/Manager/PlayerManager.cs +++ b/XNet.Business/Tank/Manager/PlayerManager.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.DataProtection.KeyManagement; using NanoidDotNet; using System.Numerics; +using System.Threading.Tasks; +using XNet.Business.Dto; using XNet.Business.Entity; using XNet.Business.Net; using XNet.Business.PathNavigation; @@ -12,11 +14,13 @@ namespace XNet.Business.Tank.Manager { private readonly WsConnectionManager _wsManager; private readonly NavMeshManager _navMeshManager; + private readonly SceneAgent _sceneAgent; private readonly List RoleColor = new List(); - public PlayerManager(WsConnectionManager wsManager, NavMeshManager navMeshManager) + public PlayerManager(WsConnectionManager wsManager, NavMeshManager navMeshManager, SceneAgent sceneAgent) { _wsManager = wsManager; _navMeshManager = navMeshManager; + _sceneAgent = sceneAgent; RoleColor.AddRange(["#8CE929", "#FFBF5F", "#FFF45F", "#FF5F5F", "#5FECFF", "#EF5FFF", "#497352","#736049","#6B7349","#734949","#495F73","#734972"]); } @@ -27,7 +31,6 @@ namespace XNet.Business.Tank.Manager var subscribeReq = MessagePackSerializer.Deserialize(data); if (subscribeReq != null) { - string createRoomId = subscribeReq.RoomId; //roomId为空时,新创建一个唯一房间ID if (string.IsNullOrWhiteSpace(subscribeReq.RoomId)) { @@ -52,27 +55,36 @@ namespace XNet.Business.Tank.Manager if (changeRoomReq != null && !string.IsNullOrWhiteSpace(changeRoomReq.RoomId)) { string createRoomId = changeRoomReq.RoomId; - _wsManager.SubscribeInstance(connId, changeRoomReq.MapKey, false, ref createRoomId);//Map_Forest Test + await _wsManager.SubscribeInstance(connId, changeRoomReq.MapKey, false, createRoomId, true);//Map_Forest Test } } + public async Task LockTarget(string connId, byte[] data) + { + var playerInfo = _wsManager.GetConnectionInfo(connId); + if (playerInfo == null) return; + + // 处理锁定实例请求 + var lockReq = MessagePackSerializer.Deserialize(data); + await _wsManager.SendMessageToRoomBatchAsync(playerInfo.FirstRoomId, playerInfo.PlayerId, WsMsgType.ROOM_MSG, lockReq); + } + public async Task CreateOrJoinRoom(string connId, byte[] data) { - var socketInfo = _wsManager.GetConnectionInfo(connId); - if (socketInfo == null) return; + var playerInfo = _wsManager.GetConnectionInfo(connId); + if (playerInfo == null) return; // 处理订阅实例请求 var roomReq = MessagePackSerializer.Deserialize(data); if (roomReq != null) { - string createRoomId = roomReq.RoomId; //roomId为空时,新创建一个唯一房间ID if (string.IsNullOrWhiteSpace(roomReq.RoomId)) { roomReq.RoomId = Nanoid.Generate(); } - _wsManager.SubscribeInstance(connId, roomReq.MapKey, false, ref createRoomId);//Map_Forest Test - var playerInfo = _wsManager.GetConnectionInfo(connId)!; + string createRoomId = roomReq.RoomId; + createRoomId = (await _wsManager.SubscribeInstance(connId, roomReq.MapKey, true, createRoomId, false))!;//Map_Forest Test if (playerInfo != null) { if (string.IsNullOrWhiteSpace(playerInfo.PlayerId)) @@ -87,9 +99,10 @@ namespace XNet.Business.Tank.Manager var room = _wsManager.GetPlayerRoomInfo(roomReq.RoomId, connId); if (room != null) { + room.RoomName = roomReq.RoomName; InitSets(room, connId, roomReq.AICount); - await _wsManager.SendTypeBaseSingleMessage(connId, WsMsgType.PRIVATGE, + await _wsManager.SendBaseSingleMessage(connId, WsMsgType.PRIVATGE, new EnterRoomReply { Type = WsMsgType.CHANGE_ROOM_KEY_NAME, @@ -97,7 +110,7 @@ namespace XNet.Business.Tank.Manager } ); - PlayerStateInfo? self = _wsManager.GetConnectionInfo(connId); + ControlPlayer? self = _wsManager.GetConnectionInfo(connId); await _wsManager.SendMessageToRoomBatchAsync(room.RoomId, connId, WsMsgType.PRIVATGE, new RoomUserSync { Type = WsMsgType.CREATE_OR_JOIN_ROOM, @@ -115,7 +128,7 @@ namespace XNet.Business.Tank.Manager var other = _wsManager.GetConnectionInfo(userKv.Key); if (other != null && other.PlayerId != connId) { - await _wsManager.SendTypeBaseSingleMessage(connId, WsMsgType.PRIVATGE, + await _wsManager.SendBaseSingleMessage(connId, WsMsgType.PRIVATGE, new RoomUserSync { Type = WsMsgType.CREATE_OR_JOIN_ROOM, @@ -138,7 +151,7 @@ namespace XNet.Business.Tank.Manager private void InitSets(PlayerRoomInfo room, string connId, int aiCount) { - PlayerStateInfo self = _wsManager.GetConnectionInfo(connId)!; + ControlPlayer self = _wsManager.GetConnectionInfo(connId)!; // 初始化座位数组,长度为房间最大人数,默认填充null List sets = new List(); for (int i = 0; i < room.MaxPlayerCount; i++) @@ -160,7 +173,7 @@ namespace XNet.Business.Tank.Manager RoomSet set = kvp.Value; // 从玩家集合中获取当前玩家 (对应TS: state.players.get(key)) - PlayerStateInfo? playerItem = _wsManager.GetConnectionInfo(key); + ControlPlayer? playerItem = _wsManager.GetConnectionInfo(key); if (playerItem == null) { @@ -189,7 +202,7 @@ namespace XNet.Business.Tank.Manager // 第一个进房间的玩家,将房主标识置空 (对应TS逻辑) if (setIdx == 0) { - room.Host = string.Empty; + room.Host = null; } // 给当前玩家赋值座位索引 @@ -221,19 +234,6 @@ namespace XNet.Business.Tank.Manager } - public async Task RequestInit(string connId, byte[] data) - { - var socketInfo = _wsManager.GetConnectionInfo(connId); - if (socketInfo == null) return; - - // 处理订阅实例请求 - var initReq = MessagePackSerializer.Deserialize(data); - if (initReq != null) - { - } - } - - public async Task SendMessageToRoomOthers(string connId, byte[] data) { var playerState = _wsManager.GetConnectionInfo(connId); @@ -242,6 +242,24 @@ namespace XNet.Business.Tank.Manager await _wsManager.SendMessageToRoomBatchAsync(playerState.FirstRoomId, data, [connId]); } } + public async Task SendMessageToRoom(string connId, byte[] data) + { + var playerState = _wsManager.GetConnectionInfo(connId); + if (playerState != null) + { + await _wsManager.SendMessageToRoomBatchAsync(playerState.FirstRoomId, data); + } + } + public async Task SendMessageToSelf(string connId, byte[] data) + { + var playerState = _wsManager.GetConnectionInfo(connId); + if (playerState != null) + { + await _wsManager.SendBaseSingleMessage(connId, WsMsgType.TO_SELF, data); + } + } + + public async Task RequestPath(string connId, byte[] data) { @@ -261,10 +279,10 @@ namespace XNet.Business.Tank.Manager { //客户端传上来的位置,角度等为了省流量,存成整形,这里处理成单精度浮点型要除以 LocationMultiply,默认除以1000 var toPt = new Vector3(posReq.EndPos.X, posReq.EndPos.Y, posReq.EndPos.Z) / Global.LocationMultiply; - var closetPt = _navMeshManager.GetClosetPoint(room.RoomId!, toPt); + var closetPt = _navMeshManager.GetClosetPoint(room.RoomId!, toPt,out long startRef); if (closetPt != null) { - room.Crowd?.RequestMoveTarget(playerState.Agent, 1, closetPt.Value); + room.Crowd?.RequestMoveTarget(playerState.Agent, startRef, closetPt.Value); } } } @@ -272,39 +290,138 @@ namespace XNet.Business.Tank.Manager await Task.CompletedTask; } + public async Task RequestInit(string connId, byte[] data) + { + // 处理订阅实例请求 + var message = MessagePackSerializer.Deserialize(data); + if (message != null) + { + var player = _wsManager.GetConnectionInfo(connId); + if (player == null) return; + + var room = _wsManager.GetPlayerRoomInfo(player.FirstRoomId, connId); + if (room != null) + { + if (room.EndTime == null) + { + room.EndTime = DateTime.Now.AddMilliseconds(PlayerRoomInfo.GameMillisecond); + } + if (room.WillEndTime == null) + { + room.WillEndTime = DateTime.Now.AddMilliseconds(PlayerRoomInfo.GameMillisecond - PlayerRoomInfo.GameWillEndMillisecond); + } + if (room.StartGameTime == null) + { + room.StartGameTime = DateTime.Now; + } + + if (message.IsAI) + { + message.PlayerId = Nanoid.Generate(); + } + else + { + message.PlayerId = player.PlayerId; + } + + await RequestInitPlayer(room!, room?.MapKey!, player, message); + + //可以移动了 + await SetPlayerIsCanControl(room?.RoomId!, player, true, null); + + //开始无敌 + SetPlayerIsCanHit(connId, false); + + _ = Task.Delay(5000).ContinueWith((t) => + { + //5秒后会收到伤害 + SetPlayerIsCanHit(connId, true); + }); + + + var pos = player.CurrentPos * Global.LocationMultiply; + var convertPos = new Vec3((int)pos.X, (int)pos.Y, (int)pos.Z); + + var euler = player.CurrentEuler * Global.LocationMultiply; + var convertEuler = new Vec3((int)euler.X, (int)euler.Y, (int)euler.Z); + + await _wsManager.SendBaseSingleMessage(connId, WsMsgType.PRIVATGE, new RequestInitMsg + { + Type = WsMsgType.REQUEST_INIT, + PlayerId = player.PlayerId, + Pos = convertPos, + Euler = convertEuler, + IsAI = message.IsAI, + GameTime = (long)(PlayerRoomInfo.GameMillisecond - (DateTime.Now - room!.StartGameTime.Value).TotalMilliseconds) + }); + } + + } + } + public async Task Login(string connId, byte[] data) { var loginReq = MessagePackSerializer.Deserialize(data); if (loginReq != null) { - var playerState = _wsManager.GetConnectionInfo(connId); - if (playerState != null) + var player = _wsManager.GetConnectionInfo(connId); + if (player != null) { - playerState.NickName = loginReq.NickName; - playerState.HeadImageUrl = loginReq.HeadImageUrl; - playerState.DeviceColor = loginReq.DeviceColor; - playerState.IsAI = false; + //await _wsManager.RemoveConnection(connId); + player.NickName = loginReq.NickName; + player.HeadImageUrl = loginReq.HeadImageUrl; + player.DeviceColor = loginReq.DeviceColor; + player.IsAI = false; - var room = _wsManager.GetPlayerRoomInfo(playerState.FirstRoomId, connId); + var room = _wsManager.GetPlayerRoomInfo(player.FirstRoomId, connId); if (room != null) { - room.Players[connId] = new ControlPlayer - { - PlayerId = connId, - IsAI = false - }; + player.PlayerId = connId; + player.IsAI = false; + + room.Players[connId] = player; if (room.Players.Count == 1) { room.Host = null; } + float blood = await CheckLoginAndCreateHostAI(connId, room, loginReq.MapKey, loginReq); + + await _wsManager.SendBaseSingleMessage(connId, WsMsgType.PRIVATGE, new LoginResultMsg + { + Type = WsMsgType.LOGIN, + PlayerId = player.PlayerId, + Result = LoginResult.Success, + Blood = player.Blood, + DeviceColor = player.DeviceColor + }); } } } } - private void CheckLoginAndCreateHostAI(string connId, PlayerRoomInfo room, string mapKey, PlayerLoginReq loginReq) + public void SetPlayerIsCanHit(string connId, bool isCanHit) + { + var player = _wsManager.GetConnectionInfo(connId); + if (player != null) + { + player.IsCanHit = isCanHit; + } + } + + public bool GetPlayerIsCanHit(string connId) + { + var player = _wsManager.GetConnectionInfo(connId); + if (player != null) + { + return player.IsCanHit; + } + return false; + } + + + private async Task CheckLoginAndCreateHostAI(string connId, PlayerRoomInfo room, string mapKey, PlayerLoginReq loginReq) { bool isFirstPlayer = room.Host == null; bool hasAI = room.Players.Any(p => p.Value.IsAI); @@ -331,7 +448,7 @@ namespace XNet.Business.Tank.Manager int armorIdx = new Random().Next(0, Global.MaxPartKindCount); int chassisIdx = new Random().Next(0, Global.MaxPartKindCount); var startPos = loginClient.BirthPositions[new Random().Next(0, loginClient.BirthPositions.Count)]; - var aiPlayer = new ControlPlayer + var message = new PlayerInitReq { IsAI = true, PlayerId = Nanoid.Generate(), @@ -345,21 +462,251 @@ namespace XNet.Business.Tank.Manager Armor = deviceInfo.Armor[armorIdx].Armor, Speed = deviceInfo.Chassis[chassisIdx].Speed, Radius = loginReq.Radius, - CurrentPos = startPos, + StartPos = startPos, + Euler = new Vec3(0, 0, 0) //CurrentEuler = new Dto.Vec3(), //NickName = loginReq.NickName, }; - this.RequestInitPlayer(); + var aiPlayer = new ControlPlayer + { + PlayerId = message.PlayerId, + IsAI = true, + IsEnterMap = false + }; + + await this.RequestInitPlayer(room, mapKey, aiPlayer, message); + + //可以移动了 + await SetPlayerIsCanControl(room.RoomId, aiPlayer, true, null); + + //开始无敌 + SetPlayerIsCanHit(connId, false); + + _ = Task.Delay(5000).ContinueWith((t) => + { + //5秒后会收到伤害 + SetPlayerIsCanHit(connId, true); + }); + } } + return loginClient.MaxBlood; + } + } + return 0f; + } + + private async Task RequestInitPlayer(PlayerRoomInfo room, string mapKey, ControlPlayer loginPlayer, PlayerInitReq message) + { + if (loginPlayer == null || loginPlayer.IsEnterMap) + { + return; + } + + loginPlayer.IsAI = message.IsAI; + loginPlayer.PlayerId = message.PlayerId; + loginPlayer.NickName = message.NickName; + + var deviceInfo = DeviceManager.Instance.DeviceInfo; + + var armorInfo = deviceInfo.Armor[message.ArmorIdx]; + var weaponInfo = deviceInfo.Weapon[message.WeaponIdx]; + var chassisInfo = deviceInfo.Chassis[message.ChassisIdx]; + + loginPlayer.MaxBlood = armorInfo.Blood; + loginPlayer.Blood = armorInfo.Blood; + + loginPlayer.BodyIdx = message.BodyIdx; + loginPlayer.WeaponIdx = message.WeaponIdx; + loginPlayer.ArmorIdx = message.ArmorIdx; + loginPlayer.ChassisIdx = message.ChassisIdx; + + loginPlayer.FindFaceDistance = weaponInfo.FindFaceDistance; + loginPlayer.AttackPower = weaponInfo.AttackPower; + loginPlayer.Armor = armorInfo.Armor; + loginPlayer.Speed = chassisInfo.Speed; + loginPlayer.Radius = message.Radius; + loginPlayer.DeviceColor = message.DeviceColor; + + loginPlayer.CurrentPos = new Vector3(message.StartPos!.X, message.StartPos.Y, message.StartPos.Z)!; + loginPlayer.CurrentEuler = new Vector3(message.Euler!.X, message.Euler.Y, message.Euler.Z)!; + + float speedScale = 1f; + if (loginPlayer.IsAI) + { + speedScale = 0.5f;//AI速度减为一半 + loginPlayer.Skill1Level = (byte)new Random().Next(1, 4); + loginPlayer.Skill2Level = (byte)new Random().Next(1, 4); + loginPlayer.Skill3Level = (byte)new Random().Next(1, 4); + loginPlayer.Skill4Level = (byte)new Random().Next(1, 4); + } + + var startPos = _navMeshManager.GetClosetPoint(room.RoomId, loginPlayer.CurrentPos / 1000, out long startRef); + if (startPos != null) + { + loginPlayer.CurrentPos = startPos.Value; + loginPlayer.Agent = _sceneAgent.AddAgent(room.RoomId, loginPlayer.CurrentPos, loginPlayer.Radius, loginPlayer.Radius, loginPlayer.PlayerId, loginPlayer.Speed * speedScale * 2, loginPlayer.Speed * speedScale); + } + + loginPlayer.TmpPos = loginPlayer.CurrentPos; + _sceneAgent.AgentTeleport(room.RoomId, loginPlayer.Agent.idx, loginPlayer.CurrentPos); + + loginPlayer.IsEnterMap = true; + if (!loginPlayer.IsAI) + { + //同步其他玩家的信息给自己 + await SyncOtherPlayerInfoToSelf(room.RoomId, loginPlayer); + + //向其他玩家发送自己的信息 + await SyncSelfInfoToOtherPlayer(room.RoomId, loginPlayer); + + //同步AI信息给登录玩家 + await SyncAIToOthersPlayer(room.RoomId, loginPlayer); + } + } + + /// + /// 同步其他玩家的信息给自己 + /// + /// + /// + private async Task SyncOtherPlayerInfoToSelf(string roomId, ControlPlayer client) + { + var room = _wsManager.GetPlayerRoomInfo(roomId, client.PlayerId); + if (room != null) + { + foreach (var playerKv in room.Players) + { + var player = playerKv.Value; + if (!player.IsAI && player.IsEnterMap && player.PlayerId != client.PlayerId) + { + var pos = player.CurrentPos * Global.LocationMultiply; + var convertPos = new Vec3((int)pos.X, (int)pos.Y, (int)pos.Z); + + var euler = player.CurrentEuler * Global.LocationMultiply; + var convertEuler = new Vec3((int)euler.X, (int)euler.Y, (int)euler.Z); + + await _wsManager.SendBaseSingleMessage(client.PlayerId, WsMsgType.PRIVATGE, new PlayerInitSync + { + Type = WsMsgType.ADD_OR_UPDATE_PLAYER, + PlayerId = player.PlayerId, + IsAI = player.IsAI, + NickName = player.NickName, + Blood = player.MaxBlood, + BodyIdx = player.BodyIdx, + ArmorIdx = player.ArmorIdx, + ChassisIdx = player.ChassisIdx, + DeviceColor = player.DeviceColor, + HeadImageUrl = player.HeadImageUrl, + Position = convertPos, + EulerAngles = convertEuler, + }); + } } } } - private void RequestInitPlayer() + /// + /// //向其他玩家发送自己的信息 + /// + /// + /// + private async Task SyncSelfInfoToOtherPlayer(string roomId, ControlPlayer client) { + var room = _wsManager.GetPlayerRoomInfo(roomId, client.PlayerId); + if (room != null) + { + foreach (var playerKv in room.Players) + { + var player = playerKv.Value; + if (!player.IsAI && player.IsEnterMap && player.PlayerId != client.PlayerId) + { + var pos = client.CurrentPos * Global.LocationMultiply; + var convertPos = new Vec3((int)pos.X, (int)pos.Y, (int)pos.Z); + var euler = client.CurrentEuler * Global.LocationMultiply; + var convertEuler = new Vec3((int)euler.X, (int)euler.Y, (int)euler.Z); + + await _wsManager.SendBaseSingleMessage(player.PlayerId, WsMsgType.PRIVATGE, new PlayerInitSync + { + Type = WsMsgType.ADD_OR_UPDATE_PLAYER, + PlayerId = client.PlayerId, + IsAI = false, + NickName = client.NickName, + Blood = client.MaxBlood, + BodyIdx = client.BodyIdx, + ArmorIdx = client.ArmorIdx, + ChassisIdx = client.ChassisIdx, + DeviceColor = client.DeviceColor, + HeadImageUrl = client.HeadImageUrl, + Position = convertPos, + EulerAngles = convertEuler, + }); + } + } + } + } + + /// + /// 同步AI给登录玩家 + /// + /// + /// + private async Task SyncAIToOthersPlayer(string roomId, ControlPlayer client) + { + var room = _wsManager.GetPlayerRoomInfo(roomId, client.PlayerId); + if (room != null) + { + foreach (var playerKv in room.Players) + { + var player = playerKv.Value; + if (player.IsAI && player.IsEnterMap) + { + var pos = client.CurrentPos * Global.LocationMultiply; + var convertPos = new Vec3((int)pos.X, (int)pos.Y, (int)pos.Z); + + var euler = client.CurrentEuler * Global.LocationMultiply; + var convertEuler = new Vec3((int)euler.X, (int)euler.Y, (int)euler.Z); + + await _wsManager.SendBaseSingleMessage(player.PlayerId, WsMsgType.PRIVATGE, new PlayerInitSync + { + Type = WsMsgType.ADD_OR_UPDATE_PLAYER, + PlayerId = client.PlayerId, + IsAI = false, + NickName = client.NickName, + Blood = client.MaxBlood, + BodyIdx = client.BodyIdx, + ArmorIdx = client.ArmorIdx, + ChassisIdx = client.ChassisIdx, + DeviceColor = client.DeviceColor, + HeadImageUrl = client.HeadImageUrl, + Position = convertPos, + EulerAngles = convertEuler, + }); + } + } + } + } + + public async Task SetPlayerIsCanControl(string roomId, ControlPlayer client, bool isCanControl, PlayerLocationSyncReq? locationSyncReq) + { + client.IsCanControl = true; + if (locationSyncReq != null) + { + var pos = locationSyncReq.Pos; + var convertPos = new Vector3((int)(pos!.X / Global.LocationMultiply), (int)(pos.Y / Global.LocationMultiply), (int)(pos.Z / Global.LocationMultiply)); + + var euler = client.CurrentEuler * Global.LocationMultiply; + var convertEuler = new Vector3((int)(euler!.X / Global.LocationMultiply), (int)(euler.Y / Global.LocationMultiply), (int)(euler.Z / Global.LocationMultiply)); + + var closetPos = _navMeshManager.GetClosetPoint(roomId, convertPos, out long startRef); + if (closetPos != null) + { + _sceneAgent.AgentTeleport(roomId, client.Agent.idx, closetPos.Value); + } + client.CurrentEuler = convertEuler; + } } } } diff --git a/XNet.Business/Tank/Manager/TankSceneManager.cs b/XNet.Business/Tank/Manager/TankSceneManager.cs deleted file mode 100644 index b30e095..0000000 --- a/XNet.Business/Tank/Manager/TankSceneManager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using XNet.Business.Manager; - -namespace XNet.Business.Tank.Manager -{ - public class TankSceneManager : SceneManager - { - - } -} diff --git a/XNet/Program.cs b/XNet/Program.cs index 88dd6c7..a0fdc75 100644 --- a/XNet/Program.cs +++ b/XNet/Program.cs @@ -46,7 +46,9 @@ app.UseStaticFiles(); app.UseWebSockets(); // WebSocket -app.MapWebSocketServer(app.Services.GetRequiredService(), app.Services.GetRequiredService()); +app.MapWebSocketServer(app.Services.GetRequiredService(), + app.Services.GetRequiredService(), + app.Services.GetRequiredService()); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) diff --git a/XNet/XNet.Api.csproj b/XNet/XNet.Api.csproj index dd5280e..9a0c372 100644 --- a/XNet/XNet.Api.csproj +++ b/XNet/XNet.Api.csproj @@ -35,4 +35,10 @@ + + + PreserveNewest + + + diff --git a/XNet/wwwroot/role/device.json b/XNet/role/device.json similarity index 100% rename from XNet/wwwroot/role/device.json rename to XNet/role/device.json