- 补充游戏核心逻辑
- 完成 TodOList P0
- 添加游戏内控制台便于调试
- 增加敌人互斥机制提高运算负载
- 优化互斥算法将游戏性能基准线拉到 1k 敌人
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace CustomUtility
|
||||
{
|
||||
public static class EnemySeparationSolverProvider
|
||||
{
|
||||
private struct Registration
|
||||
{
|
||||
public Transform Transform;
|
||||
public float BodyRadius;
|
||||
}
|
||||
|
||||
private static IEnemySeparationSolver _current = new GridBucketEnemySeparationSolver();
|
||||
private static readonly Dictionary<MovementComponent, Registration> Registrations = new();
|
||||
|
||||
public static IEnemySeparationSolver Current => _current;
|
||||
public static string CurrentSolverName => _current.GetType().Name;
|
||||
|
||||
public static void SetSolver(IEnemySeparationSolver solver)
|
||||
{
|
||||
if (solver == null) return;
|
||||
_current = solver;
|
||||
ReRegisterAll();
|
||||
}
|
||||
|
||||
public static void UseGridBucketSolver(float cellSize = 1f)
|
||||
{
|
||||
SetSolver(new GridBucketEnemySeparationSolver(cellSize));
|
||||
}
|
||||
|
||||
public static void UseNaiveSolver()
|
||||
{
|
||||
SetSolver(new NaiveEnemySeparationSolver());
|
||||
}
|
||||
|
||||
public static void Register(MovementComponent mover, Transform transform, float bodyRadius)
|
||||
{
|
||||
if (mover == null || transform == null) return;
|
||||
|
||||
var registration = new Registration
|
||||
{
|
||||
Transform = transform,
|
||||
BodyRadius = bodyRadius
|
||||
};
|
||||
Registrations[mover] = registration;
|
||||
_current.Register(mover, transform, bodyRadius);
|
||||
}
|
||||
|
||||
public static void Unregister(MovementComponent mover)
|
||||
{
|
||||
if (mover == null) return;
|
||||
|
||||
_current.Unregister(mover);
|
||||
Registrations.Remove(mover);
|
||||
}
|
||||
|
||||
public static Vector3 Resolve(MovementComponent mover, Vector3 desiredPosition, Vector3 fallbackDirection,
|
||||
int iterations)
|
||||
{
|
||||
return _current.Resolve(mover, desiredPosition, fallbackDirection, iterations);
|
||||
}
|
||||
|
||||
private static void ReRegisterAll()
|
||||
{
|
||||
foreach (var pair in Registrations)
|
||||
{
|
||||
MovementComponent mover = pair.Key;
|
||||
Registration registration = pair.Value;
|
||||
if (mover == null || registration.Transform == null) continue;
|
||||
|
||||
_current.Register(mover, registration.Transform, registration.BodyRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cf44095cd7c76043a8e8a44dc5a0888
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,223 @@
|
||||
using Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace CustomUtility
|
||||
{
|
||||
public sealed class GridBucketEnemySeparationSolver : IEnemySeparationSolver
|
||||
{
|
||||
private sealed class Agent
|
||||
{
|
||||
public Transform Transform;
|
||||
public float Radius;
|
||||
public Vector3 Position;
|
||||
public int CellX;
|
||||
public int CellZ;
|
||||
}
|
||||
|
||||
private readonly System.Collections.Generic.Dictionary<MovementComponent, Agent> _agents = new();
|
||||
|
||||
private readonly System.Collections.Generic.Dictionary<long, System.Collections.Generic.List<MovementComponent>>
|
||||
_buckets = new();
|
||||
|
||||
private readonly System.Collections.Generic.List<MovementComponent> _recycle = new();
|
||||
private readonly float _cellSize;
|
||||
|
||||
private int _snapshotFrame = -1;
|
||||
private float _maxRadius = 0.45f;
|
||||
|
||||
public GridBucketEnemySeparationSolver(float cellSize = 1f)
|
||||
{
|
||||
_cellSize = Mathf.Max(0.1f, cellSize);
|
||||
}
|
||||
|
||||
public void Register(MovementComponent mover, Transform transform, float bodyRadius)
|
||||
{
|
||||
if (mover == null || transform == null) return;
|
||||
|
||||
if (!_agents.TryGetValue(mover, out var agent))
|
||||
{
|
||||
agent = new Agent();
|
||||
_agents.Add(mover, agent);
|
||||
}
|
||||
|
||||
agent.Transform = transform;
|
||||
agent.Radius = Mathf.Max(0.01f, bodyRadius);
|
||||
if (agent.Radius > _maxRadius)
|
||||
{
|
||||
_maxRadius = agent.Radius;
|
||||
}
|
||||
|
||||
_snapshotFrame = -1;
|
||||
}
|
||||
|
||||
public void Unregister(MovementComponent mover)
|
||||
{
|
||||
if (mover == null) return;
|
||||
if (!_agents.TryGetValue(mover, out var agent)) return;
|
||||
|
||||
RemoveFromBucket(mover, agent.CellX, agent.CellZ);
|
||||
_agents.Remove(mover);
|
||||
RecalculateMaxRadius();
|
||||
_snapshotFrame = -1;
|
||||
}
|
||||
|
||||
public Vector3 Resolve(MovementComponent mover, Vector3 desiredPosition, Vector3 fallbackDirection,
|
||||
int iterations)
|
||||
{
|
||||
if (mover == null) return desiredPosition;
|
||||
if (!_agents.TryGetValue(mover, out var self)) return desiredPosition;
|
||||
|
||||
EnsureSnapshot();
|
||||
|
||||
Vector3 candidate = desiredPosition;
|
||||
candidate.y = 0f;
|
||||
|
||||
int effectiveIterations = Mathf.Max(1, iterations);
|
||||
int queryRange = Mathf.Max(1, Mathf.CeilToInt((self.Radius + _maxRadius) / _cellSize));
|
||||
Vector3 fallback = fallbackDirection.sqrMagnitude > 0.0001f ? fallbackDirection.normalized : Vector3.right;
|
||||
fallback.y = 0f;
|
||||
|
||||
for (int iter = 0; iter < effectiveIterations; iter++)
|
||||
{
|
||||
int cellX = ToCell(candidate.x);
|
||||
int cellZ = ToCell(candidate.z);
|
||||
|
||||
for (int dx = -queryRange; dx <= queryRange; dx++)
|
||||
{
|
||||
for (int dz = -queryRange; dz <= queryRange; dz++)
|
||||
{
|
||||
if (!_buckets.TryGetValue(CellKey(cellX + dx, cellZ + dz), out var bucket)) continue;
|
||||
|
||||
for (int i = 0; i < bucket.Count; i++)
|
||||
{
|
||||
MovementComponent otherMover = bucket[i];
|
||||
if (otherMover == mover) continue;
|
||||
if (!_agents.TryGetValue(otherMover, out var other)) continue;
|
||||
|
||||
Vector3 toSelf = candidate - other.Position;
|
||||
float minDistance = self.Radius + other.Radius;
|
||||
float minDistanceSq = minDistance * minDistance;
|
||||
float sqrDistance = toSelf.sqrMagnitude;
|
||||
|
||||
if (sqrDistance <= Mathf.Epsilon)
|
||||
{
|
||||
candidate += fallback * (self.Radius * 0.25f);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sqrDistance >= minDistanceSq) continue;
|
||||
|
||||
float distance = Mathf.Sqrt(sqrDistance);
|
||||
float penetration = minDistance - distance;
|
||||
candidate += (toSelf / distance) * penetration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyncAgentPosition(mover, self, candidate);
|
||||
|
||||
candidate.y = desiredPosition.y;
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private void EnsureSnapshot()
|
||||
{
|
||||
int frame = Time.frameCount;
|
||||
if (_snapshotFrame == frame) return;
|
||||
|
||||
_snapshotFrame = frame;
|
||||
_buckets.Clear();
|
||||
_recycle.Clear();
|
||||
|
||||
foreach (var pair in _agents)
|
||||
{
|
||||
MovementComponent mover = pair.Key;
|
||||
Agent agent = pair.Value;
|
||||
if (mover == null || agent.Transform == null)
|
||||
{
|
||||
_recycle.Add(mover);
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 position = agent.Transform.position;
|
||||
position.y = 0f;
|
||||
agent.Position = position;
|
||||
|
||||
agent.CellX = ToCell(position.x);
|
||||
agent.CellZ = ToCell(position.z);
|
||||
AddToBucket(mover, agent.CellX, agent.CellZ);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _recycle.Count; i++)
|
||||
{
|
||||
_agents.Remove(_recycle[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncAgentPosition(MovementComponent mover, Agent agent, Vector3 position)
|
||||
{
|
||||
int newCellX = ToCell(position.x);
|
||||
int newCellZ = ToCell(position.z);
|
||||
|
||||
if (agent.CellX != newCellX || agent.CellZ != newCellZ)
|
||||
{
|
||||
RemoveFromBucket(mover, agent.CellX, agent.CellZ);
|
||||
AddToBucket(mover, newCellX, newCellZ);
|
||||
agent.CellX = newCellX;
|
||||
agent.CellZ = newCellZ;
|
||||
}
|
||||
|
||||
agent.Position = position;
|
||||
}
|
||||
|
||||
private void AddToBucket(MovementComponent mover, int cellX, int cellZ)
|
||||
{
|
||||
long key = CellKey(cellX, cellZ);
|
||||
if (!_buckets.TryGetValue(key, out var list))
|
||||
{
|
||||
list = new System.Collections.Generic.List<MovementComponent>(8);
|
||||
_buckets.Add(key, list);
|
||||
}
|
||||
|
||||
list.Add(mover);
|
||||
}
|
||||
|
||||
private void RemoveFromBucket(MovementComponent mover, int cellX, int cellZ)
|
||||
{
|
||||
long key = CellKey(cellX, cellZ);
|
||||
if (!_buckets.TryGetValue(key, out var list)) return;
|
||||
|
||||
list.Remove(mover);
|
||||
if (list.Count == 0)
|
||||
{
|
||||
_buckets.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateMaxRadius()
|
||||
{
|
||||
float max = 0.01f;
|
||||
foreach (var pair in _agents)
|
||||
{
|
||||
if (pair.Value.Radius > max)
|
||||
{
|
||||
max = pair.Value.Radius;
|
||||
}
|
||||
}
|
||||
|
||||
_maxRadius = max;
|
||||
}
|
||||
|
||||
private int ToCell(float value)
|
||||
{
|
||||
return Mathf.FloorToInt(value / _cellSize);
|
||||
}
|
||||
|
||||
private static long CellKey(int x, int z)
|
||||
{
|
||||
return ((long)x << 32) ^ (uint)z;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7c10dca24b508f4fa6726eae7ac2fb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace CustomUtility
|
||||
{
|
||||
public interface IEnemySeparationSolver
|
||||
{
|
||||
void Register(MovementComponent mover, Transform transform, float bodyRadius);
|
||||
void Unregister(MovementComponent mover);
|
||||
Vector3 Resolve(MovementComponent mover, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3960124c8fe4304493659a13e5a9439
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
using Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace CustomUtility
|
||||
{
|
||||
public sealed class NaiveEnemySeparationSolver : IEnemySeparationSolver
|
||||
{
|
||||
private sealed class Agent
|
||||
{
|
||||
public Transform Transform;
|
||||
public float Radius;
|
||||
}
|
||||
|
||||
private readonly System.Collections.Generic.Dictionary<MovementComponent, Agent> _agents = new();
|
||||
private readonly System.Collections.Generic.List<MovementComponent> _agentKeys = new();
|
||||
|
||||
public void Register(MovementComponent mover, Transform transform, float bodyRadius)
|
||||
{
|
||||
if (mover == null || transform == null) return;
|
||||
|
||||
if (!_agents.TryGetValue(mover, out var agent))
|
||||
{
|
||||
agent = new Agent();
|
||||
_agents.Add(mover, agent);
|
||||
}
|
||||
|
||||
agent.Transform = transform;
|
||||
agent.Radius = Mathf.Max(0.01f, bodyRadius);
|
||||
}
|
||||
|
||||
public void Unregister(MovementComponent mover)
|
||||
{
|
||||
if (mover == null) return;
|
||||
_agents.Remove(mover);
|
||||
}
|
||||
|
||||
public Vector3 Resolve(MovementComponent mover, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations)
|
||||
{
|
||||
if (mover == null) return desiredPosition;
|
||||
if (!_agents.TryGetValue(mover, out var self)) return desiredPosition;
|
||||
|
||||
Vector3 candidate = desiredPosition;
|
||||
candidate.y = 0f;
|
||||
|
||||
Vector3 fallback = fallbackDirection.sqrMagnitude > 0.0001f ? fallbackDirection.normalized : Vector3.right;
|
||||
fallback.y = 0f;
|
||||
|
||||
_agentKeys.Clear();
|
||||
foreach (var pair in _agents)
|
||||
{
|
||||
_agentKeys.Add(pair.Key);
|
||||
}
|
||||
|
||||
int effectiveIterations = Mathf.Max(1, iterations);
|
||||
for (int iter = 0; iter < effectiveIterations; iter++)
|
||||
{
|
||||
for (int i = 0; i < _agentKeys.Count; i++)
|
||||
{
|
||||
MovementComponent otherMover = _agentKeys[i];
|
||||
if (otherMover == mover) continue;
|
||||
if (!_agents.TryGetValue(otherMover, out var other)) continue;
|
||||
if (other.Transform == null) continue;
|
||||
|
||||
Vector3 otherPosition = other.Transform.position;
|
||||
otherPosition.y = 0f;
|
||||
|
||||
Vector3 toSelf = candidate - otherPosition;
|
||||
float minDistance = self.Radius + other.Radius;
|
||||
float minDistanceSq = minDistance * minDistance;
|
||||
float sqrDistance = toSelf.sqrMagnitude;
|
||||
|
||||
if (sqrDistance <= Mathf.Epsilon)
|
||||
{
|
||||
candidate += fallback * (self.Radius * 0.25f);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sqrDistance >= minDistanceSq) continue;
|
||||
|
||||
float distance = Mathf.Sqrt(sqrDistance);
|
||||
float penetration = minDistance - distance;
|
||||
candidate += (toSelf / distance) * penetration;
|
||||
}
|
||||
}
|
||||
|
||||
candidate.y = desiredPosition.y;
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec8ec1013900437498da4613f680a898
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user