mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-11-11 08:38:45 +00:00
提取RVO 寻路
This commit is contained in:
@@ -8,7 +8,7 @@ namespace Game.Plugins.App
|
||||
|
||||
public static readonly JNGSocket Socket = new JNGSocket();
|
||||
public static readonly JNGSyncFrame Sync = new JNGSyncFrame();
|
||||
public static readonly JAPI Api = new(new JAPIConfig(){BaseURL = "http://127.0.0.1:8080"});
|
||||
public static readonly JAPI Api = new(new JAPIConfig(){BaseURL = "http://192.168.0.116:8080"});
|
||||
public static readonly EventDispatcher Event = EventDispatcher.Event;
|
||||
|
||||
public static SystemBase[] System()
|
||||
|
||||
3
JNFrame/Assets/Game/Plugins/App/Game/RVO.meta
Normal file
3
JNFrame/Assets/Game/Plugins/App/Game/RVO.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78fd52a1d23c49618d1da7e84e276adc
|
||||
timeCreated: 1707121689
|
||||
28
JNFrame/Assets/Game/Plugins/App/Game/RVO/JNGRVOManager.cs
Normal file
28
JNFrame/Assets/Game/Plugins/App/Game/RVO/JNGRVOManager.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Game.Plugins.App.Sync;
|
||||
using Game.Plugins.JNGame.Sync.Frame.AstarPath.RVO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Game.Plugins.App.Game.RVO
|
||||
{
|
||||
//RVO 寻路
|
||||
public class JNGRVOManager : JNGSyncFrameDefault
|
||||
{
|
||||
|
||||
private JNRVOSimulator _simulator = new JNRVOSimulator(MovementPlane.XY);
|
||||
public JNRVOSimulator Simulator => _simulator;
|
||||
|
||||
public override void OnSyncLoad()
|
||||
{
|
||||
base.OnSyncLoad();
|
||||
}
|
||||
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
Simulator.DesiredDeltaTime = 1.0f / (1000f / dt);
|
||||
Simulator.symmetryBreakingBias = 0.1f;
|
||||
//更新寻路
|
||||
Simulator.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ed6c9606e7b4be7844512656ed3b2cd
|
||||
timeCreated: 1707121704
|
||||
@@ -8,7 +8,7 @@ namespace Game.Plugins.App
|
||||
protected override async UniTask<string> GetUrl()
|
||||
{
|
||||
await UniTask.NextFrame();
|
||||
return "ws://127.0.0.1:8080/websocket";
|
||||
return "ws://192.168.0.116:8080/websocket";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ namespace Game.Plugins.App.Sync
|
||||
}
|
||||
|
||||
public override void OnSyncLoad(){}
|
||||
public override void OnSyncStart(){}
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, T input)
|
||||
{
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace Game.Plugins.App.Sync
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void OnLateSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -28,18 +28,18 @@ namespace Pathfinding {
|
||||
// This is enough in theory, but this script will also update the destination every
|
||||
// frame as the destination is used for debugging and may be used for other things by other
|
||||
// scripts as well. So it makes sense that it is up to date every frame.
|
||||
if (ai != null) ai.onSearchPath += OnUpdate;
|
||||
if (ai != null) ai.onSearchPath += OnUpdatePos;
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
if (ai != null) ai.onSearchPath -= OnUpdate;
|
||||
if (ai != null) ai.onSearchPath -= OnUpdatePos;
|
||||
}
|
||||
|
||||
/// <summary>Updates the AI's destination every frame</summary>
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
this.OnUpdate();
|
||||
this.OnUpdatePos();
|
||||
}
|
||||
|
||||
public void SetTarget(Vector3 pos)
|
||||
@@ -47,7 +47,7 @@ namespace Pathfinding {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public void OnUpdate()
|
||||
public void OnUpdatePos()
|
||||
{
|
||||
if (ai != null) ai.destination = pos;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,9 @@ namespace Pathfinding {
|
||||
}
|
||||
|
||||
/// <summary>Update is called once per frame</summary>
|
||||
void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
if (targets.Length == 0) return;
|
||||
|
||||
// Note: using reachedEndOfPath and pathPending instead of reachedDestination here because
|
||||
|
||||
@@ -374,12 +374,9 @@ namespace Pathfinding {
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts searching for paths.
|
||||
/// If you override this method you should in most cases call base.Start () at the start of it.
|
||||
/// See: <see cref="Init"/>
|
||||
/// </summary>
|
||||
protected virtual void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
startHasRun = true;
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -377,13 +377,16 @@ namespace Pathfinding {
|
||||
seeker.startEndModifier.adjustStartPoint = () => simulatedPosition;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Starts searching for paths.
|
||||
/// If you override this function you should in most cases call base.Start () at the start of it.
|
||||
/// See: <see cref="Init"/>
|
||||
/// See: <see cref="RepeatTrySearchPath"/>
|
||||
/// </summary>
|
||||
protected virtual void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
startHasRun = true;
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ namespace Pathfinding.Examples {
|
||||
public GraphNode targetNode;
|
||||
public BlockManager.TraversalProvider traversalProvider;
|
||||
|
||||
void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
blocker.BlockAtCurrentPosition();
|
||||
}
|
||||
|
||||
|
||||
@@ -988,7 +988,12 @@ public class AstarPath : VersionedMonoBehaviour {
|
||||
// and then processes the graph updates
|
||||
AddWorkItem(new AstarWorkItem(() => {
|
||||
graphUpdatesWorkItemAdded = false;
|
||||
lastGraphUpdate = Time.realtimeSinceStartup;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
lastGraphUpdate = Time.realtimeSinceStartup;
|
||||
#else
|
||||
lastGraphUpdate = GetSync().Time.realtimeSinceStartup;
|
||||
#endif
|
||||
|
||||
workItem.init();
|
||||
}, workItem.update));
|
||||
|
||||
@@ -130,8 +130,11 @@ namespace Pathfinding {
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("useWorldSpace")]
|
||||
private bool legacyUseWorldSpace;
|
||||
|
||||
|
||||
/// <summary>Do some stuff at start</summary>
|
||||
public void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
if (!Application.isPlaying) return;
|
||||
|
||||
// If firstApplied is true, that means the graph was scanned during Awake.
|
||||
@@ -140,7 +143,7 @@ namespace Pathfinding {
|
||||
Apply();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void OnPostScan () {
|
||||
if (applyOnScan) Apply();
|
||||
}
|
||||
|
||||
@@ -91,7 +91,9 @@ namespace Pathfinding {
|
||||
float graphHeight = 100;
|
||||
float graphOffset = 50;
|
||||
|
||||
public void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
useGUILayout = false;
|
||||
|
||||
fpsDrops = new float[fpsDropCounterSize];
|
||||
|
||||
@@ -2,6 +2,7 @@ using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Plugins.JNGame.Sync.Frame.Entity;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace Pathfinding {
|
||||
@@ -298,7 +299,7 @@ namespace Pathfinding {
|
||||
// One tick is 1/10000 of a millisecond.
|
||||
// We need to check once in a while if the thread should be stopped.
|
||||
long maxTicks = (long)(10*10000);
|
||||
long targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
|
||||
long targetTick = JNTime.Time.Ticks + maxTicks;
|
||||
while (true) {
|
||||
// The path we are currently calculating
|
||||
Path path = queue.Pop();
|
||||
@@ -321,7 +322,7 @@ namespace Pathfinding {
|
||||
}
|
||||
|
||||
// Tick for when the path started, used for calculating how long time the calculation took
|
||||
long startTicks = System.DateTime.UtcNow.Ticks;
|
||||
long startTicks = JNTime.Time.Ticks;
|
||||
|
||||
// Prepare the path
|
||||
ipath.Prepare();
|
||||
@@ -344,7 +345,7 @@ namespace Pathfinding {
|
||||
// or when it has finished calculation
|
||||
ipath.CalculateStep(targetTick);
|
||||
|
||||
targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
|
||||
targetTick = JNTime.Time.Ticks + maxTicks;
|
||||
|
||||
// Cancel function (and thus the thread) if no more paths should be accepted.
|
||||
// This is done when the A* object is about to be destroyed
|
||||
@@ -354,11 +355,11 @@ namespace Pathfinding {
|
||||
}
|
||||
}
|
||||
|
||||
path.duration = (System.DateTime.UtcNow.Ticks - startTicks)*0.0001F;
|
||||
path.duration = (JNTime.Time.Ticks - startTicks)*0.0001F;
|
||||
|
||||
#if ProfileAstar
|
||||
System.Threading.Interlocked.Increment(ref AstarPath.PathsCompleted);
|
||||
System.Threading.Interlocked.Add(ref AstarPath.TotalSearchTime, System.DateTime.UtcNow.Ticks - startTicks);
|
||||
System.Threading.Interlocked.Add(ref AstarPath.TotalSearchTime, JNTime.Time.Ticks - startTicks);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -421,7 +422,7 @@ namespace Pathfinding {
|
||||
IEnumerator CalculatePaths (PathHandler pathHandler) {
|
||||
// Max number of ticks before yielding/sleeping
|
||||
long maxTicks = (long)(astar.maxFrameTime*10000);
|
||||
long targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
|
||||
long targetTick = JNTime.Time.Ticks + maxTicks;
|
||||
|
||||
while (true) {
|
||||
// The path we are currently calculating
|
||||
@@ -463,7 +464,7 @@ namespace Pathfinding {
|
||||
if (tmpOnPathPreSearch != null) tmpOnPathPreSearch(p);
|
||||
|
||||
// Tick for when the path started, used for calculating how long time the calculation took
|
||||
long startTicks = System.DateTime.UtcNow.Ticks;
|
||||
long startTicks = JNTime.Time.Ticks;
|
||||
long totalTicks = 0;
|
||||
|
||||
|
||||
@@ -493,12 +494,12 @@ namespace Pathfinding {
|
||||
// Improves latency
|
||||
if (p.CompleteState != PathCompleteState.NotCalculated) break;
|
||||
|
||||
totalTicks += System.DateTime.UtcNow.Ticks-startTicks;
|
||||
totalTicks += JNTime.Time.Ticks-startTicks;
|
||||
// Yield/sleep so other threads can work
|
||||
|
||||
yield return null;
|
||||
|
||||
startTicks = System.DateTime.UtcNow.Ticks;
|
||||
startTicks = JNTime.Time.Ticks;
|
||||
|
||||
// Cancel function (and thus the thread) if no more paths should be accepted.
|
||||
// This is done when the A* object is about to be destroyed
|
||||
@@ -507,10 +508,10 @@ namespace Pathfinding {
|
||||
p.FailWithError("AstarPath object destroyed");
|
||||
}
|
||||
|
||||
targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
|
||||
targetTick = JNTime.Time.Ticks + maxTicks;
|
||||
}
|
||||
|
||||
totalTicks += System.DateTime.UtcNow.Ticks-startTicks;
|
||||
totalTicks += JNTime.Time.Ticks-startTicks;
|
||||
p.duration = totalTicks*0.0001F;
|
||||
|
||||
#if ProfileAstar
|
||||
@@ -541,9 +542,9 @@ namespace Pathfinding {
|
||||
|
||||
|
||||
// Wait a bit if we have calculated a lot of paths
|
||||
if (System.DateTime.UtcNow.Ticks > targetTick) {
|
||||
if (JNTime.Time.Ticks > targetTick) {
|
||||
yield return null;
|
||||
targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
|
||||
targetTick = JNTime.Time.Ticks + maxTicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Plugins.JNGame.Sync.Frame.Entity;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace Pathfinding {
|
||||
@@ -36,7 +37,7 @@ namespace Pathfinding {
|
||||
Profiler.BeginSample("Calling Path Callbacks");
|
||||
|
||||
// Hard coded limit on 1.0 ms
|
||||
long targetTick = timeSlice ? System.DateTime.UtcNow.Ticks + 1 * 10000 : 0;
|
||||
long targetTick = timeSlice ? JNTime.Time.Ticks + 1 * 10000 : 0;
|
||||
|
||||
int counter = 0;
|
||||
// Loop through the linked list and return all paths
|
||||
@@ -66,7 +67,7 @@ namespace Pathfinding {
|
||||
// At least 5 paths will be returned, even if timeSlice is enabled
|
||||
if (counter > 5 && timeSlice) {
|
||||
counter = 0;
|
||||
if (System.DateTime.UtcNow.Ticks >= targetTick) {
|
||||
if (JNTime.Time.Ticks >= targetTick) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,14 +446,12 @@ namespace Pathfinding.RVO.Sampled {
|
||||
public static VO SegmentObstacle (Vector2 segmentStart, Vector2 segmentEnd, Vector2 offset, float radius, float inverseDt, float inverseDeltaTime) {
|
||||
var vo = new VO();
|
||||
|
||||
// Adjusted so that a parameter weightFactor of 1 will be the default ("natural") weight factor
|
||||
vo.weightFactor = 1;
|
||||
// Just higher than anything else
|
||||
vo.weightBonus = Mathf.Max(radius, 1)*40;
|
||||
|
||||
var closestOnSegment = VectorMath.ClosestPointOnSegment(segmentStart, segmentEnd, Vector2.zero);
|
||||
|
||||
// Collision?
|
||||
if (closestOnSegment.magnitude <= radius) {
|
||||
vo.colliding = true;
|
||||
|
||||
@@ -482,8 +480,6 @@ namespace Pathfinding.RVO.Sampled {
|
||||
vo.cutoffLine = segmentStart + new Vector2(-cutoffTangent.y, cutoffTangent.x) * radius;
|
||||
vo.cutoffLine += offset;
|
||||
|
||||
// See documentation for details
|
||||
// The call to Max is just to prevent floating point errors causing NaNs to appear
|
||||
var startSqrMagnitude = segmentStart.sqrMagnitude;
|
||||
var normal1 = -VectorMath.ComplexMultiply(segmentStart, new Vector2(radius, Mathf.Sqrt(Mathf.Max(0, startSqrMagnitude - radius*radius)))) / startSqrMagnitude;
|
||||
var endSqrMagnitude = segmentEnd.sqrMagnitude;
|
||||
@@ -492,7 +488,6 @@ namespace Pathfinding.RVO.Sampled {
|
||||
vo.line1 = segmentStart + normal1 * radius + offset;
|
||||
vo.line2 = segmentEnd + normal2 * radius + offset;
|
||||
|
||||
// Note that the normals are already normalized
|
||||
vo.dir1 = new Vector2(normal1.y, -normal1.x);
|
||||
vo.dir2 = new Vector2(normal2.y, -normal2.x);
|
||||
|
||||
|
||||
@@ -314,7 +314,10 @@ namespace Pathfinding.RVO {
|
||||
public float DeltaTime { get { return deltaTime; } }
|
||||
|
||||
/// <summary>Is using multithreading</summary>
|
||||
public bool Multithreading { get { return workers != null && workers.Length > 0; } }
|
||||
public bool Multithreading
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Time in seconds between each simulation step.
|
||||
@@ -722,7 +725,7 @@ namespace Pathfinding.RVO {
|
||||
doUpdateObstacles = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Should be called once per frame</summary>
|
||||
public void Update () {
|
||||
// Initialize last step
|
||||
|
||||
@@ -24,7 +24,9 @@ namespace Pathfinding.Examples {
|
||||
|
||||
Camera cam;
|
||||
|
||||
public void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
cam = Camera.main;
|
||||
var simu = RVOSimulator.active;
|
||||
if (simu == null) {
|
||||
|
||||
@@ -89,7 +89,10 @@ namespace Pathfinding.Examples {
|
||||
Vector2[] interpolatedVelocities;
|
||||
Vector2[] interpolatedRotations;
|
||||
|
||||
public void Start () {
|
||||
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
mesh = new Mesh();
|
||||
RVOSimulator rvoSim = FindObjectOfType(typeof(RVOSimulator)) as RVOSimulator;
|
||||
if (rvoSim == null) {
|
||||
@@ -230,7 +233,9 @@ namespace Pathfinding.Examples {
|
||||
}
|
||||
}
|
||||
|
||||
public void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
if (agents == null || mesh == null) return;
|
||||
|
||||
if (agents.Count != goals.Count) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using Game.Plugins.App.Sync;
|
||||
|
||||
namespace Pathfinding.Examples {
|
||||
/// <summary>
|
||||
@@ -7,7 +8,7 @@ namespace Pathfinding.Examples {
|
||||
/// Used in a example scene
|
||||
/// </summary>
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_examples_1_1_r_v_o_agent_placer.php")]
|
||||
public class RVOAgentPlacer : MonoBehaviour {
|
||||
public class RVOAgentPlacer : JNGSyncFrameDefault {
|
||||
public int agents = 100;
|
||||
|
||||
public float ringSize = 100;
|
||||
@@ -19,9 +20,9 @@ namespace Pathfinding.Examples {
|
||||
|
||||
public float repathRate = 1;
|
||||
|
||||
// Use this for initialization
|
||||
IEnumerator Start () {
|
||||
yield return null;
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
|
||||
for (int i = 0; i < agents; i++) {
|
||||
float angle = ((float)i / agents)*(float)System.Math.PI*2;
|
||||
@@ -34,7 +35,7 @@ namespace Pathfinding.Examples {
|
||||
|
||||
if (ag == null) {
|
||||
Debug.LogError("Prefab does not have an RVOExampleAgent component attached");
|
||||
yield break;
|
||||
return;
|
||||
}
|
||||
|
||||
//ag.radius = radius;
|
||||
|
||||
@@ -125,7 +125,9 @@ namespace Pathfinding.Examples {
|
||||
}
|
||||
}
|
||||
|
||||
public void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
if (GetSync().Time.time >= nextRepath && canSearchAgain) {
|
||||
RecalculatePath();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Game.Plugins.App.Sync;
|
||||
|
||||
namespace Pathfinding.Examples {
|
||||
/// <summary>Example script for generating an infinite procedural world</summary>
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_examples_1_1_procedural_world.php")]
|
||||
public class ProceduralWorld : MonoBehaviour {
|
||||
public class ProceduralWorld : JNGSyncFrameDefault {
|
||||
public Transform target;
|
||||
|
||||
public ProceduralPrefab[] prefabs;
|
||||
@@ -78,17 +79,20 @@ namespace Pathfinding.Examples {
|
||||
Dictionary<Int2, ProceduralTile> tiles = new Dictionary<Int2, ProceduralTile>();
|
||||
|
||||
// Use this for initialization
|
||||
void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
// Calculate the closest tiles
|
||||
// and then recalculate the graph
|
||||
Update();
|
||||
// UnityEngine.PlayerLoop.Update();
|
||||
AstarPath.active.Scan();
|
||||
|
||||
StartCoroutine(GenerateTiles());
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
// Calculate the tile the target is standing on
|
||||
Int2 p = new Int2(Mathf.RoundToInt((target.position.x - tileSize*0.5f) / tileSize), Mathf.RoundToInt((target.position.z - tileSize*0.5f) / tileSize));
|
||||
|
||||
|
||||
@@ -48,8 +48,9 @@ namespace Pathfinding.Examples {
|
||||
return Vector3.Cross(derivate, secondDerivative) / (dx*dx*dx);
|
||||
}
|
||||
|
||||
/// <summary>Update is called once per frame</summary>
|
||||
void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
// Move the agent a small distance along the path, according to its speed
|
||||
float mn = time;
|
||||
float mx = time+1;
|
||||
|
||||
@@ -8,7 +8,9 @@ namespace Pathfinding {
|
||||
Matrix4x4 originalMatrix;
|
||||
public GraphTransform transformation { get; private set; }
|
||||
|
||||
void Start () {
|
||||
public override void OnSyncLoad()
|
||||
{
|
||||
base.OnSyncLoad();
|
||||
originalMatrix = transform.worldToLocalMatrix;
|
||||
transform.hasChanged = true;
|
||||
Refresh();
|
||||
|
||||
@@ -41,9 +41,10 @@ namespace Pathfinding.Examples {
|
||||
movementPlane = graph.transformation;
|
||||
}
|
||||
|
||||
protected override void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
RefreshTransform();
|
||||
base.Start();
|
||||
base.OnSyncStart();
|
||||
}
|
||||
|
||||
protected override void CalculatePathRequestEndpoints (out Vector3 start, out Vector3 end) {
|
||||
|
||||
@@ -1,31 +1,37 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Game.Plugins.App.Sync;
|
||||
|
||||
namespace Pathfinding.Examples {
|
||||
/// <summary>Helper script in the example scene 'Turn Based'</summary>
|
||||
[RequireComponent(typeof(Animator))]
|
||||
[RequireComponent(typeof(SingleNodeBlocker))]
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_examples_1_1_turn_based_door.php")]
|
||||
public class TurnBasedDoor : MonoBehaviour {
|
||||
public class TurnBasedDoor : JNGSyncFrameDefault {
|
||||
Animator animator;
|
||||
SingleNodeBlocker blocker;
|
||||
|
||||
bool open;
|
||||
|
||||
void Awake () {
|
||||
|
||||
public override void OnSyncLoad()
|
||||
{
|
||||
base.OnSyncLoad();
|
||||
animator = GetComponent<Animator>();
|
||||
blocker = GetComponent<SingleNodeBlocker>();
|
||||
}
|
||||
|
||||
void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
// Make sure the door starts out blocked
|
||||
blocker.BlockAtCurrentPosition();
|
||||
animator.CrossFade("close", 0.2f);
|
||||
}
|
||||
|
||||
public void Close () {
|
||||
StartCoroutine(WaitAndClose());
|
||||
// StartCoroutine(WaitAndClose());
|
||||
}
|
||||
|
||||
IEnumerator WaitAndClose () {
|
||||
|
||||
@@ -31,7 +31,9 @@ namespace Pathfinding.Examples {
|
||||
eventSystem = FindObjectOfType<EventSystem>();
|
||||
}
|
||||
|
||||
void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
|
||||
|
||||
// Ignore any input while the mouse is over a UI element
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using Game.Plugins.App.Sync;
|
||||
|
||||
namespace Pathfinding.Examples {
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_examples_1_1_example_mover.php")]
|
||||
public class ExampleMover : MonoBehaviour {
|
||||
public class ExampleMover : JNGSyncFrameDefault {
|
||||
RVOExampleAgent agent;
|
||||
public Transform target;
|
||||
|
||||
// Use this for initialization
|
||||
void Awake () {
|
||||
public override void OnSyncLoad()
|
||||
{
|
||||
base.OnSyncLoad();
|
||||
agent = GetComponent<RVOExampleAgent>();
|
||||
}
|
||||
|
||||
void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
agent.SetTarget(target.position);
|
||||
}
|
||||
|
||||
void LateUpdate () {
|
||||
public override void OnLateSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnLateSyncUpdate(dt, frame, input);
|
||||
if (Input.GetKeyDown(KeyCode.Mouse0)) {
|
||||
agent.SetTarget(target.position);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Game.Plugins.App.Sync;
|
||||
using Pathfinding.Util;
|
||||
namespace Pathfinding.Examples {
|
||||
/// <summary>
|
||||
@@ -17,7 +18,7 @@ namespace Pathfinding.Examples {
|
||||
/// See: Pathfinding.FloodPathTracer
|
||||
/// </summary>
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_examples_1_1_path_types_demo.php")]
|
||||
public class PathTypesDemo : MonoBehaviour {
|
||||
public class PathTypesDemo : JNGSyncFrameDefault {
|
||||
public DemoMode activeDemo = DemoMode.ABPath;
|
||||
|
||||
public enum DemoMode {
|
||||
@@ -64,7 +65,9 @@ namespace Pathfinding.Examples {
|
||||
List<Vector3> multipoints = new List<Vector3>();
|
||||
|
||||
// Update is called once per frame
|
||||
void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
|
||||
|
||||
// Find the intersection with the y=0 plane
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Game.Plugins.App.Sync;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding.Examples {
|
||||
/// <summary>Example script used in the example scenes</summary>
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_examples_1_1_door_controller.php")]
|
||||
public class DoorController : MonoBehaviour {
|
||||
public class DoorController : JNGSyncFrameDefault {
|
||||
private bool open = false;
|
||||
|
||||
public int opentag = 1;
|
||||
@@ -13,7 +14,9 @@ namespace Pathfinding.Examples {
|
||||
|
||||
Bounds bounds;
|
||||
|
||||
public void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
// Capture the bounds of the collider while it is closed
|
||||
bounds = GetComponent<Collider>().bounds;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Game.Plugins.App.Sync;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
@@ -19,10 +20,12 @@ namespace Pathfinding {
|
||||
/// </summary>
|
||||
[AddComponentMenu("Pathfinding/Navmesh/RecastTileUpdate")]
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_recast_tile_update.php")]
|
||||
public class RecastTileUpdate : MonoBehaviour {
|
||||
public class RecastTileUpdate : JNGSyncFrameDefault {
|
||||
public static event System.Action<Bounds> OnNeedUpdates;
|
||||
|
||||
void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
ScheduleUpdate();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using Game.Plugins.App.Sync;
|
||||
using Pathfinding;
|
||||
|
||||
namespace Pathfinding.Examples {
|
||||
@@ -9,8 +10,10 @@ namespace Pathfinding.Examples {
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
[HelpURL("https://arongranberg.com/astar/documentation/stable/class_snap_to_node.php")]
|
||||
public class SnapToNode : MonoBehaviour {
|
||||
void Update () {
|
||||
public class SnapToNode : JNGSyncFrameDefault {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
if (transform.hasChanged && AstarPath.active != null) {
|
||||
var node = AstarPath.active.GetNearest(transform.position, NNConstraint.None).node;
|
||||
if (node != null) {
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace Pathfinding {
|
||||
|
||||
Camera cam;
|
||||
|
||||
public void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
//Cache the Main Camera
|
||||
cam = Camera.main;
|
||||
// Slightly inefficient way of finding all AIs, but this is just an example script, so it doesn't matter much.
|
||||
@@ -41,12 +43,14 @@ namespace Pathfinding {
|
||||
}
|
||||
|
||||
/// <summary>Update is called once per frame</summary>
|
||||
void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
if (!onlyOnDoubleClick && cam != null) {
|
||||
UpdateTargetPosition();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UpdateTargetPosition () {
|
||||
Vector3 newPosition = Vector3.zero;
|
||||
bool positionFound = false;
|
||||
|
||||
@@ -211,9 +211,9 @@ namespace Pathfinding {
|
||||
/// <param name="label">Label to display</param>
|
||||
/// <param name="selected">Current LayerMask</param>
|
||||
public static LayerMask LayerMaskField (string label, LayerMask selected) {
|
||||
if (Event.current.type == EventType.Layout && System.DateTime.UtcNow.Ticks - lastUpdateTick > 10000000L) {
|
||||
if (Event.current.type == EventType.Layout && JNTime.Time.Ticks - lastUpdateTick > 10000000L) {
|
||||
layerNames.Clear();
|
||||
lastUpdateTick = System.DateTime.UtcNow.Ticks;
|
||||
lastUpdateTick = JNTime.Time.Ticks;
|
||||
}
|
||||
|
||||
string[] currentLayerNames;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Plugins.JNGame.Sync.Frame.Entity;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
@@ -613,7 +614,7 @@ namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Calculates the path until completed or until the time has passed targetTick.
|
||||
/// Usually a check is only done every 500 nodes if the time has passed targetTick.
|
||||
/// Time/Ticks are got from System.DateTime.UtcNow.Ticks.
|
||||
/// Time/Ticks are got from JNTime.Time.Ticks.
|
||||
///
|
||||
/// Basic outline of what the function does for the standard path (Pathfinding.ABPath).
|
||||
/// <code>
|
||||
@@ -670,7 +671,7 @@ namespace Pathfinding {
|
||||
// Check for time every 500 nodes, roughly every 0.5 ms usually
|
||||
if (counter > 500) {
|
||||
// Have we exceded the maxFrameTime, if so we should wait one frame before continuing the search since we don't want the game to lag
|
||||
if (System.DateTime.UtcNow.Ticks >= targetTick) {
|
||||
if (JNTime.Time.Ticks >= targetTick) {
|
||||
// Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Plugins.JNGame.Sync.Frame.Entity;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
@@ -598,7 +599,7 @@ namespace Pathfinding {
|
||||
// Check for time every 500 nodes, roughly every 0.5 ms usually
|
||||
if (counter > 500) {
|
||||
// Have we exceded the maxFrameTime, if so we should wait one frame before continuing the search since we don't want the game to lag
|
||||
if (System.DateTime.UtcNow.Ticks >= targetTick) {
|
||||
if (JNTime.Time.Ticks >= targetTick) {
|
||||
// Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Plugins.JNGame.Sync.Frame.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
@@ -260,7 +261,7 @@ namespace Pathfinding {
|
||||
// Check for time every 500 nodes, roughly every 0.5 ms usually
|
||||
if (counter > 500) {
|
||||
// Have we exceded the maxFrameTime, if so we should wait one frame before continuing the search since we don't want the game to lag
|
||||
if (System.DateTime.UtcNow.Ticks >= targetTick) {
|
||||
if (JNTime.Time.Ticks >= targetTick) {
|
||||
// Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Plugins.JNGame.Sync.Frame.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
@@ -102,7 +103,7 @@ namespace Pathfinding {
|
||||
// Check for time every 500 nodes, roughly every 0.5 ms usually
|
||||
if (counter > 500) {
|
||||
// Have we exceded the maxFrameTime, if so we should wait one frame before continuing the search since we don't want the game to lag
|
||||
if (System.DateTime.UtcNow.Ticks >= targetTick) {
|
||||
if (JNTime.Time.Ticks >= targetTick) {
|
||||
//Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,9 @@ namespace Pathfinding.RVO {
|
||||
}
|
||||
|
||||
/// <summary>Creates obstacles</summary>
|
||||
public void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
addedObstacles = new List<ObstacleVertex>();
|
||||
sourceObstacles = new List<Vector3[]>();
|
||||
prevUpdateMatrix = GetMatrix();
|
||||
@@ -212,7 +214,9 @@ namespace Pathfinding.RVO {
|
||||
/// Updates obstacle if required.
|
||||
/// Checks for if the obstacle should be updated (e.g if it has moved)
|
||||
/// </summary>
|
||||
public void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
Matrix4x4 m = GetMatrix();
|
||||
|
||||
if (m != prevUpdateMatrix) {
|
||||
@@ -223,7 +227,6 @@ namespace Pathfinding.RVO {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finds a simulator in the scene.
|
||||
///
|
||||
|
||||
@@ -75,7 +75,9 @@ namespace Pathfinding {
|
||||
#endregion
|
||||
}
|
||||
|
||||
void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
if (!AstarPath.active)
|
||||
throw new System.Exception("No AstarPath object in the scene");
|
||||
}
|
||||
|
||||
@@ -69,7 +69,9 @@ namespace Pathfinding {
|
||||
[HideInInspector]
|
||||
public int graphIndex;
|
||||
|
||||
void Start () {
|
||||
public override void OnSyncStart()
|
||||
{
|
||||
base.OnSyncStart();
|
||||
if (AstarPath.active == null) throw new System.Exception("There is no AstarPath object in the scene");
|
||||
|
||||
// If one creates this component via a script then they may have already set the graph field.
|
||||
@@ -84,9 +86,11 @@ namespace Pathfinding {
|
||||
|
||||
UpdateGraph();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Update is called once per frame</summary>
|
||||
void Update () {
|
||||
public override void OnSyncUpdate(int dt, JNFrameInfo frame, Object input)
|
||||
{
|
||||
base.OnSyncUpdate(dt, frame, input);
|
||||
if (graph == null) return;
|
||||
|
||||
// Calculate where the graph center and the target position is in graph space
|
||||
|
||||
8
JNFrame/Assets/Game/Plugins/JNGame/Sync/Frame/AStar.meta
Normal file
8
JNFrame/Assets/Game/Plugins/JNGame/Sync/Frame/AStar.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fcc24a8c5f74ee29f3ae27768122f30
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Game.Plugins.JNGame.Sync.Frame.AstarPath
|
||||
{
|
||||
public class JNAStarBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3417f25b3114ecc8e596be23f70851f
|
||||
timeCreated: 1707104675
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
namespace Game.Plugins.JNGame.Sync.Frame.AstarPath
|
||||
{
|
||||
/**
|
||||
* 寻路类
|
||||
*/
|
||||
public class JNAStarPath
|
||||
{
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79d7cad08d874b6aba4388770d624c79
|
||||
timeCreated: 1707104391
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a19843b56634bfca2b3e6234c425d8a
|
||||
timeCreated: 1707102377
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 821e5ded88f4428a9b5aad0aed6ffd1a
|
||||
timeCreated: 1707114274
|
||||
@@ -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; // 指向上一个顶点的引用。
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4aedb5e02e7a44d38eea97702c35fdc7
|
||||
timeCreated: 1707113948
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 660414f56e8b4287a616f5c4144acced
|
||||
timeCreated: 1707116244
|
||||
@@ -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>
|
||||
/// 判断用于移动的是XY(2D)平面还是XZ(3D)平面。
|
||||
/// </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(); // 计算四叉树中所有元素的速度(用于碰撞检测等)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11e5d1e71ebb4bc2982a35fe3a32eab1
|
||||
timeCreated: 1707104628
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 064be9ef1f5e4efbb820dd632b98d5be
|
||||
timeCreated: 1707118794
|
||||
320
JNFrame/Assets/Game/Plugins/JNGame/Sync/Frame/AStar/Util/Int3.cs
Normal file
320
JNFrame/Assets/Game/Plugins/JNGame/Sync/Frame/AStar/Util/Int3.cs
Normal 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+")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0dfda11c977a492fb003ee4fee3f47b1
|
||||
timeCreated: 1707118870
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fa8e1f5ed4b49ceb6a2f9dc9288eee7
|
||||
timeCreated: 1707118797
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Plugins.JNGame.Util
|
||||
|
||||
public async UniTask<T> Post<T,TA>(string url,TA data)
|
||||
{
|
||||
var request = UnityWebRequest.PostWwwForm($"{this._config.BaseURL}/{url}",JsonConvert.SerializeObject(data));
|
||||
var request = UnityWebRequest.Post($"{this._config.BaseURL}/{url}",JsonConvert.SerializeObject(data));
|
||||
return JsonConvert.DeserializeObject<T>((await request.SendWebRequest()).downloadHandler.text);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user