提取RVO 寻路

This commit is contained in:
PC-20230316NUNE\Administrator
2024-02-05 18:56:55 +08:00
parent 04043cc6fc
commit 16d943ab6b
208 changed files with 42246 additions and 37182 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9fcc24a8c5f74ee29f3ae27768122f30
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
namespace Game.Plugins.JNGame.Sync.Frame.AstarPath
{
public class JNAStarBase
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a3417f25b3114ecc8e596be23f70851f
timeCreated: 1707104675

View File

@@ -0,0 +1,13 @@

namespace Game.Plugins.JNGame.Sync.Frame.AstarPath
{
/**
* 寻路类
*/
public class JNAStarPath
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 79d7cad08d874b6aba4388770d624c79
timeCreated: 1707104391

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4a19843b56634bfca2b3e6234c425d8a
timeCreated: 1707102377

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 821e5ded88f4428a9b5aad0aed6ffd1a
timeCreated: 1707114274

View File

@@ -0,0 +1,45 @@
using UnityEngine;
namespace Game.Plugins.JNGame.Sync.Frame.AstarPath.RVO
{
/// <summary>
/// 一个障碍物中的顶点。
/// 这是一个链表,因此一个顶点可以用来引用整个障碍物。
/// </summary>
public class JNObstacleVertex {
/// <summary>
/// 指示是否忽略此顶点的布尔值。
/// </summary>
public bool ignore;
/// <summary>
/// 顶点的位置。
/// </summary>
public Vector3 position; // 顶点的三维位置。
/// <summary>
/// 顶点的方向或法线。
/// </summary>
public Vector2 dir; // 顶点的二维方向或法线。
/// <summary>
/// 在此顶点上的障碍物的高度。
/// </summary>
public float height; // 障碍物的高度。
/// <summary>
/// 此障碍物的碰撞层。
/// </summary>
public JNRVOLayer layer = JNRVOLayer.DefaultObstacle; // 障碍物的碰撞层,默认为默认障碍物层。
/// <summary>
/// 在障碍物中指向下一个顶点的引用。
/// </summary>
public JNObstacleVertex next; // 指向下一个顶点的引用。
/// <summary>
/// 在障碍物中指向上一个顶点的引用。
/// </summary>
public JNObstacleVertex prev; // 指向上一个顶点的引用。
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4aedb5e02e7a44d38eea97702c35fdc7
timeCreated: 1707113948

View File

@@ -0,0 +1,246 @@
using System;
using UnityEngine;
namespace Game.Plugins.JNGame.Sync.Frame.AstarPath.RVO
{
/// <summary>
/// 用于快速寻找RVO代理的最近邻居的四叉树。
/// 详见Pathfinding.RVO.Simulator
/// </summary>
public class JNRVOQuadtree
{
const int LeafSize = 15;
float maxRadius = 0;
/// <summary>
/// 用于存储RVO代理的四叉树的节点。
/// 详见Pathfinding.GraphNode该节点类用于存储路径查找数据。
/// </summary>
struct Node {
public int child00; // 可能是一个指向子节点的引用或索引。
public JNRVOAgent linkedList; // 代理链表的头节点。
public byte count; // 该节点中的代理数量。
public float maxSpeed; // 该节点中的代理的最大速度。
public void Add(JNRVOAgent agent) { // 添加新的代理到链表的尾部的方法。
agent.next = linkedList; // 将新代理的next指向当前的链表头。
linkedList = agent; // 将链表头更新为新代理。
}
/// <summary>
/// 将代理分配到子节点中。
/// 用于将一个节点中的代理分发到其子节点中。通常在节点被分割后使用。
/// </summary>
public void Distribute(Node[] nodes, Rect r) {
Vector2 c = r.center; // 获取矩形的中心点
while (linkedList != null) { // 当代理链表不为空时
JNRVOAgent nx = linkedList.next; // 保存下一个代理的引用
var index = child00 + (linkedList.position.x > c.x ? 2 : 0) + (linkedList.position.y > c.y ? 1 : 0); // 计算代理应该分配到的子节点的索引
nodes[index].Add(linkedList); // 将代理添加到对应的子节点中
linkedList = nx; // 移动到链表中的下一个代理
}
count = 0; // 重置代理计数器
}
/// <summary>
/// 计算节点的最大速度。
/// 用于计算一个节点及其所有子节点的最大速度。
/// </summary>
public float CalculateMaxSpeed(Node[] nodes, int index) {
if (child00 == index) { // 如果当前节点是叶节点
// 遍历叶节点中的所有代理,找到最大的速度
for (var agent = linkedList; agent != null; agent = agent.next) {
maxSpeed = Math.Max(maxSpeed, agent.CalculatedSpeed);
}
} else { // 如果当前节点不是叶节点
maxSpeed = Math.Max(nodes[child00].CalculateMaxSpeed(nodes, child00), nodes[child00+1].CalculateMaxSpeed(nodes, child00+1)); // 分别计算两个子节点的最大速度,取两者中的最大值
maxSpeed = Math.Max(maxSpeed, nodes[child00+2].CalculateMaxSpeed(nodes, child00+2)); // 计算第三个子节点的最大速度,并与之前的最大值比较并更新
maxSpeed = Math.Max(maxSpeed, nodes[child00+3].CalculateMaxSpeed(nodes, child00+3)); // 计算第四个子节点的最大速度,并与之前的最大值比较并更新
}
return maxSpeed; // 返回计算得到的最大速度
}
}
Node[] nodes = new Node[16];
int filledNodes = 1;
Rect bounds;
/// <summary>从树中移除所有代理</summary>
public void Clear() {
nodes[0] = new Node();
filledNodes = 1;
maxRadius = 0;
}
public void SetBounds(Rect r) {
bounds = r;
}
int GetNodeIndex() {
if (filledNodes + 4 >= nodes.Length) {
var nds = new Node[nodes.Length * 2];
for (int i = 0; i < nodes.Length; i++) nds[i] = nodes[i];
nodes = nds;
}
nodes[filledNodes] = new Node();
nodes[filledNodes].child00 = filledNodes;
filledNodes++;
nodes[filledNodes] = new Node();
nodes[filledNodes].child00 = filledNodes;
filledNodes++;
nodes[filledNodes] = new Node();
nodes[filledNodes].child00 = filledNodes;
filledNodes++;
nodes[filledNodes] = new Node();
nodes[filledNodes].child00 = filledNodes;
filledNodes++;
return filledNodes - 4;
}
/// <summary>
/// 将一个代理添加到树中。
/// 注意:代理不能被多次添加到同一棵树中。
/// </summary>
public void Insert(JNRVOAgent agent) {
int i = 0;
Rect r = bounds;
Vector2 p = new Vector2(agent.position.x, agent.position.y);
agent.next = null;
maxRadius = System.Math.Max(agent.radius, maxRadius);
int depth = 0;
while (true) {
depth++;
if (nodes[i].child00 == i) {
// 叶节点。如果代理数量大于等于LeafSize则深度超过10时停止避免过多的代理在同一位置
if (nodes[i].count < LeafSize || depth > 10) {
nodes[i].Add(agent);
nodes[i].count++;
break;
} else {
// 拆分节点
nodes[i].child00 = GetNodeIndex();
nodes[i].Distribute(nodes, r);
}
}
// 注意这里没有else子句
if (nodes[i].child00 != i) {
// 非叶节点
Vector2 c = r.center;
if (p.x > c.x) {
if (p.y > c.y) {
i = nodes[i].child00 + 3;
r = Rect.MinMaxRect(c.x, c.y, r.xMax, r.yMax);
} else {
i = nodes[i].child00 + 2;
r = Rect.MinMaxRect(c.x, r.yMin, r.xMax, c.y);
}
} else {
if (p.y > c.y) {
i = nodes[i].child00 + 1;
r = Rect.MinMaxRect(r.xMin, c.y, c.x, r.yMax);
} else {
i = nodes[i].child00;
r = Rect.MinMaxRect(r.xMin, r.yMin, c.x, c.y);
}
}
}
}
}
public void CalculateSpeeds () {
nodes[0].CalculateMaxSpeed(nodes, 0);
}
public void Query (Vector2 p, float speed, float timeHorizon, float agentRadius, JNRVOAgent agent) {
new QuadtreeQuery {
p = p, speed = speed, timeHorizon = timeHorizon, maxRadius = float.PositiveInfinity,
agentRadius = agentRadius, agent = agent, nodes = nodes
}.QueryRec(0, bounds);
}
struct QuadtreeQuery {
public Vector2 p;
public float speed, timeHorizon, agentRadius, maxRadius;
public JNRVOAgent agent;
public Node[] nodes;
public void QueryRec(int i, Rect r) {
// 确定需要搜索的半径,以便将所有代理考虑在内
// 注意第二个agentRadius的使用实际上应该是其他代理的半径而不是此代理的半径
// 但是出于性能和简化的考虑,我们假设代理具有大致相同的半径
// 因此,具有非常小半径的代理在某些情况下可能会过晚地检测到具有非常大半径的代理
// 但是这种效果应该是次要的。
var radius = System.Math.Min(System.Math.Max((nodes[i].maxSpeed + speed)*timeHorizon, agentRadius) + agentRadius, maxRadius);
if (nodes[i].child00 == i) {
// 叶节点
for (JNRVOAgent a = nodes[i].linkedList; a != null; a = a.next) {
float v = agent.InsertAgentNeighbour(a, radius*radius);
// 如果代理达到了附近代理的最大数量阈值,则限制搜索
if (v < maxRadius*maxRadius) {
maxRadius = Mathf.Sqrt(v);
}
}
} else {
// 非叶节点
Vector2 c = r.center;
if (p.x-radius < c.x) {
if (p.y-radius < c.y) {
QueryRec(nodes[i].child00, Rect.MinMaxRect(r.xMin, r.yMin, c.x, c.y));
radius = System.Math.Min(radius, maxRadius);
}
if (p.y+radius > c.y) {
QueryRec(nodes[i].child00+1, Rect.MinMaxRect(r.xMin, c.y, c.x, r.yMax));
radius = System.Math.Min(radius, maxRadius);
}
}
if (p.x+radius > c.x) {
if (p.y-radius < c.y) {
QueryRec(nodes[i].child00+2, Rect.MinMaxRect(c.x, r.yMin, r.xMax, c.y));
radius = System.Math.Min(radius, maxRadius);
}
if (p.y+radius > c.y) {
QueryRec(nodes[i].child00+3, Rect.MinMaxRect(c.x, c.y, r.xMax, r.yMax));
}
}
}
}
}
public void DebugDraw () {
DebugDrawRec(0, bounds);
}
void DebugDrawRec (int i, Rect r) {
Debug.DrawLine(new Vector3(r.xMin, 0, r.yMin), new Vector3(r.xMax, 0, r.yMin), Color.white);
Debug.DrawLine(new Vector3(r.xMax, 0, r.yMin), new Vector3(r.xMax, 0, r.yMax), Color.white);
Debug.DrawLine(new Vector3(r.xMax, 0, r.yMax), new Vector3(r.xMin, 0, r.yMax), Color.white);
Debug.DrawLine(new Vector3(r.xMin, 0, r.yMax), new Vector3(r.xMin, 0, r.yMin), Color.white);
if (nodes[i].child00 != i) {
// Not a leaf node
Vector2 c = r.center;
DebugDrawRec(nodes[i].child00+3, Rect.MinMaxRect(c.x, c.y, r.xMax, r.yMax));
DebugDrawRec(nodes[i].child00+2, Rect.MinMaxRect(c.x, r.yMin, r.xMax, c.y));
DebugDrawRec(nodes[i].child00+1, Rect.MinMaxRect(r.xMin, c.y, c.x, r.yMax));
DebugDrawRec(nodes[i].child00+0, Rect.MinMaxRect(r.xMin, r.yMin, c.x, c.y));
}
for (JNRVOAgent a = nodes[i].linkedList; a != null; a = a.next) {
var p = nodes[i].linkedList.position;
Debug.DrawLine(new Vector3(p.x, 0, p.y)+Vector3.up, new Vector3(a.position.x, 0, a.position.y)+Vector3.up, new Color(1, 1, 0, 0.5f));
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 660414f56e8b4287a616f5c4144acced
timeCreated: 1707116244

View File

@@ -0,0 +1,485 @@
using System;
using System.Collections.Generic;
using Plugins.JNGame.Sync.Frame.Entity;
using UnityEngine;
namespace Game.Plugins.JNGame.Sync.Frame.AstarPath.RVO
{
/// <summary>
/// 代理类的属性暴露。
/// </summary>
public interface JNIAgent
{
/// <summary>
/// 代理的位置。
/// 代理本身不会移动需要一个移动脚本负责读取CalculatedTargetPoint和CalculatedSpeed属性并以该速度朝该点移动。
/// 该属性应每帧设置一次。
///
/// 注意这是一个Vector2而不是Vector3因为RVO在内部以2D模拟一切。 position3D = new Vector3(agent.Position.x, agent.ElevationCoordinate, agent.Position.y);
/// </code>
/// </summary>
Vector2 Position { get; set; }
/// <summary>
/// 在高度方向上分隔角色的坐标。
/// 由于RVO可以在2D或3D中使用所以仅仅使用3D位置的y坐标并不简单。
/// 在3D中这很可能会被设置为y坐标但在2D俯视在大多数情况下应该设置为0因为所有角色总是在同一平面上但是它可以被设置为其他值例如如果游戏是2D等距的。
///
/// 该位置假定在角色的基础部分(靠近脚部)。
/// </summary>
float ElevationCoordinate { get; set; }
/// <summary>
/// 避免碰撞的最优移动点。
/// 移动脚本应以<see cref="CalculatedSpeed"/>的速度朝此点移动。
///
/// 注意这是一个Vector2而不是Vector3因为SetTarget方法接受的是Vector2。
///
/// 参考RVOController.CalculateMovementDelta。
/// </summary>
Vector2 CalculatedTargetPoint { get; }
/// <summary>
/// 代理避免碰撞的最优速度。
/// 移动脚本应以此速度朝向<see cref="CalculatedTargetPoint"/>移动。
/// </summary>
float CalculatedSpeed { get; }
/// <summary>
/// 代理应朝向移动的点。
/// 通常每帧设置一次。代理将尽量接近目标点。将在下一个模拟步骤中生效。
///
/// 注意:系统假定代理在到达目标点时会停止,
/// 因此,如果您只想向特定方向移动代理,请确保将目标点设置在角色前方一段距离处,
/// 否则系统可能无法很好地避免碰撞。简而言之,系统(以简化方式)会认为代理会在碰撞前停止,
/// 因此不会减速或改变方向。请参阅下面的图片。在图片中desiredSpeed 是蓝色箭头的长度,
/// 目标点是黑色箭头指向的点。在上图中,代理没有避免红色代理(您可以假设红色代理的速度非常小以简化问题),
/// 而在下图中它确实避免了。normalized * remainingPathDistance
/// </code>
/// 其中 remainingPathDistance 是角色到达路径终点之前的距离。这很好用,因为在路径的终点处,
/// 到下一个路点的方向将只是到路径上最后一个点的方向,而 remainingPathDistance 将是到路径上最后一个点的距离,
/// 因此 targetPoint 将被设置为简单地设置为路径上的最后一个点。但是,当 remainingPathDistance 很大时,
/// targetPoint 将设置得如此远,以至于代理基本上会被指示朝某个方向移动,这正是我们想要的。
/// [打开在线文档查看图片]
/// </summary>
/// <param name="targetPoint">世界空间中的目标点XZ平面或XY平面取决于模拟是否配置为2D或3D
/// 请注意这是一个Vector2而不是Vector3因为系统内部以2D方式模拟一切。因此如果您的代理在XZ平面上移动您必须将其提供为具有(x,z)坐标的Vector2。</param>
/// <param name="desiredSpeed">代理的期望速度。以世界单位每秒为单位。代理将尽量以这个速度移动。</param>
/// <param name="maxSpeed">代理的最大速度。以世界单位每秒为单位。如果必要(例如,如果另一个代理正朝向此代理的碰撞轨迹移动),代理可以以这个速度移动。
/// 应该至少与desiredSpeed一样高但建议使用比desiredSpeed稍高的值例如desiredSpeed*1.2)。</param>
void SetTarget(Vector2 targetPoint, float desiredSpeed, float maxSpeed);
/// <summary>被锁定的代理将被假定为不会移动</summary>
bool Locked { get; set; }
/// <summary>
/// 代理在世界单位中的半径。
/// 代理被建模为圆形/圆柱形。
/// </summary>
float Radius { get; set; }
/// <summary>
/// 代理在世界单位中的高度。
/// 代理被建模为圆形/圆柱形。
/// </summary>
float Height { get; set; }
/// <summary>
/// 估计用于查看未来与代理发生碰撞的最大秒数。
/// 事实证明,此变量也非常适合用于控制代理的避免优先级。
/// 值较低的代理会较少避免其他代理,因此可以通过赋予它们较低的值来创建“高优先级代理”。
/// </summary>
float AgentTimeHorizon { get; set; }
/// <summary>
/// 估计用于查看未来与障碍物发生碰撞的最大秒数。
/// </summary>
float ObstacleTimeHorizon { get; set; }
/// <summary>
/// 要考虑的最大代理数量。
/// 减少此值可以提高性能,增加它可以提高模拟的质量。
/// </summary>
int MaxNeighbours { get; set; }
/// <summary>
/// 上一个模拟步骤中代理考虑的邻居数量。
/// </summary>
int NeighbourCount { get; }
/// <summary>
/// 指定此代理的避障层。
/// 其他代理上的<see cref="CollidesWith"/>掩码将决定它们是否会避开此代理。
/// </summary>
JNRVOLayer Layer { get; set; }
/// <summary>
/// 用于指定该代理会避开的图层掩码。
/// 可以将其设置为CollidesWith = RVOLayer.DefaultAgent | RVOLayer.Layer3 | RVOLayer.Layer6 ...
///
/// 参考http://en.wikipedia.org/wiki/Mask_(computing)
/// </summary>
JNRVOLayer CollidesWith { get; set; }
/// <summary>
/// 绘制调试信息。
///
/// 注意:即使<see cref="Pathfinding.RVO.Simulator.movementPlane"/>设置为XY平面也将在XZ平面上始终绘制调试信息。
/// 注意:如果模拟器组件启用了多线程,则忽略此属性。
/// 因为Unity的调试API只能从主线程调用。
/// </summary>
bool DebugDraw { get; set; }
/// <summary>
/// 在上一个模拟步骤中与代理近距离接触的障碍物段落的列表。
/// 例如,可以用于施加额外的墙壁避免力。
/// 段落由障碍物的顶点及其.next属性形成。
///
/// 错误始终返回null。
/// </summary>
List<JNObstacleVertex> NeighbourObstacles { get; }
/// <summary>
/// 其他代理避免此代理的强烈程度。
/// 通常是一个介于0和1之间的值。
/// 具有相似优先级的代理将以相同的强度相互避免。
/// 如果一个代理看到另一个优先级高于自己的代理,它会更加强烈地避免该代理。
/// 在极端情况下例如此代理的优先级为0而另一个代理的优先级为1它会将另一个代理视为移动障碍物。
/// 类似地如果一个代理看到另一个优先级低于自己的代理它会避免该代理的强度较小。0:
/// avoidanceStrength = other.priority / (this.priority + other.priority);
/// 否则:
/// avoidanceStrength = 0.5
/// </code>
/// </summary>
float Priority { get; set; }
/// <summary>
/// 在避障计算开始之前将被调用的回调函数。
/// 用于使用最新值更新其他属性。
/// </summary>
System.Action PreCalculationCallback { set; }
/// <summary>
/// 设置代理当前与之碰撞的墙壁(或其他物体)的法线。
/// 这用于使RVO系统能够感知物理或其他代理被夹在导航网格上的情况。
/// 其他代理观察到的此代理的速度将被修改,因此不会产生撞墙的分量。
/// 但是代理不会开始避开墙壁要实现这一点您需要添加RVO障碍物。
///
/// 在下一个模拟步骤之后,此值将被清除,通常应在碰撞仍在发生时每个帧设置此值。
/// </summary>
void SetCollisionNormal(Vector2 normal);
/// <summary>
/// 设置代理的当前速度。
/// 这将完全覆盖本地避障输入。
/// 如果您有一个由玩家控制的角色,并希望其他代理避开它,这很有用。
///
/// 调用此方法将使代理在下一个模拟步骤中被标记为外部控制。
/// 下一个模拟步骤中的本地避障计算将被跳过,但之后将恢复,除非再次调用此方法。
/// </summary>
void ForceSetVelocity(Vector2 velocity);
}
/// <summary>
/// 描述物体主要在哪个平面上移动的枚举类型。
/// </summary>
public enum MovementPlane {
/// <summary>
/// 物体主要在XZ平面上移动3D环境
/// </summary>
XZ,
/// <summary>
/// 物体主要在XY平面上移动2D环境
/// </summary>
XY
}
[System.Flags]
public enum JNRVOLayer {
DefaultAgent = 1 << 0,
DefaultObstacle = 1 << 1,
Layer2 = 1 << 2,
Layer3 = 1 << 3,
Layer4 = 1 << 4,
Layer5 = 1 << 5,
Layer6 = 1 << 6,
Layer7 = 1 << 7,
Layer8 = 1 << 8,
Layer9 = 1 << 9,
Layer10 = 1 << 10,
Layer11 = 1 << 11,
Layer12 = 1 << 12,
Layer13 = 1 << 13,
Layer14 = 1 << 14,
Layer15 = 1 << 15,
Layer16 = 1 << 16,
Layer17 = 1 << 17,
Layer18 = 1 << 18,
Layer19 = 1 << 19,
Layer20 = 1 << 20,
Layer21 = 1 << 21,
Layer22 = 1 << 22,
Layer23 = 1 << 23,
Layer24 = 1 << 24,
Layer25 = 1 << 25,
Layer26 = 1 << 26,
Layer27 = 1 << 27,
Layer28 = 1 << 28,
Layer29 = 1 << 29,
Layer30 = 1 << 30
}
internal class WorkerContext {
public JNRVOAgent.VOBuffer vos = new JNRVOAgent.VOBuffer(16);
public const int KeepCount = 3;
public Vector2[] bestPos = new Vector2[KeepCount];
public float[] bestSizes = new float[KeepCount];
public float[] bestScores = new float[KeepCount+1];
public Vector2[] samplePos = new Vector2[50];
public float[] sampleSize = new float[50];
}
/// <summary>
/// RVO 模拟器
/// 本地避障模拟器。
/// 这个类使用Reciprocal Velocity Obstacles (RVO) 和 Optimal Reciprocal Collision Avoidance (ORCA) 方法处理多个代理的本地避障模拟。
///
/// 这个类负责根据脚本提供的期望速度计算速度。然而它不负责在Unity场景中移动任何物体。对于这个功能还有其他脚本可用详见下文
///
/// 可以向模拟器添加和移除障碍物,也可以随时添加和移除代理。
/// 参考RVOSimulator
/// 参考RVOAgent
/// 参考Pathfinding.RVO.IAgent
///
/// 该实现使用基于采样的算法,结合梯度下降法来找到避障速度。
///
/// 你可能主要会使用包装类RVOSimulator。
/// </summary>
public class JNRVOSimulator
{
//
// /// <summary>
// /// 使用双缓冲技术。
// /// 双缓冲技术是一种图形渲染技术,用于减少屏幕撕裂和闪烁现象。
// /// </summary>
// private readonly bool doubleBuffering = true;
/// <summary>模拟中的代理</summary>
/// 代理可以理解为模拟中的实体或对象,它们会根据模拟规则进行移动或交互。
List<JNRVOAgent> agents = new List<JNRVOAgent>();
/// <summary>模拟中的障碍物</summary>
/// 障碍物是模拟环境中的静态对象,代理在移动时需要避免与障碍物发生碰撞。
public List<JNObstacleVertex> obstacles = new List<JNObstacleVertex>();
/// <summary>
/// 用于此模拟的四叉树。
/// 四叉树是一种数据结构,用于高效地管理和查询空间信息。在此模拟中,四叉树用于快速查找代理的邻居。
/// </summary>
public JNRVOQuadtree Quadtree = new JNRVOQuadtree();
private float deltaTime;
/// <summary>上次模拟的时间步长</summary>
private float lastStep = -99999;
/// <summary>是否需要更新障碍物的状态</summary>
private bool doUpdateObstacles = false;
/// <summary>是否需要清除障碍物</summary>
private bool doCleanObstacles = false;
/// <summary>当前时间步长的持续时间</summary>
public float DeltaTime { get { return deltaTime; } }
/// <summary>是否使用多线程进行模拟</summary>
public bool Multithreading
{
get { return false; } // 目前未启用多线程模拟功能。
}
private WorkerContext context = new WorkerContext();
/// <summary>
/// Inverse desired simulation fps.
/// See: DesiredDeltaTime
/// </summary>
private float desiredDeltaTime = 0.05f;
/// <summary>
/// Time in seconds between each simulation step.
/// This is the desired delta time, the simulation will never run at a higher fps than
/// the rate at which the Update function is called.
/// </summary>
public float DesiredDeltaTime { get { return desiredDeltaTime; } set { desiredDeltaTime = Math.Max(value, 0.0f); } }
/// <summary>
/// 使代理在彼此之间通过右侧。
/// 如果一个代理的期望速度使其与另一个代理或障碍物处于碰撞航线上,
/// 其期望速度将向右旋转这个弧度数1弧度约等于57°
/// 这有助于打破对称性,并使更快地解决某些情况成为可能。
///
/// 当许多代理具有相同的目标时,这可能会产生副作用,即围绕目标点的群体
/// 可能会作为一个整体开始围绕目标点旋转。
///
/// 推荐的值范围是0到0.2。
///
/// 如果此值为负数,代理将偏向于在左侧彼此通过。
/// </summary>
public float symmetryBreakingBias = 0.1f;
/// <summary>
/// 判断用于移动的是XY2D平面还是XZ3D平面。
/// </summary>
public readonly MovementPlane movementPlane = MovementPlane.XZ;
public JNRVOSimulator (MovementPlane movementPlane) {
this.movementPlane = movementPlane;
Quadtree = new JNRVOQuadtree();
agents = new List<JNRVOAgent>();
obstacles = new List<JNObstacleVertex>();
}
void PreCalculation () {
for (int i = 0; i < agents.Count; i++) agents[i].PreCalculation();
}
void CleanAndUpdateObstaclesIfNecessary () {
if (doCleanObstacles) {
CleanObstacles();
doCleanObstacles = false;
doUpdateObstacles = true;
}
if (doUpdateObstacles) {
doUpdateObstacles = false;
}
}
private void CleanObstacles () {
}
/// <summary>
/// 从模拟中移除所有代理。
/// </summary>
public void ClearAgents () {
// 遍历代理列表,将每个代理的模拟器设置为 null。
for (int i = 0; i < agents.Count; i++) {
agents[i].simulator = null;
}
// 清空代理列表。
agents.Clear();
}
/// <summary>
/// 在指定位置添加一个代理。
/// 您可以使用返回的接口来读取和写入参数,并设置例如半径和要移动到的目标点。
///
/// 查看RemoveAgent
/// </summary>
/// <param name="position">参见IAgent.Position</param>
/// <param name="elevationCoordinate">参见IAgent.ElevationCoordinate</param>
public JNIAgent AddAgent (Vector2 position, float elevationCoordinate) {
// 创建一个新的代理实例,并传入位置和海拔坐标。
JNRVOAgent agent = new JNRVOAgent(position, elevationCoordinate);
// 调用另一个AddAgent方法来添加代理。
return AddAgent(agent);
}
/// <summary>
/// 添加先前已从模拟中移除的代理到模拟中。
/// 查看RemoveAgent
/// </summary>
public JNIAgent AddAgent (JNIAgent agent) {
// 检查代理是否为空,如果为空则抛出异常
if (agent == null) throw new System.ArgumentNullException("Agent must not be null");
// 尝试将代理转换为 Agent 类型,并检查转换是否成功
JNRVOAgent agentReal = agent as JNRVOAgent;
if (agentReal == null) throw new System.ArgumentException("The agent must be of type Agent. Agent was of type "+agent.GetType());
// 检查代理是否已经存在于模拟中,如果存在则抛出异常
if (agentReal.simulator != null && agentReal.simulator == this) throw new System.ArgumentException("The agent is already in the simulation");
else if (agentReal.simulator != null) throw new System.ArgumentException("The agent is already added to another simulation");
// 如果代理当前没有模拟器,则为该代理设置当前模拟器
agentReal.simulator = this;
// 将代理添加到代理列表中
agents.Add(agentReal);
return agent; // 返回添加的代理对象
}
/// <summary>
/// 每帧调用一次
/// </summary>
public void Update()
{
if (lastStep < 0) {
lastStep = 0;
deltaTime = DesiredDeltaTime;
}
if (JNTime.Time.time - lastStep >= DesiredDeltaTime)
{
deltaTime = JNTime.Time.time - lastStep;
lastStep = JNTime.Time.time;
deltaTime = Math.Max(deltaTime, 1.0f / 2000f);
PreCalculation(); // 预计算
CleanAndUpdateObstaclesIfNecessary(); // 清理并更新障碍物
BuildQuadtree(); // 构建四叉树
// 遍历所有代理,切换缓冲区
for (int i = 0; i < agents.Count; i++)
{
agents[i].BufferSwitch();
}
// 遍历所有代理,计算邻居和速度
for (int i = 0; i < agents.Count; i++)
{
agents[i].CalculateNeighbours();
agents[i].CalculateVelocity(context);
}
// 遍历所有代理,后计算
for (int i = 0; i < agents.Count; i++)
{
agents[i].PostCalculation();
}
}
}
/// <summary>
/// 构建四叉树
/// </summary>
void BuildQuadtree()
{
Quadtree.Clear(); // 清除四叉树中的所有元素
if (agents.Count > 0) // 如果代理数量大于0
{
Rect bounds = Rect.MinMaxRect(agents[0].position.x, agents[0].position.y, agents[0].position.x, agents[0].position.y); // 获取第一个代理的位置作为矩形边界的初始值
for (int i = 1; i < agents.Count; i++) // 遍历所有代理,更新矩形边界的最小和最大值
{
Vector2 p = agents[i].position;
bounds = Rect.MinMaxRect(Mathf.Min(bounds.xMin, p.x), Mathf.Min(bounds.yMin, p.y), Mathf.Max(bounds.xMax, p.x), Mathf.Max(bounds.yMax, p.y));
}
Quadtree.SetBounds(bounds); // 设置四叉树的边界
for (int i = 0; i < agents.Count; i++) // 遍历所有代理,将代理插入四叉树中
{
Quadtree.Insert(agents[i]);
}
//quadtree.DebugDraw(); // 可选:绘制四叉树用于调试(如果需要)
}
Quadtree.CalculateSpeeds(); // 计算四叉树中所有元素的速度(用于碰撞检测等)
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 11e5d1e71ebb4bc2982a35fe3a32eab1
timeCreated: 1707104628

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 064be9ef1f5e4efbb820dd632b98d5be
timeCreated: 1707118794

View File

@@ -0,0 +1,320 @@
using UnityEngine;
namespace Game.Plugins.JNGame.Sync.Frame.AStar.Util {
/// <summary>Holds a coordinate in integers</summary>
public struct Int3 : System.IEquatable<Int3> {
public int x;
public int y;
public int z;
//These should be set to the same value (only PrecisionFactor should be 1 divided by Precision)
/// <summary>
/// Precision for the integer coordinates.
/// One world unit is divided into [value] pieces. A value of 1000 would mean millimeter precision, a value of 1 would mean meter precision (assuming 1 world unit = 1 meter).
/// This value affects the maximum coordinates for nodes as well as how large the cost values are for moving between two nodes.
/// A higher value means that you also have to set all penalty values to a higher value to compensate since the normal cost of moving will be higher.
/// </summary>
public const int Precision = 1000;
/// <summary><see cref="Precision"/> as a float</summary>
public const float FloatPrecision = 1000F;
/// <summary>1 divided by <see cref="Precision"/></summary>
public const float PrecisionFactor = 0.001F;
public static Int3 zero { get { return new Int3(); } }
public Int3 (Vector3 position) {
x = (int)System.Math.Round(position.x*FloatPrecision);
y = (int)System.Math.Round(position.y*FloatPrecision);
z = (int)System.Math.Round(position.z*FloatPrecision);
}
public Int3 (int _x, int _y, int _z) {
x = _x;
y = _y;
z = _z;
}
public static bool operator == (Int3 lhs, Int3 rhs) {
return lhs.x == rhs.x &&
lhs.y == rhs.y &&
lhs.z == rhs.z;
}
public static bool operator != (Int3 lhs, Int3 rhs) {
return lhs.x != rhs.x ||
lhs.y != rhs.y ||
lhs.z != rhs.z;
}
public static explicit operator Int3 (Vector3 ob) {
return new Int3(
(int)System.Math.Round(ob.x*FloatPrecision),
(int)System.Math.Round(ob.y*FloatPrecision),
(int)System.Math.Round(ob.z*FloatPrecision)
);
}
public static explicit operator Vector3 (Int3 ob) {
return new Vector3(ob.x*PrecisionFactor, ob.y*PrecisionFactor, ob.z*PrecisionFactor);
}
public static Int3 operator - (Int3 lhs, Int3 rhs) {
lhs.x -= rhs.x;
lhs.y -= rhs.y;
lhs.z -= rhs.z;
return lhs;
}
public static Int3 operator - (Int3 lhs) {
lhs.x = -lhs.x;
lhs.y = -lhs.y;
lhs.z = -lhs.z;
return lhs;
}
public static Int3 operator + (Int3 lhs, Int3 rhs) {
lhs.x += rhs.x;
lhs.y += rhs.y;
lhs.z += rhs.z;
return lhs;
}
public static Int3 operator * (Int3 lhs, int rhs) {
lhs.x *= rhs;
lhs.y *= rhs;
lhs.z *= rhs;
return lhs;
}
public static Int3 operator * (Int3 lhs, float rhs) {
lhs.x = (int)System.Math.Round(lhs.x * rhs);
lhs.y = (int)System.Math.Round(lhs.y * rhs);
lhs.z = (int)System.Math.Round(lhs.z * rhs);
return lhs;
}
public static Int3 operator * (Int3 lhs, double rhs) {
lhs.x = (int)System.Math.Round(lhs.x * rhs);
lhs.y = (int)System.Math.Round(lhs.y * rhs);
lhs.z = (int)System.Math.Round(lhs.z * rhs);
return lhs;
}
public static Int3 operator / (Int3 lhs, float rhs) {
lhs.x = (int)System.Math.Round(lhs.x / rhs);
lhs.y = (int)System.Math.Round(lhs.y / rhs);
lhs.z = (int)System.Math.Round(lhs.z / rhs);
return lhs;
}
public int this[int i] {
get {
return i == 0 ? x : (i == 1 ? y : z);
}
set {
if (i == 0) x = value;
else if (i == 1) y = value;
else z = value;
}
}
/// <summary>Angle between the vectors in radians</summary>
public static float Angle (Int3 lhs, Int3 rhs) {
double cos = Dot(lhs, rhs)/ ((double)lhs.magnitude*(double)rhs.magnitude);
cos = cos < -1 ? -1 : (cos > 1 ? 1 : cos);
return (float)System.Math.Acos(cos);
}
public static int Dot (Int3 lhs, Int3 rhs) {
return
lhs.x * rhs.x +
lhs.y * rhs.y +
lhs.z * rhs.z;
}
public static long DotLong (Int3 lhs, Int3 rhs) {
return
(long)lhs.x * (long)rhs.x +
(long)lhs.y * (long)rhs.y +
(long)lhs.z * (long)rhs.z;
}
/// <summary>
/// Normal in 2D space (XZ).
/// Equivalent to Cross(this, Int3(0,1,0) )
/// except that the Y coordinate is left unchanged with this operation.
/// </summary>
public Int3 Normal2D () {
return new Int3(z, y, -x);
}
/// <summary>
/// Returns the magnitude of the vector. The magnitude is the 'length' of the vector from 0,0,0 to this point. Can be used for distance calculations:
/// <code> Debug.Log ("Distance between 3,4,5 and 6,7,8 is: "+(new Int3(3,4,5) - new Int3(6,7,8)).magnitude); </code>
/// </summary>
public float magnitude {
get {
//It turns out that using doubles is just as fast as using ints with Mathf.Sqrt. And this can also handle larger numbers (possibly with small errors when using huge numbers)!
double _x = x;
double _y = y;
double _z = z;
return (float)System.Math.Sqrt(_x*_x+_y*_y+_z*_z);
}
}
/// <summary>
/// Magnitude used for the cost between two nodes. The default cost between two nodes can be calculated like this:
/// <code> int cost = (node1.position-node2.position).costMagnitude; </code>
///
/// This is simply the magnitude, rounded to the nearest integer
/// </summary>
public int costMagnitude {
get {
return (int)System.Math.Round(magnitude);
}
}
/// <summary>The squared magnitude of the vector</summary>
public float sqrMagnitude {
get {
double _x = x;
double _y = y;
double _z = z;
return (float)(_x*_x+_y*_y+_z*_z);
}
}
/// <summary>The squared magnitude of the vector</summary>
public long sqrMagnitudeLong {
get {
long _x = x;
long _y = y;
long _z = z;
return (_x*_x+_y*_y+_z*_z);
}
}
public static implicit operator string (Int3 obj) {
return obj.ToString();
}
/// <summary>Returns a nicely formatted string representing the vector</summary>
public override string ToString () {
return "( "+x+", "+y+", "+z+")";
}
public override bool Equals (System.Object obj) {
if (obj == null) return false;
var rhs = (Int3)obj;
return x == rhs.x &&
y == rhs.y &&
z == rhs.z;
}
#region IEquatable implementation
public bool Equals (Int3 other) {
return x == other.x && y == other.y && z == other.z;
}
#endregion
public override int GetHashCode () {
return x*73856093 ^ y*19349669 ^ z*83492791;
}
}
/// <summary>Two Dimensional Integer Coordinate Pair</summary>
public struct Int2 : System.IEquatable<Int2> {
public int x;
public int y;
public Int2 (int x, int y) {
this.x = x;
this.y = y;
}
public long sqrMagnitudeLong {
get {
return (long)x*(long)x+(long)y*(long)y;
}
}
public static Int2 operator - (Int2 lhs) {
lhs.x = -lhs.x;
lhs.y = -lhs.y;
return lhs;
}
public static Int2 operator + (Int2 a, Int2 b) {
return new Int2(a.x+b.x, a.y+b.y);
}
public static Int2 operator - (Int2 a, Int2 b) {
return new Int2(a.x-b.x, a.y-b.y);
}
public static bool operator == (Int2 a, Int2 b) {
return a.x == b.x && a.y == b.y;
}
public static bool operator != (Int2 a, Int2 b) {
return a.x != b.x || a.y != b.y;
}
/// <summary>Dot product of the two coordinates</summary>
public static long DotLong (Int2 a, Int2 b) {
return (long)a.x*(long)b.x + (long)a.y*(long)b.y;
}
public override bool Equals (System.Object o) {
if (o == null) return false;
var rhs = (Int2)o;
return x == rhs.x && y == rhs.y;
}
#region IEquatable implementation
public bool Equals (Int2 other) {
return x == other.x && y == other.y;
}
#endregion
public override int GetHashCode () {
return x*49157+y*98317;
}
public static Int2 Min (Int2 a, Int2 b) {
return new Int2(System.Math.Min(a.x, b.x), System.Math.Min(a.y, b.y));
}
public static Int2 Max (Int2 a, Int2 b) {
return new Int2(System.Math.Max(a.x, b.x), System.Math.Max(a.y, b.y));
}
public static Int2 FromInt3XZ (Int3 o) {
return new Int2(o.x, o.z);
}
public static Int3 ToInt3XZ (Int2 o) {
return new Int3(o.x, 0, o.y);
}
public override string ToString () {
return "("+x+", " +y+")";
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0dfda11c977a492fb003ee4fee3f47b1
timeCreated: 1707118870

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3fa8e1f5ed4b49ceb6a2f9dc9288eee7
timeCreated: 1707118797

View File

@@ -1,4 +1,6 @@

using System;
namespace Plugins.JNGame.Sync.Frame.Entity
{
public class JNTime
@@ -45,8 +47,13 @@ namespace Plugins.JNGame.Sync.Frame.Entity
/// Time.timeSinceLevelLoad是一个表示从当前场景开始到现在所经过的时间的属性单位是秒。这个属性通常用于计算从加载关卡开始到当前帧的时间并且会随着游戏的暂停而停止计算
/// </summary>
public float timeSinceLevelLoad => _sync == null ? UnityEngine.Time.timeSinceLevelLoad : ((float)_sync.NSyncTime / _sync.NDivideFrame) / 1000;
/// <summary>
/// System.DateTime.UtcNow.Ticks
/// </summary>
public long Ticks => _sync == null ? DateTime.UtcNow.Ticks : _sync.NSyncTime * _sync.NLocalRunFrame;
public JNTime(JNSyncFrame sync)
{
Time = this;

View File

@@ -10,6 +10,12 @@ namespace Plugins.JNGame.Sync.Frame.game
public int _nId;
[HideInInspector]
public int NID => _nId;
//是否执行Start方法
[HideInInspector]
public Boolean IsRunStart = false;
public abstract void OnSyncStart();
public abstract Boolean IsInput();

View File

@@ -95,7 +95,7 @@ namespace Plugins.JNGame.Sync.Frame
public override Task OnInit()
{
Physics.simulationMode = SimulationMode.Script;
Physics.autoSimulation = false;
Physics.autoSyncTransforms = false;
this.OnReset();
return Task.CompletedTask;
@@ -127,10 +127,11 @@ namespace Plugins.JNGame.Sync.Frame
this.dtInputTotal = 0;
this._isRequestServerData = false;
Physics.SyncTransforms();
//清除定时器
SingletonUtil<JNFrameTime>.Clean();
// Physics.SyncTransforms();
EventDispatcher.Event.Dispatch(JNSyncFrameEvent.CREATE);
}
@@ -145,10 +146,11 @@ namespace Plugins.JNGame.Sync.Frame
dtInputTotal += dt;
int nSyncTime = this.DyTime();
this._isLoop = this._nFrameQueue.Count > 20;
if(nSyncTime > 0){
while(nSyncTime != 0 && this.dtTotal > nSyncTime){
this._isLoop = false;
this.OnUpdate();
this.dtTotal -= nSyncTime;
nSyncTime = this.DyTime();
@@ -158,7 +160,6 @@ namespace Plugins.JNGame.Sync.Frame
long endTime = (new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()) + 66;
while(this.DyTime() == 0 && (new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()) < endTime)
{
this._isLoop = true;
this.OnUpdate();
}
dtTotal = 0;
@@ -224,6 +225,16 @@ namespace Plugins.JNGame.Sync.Frame
// Debug.Log(inputs.Count);
//运行生命周期
this._nSyncActors.ToList().ForEach(child =>
{
if (!child.IsRunStart)
{
child.IsRunStart = true;
child.OnSyncStart();
}
});
//运行之前帧
this._nSyncActors.ToList().ForEach(child =>
{
@@ -256,10 +267,9 @@ namespace Plugins.JNGame.Sync.Frame
//执行定时器
SingletonUtil<JNFrameTime>.Instance.Update(dt);
// //执行下一帧物理
//执行下一帧物理
// Physics.Simulate((float)dt / 1000);
// Physics.SyncTransforms();
Physics.SyncTransforms();
}