diff --git a/FishROV/Assets/Scenes/AvoidancePerfDemo.unity b/FishROV/Assets/Scenes/AvoidancePerfDemo.unity index a72defc..0f1aee0 100644 --- a/FishROV/Assets/Scenes/AvoidancePerfDemo.unity +++ b/FishROV/Assets/Scenes/AvoidancePerfDemo.unity @@ -165,4 +165,3 @@ MonoBehaviour: dimensionMode: 1 viewMode: 0 fishCount: 300 - sharkCount: 1 diff --git a/FishROV/Assets/Scripts/Benchmark/Agents/BenchmarkAgent.cs b/FishROV/Assets/Scripts/Benchmark/Agents/BenchmarkAgent.cs index 5059fe5..40cd5a2 100644 --- a/FishROV/Assets/Scripts/Benchmark/Agents/BenchmarkAgent.cs +++ b/FishROV/Assets/Scripts/Benchmark/Agents/BenchmarkAgent.cs @@ -22,19 +22,15 @@ namespace FishROV.AvoidanceBenchmark public static BenchmarkAgent CreatePrimitive(BenchmarkAgentSpawnInfo spawnInfo, Transform parent) { - var go = spawnInfo.Role == BenchmarkAgentRole.Shark - ? GameObject.CreatePrimitive(PrimitiveType.Capsule) - : CreateConeObject(); + var go = CreateConeObject(); go.name = $"{spawnInfo.Role}_{spawnInfo.GroupId:0000}"; go.transform.SetParent(parent, false); go.transform.position = spawnInfo.Position; - var scale = spawnInfo.Role == BenchmarkAgentRole.Shark - ? new Vector3(spawnInfo.Radius, spawnInfo.Height, spawnInfo.Radius) - : new Vector3( - Mathf.Max(0.1f, spawnInfo.Radius * 1.4f), - Mathf.Max(0.1f, spawnInfo.Radius * 1.4f), - Mathf.Max(0.2f, spawnInfo.Radius * 3.2f)); + var scale = new Vector3( + Mathf.Max(0.1f, spawnInfo.Radius * 1.4f), + Mathf.Max(0.1f, spawnInfo.Radius * 1.4f), + Mathf.Max(0.2f, spawnInfo.Radius * 3.2f)); go.transform.localScale = scale; var collider = go.GetComponent(); @@ -101,9 +97,6 @@ namespace FishROV.AvoidanceBenchmark case BenchmarkAgentRole.Fish: color = new Color(0.10f, 0.72f, 0.95f); break; - case BenchmarkAgentRole.Shark: - color = new Color(0.95f, 0.22f, 0.16f); - break; case BenchmarkAgentRole.SchoolProxy: color = new Color(0.20f, 0.95f, 0.42f); break; diff --git a/FishROV/Assets/Scripts/Benchmark/AvoidanceBenchmarkTypes.cs b/FishROV/Assets/Scripts/Benchmark/AvoidanceBenchmarkTypes.cs index dee495c..7c9bdd7 100644 --- a/FishROV/Assets/Scripts/Benchmark/AvoidanceBenchmarkTypes.cs +++ b/FishROV/Assets/Scripts/Benchmark/AvoidanceBenchmarkTypes.cs @@ -6,19 +6,16 @@ namespace FishROV.AvoidanceBenchmark public enum AvoidanceFrameworkKind { NoAvoidance = 0, - AstarRvo = 1, - Rvo2 = 2, - UnityNavMesh = 3, - SamplingLocal = 4 + Rvo2 = 1, + SamplingLocal = 2 } public enum AvoidanceScenarioKind { FreeSwim = 0, CrossFlow = 1, - SharkCharge = 2, - NarrowPassage = 3, - DenseCircleStress = 4 + NarrowPassage = 2, + DenseCircleStress = 3 } public enum AvoidanceDimensionMode @@ -33,16 +30,14 @@ namespace FishROV.AvoidanceBenchmark Overview = 0, TopDown = 1, Side = 2, - Shark = 3, - FollowSchool = 4 + FollowSchool = 3 } public enum BenchmarkAgentRole { Fish = 0, - Shark = 1, - SchoolProxy = 2, - Obstacle = 3 + SchoolProxy = 1, + Obstacle = 2 } [Serializable] @@ -67,13 +62,10 @@ namespace FishROV.AvoidanceBenchmark public AvoidanceScenarioKind Scenario = AvoidanceScenarioKind.FreeSwim; public AvoidanceDimensionMode DimensionMode = AvoidanceDimensionMode.XZ25D; public int FishCount = 300; - public int SharkCount = 1; public int SchoolCount = 1; public float ArenaRadius = 28f; public float FishRadius = 0.35f; - public float SharkRadius = 2.2f; public float FishSpeed = 2.4f; - public float SharkSpeed = 3.4f; public int MaxNeighbours = 8; public int RandomSeed = 13999; public bool UseSchoolProxy = false; diff --git a/FishROV/Assets/Scripts/Benchmark/AvoidancePerfBootstrap.cs b/FishROV/Assets/Scripts/Benchmark/AvoidancePerfBootstrap.cs index 5ec6ab4..fc311e3 100644 --- a/FishROV/Assets/Scripts/Benchmark/AvoidancePerfBootstrap.cs +++ b/FishROV/Assets/Scripts/Benchmark/AvoidancePerfBootstrap.cs @@ -12,7 +12,6 @@ namespace FishROV.AvoidanceBenchmark [SerializeField] private AvoidanceDimensionMode dimensionMode = AvoidanceDimensionMode.XZ25D; [SerializeField] private BenchmarkViewMode viewMode = BenchmarkViewMode.Overview; [SerializeField] private int fishCount = 300; - [SerializeField] private int sharkCount = 1; private readonly List agents = new List(512); private readonly AvoidanceScenarioController scenarioController = new AvoidanceScenarioController(); @@ -68,7 +67,7 @@ namespace FishROV.AvoidanceBenchmark GUILayout.Label("FishROV 本地避障压测"); GUILayout.Label($"当前方案:{adapter?.Name ?? "未初始化"}"); GUILayout.Label($"状态:{adapter?.Status ?? "未初始化"}"); - GUILayout.Label($"对象数量:小鱼 {fishCount},鲨鱼 {sharkCount},总数 {agents.Count}"); + GUILayout.Label($"对象数量:鱼 {fishCount},总数 {agents.Count}"); GUILayout.Label($"帧率:平均 {metrics.AverageFps:F1} | 1% Low {metrics.OnePercentLowFps:F1}"); GUILayout.Label($"模拟耗时:{metrics.SimulationMs:F2} ms | GC:{metrics.GcAllocBytes / 1024f:F1} KB"); GUILayout.Label($"碰撞调试:XZ重叠 {collisionProbe.HorizontalOverlapPairs} 对 | 3D重叠 {collisionProbe.SpatialOverlapPairs} 对 | 最近间距 {collisionProbe.MinimumClearance:F2}"); @@ -151,7 +150,6 @@ namespace FishROV.AvoidanceBenchmark Scenario = scenario, DimensionMode = dimensionMode, FishCount = fishCount, - SharkCount = sharkCount, UseSchoolProxy = false }; @@ -161,7 +159,6 @@ namespace FishROV.AvoidanceBenchmark adapter.Initialize(config); SpawnFish(); - SpawnSharks(); scenarioController.Reset(config, agents); } @@ -185,27 +182,6 @@ namespace FishROV.AvoidanceBenchmark } } - private void SpawnSharks() - { - for (var i = 0; i < config.SharkCount; i++) - { - var angle = i * Mathf.PI * 2f / Mathf.Max(1, config.SharkCount); - var spawn = new BenchmarkAgentSpawnInfo - { - Position = new Vector3(Mathf.Cos(angle), 0f, Mathf.Sin(angle)) * config.ArenaRadius * 0.85f, - Radius = config.SharkRadius, - Height = config.SharkRadius * 2f, - MaxSpeed = config.SharkSpeed, - Layer = 2, - CollidesWith = 4, - Priority = 1f, - Role = BenchmarkAgentRole.Shark, - GroupId = i - }; - agents.Add(adapter.CreateAgent(spawn, agentRoot)); - } - } - private Vector3 SpawnPosition(int index, int total, float radius) { if (scenario == AvoidanceScenarioKind.CrossFlow) @@ -282,12 +258,6 @@ namespace FishROV.AvoidanceBenchmark position = new Vector3(0f, radius * 0.45f, -radius * 1.9f); rotation = Quaternion.Euler(12f, 0f, 0f); break; - case BenchmarkViewMode.Shark: - var shark = agents.Find(agent => agent.Role == BenchmarkAgentRole.Shark); - var sharkPos = shark != null ? shark.transform.position : Vector3.zero; - position = sharkPos + new Vector3(0f, radius * 0.35f, -radius * 0.75f); - rotation = Quaternion.LookRotation(sharkPos - position + Vector3.up * 2f, Vector3.up); - break; case BenchmarkViewMode.FollowSchool: var center = CalculateAgentCenter(); position = center + new Vector3(0f, radius * 0.65f, -radius); @@ -342,20 +312,14 @@ namespace FishROV.AvoidanceBenchmark { case AvoidanceFrameworkKind.NoAvoidance: return "无避障"; - case AvoidanceFrameworkKind.AstarRvo: - return "A* RVO"; case AvoidanceFrameworkKind.Rvo2: return "RVO2"; - case AvoidanceFrameworkKind.UnityNavMesh: - return "NavMesh"; case AvoidanceFrameworkKind.SamplingLocal: - return "采样避障"; + return "自研版"; case AvoidanceScenarioKind.FreeSwim: return "自由巡游"; case AvoidanceScenarioKind.CrossFlow: return "对向穿流"; - case AvoidanceScenarioKind.SharkCharge: - return "鲨鱼冲群"; case AvoidanceScenarioKind.NarrowPassage: return "狭窄通道"; case AvoidanceScenarioKind.DenseCircleStress: @@ -372,8 +336,6 @@ namespace FishROV.AvoidanceBenchmark return "俯视"; case BenchmarkViewMode.Side: return "侧视"; - case BenchmarkViewMode.Shark: - return "跟鲨鱼"; case BenchmarkViewMode.FollowSchool: return "跟鱼群"; default: diff --git a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AstarRvoAdapter.cs b/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AstarRvoAdapter.cs deleted file mode 100644 index d0c66ea..0000000 --- a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AstarRvoAdapter.cs +++ /dev/null @@ -1,286 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using UnityEngine; - -namespace FishROV.AvoidanceBenchmark -{ - public sealed class AstarRvoAdapter : IAvoidanceAdapter - { - private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; - - private readonly Dictionary bindings = - new Dictionary(512); - - private Type rvoControllerType; - private Type rvoSimulatorType; - private MethodInfo setTargetMethod; - private MethodInfo calculateMovementDeltaMethod; - private GameObject simulatorObject; - private AvoidanceBenchmarkConfig config; - private string status = "Not initialized"; - - public string Name => "A* RVO 本地避障"; - public bool IsAvailable => rvoControllerType != null && rvoSimulatorType != null; - public string Status => status; - - public void Initialize(AvoidanceBenchmarkConfig config) - { - this.config = config; - ResolveAstarTypes(); - - if (!IsAvailable) - { - status = "未检测到 A* Pathfinding Project Pro 的 RVO 模块;请导入 A* Pro,确保存在 Pathfinding.RVO.RVOController 和 RVOSimulator。当前退回基线移动。"; - return; - } - - simulatorObject = new GameObject("A* RVO 模拟器"); - var simulator = simulatorObject.AddComponent(rvoSimulatorType); - ConfigureSimulator(simulator); - - var dimensionNote = config.DimensionMode == AvoidanceDimensionMode.XYZ3D - ? "3D 压测会投影到 A* RVO 的 XZ 平面" - : "XZ 平面"; - status = $"A* Pro RVO 已通过反射接入({dimensionNote})。"; - } - - public BenchmarkAgent CreateAgent(BenchmarkAgentSpawnInfo spawnInfo, Transform parent) - { - var agent = BenchmarkAgent.CreatePrimitive(spawnInfo, parent); - if (!IsAvailable) - { - return agent; - } - - var controller = agent.gameObject.AddComponent(rvoControllerType); - ConfigureController(controller, spawnInfo); - bindings[agent] = new RvoAgentBinding(agent, controller); - return agent; - } - - public void SetPreferredVelocity(BenchmarkAgent agent, Vector3 preferredVelocity) - { - if (!IsAvailable || !bindings.TryGetValue(agent, out var binding)) - { - agent.ApplyVelocity(preferredVelocity); - return; - } - - binding.PreferredVelocity = Vector3.ClampMagnitude(preferredVelocity, agent.MaxSpeed); - } - - public void SetTarget(BenchmarkAgent agent, Vector3 target) - { - agent.Target = target; - } - - public void Tick(float deltaTime) - { - if (!IsAvailable || deltaTime <= 0f) - { - return; - } - - foreach (var binding in bindings.Values) - { - var velocity = CalculateAvoidedVelocity(binding, deltaTime); - binding.Agent.ApplyVelocity(velocity); - } - } - - public void Dispose() - { - bindings.Clear(); - if (simulatorObject != null) - { - UnityEngine.Object.Destroy(simulatorObject); - simulatorObject = null; - } - } - - private void ResolveAstarTypes() - { - rvoControllerType = FindType("Pathfinding.RVO.RVOController"); - rvoSimulatorType = FindType("Pathfinding.RVO.RVOSimulator"); - - if (rvoControllerType == null) - { - return; - } - - setTargetMethod = rvoControllerType.GetMethod( - "SetTarget", - MemberFlags, - null, - new[] { typeof(Vector3), typeof(float), typeof(float), typeof(Vector3) }, - null); - calculateMovementDeltaMethod = rvoControllerType.GetMethod( - "CalculateMovementDelta", - MemberFlags, - null, - new[] { typeof(Vector3), typeof(float) }, - null); - } - - private void ConfigureSimulator(object simulator) - { - SetMember(simulator, "desiredSimulationFPS", 60); - SetMember(simulator, "movementPlane", "XZ"); - SetMember(simulator, "doubleBuffering", true); - SetMember(simulator, "workerThreads", "None"); - SetMember(simulator, "symmetryBreakingBias", 0.1f); - } - - private void ConfigureController(object controller, BenchmarkAgentSpawnInfo spawnInfo) - { - SetMember(controller, "radius", spawnInfo.Radius); - SetMember(controller, "height", Mathf.Max(spawnInfo.Height, spawnInfo.Radius * 2f)); - SetMember(controller, "center", Vector3.up * spawnInfo.Height * 0.5f); - SetMember(controller, "maxSpeed", spawnInfo.MaxSpeed); - SetMember(controller, "priority", Mathf.Clamp01(spawnInfo.Priority)); - SetMember(controller, "layer", spawnInfo.Layer); - SetMember(controller, "collidesWith", spawnInfo.CollidesWith); - SetMember(controller, "locked", spawnInfo.IsKinematicObstacle); - SetMember(controller, "lockWhenNotMoving", spawnInfo.IsKinematicObstacle); - } - - private Vector3 CalculateAvoidedVelocity(RvoAgentBinding binding, float deltaTime) - { - var agent = binding.Agent; - var preferredVelocity = binding.PreferredVelocity; - var position = agent.transform.position; - var target = position + preferredVelocity; - var speed = preferredVelocity.magnitude; - - if (setTargetMethod != null) - { - TryInvoke( - setTargetMethod, - binding.Controller, - new object[] { target, speed, agent.MaxSpeed, target }); - } - - if (calculateMovementDeltaMethod == null) - { - return preferredVelocity; - } - - try - { - var delta = calculateMovementDeltaMethod.Invoke( - binding.Controller, - new object[] { position, deltaTime }); - if (delta is Vector3 movementDelta) - { - return movementDelta / deltaTime; - } - } - catch (Exception exception) - { - status = $"A* RVO 反射调用失败:{exception.GetBaseException().Message}"; - } - - return preferredVelocity; - } - - private static void TryInvoke(MethodInfo method, object target, object[] parameters) - { - try - { - method.Invoke(target, parameters); - } - catch - { - // Keep the benchmark running even when an A* version changes a reflected member. - } - } - - private static void SetMember(object target, string name, object value) - { - if (target == null) - { - return; - } - - var type = target.GetType(); - var property = type.GetProperty(name, MemberFlags); - if (property != null && property.CanWrite) - { - TrySetValue(property.PropertyType, value, converted => property.SetValue(target, converted, null)); - return; - } - - var field = type.GetField(name, MemberFlags); - if (field != null) - { - TrySetValue(field.FieldType, value, converted => field.SetValue(target, converted)); - } - } - - private static void TrySetValue(Type targetType, object value, Action setter) - { - try - { - setter(ConvertValue(targetType, value)); - } - catch - { - // Optional reflected configuration only; defaults are acceptable across A* versions. - } - } - - private static object ConvertValue(Type targetType, object value) - { - if (value == null || targetType.IsInstanceOfType(value)) - { - return value; - } - - if (targetType.IsEnum) - { - if (value is string enumName) - { - return Enum.Parse(targetType, enumName); - } - - return Enum.ToObject(targetType, value); - } - - return Convert.ChangeType(value, targetType); - } - - private static Type FindType(string fullName) - { - var direct = Type.GetType(fullName); - if (direct != null) - { - return direct; - } - - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - var type = assembly.GetType(fullName); - if (type != null) - { - return type; - } - } - - return null; - } - - private sealed class RvoAgentBinding - { - public RvoAgentBinding(BenchmarkAgent agent, object controller) - { - Agent = agent; - Controller = controller; - } - - public BenchmarkAgent Agent { get; } - public object Controller { get; } - public Vector3 PreferredVelocity { get; set; } - } - } -} diff --git a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AstarRvoAdapter.cs.meta b/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AstarRvoAdapter.cs.meta deleted file mode 100644 index 55cd477..0000000 --- a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AstarRvoAdapter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3f0e3f32e10e42eda4a2c6e58d0f9f8b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AvoidanceAdapterRegistry.cs b/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AvoidanceAdapterRegistry.cs index 9cd96fb..1db5ef8 100644 --- a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AvoidanceAdapterRegistry.cs +++ b/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/AvoidanceAdapterRegistry.cs @@ -8,12 +8,8 @@ namespace FishROV.AvoidanceBenchmark { case AvoidanceFrameworkKind.NoAvoidance: return new NoAvoidanceAdapter(); - case AvoidanceFrameworkKind.AstarRvo: - return new AstarRvoAdapter(); case AvoidanceFrameworkKind.Rvo2: return new Rvo2Adapter(); - case AvoidanceFrameworkKind.UnityNavMesh: - return new UnityNavMeshAdapter(); case AvoidanceFrameworkKind.SamplingLocal: return new SamplingLocalAvoidanceAdapter(); default: diff --git a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/SamplingLocalAvoidanceAdapter.cs b/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/SamplingLocalAvoidanceAdapter.cs index 760bca3..a393259 100644 --- a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/SamplingLocalAvoidanceAdapter.cs +++ b/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/SamplingLocalAvoidanceAdapter.cs @@ -34,7 +34,7 @@ namespace FishROV.AvoidanceBenchmark states.Clear(); orderedStates.Clear(); buckets.Clear(); - cellSize = Mathf.Max(config.SharkRadius * 2.5f, config.FishRadius * NeighbourRangeMultiplier); + cellSize = config.FishRadius * NeighbourRangeMultiplier; } public BenchmarkAgent CreateAgent(BenchmarkAgentSpawnInfo spawnInfo, Transform parent) @@ -143,7 +143,7 @@ namespace FishROV.AvoidanceBenchmark var position = state.Agent.transform.position; var centerCell = ToCell(position); var queryRange = Mathf.CeilToInt( - Mathf.Max(state.Agent.Radius * NeighbourRangeMultiplier, state.Agent.Radius + config.SharkRadius) / cellSize); + state.Agent.Radius * NeighbourRangeMultiplier / cellSize); queryRange = Mathf.Clamp(queryRange, 1, 4); for (var x = -queryRange; x <= queryRange; x++) diff --git a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/UnityNavMeshAdapter.cs b/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/UnityNavMeshAdapter.cs deleted file mode 100644 index 9820d9f..0000000 --- a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/UnityNavMeshAdapter.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.AI; - -namespace FishROV.AvoidanceBenchmark -{ - public sealed class UnityNavMeshAdapter : IAvoidanceAdapter - { - private readonly Dictionary navAgents = new Dictionary(512); - private AvoidanceBenchmarkConfig config; - private NavMeshData runtimeNavMeshData; - private NavMeshDataInstance runtimeNavMeshInstance; - private bool hasNavMeshData; - private int fallbackAgentCount; - - public string Name => "Unity NavMeshAgent 对照组"; - public bool IsAvailable => hasNavMeshData; - - public string Status - { - get - { - if (!hasNavMeshData) - { - return "NavMesh 数据构建失败;当前退回基线移动。"; - } - - if (fallbackAgentCount > 0) - { - return $"NavMeshAgent 已接管 {navAgents.Count} 个对象;{fallbackAgentCount} 个对象无法放到 NavMesh 上,使用基线移动。"; - } - - return $"Unity NavMeshAgent 避障已接管 {navAgents.Count} 个对象。"; - } - } - - public void Initialize(AvoidanceBenchmarkConfig config) - { - this.config = config.Clone(); - navAgents.Clear(); - fallbackAgentCount = 0; - BuildRuntimeNavMesh(); - - var triangulation = NavMesh.CalculateTriangulation(); - hasNavMeshData = triangulation.vertices != null && triangulation.vertices.Length > 0; - } - - public BenchmarkAgent CreateAgent(BenchmarkAgentSpawnInfo spawnInfo, Transform parent) - { - var benchmarkAgent = BenchmarkAgent.CreatePrimitive(spawnInfo, parent); - if (!hasNavMeshData) - { - return benchmarkAgent; - } - - if (!TryPlaceOnNavMesh(benchmarkAgent, spawnInfo)) - { - fallbackAgentCount++; - return benchmarkAgent; - } - - var navAgent = benchmarkAgent.gameObject.AddComponent(); - ConfigureNavMeshAgent(navAgent, spawnInfo); - navAgents[benchmarkAgent] = navAgent; - return benchmarkAgent; - } - - public void SetPreferredVelocity(BenchmarkAgent agent, Vector3 preferredVelocity) - { - if (!hasNavMeshData || !navAgents.TryGetValue(agent, out var navAgent) || !navAgent.isOnNavMesh) - { - agent.ApplyVelocity(preferredVelocity); - return; - } - - var navVelocity = preferredVelocity; - if (config.DimensionMode != AvoidanceDimensionMode.XYZ3D) - { - navVelocity.y = 0f; - } - - navAgent.nextPosition = agent.transform.position; - navAgent.velocity = Vector3.ClampMagnitude(navVelocity, agent.MaxSpeed); - } - - public void SetTarget(BenchmarkAgent agent, Vector3 target) - { - agent.Target = target; - } - - public void Tick(float deltaTime) - { - if (!hasNavMeshData) - { - return; - } - - foreach (var entry in navAgents) - { - var benchmarkAgent = entry.Key; - var navAgent = entry.Value; - if (benchmarkAgent == null || navAgent == null || !navAgent.isOnNavMesh) - { - continue; - } - - var smoothedVelocity = Vector3.MoveTowards( - benchmarkAgent.Velocity, - navAgent.velocity, - navAgent.acceleration * deltaTime); - benchmarkAgent.ApplyVelocity(smoothedVelocity); - navAgent.nextPosition = benchmarkAgent.transform.position; - } - } - - public void Dispose() - { - foreach (var navAgent in navAgents.Values) - { - if (navAgent != null) - { - Object.Destroy(navAgent); - } - } - - navAgents.Clear(); - fallbackAgentCount = 0; - - if (runtimeNavMeshInstance.valid) - { - runtimeNavMeshInstance.Remove(); - } - - runtimeNavMeshData = null; - } - - private void BuildRuntimeNavMesh() - { - if (runtimeNavMeshInstance.valid) - { - runtimeNavMeshInstance.Remove(); - } - - var buildSettings = NavMesh.GetSettingsByID(0); - buildSettings.agentRadius = Mathf.Max(0.05f, config.FishRadius); - buildSettings.agentHeight = Mathf.Max(0.2f, config.FishRadius * 2f); - buildSettings.agentClimb = 0.4f; - buildSettings.agentSlope = 45f; - - var arenaSize = Mathf.Max(8f, config.ArenaRadius * 2.4f); - var sources = new List - { - new NavMeshBuildSource - { - shape = NavMeshBuildSourceShape.Box, - transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, Vector3.one), - size = new Vector3(arenaSize, 0.1f, arenaSize), - area = 0 - } - }; - - var bounds = new Bounds(Vector3.zero, new Vector3(arenaSize, 2f, arenaSize)); - runtimeNavMeshData = NavMeshBuilder.BuildNavMeshData(buildSettings, sources, bounds, Vector3.zero, Quaternion.identity); - if (runtimeNavMeshData != null) - { - runtimeNavMeshInstance = NavMesh.AddNavMeshData(runtimeNavMeshData); - } - } - - private bool TryPlaceOnNavMesh(BenchmarkAgent benchmarkAgent, BenchmarkAgentSpawnInfo spawnInfo) - { - var searchDistance = Mathf.Max(2f, spawnInfo.Height + spawnInfo.Radius); - if (!NavMesh.SamplePosition(spawnInfo.Position, out var hit, searchDistance, NavMesh.AllAreas)) - { - return false; - } - - benchmarkAgent.transform.position = hit.position; - return true; - } - - private static void ConfigureNavMeshAgent(NavMeshAgent navAgent, BenchmarkAgentSpawnInfo spawnInfo) - { - navAgent.radius = Mathf.Max(0.01f, spawnInfo.Radius); - navAgent.height = Mathf.Max(0.1f, spawnInfo.Height); - navAgent.speed = Mathf.Max(0.01f, spawnInfo.MaxSpeed); - navAgent.acceleration = navAgent.speed * 8f; - navAgent.angularSpeed = 720f; - navAgent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance; - navAgent.avoidancePriority = Mathf.Clamp(Mathf.RoundToInt((1f - spawnInfo.Priority) * 99f), 0, 99); - navAgent.autoBraking = false; - navAgent.autoRepath = false; - navAgent.updatePosition = false; - navAgent.updateRotation = false; - } - } -} diff --git a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/UnityNavMeshAdapter.cs.meta b/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/UnityNavMeshAdapter.cs.meta deleted file mode 100644 index 86600cd..0000000 --- a/FishROV/Assets/Scripts/Benchmark/FrameworkAdapters/UnityNavMeshAdapter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7d3b299dd0504f849b83e4af9ed272a7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/FishROV/Assets/Scripts/Benchmark/Scenarios/AvoidanceScenarioController.cs b/FishROV/Assets/Scripts/Benchmark/Scenarios/AvoidanceScenarioController.cs index 59cc331..f6e2e96 100644 --- a/FishROV/Assets/Scripts/Benchmark/Scenarios/AvoidanceScenarioController.cs +++ b/FishROV/Assets/Scripts/Benchmark/Scenarios/AvoidanceScenarioController.cs @@ -43,11 +43,6 @@ namespace FishROV.AvoidanceBenchmark desired.y = 0f; } - if (config.Scenario == AvoidanceScenarioKind.SharkCharge && agent.Role == BenchmarkAgentRole.Shark) - { - desired = new Vector3(Mathf.Sin(elapsed * 0.35f) * 8f, 0f, -agent.transform.position.z); - } - if (desired.sqrMagnitude < 0.0001f) { return Vector3.zero; @@ -66,13 +61,6 @@ namespace FishROV.AvoidanceBenchmark return agent.GroupId % 2 == 0 ? new Vector3(radius, HeightFor(agent), Random.Range(-radius, radius)) : new Vector3(-radius, HeightFor(agent), Random.Range(-radius, radius)); - case AvoidanceScenarioKind.SharkCharge: - if (agent.Role == BenchmarkAgentRole.Shark) - { - return new Vector3(0f, HeightFor(agent), -radius); - } - var schoolPoint = Random.insideUnitCircle * radius * 0.45f; - return new Vector3(schoolPoint.x, HeightFor(agent), schoolPoint.y); case AvoidanceScenarioKind.NarrowPassage: return new Vector3(agent.GroupId % 2 == 0 ? radius : -radius, HeightFor(agent), Random.Range(-3f, 3f)); case AvoidanceScenarioKind.DenseCircleStress: diff --git a/FishROV/Docs/avoidance-core-comparison.html b/FishROV/Docs/avoidance-core-comparison.html index 19354ae..262507c 100644 --- a/FishROV/Docs/avoidance-core-comparison.html +++ b/FishROV/Docs/avoidance-core-comparison.html @@ -483,7 +483,7 @@
  • 大多数方案都需要上层提供目标或期望速度,然后再做局部修正。
  • 核心输入都离不开位置、速度、半径、邻居范围和预测时间。
  • 都在权衡两个目标:尽量接近期望运动,同时减少未来碰撞风险。
  • -
  • 除 NavMesh 外,大多数方案都不负责全局路径,不理解死胡同、任务顺序和长期收益。
  • +
  • 当前压测只关心局部速度修正,不比较全局寻路、地图可达性或复杂玩法状态。
  • 在 FishROV 的 3D/2.5D 场景里,多数方案仍需要明确如何处理 XZ 投影、高度层和真实体积冲突。
  • @@ -550,15 +550,6 @@ 训练成本高,泛化依赖仿真覆盖,工程可控性较弱。 建议先吸收风险表达和 reward/score 思想,不急着引入完整 RL 链路。 - - Unity NavMeshAgent - 导航网格路径跟随
    先理解哪里能走,再沿路径局部避让。 - 在 NavMesh 上规划路径,沿拐点移动,同时使用内置局部避让。 - 全局可达性、路径搜索、内置工程避让。 - 静态地图、走廊、房间、地形导航。 - 局部避障不是核心强项,高密度动态体易抖,主要是平面系统。 - 适合作为全局路径层或工程对照,不适合作为鱼群局部避障核心。 - @@ -569,7 +560,7 @@

    1. 位置空间还是速度空间

    -

    RVO2、HRVO、RVO_Py_MAS、rl_rvo_nav 都把问题转成“哪些速度会导致未来碰撞”。NavMesh 主要先回答“哪里能走”。采样学习版虽然是评分,但最关键的预测碰撞项也已经进入速度空间。

    +

    RVO2、HRVO、RVO_Py_MAS、rl_rvo_nav 都把问题转成“哪些速度会导致未来碰撞”。采样学习版虽然是评分,但最关键的预测碰撞项也已经进入速度空间。

    2. 约束、过滤还是惩罚

    @@ -577,15 +568,15 @@

    3. 谁承担避让责任

    -

    VO 偏“我躲别人”;RVO/ORCA 假设双方共同承担;HRVO 在 reciprocal 基础上减少来回换边。NavMesh 用优先级处理谁让路。采样版需要靠评分项或通行偏置来塑造行为。

    +

    VO 偏“我躲别人”;RVO/ORCA 假设双方共同承担;HRVO 在 reciprocal 基础上减少来回换边。采样版需要靠评分项或通行偏置来塑造行为。

    -

    4. 是否理解地图

    -

    NavMesh 有全局可达性和路径图。其他方案基本只处理局部邻居,不知道死胡同、洞穴、任务顺序和远期收益。因此 FishROV 最可能需要“全局/中层路径 + 局部避障 + 运动控制”的分层结构。

    +

    4. 是否进入主压测

    +

    当前主压测只保留 RVO2 原版和自研版。HRVO、RVO_Py_MAS、RL-RVO 只作为思想参考,不进入编辑器选择项。

    5. 可解释性和可改性

    -

    RVO2 理论清晰但实现复杂。采样学习版最容易改权重和新增评分项。RVO_Py_MAS最适合读懂速度障碍。RL 灵活但可解释性和复现实验成本更高。NavMesh 是工程黑盒较多的成熟系统。

    +

    RVO2 理论清晰但实现复杂。自研版最容易改权重和新增评分项。RVO_Py_MAS 最适合读懂速度障碍。RL 灵活但可解释性和复现实验成本更高。

    6. 2D 与 3D 的边界

    @@ -609,15 +600,7 @@
    采样学习版
    RVO_Py_MAS
    -
    NavMesh / RVO2
    -
    -
    -
    -
    全局路径能力
    -
    -
    NavMeshAgent
    -
    RVO 系列
    -
    采样 / RL
    +
    RVO2
    @@ -635,7 +618,7 @@

    暂缓其他方案

    -

    NavMesh、HRVO、RL-RVO 暂时不用进主压测。先把两条主线测准,再决定是否补防振荡或全局可达性。

    +

    HRVO、RVO_Py_MAS、RL-RVO 暂时不用进编辑器主压测。先把 RVO2 和自研版两条主线测准。

    diff --git a/FishROV/Docs/local-avoidance-benchmark-notes.md b/FishROV/Docs/local-avoidance-benchmark-notes.md index d3cffe5..5bbefc4 100644 --- a/FishROV/Docs/local-avoidance-benchmark-notes.md +++ b/FishROV/Docs/local-avoidance-benchmark-notes.md @@ -7,26 +7,25 @@ - `Rvo2Adapter`:成熟 RVO2 / ORCA 基准。 - 项目自研 RVO2 思想版本:用于验证可控性、可调参空间和性能上限。 -其他方案可以保留为参考或历史对照,但不进入当前主压测结论。不同方案之间唯一应该变化的变量,是 `AvoidanceFrameworkKind` 选择后由 `AvoidanceAdapterRegistry` 创建的 adapter。 +当前工程入口只保留 `NoAvoidance`、`Rvo2` 和自研版。不同方案之间唯一应该变化的变量,是 `AvoidanceFrameworkKind` 选择后由 `AvoidanceAdapterRegistry` 创建的 adapter。 ## 共享运行入口 - 场景:`Assets/Scenes/AvoidancePerfDemo.unity` - 入口脚本:`AvoidancePerfBootstrap` - Game 视口调试面板: - - 避障方案选择 + - 避障方案选择:无避障、RVO2、自研版 - 测试场景选择 - 2D / 2.5D / 3D 模式 - 观察视角切换 - - 小鱼数量预设和滑条 + - 鱼数量预设和滑条 - 暂停 / 继续 / 重置 - 平均 FPS、1% Low FPS、模拟耗时、GC 分配 - 碰撞调试:XZ 平面重叠对数、3D 空间重叠对数、最近清空间距 -- 表现对象:鱼群为运行时圆锥,圆锥尖头表示朝向;鲨鱼为 Capsule。 +- 表现对象:鱼为运行时圆锥,圆锥尖头表示朝向。 - 共享场景: - 自由巡游 - 对向穿流 - - 鲨鱼冲群 - 狭窄通道 - 圆阵高压 @@ -41,7 +40,7 @@ Assets/Scripts/Benchmark/FrameworkAdapters/ adapter 可以给运行时创建的对象追加框架自己的组件,但不应改变: - 场景生成逻辑 -- 小鱼/鲨鱼数量档 +- 鱼数量档 - Game 视口调试控件 - 指标名称 - 相机/视角模式 @@ -51,7 +50,7 @@ adapter 可以给运行时创建的对象追加框架自己的组件,但不应 `NoAvoidanceAdapter` 是对照基线:只按期望速度移动,不做避障。 -当前压测结论主线只比较 RVO2 原版和基于 RVO2 思想的自研版本。A* RVO、Unity NavMesh、采样避障学习版可以继续保留在调试面板里,但当前不作为主结论对象。 +当前压测结论主线只比较 RVO2 原版和基于 RVO2 思想的自研版本。编辑器压测入口已经只保留无避障、RVO2 和自研版,避免无关选项干扰当前结论。 ## 当前压测口径 @@ -86,15 +85,6 @@ adapter 可以给运行时创建的对象追加框架自己的组件,但不应 - 是否连接调试器。 - 是否出现明显发热、降频或后台干扰。 -## A* Pathfinding Project Pro RVO Adapter - -`AstarRvoAdapter` 位于 `Assets/Scripts/Benchmark/FrameworkAdapters/`,由 `AvoidanceFrameworkKind.AstarRvo` 选择。 - -- adapter 通过反射查找 `Pathfinding.RVO.RVOController` 和 `Pathfinding.RVO.RVOSimulator`,所以没有导入 A* Pro 时工程仍可编译。 -- 如果缺少 A* Pro RVO 类型,Game 视口状态会提示缺包,并退回基线移动。 -- 如果类型存在,adapter 会创建运行时 `RVOSimulator`,给 benchmark 对象添加 `RVOController`,把期望速度送入 A* RVO,再把反射得到的避障速度写回 `BenchmarkAgent.ApplyVelocity`。 -- 共享场景、HUD、数量档、指标和相机逻辑不变。 - ## RVO2 Adapter 当前已在 `Assets/ThirdParty/RVO2-CS/` 导入 `snape/RVO2-CS` 的核心 `RVOCS` 源码,并保留 Apache-2.0 许可证说明。 @@ -103,15 +93,6 @@ adapter 可以给运行时创建的对象追加框架自己的组件,但不应 - 每帧设置期望速度前会同步 Unity Transform 位置,避免 RVO 内部位置和 benchmark 边界夹取结果漂移。 - RVO2-CS 是 2D 算法,2D 和 2.5D 使用 Unity `x/z` 到 RVO `x/y` 的映射;3D 模式会投影到 XZ 平面。 -## Unity NavMesh Adapter - -`UnityNavMeshAdapter` 使用内置 `UnityEngine.AI.NavMeshAgent` 做对照组。 - -- adapter 会使用 `NavMeshBuilder.BuildNavMeshData` 为 benchmark 场景运行时生成一张平面 NavMesh,不需要手动烘焙场景。 -- 如果运行时 NavMesh 构建失败,Game 视口状态会提示,并退回基线移动,不会添加无效 `NavMeshAgent`。 -- NavMeshAgent 计算出的速度会按自身 `acceleration` 平滑后再交给 benchmark 可视对象,减少高密度场景的一帧速度尖峰抖动。 -- 因为 NavMesh 是平面数据,3D 压测更适合作为 2D / 2.5D 对照;完整 3D 体积避障仍需要专门方案。 - ## 采样避障学习版 `SamplingLocalAvoidanceAdapter` 是项目内自写的学习算法,用来和成熟框架做横向对比。 @@ -119,4 +100,4 @@ adapter 可以给运行时创建的对象追加框架自己的组件,但不应 - 使用空间哈希查找近邻,避免全量 O(n²) 扫描。 - 对每个 agent 采样多组候选速度,并用目标偏差、速度平滑、预测碰撞、当前重叠、边界越界惩罚打分。 - 它不是完整 ORCA,也不保证无碰撞;目的在于提供一个可读、可改、可调参的学习基线。 -- 如果它在某些场景比 RVO2 或 NavMesh 更稳,只能说明当前启发式适合该场景,不代表算法理论上更强。 +- 如果它在某些场景比 RVO2 更稳,只能说明当前启发式适合该场景,不代表算法理论上更强。