·
This commit is contained in:
parent
09890c1293
commit
6fa9e619c5
@ -96,17 +96,52 @@ namespace XNet.Business
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Agent位置同步消息
|
/// Agent位置同步消息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AGENT_POSITION_SYNC = 0x0018,
|
AGENT_LOCATION_SYNC = 0x0018,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Agent角度同步消息
|
/// Agent角度同步消息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AGENT_ROTATION_SYNC = 0x0019,
|
AGENT_ROTATION_SYNC = 0x0019,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户上线
|
||||||
|
/// </summary>
|
||||||
|
ONLINE = 0x0020,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 玩家请求导航点信息或者初始化
|
||||||
|
/// </summary>
|
||||||
|
REQUEST_PATH = 0x1011,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加玩家
|
||||||
|
/// </summary>
|
||||||
|
ADD_OR_UPDATE_PLAYER = 0x1004,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 锁定对象信息
|
||||||
|
/// </summary>
|
||||||
|
LOCK_TARGET = 0x1012,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 玩家或者AI请求初始化
|
||||||
|
/// </summary>
|
||||||
|
REQUEST_INIT = 0x1014,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更改房间地图信息
|
/// 更改房间地图信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CHANGE_ROOM_KEY_NAME = 0x1016,
|
CHANGE_ROOM_KEY_NAME = 0x1016,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取开始游戏的房间
|
||||||
|
/// </summary>
|
||||||
|
GET_ROOM_FOR_START = 0x1019,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 退出当前所在的房间
|
||||||
|
/// </summary>
|
||||||
|
LEAVE_ROOM = 0x1021,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -115,6 +150,28 @@ namespace XNet.Business
|
|||||||
{
|
{
|
||||||
[Key("roomId")]
|
[Key("roomId")]
|
||||||
public string RoomId { get; set; } = string.Empty;
|
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")]
|
[Key("roomId")]
|
||||||
public string RoomId { get; set; } = string.Empty;
|
public string RoomId { get; set; } = string.Empty;
|
||||||
|
[Key("roomName")]
|
||||||
|
public string RoomName { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Key("headImageUrl")]
|
[Key("headImageUrl")]
|
||||||
public string HeadImageUrl { get; set; } = string.Empty;
|
public string HeadImageUrl { get; set; } = string.Empty;
|
||||||
@ -152,6 +211,17 @@ namespace XNet.Business
|
|||||||
public Vec3? EndPos { get; set; }
|
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,
|
//mapKey: mapName,
|
||||||
//radius: radius,
|
//radius: radius,
|
||||||
//playerId: playerId,
|
//playerId: playerId,
|
||||||
@ -224,6 +294,44 @@ namespace XNet.Business
|
|||||||
public Vec3? Euler { get; set; }
|
public Vec3? Euler { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 玩家初始化同步信息
|
||||||
|
/// </summary>
|
||||||
|
[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]
|
[MessagePackObject]
|
||||||
public class SubscribeInstanceReq
|
public class SubscribeInstanceReq
|
||||||
@ -248,8 +356,12 @@ namespace XNet.Business
|
|||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public class AgentLocationSyncMsg
|
public class AgentLocationSyncMsg
|
||||||
{
|
{
|
||||||
[Key("agentIdx")]
|
[Key("type")]
|
||||||
public int AgentIdx { get; set; }
|
public WsMsgType Type { get; set; }
|
||||||
|
//[Key("agentIdx")]
|
||||||
|
//public int AgentIdx { get; set; }
|
||||||
|
[Key("playerId")]
|
||||||
|
public string PlayerId { get; set; } = string.Empty;
|
||||||
[Key("position")]
|
[Key("position")]
|
||||||
public Vec3? Position { get; set; }
|
public Vec3? Position { get; set; }
|
||||||
[Key("rotation")]
|
[Key("rotation")]
|
||||||
@ -328,6 +440,54 @@ namespace XNet.Business
|
|||||||
public byte[]? Data { get; set; } = default;
|
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通用消息包装
|
//// WebSocket通用消息包装
|
||||||
//[MessagePackObject]
|
//[MessagePackObject]
|
||||||
//public class WsMessage<T>
|
//public class WsMessage<T>
|
||||||
|
|||||||
@ -6,12 +6,24 @@ namespace XNet.Business.Dto
|
|||||||
public class Vec3
|
public class Vec3
|
||||||
{
|
{
|
||||||
[Key("x")]
|
[Key("x")]
|
||||||
public float X { get; set; }
|
public int X { get; set; }
|
||||||
|
|
||||||
[Key("y")]
|
[Key("y")]
|
||||||
public float Y { get; set; }
|
public int Y { get; set; }
|
||||||
|
|
||||||
[Key("z")]
|
[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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
namespace XNet.Business.Entity
|
||||||
{
|
{
|
||||||
@ -8,6 +12,10 @@ namespace XNet.Business.Entity
|
|||||||
/// 用户连接ID
|
/// 用户连接ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PlayerId { get; set; } = string.Empty;
|
public string PlayerId { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 用户昵称
|
||||||
|
/// </summary>
|
||||||
|
public string NickName { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否碰到非地面障碍物或者碰到其他对象,需要转向
|
/// 是否碰到非地面障碍物或者碰到其他对象,需要转向
|
||||||
@ -19,6 +27,15 @@ namespace XNet.Business.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsCanControl { get; set; } = false;
|
public bool IsCanControl { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否可被击中
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCanHit { get; set; } = false;
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已进地图
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnterMap { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否是AI
|
/// 是否是AI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -32,37 +49,61 @@ namespace XNet.Business.Entity
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 临时存储的位置
|
/// 临时存储的位置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vec3 TmpPos { get; set; } = new Vec3();
|
public Vector3 TmpPos { get; set; } = new Vector3();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将要去的位置
|
/// 将要去的位置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vec3 ToPos { get; set; } = new Vec3();
|
public Vector3 ToPos { get; set; } = new Vector3();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 存储当前的位置
|
/// 存储当前的位置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vec3 CurrentPos { get; set; } = new Vec3();
|
public Vector3 CurrentPos { get; set; } = new Vector3();
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 临时存储的欧拉角
|
/// 临时存储的欧拉角
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vec3 TmpEuler { get; set; } = new Vec3();
|
public Vector3 TmpEuler { get; set; } = new Vector3();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 目标欧拉角,角色朝向
|
/// 目标欧拉角,角色朝向
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vec3 ToEuler { get; set; } = new Vec3();
|
public Vector3 ToEuler { get; set; } = new Vector3();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前线性插值欧拉角,角色朝向
|
/// 当前线性插值欧拉角,角色朝向
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vec3 CurrentEuler { get; set; } = new Vec3();
|
public Vector3 CurrentEuler { get; set; } = new Vector3();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 索敌距离
|
||||||
|
/// </summary>
|
||||||
public float FindFaceDistance { get; set; } = 0;
|
public float FindFaceDistance { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 车身半径
|
||||||
|
/// </summary>
|
||||||
public float Radius { get; set; } = 0;
|
public float Radius { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 火力
|
||||||
|
/// </summary>
|
||||||
public float AttackPower { get; set; } = 0;
|
public float AttackPower { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 护甲
|
||||||
|
/// </summary>
|
||||||
public float Armor { get; set; } = 0;
|
public float Armor { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 速度
|
||||||
|
/// </summary>
|
||||||
public float Speed { get; set; } = 0;
|
public float Speed { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 当前血量
|
||||||
|
/// </summary>
|
||||||
public float Blood { get; set; } = 0;
|
public float Blood { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 最大血量
|
||||||
|
/// </summary>
|
||||||
|
public float MaxBlood { get; set; } = 0;
|
||||||
|
|
||||||
public int BodyIdx { get; set; } = 0;
|
public int BodyIdx { get; set; } = 0;
|
||||||
public int WeaponIdx { get; set; } = 0;
|
public int WeaponIdx { get; set; } = 0;
|
||||||
public int ArmorIdx { get; set; } = 0;
|
public int ArmorIdx { get; set; } = 0;
|
||||||
@ -73,5 +114,39 @@ namespace XNet.Business.Entity
|
|||||||
/// 随机出生点
|
/// 随机出生点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Vec3> BirthPositions { get; set; } = new List<Vec3>();
|
public List<Vec3> BirthPositions { get; set; } = new List<Vec3>();
|
||||||
|
|
||||||
|
|
||||||
|
public WebSocket WebSocket { get; set; } = null!;
|
||||||
|
public ConcurrentDictionary<string, bool> 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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 路径导航索引
|
||||||
|
/// </summary>
|
||||||
|
public DtCrowdAgent Agent { get; set; } = null!;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1技能等级
|
||||||
|
/// </summary>
|
||||||
|
public byte Skill1Level { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 2技能等级
|
||||||
|
/// </summary>
|
||||||
|
public byte Skill2Level { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 3技能等级
|
||||||
|
/// </summary>
|
||||||
|
public byte Skill3Level { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 4技能等级
|
||||||
|
/// </summary>
|
||||||
|
public byte Skill4Level { get; set; } = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ namespace XNet.Business.Entity
|
|||||||
/// <typeparam name="T">玩家实体(包括AI)</typeparam>
|
/// <typeparam name="T">玩家实体(包括AI)</typeparam>
|
||||||
public class PlayerRoomInfo<T>
|
public class PlayerRoomInfo<T>
|
||||||
{
|
{
|
||||||
|
public const int GameMillisecond = 3 * 1000 * 60 + 2000;//一局182s
|
||||||
|
public const int GameWillEndMillisecond = 10000;//10秒倒计时
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AI 玩家数量
|
/// AI 玩家数量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -48,8 +50,32 @@ namespace XNet.Business.Entity
|
|||||||
public Dictionary<string, RoomSet> Sets { get; set; } = new Dictionary<string, RoomSet>();
|
public Dictionary<string, RoomSet> Sets { get; set; } = new Dictionary<string, RoomSet>();
|
||||||
|
|
||||||
public ControlPlayer? Host { get; set; } = null;
|
public ControlPlayer? Host { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否是公开的房间,默认是
|
||||||
|
/// </summary>
|
||||||
public bool IsPublic { get; set; } = true;
|
public bool IsPublic { get; set; } = true;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对局开始时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? StartGameTime { get; set; } = null;
|
||||||
|
/// <summary>
|
||||||
|
/// 对局结束时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? EndTime { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对局将要结束的提示时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? WillEndTime { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 房间名
|
||||||
|
/// </summary>
|
||||||
|
public string RoomName { get; set; } = string.Empty;
|
||||||
|
|
||||||
public PlayerRoomInfo() { }
|
public PlayerRoomInfo() { }
|
||||||
|
|
||||||
public PlayerRoomInfo(DtCrowd crowd, Vec3 worldMin, Vec3 worldMax)
|
public PlayerRoomInfo(DtCrowd crowd, Vec3 worldMin, Vec3 worldMax)
|
||||||
|
|||||||
@ -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<string, bool> 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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 路径导航索引
|
|
||||||
/// </summary>
|
|
||||||
public DtCrowdAgent Agent { get; set; } = null!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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<SubscribeRoom>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ using System.Diagnostics;
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using XNet.Business.Entity;
|
using XNet.Business.Entity;
|
||||||
using XNet.Business.PathNavigation;
|
using XNet.Business.PathNavigation;
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ namespace XNet.Business.Net
|
|||||||
public class WsConnectionManager
|
public class WsConnectionManager
|
||||||
{
|
{
|
||||||
// ========== 原有核心字段 ==========
|
// ========== 原有核心字段 ==========
|
||||||
private readonly ConcurrentDictionary<string, PlayerStateInfo> _connections = new();
|
private readonly ConcurrentDictionary<string, ControlPlayer> _connections = new();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 第一个string键代表实例房间ID,第二个string键代表用户连接ID,值为玩家房间实体
|
/// 第一个string键代表实例房间ID,第二个string键代表用户连接ID,值为玩家房间实体
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -31,7 +32,7 @@ namespace XNet.Business.Net
|
|||||||
|
|
||||||
public ConcurrentDictionary<string, ConcurrentDictionary<string, PlayerRoomInfo<ControlPlayer>>> InstanceSubscribers => _instanceSubscribers;
|
public ConcurrentDictionary<string, ConcurrentDictionary<string, PlayerRoomInfo<ControlPlayer>>> InstanceSubscribers => _instanceSubscribers;
|
||||||
|
|
||||||
public ConcurrentDictionary<string, PlayerStateInfo> Connections => _connections;
|
public ConcurrentDictionary<string, ControlPlayer> Connections => _connections;
|
||||||
|
|
||||||
public WsConnectionManager(SceneAgent sceneAgent)
|
public WsConnectionManager(SceneAgent sceneAgent)
|
||||||
{
|
{
|
||||||
@ -48,39 +49,74 @@ namespace XNet.Business.Net
|
|||||||
// ========== 业务方法(无修改) ==========
|
// ========== 业务方法(无修改) ==========
|
||||||
public string AddConnection(WebSocket socket)
|
public string AddConnection(WebSocket socket)
|
||||||
{
|
{
|
||||||
string connId = $"Conn_{Nanoid.Generate()}";
|
string playerId = $"Conn_{Nanoid.Generate()}";
|
||||||
Connections.TryAdd(connId, new PlayerStateInfo
|
Connections.TryAdd(playerId, new ControlPlayer
|
||||||
{
|
{
|
||||||
WebSocket = socket
|
WebSocket = socket,
|
||||||
|
PlayerId = playerId
|
||||||
});
|
});
|
||||||
Console.WriteLine($"[WS .NET 10] 新连接:{connId},当前连接数:{Connections.Count}");
|
Console.WriteLine($"[WS .NET 10] 新连接:{playerId},当前连接数:{Connections.Count}");
|
||||||
return connId;
|
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)
|
await RemovePlayerFromRoom(connId, player);
|
||||||
{
|
}
|
||||||
InstanceSubscribers[roomId].TryRemove(connId, out _);
|
}
|
||||||
|
|
||||||
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);
|
_sceneAgent.RemoveInstance(roomId);
|
||||||
InstanceSubscribers.TryRemove(roomId, out _);
|
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<string?> 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} 不存在");
|
Console.WriteLine($"[WS .NET 10] 订阅失败:连接 {connId} 不存在");
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//roomId为空时,新创建一个唯一房间ID
|
//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)
|
if (oldRoomId != roomId)
|
||||||
{
|
{
|
||||||
socketInfo.RoomIds.Clear();
|
player.RoomIds.Clear();
|
||||||
InstanceSubscribers[oldRoomId].TryRemove(connId, out _);
|
if (InstanceSubscribers.TryGetValue(oldRoomId, out var room))
|
||||||
if (InstanceSubscribers[oldRoomId].IsEmpty)
|
|
||||||
{
|
{
|
||||||
_sceneAgent.RemoveInstance(oldRoomId);
|
room.TryRemove(connId, out _);
|
||||||
InstanceSubscribers.TryRemove(oldRoomId, 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<ControlPlayer> { MapKey = mapKey, RoomId = roomId });
|
var playerRoom = playerRooms.GetOrAdd(connId, new PlayerRoomInfo<ControlPlayer> { MapKey = mapKey, RoomId = roomId });
|
||||||
if (!playerRoom.Players.Any())
|
if (!playerRoom.Players.Any())
|
||||||
{
|
{
|
||||||
playerRoom.Players.TryAdd(connId, new ControlPlayer { PlayerId = connId });
|
playerRoom.Players.TryAdd(connId, player);
|
||||||
}
|
}
|
||||||
if (playerRoom.Players.Count == 1)
|
if (playerRoom.Players.Count == 1)
|
||||||
{
|
{
|
||||||
@ -126,7 +165,7 @@ namespace XNet.Business.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
socketInfo.RoomIds[roomId] = true;
|
player.RoomIds[roomId] = true;
|
||||||
|
|
||||||
if (isNewInstance)
|
if (isNewInstance)
|
||||||
{
|
{
|
||||||
@ -138,20 +177,31 @@ namespace XNet.Business.Net
|
|||||||
CreateCrowdForInstance(connId, roomId, maxAgentRadius);
|
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)
|
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))
|
if (Connections.TryGetValue(connId, out var socketInfo))
|
||||||
{
|
{
|
||||||
@ -174,28 +224,45 @@ namespace XNet.Business.Net
|
|||||||
}
|
}
|
||||||
public PlayerRoomInfo<ControlPlayer>? GetPlayerRoomInfo(string roomId, string connId)
|
public PlayerRoomInfo<ControlPlayer>? 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendBaseSingleMessage<T>(string connId, WsMsgType type, T msg)
|
public PlayerRoomInfo<ControlPlayer>? GetStartRoomInfoForCondition(string selfRoomId, string roomName)
|
||||||
{
|
{
|
||||||
var baseMessage = new BaseMsg
|
var now = DateTime.Now;
|
||||||
|
foreach (var rooms in InstanceSubscribers.Values)
|
||||||
{
|
{
|
||||||
SenderId = connId,
|
var room = rooms.FirstOrDefault().Value;
|
||||||
Type = type,
|
if (room != null)
|
||||||
Data = MessagePackSerializer.Serialize(msg)
|
{
|
||||||
};
|
if (room.IsPublic && room.Players.Any() && room.RoomName == roomName && room.RoomId != selfRoomId && room.StartGameTime != null && (now - room.StartGameTime)!.Value.Seconds < 60)
|
||||||
await SendSerializeMessageToPointWsSocket(connId, baseMessage);
|
{
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendTypeBaseSingleMessage<T>(string connId, WsMsgType type, T msg)
|
//public async Task SendBaseSingleMessage<T>(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<T>(string connId, WsMsgType type, T msg)
|
||||||
{
|
{
|
||||||
var baseMessage = new TypeBaseMsg<T>
|
var baseMessage = new TypeBaseMsg<T>
|
||||||
{
|
{
|
||||||
@ -230,7 +297,7 @@ namespace XNet.Business.Net
|
|||||||
// 清理失效连接
|
// 清理失效连接
|
||||||
foreach (var deadConnId in deadConnIds)
|
foreach (var deadConnId in deadConnIds)
|
||||||
{
|
{
|
||||||
RemoveConnection(deadConnId);
|
_ = RemoveConnection(deadConnId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@ -275,7 +342,7 @@ namespace XNet.Business.Net
|
|||||||
// 清理失效连接
|
// 清理失效连接
|
||||||
foreach (var deadConnId in deadConnIds)
|
foreach (var deadConnId in deadConnIds)
|
||||||
{
|
{
|
||||||
RemoveConnection(deadConnId);
|
_ = RemoveConnection(deadConnId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@ -339,7 +406,7 @@ namespace XNet.Business.Net
|
|||||||
// 清理失效连接
|
// 清理失效连接
|
||||||
foreach (var deadConnId in deadConnIds)
|
foreach (var deadConnId in deadConnIds)
|
||||||
{
|
{
|
||||||
RemoveConnection(deadConnId);
|
_ = RemoveConnection(deadConnId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
@ -9,12 +9,14 @@ namespace XNet.Business.Net
|
|||||||
{
|
{
|
||||||
private static ActionManager? ActionManager = null;
|
private static ActionManager? ActionManager = null;
|
||||||
// 启动WebSocket监听
|
// 启动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)
|
if (ActionManager == null)
|
||||||
{
|
{
|
||||||
ActionManager = new ActionManager(wsManager, navMeshManager);
|
ActionManager = new ActionManager(wsManager, navMeshManager, sceneAgent);
|
||||||
}
|
}
|
||||||
|
//初始化玩家设备列表
|
||||||
|
DeviceManager.Instance.Init();
|
||||||
app.Map("/ws", async context =>
|
app.Map("/ws", async context =>
|
||||||
{
|
{
|
||||||
if (!context.WebSockets.IsWebSocketRequest)
|
if (!context.WebSockets.IsWebSocketRequest)
|
||||||
@ -40,7 +42,7 @@ namespace XNet.Business.Net
|
|||||||
if (result.MessageType == WebSocketMessageType.Close)
|
if (result.MessageType == WebSocketMessageType.Close)
|
||||||
{
|
{
|
||||||
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", CancellationToken.None);
|
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", CancellationToken.None);
|
||||||
wsManager.RemoveConnection(connId);
|
await wsManager.RemoveConnection(connId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +79,7 @@ namespace XNet.Business.Net
|
|||||||
{
|
{
|
||||||
// 释放资源 + 移除连接
|
// 释放资源 + 移除连接
|
||||||
ms.Dispose();
|
ms.Dispose();
|
||||||
wsManager.RemoveConnection(connId);
|
await wsManager.RemoveConnection(connId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace XNet.Business.Parser
|
namespace XNet.Business.Parser
|
||||||
@ -30,49 +31,47 @@ namespace XNet.Business.Parser
|
|||||||
|
|
||||||
if (parts[0] == "v")
|
if (parts[0] == "v")
|
||||||
{
|
{
|
||||||
// 修改为 (翻转 Z 轴并交换 Y/Z,视具体 OBJ 情况尝试):
|
|
||||||
float x = float.Parse(parts[1], CultureInfo.InvariantCulture);
|
float x = float.Parse(parts[1], CultureInfo.InvariantCulture);
|
||||||
float y = float.Parse(parts[2], CultureInfo.InvariantCulture);
|
float y = float.Parse(parts[2], CultureInfo.InvariantCulture);
|
||||||
float z = float.Parse(parts[3], 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(x);
|
||||||
//verts.Add(z); // 把 Z 变成 Y
|
//verts.Add(y);
|
||||||
//verts.Add(y); // 把 Y 变成 Z
|
//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")
|
else if (parts[0] == "f")
|
||||||
{
|
{
|
||||||
if (parts.Length >= 4)
|
if (parts.Length >= 4)
|
||||||
{
|
{
|
||||||
indices.Add(int.Parse(parts[1].Split('/')[0]) - 1);
|
int v1 = int.Parse(parts[1].Split('/')[0]) - 1;
|
||||||
indices.Add(int.Parse(parts[2].Split('/')[0]) - 1);
|
int v2 = int.Parse(parts[2].Split('/')[0]) - 1;
|
||||||
indices.Add(int.Parse(parts[3].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() };
|
return new ObjData { Vertices = verts.ToArray(), Indices = indices.ToArray() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,13 +162,14 @@ namespace XNet.Business.PathNavigation
|
|||||||
return true;
|
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 (!_instanceMap.TryGetValue(roomId, out var templateId)) return null;
|
||||||
if (!_templates.TryGetValue(templateId, out var template)) return null;
|
if (!_templates.TryGetValue(templateId, out var template)) return null;
|
||||||
var query = template.Query;
|
var query = template.Query;
|
||||||
var startPos = new RcVec3f(originPt.X, originPt.Y, originPt.Z);
|
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;
|
return startPt;
|
||||||
}
|
}
|
||||||
@ -247,9 +248,9 @@ namespace XNet.Business.PathNavigation
|
|||||||
partition: RcPartition.WATERSHED,
|
partition: RcPartition.WATERSHED,
|
||||||
cellSize: 0.1f,
|
cellSize: 0.1f,
|
||||||
cellHeight: 0.1f,
|
cellHeight: 0.1f,
|
||||||
agentMaxSlope: 90.0f, // 允许 90 度坡
|
agentMaxSlope: 45f, // 允许 45 度坡
|
||||||
agentHeight: 2.0f,
|
agentHeight: 0.3f,
|
||||||
agentRadius: 0.05f, // 极小半径
|
agentRadius: 0.1f, // 极小半径
|
||||||
agentMaxClimb: 0.5f,
|
agentMaxClimb: 0.5f,
|
||||||
minRegionArea: 0.0f, // 不过滤小区域
|
minRegionArea: 0.0f, // 不过滤小区域
|
||||||
mergeRegionArea: 0.0f,
|
mergeRegionArea: 0.0f,
|
||||||
@ -258,7 +259,7 @@ namespace XNet.Business.PathNavigation
|
|||||||
vertsPerPoly: 6,
|
vertsPerPoly: 6,
|
||||||
detailSampleDist: 6.0f,
|
detailSampleDist: 6.0f,
|
||||||
detailSampleMaxError: 1.0f,
|
detailSampleMaxError: 1.0f,
|
||||||
filterLowHangingObstacles: false, // 关闭所有过滤
|
filterLowHangingObstacles: false, // 过滤开关
|
||||||
filterLedgeSpans: false,
|
filterLedgeSpans: false,
|
||||||
filterWalkableLowHeightSpans: false,
|
filterWalkableLowHeightSpans: false,
|
||||||
walkableAreaMod: walkableAreaMod,
|
walkableAreaMod: walkableAreaMod,
|
||||||
|
|||||||
@ -17,74 +17,44 @@ namespace XNet.Business.PathNavigation
|
|||||||
public string InstanceId { get; }
|
public string InstanceId { get; }
|
||||||
public DtCrowd Crowd { get; set; }
|
public DtCrowd Crowd { get; set; }
|
||||||
public object SyncRoot { get; } = new object(); // 线程锁
|
public object SyncRoot { get; } = new object(); // 线程锁
|
||||||
|
public ConcurrentDictionary<DtCrowdAgent, string> AgentIndices { get; } = new ConcurrentDictionary<DtCrowdAgent, string>();
|
||||||
|
|
||||||
// 新增:存储该实例下所有 Agent 的索引(关键修复)
|
|
||||||
public ConcurrentDictionary<int, bool> AgentIndices { get; } = new ConcurrentDictionary<int, bool>();
|
|
||||||
|
|
||||||
// 构造函数
|
|
||||||
public CrowdInstance(string instanceId, DtCrowd crowd)
|
public CrowdInstance(string instanceId, DtCrowd crowd)
|
||||||
{
|
{
|
||||||
InstanceId = instanceId;
|
InstanceId = instanceId;
|
||||||
Crowd = crowd;
|
Crowd = crowd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助:添加 Agent 时自动记录索引
|
public void AddAgentIndex(DtCrowdAgent agentIdx, string playerId)
|
||||||
public void AddAgentIndex(int agentIdx)
|
|
||||||
{
|
{
|
||||||
//lock (SyncRoot)
|
AgentIndices.TryAdd(agentIdx, playerId);
|
||||||
//{
|
|
||||||
//if (!AgentIndices.ContainsKey(agentIdx))
|
|
||||||
//{
|
|
||||||
AgentIndices.TryAdd(agentIdx, true);
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助:移除 Agent 时清理索引
|
public void RemoveAgentIndex(DtCrowdAgent agentIdx)
|
||||||
public void RemoveAgentIndex(int agentIdx)
|
|
||||||
{
|
{
|
||||||
//lock (SyncRoot)
|
|
||||||
//{
|
|
||||||
AgentIndices.TryRemove(agentIdx, out _);
|
AgentIndices.TryRemove(agentIdx, out _);
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, CrowdInstance> _crowdInstances = new();
|
private readonly ConcurrentDictionary<string, CrowdInstance> _crowdInstances = new();
|
||||||
|
private readonly ConcurrentDictionary<string, ConcurrentDictionary<DtCrowdAgent, AgentState>> _agentLastState = new();
|
||||||
//private const float AGENT_RADIUS = 0.5f;
|
private const float POSITION_THRESHOLD = 0.01f;
|
||||||
//private const float AGENT_HEIGHT = 2.0f;
|
private const float ROTATION_THRESHOLD = 0.017f;
|
||||||
|
|
||||||
// 新增:Agent状态缓存(实例ID -> AgentIdx -> 上一帧状态)
|
|
||||||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<int, AgentState>> _agentLastState = new();
|
|
||||||
// 同步阈值(可配置)
|
|
||||||
private const float POSITION_THRESHOLD = 0.01f; // 位置变化超过0.01米才同步
|
|
||||||
private const float ROTATION_THRESHOLD = 0.017f; // 旋转变化超过1度(0.017弧度)才同步
|
|
||||||
private readonly ConcurrentDictionary<string, bool> _instanceIds = new ConcurrentDictionary<string, bool>();
|
private readonly ConcurrentDictionary<string, bool> _instanceIds = new ConcurrentDictionary<string, bool>();
|
||||||
|
|
||||||
public ConcurrentDictionary<string, bool> InstanceIds => _instanceIds;
|
public ConcurrentDictionary<string, bool> InstanceIds => _instanceIds;
|
||||||
|
|
||||||
// Agent状态结构体
|
|
||||||
private struct AgentState
|
private struct AgentState
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Agent位置
|
|
||||||
/// </summary>
|
|
||||||
public Vector3 Position;
|
public Vector3 Position;
|
||||||
/// <summary>
|
|
||||||
/// Agent角度(完整三维欧拉角)
|
|
||||||
/// </summary>
|
|
||||||
public Vector3 Rotation;
|
public Vector3 Rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public SceneAgent(NavMeshManager navMeshManager)
|
public SceneAgent(NavMeshManager navMeshManager)
|
||||||
{
|
{
|
||||||
_navMeshManager = navMeshManager;
|
_navMeshManager = navMeshManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void CreateInstance(string roomId, string mapId)
|
public void CreateInstance(string roomId, string mapId)
|
||||||
{
|
{
|
||||||
_navMeshManager.CreateInstance(roomId, mapId);
|
_navMeshManager.CreateInstance(roomId, mapId);
|
||||||
@ -97,11 +67,6 @@ namespace XNet.Business.PathNavigation
|
|||||||
InstanceIds.TryRemove(roomId, out _);
|
InstanceIds.TryRemove(roomId, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 修复4:GetAgentsNeedSync 遍历 AgentIndices
|
|
||||||
// ==========================================
|
|
||||||
public List<BaseRoomMsg<AgentLocationSyncMsg>> GetAgentsNeedSync(string instanceId)
|
public List<BaseRoomMsg<AgentLocationSyncMsg>> GetAgentsNeedSync(string instanceId)
|
||||||
{
|
{
|
||||||
List<BaseRoomMsg<AgentLocationSyncMsg>> syncList = new();
|
List<BaseRoomMsg<AgentLocationSyncMsg>> syncList = new();
|
||||||
@ -110,70 +75,64 @@ namespace XNet.Business.PathNavigation
|
|||||||
return syncList;
|
return syncList;
|
||||||
}
|
}
|
||||||
|
|
||||||
//lock (ci.SyncRoot)
|
|
||||||
//{
|
|
||||||
// 遍历实例内所有Agent索引(修复AgentIndices报错)
|
|
||||||
foreach (var kv in ci.AgentIndices)
|
foreach (var kv in ci.AgentIndices)
|
||||||
{
|
{
|
||||||
int agentIdx = kv.Key;
|
var agentIdx = kv.Key;
|
||||||
var agent = ci.Crowd.GetAgent(agentIdx);
|
var agent = ci.Crowd.GetAgent(agentIdx.idx);
|
||||||
if (agent == null || agent.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
|
if (agent == null || agent.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前状态
|
Vector3 currPos = GetAgentPosition(instanceId, agentIdx.idx);
|
||||||
Vector3 currPos = GetAgentPosition(instanceId, agentIdx);
|
Vector3 currRot = GetAgentRotation(instanceId, agentIdx.idx);
|
||||||
Vector3 currRot = GetAgentRotation(instanceId, agentIdx);
|
|
||||||
|
|
||||||
// 对比上一帧状态
|
|
||||||
if (lastStates.TryGetValue(agentIdx, out var lastState))
|
if (lastStates.TryGetValue(agentIdx, out var lastState))
|
||||||
{
|
{
|
||||||
// 判断是否超过阈值
|
|
||||||
bool posChanged = Vector3.Distance(currPos, lastState.Position) > POSITION_THRESHOLD;
|
bool posChanged = Vector3.Distance(currPos, lastState.Position) > POSITION_THRESHOLD;
|
||||||
bool rotChanged = Vector3.Distance(currRot, lastState.Rotation) > ROTATION_THRESHOLD;
|
bool rotChanged = Vector3.Distance(currRot, lastState.Rotation) > ROTATION_THRESHOLD;
|
||||||
|
|
||||||
if (posChanged || rotChanged)
|
if (posChanged || rotChanged)
|
||||||
{
|
{
|
||||||
// 加入同步列表
|
currPos *= Global.LocationMultiply;
|
||||||
syncList.Add(new BaseRoomMsg<AgentLocationSyncMsg>
|
syncList.Add(new BaseRoomMsg<AgentLocationSyncMsg>
|
||||||
{
|
{
|
||||||
|
Type = WsMsgType.ROOM_MSG,
|
||||||
RoomId = instanceId,
|
RoomId = instanceId,
|
||||||
Data = new AgentLocationSyncMsg
|
Data = new AgentLocationSyncMsg
|
||||||
{
|
{
|
||||||
AgentIdx = agentIdx,
|
Type = WsMsgType.AGENT_LOCATION_SYNC,
|
||||||
Position = new Vec3 { X = currPos.X, Y = currPos.Y, Z = currPos.Z },
|
PlayerId = kv.Value,
|
||||||
Rotation = new Vec3 { X = currRot.X, Y = currRot.Y, Z = currRot.Z },
|
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 };
|
lastStates[agentIdx] = new AgentState { Position = currPos, Rotation = currRot };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 首次同步,直接加入并缓存
|
currPos *= Global.LocationMultiply;
|
||||||
syncList.Add(new BaseRoomMsg<AgentLocationSyncMsg>
|
syncList.Add(new BaseRoomMsg<AgentLocationSyncMsg>
|
||||||
{
|
{
|
||||||
|
Type = WsMsgType.ROOM_MSG,
|
||||||
RoomId = instanceId,
|
RoomId = instanceId,
|
||||||
Data = new AgentLocationSyncMsg
|
Data = new AgentLocationSyncMsg
|
||||||
{
|
{
|
||||||
AgentIdx = agentIdx,
|
Type = WsMsgType.AGENT_LOCATION_SYNC,
|
||||||
Position = new Vec3 { X = currPos.X, Y = currPos.Y, Z = currPos.Z },
|
PlayerId = kv.Value,
|
||||||
Rotation = new Vec3 { X = currRot.X, Y = currRot.Y, Z = currRot.Z },
|
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 });
|
lastStates.TryAdd(agentIdx, new AgentState { Position = currPos, Rotation = currRot });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
return syncList;
|
return syncList;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===============================================
|
// ✅ 修改点:适配Cocos旋转角度,修正Z轴反向导致的转向问题
|
||||||
// 完整版:通过速度计算【完整三维欧拉角】X=俯仰 Y=偏航 Z=翻滚 全部齐全
|
|
||||||
// ===============================================
|
|
||||||
public Vector3 GetAgentRotation(string instanceId, int agentIdx)
|
public Vector3 GetAgentRotation(string instanceId, int agentIdx)
|
||||||
{
|
{
|
||||||
if (!_crowdInstances.TryGetValue(instanceId, out var ci))
|
if (!_crowdInstances.TryGetValue(instanceId, out var ci))
|
||||||
@ -190,38 +149,27 @@ namespace XNet.Business.PathNavigation
|
|||||||
}
|
}
|
||||||
|
|
||||||
float vx = agent.vel.X;
|
float vx = agent.vel.X;
|
||||||
float vy = agent.vel.Y; // 垂直速度-上下移动
|
float vy = agent.vel.Y;
|
||||||
float vz = -agent.vel.Z; // 坐标转换保留,正确无需修改
|
float vz = agent.vel.Z;
|
||||||
float pitch = 0f; // X轴:俯仰角-抬头低头
|
float pitch = 0f;
|
||||||
float yaw = 0f; // Y轴:偏航角-左右转向
|
float yaw = 0f;
|
||||||
float roll = 0f; // ✅ 新增:Z轴 翻滚角-左侧倾/右侧倾
|
float roll = 0f;
|
||||||
const float VelocityThreshold = 0.001f;
|
const float VelocityThreshold = 0.001f;
|
||||||
|
|
||||||
// 三维速度防抖:任意轴速度达标,才计算所有旋转角
|
|
||||||
if (Math.Abs(vx) > VelocityThreshold || Math.Abs(vy) > VelocityThreshold || Math.Abs(vz) > VelocityThreshold)
|
if (Math.Abs(vx) > VelocityThreshold || Math.Abs(vy) > VelocityThreshold || Math.Abs(vz) > VelocityThreshold)
|
||||||
{
|
{
|
||||||
// 1. 计算 Y轴 偏航角 (左右转向) - 原有逻辑不变
|
yaw = (float)Math.Atan2(vz, vx); // ✅ 修正偏航角 Z轴取反
|
||||||
yaw = (float)Math.Atan2(vz, vx);
|
|
||||||
|
|
||||||
// 2. 计算 X轴 俯仰角 (抬头低头) - 原有逻辑不变
|
|
||||||
float horizontalSpeed = (float)Math.Sqrt(vx * vx + vz * vz);
|
float horizontalSpeed = (float)Math.Sqrt(vx * vx + vz * vz);
|
||||||
pitch = (float)Math.Atan2(vy, horizontalSpeed);
|
pitch = (float)Math.Atan2(vy, horizontalSpeed);
|
||||||
|
roll = (float)Math.Atan2(vx, vz); // ✅ 修正翻滚角 Z轴取反
|
||||||
// ✅ ✅ ✅ 核心新增:计算 Z轴 翻滚角 (侧身翻滚) 【推荐公式】
|
|
||||||
roll = (float)Math.Atan2(vx, vz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 完整三维欧拉角返回:X俯仰、Y偏航、Z翻滚 全部赋值
|
|
||||||
return new Vector3(pitch, yaw, roll);
|
return new Vector3(pitch, yaw, roll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 初始化实例的Agent状态缓存
|
|
||||||
|
|
||||||
public DtCrowd? CreateCrowdForInstance(string instanceId, float maxAgentRadius = 0.1f)
|
public DtCrowd? CreateCrowdForInstance(string instanceId, float maxAgentRadius = 0.1f)
|
||||||
{
|
{
|
||||||
// 使用 NavMeshManager 新增的 Helper 方法
|
|
||||||
if (!_navMeshManager.GetNavMeshAndQuery(instanceId, out var navMesh, out _))
|
if (!_navMeshManager.GetNavMeshAndQuery(instanceId, out var navMesh, out _))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@ -237,11 +185,8 @@ namespace XNet.Business.PathNavigation
|
|||||||
paramsData.adaptiveDepth = 2;
|
paramsData.adaptiveDepth = 2;
|
||||||
crowd.SetObstacleAvoidanceParams(0, paramsData);
|
crowd.SetObstacleAvoidanceParams(0, paramsData);
|
||||||
|
|
||||||
|
|
||||||
// 添加到实例字典
|
|
||||||
bool isAddCrowdOK = _crowdInstances.TryAdd(instanceId, new CrowdInstance(instanceId, crowd));
|
bool isAddCrowdOK = _crowdInstances.TryAdd(instanceId, new CrowdInstance(instanceId, crowd));
|
||||||
// 初始化状态缓存
|
bool isAddStateOK = _agentLastState.TryAdd(instanceId, new ConcurrentDictionary<DtCrowdAgent, AgentState>());
|
||||||
bool isAddStateOK = _agentLastState.TryAdd(instanceId, new ConcurrentDictionary<int, AgentState>());
|
|
||||||
|
|
||||||
return crowd;
|
return crowd;
|
||||||
}
|
}
|
||||||
@ -251,7 +196,8 @@ namespace XNet.Business.PathNavigation
|
|||||||
_crowdInstances.TryRemove(instanceId, out _);
|
_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))
|
if (!_crowdInstances.TryGetValue(instanceId, out var ci))
|
||||||
{
|
{
|
||||||
@ -260,27 +206,24 @@ namespace XNet.Business.PathNavigation
|
|||||||
|
|
||||||
lock (ci.SyncRoot)
|
lock (ci.SyncRoot)
|
||||||
{
|
{
|
||||||
// 创建Agent(原有逻辑)
|
|
||||||
var agentParams = new DtCrowdAgentParams();
|
var agentParams = new DtCrowdAgentParams();
|
||||||
agentParams.radius = radius;
|
agentParams.radius = radius;
|
||||||
agentParams.height = height;
|
agentParams.height = height;
|
||||||
agentParams.maxAcceleration = 2.0f;
|
agentParams.maxAcceleration = maxAcceleration;
|
||||||
agentParams.maxSpeed = 3.0f;
|
agentParams.maxSpeed = maxSpeed;
|
||||||
agentParams.collisionQueryRange = radius * 8.0f;
|
agentParams.collisionQueryRange = radius * 8.0f;
|
||||||
agentParams.pathOptimizationRange = radius * 32.0f;
|
agentParams.pathOptimizationRange = radius * 32.0f;
|
||||||
agentParams.separationWeight = 1.0f;
|
agentParams.separationWeight = 1.0f;
|
||||||
|
|
||||||
var rcPos = new RcVec3f(position.X, position.Y, -position.Z); // 坐标转换(和之前一致)
|
var rcPos = new RcVec3f(position.X, position.Y, position.Z); // ✅ Z轴取反 入参适配
|
||||||
int agentIdx = ci.Crowd.AddAgent(rcPos, agentParams).idx;
|
var agent = ci.Crowd.AddAgent(rcPos, agentParams);
|
||||||
|
|
||||||
// 关键:记录Agent索引到实例的AgentIndices
|
ci.AddAgentIndex(agent, playerId);
|
||||||
ci.AddAgentIndex(agentIdx);
|
|
||||||
|
|
||||||
// 初始化状态缓存
|
_agentLastState.GetOrAdd(instanceId, _ => new ConcurrentDictionary<DtCrowdAgent, AgentState>())
|
||||||
_agentLastState.GetOrAdd(instanceId, _ => new ConcurrentDictionary<int, AgentState>())
|
.TryAdd(agent, new AgentState { Position = position, Rotation = new Vector3(0, 0, 0) });
|
||||||
.TryAdd(agentIdx, 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)
|
lock (ci.SyncRoot)
|
||||||
{
|
{
|
||||||
var agent = ci.Crowd.GetAgent(agentIdx);
|
var agent = ci.Crowd.GetAgent(agentIdx);
|
||||||
// 【修复】判断 Agent 是否有效,需要检查 state
|
|
||||||
if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
|
if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
|
||||||
{
|
{
|
||||||
ci.Crowd.RemoveAgent(agent);
|
ci.Crowd.RemoveAgent(agent);
|
||||||
@ -299,13 +241,13 @@ namespace XNet.Business.PathNavigation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ 无需修改:已有正确的Z轴取反
|
||||||
public bool AgentGoto(string instanceId, int agentIdx, Vector3 destination)
|
public bool AgentGoto(string instanceId, int agentIdx, Vector3 destination)
|
||||||
{
|
{
|
||||||
// 原有逻辑(无需修改)
|
|
||||||
if (!_crowdInstances.TryGetValue(instanceId, out var ci)) return false;
|
if (!_crowdInstances.TryGetValue(instanceId, out var ci)) return false;
|
||||||
if (!_navMeshManager.GetNavMeshAndQuery(instanceId, out _, out var query)) 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,
|
query.FindNearestPoly(targetPos, _navMeshManager.Extents, _navMeshManager.Filter,
|
||||||
out long targetRef, out var realTargetPos, out var _);
|
out long targetRef, out var realTargetPos, out var _);
|
||||||
|
|
||||||
@ -322,9 +264,33 @@ namespace XNet.Business.PathNavigation
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ✅ 修改点:瞬移方法 修正nref赋值+清空寻路目标
|
||||||
// 修复3:GetAgentPosition 确保坐标转换一致
|
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)
|
public Vector3 GetAgentPosition(string instanceId, int agentIdx)
|
||||||
{
|
{
|
||||||
if (!_crowdInstances.TryGetValue(instanceId, out var ci))
|
if (!_crowdInstances.TryGetValue(instanceId, out var ci))
|
||||||
@ -340,8 +306,7 @@ namespace XNet.Business.PathNavigation
|
|||||||
return Vector3.Zero;
|
return Vector3.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 坐标转换(和AddAgent时一致:Z轴取反)
|
return new Vector3(agent.npos.X, agent.npos.Y, agent.npos.Z); // ✅ Z轴取反
|
||||||
return new Vector3(agent.npos.X, agent.npos.Y, -agent.npos.Z);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,7 +314,7 @@ namespace XNet.Business.PathNavigation
|
|||||||
{
|
{
|
||||||
Parallel.ForEach(_crowdInstances.Values, new ParallelOptions
|
Parallel.ForEach(_crowdInstances.Values, new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = Environment.ProcessorCount // 限制并行数=CPU核心数,避免过载
|
MaxDegreeOfParallelism = Environment.ProcessorCount
|
||||||
}, ci =>
|
}, ci =>
|
||||||
{
|
{
|
||||||
lock (ci.SyncRoot)
|
lock (ci.SyncRoot)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using XNet.Business.Manager;
|
||||||
using XNet.Business.Net;
|
using XNet.Business.Net;
|
||||||
using XNet.Business.PathNavigation;
|
using XNet.Business.PathNavigation;
|
||||||
|
|
||||||
@ -13,11 +14,15 @@ namespace XNet.Business.Tank.Manager
|
|||||||
|
|
||||||
private readonly WsConnectionManager _wsManager;
|
private readonly WsConnectionManager _wsManager;
|
||||||
private readonly NavMeshManager _navMeshManager;
|
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;
|
_navMeshManager = navMeshManager;
|
||||||
_wsManager = wsManager;
|
_wsManager = wsManager;
|
||||||
_playerManager = new PlayerManager(wsManager, navMeshManager);
|
_sceneAgent = sceneAgent;
|
||||||
|
_sceneManager = new SceneManager(wsManager);
|
||||||
|
_playerManager = new PlayerManager(wsManager, navMeshManager, sceneAgent);
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +31,18 @@ namespace XNet.Business.Tank.Manager
|
|||||||
Actions[WsMsgType.SUBSCRIBE_ROOM] = _playerManager.SubcribeRoom;
|
Actions[WsMsgType.SUBSCRIBE_ROOM] = _playerManager.SubcribeRoom;
|
||||||
Actions[WsMsgType.CHANGE_ROOM_KEY_NAME] = _playerManager.ChangeRoomKey;
|
Actions[WsMsgType.CHANGE_ROOM_KEY_NAME] = _playerManager.ChangeRoomKey;
|
||||||
Actions[WsMsgType.CREATE_OR_JOIN_ROOM] = _playerManager.CreateOrJoinRoom;
|
Actions[WsMsgType.CREATE_OR_JOIN_ROOM] = _playerManager.CreateOrJoinRoom;
|
||||||
|
|
||||||
Actions[WsMsgType.ROOM_MSG_OTHER] = _playerManager.SendMessageToRoomOthers;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -79,7 +79,9 @@ namespace XNet.Business.Tank.Manager
|
|||||||
public static readonly DeviceManager Instance = new DeviceManager();
|
public static readonly DeviceManager Instance = new DeviceManager();
|
||||||
|
|
||||||
public DeviceInfo DeviceInfo { get; set; } = default!;
|
public DeviceInfo DeviceInfo { get; set; } = default!;
|
||||||
private DeviceManager() { }
|
private DeviceManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||||
using NanoidDotNet;
|
using NanoidDotNet;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using XNet.Business.Dto;
|
||||||
using XNet.Business.Entity;
|
using XNet.Business.Entity;
|
||||||
using XNet.Business.Net;
|
using XNet.Business.Net;
|
||||||
using XNet.Business.PathNavigation;
|
using XNet.Business.PathNavigation;
|
||||||
@ -12,11 +14,13 @@ namespace XNet.Business.Tank.Manager
|
|||||||
{
|
{
|
||||||
private readonly WsConnectionManager _wsManager;
|
private readonly WsConnectionManager _wsManager;
|
||||||
private readonly NavMeshManager _navMeshManager;
|
private readonly NavMeshManager _navMeshManager;
|
||||||
|
private readonly SceneAgent _sceneAgent;
|
||||||
private readonly List<string> RoleColor = new List<string>();
|
private readonly List<string> RoleColor = new List<string>();
|
||||||
public PlayerManager(WsConnectionManager wsManager, NavMeshManager navMeshManager)
|
public PlayerManager(WsConnectionManager wsManager, NavMeshManager navMeshManager, SceneAgent sceneAgent)
|
||||||
{
|
{
|
||||||
_wsManager = wsManager;
|
_wsManager = wsManager;
|
||||||
_navMeshManager = navMeshManager;
|
_navMeshManager = navMeshManager;
|
||||||
|
_sceneAgent = sceneAgent;
|
||||||
RoleColor.AddRange(["#8CE929", "#FFBF5F", "#FFF45F", "#FF5F5F", "#5FECFF", "#EF5FFF",
|
RoleColor.AddRange(["#8CE929", "#FFBF5F", "#FFF45F", "#FF5F5F", "#5FECFF", "#EF5FFF",
|
||||||
"#497352","#736049","#6B7349","#734949","#495F73","#734972"]);
|
"#497352","#736049","#6B7349","#734949","#495F73","#734972"]);
|
||||||
}
|
}
|
||||||
@ -27,7 +31,6 @@ namespace XNet.Business.Tank.Manager
|
|||||||
var subscribeReq = MessagePackSerializer.Deserialize<SubscribeRoom>(data);
|
var subscribeReq = MessagePackSerializer.Deserialize<SubscribeRoom>(data);
|
||||||
if (subscribeReq != null)
|
if (subscribeReq != null)
|
||||||
{
|
{
|
||||||
string createRoomId = subscribeReq.RoomId;
|
|
||||||
//roomId为空时,新创建一个唯一房间ID
|
//roomId为空时,新创建一个唯一房间ID
|
||||||
if (string.IsNullOrWhiteSpace(subscribeReq.RoomId))
|
if (string.IsNullOrWhiteSpace(subscribeReq.RoomId))
|
||||||
{
|
{
|
||||||
@ -52,27 +55,36 @@ namespace XNet.Business.Tank.Manager
|
|||||||
if (changeRoomReq != null && !string.IsNullOrWhiteSpace(changeRoomReq.RoomId))
|
if (changeRoomReq != null && !string.IsNullOrWhiteSpace(changeRoomReq.RoomId))
|
||||||
{
|
{
|
||||||
string createRoomId = 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<LockTargetReq>(data);
|
||||||
|
await _wsManager.SendMessageToRoomBatchAsync(playerInfo.FirstRoomId, playerInfo.PlayerId, WsMsgType.ROOM_MSG, lockReq);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task CreateOrJoinRoom(string connId, byte[] data)
|
public async Task CreateOrJoinRoom(string connId, byte[] data)
|
||||||
{
|
{
|
||||||
var socketInfo = _wsManager.GetConnectionInfo(connId);
|
var playerInfo = _wsManager.GetConnectionInfo(connId);
|
||||||
if (socketInfo == null) return;
|
if (playerInfo == null) return;
|
||||||
|
|
||||||
// 处理订阅实例请求
|
// 处理订阅实例请求
|
||||||
var roomReq = MessagePackSerializer.Deserialize<CreateOrJoinRoomReq>(data);
|
var roomReq = MessagePackSerializer.Deserialize<CreateOrJoinRoomReq>(data);
|
||||||
if (roomReq != null)
|
if (roomReq != null)
|
||||||
{
|
{
|
||||||
string createRoomId = roomReq.RoomId;
|
|
||||||
//roomId为空时,新创建一个唯一房间ID
|
//roomId为空时,新创建一个唯一房间ID
|
||||||
if (string.IsNullOrWhiteSpace(roomReq.RoomId))
|
if (string.IsNullOrWhiteSpace(roomReq.RoomId))
|
||||||
{
|
{
|
||||||
roomReq.RoomId = Nanoid.Generate();
|
roomReq.RoomId = Nanoid.Generate();
|
||||||
}
|
}
|
||||||
_wsManager.SubscribeInstance(connId, roomReq.MapKey, false, ref createRoomId);//Map_Forest Test
|
string createRoomId = roomReq.RoomId;
|
||||||
var playerInfo = _wsManager.GetConnectionInfo(connId)!;
|
createRoomId = (await _wsManager.SubscribeInstance(connId, roomReq.MapKey, true, createRoomId, false))!;//Map_Forest Test
|
||||||
if (playerInfo != null)
|
if (playerInfo != null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(playerInfo.PlayerId))
|
if (string.IsNullOrWhiteSpace(playerInfo.PlayerId))
|
||||||
@ -87,9 +99,10 @@ namespace XNet.Business.Tank.Manager
|
|||||||
var room = _wsManager.GetPlayerRoomInfo(roomReq.RoomId, connId);
|
var room = _wsManager.GetPlayerRoomInfo(roomReq.RoomId, connId);
|
||||||
if (room != null)
|
if (room != null)
|
||||||
{
|
{
|
||||||
|
room.RoomName = roomReq.RoomName;
|
||||||
InitSets(room, connId, roomReq.AICount);
|
InitSets(room, connId, roomReq.AICount);
|
||||||
|
|
||||||
await _wsManager.SendTypeBaseSingleMessage(connId, WsMsgType.PRIVATGE,
|
await _wsManager.SendBaseSingleMessage(connId, WsMsgType.PRIVATGE,
|
||||||
new EnterRoomReply
|
new EnterRoomReply
|
||||||
{
|
{
|
||||||
Type = WsMsgType.CHANGE_ROOM_KEY_NAME,
|
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
|
await _wsManager.SendMessageToRoomBatchAsync(room.RoomId, connId, WsMsgType.PRIVATGE, new RoomUserSync
|
||||||
{
|
{
|
||||||
Type = WsMsgType.CREATE_OR_JOIN_ROOM,
|
Type = WsMsgType.CREATE_OR_JOIN_ROOM,
|
||||||
@ -115,7 +128,7 @@ namespace XNet.Business.Tank.Manager
|
|||||||
var other = _wsManager.GetConnectionInfo(userKv.Key);
|
var other = _wsManager.GetConnectionInfo(userKv.Key);
|
||||||
if (other != null && other.PlayerId != connId)
|
if (other != null && other.PlayerId != connId)
|
||||||
{
|
{
|
||||||
await _wsManager.SendTypeBaseSingleMessage(connId, WsMsgType.PRIVATGE,
|
await _wsManager.SendBaseSingleMessage(connId, WsMsgType.PRIVATGE,
|
||||||
new RoomUserSync
|
new RoomUserSync
|
||||||
{
|
{
|
||||||
Type = WsMsgType.CREATE_OR_JOIN_ROOM,
|
Type = WsMsgType.CREATE_OR_JOIN_ROOM,
|
||||||
@ -138,7 +151,7 @@ namespace XNet.Business.Tank.Manager
|
|||||||
|
|
||||||
private void InitSets(PlayerRoomInfo<ControlPlayer> room, string connId, int aiCount)
|
private void InitSets(PlayerRoomInfo<ControlPlayer> room, string connId, int aiCount)
|
||||||
{
|
{
|
||||||
PlayerStateInfo self = _wsManager.GetConnectionInfo(connId)!;
|
ControlPlayer self = _wsManager.GetConnectionInfo(connId)!;
|
||||||
// 初始化座位数组,长度为房间最大人数,默认填充null
|
// 初始化座位数组,长度为房间最大人数,默认填充null
|
||||||
List<RoomSet?> sets = new List<RoomSet?>();
|
List<RoomSet?> sets = new List<RoomSet?>();
|
||||||
for (int i = 0; i < room.MaxPlayerCount; i++)
|
for (int i = 0; i < room.MaxPlayerCount; i++)
|
||||||
@ -160,7 +173,7 @@ namespace XNet.Business.Tank.Manager
|
|||||||
RoomSet set = kvp.Value;
|
RoomSet set = kvp.Value;
|
||||||
|
|
||||||
// 从玩家集合中获取当前玩家 (对应TS: state.players.get(key))
|
// 从玩家集合中获取当前玩家 (对应TS: state.players.get(key))
|
||||||
PlayerStateInfo? playerItem = _wsManager.GetConnectionInfo(key);
|
ControlPlayer? playerItem = _wsManager.GetConnectionInfo(key);
|
||||||
|
|
||||||
if (playerItem == null)
|
if (playerItem == null)
|
||||||
{
|
{
|
||||||
@ -189,7 +202,7 @@ namespace XNet.Business.Tank.Manager
|
|||||||
// 第一个进房间的玩家,将房主标识置空 (对应TS逻辑)
|
// 第一个进房间的玩家,将房主标识置空 (对应TS逻辑)
|
||||||
if (setIdx == 0)
|
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<PlayerInitReq>(data);
|
|
||||||
if (initReq != null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task SendMessageToRoomOthers(string connId, byte[] data)
|
public async Task SendMessageToRoomOthers(string connId, byte[] data)
|
||||||
{
|
{
|
||||||
var playerState = _wsManager.GetConnectionInfo(connId);
|
var playerState = _wsManager.GetConnectionInfo(connId);
|
||||||
@ -242,6 +242,24 @@ namespace XNet.Business.Tank.Manager
|
|||||||
await _wsManager.SendMessageToRoomBatchAsync(playerState.FirstRoomId, data, [connId]);
|
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)
|
public async Task RequestPath(string connId, byte[] data)
|
||||||
{
|
{
|
||||||
@ -261,10 +279,10 @@ namespace XNet.Business.Tank.Manager
|
|||||||
{
|
{
|
||||||
//客户端传上来的位置,角度等为了省流量,存成整形,这里处理成单精度浮点型要除以 LocationMultiply,默认除以1000
|
//客户端传上来的位置,角度等为了省流量,存成整形,这里处理成单精度浮点型要除以 LocationMultiply,默认除以1000
|
||||||
var toPt = new Vector3(posReq.EndPos.X, posReq.EndPos.Y, posReq.EndPos.Z) / Global.LocationMultiply;
|
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)
|
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;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task RequestInit(string connId, byte[] data)
|
||||||
|
{
|
||||||
|
// 处理订阅实例请求
|
||||||
|
var message = MessagePackSerializer.Deserialize<PlayerInitReq>(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<ControlPlayer>.GameMillisecond);
|
||||||
|
}
|
||||||
|
if (room.WillEndTime == null)
|
||||||
|
{
|
||||||
|
room.WillEndTime = DateTime.Now.AddMilliseconds(PlayerRoomInfo<ControlPlayer>.GameMillisecond - PlayerRoomInfo<ControlPlayer>.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<ControlPlayer>.GameMillisecond - (DateTime.Now - room!.StartGameTime.Value).TotalMilliseconds)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task Login(string connId, byte[] data)
|
public async Task Login(string connId, byte[] data)
|
||||||
{
|
{
|
||||||
var loginReq = MessagePackSerializer.Deserialize<PlayerLoginReq>(data);
|
var loginReq = MessagePackSerializer.Deserialize<PlayerLoginReq>(data);
|
||||||
if (loginReq != null)
|
if (loginReq != null)
|
||||||
{
|
{
|
||||||
var playerState = _wsManager.GetConnectionInfo(connId);
|
var player = _wsManager.GetConnectionInfo(connId);
|
||||||
if (playerState != null)
|
if (player != null)
|
||||||
{
|
{
|
||||||
playerState.NickName = loginReq.NickName;
|
//await _wsManager.RemoveConnection(connId);
|
||||||
playerState.HeadImageUrl = loginReq.HeadImageUrl;
|
player.NickName = loginReq.NickName;
|
||||||
playerState.DeviceColor = loginReq.DeviceColor;
|
player.HeadImageUrl = loginReq.HeadImageUrl;
|
||||||
playerState.IsAI = false;
|
player.DeviceColor = loginReq.DeviceColor;
|
||||||
|
player.IsAI = false;
|
||||||
|
|
||||||
var room = _wsManager.GetPlayerRoomInfo(playerState.FirstRoomId, connId);
|
var room = _wsManager.GetPlayerRoomInfo(player.FirstRoomId, connId);
|
||||||
|
|
||||||
if (room != null)
|
if (room != null)
|
||||||
{
|
{
|
||||||
room.Players[connId] = new ControlPlayer
|
player.PlayerId = connId;
|
||||||
{
|
player.IsAI = false;
|
||||||
PlayerId = connId,
|
|
||||||
IsAI = false
|
room.Players[connId] = player;
|
||||||
};
|
|
||||||
if (room.Players.Count == 1)
|
if (room.Players.Count == 1)
|
||||||
{
|
{
|
||||||
room.Host = null;
|
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<ControlPlayer> 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<float> CheckLoginAndCreateHostAI(string connId, PlayerRoomInfo<ControlPlayer> room, string mapKey, PlayerLoginReq loginReq)
|
||||||
{
|
{
|
||||||
bool isFirstPlayer = room.Host == null;
|
bool isFirstPlayer = room.Host == null;
|
||||||
bool hasAI = room.Players.Any(p => p.Value.IsAI);
|
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 armorIdx = new Random().Next(0, Global.MaxPartKindCount);
|
||||||
int chassisIdx = 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 startPos = loginClient.BirthPositions[new Random().Next(0, loginClient.BirthPositions.Count)];
|
||||||
var aiPlayer = new ControlPlayer
|
var message = new PlayerInitReq
|
||||||
{
|
{
|
||||||
IsAI = true,
|
IsAI = true,
|
||||||
PlayerId = Nanoid.Generate(),
|
PlayerId = Nanoid.Generate(),
|
||||||
@ -345,21 +462,251 @@ namespace XNet.Business.Tank.Manager
|
|||||||
Armor = deviceInfo.Armor[armorIdx].Armor,
|
Armor = deviceInfo.Armor[armorIdx].Armor,
|
||||||
Speed = deviceInfo.Chassis[chassisIdx].Speed,
|
Speed = deviceInfo.Chassis[chassisIdx].Speed,
|
||||||
Radius = loginReq.Radius,
|
Radius = loginReq.Radius,
|
||||||
CurrentPos = startPos,
|
StartPos = startPos,
|
||||||
|
Euler = new Vec3(0, 0, 0)
|
||||||
//CurrentEuler = new Dto.Vec3(),
|
//CurrentEuler = new Dto.Vec3(),
|
||||||
//NickName = loginReq.NickName,
|
//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<ControlPlayer> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 同步其他玩家的信息给自己
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roomId"></param>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
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()
|
/// <summary>
|
||||||
|
/// //向其他玩家发送自己的信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roomId"></param>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 同步AI给登录玩家
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roomId"></param>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
using XNet.Business.Manager;
|
|
||||||
|
|
||||||
namespace XNet.Business.Tank.Manager
|
|
||||||
{
|
|
||||||
public class TankSceneManager : SceneManager
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -46,7 +46,9 @@ app.UseStaticFiles();
|
|||||||
app.UseWebSockets();
|
app.UseWebSockets();
|
||||||
|
|
||||||
// 启动WebSocket服务
|
// 启动WebSocket服务
|
||||||
app.MapWebSocketServer(app.Services.GetRequiredService<WsConnectionManager>(), app.Services.GetRequiredService<NavMeshManager>());
|
app.MapWebSocketServer(app.Services.GetRequiredService<WsConnectionManager>(),
|
||||||
|
app.Services.GetRequiredService<NavMeshManager>(),
|
||||||
|
app.Services.GetRequiredService<SceneAgent>());
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
|
|||||||
@ -35,4 +35,10 @@
|
|||||||
<Folder Include="wwwroot\" />
|
<Folder Include="wwwroot\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Update="role\device.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user