简单重构拆分 SimulationWorld
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
# CodeGraph data files — local to each machine, not for committing.
|
||||||
|
# Ignore everything in .codegraph/ except this file itself, so transient
|
||||||
|
# files (the database, daemon.pid, sockets, logs) never show up in git.
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# AGENTS.md — SBE(Seek-Battle-Evacuate)
|
# AGENTS.md
|
||||||
|
|
||||||
单人 3d 类吸血鬼幸存者游戏原型。Unity 2022.3.62f3,框架为 GameFramework(UGF,源码在 `Assets/GameFramework/`),配置表用 Luban,插件有 UniTask、DOTween 等。
|
单人 3d 类吸血鬼幸存者游戏原型。Unity 2022.3.62f3,框架为 GameFramework(UGF,源码在 `Assets/GameFramework/`),配置表用 Luban,插件有 UniTask、DOTween 等。
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
- 不做静态回退(不用默认值/兜底逻辑静默继续);异常路径直接 `Log.Error(...)` 抛出(UGF 的 `Log`,支持格式串,如 `Log.Error("Entity data is invalid.")`)。
|
- 不做静态回退(不用默认值/兜底逻辑静默继续);异常路径直接 `Log.Error(...)` 抛出(UGF 的 `Log`,支持格式串,如 `Log.Error("Entity data is invalid.")`)。
|
||||||
- UI 的序列化字段(`[SerializeField]` 及 View 生成的引用)**不做判空检查**,默认已在 Inspector 中赋值。
|
- UI 的序列化字段(`[SerializeField]` 及 View 生成的引用)**不做判空检查**,默认已在 Inspector 中赋值。
|
||||||
- GameEntry 中提供的静态入口不对其进行判空,默认其不为空。
|
- GameEntry 中提供的静态入口不对其进行判空,默认其不为空。
|
||||||
|
- 对于从 Luban 中取出的数据,默认其是有效且不为空的,代码里不对其做任何有效性检测
|
||||||
|
|
||||||
## 代码风格
|
## 代码风格
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 67ad529203c03d8419375e2342eb485f
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct EnemyAttackSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public const float DefaultAttackRange = 1f;
|
||||||
|
|
||||||
|
public float AttackRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 59ae2375305641a6a36d1bea0bf1abc2
|
guid: 35d153869faa1494c9fbb7218bbdd5b0
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct EnemyBehaviorSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public const int StateIdle = 0;
|
||||||
|
public const int StateChasing = 1;
|
||||||
|
public const int StateInAttackRange = 2;
|
||||||
|
|
||||||
|
public int State;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: b2c8675358384b21925e6f660ce61a79
|
guid: 23b79d0c84ef6904588ff8da30bf4108
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct EnemyIdentitySim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public int EntityId;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 57a3f582d9c04803908205e0cd4b5b75
|
guid: f89e25340a7475048a94caab1c77e745
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct EnemyMoveConfigSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public bool AvoidOverlap;
|
||||||
|
public float BodyRadius;
|
||||||
|
public int SeparationIterations;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fa8be64dc4a8e0c4598f5b0be7fa7e03
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct EnemyMoveInputSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public float Speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b8883455a6bb9c94eba26c839184ca0b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct EnemySeparationSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public float2 PreviousPush;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: eaeb6060e86ea424f956a2a478bc6126
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal sealed class EnemyStore : EntityComponentStore
|
||||||
|
{
|
||||||
|
private Column<EnemyIdentitySim> _identity;
|
||||||
|
private Column<EnemyTransformSim> _transform;
|
||||||
|
private Column<EnemyMoveInputSim> _moveInput;
|
||||||
|
private Column<EnemyMoveConfigSim> _moveConfig;
|
||||||
|
private Column<EnemyAttackSim> _attack;
|
||||||
|
private Column<EnemyBehaviorSim> _behavior;
|
||||||
|
private Column<EnemySeparationSim> _separation;
|
||||||
|
|
||||||
|
public Column<EnemyIdentitySim> Identity => _identity;
|
||||||
|
public Column<EnemyTransformSim> Transform => _transform;
|
||||||
|
public Column<EnemyMoveInputSim> MoveInput => _moveInput;
|
||||||
|
public Column<EnemyMoveConfigSim> MoveConfig => _moveConfig;
|
||||||
|
public Column<EnemyAttackSim> Attack => _attack;
|
||||||
|
public Column<EnemyBehaviorSim> Behavior => _behavior;
|
||||||
|
public Column<EnemySeparationSim> Separation => _separation;
|
||||||
|
|
||||||
|
public void Initialize(int capacity)
|
||||||
|
{
|
||||||
|
if (IsCreated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
_identity = Register<EnemyIdentitySim>(capacity);
|
||||||
|
_transform = Register<EnemyTransformSim>(capacity);
|
||||||
|
_moveInput = Register<EnemyMoveInputSim>(capacity);
|
||||||
|
_moveConfig = Register<EnemyMoveConfigSim>(capacity);
|
||||||
|
_attack = Register<EnemyAttackSim>(capacity);
|
||||||
|
_behavior = Register<EnemyBehaviorSim>(capacity);
|
||||||
|
_separation = Register<EnemySeparationSim>(capacity);
|
||||||
|
MarkInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Add(in EnemyIdentitySim identity, in EnemyTransformSim transform, in EnemyMoveInputSim moveInput,
|
||||||
|
in EnemyMoveConfigSim moveConfig, in EnemyAttackSim attack, in EnemyBehaviorSim behavior)
|
||||||
|
{
|
||||||
|
int index = AppendRow(identity.EntityId);
|
||||||
|
_identity.Data.Add(identity);
|
||||||
|
_transform.Data.Add(transform);
|
||||||
|
_moveInput.Data.Add(moveInput);
|
||||||
|
_moveConfig.Data.Add(moveConfig);
|
||||||
|
_attack.Data.Add(attack);
|
||||||
|
_behavior.Data.Add(behavior);
|
||||||
|
_separation.Data.Add(default);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
_identity = null;
|
||||||
|
_transform = null;
|
||||||
|
_moveInput = null;
|
||||||
|
_moveConfig = null;
|
||||||
|
_attack = null;
|
||||||
|
_behavior = null;
|
||||||
|
_separation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 21d84c6a187090e46a1ec7637519c5a7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct EnemyTransformSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public float3 Position;
|
||||||
|
public float3 Forward;
|
||||||
|
public quaternion Rotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5d8966797f1b08648886988017716ea3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Collections;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal abstract class EntityComponentStore
|
||||||
|
{
|
||||||
|
internal interface IColumn
|
||||||
|
{
|
||||||
|
bool IsCreated { get; }
|
||||||
|
void EnsureCapacity(int count);
|
||||||
|
void SwapBackRemove(int index, int lastIndex);
|
||||||
|
void Clear();
|
||||||
|
void Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class Column<T> : IColumn where T : unmanaged, ISimulationComponent
|
||||||
|
{
|
||||||
|
private NativeList<T> _data;
|
||||||
|
|
||||||
|
public Column(int capacity)
|
||||||
|
{
|
||||||
|
_data = new NativeList<T>(capacity, Allocator.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeList<T> Data => _data;
|
||||||
|
|
||||||
|
public bool IsCreated => _data.IsCreated;
|
||||||
|
|
||||||
|
public ref T this[int index] => ref _data.ElementAt(index);
|
||||||
|
|
||||||
|
public void EnsureCapacity(int count)
|
||||||
|
{
|
||||||
|
if (_data.IsCreated && _data.Capacity < count)
|
||||||
|
{
|
||||||
|
_data.Capacity = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SwapBackRemove(int index, int lastIndex)
|
||||||
|
{
|
||||||
|
if (index != lastIndex)
|
||||||
|
{
|
||||||
|
_data[index] = _data[lastIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
_data.RemoveAt(lastIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if (_data.IsCreated)
|
||||||
|
{
|
||||||
|
_data.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_data.IsCreated)
|
||||||
|
{
|
||||||
|
_data.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_data = default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly EntityBinding _binding = new();
|
||||||
|
private readonly List<IColumn> _columns = new();
|
||||||
|
private int _count;
|
||||||
|
|
||||||
|
public int Count => _count;
|
||||||
|
|
||||||
|
public bool IsCreated { get; private set; }
|
||||||
|
|
||||||
|
public void EnsureCapacity(int count)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _columns.Count; i++)
|
||||||
|
{
|
||||||
|
_columns[i].EnsureCapacity(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetSimulationIndex(int entityId, out int index)
|
||||||
|
{
|
||||||
|
return _binding.TryGetSimulationIndex(entityId, out index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveByEntityId(int entityId)
|
||||||
|
{
|
||||||
|
if (!_binding.TryGetSimulationIndex(entityId, out int index))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastIndex = _count - 1;
|
||||||
|
bool hasMovedRow = index != lastIndex;
|
||||||
|
int movedEntityId = 0;
|
||||||
|
if (hasMovedRow)
|
||||||
|
{
|
||||||
|
_binding.TryGetEntityId(lastIndex, out movedEntityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _columns.Count; i++)
|
||||||
|
{
|
||||||
|
_columns[i].SwapBackRemove(index, lastIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
_binding.UnbindByEntityId(entityId);
|
||||||
|
if (hasMovedRow)
|
||||||
|
{
|
||||||
|
_binding.RemapIndex(movedEntityId, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
_count--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _columns.Count; i++)
|
||||||
|
{
|
||||||
|
_columns[i].Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_binding.Clear();
|
||||||
|
_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Dispose()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _columns.Count; i++)
|
||||||
|
{
|
||||||
|
_columns[i].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_columns.Clear();
|
||||||
|
_binding.Clear();
|
||||||
|
_count = 0;
|
||||||
|
IsCreated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Column<T> Register<T>(int capacity) where T : unmanaged, ISimulationComponent
|
||||||
|
{
|
||||||
|
Column<T> column = new Column<T>(capacity);
|
||||||
|
_columns.Add(column);
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void MarkInitialized()
|
||||||
|
{
|
||||||
|
IsCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int AppendRow(int entityId)
|
||||||
|
{
|
||||||
|
int index = _count;
|
||||||
|
_binding.Bind(entityId, index);
|
||||||
|
_count++;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ea8b87c1fbddf6743a7ce3339dc943a2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal interface ISimulationComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4f605b392ae908a439b2028cf5a4eb05
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct PickupIdentitySim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public int EntityId;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: be2030806e57ecc4f9276b506c800542
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct PickupRadiusSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public float Radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d5d8bf9445be1f14bba1ccf4093045f1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct PickupStateSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public const int StateIdle = 0;
|
||||||
|
public const int StateAbsorbing = 1;
|
||||||
|
|
||||||
|
public int State;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e505617febb37c54bbec57f8882d3a8a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal sealed class PickupStore : EntityComponentStore
|
||||||
|
{
|
||||||
|
private Column<PickupIdentitySim> _identity;
|
||||||
|
private Column<PickupTransformSim> _transform;
|
||||||
|
private Column<PickupRadiusSim> _radius;
|
||||||
|
private Column<PickupStateSim> _state;
|
||||||
|
|
||||||
|
public Column<PickupIdentitySim> Identity => _identity;
|
||||||
|
public Column<PickupTransformSim> Transform => _transform;
|
||||||
|
public Column<PickupRadiusSim> Radius => _radius;
|
||||||
|
public Column<PickupStateSim> State => _state;
|
||||||
|
|
||||||
|
public void Initialize(int capacity)
|
||||||
|
{
|
||||||
|
if (IsCreated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
_identity = Register<PickupIdentitySim>(capacity);
|
||||||
|
_transform = Register<PickupTransformSim>(capacity);
|
||||||
|
_radius = Register<PickupRadiusSim>(capacity);
|
||||||
|
_state = Register<PickupStateSim>(capacity);
|
||||||
|
MarkInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Add(in PickupIdentitySim identity, in PickupTransformSim transform, in PickupRadiusSim radius,
|
||||||
|
in PickupStateSim state = default)
|
||||||
|
{
|
||||||
|
int index = AppendRow(identity.EntityId);
|
||||||
|
_identity.Data.Add(identity);
|
||||||
|
_transform.Data.Add(transform);
|
||||||
|
_radius.Data.Add(radius);
|
||||||
|
_state.Data.Add(state);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
_identity = null;
|
||||||
|
_transform = null;
|
||||||
|
_radius = null;
|
||||||
|
_state = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 88ed8cf0bf6d9cb459e071ff5b93dc54
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct PickupTransformSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public float3 Position;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8046df9b31763ac448c52a088f9fd9d5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct ProjectileIdentitySim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public int EntityId;
|
||||||
|
public int OwnerEntityId;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 52b2efbbc6a2ee94a81016767ef6a6bb
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct ProjectileLifeSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public float Age;
|
||||||
|
public float LifeTime;
|
||||||
|
public bool Active;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4b7fb93b432358948ae81a913129bcf7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal sealed class ProjectileStore : EntityComponentStore
|
||||||
|
{
|
||||||
|
private Column<ProjectileIdentitySim> _identity;
|
||||||
|
private Column<ProjectileTransformSim> _transform;
|
||||||
|
private Column<ProjectileVelocitySim> _velocity;
|
||||||
|
private Column<ProjectileLifeSim> _life;
|
||||||
|
|
||||||
|
public Column<ProjectileIdentitySim> Identity => _identity;
|
||||||
|
public Column<ProjectileTransformSim> Transform => _transform;
|
||||||
|
public Column<ProjectileVelocitySim> Velocity => _velocity;
|
||||||
|
public Column<ProjectileLifeSim> Life => _life;
|
||||||
|
|
||||||
|
public void Initialize(int capacity)
|
||||||
|
{
|
||||||
|
if (IsCreated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
_identity = Register<ProjectileIdentitySim>(capacity);
|
||||||
|
_transform = Register<ProjectileTransformSim>(capacity);
|
||||||
|
_velocity = Register<ProjectileVelocitySim>(capacity);
|
||||||
|
_life = Register<ProjectileLifeSim>(capacity);
|
||||||
|
MarkInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Add(in ProjectileIdentitySim identity, in ProjectileTransformSim transform,
|
||||||
|
in ProjectileVelocitySim velocity, in ProjectileLifeSim life)
|
||||||
|
{
|
||||||
|
int index = AppendRow(identity.EntityId);
|
||||||
|
_identity.Data.Add(identity);
|
||||||
|
_transform.Data.Add(transform);
|
||||||
|
_velocity.Data.Add(velocity);
|
||||||
|
_life.Data.Add(life);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
_identity = null;
|
||||||
|
_transform = null;
|
||||||
|
_velocity = null;
|
||||||
|
_life = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6de5ba3f6e96dc845b50ca960aa5ae32
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct ProjectileTransformSim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public float3 Position;
|
||||||
|
public float3 Forward;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 59b7c7f9782759b498e3b1d9a507ca81
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal struct ProjectileVelocitySim : ISimulationComponent
|
||||||
|
{
|
||||||
|
public float3 Velocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 01a59e095dd7381469d25c36b4a7e878
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+20
-19
@@ -7,17 +7,18 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
#region Collision Transient
|
#region Collision Transient
|
||||||
|
|
||||||
public int CollisionCandidateCount => _collisionCandidates.IsCreated ? _collisionCandidates.Length : 0;
|
public SimulationStats Stats => new SimulationStats(
|
||||||
public int PendingAreaCollisionRequestCount => _areaCollisionRequests.Count;
|
_collisionCandidates.IsCreated ? _collisionCandidates.Length : 0,
|
||||||
public int LastCollisionQueryCount => _lastCollisionQueryCount;
|
_areaCollisionRequests.Count,
|
||||||
public int LastProjectileCollisionQueryCount => _lastProjectileCollisionQueryCount;
|
_lastCollisionQueryCount,
|
||||||
public int LastAreaCollisionQueryCount => _lastAreaCollisionQueryCount;
|
_lastProjectileCollisionQueryCount,
|
||||||
public int LastCollisionCandidateCount => _lastCollisionCandidateCount;
|
_lastAreaCollisionQueryCount,
|
||||||
public int LastProjectileCollisionCandidateCount => _lastProjectileCollisionCandidateCount;
|
_lastCollisionCandidateCount,
|
||||||
public int LastAreaCollisionCandidateCount => _lastAreaCollisionCandidateCount;
|
_lastProjectileCollisionCandidateCount,
|
||||||
public int LastResolvedAreaHitCount => _lastResolvedAreaHitCount;
|
_lastAreaCollisionCandidateCount,
|
||||||
public float LastCollisionCellSize => _lastCollisionCellSize;
|
_lastResolvedAreaHitCount,
|
||||||
public bool LastCollisionHasEnemyTargets => _lastCollisionHasEnemyTargets;
|
_lastCollisionCellSize,
|
||||||
|
_lastCollisionHasEnemyTargets);
|
||||||
|
|
||||||
private void ResetCollisionRuntimeStats()
|
private void ResetCollisionRuntimeStats()
|
||||||
{
|
{
|
||||||
@@ -45,8 +46,8 @@ namespace SepCore.Simulation
|
|||||||
_enemyCollisionBuckets.Clear();
|
_enemyCollisionBuckets.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddProjectileCollisionQuery(int queryId, in ProjectileJobOutputData projectile, float radius,
|
private void AddProjectileCollisionQuery(int queryId, in ProjectileTransformSim transform,
|
||||||
int maxTargets = 1)
|
in ProjectileIdentitySim identity, float radius, int maxTargets = 1)
|
||||||
{
|
{
|
||||||
if (!_collisionQueryInputs.IsCreated || radius <= 0f)
|
if (!_collisionQueryInputs.IsCreated || radius <= 0f)
|
||||||
{
|
{
|
||||||
@@ -56,14 +57,14 @@ namespace SepCore.Simulation
|
|||||||
_collisionQueryInputs.Add(new CollisionQueryData
|
_collisionQueryInputs.Add(new CollisionQueryData
|
||||||
{
|
{
|
||||||
QueryId = queryId,
|
QueryId = queryId,
|
||||||
SourceType = CollisionSourceTypeProjectile,
|
SourceType = SimulationCollision.SourceTypeProjectile,
|
||||||
SourceEntityId = projectile.EntityId,
|
SourceEntityId = identity.EntityId,
|
||||||
SourceOwnerEntityId = projectile.OwnerEntityId,
|
SourceOwnerEntityId = identity.OwnerEntityId,
|
||||||
SourceWasActiveAtQueryTime = true,
|
SourceWasActiveAtQueryTime = true,
|
||||||
Position = projectile.Position,
|
Position = transform.Position,
|
||||||
Radius = radius,
|
Radius = radius,
|
||||||
MaxTargets = math.max(1, maxTargets),
|
MaxTargets = math.max(1, maxTargets),
|
||||||
ShapeType = CollisionShapeCircle,
|
ShapeType = SimulationCollision.ShapeCircle,
|
||||||
Direction = new float3(0f, 0f, 1f),
|
Direction = new float3(0f, 0f, 1f),
|
||||||
HalfAngleDeg = 180f,
|
HalfAngleDeg = 180f,
|
||||||
HalfWidth = 0f,
|
HalfWidth = 0f,
|
||||||
@@ -94,7 +95,7 @@ namespace SepCore.Simulation
|
|||||||
_collisionQueryInputs.Add(new CollisionQueryData
|
_collisionQueryInputs.Add(new CollisionQueryData
|
||||||
{
|
{
|
||||||
QueryId = queryId,
|
QueryId = queryId,
|
||||||
SourceType = CollisionSourceTypeArea,
|
SourceType = SimulationCollision.SourceTypeArea,
|
||||||
SourceEntityId = sourceEntityId,
|
SourceEntityId = sourceEntityId,
|
||||||
SourceOwnerEntityId = sourceOwnerEntityId,
|
SourceOwnerEntityId = sourceOwnerEntityId,
|
||||||
SourceWasActiveAtQueryTime = sourceWasActiveAtQueryTime,
|
SourceWasActiveAtQueryTime = sourceWasActiveAtQueryTime,
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
public sealed partial class SimulationWorld
|
||||||
|
{
|
||||||
|
#region Enemy Separation Buffers
|
||||||
|
|
||||||
|
private void PrepareEnemySeparationJobBuffers(int enemyCount, int bucketCapacity)
|
||||||
|
{
|
||||||
|
InitializeJobDataChannels();
|
||||||
|
EnsureCapacity(ref _enemySeparationTransforms, enemyCount);
|
||||||
|
_enemySeparationTransforms.Clear();
|
||||||
|
if (enemyCount > 0)
|
||||||
|
{
|
||||||
|
_enemySeparationTransforms.ResizeUninitialized(enemyCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCapacity(ref _enemySeparationBuckets, bucketCapacity);
|
||||||
|
_enemySeparationBuckets.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8a7f58c069d920f4698662060cb2db0b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
-91
@@ -1,91 +0,0 @@
|
|||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
#region Enemy Separation Temporal
|
|
||||||
|
|
||||||
private void PrepareEnemySeparationJobBuffers(int enemyCount, int bucketCapacity)
|
|
||||||
{
|
|
||||||
InitializeJobDataChannels();
|
|
||||||
EnsureCapacity(ref _enemyJobSeparationOutputs, enemyCount);
|
|
||||||
_enemyJobSeparationOutputs.Clear();
|
|
||||||
if (enemyCount > 0)
|
|
||||||
{
|
|
||||||
_enemyJobSeparationOutputs.ResizeUninitialized(enemyCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureCapacity(ref _enemySeparationBuckets, bucketCapacity);
|
|
||||||
_enemySeparationBuckets.Clear();
|
|
||||||
|
|
||||||
EnsureCapacity(ref _enemySeparationPreviousPushes, enemyCount);
|
|
||||||
EnsureCapacity(ref _enemySeparationCurrentPushes, enemyCount);
|
|
||||||
|
|
||||||
if (_enemySeparationPreviousPushes.Length < enemyCount)
|
|
||||||
{
|
|
||||||
int oldLength = _enemySeparationPreviousPushes.Length;
|
|
||||||
_enemySeparationPreviousPushes.ResizeUninitialized(enemyCount);
|
|
||||||
for (int i = oldLength; i < enemyCount; i++)
|
|
||||||
{
|
|
||||||
_enemySeparationPreviousPushes[i] = float2.zero;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_enemySeparationPreviousPushes.Length > enemyCount)
|
|
||||||
{
|
|
||||||
_enemySeparationPreviousPushes.ResizeUninitialized(enemyCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
_enemySeparationCurrentPushes.Clear();
|
|
||||||
if (enemyCount > 0)
|
|
||||||
{
|
|
||||||
_enemySeparationCurrentPushes.ResizeUninitialized(enemyCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CommitEnemySeparationTemporalBuffers(int enemyCount)
|
|
||||||
{
|
|
||||||
if (!_enemySeparationPreviousPushes.IsCreated || !_enemySeparationCurrentPushes.IsCreated)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int copyCount = math.min(enemyCount,
|
|
||||||
math.min(_enemySeparationPreviousPushes.Length, _enemySeparationCurrentPushes.Length));
|
|
||||||
for (int i = 0; i < copyCount; i++)
|
|
||||||
{
|
|
||||||
_enemySeparationPreviousPushes[i] = _enemySeparationCurrentPushes[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEnemyAddedToSeparationTemporalBuffers()
|
|
||||||
{
|
|
||||||
if (_enemySeparationPreviousPushes.IsCreated)
|
|
||||||
{
|
|
||||||
_enemySeparationPreviousPushes.Add(float2.zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_enemySeparationCurrentPushes.IsCreated)
|
|
||||||
{
|
|
||||||
_enemySeparationCurrentPushes.Add(float2.zero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEnemyRemovedFromSeparationTemporalBuffers(int removedIndex)
|
|
||||||
{
|
|
||||||
if (_enemySeparationPreviousPushes.IsCreated && removedIndex >= 0 &&
|
|
||||||
removedIndex < _enemySeparationPreviousPushes.Length)
|
|
||||||
{
|
|
||||||
_enemySeparationPreviousPushes.RemoveAtSwapBack(removedIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_enemySeparationCurrentPushes.IsCreated && removedIndex >= 0 &&
|
|
||||||
removedIndex < _enemySeparationCurrentPushes.Length)
|
|
||||||
{
|
|
||||||
_enemySeparationCurrentPushes.RemoveAtSwapBack(removedIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-8
@@ -5,14 +5,6 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
public sealed partial class SimulationWorld
|
public sealed partial class SimulationWorld
|
||||||
{
|
{
|
||||||
// Shared channel constants plus compatibility fields still reflected by tests.
|
|
||||||
private const int CollisionSourceTypeProjectile = 1;
|
|
||||||
private const int CollisionSourceTypeArea = 2;
|
|
||||||
private const int CollisionShapeCircle = 0;
|
|
||||||
private const int CollisionShapeSector = 1;
|
|
||||||
private const int CollisionShapeRectangle = 2;
|
|
||||||
|
|
||||||
// Kept as top-level fields because current regression tests reflect them directly.
|
|
||||||
private NativeList<CollisionQueryData> _collisionQueryInputs;
|
private NativeList<CollisionQueryData> _collisionQueryInputs;
|
||||||
private readonly List<AreaCollisionRequestData> _areaCollisionRequests = new(16);
|
private readonly List<AreaCollisionRequestData> _areaCollisionRequests = new(16);
|
||||||
}
|
}
|
||||||
|
|||||||
-151
@@ -1,151 +0,0 @@
|
|||||||
using Unity.Mathematics;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
#region Job Data Conversion
|
|
||||||
|
|
||||||
private void SyncSimulationStateToJobInputs()
|
|
||||||
{
|
|
||||||
InitializeJobDataChannels();
|
|
||||||
EnsureCapacity(ref _enemyJobInputs, _enemies.Count);
|
|
||||||
EnsureCapacity(ref _projectileJobInputs, _projectiles.Count);
|
|
||||||
|
|
||||||
_enemyJobInputs.Clear();
|
|
||||||
_projectileJobInputs.Clear();
|
|
||||||
|
|
||||||
foreach (EnemySimData data in _enemies)
|
|
||||||
{
|
|
||||||
_enemyJobInputs.Add(ConvertToEnemyJobInput(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ProjectileSimData data in _projectiles)
|
|
||||||
{
|
|
||||||
_projectileJobInputs.Add(ConvertToProjectileJobInput(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PrepareEnemyJobOutputBuffer(int enemyCount)
|
|
||||||
{
|
|
||||||
InitializeJobDataChannels();
|
|
||||||
EnsureCapacity(ref _enemyJobOutputs, enemyCount);
|
|
||||||
_enemyJobOutputs.Clear();
|
|
||||||
|
|
||||||
if (enemyCount > 0)
|
|
||||||
{
|
|
||||||
_enemyJobOutputs.ResizeUninitialized(enemyCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PrepareProjectileJobOutputBuffer(int projectileCount)
|
|
||||||
{
|
|
||||||
InitializeJobDataChannels();
|
|
||||||
EnsureCapacity(ref _projectileJobOutputs, projectileCount);
|
|
||||||
_projectileJobOutputs.Clear();
|
|
||||||
|
|
||||||
if (projectileCount > 0)
|
|
||||||
{
|
|
||||||
_projectileJobOutputs.ResizeUninitialized(projectileCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CopyProjectileInputsToOutputs()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _projectileJobInputs.Length; i++)
|
|
||||||
{
|
|
||||||
ProjectileJobInputData input = _projectileJobInputs[i];
|
|
||||||
_projectileJobOutputs[i] = new ProjectileJobOutputData
|
|
||||||
{
|
|
||||||
EntityId = input.EntityId,
|
|
||||||
OwnerEntityId = input.OwnerEntityId,
|
|
||||||
Position = input.Position,
|
|
||||||
Forward = input.Forward,
|
|
||||||
Velocity = input.Velocity,
|
|
||||||
Speed = input.Speed,
|
|
||||||
LifeTime = input.LifeTime,
|
|
||||||
Age = input.Age,
|
|
||||||
Active = input.Active,
|
|
||||||
RemainingLifetime = input.RemainingLifetime,
|
|
||||||
State = input.State
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static EnemyJobInputData ConvertToEnemyJobInput(in EnemySimData enemy)
|
|
||||||
{
|
|
||||||
return new EnemyJobInputData
|
|
||||||
{
|
|
||||||
EntityId = enemy.EntityId,
|
|
||||||
Position = new float3(enemy.Position.x, enemy.Position.y, enemy.Position.z),
|
|
||||||
Forward = new float3(enemy.Forward.x, enemy.Forward.y, enemy.Forward.z),
|
|
||||||
Rotation = new quaternion(enemy.Rotation.x, enemy.Rotation.y, enemy.Rotation.z, enemy.Rotation.w),
|
|
||||||
Speed = enemy.Speed,
|
|
||||||
AttackRange = enemy.AttackRange,
|
|
||||||
AvoidEnemyOverlap = enemy.AvoidEnemyOverlap,
|
|
||||||
EnemyBodyRadius = enemy.EnemyBodyRadius,
|
|
||||||
SeparationIterations = enemy.SeparationIterations,
|
|
||||||
TargetType = enemy.TargetType,
|
|
||||||
State = enemy.State
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static EnemySimData ConvertToEnemySimData(in EnemyJobOutputData enemy)
|
|
||||||
{
|
|
||||||
return new EnemySimData
|
|
||||||
{
|
|
||||||
EntityId = enemy.EntityId,
|
|
||||||
Position = new Vector3(enemy.Position.x, enemy.Position.y, enemy.Position.z),
|
|
||||||
Forward = new Vector3(enemy.Forward.x, enemy.Forward.y, enemy.Forward.z),
|
|
||||||
Rotation = new Quaternion(enemy.Rotation.value.x, enemy.Rotation.value.y, enemy.Rotation.value.z,
|
|
||||||
enemy.Rotation.value.w),
|
|
||||||
Speed = enemy.Speed,
|
|
||||||
AttackRange = enemy.AttackRange,
|
|
||||||
AvoidEnemyOverlap = enemy.AvoidEnemyOverlap,
|
|
||||||
EnemyBodyRadius = enemy.EnemyBodyRadius,
|
|
||||||
SeparationIterations = enemy.SeparationIterations,
|
|
||||||
TargetType = enemy.TargetType,
|
|
||||||
State = enemy.State
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ProjectileJobInputData ConvertToProjectileJobInput(in ProjectileSimData projectile)
|
|
||||||
{
|
|
||||||
return new ProjectileJobInputData
|
|
||||||
{
|
|
||||||
EntityId = projectile.EntityId,
|
|
||||||
OwnerEntityId = projectile.OwnerEntityId,
|
|
||||||
Position = new float3(projectile.Position.x, projectile.Position.y, projectile.Position.z),
|
|
||||||
Forward = new float3(projectile.Forward.x, projectile.Forward.y, projectile.Forward.z),
|
|
||||||
Velocity = new float3(projectile.Velocity.x, projectile.Velocity.y, projectile.Velocity.z),
|
|
||||||
Speed = projectile.Speed,
|
|
||||||
LifeTime = projectile.LifeTime,
|
|
||||||
Age = projectile.Age,
|
|
||||||
Active = projectile.Active,
|
|
||||||
RemainingLifetime = projectile.RemainingLifetime,
|
|
||||||
State = projectile.State
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ProjectileSimData ConvertToProjectileSimData(in ProjectileJobOutputData projectile)
|
|
||||||
{
|
|
||||||
return new ProjectileSimData
|
|
||||||
{
|
|
||||||
EntityId = projectile.EntityId,
|
|
||||||
OwnerEntityId = projectile.OwnerEntityId,
|
|
||||||
Position = new Vector3(projectile.Position.x, projectile.Position.y, projectile.Position.z),
|
|
||||||
Forward = new Vector3(projectile.Forward.x, projectile.Forward.y, projectile.Forward.z),
|
|
||||||
Velocity = new Vector3(projectile.Velocity.x, projectile.Velocity.y, projectile.Velocity.z),
|
|
||||||
Speed = projectile.Speed,
|
|
||||||
LifeTime = projectile.LifeTime,
|
|
||||||
Age = projectile.Age,
|
|
||||||
Active = projectile.Active,
|
|
||||||
RemainingLifetime = projectile.RemainingLifetime,
|
|
||||||
State = projectile.State
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+28
-89
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
{
|
{
|
||||||
@@ -16,70 +15,44 @@ namespace SepCore.Simulation
|
|||||||
}
|
}
|
||||||
|
|
||||||
DisposeJobDataChannels();
|
DisposeJobDataChannels();
|
||||||
_enemyJobInputs = new NativeList<EnemyJobInputData>(64, Allocator.Persistent);
|
_enemies.Initialize(64);
|
||||||
_enemyJobOutputs = new NativeList<EnemyJobOutputData>(64, Allocator.Persistent);
|
_projectiles.Initialize(64);
|
||||||
_enemyJobSeparationOutputs = new NativeList<EnemyJobOutputData>(64, Allocator.Persistent);
|
_pickups.Initialize(32);
|
||||||
_enemySeparationPreviousPushes = new NativeList<float2>(64, Allocator.Persistent);
|
_enemySeparationTransforms = new NativeList<EnemyTransformSim>(64, Allocator.Persistent);
|
||||||
_enemySeparationCurrentPushes = new NativeList<float2>(64, Allocator.Persistent);
|
_collectedPickups = new NativeList<int>(64, Allocator.Persistent);
|
||||||
_projectileJobInputs = new NativeList<ProjectileJobInputData>(64, Allocator.Persistent);
|
_enemySeparationBuckets = new NativeParallelMultiHashMap<long, int>(256, Allocator.Persistent);
|
||||||
_projectileJobOutputs = new NativeList<ProjectileJobOutputData>(64, Allocator.Persistent);
|
|
||||||
_collisionQueryInputs = new NativeList<CollisionQueryData>(64, Allocator.Persistent);
|
_collisionQueryInputs = new NativeList<CollisionQueryData>(64, Allocator.Persistent);
|
||||||
_collisionCandidates = new NativeList<CollisionCandidateData>(128, Allocator.Persistent);
|
_collisionCandidates = new NativeList<CollisionCandidateData>(128, Allocator.Persistent);
|
||||||
_enemySeparationBuckets = new NativeParallelMultiHashMap<long, int>(256, Allocator.Persistent);
|
|
||||||
_enemyCollisionBuckets = new NativeParallelMultiHashMap<long, int>(256, Allocator.Persistent);
|
_enemyCollisionBuckets = new NativeParallelMultiHashMap<long, int>(256, Allocator.Persistent);
|
||||||
InitializeEnemyTargetSpatialIndex();
|
InitializeEnemyTargetSpatialIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeJobDataChannels()
|
private void DisposeJobDataChannels()
|
||||||
{
|
{
|
||||||
if (_enemyJobInputs.IsCreated)
|
_enemies.Dispose();
|
||||||
|
_projectiles.Dispose();
|
||||||
|
_pickups.Dispose();
|
||||||
|
|
||||||
|
if (_enemySeparationTransforms.IsCreated)
|
||||||
{
|
{
|
||||||
_enemyJobInputs.Dispose();
|
_enemySeparationTransforms.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_enemyJobInputs = default;
|
_enemySeparationTransforms = default;
|
||||||
|
|
||||||
if (_enemyJobOutputs.IsCreated)
|
if (_collectedPickups.IsCreated)
|
||||||
{
|
{
|
||||||
_enemyJobOutputs.Dispose();
|
_collectedPickups.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_enemyJobOutputs = default;
|
_collectedPickups = default;
|
||||||
|
|
||||||
if (_enemyJobSeparationOutputs.IsCreated)
|
if (_enemySeparationBuckets.IsCreated)
|
||||||
{
|
{
|
||||||
_enemyJobSeparationOutputs.Dispose();
|
_enemySeparationBuckets.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_enemyJobSeparationOutputs = default;
|
_enemySeparationBuckets = default;
|
||||||
|
|
||||||
if (_enemySeparationPreviousPushes.IsCreated)
|
|
||||||
{
|
|
||||||
_enemySeparationPreviousPushes.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_enemySeparationPreviousPushes = default;
|
|
||||||
|
|
||||||
if (_enemySeparationCurrentPushes.IsCreated)
|
|
||||||
{
|
|
||||||
_enemySeparationCurrentPushes.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_enemySeparationCurrentPushes = default;
|
|
||||||
|
|
||||||
if (_projectileJobInputs.IsCreated)
|
|
||||||
{
|
|
||||||
_projectileJobInputs.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_projectileJobInputs = default;
|
|
||||||
|
|
||||||
if (_projectileJobOutputs.IsCreated)
|
|
||||||
{
|
|
||||||
_projectileJobOutputs.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_projectileJobOutputs = default;
|
|
||||||
|
|
||||||
if (_collisionQueryInputs.IsCreated)
|
if (_collisionQueryInputs.IsCreated)
|
||||||
{
|
{
|
||||||
@@ -95,13 +68,6 @@ namespace SepCore.Simulation
|
|||||||
|
|
||||||
_collisionCandidates = default;
|
_collisionCandidates = default;
|
||||||
|
|
||||||
if (_enemySeparationBuckets.IsCreated)
|
|
||||||
{
|
|
||||||
_enemySeparationBuckets.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_enemySeparationBuckets = default;
|
|
||||||
|
|
||||||
if (_enemyCollisionBuckets.IsCreated)
|
if (_enemyCollisionBuckets.IsCreated)
|
||||||
{
|
{
|
||||||
_enemyCollisionBuckets.Dispose();
|
_enemyCollisionBuckets.Dispose();
|
||||||
@@ -127,24 +93,14 @@ namespace SepCore.Simulation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_enemyJobInputs.IsCreated)
|
if (_enemySeparationTransforms.IsCreated)
|
||||||
{
|
{
|
||||||
_enemyJobInputs.Clear();
|
_enemySeparationTransforms.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_enemyJobOutputs.IsCreated)
|
if (_collectedPickups.IsCreated)
|
||||||
{
|
{
|
||||||
_enemyJobOutputs.Clear();
|
_collectedPickups.Clear();
|
||||||
}
|
|
||||||
|
|
||||||
if (_projectileJobInputs.IsCreated)
|
|
||||||
{
|
|
||||||
_projectileJobInputs.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_projectileJobOutputs.IsCreated)
|
|
||||||
{
|
|
||||||
_projectileJobOutputs.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_collisionQueryInputs.IsCreated)
|
if (_collisionQueryInputs.IsCreated)
|
||||||
@@ -157,21 +113,6 @@ namespace SepCore.Simulation
|
|||||||
_collisionCandidates.Clear();
|
_collisionCandidates.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_enemyJobSeparationOutputs.IsCreated)
|
|
||||||
{
|
|
||||||
_enemyJobSeparationOutputs.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_enemySeparationPreviousPushes.IsCreated)
|
|
||||||
{
|
|
||||||
_enemySeparationPreviousPushes.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_enemySeparationCurrentPushes.IsCreated)
|
|
||||||
{
|
|
||||||
_enemySeparationCurrentPushes.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_enemySeparationBuckets.IsCreated)
|
if (_enemySeparationBuckets.IsCreated)
|
||||||
{
|
{
|
||||||
_enemySeparationBuckets.Clear();
|
_enemySeparationBuckets.Clear();
|
||||||
@@ -191,13 +132,11 @@ namespace SepCore.Simulation
|
|||||||
|
|
||||||
private bool AreJobDataChannelsUsable()
|
private bool AreJobDataChannelsUsable()
|
||||||
{
|
{
|
||||||
return IsNativeListUsable(_enemyJobInputs) &&
|
return _enemies.IsCreated &&
|
||||||
IsNativeListUsable(_enemyJobOutputs) &&
|
_projectiles.IsCreated &&
|
||||||
IsNativeListUsable(_enemyJobSeparationOutputs) &&
|
_pickups.IsCreated &&
|
||||||
IsNativeListUsable(_enemySeparationPreviousPushes) &&
|
IsNativeListUsable(_enemySeparationTransforms) &&
|
||||||
IsNativeListUsable(_enemySeparationCurrentPushes) &&
|
IsNativeListUsable(_collectedPickups) &&
|
||||||
IsNativeListUsable(_projectileJobInputs) &&
|
|
||||||
IsNativeListUsable(_projectileJobOutputs) &&
|
|
||||||
IsNativeListUsable(_collisionQueryInputs) &&
|
IsNativeListUsable(_collisionQueryInputs) &&
|
||||||
IsNativeListUsable(_collisionCandidates) &&
|
IsNativeListUsable(_collisionCandidates) &&
|
||||||
IsNativeMultiHashMapUsable(_enemySeparationBuckets) &&
|
IsNativeMultiHashMapUsable(_enemySeparationBuckets) &&
|
||||||
|
|||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
#region Job Output Commit
|
|
||||||
|
|
||||||
private void ApplyJobOutputsToSimulationState()
|
|
||||||
{
|
|
||||||
int enemyCount = Mathf.Min(_enemies.Count, _enemyJobOutputs.Length);
|
|
||||||
bool hasEnemyPositionChanged = false;
|
|
||||||
for (int i = 0; i < enemyCount; i++)
|
|
||||||
{
|
|
||||||
EnemyJobOutputData output = _enemyJobOutputs[i];
|
|
||||||
if (!hasEnemyPositionChanged)
|
|
||||||
{
|
|
||||||
Vector3 currentPosition = _enemies[i].Position;
|
|
||||||
hasEnemyPositionChanged = currentPosition.x != output.Position.x ||
|
|
||||||
currentPosition.z != output.Position.z;
|
|
||||||
}
|
|
||||||
|
|
||||||
_enemies[i] = ConvertToEnemySimData(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasEnemyPositionChanged)
|
|
||||||
{
|
|
||||||
MarkEnemyTargetSpatialIndexDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
int projectileCount = Mathf.Min(_projectiles.Count, _projectileJobOutputs.Length);
|
|
||||||
for (int i = 0; i < projectileCount; i++)
|
|
||||||
{
|
|
||||||
_projectiles[i] = ConvertToProjectileSimData(_projectileJobOutputs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
public interface ISimulationWorld
|
||||||
|
{
|
||||||
|
SimulationStats Stats { get; }
|
||||||
|
|
||||||
|
void Tick(in SimulationTickContext context);
|
||||||
|
|
||||||
|
void ClearSimulationState();
|
||||||
|
|
||||||
|
bool TryGetNearestEnemyEntityId(Vector3 origin, float maxSqrRange, out int enemyEntityId);
|
||||||
|
|
||||||
|
bool TryRequestAreaCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center, float radius,
|
||||||
|
int maxTargets = 16);
|
||||||
|
|
||||||
|
bool TryRequestSectorCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center, float radius,
|
||||||
|
in Vector3 direction, float halfAngleDeg, int maxTargets = 16);
|
||||||
|
|
||||||
|
bool TryRequestRectangleCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
||||||
|
float halfWidth, float halfLength, in Vector3 direction, int maxTargets = 16);
|
||||||
|
|
||||||
|
void SyncPlayerMovementInput(Transform playerTransform, bool isMoving, in Vector3 direction, float speed);
|
||||||
|
|
||||||
|
void UnregisterPlayerMovement(Transform playerTransform);
|
||||||
|
|
||||||
|
void SyncEnemyMovementInput(int entityId, bool isMoving, in Vector3 direction, float speed,
|
||||||
|
bool avoidEnemyOverlap, float enemyBodyRadius, int separationIterations);
|
||||||
|
|
||||||
|
void SyncPlayerAbsorbSettings(float absorbRange, float absorbSpeed, float collectDistance);
|
||||||
|
|
||||||
|
void TriggerFullMapMagnet();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f0174cbb31d284749a20c973c0153ca9
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+7
-10
@@ -1,14 +1,11 @@
|
|||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
{
|
{
|
||||||
public sealed partial class SimulationWorld
|
internal struct AreaCollisionHitEventData
|
||||||
{
|
{
|
||||||
private struct AreaCollisionHitEventData
|
public int QueryId;
|
||||||
{
|
public int SourceEntityId;
|
||||||
public int QueryId;
|
public int SourceOwnerEntityId;
|
||||||
public int SourceEntityId;
|
public int TargetEntityId;
|
||||||
public int SourceOwnerEntityId;
|
public float SqrDistance;
|
||||||
public int TargetEntityId;
|
|
||||||
public float SqrDistance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-15
@@ -2,21 +2,18 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
{
|
{
|
||||||
public sealed partial class SimulationWorld
|
internal struct AreaCollisionRequestData
|
||||||
{
|
{
|
||||||
private struct AreaCollisionRequestData
|
public int SourceEntityId;
|
||||||
{
|
public int SourceOwnerEntityId;
|
||||||
public int SourceEntityId;
|
public bool SourceWasActiveAtQueryTime;
|
||||||
public int SourceOwnerEntityId;
|
public Vector3 Center;
|
||||||
public bool SourceWasActiveAtQueryTime;
|
public float Radius;
|
||||||
public Vector3 Center;
|
public int MaxTargets;
|
||||||
public float Radius;
|
public int ShapeType;
|
||||||
public int MaxTargets;
|
public Vector3 Direction;
|
||||||
public int ShapeType;
|
public float HalfAngleDeg;
|
||||||
public Vector3 Direction;
|
public float HalfWidth;
|
||||||
public float HalfAngleDeg;
|
public float HalfLength;
|
||||||
public float HalfWidth;
|
|
||||||
public float HalfLength;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
using Unity.Burst;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Jobs;
|
|
||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
[BurstCompile]
|
|
||||||
private struct BuildEnemySeparationBucketsBurstJob : IJobParallelFor
|
|
||||||
{
|
|
||||||
[ReadOnly] public NativeArray<EnemyJobOutputData> Inputs;
|
|
||||||
public NativeParallelMultiHashMap<long, int>.ParallelWriter Buckets;
|
|
||||||
public float CellSize;
|
|
||||||
|
|
||||||
public void Execute(int index)
|
|
||||||
{
|
|
||||||
BuildEnemySeparationBucket(
|
|
||||||
index,
|
|
||||||
Inputs,
|
|
||||||
Buckets,
|
|
||||||
CellSize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void BuildEnemySeparationBucket(
|
|
||||||
int index,
|
|
||||||
NativeArray<EnemyJobOutputData> inputs,
|
|
||||||
NativeParallelMultiHashMap<long, int>.ParallelWriter buckets,
|
|
||||||
float cellSize
|
|
||||||
)
|
|
||||||
{
|
|
||||||
EnemyJobOutputData output = inputs[index];
|
|
||||||
if (!output.AvoidEnemyOverlap)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 position = output.Position;
|
|
||||||
position.y = 0f;
|
|
||||||
int cellX = (int)math.floor(position.x / cellSize);
|
|
||||||
int cellZ = (int)math.floor(position.z / cellSize);
|
|
||||||
buckets.Add(SeparationCellKey(cellX, cellZ), index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: e833e91677bb49a4a458c9c8b90099e9
|
|
||||||
timeCreated: 1771901500
|
|
||||||
+8
-11
@@ -1,15 +1,12 @@
|
|||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
{
|
{
|
||||||
public sealed partial class SimulationWorld
|
internal struct CollisionCandidateData
|
||||||
{
|
{
|
||||||
private struct CollisionCandidateData
|
public int QueryId;
|
||||||
{
|
public int SourceType;
|
||||||
public int QueryId;
|
public int SourceEntityId;
|
||||||
public int SourceType;
|
public int SourceOwnerEntityId;
|
||||||
public int SourceEntityId;
|
public int TargetEntityId;
|
||||||
public int SourceOwnerEntityId;
|
public float SqrDistance;
|
||||||
public int TargetEntityId;
|
|
||||||
public float SqrDistance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-17
@@ -2,23 +2,20 @@ using Unity.Mathematics;
|
|||||||
|
|
||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
{
|
{
|
||||||
public sealed partial class SimulationWorld
|
internal struct CollisionQueryData
|
||||||
{
|
{
|
||||||
private struct CollisionQueryData
|
public int QueryId;
|
||||||
{
|
public int SourceType;
|
||||||
public int QueryId;
|
public int SourceEntityId;
|
||||||
public int SourceType;
|
public int SourceOwnerEntityId;
|
||||||
public int SourceEntityId;
|
public bool SourceWasActiveAtQueryTime;
|
||||||
public int SourceOwnerEntityId;
|
public float3 Position;
|
||||||
public bool SourceWasActiveAtQueryTime;
|
public float Radius;
|
||||||
public float3 Position;
|
public int MaxTargets;
|
||||||
public float Radius;
|
public int ShapeType;
|
||||||
public int MaxTargets;
|
public float3 Direction;
|
||||||
public int ShapeType;
|
public float HalfAngleDeg;
|
||||||
public float3 Direction;
|
public float HalfWidth;
|
||||||
public float HalfAngleDeg;
|
public float HalfLength;
|
||||||
public float HalfWidth;
|
|
||||||
public float HalfLength;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
private struct EnemyJobInputData
|
|
||||||
{
|
|
||||||
public int EntityId;
|
|
||||||
public float3 Position;
|
|
||||||
public float3 Forward;
|
|
||||||
public quaternion Rotation;
|
|
||||||
public float Speed;
|
|
||||||
public float AttackRange;
|
|
||||||
public bool AvoidEnemyOverlap;
|
|
||||||
public float EnemyBodyRadius;
|
|
||||||
public int SeparationIterations;
|
|
||||||
public int TargetType;
|
|
||||||
public int State;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 171936b8a6b84a8fbacc7651ce473b98
|
|
||||||
timeCreated: 1771901146
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
private struct EnemyJobOutputData
|
|
||||||
{
|
|
||||||
public int EntityId;
|
|
||||||
public float3 Position;
|
|
||||||
public float3 Forward;
|
|
||||||
public quaternion Rotation;
|
|
||||||
public float Speed;
|
|
||||||
public float AttackRange;
|
|
||||||
public bool AvoidEnemyOverlap;
|
|
||||||
public float EnemyBodyRadius;
|
|
||||||
public int SeparationIterations;
|
|
||||||
public int TargetType;
|
|
||||||
public int State;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 34ef6fbf123f45ca88d8e7ab8311d543
|
|
||||||
timeCreated: 1771901204
|
|
||||||
-93
@@ -1,93 +0,0 @@
|
|||||||
using Unity.Burst;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Jobs;
|
|
||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
[BurstCompile]
|
|
||||||
private struct EnemyMovementBurstJob : IJobParallelFor
|
|
||||||
{
|
|
||||||
[ReadOnly] public NativeArray<EnemyJobInputData> Inputs;
|
|
||||||
public NativeArray<EnemyJobOutputData> Outputs;
|
|
||||||
public float DeltaTime;
|
|
||||||
public float3 PlayerPosition;
|
|
||||||
|
|
||||||
public void Execute(int index)
|
|
||||||
{
|
|
||||||
ExecuteEnemyMovement(
|
|
||||||
index,
|
|
||||||
Inputs,
|
|
||||||
Outputs,
|
|
||||||
DeltaTime,
|
|
||||||
PlayerPosition
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ExecuteEnemyMovement(
|
|
||||||
int index,
|
|
||||||
NativeArray<EnemyJobInputData> inputs,
|
|
||||||
NativeArray<EnemyJobOutputData> outputs,
|
|
||||||
float deltaTime,
|
|
||||||
float3 playerPosition
|
|
||||||
)
|
|
||||||
{
|
|
||||||
EnemyJobInputData input = inputs[index];
|
|
||||||
float attackRange = input.AttackRange > 0f ? input.AttackRange : DefaultAttackRange;
|
|
||||||
float attackRangeSqr = attackRange * attackRange;
|
|
||||||
|
|
||||||
float3 currentPosition = input.Position;
|
|
||||||
float3 horizontalPosition = new float3(currentPosition.x, 0f, currentPosition.z);
|
|
||||||
float3 toPlayer = playerPosition - horizontalPosition;
|
|
||||||
float sqrDistance = math.lengthsq(toPlayer);
|
|
||||||
bool isInAttackRange = sqrDistance <= attackRangeSqr;
|
|
||||||
bool canChase = !isInAttackRange && input.Speed > 0f && sqrDistance > float.Epsilon;
|
|
||||||
|
|
||||||
float3 forward = input.Forward;
|
|
||||||
float3 desiredPosition = currentPosition;
|
|
||||||
quaternion rotation = input.Rotation;
|
|
||||||
|
|
||||||
if (canChase)
|
|
||||||
{
|
|
||||||
forward = math.normalizesafe(toPlayer, forward);
|
|
||||||
desiredPosition = currentPosition + forward * input.Speed * deltaTime;
|
|
||||||
if (math.lengthsq(forward) > float.Epsilon)
|
|
||||||
{
|
|
||||||
rotation = quaternion.LookRotationSafe(forward, math.up());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int nextState;
|
|
||||||
if (isInAttackRange)
|
|
||||||
{
|
|
||||||
nextState = EnemyStateInAttackRange;
|
|
||||||
}
|
|
||||||
else if (canChase)
|
|
||||||
{
|
|
||||||
nextState = EnemyStateChasing;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nextState = EnemyStateIdle;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs[index] = new EnemyJobOutputData
|
|
||||||
{
|
|
||||||
EntityId = input.EntityId,
|
|
||||||
Position = desiredPosition,
|
|
||||||
Forward = forward,
|
|
||||||
Rotation = rotation,
|
|
||||||
Speed = input.Speed,
|
|
||||||
AttackRange = attackRange,
|
|
||||||
AvoidEnemyOverlap = input.AvoidEnemyOverlap,
|
|
||||||
EnemyBodyRadius = input.EnemyBodyRadius,
|
|
||||||
SeparationIterations = input.SeparationIterations,
|
|
||||||
TargetType = input.TargetType,
|
|
||||||
State = nextState
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: abd5519779ed41d4947280eec326ffb1
|
|
||||||
timeCreated: 1771901472
|
|
||||||
-245
@@ -1,245 +0,0 @@
|
|||||||
using Unity.Burst;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Jobs;
|
|
||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
[BurstCompile]
|
|
||||||
private struct EnemySeparationBurstJob : IJobParallelFor
|
|
||||||
{
|
|
||||||
[ReadOnly] public NativeArray<EnemyJobOutputData> Inputs;
|
|
||||||
[ReadOnly] public NativeParallelMultiHashMap<long, int> Buckets;
|
|
||||||
[ReadOnly] public NativeArray<float2> PreviousPushes;
|
|
||||||
public NativeArray<EnemyJobOutputData> Outputs;
|
|
||||||
public NativeArray<float2> CurrentPushes;
|
|
||||||
public float CellSize;
|
|
||||||
public float MaxRadius;
|
|
||||||
public float3 PlayerPosition;
|
|
||||||
public float PushDamping;
|
|
||||||
public float MaxStepScale;
|
|
||||||
public bool UseTangentialInAttackRange;
|
|
||||||
public float PushSmoothing;
|
|
||||||
|
|
||||||
public void Execute(int index)
|
|
||||||
{
|
|
||||||
ExecuteEnemySeparation(
|
|
||||||
index,
|
|
||||||
Inputs,
|
|
||||||
Buckets,
|
|
||||||
Outputs,
|
|
||||||
CellSize,
|
|
||||||
MaxRadius,
|
|
||||||
PlayerPosition,
|
|
||||||
PushDamping,
|
|
||||||
MaxStepScale,
|
|
||||||
UseTangentialInAttackRange,
|
|
||||||
PreviousPushes,
|
|
||||||
CurrentPushes,
|
|
||||||
PushSmoothing
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ExecuteEnemySeparation(
|
|
||||||
int index,
|
|
||||||
NativeArray<EnemyJobOutputData> inputs,
|
|
||||||
NativeParallelMultiHashMap<long, int> buckets,
|
|
||||||
NativeArray<EnemyJobOutputData> outputs,
|
|
||||||
float cellSize,
|
|
||||||
float maxRadius,
|
|
||||||
float3 playerPosition,
|
|
||||||
float pushDamping,
|
|
||||||
float maxStepScale,
|
|
||||||
bool useTangentialInAttackRange,
|
|
||||||
NativeArray<float2> previousPushes,
|
|
||||||
NativeArray<float2> currentPushes,
|
|
||||||
float pushSmoothing
|
|
||||||
)
|
|
||||||
{
|
|
||||||
currentPushes[index] = float2.zero;
|
|
||||||
EnemyJobOutputData self = inputs[index];
|
|
||||||
if (!self.AvoidEnemyOverlap)
|
|
||||||
{
|
|
||||||
outputs[index] = self;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 candidate = self.Position;
|
|
||||||
candidate.y = 0f;
|
|
||||||
float3 original = candidate;
|
|
||||||
float3 fallback =
|
|
||||||
math.normalizesafe(new float3(self.Forward.x, 0f, self.Forward.z), new float3(1f, 0f, 0f));
|
|
||||||
float selfRadius = self.EnemyBodyRadius > 0f ? self.EnemyBodyRadius : 0.45f;
|
|
||||||
int iterations = self.SeparationIterations > 0 ? self.SeparationIterations : 1;
|
|
||||||
int queryRange = math.max(1, (int)math.ceil((selfRadius + maxRadius) / cellSize));
|
|
||||||
|
|
||||||
for (int iter = 0; iter < iterations; iter++)
|
|
||||||
{
|
|
||||||
int cellX = (int)math.floor(candidate.x / cellSize);
|
|
||||||
int cellZ = (int)math.floor(candidate.z / cellSize);
|
|
||||||
float3 pushAccumulation = float3.zero;
|
|
||||||
|
|
||||||
for (int dx = -queryRange; dx <= queryRange; dx++)
|
|
||||||
{
|
|
||||||
for (int dz = -queryRange; dz <= queryRange; dz++)
|
|
||||||
{
|
|
||||||
long key = SeparationCellKey(cellX + dx, cellZ + dz);
|
|
||||||
if (!buckets.TryGetFirstValue(key, out int otherIndex,
|
|
||||||
out NativeParallelMultiHashMapIterator<long> iterator))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (otherIndex == index)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnemyJobOutputData other = inputs[otherIndex];
|
|
||||||
if (!other.AvoidEnemyOverlap)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float otherRadius = other.EnemyBodyRadius > 0f ? other.EnemyBodyRadius : 0.45f;
|
|
||||||
float minDistance = selfRadius + otherRadius;
|
|
||||||
float minDistanceSqr = minDistance * minDistance;
|
|
||||||
|
|
||||||
float3 otherPosition = other.Position;
|
|
||||||
otherPosition.y = 0f;
|
|
||||||
float3 toSelf = candidate - otherPosition;
|
|
||||||
float sqrDistance = math.lengthsq(toSelf);
|
|
||||||
|
|
||||||
if (sqrDistance <= float.Epsilon)
|
|
||||||
{
|
|
||||||
float3 zeroDistanceAxis = GetZeroDistanceSeparationAxis(index, otherIndex);
|
|
||||||
float directionSign = index < otherIndex ? 1f : -1f;
|
|
||||||
pushAccumulation += zeroDistanceAxis * (selfRadius * 0.25f * directionSign);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sqrDistance >= minDistanceSqr)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float distance = math.sqrt(sqrDistance);
|
|
||||||
float penetration = minDistance - distance;
|
|
||||||
pushAccumulation += (toSelf / distance) * penetration;
|
|
||||||
} while (buckets.TryGetNextValue(out otherIndex, ref iterator));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (math.lengthsq(pushAccumulation) <= float.Epsilon)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 resolvedPush = pushAccumulation * pushDamping;
|
|
||||||
|
|
||||||
float maxStep = selfRadius * maxStepScale;
|
|
||||||
float pushLength = math.length(resolvedPush);
|
|
||||||
if (pushLength > maxStep && pushLength > float.Epsilon)
|
|
||||||
{
|
|
||||||
resolvedPush = resolvedPush / pushLength * maxStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
candidate += resolvedPush;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 framePush = candidate - original;
|
|
||||||
float2 previousPush2 = previousPushes[index];
|
|
||||||
float3 previousPush = new float3(previousPush2.x, 0f, previousPush2.y);
|
|
||||||
float3 smoothedPush = SmoothSeparationPush(framePush, previousPush, pushSmoothing);
|
|
||||||
|
|
||||||
if (useTangentialInAttackRange && self.State == EnemyStateInAttackRange)
|
|
||||||
{
|
|
||||||
smoothedPush = ProjectToTangential(smoothedPush, playerPosition, original);
|
|
||||||
}
|
|
||||||
|
|
||||||
float maxTotalStep = selfRadius * maxStepScale * iterations;
|
|
||||||
float smoothedLength = math.length(smoothedPush);
|
|
||||||
if (smoothedLength > maxTotalStep && smoothedLength > float.Epsilon)
|
|
||||||
{
|
|
||||||
smoothedPush = smoothedPush / smoothedLength * maxTotalStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 finalPosition = original + smoothedPush;
|
|
||||||
currentPushes[index] = new float2(smoothedPush.x, smoothedPush.z);
|
|
||||||
self.Position = new float3(finalPosition.x, self.Position.y, finalPosition.z);
|
|
||||||
if (math.lengthsq(smoothedPush) > float.Epsilon)
|
|
||||||
{
|
|
||||||
self.Forward = new float3(fallback.x, self.Forward.y, fallback.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs[index] = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float3 SmoothSeparationPush(float3 framePush, float3 previousPush, float pushSmoothing)
|
|
||||||
{
|
|
||||||
float frameLengthSqr = math.lengthsq(framePush);
|
|
||||||
float previousLengthSqr = math.lengthsq(previousPush);
|
|
||||||
|
|
||||||
if (frameLengthSqr <= float.Epsilon)
|
|
||||||
{
|
|
||||||
return float3.zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousLengthSqr <= float.Epsilon || pushSmoothing <= 0f)
|
|
||||||
{
|
|
||||||
return framePush;
|
|
||||||
}
|
|
||||||
|
|
||||||
float frameLength = math.sqrt(frameLengthSqr);
|
|
||||||
float previousLength = math.sqrt(previousLengthSqr);
|
|
||||||
float3 frameDirection = framePush / frameLength;
|
|
||||||
float3 previousDirection = previousPush / previousLength;
|
|
||||||
float directionAlignment = math.dot(frameDirection, previousDirection);
|
|
||||||
|
|
||||||
if (directionAlignment >= 0.35f)
|
|
||||||
{
|
|
||||||
return framePush;
|
|
||||||
}
|
|
||||||
|
|
||||||
float directionalFactor = math.saturate((0.35f - directionAlignment) / 1.35f);
|
|
||||||
float smoothingStrength = pushSmoothing * directionalFactor;
|
|
||||||
return math.lerp(framePush, previousPush, smoothingStrength);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float3 ProjectToTangential(float3 push, float3 playerPosition, float3 currentPosition)
|
|
||||||
{
|
|
||||||
if (math.lengthsq(push) <= float.Epsilon)
|
|
||||||
{
|
|
||||||
return push;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 toPlayer = playerPosition - currentPosition;
|
|
||||||
float toPlayerSqr = math.lengthsq(toPlayer);
|
|
||||||
if (toPlayerSqr <= float.Epsilon)
|
|
||||||
{
|
|
||||||
return push;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 radialDirection = toPlayer / math.sqrt(toPlayerSqr);
|
|
||||||
float radialOffset = math.dot(push, radialDirection);
|
|
||||||
return push - radialDirection * radialOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float3 GetZeroDistanceSeparationAxis(int index, int otherIndex)
|
|
||||||
{
|
|
||||||
int lowIndex = math.min(index, otherIndex);
|
|
||||||
int highIndex = math.max(index, otherIndex);
|
|
||||||
uint pairHash = (uint)(lowIndex * 73856093) ^ (uint)(highIndex * 19349663);
|
|
||||||
|
|
||||||
float axisX = (pairHash & 1023u) / 511.5f - 1f;
|
|
||||||
float axisZ = ((pairHash >> 10) & 1023u) / 511.5f - 1f;
|
|
||||||
float3 axis = new float3(axisX, 0f, axisZ);
|
|
||||||
return math.normalizesafe(axis, new float3(1f, 0f, 0f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 94a77477c3a8410cb58b5028dd5d2c63
|
|
||||||
timeCreated: 1771901543
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
private struct ProjectileJobInputData
|
|
||||||
{
|
|
||||||
public int EntityId;
|
|
||||||
public int OwnerEntityId;
|
|
||||||
public float3 Position;
|
|
||||||
public float3 Forward;
|
|
||||||
public float3 Velocity;
|
|
||||||
public float Speed;
|
|
||||||
public float LifeTime;
|
|
||||||
public float Age;
|
|
||||||
public bool Active;
|
|
||||||
public float RemainingLifetime;
|
|
||||||
public int State;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 334ca3869d7940058256ba5419573637
|
|
||||||
timeCreated: 1771901235
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
private struct ProjectileJobOutputData
|
|
||||||
{
|
|
||||||
public int EntityId;
|
|
||||||
public int OwnerEntityId;
|
|
||||||
public float3 Position;
|
|
||||||
public float3 Forward;
|
|
||||||
public float3 Velocity;
|
|
||||||
public float Speed;
|
|
||||||
public float LifeTime;
|
|
||||||
public float Age;
|
|
||||||
public bool Active;
|
|
||||||
public float RemainingLifetime;
|
|
||||||
public int State;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: b5c4a144914548379d9991d8ef061da7
|
|
||||||
timeCreated: 1771901266
|
|
||||||
-118
@@ -1,118 +0,0 @@
|
|||||||
using Unity.Burst;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Jobs;
|
|
||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
[BurstCompile]
|
|
||||||
private struct ProjectileMovementBurstJob : IJobParallelFor
|
|
||||||
{
|
|
||||||
[ReadOnly] public NativeArray<ProjectileJobInputData> Inputs;
|
|
||||||
public NativeArray<ProjectileJobOutputData> Outputs;
|
|
||||||
public float DeltaTime;
|
|
||||||
public float3 PlayerPosition;
|
|
||||||
public float MaxSqrDistanceFromPlayer;
|
|
||||||
public float MaxVerticalOffsetFromPlayer;
|
|
||||||
|
|
||||||
public void Execute(int index)
|
|
||||||
{
|
|
||||||
ExecuteProjectileMovement(
|
|
||||||
index,
|
|
||||||
Inputs,
|
|
||||||
Outputs,
|
|
||||||
DeltaTime,
|
|
||||||
PlayerPosition,
|
|
||||||
MaxSqrDistanceFromPlayer,
|
|
||||||
MaxVerticalOffsetFromPlayer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ExecuteProjectileMovement(
|
|
||||||
int index,
|
|
||||||
NativeArray<ProjectileJobInputData> inputs,
|
|
||||||
NativeArray<ProjectileJobOutputData> outputs,
|
|
||||||
float deltaTime,
|
|
||||||
float3 playerPosition,
|
|
||||||
float maxSqrDistanceFromPlayer,
|
|
||||||
float maxVerticalOffsetFromPlayer)
|
|
||||||
{
|
|
||||||
ProjectileJobInputData input = inputs[index];
|
|
||||||
ProjectileJobOutputData output = new ProjectileJobOutputData
|
|
||||||
{
|
|
||||||
EntityId = input.EntityId,
|
|
||||||
OwnerEntityId = input.OwnerEntityId,
|
|
||||||
Position = input.Position,
|
|
||||||
Forward = input.Forward,
|
|
||||||
Velocity = input.Velocity,
|
|
||||||
Speed = input.Speed,
|
|
||||||
LifeTime = input.LifeTime,
|
|
||||||
Age = input.Age,
|
|
||||||
Active = input.Active,
|
|
||||||
RemainingLifetime = input.RemainingLifetime,
|
|
||||||
State = input.State
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!input.Active)
|
|
||||||
{
|
|
||||||
output.State = ProjectileStateExpired;
|
|
||||||
outputs[index] = output;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 position = input.Position;
|
|
||||||
float3 forward = input.Forward;
|
|
||||||
float3 velocity = input.Velocity;
|
|
||||||
if (math.lengthsq(velocity) <= float.Epsilon && input.Speed > 0f)
|
|
||||||
{
|
|
||||||
float3 moveDirection = math.normalizesafe(forward, new float3(0f, 0f, 1f));
|
|
||||||
velocity = moveDirection * input.Speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 nextPosition = position + velocity * deltaTime;
|
|
||||||
float nextAge = math.max(0f, input.Age + deltaTime);
|
|
||||||
float nextRemainingLifetime = input.RemainingLifetime;
|
|
||||||
bool shouldExpire = false;
|
|
||||||
|
|
||||||
if (input.LifeTime > 0f)
|
|
||||||
{
|
|
||||||
nextRemainingLifetime = math.max(0f, input.LifeTime - nextAge);
|
|
||||||
shouldExpire = nextAge >= input.LifeTime;
|
|
||||||
}
|
|
||||||
else if (input.RemainingLifetime > 0f)
|
|
||||||
{
|
|
||||||
nextRemainingLifetime = math.max(0f, input.RemainingLifetime - deltaTime);
|
|
||||||
shouldExpire = nextRemainingLifetime <= float.Epsilon;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldExpire && maxSqrDistanceFromPlayer > 0f)
|
|
||||||
{
|
|
||||||
float3 horizontalDelta =
|
|
||||||
new float3(nextPosition.x - playerPosition.x, 0f, nextPosition.z - playerPosition.z);
|
|
||||||
shouldExpire = math.lengthsq(horizontalDelta) > maxSqrDistanceFromPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldExpire && maxVerticalOffsetFromPlayer > 0f)
|
|
||||||
{
|
|
||||||
shouldExpire = math.abs(nextPosition.y - playerPosition.y) > maxVerticalOffsetFromPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.Position = nextPosition;
|
|
||||||
output.Velocity = velocity;
|
|
||||||
output.Age = nextAge;
|
|
||||||
output.RemainingLifetime = nextRemainingLifetime;
|
|
||||||
output.Active = !shouldExpire;
|
|
||||||
output.State = shouldExpire ? ProjectileStateExpired : ProjectileStateActive;
|
|
||||||
|
|
||||||
if (math.lengthsq(velocity) > float.Epsilon)
|
|
||||||
{
|
|
||||||
float3 moveForward = math.normalizesafe(velocity, forward);
|
|
||||||
output.Forward = moveForward;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs[index] = output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 49001e849c1b4afdaa37d851b9b76866
|
|
||||||
timeCreated: 1771901884
|
|
||||||
-132
@@ -1,132 +0,0 @@
|
|||||||
using Unity.Burst;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Jobs;
|
|
||||||
using Unity.Mathematics;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
|
||||||
{
|
|
||||||
public sealed partial class SimulationWorld
|
|
||||||
{
|
|
||||||
[BurstCompile]
|
|
||||||
private struct QueryCollisionCandidatesBurstJob : IJobParallelFor
|
|
||||||
{
|
|
||||||
[ReadOnly] public NativeArray<CollisionQueryData> Queries;
|
|
||||||
[ReadOnly] public NativeParallelMultiHashMap<long, int> EnemyBuckets;
|
|
||||||
[ReadOnly] public NativeArray<EnemyJobOutputData> EnemyOutputs;
|
|
||||||
public NativeList<CollisionCandidateData>.ParallelWriter Candidates;
|
|
||||||
public bool HasEnemyTargets;
|
|
||||||
public bool HasPlayerTarget;
|
|
||||||
public int PlayerTargetEntityId;
|
|
||||||
public float3 PlayerPosition;
|
|
||||||
public float CellSize;
|
|
||||||
|
|
||||||
public void Execute(int index)
|
|
||||||
{
|
|
||||||
CollisionQueryData query = Queries[index];
|
|
||||||
int centerCellX = (int)math.floor(query.Position.x / CellSize);
|
|
||||||
int centerCellZ = (int)math.floor(query.Position.z / CellSize);
|
|
||||||
int queryRange = math.max(1, (int)math.ceil(query.Radius / CellSize));
|
|
||||||
int selectedCount = 0;
|
|
||||||
bool reachedLimit = false;
|
|
||||||
|
|
||||||
if (HasPlayerTarget && query.SourceEntityId != PlayerTargetEntityId &&
|
|
||||||
query.SourceOwnerEntityId != PlayerTargetEntityId)
|
|
||||||
{
|
|
||||||
float3 playerPosition = PlayerPosition;
|
|
||||||
if (query.SourceType == CollisionSourceTypeArea)
|
|
||||||
{
|
|
||||||
playerPosition.y = query.Position.y;
|
|
||||||
}
|
|
||||||
float3 playerDelta = playerPosition - query.Position;
|
|
||||||
float playerSqrDistance = math.lengthsq(playerDelta);
|
|
||||||
float playerRadiusSqr = query.Radius * query.Radius;
|
|
||||||
if (playerSqrDistance <= playerRadiusSqr)
|
|
||||||
{
|
|
||||||
Candidates.AddNoResize(new CollisionCandidateData
|
|
||||||
{
|
|
||||||
QueryId = query.QueryId,
|
|
||||||
SourceType = query.SourceType,
|
|
||||||
SourceEntityId = query.SourceEntityId,
|
|
||||||
SourceOwnerEntityId = query.SourceOwnerEntityId,
|
|
||||||
TargetEntityId = PlayerTargetEntityId,
|
|
||||||
SqrDistance = playerSqrDistance
|
|
||||||
});
|
|
||||||
|
|
||||||
selectedCount++;
|
|
||||||
if (selectedCount >= query.MaxTargets)
|
|
||||||
{
|
|
||||||
reachedLimit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!HasEnemyTargets || reachedLimit)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int dx = -queryRange; dx <= queryRange && !reachedLimit; dx++)
|
|
||||||
{
|
|
||||||
for (int dz = -queryRange; dz <= queryRange && !reachedLimit; dz++)
|
|
||||||
{
|
|
||||||
long key = SeparationCellKey(centerCellX + dx, centerCellZ + dz);
|
|
||||||
if (!EnemyBuckets.TryGetFirstValue(key, out int enemyIndex,
|
|
||||||
out NativeParallelMultiHashMapIterator<long> iterator))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (enemyIndex < 0 || enemyIndex >= EnemyOutputs.Length)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnemyJobOutputData enemy = EnemyOutputs[enemyIndex];
|
|
||||||
if (enemy.EntityId == query.SourceOwnerEntityId)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float deltaY = query.SourceType == CollisionSourceTypeArea
|
|
||||||
? 0f
|
|
||||||
: enemy.Position.y - query.Position.y;
|
|
||||||
float3 delta = new float3(
|
|
||||||
enemy.Position.x - query.Position.x,
|
|
||||||
deltaY,
|
|
||||||
enemy.Position.z - query.Position.z);
|
|
||||||
float sqrDistance = math.lengthsq(delta);
|
|
||||||
float targetRadius = query.SourceType == CollisionSourceTypeArea
|
|
||||||
? math.max(0f, enemy.EnemyBodyRadius)
|
|
||||||
: 0f;
|
|
||||||
float effectiveRadius = query.Radius + targetRadius;
|
|
||||||
if (sqrDistance > effectiveRadius * effectiveRadius)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Candidates.AddNoResize(new CollisionCandidateData
|
|
||||||
{
|
|
||||||
QueryId = query.QueryId,
|
|
||||||
SourceType = query.SourceType,
|
|
||||||
SourceEntityId = query.SourceEntityId,
|
|
||||||
SourceOwnerEntityId = query.SourceOwnerEntityId,
|
|
||||||
TargetEntityId = enemy.EntityId,
|
|
||||||
SqrDistance = sqrDistance
|
|
||||||
});
|
|
||||||
|
|
||||||
selectedCount++;
|
|
||||||
|
|
||||||
if (selectedCount >= query.MaxTargets)
|
|
||||||
{
|
|
||||||
reachedLimit = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (EnemyBuckets.TryGetNextValue(out enemyIndex, ref iterator));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 81aa43bb35f547c48b867eafa17a3857
|
|
||||||
timeCreated: 1771914779
|
|
||||||
+16
-85
@@ -1,8 +1,5 @@
|
|||||||
using SepCore.Debugger;
|
using SepCore.Debugger;
|
||||||
using SepCore.Entity;
|
using SepCore.Entity;
|
||||||
using Unity.Burst;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Jobs;
|
|
||||||
using Unity.Mathematics;
|
using Unity.Mathematics;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@@ -24,7 +21,7 @@ namespace SepCore.Simulation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int projectileCount = _projectileJobOutputs.Length;
|
int projectileCount = _projectiles.Count;
|
||||||
int areaQueryCount = _areaCollisionRequests.Count;
|
int areaQueryCount = _areaCollisionRequests.Count;
|
||||||
if (projectileCount == 0 && areaQueryCount == 0)
|
if (projectileCount == 0 && areaQueryCount == 0)
|
||||||
{
|
{
|
||||||
@@ -42,13 +39,15 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < projectileCount; i++)
|
for (int i = 0; i < projectileCount; i++)
|
||||||
{
|
{
|
||||||
ProjectileJobOutputData projectile = _projectileJobOutputs[i];
|
if (!_projectiles.Life[i].Active)
|
||||||
if (!projectile.Active || projectile.State != ProjectileStateActive)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddProjectileCollisionQuery(queryId, in projectile, queryRadius, maxCandidatesPerQuery);
|
ProjectileTransformSim projectileTransform = _projectiles.Transform[i];
|
||||||
|
ProjectileIdentitySim projectileIdentity = _projectiles.Identity[i];
|
||||||
|
AddProjectileCollisionQuery(queryId, in projectileTransform, in projectileIdentity, queryRadius,
|
||||||
|
maxCandidatesPerQuery);
|
||||||
queryId++;
|
queryId++;
|
||||||
projectileQueryCount++;
|
projectileQueryCount++;
|
||||||
}
|
}
|
||||||
@@ -80,14 +79,14 @@ namespace SepCore.Simulation
|
|||||||
_lastProjectileCollisionCandidateCount = 0;
|
_lastProjectileCollisionCandidateCount = 0;
|
||||||
_lastAreaCollisionCandidateCount = 0;
|
_lastAreaCollisionCandidateCount = 0;
|
||||||
_lastCollisionCellSize = 0f;
|
_lastCollisionCellSize = 0f;
|
||||||
_lastCollisionHasEnemyTargets = _enemyJobOutputs.Length > 0;
|
_lastCollisionHasEnemyTargets = _enemies.Count > 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float autoCellSize = maxQueryRadius * 2f;
|
float autoCellSize = maxQueryRadius * 2f;
|
||||||
float configuredCellSize = _projectileCollisionCellSize > 0f ? _projectileCollisionCellSize : autoCellSize;
|
float configuredCellSize = _projectileCollisionCellSize > 0f ? _projectileCollisionCellSize : autoCellSize;
|
||||||
float cellSize = Mathf.Max(0.1f, configuredCellSize);
|
float cellSize = Mathf.Max(0.1f, configuredCellSize);
|
||||||
bool hasEnemyTargets = _enemyJobOutputs.Length > 0;
|
bool hasEnemyTargets = _enemies.Count > 0;
|
||||||
_lastCollisionCellSize = cellSize;
|
_lastCollisionCellSize = cellSize;
|
||||||
_lastCollisionHasEnemyTargets = hasEnemyTargets;
|
_lastCollisionHasEnemyTargets = hasEnemyTargets;
|
||||||
|
|
||||||
@@ -95,13 +94,17 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
if (hasEnemyTargets)
|
if (hasEnemyTargets)
|
||||||
{
|
{
|
||||||
BuildEnemyCollisionBuckets(cellSize);
|
CollisionQuerySystem.BuildEnemyCollisionBuckets(_enemies, _enemyCollisionBuckets, cellSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (CustomProfilerMarker.Collision_QueryCandidates.Auto())
|
using (CustomProfilerMarker.Collision_QueryCandidates.Auto())
|
||||||
{
|
{
|
||||||
_collisionCandidateQueryScheduled = ScheduleCollisionCandidateQueryJob(cellSize, hasEnemyTargets,
|
bool hasPlayerTarget = TryGetPlayerCollisionTarget(out int playerTargetEntityId,
|
||||||
|
out float3 playerPosition);
|
||||||
|
_collisionCandidateQueryScheduled = CollisionQuerySystem.ScheduleCandidateQuery(
|
||||||
|
_collisionQueryInputs, _collisionCandidates, _enemyCollisionBuckets, _enemies, hasEnemyTargets,
|
||||||
|
hasPlayerTarget, playerTargetEntityId, playerPosition, cellSize,
|
||||||
out _collisionCandidateQueryHandle);
|
out _collisionCandidateQueryHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,78 +123,6 @@ namespace SepCore.Simulation
|
|||||||
_lastCollisionCandidateCount = projectileCandidateCount + areaCandidateCount;
|
_lastCollisionCandidateCount = projectileCandidateCount + areaCandidateCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildEnemyCollisionBuckets(float cellSize)
|
|
||||||
{
|
|
||||||
_enemyCollisionBuckets.Clear();
|
|
||||||
int enemyCount = _enemyJobOutputs.Length;
|
|
||||||
if (enemyCount <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildCollisionBucketsBurstJob job = new BuildCollisionBucketsBurstJob
|
|
||||||
{
|
|
||||||
EnemyOutputs = _enemyJobOutputs.AsArray(),
|
|
||||||
Buckets = _enemyCollisionBuckets.AsParallelWriter(),
|
|
||||||
CellSize = cellSize
|
|
||||||
};
|
|
||||||
|
|
||||||
using (CustomProfilerMarker.TickEnemies_Complete.Auto())
|
|
||||||
{
|
|
||||||
JobHandle handle = job.Schedule(enemyCount, 64);
|
|
||||||
handle.Complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[BurstCompile]
|
|
||||||
private struct BuildCollisionBucketsBurstJob : IJobParallelFor
|
|
||||||
{
|
|
||||||
[ReadOnly] public NativeArray<EnemyJobOutputData> EnemyOutputs;
|
|
||||||
public NativeParallelMultiHashMap<long, int>.ParallelWriter Buckets;
|
|
||||||
public float CellSize;
|
|
||||||
|
|
||||||
public void Execute(int index)
|
|
||||||
{
|
|
||||||
EnemyJobOutputData enemy = EnemyOutputs[index];
|
|
||||||
int cellX = (int)math.floor(enemy.Position.x / CellSize);
|
|
||||||
int cellZ = (int)math.floor(enemy.Position.z / CellSize);
|
|
||||||
Buckets.Add(SeparationCellKey(cellX, cellZ), index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ScheduleCollisionCandidateQueryJob(float cellSize, bool hasEnemyTargets, out JobHandle handle)
|
|
||||||
{
|
|
||||||
handle = default;
|
|
||||||
if (!_collisionQueryInputs.IsCreated || !_collisionCandidates.IsCreated)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_collisionCandidates.Clear();
|
|
||||||
int queryCount = _collisionQueryInputs.Length;
|
|
||||||
if (queryCount == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasPlayerTarget = TryGetPlayerCollisionTarget(out int playerTargetEntityId, out float3 playerPosition);
|
|
||||||
QueryCollisionCandidatesBurstJob job = new QueryCollisionCandidatesBurstJob
|
|
||||||
{
|
|
||||||
Queries = _collisionQueryInputs.AsArray(),
|
|
||||||
EnemyBuckets = _enemyCollisionBuckets,
|
|
||||||
EnemyOutputs = _enemyJobOutputs.AsArray(),
|
|
||||||
Candidates = _collisionCandidates.AsParallelWriter(),
|
|
||||||
HasEnemyTargets = hasEnemyTargets,
|
|
||||||
HasPlayerTarget = hasPlayerTarget,
|
|
||||||
PlayerTargetEntityId = playerTargetEntityId,
|
|
||||||
PlayerPosition = playerPosition,
|
|
||||||
CellSize = cellSize
|
|
||||||
};
|
|
||||||
|
|
||||||
handle = job.Schedule(queryCount, 64);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CountCollisionCandidatesBySourceType(out int projectileCandidateCount, out int areaCandidateCount)
|
private void CountCollisionCandidatesBySourceType(out int projectileCandidateCount, out int areaCandidateCount)
|
||||||
{
|
{
|
||||||
projectileCandidateCount = 0;
|
projectileCandidateCount = 0;
|
||||||
@@ -205,11 +136,11 @@ namespace SepCore.Simulation
|
|||||||
for (int i = 0; i < _collisionCandidates.Length; i++)
|
for (int i = 0; i < _collisionCandidates.Length; i++)
|
||||||
{
|
{
|
||||||
CollisionCandidateData candidate = _collisionCandidates[i];
|
CollisionCandidateData candidate = _collisionCandidates[i];
|
||||||
if (candidate.SourceType == CollisionSourceTypeProjectile)
|
if (candidate.SourceType == SimulationCollision.SourceTypeProjectile)
|
||||||
{
|
{
|
||||||
projectileCandidateCount++;
|
projectileCandidateCount++;
|
||||||
}
|
}
|
||||||
else if (candidate.SourceType == CollisionSourceTypeArea)
|
else if (candidate.SourceType == SimulationCollision.SourceTypeArea)
|
||||||
{
|
{
|
||||||
areaCandidateCount++;
|
areaCandidateCount++;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -11,14 +11,14 @@ namespace SepCore.Simulation
|
|||||||
float radius, int maxTargets = 16)
|
float radius, int maxTargets = 16)
|
||||||
{
|
{
|
||||||
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
||||||
maxTargets, CollisionShapeCircle, Vector3.forward, 180f, 0f, 0f);
|
maxTargets, SimulationCollision.ShapeCircle, Vector3.forward, 180f, 0f, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRequestSectorCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
public bool TryRequestSectorCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
||||||
float radius, in Vector3 direction, float halfAngleDeg, int maxTargets = 16)
|
float radius, in Vector3 direction, float halfAngleDeg, int maxTargets = 16)
|
||||||
{
|
{
|
||||||
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
||||||
maxTargets, CollisionShapeSector, direction, halfAngleDeg, 0f, 0f);
|
maxTargets, SimulationCollision.ShapeSector, direction, halfAngleDeg, 0f, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRequestRectangleCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
public bool TryRequestRectangleCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
||||||
@@ -28,7 +28,7 @@ namespace SepCore.Simulation
|
|||||||
float safeHalfLength = Mathf.Max(0.01f, halfLength);
|
float safeHalfLength = Mathf.Max(0.01f, halfLength);
|
||||||
float boundingRadius = Mathf.Sqrt(safeHalfWidth * safeHalfWidth + safeHalfLength * safeHalfLength);
|
float boundingRadius = Mathf.Sqrt(safeHalfWidth * safeHalfWidth + safeHalfLength * safeHalfLength);
|
||||||
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, boundingRadius,
|
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, boundingRadius,
|
||||||
maxTargets, CollisionShapeRectangle, direction, 0f, safeHalfWidth, safeHalfLength);
|
maxTargets, SimulationCollision.ShapeRectangle, direction, 0f, safeHalfWidth, safeHalfLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryRequestAreaCollisionInternal(int sourceEntityId, int sourceOwnerEntityId,
|
private bool TryRequestAreaCollisionInternal(int sourceEntityId, int sourceOwnerEntityId,
|
||||||
|
|||||||
+22
-34
@@ -36,7 +36,7 @@ namespace SepCore.Simulation
|
|||||||
for (int i = 0; i < _collisionCandidates.Length; i++)
|
for (int i = 0; i < _collisionCandidates.Length; i++)
|
||||||
{
|
{
|
||||||
CollisionCandidateData candidate = _collisionCandidates[i];
|
CollisionCandidateData candidate = _collisionCandidates[i];
|
||||||
if (candidate.SourceType == CollisionSourceTypeProjectile)
|
if (candidate.SourceType == SimulationCollision.SourceTypeProjectile)
|
||||||
{
|
{
|
||||||
int projectileEntityId = candidate.SourceEntityId;
|
int projectileEntityId = candidate.SourceEntityId;
|
||||||
if (_projectileResolvedEntityIds.Contains(projectileEntityId))
|
if (_projectileResolvedEntityIds.Contains(projectileEntityId))
|
||||||
@@ -44,7 +44,7 @@ namespace SepCore.Simulation
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryGetActiveProjectileSimData(projectileEntityId, out _, out ProjectileSimData projectile))
|
if (!TryGetActiveProjectileIndex(projectileEntityId, out int projectileIndex))
|
||||||
{
|
{
|
||||||
_projectileResolvedEntityIds.Add(projectileEntityId);
|
_projectileResolvedEntityIds.Add(projectileEntityId);
|
||||||
continue;
|
continue;
|
||||||
@@ -53,14 +53,15 @@ namespace SepCore.Simulation
|
|||||||
bool shouldExpireProjectile = true;
|
bool shouldExpireProjectile = true;
|
||||||
bool shouldDispatchPresentation = false;
|
bool shouldDispatchPresentation = false;
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
Vector3 hitPosition = projectile.Position;
|
Vector3 projectilePosition = ToVector3(_projectiles.Transform[projectileIndex].Position);
|
||||||
|
Vector3 hitPosition = projectilePosition;
|
||||||
if (TryGetAliveTargetableEntity(candidate.TargetEntityId, out TargetableObject target))
|
if (TryGetAliveTargetableEntity(candidate.TargetEntityId, out TargetableObject target))
|
||||||
{
|
{
|
||||||
EntityBase sourceEntity = TryGetEntityById(candidate.SourceEntityId);
|
EntityBase sourceEntity = TryGetEntityById(candidate.SourceEntityId);
|
||||||
EntityBase ownerEntity = TryGetEntityById(candidate.SourceOwnerEntityId);
|
EntityBase ownerEntity = TryGetEntityById(candidate.SourceOwnerEntityId);
|
||||||
shouldExpireProjectile = ResolveProjectileHitAgainstTarget(target, sourceEntity,
|
shouldExpireProjectile = ResolveProjectileHitAgainstTarget(target, sourceEntity,
|
||||||
ownerEntity,
|
ownerEntity,
|
||||||
in projectile,
|
in projectilePosition,
|
||||||
out damage, out hitPosition, out shouldDispatchPresentation);
|
out damage, out hitPosition, out shouldDispatchPresentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ namespace SepCore.Simulation
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candidate.SourceType == CollisionSourceTypeArea)
|
if (candidate.SourceType == SimulationCollision.SourceTypeArea)
|
||||||
{
|
{
|
||||||
long dedupKey = (((long)candidate.QueryId) << 32) ^ (uint)candidate.TargetEntityId;
|
long dedupKey = (((long)candidate.QueryId) << 32) ^ (uint)candidate.TargetEntityId;
|
||||||
if (!_areaCollisionHitDedupKeys.Add(dedupKey))
|
if (!_areaCollisionHitDedupKeys.Add(dedupKey))
|
||||||
@@ -112,11 +113,11 @@ namespace SepCore.Simulation
|
|||||||
|
|
||||||
private bool ResolveProjectileHitAgainstTarget(TargetableObject target, EntityBase sourceEntity,
|
private bool ResolveProjectileHitAgainstTarget(TargetableObject target, EntityBase sourceEntity,
|
||||||
EntityBase ownerEntity,
|
EntityBase ownerEntity,
|
||||||
in ProjectileSimData projectile, out int damage, out Vector3 hitPosition,
|
in Vector3 projectilePosition, out int damage, out Vector3 hitPosition,
|
||||||
out bool shouldDispatchPresentation)
|
out bool shouldDispatchPresentation)
|
||||||
{
|
{
|
||||||
damage = 0;
|
damage = 0;
|
||||||
hitPosition = projectile.Position;
|
hitPosition = projectilePosition;
|
||||||
shouldDispatchPresentation = false;
|
shouldDispatchPresentation = false;
|
||||||
|
|
||||||
if (target == null || !target.Available || target.IsDead)
|
if (target == null || !target.Available || target.IsDead)
|
||||||
@@ -167,7 +168,7 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
AreaCollisionHitEventData hitEvent = _areaCollisionHitEvents[i];
|
AreaCollisionHitEventData hitEvent = _areaCollisionHitEvents[i];
|
||||||
if (!TryGetCollisionQueryByQueryId(hitEvent.QueryId, out CollisionQueryData query) ||
|
if (!TryGetCollisionQueryByQueryId(hitEvent.QueryId, out CollisionQueryData query) ||
|
||||||
query.SourceType != CollisionSourceTypeArea)
|
query.SourceType != SimulationCollision.SourceTypeArea)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -238,9 +239,9 @@ namespace SepCore.Simulation
|
|||||||
return 0f;
|
return 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target is EnemyBase && TryGetEnemyData(target.Id, out EnemySimData enemyData))
|
if (target is EnemyBase && _enemies.TryGetSimulationIndex(target.Id, out int enemyIndex))
|
||||||
{
|
{
|
||||||
return Mathf.Max(0f, enemyData.EnemyBodyRadius);
|
return Mathf.Max(0f, _enemies.MoveConfig[enemyIndex].BodyRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
MovementComponent movementComponent = target.GetComponent<MovementComponent>();
|
MovementComponent movementComponent = target.GetComponent<MovementComponent>();
|
||||||
@@ -267,7 +268,7 @@ namespace SepCore.Simulation
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ShapeType == CollisionShapeRectangle)
|
if (query.ShapeType == SimulationCollision.ShapeRectangle)
|
||||||
{
|
{
|
||||||
Vector3 forwardRect = new Vector3(query.Direction.x, query.Direction.y, query.Direction.z);
|
Vector3 forwardRect = new Vector3(query.Direction.x, query.Direction.y, query.Direction.z);
|
||||||
forwardRect.y = 0f;
|
forwardRect.y = 0f;
|
||||||
@@ -288,7 +289,7 @@ namespace SepCore.Simulation
|
|||||||
return Mathf.Abs(forwardDistance) <= halfLength && Mathf.Abs(lateralDistance) <= halfWidth;
|
return Mathf.Abs(forwardDistance) <= halfLength && Mathf.Abs(lateralDistance) <= halfWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ShapeType != CollisionShapeSector)
|
if (query.ShapeType != SimulationCollision.ShapeSector)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -314,47 +315,34 @@ namespace SepCore.Simulation
|
|||||||
return angle <= halfAngle;
|
return angle <= halfAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetActiveProjectileSimData(int projectileEntityId, out int simulationIndex,
|
private bool TryGetActiveProjectileIndex(int projectileEntityId, out int simulationIndex)
|
||||||
out ProjectileSimData projectile)
|
|
||||||
{
|
{
|
||||||
simulationIndex = -1;
|
simulationIndex = -1;
|
||||||
projectile = default;
|
if (!_projectiles.TryGetSimulationIndex(projectileEntityId, out int foundIndex) ||
|
||||||
|
!_projectiles.Life[foundIndex].Active)
|
||||||
if (!ProjectileBinding.TryGetSimulationIndex(projectileEntityId, out int foundIndex) || foundIndex < 0 ||
|
|
||||||
foundIndex >= _projectiles.Count)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectileSimData data = _projectiles[foundIndex];
|
|
||||||
if (!data.Active || data.State != ProjectileStateActive)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
simulationIndex = foundIndex;
|
simulationIndex = foundIndex;
|
||||||
projectile = data;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MarkProjectileAsExpired(int projectileEntityId)
|
private bool MarkProjectileAsExpired(int projectileEntityId)
|
||||||
{
|
{
|
||||||
if (!ProjectileBinding.TryGetSimulationIndex(projectileEntityId, out int simulationIndex) ||
|
if (!_projectiles.TryGetSimulationIndex(projectileEntityId, out int simulationIndex))
|
||||||
simulationIndex < 0 || simulationIndex >= _projectiles.Count)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectileSimData projectile = _projectiles[simulationIndex];
|
ProjectileLifeSim life = _projectiles.Life[simulationIndex];
|
||||||
projectile.Active = false;
|
life.Active = false;
|
||||||
projectile.State = ProjectileStateExpired;
|
if (life.LifeTime > 0f && life.Age < life.LifeTime)
|
||||||
projectile.RemainingLifetime = 0f;
|
|
||||||
if (projectile.LifeTime > 0f && projectile.Age < projectile.LifeTime)
|
|
||||||
{
|
{
|
||||||
projectile.Age = projectile.LifeTime;
|
life.Age = life.LifeTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
_projectiles[simulationIndex] = projectile;
|
_projectiles.Life[simulationIndex] = life;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+180
-134
@@ -1,5 +1,5 @@
|
|||||||
using SepCore.Debugger;
|
using SepCore.Debugger;
|
||||||
using Unity.Collections;
|
using SepCore.Entity;
|
||||||
using Unity.Jobs;
|
using Unity.Jobs;
|
||||||
using Unity.Mathematics;
|
using Unity.Mathematics;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -8,7 +8,36 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
public sealed partial class SimulationWorld
|
public sealed partial class SimulationWorld
|
||||||
{
|
{
|
||||||
// Orchestrates per-tick simulation pipeline and enemy movement/separation jobs.
|
// Orchestrates the per-tick simulation pipeline over the component systems.
|
||||||
|
private float _playerAbsorbRange = -1f;
|
||||||
|
private float _playerAbsorbSpeed = -1f;
|
||||||
|
private float _playerCollectDistance = -1f;
|
||||||
|
|
||||||
|
public void SyncPlayerAbsorbSettings(float absorbRange, float absorbSpeed, float collectDistance)
|
||||||
|
{
|
||||||
|
_playerAbsorbRange = absorbRange;
|
||||||
|
_playerAbsorbSpeed = absorbSpeed;
|
||||||
|
_playerCollectDistance = collectDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float ResolveAbsorbRange()
|
||||||
|
{
|
||||||
|
if (_playerAbsorbRange >= 0f) return _playerAbsorbRange;
|
||||||
|
return GameEntry.Luban != null ? GameEntry.Luban.Global.DefaultAbsorbRange / 1000f : 2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float ResolveAbsorbSpeed()
|
||||||
|
{
|
||||||
|
if (_playerAbsorbSpeed >= 0f) return _playerAbsorbSpeed;
|
||||||
|
return GameEntry.Luban != null ? GameEntry.Luban.Global.AbsorbSpeed / 1000f : 8f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float ResolveCollectDistance()
|
||||||
|
{
|
||||||
|
if (_playerCollectDistance >= 0f) return _playerCollectDistance;
|
||||||
|
return GameEntry.Luban != null ? GameEntry.Luban.Global.CollectDistance / 1000f : 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
[Header("敌人互斥参数")]
|
[Header("敌人互斥参数")]
|
||||||
[Tooltip("敌人互斥分桶使用的网格尺寸。小于等于 0 时,将根据敌人体积半径自动计算。")]
|
[Tooltip("敌人互斥分桶使用的网格尺寸。小于等于 0 时,将根据敌人体积半径自动计算。")]
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
@@ -46,32 +75,31 @@ namespace SepCore.Simulation
|
|||||||
JobHandle enemySeparationHandle = default;
|
JobHandle enemySeparationHandle = default;
|
||||||
bool hasEnemySeparationJob = false;
|
bool hasEnemySeparationJob = false;
|
||||||
bool hasEnemySeparationCandidates = false;
|
bool hasEnemySeparationCandidates = false;
|
||||||
int enemySeparationCount = 0;
|
bool hasMovingEnemy = false;
|
||||||
|
int enemyCount = 0;
|
||||||
float enemySeparationMaxRadius = 0.45f;
|
float enemySeparationMaxRadius = 0.45f;
|
||||||
|
|
||||||
// 2. BuildInput 阶段:
|
// 2. BuildInput 阶段:
|
||||||
// 把 _enemies/_projectiles 同步到 Native 输入,准备输出缓冲;
|
// 扫描敌人组件统计是否需要分离系统以及碰撞查询容量。
|
||||||
// 统计是否需要敌人分离 Job;
|
|
||||||
// 预估并准备碰撞查询/候选缓冲。
|
|
||||||
using (CustomProfilerMarker.TickEnemies_BuildInput.Auto())
|
using (CustomProfilerMarker.TickEnemies_BuildInput.Auto())
|
||||||
{
|
{
|
||||||
SyncSimulationStateToJobInputs();
|
enemyCount = _enemies.Count;
|
||||||
int enemyCount = _enemyJobInputs.Length;
|
|
||||||
int projectileCount = _projectileJobInputs.Length;
|
|
||||||
PrepareEnemyJobOutputBuffer(enemyCount);
|
|
||||||
PrepareProjectileJobOutputBuffer(projectileCount);
|
|
||||||
|
|
||||||
enemySeparationCount = enemyCount;
|
|
||||||
for (int i = 0; i < enemyCount; i++)
|
for (int i = 0; i < enemyCount; i++)
|
||||||
{
|
{
|
||||||
EnemyJobInputData input = _enemyJobInputs[i];
|
if (_enemies.MoveInput[i].Speed > 0f)
|
||||||
if (!input.AvoidEnemyOverlap)
|
{
|
||||||
|
hasMovingEnemy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyMoveConfigSim config = _enemies.MoveConfig[i];
|
||||||
|
if (!config.AvoidOverlap)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasEnemySeparationCandidates = true;
|
hasEnemySeparationCandidates = true;
|
||||||
float radius = input.EnemyBodyRadius > 0f ? input.EnemyBodyRadius : 0.45f;
|
float radius = config.BodyRadius > 0f ? config.BodyRadius : 0.45f;
|
||||||
if (radius > enemySeparationMaxRadius)
|
if (radius > enemySeparationMaxRadius)
|
||||||
{
|
{
|
||||||
enemySeparationMaxRadius = radius;
|
enemySeparationMaxRadius = radius;
|
||||||
@@ -90,34 +118,53 @@ namespace SepCore.Simulation
|
|||||||
int projectileExpectedCount = projectileQueryCount * Mathf.Max(1, _projectileMaxCandidatesPerQuery);
|
int projectileExpectedCount = projectileQueryCount * Mathf.Max(1, _projectileMaxCandidatesPerQuery);
|
||||||
int areaExpectedCount = EstimatePendingAreaCollisionCandidateCountFromRequests();
|
int areaExpectedCount = EstimatePendingAreaCollisionCandidateCountFromRequests();
|
||||||
int expectedCandidateCount = Mathf.Max(16, projectileExpectedCount + areaExpectedCount);
|
int expectedCandidateCount = Mathf.Max(16, projectileExpectedCount + areaExpectedCount);
|
||||||
int bucketCapacity = Mathf.Max(256, _enemies.Count * 2 + queryCount);
|
int bucketCapacity = Mathf.Max(256, enemyCount * 2 + queryCount);
|
||||||
PrepareCollisionQueryAndCandidateChannels(queryCount, expectedCandidateCount, bucketCapacity);
|
PrepareCollisionQueryAndCandidateChannels(queryCount, expectedCandidateCount, bucketCapacity);
|
||||||
|
|
||||||
|
int pickupCount = _pickups.Count;
|
||||||
|
if (pickupCount > 0)
|
||||||
|
{
|
||||||
|
EnsureCapacity(ref _collectedPickups, pickupCount);
|
||||||
|
_collectedPickups.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. StateUpdate 阶段(调度两个移动 Job)
|
// 3. StateUpdate 阶段:敌人/投射物/掉落物移动系统
|
||||||
|
JobHandle pickupMovementHandle = default;
|
||||||
using (CustomProfilerMarker.TickEnemies_StateUpdate.Auto())
|
using (CustomProfilerMarker.TickEnemies_StateUpdate.Auto())
|
||||||
{
|
{
|
||||||
enemyMovementHandle = ExecuteEnemyMovementJob(in context);
|
float3 planarPlayerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
||||||
projectileMovementHandle = ExecuteProjectileMovementJob(in context);
|
float3 playerPosition = new float3(context.PlayerPosition.x, context.PlayerPosition.y,
|
||||||
|
context.PlayerPosition.z);
|
||||||
|
enemyMovementHandle = EnemyMovementSystem.Schedule(_enemies, context.DeltaTime, planarPlayerPosition,
|
||||||
|
default);
|
||||||
|
projectileMovementHandle = ProjectileMovementSystem.Schedule(_projectiles, context.DeltaTime,
|
||||||
|
playerPosition, _projectileMaxDistanceFromPlayer, _projectileMaxVerticalOffsetFromPlayer, default);
|
||||||
|
|
||||||
|
if (_pickups.Count > 0)
|
||||||
|
{
|
||||||
|
float absorbRange = ResolveAbsorbRange();
|
||||||
|
float absorbSpeed = ResolveAbsorbSpeed();
|
||||||
|
float collectDistance = ResolveCollectDistance();
|
||||||
|
pickupMovementHandle = PickupMovementSystem.Schedule(_pickups, _collectedPickups, context.DeltaTime,
|
||||||
|
planarPlayerPosition, absorbRange, absorbSpeed, collectDistance, default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Schedule 阶段(可选分离 Job)
|
// 4. Schedule 阶段:可选敌人分离系统
|
||||||
// 如果有需要分离的敌人,调度:
|
|
||||||
// BuildEnemySeparationBucketsBurstJob(依赖 EnemyMovement)
|
|
||||||
// EnemySeparationBurstJob(依赖分桶 Job)
|
|
||||||
// 然后把“敌人链路 handle”和“投射物移动 handle”合并。
|
|
||||||
JobHandle simulationHandle;
|
JobHandle simulationHandle;
|
||||||
using (CustomProfilerMarker.TickEnemies_Schedule.Auto())
|
using (CustomProfilerMarker.TickEnemies_Schedule.Auto())
|
||||||
{
|
{
|
||||||
hasEnemySeparationJob = TryScheduleEnemySeparationFromJobOutput(
|
hasEnemySeparationJob = TryScheduleEnemySeparationJob(
|
||||||
in context,
|
in context,
|
||||||
enemyMovementHandle,
|
enemyMovementHandle,
|
||||||
hasEnemySeparationCandidates,
|
hasEnemySeparationCandidates,
|
||||||
enemySeparationCount,
|
enemyCount,
|
||||||
enemySeparationMaxRadius,
|
enemySeparationMaxRadius,
|
||||||
out enemySeparationHandle);
|
out enemySeparationHandle);
|
||||||
JobHandle enemyHandle = hasEnemySeparationJob ? enemySeparationHandle : enemyMovementHandle;
|
JobHandle enemyHandle = hasEnemySeparationJob ? enemySeparationHandle : enemyMovementHandle;
|
||||||
simulationHandle = JobHandle.CombineDependencies(enemyHandle, projectileMovementHandle);
|
JobHandle movementHandle = JobHandle.CombineDependencies(enemyHandle, projectileMovementHandle);
|
||||||
|
simulationHandle = JobHandle.CombineDependencies(movementHandle, pickupMovementHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Complete 阶段:等待上述 Job 全部完成。
|
// 5. Complete 阶段:等待上述 Job 全部完成。
|
||||||
@@ -126,14 +173,19 @@ namespace SepCore.Simulation
|
|||||||
simulationHandle.Complete();
|
simulationHandle.Complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 主线程后处理阶段:
|
// 6. 主线程提交阶段:
|
||||||
// - 把分离结果覆盖回敌人输出(如果有分离 Job)
|
// - 分离结果写回敌人 Transform 组件
|
||||||
// - 构建碰撞候选(投射物查询 + area 请求查询 + 网格筛选)
|
// - 有位移时标记目标选择索引失效
|
||||||
using (CustomProfilerMarker.TickEnemies_MainThreadCommit.Auto())
|
using (CustomProfilerMarker.TickEnemies_MainThreadCommit.Auto())
|
||||||
{
|
{
|
||||||
if (hasEnemySeparationJob)
|
if (hasEnemySeparationJob)
|
||||||
{
|
{
|
||||||
CommitEnemySeparationFromJobOutput(enemySeparationCount);
|
EnemySeparationSystem.Commit(_enemies, _enemySeparationTransforms, enemyCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMovingEnemy || hasEnemySeparationJob)
|
||||||
|
{
|
||||||
|
MarkEnemyTargetSpatialIndexDirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,137 +196,131 @@ namespace SepCore.Simulation
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 7. MainThreadCommit 阶段
|
// 7. MainThreadCommit 阶段
|
||||||
// - ApplyJobOutputsToSimulationState(写回 _enemies/_projectiles)
|
|
||||||
// - ResolveCollisionCandidatesOnMainThread(命中结算、事件、范围碰撞)
|
|
||||||
// - RecycleInactiveAndExpiredProjectiles(隐藏并移除失效投射物)
|
|
||||||
using (CustomProfilerMarker.TickEnemies_WriteBack.Auto())
|
using (CustomProfilerMarker.TickEnemies_WriteBack.Auto())
|
||||||
{
|
{
|
||||||
using (CustomProfilerMarker.TickEnemies_MainThreadCommit.Auto())
|
using (CustomProfilerMarker.TickEnemies_MainThreadCommit.Auto())
|
||||||
{
|
{
|
||||||
ApplyJobOutputsToSimulationState();
|
|
||||||
ResolveCollisionCandidatesOnMainThread();
|
ResolveCollisionCandidatesOnMainThread();
|
||||||
RecycleInactiveAndExpiredProjectiles();
|
RecycleInactiveAndExpiredProjectiles();
|
||||||
|
ResolveCollectedPickupsOnMainThread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private JobHandle ExecuteEnemyMovementJob(in SimulationTickContext context)
|
private void ResolveCollectedPickupsOnMainThread()
|
||||||
{
|
{
|
||||||
int enemyCount = _enemyJobInputs.Length;
|
if (!_collectedPickups.IsCreated || _collectedPickups.Length == 0)
|
||||||
if (enemyCount == 0)
|
|
||||||
{
|
{
|
||||||
return default;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.DeltaTime <= 0f)
|
Player player = TryGetPlayer();
|
||||||
|
for (int i = 0; i < _collectedPickups.Length; i++)
|
||||||
{
|
{
|
||||||
CopyEnemyInputsToOutputs();
|
int entityId = _collectedPickups[i];
|
||||||
return default;
|
EntityBase entity = TryGetEntityById(entityId);
|
||||||
}
|
if (entity == null || !entity.Available)
|
||||||
|
|
||||||
float3 playerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
|
||||||
NativeArray<EnemyJobInputData> inputArray = _enemyJobInputs.AsArray();
|
|
||||||
NativeArray<EnemyJobOutputData> outputArray = _enemyJobOutputs.AsArray();
|
|
||||||
|
|
||||||
EnemyMovementBurstJob burstJob = new EnemyMovementBurstJob
|
|
||||||
{
|
|
||||||
Inputs = inputArray,
|
|
||||||
Outputs = outputArray,
|
|
||||||
DeltaTime = context.DeltaTime,
|
|
||||||
PlayerPosition = playerPosition
|
|
||||||
};
|
|
||||||
return burstJob.Schedule(enemyCount, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CopyEnemyInputsToOutputs()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _enemyJobInputs.Length; i++)
|
|
||||||
{
|
|
||||||
EnemyJobInputData input = _enemyJobInputs[i];
|
|
||||||
_enemyJobOutputs[i] = new EnemyJobOutputData
|
|
||||||
{
|
{
|
||||||
EntityId = input.EntityId,
|
continue;
|
||||||
Position = input.Position,
|
}
|
||||||
Forward = input.Forward,
|
|
||||||
Rotation = input.Rotation,
|
if (entity is CoinEntity coin)
|
||||||
Speed = input.Speed,
|
{
|
||||||
AttackRange = input.AttackRange,
|
if (player != null)
|
||||||
AvoidEnemyOverlap = input.AvoidEnemyOverlap,
|
{
|
||||||
EnemyBodyRadius = input.EnemyBodyRadius,
|
coin.TryCollect(player);
|
||||||
SeparationIterations = input.SeparationIterations,
|
}
|
||||||
TargetType = input.TargetType,
|
else
|
||||||
State = input.State
|
{
|
||||||
};
|
var entityComponent = GameEntry.Entity;
|
||||||
|
if (entityComponent != null)
|
||||||
|
{
|
||||||
|
entityComponent.HideEntity(coin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (entity is ExpEntity exp)
|
||||||
|
{
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
exp.TryCollect(player);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var entityComponent = GameEntry.Entity;
|
||||||
|
if (entityComponent != null)
|
||||||
|
{
|
||||||
|
entityComponent.HideEntity(exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var entityComponent = GameEntry.Entity;
|
||||||
|
if (entityComponent != null)
|
||||||
|
{
|
||||||
|
entityComponent.HideEntity(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_collectedPickups.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryScheduleEnemySeparationFromJobOutput(in SimulationTickContext context, JobHandle dependency,
|
private Player TryGetPlayer()
|
||||||
|
{
|
||||||
|
if (_playerMovementTransform != null)
|
||||||
|
{
|
||||||
|
Player player = _playerMovementTransform.GetComponent<Player>();
|
||||||
|
if (player != null && player.Available)
|
||||||
|
{
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var entityComponent = GameEntry.Entity;
|
||||||
|
if (entityComponent != null && GameEntry.Luban != null)
|
||||||
|
{
|
||||||
|
var group = entityComponent.GetEntityGroup(GameEntry.Luban.Global.PlayerGroupName);
|
||||||
|
if (group != null && group.EntityCount > 0)
|
||||||
|
{
|
||||||
|
var entities = group.GetAllEntities();
|
||||||
|
for (int i = 0; i < entities.Length; i++)
|
||||||
|
{
|
||||||
|
if (entities[i] is UnityGameFramework.Runtime.Entity ugfEntity &&
|
||||||
|
ugfEntity.Logic is Player player && player.Available)
|
||||||
|
{
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryScheduleEnemySeparationJob(in SimulationTickContext context, JobHandle dependency,
|
||||||
bool hasSeparationCandidates, int enemyCount, float maxRadius, out JobHandle separationHandle)
|
bool hasSeparationCandidates, int enemyCount, float maxRadius, out JobHandle separationHandle)
|
||||||
{
|
{
|
||||||
separationHandle = dependency;
|
|
||||||
if (enemyCount <= 0 || !hasSeparationCandidates)
|
if (enemyCount <= 0 || !hasSeparationCandidates)
|
||||||
{
|
{
|
||||||
|
separationHandle = dependency;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float autoCellSize = maxRadius * 2f;
|
float autoCellSize = maxRadius * 2f;
|
||||||
float configuredCellSize = _enemySeparationCellSize > 0f ? _enemySeparationCellSize : autoCellSize;
|
float configuredCellSize = _enemySeparationCellSize > 0f ? _enemySeparationCellSize : autoCellSize;
|
||||||
float cellSize = Mathf.Max(0.1f, configuredCellSize);
|
EnemySeparationSettings settings = new EnemySeparationSettings(
|
||||||
|
Mathf.Max(0.1f, configuredCellSize),
|
||||||
|
maxRadius,
|
||||||
|
Mathf.Clamp(_enemySeparationPushDamping, 0f, 2f),
|
||||||
|
Mathf.Max(0.1f, _enemySeparationMaxStepScale),
|
||||||
|
_enemySeparationUseTangentialInAttackRange,
|
||||||
|
Mathf.Clamp01(_enemySeparationPushSmoothing));
|
||||||
float3 playerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
float3 playerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
||||||
float pushDamping = Mathf.Clamp(_enemySeparationPushDamping, 0f, 2f);
|
separationHandle = EnemySeparationSystem.Schedule(_enemies, _enemySeparationBuckets,
|
||||||
float maxStepScale = Mathf.Max(0.1f, _enemySeparationMaxStepScale);
|
_enemySeparationTransforms, in settings, playerPosition, dependency);
|
||||||
bool useTangentialInAttackRange = _enemySeparationUseTangentialInAttackRange;
|
|
||||||
float pushSmoothing = Mathf.Clamp01(_enemySeparationPushSmoothing);
|
|
||||||
|
|
||||||
NativeArray<EnemyJobOutputData> inputArray = _enemyJobOutputs.AsArray();
|
|
||||||
NativeArray<EnemyJobOutputData> separatedOutputArray = _enemyJobSeparationOutputs.AsArray();
|
|
||||||
NativeArray<float2> previousPushes = _enemySeparationPreviousPushes.AsArray();
|
|
||||||
NativeArray<float2> currentPushes = _enemySeparationCurrentPushes.AsArray();
|
|
||||||
|
|
||||||
|
|
||||||
BuildEnemySeparationBucketsBurstJob buildJob = new BuildEnemySeparationBucketsBurstJob
|
|
||||||
{
|
|
||||||
Inputs = inputArray,
|
|
||||||
Buckets = _enemySeparationBuckets.AsParallelWriter(),
|
|
||||||
CellSize = cellSize
|
|
||||||
};
|
|
||||||
JobHandle buildHandle = buildJob.Schedule(enemyCount, 64, dependency);
|
|
||||||
EnemySeparationBurstJob separationJob = new EnemySeparationBurstJob
|
|
||||||
{
|
|
||||||
Inputs = inputArray,
|
|
||||||
Buckets = _enemySeparationBuckets,
|
|
||||||
PreviousPushes = previousPushes,
|
|
||||||
Outputs = separatedOutputArray,
|
|
||||||
CurrentPushes = currentPushes,
|
|
||||||
CellSize = cellSize,
|
|
||||||
MaxRadius = maxRadius,
|
|
||||||
PlayerPosition = playerPosition,
|
|
||||||
PushDamping = pushDamping,
|
|
||||||
MaxStepScale = maxStepScale,
|
|
||||||
UseTangentialInAttackRange = useTangentialInAttackRange,
|
|
||||||
PushSmoothing = pushSmoothing
|
|
||||||
};
|
|
||||||
separationHandle = separationJob.Schedule(enemyCount, 64, buildHandle);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CommitEnemySeparationFromJobOutput(int enemyCount)
|
|
||||||
{
|
|
||||||
if (enemyCount <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CommitEnemySeparationTemporalBuffers(enemyCount);
|
|
||||||
for (int i = 0; i < enemyCount; i++)
|
|
||||||
{
|
|
||||||
_enemyJobOutputs[i] = _enemyJobSeparationOutputs[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long SeparationCellKey(int x, int z)
|
|
||||||
{
|
|
||||||
return ((long)x << 32) ^ (uint)z;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-61
@@ -1,7 +1,4 @@
|
|||||||
using Unity.Collections;
|
using UnityEngine;
|
||||||
using Unity.Jobs;
|
|
||||||
using Unity.Mathematics;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
{
|
{
|
||||||
@@ -16,45 +13,6 @@ namespace SepCore.Simulation
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
private float _projectileMaxVerticalOffsetFromPlayer = 30f;
|
private float _projectileMaxVerticalOffsetFromPlayer = 30f;
|
||||||
|
|
||||||
#region Projectile Movement Job
|
|
||||||
|
|
||||||
private JobHandle ExecuteProjectileMovementJob(in SimulationTickContext context)
|
|
||||||
{
|
|
||||||
int projectileCount = _projectileJobInputs.Length;
|
|
||||||
if (projectileCount == 0)
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.DeltaTime <= 0f)
|
|
||||||
{
|
|
||||||
CopyProjectileInputsToOutputs();
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
float maxDistance = Mathf.Max(0f, _projectileMaxDistanceFromPlayer);
|
|
||||||
float maxSqrDistanceFromPlayer = maxDistance > 0f ? maxDistance * maxDistance : -1f;
|
|
||||||
float maxVerticalOffsetFromPlayer = Mathf.Max(0f, _projectileMaxVerticalOffsetFromPlayer);
|
|
||||||
float3 playerPosition =
|
|
||||||
new float3(context.PlayerPosition.x, context.PlayerPosition.y, context.PlayerPosition.z);
|
|
||||||
NativeArray<ProjectileJobInputData> inputArray = _projectileJobInputs.AsArray();
|
|
||||||
NativeArray<ProjectileJobOutputData> outputArray = _projectileJobOutputs.AsArray();
|
|
||||||
|
|
||||||
|
|
||||||
ProjectileMovementBurstJob burstJob = new ProjectileMovementBurstJob
|
|
||||||
{
|
|
||||||
Inputs = inputArray,
|
|
||||||
Outputs = outputArray,
|
|
||||||
DeltaTime = context.DeltaTime,
|
|
||||||
PlayerPosition = playerPosition,
|
|
||||||
MaxSqrDistanceFromPlayer = maxSqrDistanceFromPlayer,
|
|
||||||
MaxVerticalOffsetFromPlayer = maxVerticalOffsetFromPlayer
|
|
||||||
};
|
|
||||||
return burstJob.Schedule(projectileCount, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Projectile Cleanup
|
#region Projectile Cleanup
|
||||||
|
|
||||||
private void RecycleInactiveAndExpiredProjectiles()
|
private void RecycleInactiveAndExpiredProjectiles()
|
||||||
@@ -62,13 +20,13 @@ namespace SepCore.Simulation
|
|||||||
_projectileRecycleEntityIds.Clear();
|
_projectileRecycleEntityIds.Clear();
|
||||||
for (int i = 0; i < _projectiles.Count; i++)
|
for (int i = 0; i < _projectiles.Count; i++)
|
||||||
{
|
{
|
||||||
ProjectileSimData projectile = _projectiles[i];
|
ProjectileLifeSim life = _projectiles.Life[i];
|
||||||
if (!ShouldRecycleProjectileSimData(projectile))
|
if (life.Active && (life.LifeTime <= 0f || life.Age < life.LifeTime))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_projectileRecycleEntityIds.Add(projectile.EntityId);
|
_projectileRecycleEntityIds.Add(_projectiles.Identity[i].EntityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_projectileRecycleEntityIds.Count == 0)
|
if (_projectileRecycleEntityIds.Count == 0)
|
||||||
@@ -91,21 +49,6 @@ namespace SepCore.Simulation
|
|||||||
_projectileRecycleEntityIds.Clear();
|
_projectileRecycleEntityIds.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldRecycleProjectileSimData(in ProjectileSimData projectile)
|
|
||||||
{
|
|
||||||
if (!projectile.Active)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projectile.State == ProjectileStateExpired)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return projectile.LifeTime > 0f && projectile.Age >= projectile.LifeTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-13
@@ -32,40 +32,53 @@ namespace SepCore.Simulation
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_world.TryGetEnemyData(enemyEntity.Id, out EnemySimData enemyData))
|
if (!_world._enemies.TryGetSimulationIndex(enemyEntity.Id, out int simulationIndex))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyEnemyPresentation(enemyEntity, enemyData);
|
EnemyTransformSim enemyData = _world._enemies.Transform[simulationIndex];
|
||||||
|
ApplyEnemyPresentation(enemyEntity, in enemyData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var projectiles = _world._projectiles;
|
var projectiles = _world._projectiles;
|
||||||
for (int i = 0; i < projectiles.Count; i++)
|
for (int i = 0; i < projectiles.Count; i++)
|
||||||
{
|
{
|
||||||
ProjectileSimData projectileData = projectiles[i];
|
if (!projectiles.Life[i].Active)
|
||||||
if (!projectileData.Active || projectileData.State != ProjectileStateActive)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityBase projectileEntity = TryGetEntityById(projectileData.EntityId);
|
EntityBase projectileEntity = TryGetEntityById(projectiles.Identity[i].EntityId);
|
||||||
if (projectileEntity == null || !projectileEntity.Available)
|
if (projectileEntity == null || !projectileEntity.Available)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyProjectilePresentation(projectileEntity, projectileData);
|
ProjectileTransformSim projectileData = projectiles.Transform[i];
|
||||||
|
ApplyProjectilePresentation(projectileEntity, in projectileData);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pickups = _world._pickups;
|
||||||
|
for (int i = 0; i < pickups.Count; i++)
|
||||||
|
{
|
||||||
|
EntityBase pickupEntity = TryGetEntityById(pickups.Identity[i].EntityId);
|
||||||
|
if (pickupEntity == null || !pickupEntity.Available)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pickupEntity.CachedTransform.position = ToVector3(pickups.Transform[i].Position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ApplyEnemyPresentation(EnemyBase enemyEntity, in EnemySimData enemyData)
|
private static void ApplyEnemyPresentation(EnemyBase enemyEntity, in EnemyTransformSim enemyData)
|
||||||
{
|
{
|
||||||
Transform enemyTransform = enemyEntity.CachedTransform;
|
Transform enemyTransform = enemyEntity.CachedTransform;
|
||||||
enemyTransform.position = enemyData.Position;
|
enemyTransform.position = ToVector3(enemyData.Position);
|
||||||
|
|
||||||
Quaternion rotation = enemyData.Rotation;
|
Quaternion rotation = ToQuaternion(enemyData.Rotation);
|
||||||
float rotationMagnitude = Mathf.Abs(rotation.x) + Mathf.Abs(rotation.y) + Mathf.Abs(rotation.z) +
|
float rotationMagnitude = Mathf.Abs(rotation.x) + Mathf.Abs(rotation.y) + Mathf.Abs(rotation.z) +
|
||||||
Mathf.Abs(rotation.w);
|
Mathf.Abs(rotation.w);
|
||||||
if (rotationMagnitude > float.Epsilon)
|
if (rotationMagnitude > float.Epsilon)
|
||||||
@@ -74,7 +87,7 @@ namespace SepCore.Simulation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 forward = enemyData.Forward;
|
Vector3 forward = ToVector3(enemyData.Forward);
|
||||||
forward.y = 0f;
|
forward.y = 0f;
|
||||||
if (forward.sqrMagnitude > float.Epsilon)
|
if (forward.sqrMagnitude > float.Epsilon)
|
||||||
{
|
{
|
||||||
@@ -83,7 +96,7 @@ namespace SepCore.Simulation
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void ApplyProjectilePresentation(EntityBase projectileEntity,
|
private static void ApplyProjectilePresentation(EntityBase projectileEntity,
|
||||||
in ProjectileSimData projectileData)
|
in ProjectileTransformSim projectileData)
|
||||||
{
|
{
|
||||||
Transform projectileTransform = projectileEntity.CachedTransform;
|
Transform projectileTransform = projectileEntity.CachedTransform;
|
||||||
if (projectileTransform == null)
|
if (projectileTransform == null)
|
||||||
@@ -91,8 +104,8 @@ namespace SepCore.Simulation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
projectileTransform.position = projectileData.Position;
|
projectileTransform.position = ToVector3(projectileData.Position);
|
||||||
Vector3 forward = projectileData.Forward;
|
Vector3 forward = ToVector3(projectileData.Forward);
|
||||||
if (forward.sqrMagnitude <= float.Epsilon)
|
if (forward.sqrMagnitude <= float.Epsilon)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ namespace SepCore.Simulation
|
|||||||
public bool AvoidEnemyOverlap;
|
public bool AvoidEnemyOverlap;
|
||||||
public float EnemyBodyRadius;
|
public float EnemyBodyRadius;
|
||||||
public int SeparationIterations;
|
public int SeparationIterations;
|
||||||
public int TargetType;
|
|
||||||
public int State;
|
public int State;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-3
@@ -9,11 +9,8 @@ namespace SepCore.Simulation
|
|||||||
public Vector3 Position;
|
public Vector3 Position;
|
||||||
public Vector3 Forward;
|
public Vector3 Forward;
|
||||||
public Vector3 Velocity;
|
public Vector3 Velocity;
|
||||||
public float Speed;
|
|
||||||
public float LifeTime;
|
public float LifeTime;
|
||||||
public float Age;
|
public float Age;
|
||||||
public bool Active;
|
public bool Active;
|
||||||
public float RemainingLifetime;
|
|
||||||
public int State;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal static class SimulationCell
|
||||||
|
{
|
||||||
|
public static long Key(int x, int z)
|
||||||
|
{
|
||||||
|
return ((long)x << 32) ^ (uint)z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ToCell(float value, float cellSize)
|
||||||
|
{
|
||||||
|
return (int)math.floor(value / cellSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2908180e0131e63428d1bfdaed65e1ce
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal static class SimulationCollision
|
||||||
|
{
|
||||||
|
public const int SourceTypeProjectile = 1;
|
||||||
|
public const int SourceTypeArea = 2;
|
||||||
|
public const int ShapeCircle = 0;
|
||||||
|
public const int ShapeSector = 1;
|
||||||
|
public const int ShapeRectangle = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c39f2a2274ea06347970351f25e70c28
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
public readonly struct SimulationStats
|
||||||
|
{
|
||||||
|
public SimulationStats(int collisionCandidateCount, int pendingAreaCollisionRequestCount,
|
||||||
|
int lastCollisionQueryCount, int lastProjectileCollisionQueryCount, int lastAreaCollisionQueryCount,
|
||||||
|
int lastCollisionCandidateCount, int lastProjectileCollisionCandidateCount,
|
||||||
|
int lastAreaCollisionCandidateCount, int lastResolvedAreaHitCount, float lastCollisionCellSize,
|
||||||
|
bool lastCollisionHasEnemyTargets)
|
||||||
|
{
|
||||||
|
CollisionCandidateCount = collisionCandidateCount;
|
||||||
|
PendingAreaCollisionRequestCount = pendingAreaCollisionRequestCount;
|
||||||
|
LastCollisionQueryCount = lastCollisionQueryCount;
|
||||||
|
LastProjectileCollisionQueryCount = lastProjectileCollisionQueryCount;
|
||||||
|
LastAreaCollisionQueryCount = lastAreaCollisionQueryCount;
|
||||||
|
LastCollisionCandidateCount = lastCollisionCandidateCount;
|
||||||
|
LastProjectileCollisionCandidateCount = lastProjectileCollisionCandidateCount;
|
||||||
|
LastAreaCollisionCandidateCount = lastAreaCollisionCandidateCount;
|
||||||
|
LastResolvedAreaHitCount = lastResolvedAreaHitCount;
|
||||||
|
LastCollisionCellSize = lastCollisionCellSize;
|
||||||
|
LastCollisionHasEnemyTargets = lastCollisionHasEnemyTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CollisionCandidateCount { get; }
|
||||||
|
public int PendingAreaCollisionRequestCount { get; }
|
||||||
|
public int LastCollisionQueryCount { get; }
|
||||||
|
public int LastProjectileCollisionQueryCount { get; }
|
||||||
|
public int LastAreaCollisionQueryCount { get; }
|
||||||
|
public int LastCollisionCandidateCount { get; }
|
||||||
|
public int LastProjectileCollisionCandidateCount { get; }
|
||||||
|
public int LastAreaCollisionCandidateCount { get; }
|
||||||
|
public int LastResolvedAreaHitCount { get; }
|
||||||
|
public float LastCollisionCellSize { get; }
|
||||||
|
public bool LastCollisionHasEnemyTargets { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 69cb78d487cd9f54d979790a9b24ee39
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -4,15 +4,13 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
public readonly struct SimulationTickContext
|
public readonly struct SimulationTickContext
|
||||||
{
|
{
|
||||||
public SimulationTickContext(float deltaTime, float realDeltaTime, Vector3 playerPosition)
|
public SimulationTickContext(float deltaTime, Vector3 playerPosition)
|
||||||
{
|
{
|
||||||
DeltaTime = deltaTime;
|
DeltaTime = deltaTime;
|
||||||
RealDeltaTime = realDeltaTime;
|
|
||||||
PlayerPosition = playerPosition;
|
PlayerPosition = playerPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float DeltaTime { get; }
|
public float DeltaTime { get; }
|
||||||
public float RealDeltaTime { get; }
|
|
||||||
public Vector3 PlayerPosition { get; }
|
public Vector3 PlayerPosition { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-9
@@ -1,5 +1,6 @@
|
|||||||
using SepCore.Components;
|
using SepCore.Components;
|
||||||
using SepCore.Entity;
|
using SepCore.Entity;
|
||||||
|
using Unity.Mathematics;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
@@ -25,7 +26,7 @@ namespace SepCore.Simulation
|
|||||||
|
|
||||||
float attackRange = enemy.AttackRange > 0f
|
float attackRange = enemy.AttackRange > 0f
|
||||||
? enemy.AttackRange
|
? enemy.AttackRange
|
||||||
: DefaultAttackRange;
|
: EnemyAttackSim.DefaultAttackRange;
|
||||||
|
|
||||||
return new EnemySimData
|
return new EnemySimData
|
||||||
{
|
{
|
||||||
@@ -38,8 +39,7 @@ namespace SepCore.Simulation
|
|||||||
AvoidEnemyOverlap = movementComponent != null && movementComponent.AvoidEnemyOverlap,
|
AvoidEnemyOverlap = movementComponent != null && movementComponent.AvoidEnemyOverlap,
|
||||||
EnemyBodyRadius = movementComponent != null ? movementComponent.EnemyBodyRadius : 0.45f,
|
EnemyBodyRadius = movementComponent != null ? movementComponent.EnemyBodyRadius : 0.45f,
|
||||||
SeparationIterations = movementComponent != null ? movementComponent.SeparationIterations : 2,
|
SeparationIterations = movementComponent != null ? movementComponent.SeparationIterations : 2,
|
||||||
TargetType = 0,
|
State = EnemyBehaviorSim.StateIdle
|
||||||
State = EnemyStateIdle
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,12 +83,9 @@ namespace SepCore.Simulation
|
|||||||
Position = projectileEntity.CachedTransform.position,
|
Position = projectileEntity.CachedTransform.position,
|
||||||
Forward = forward,
|
Forward = forward,
|
||||||
Velocity = velocity,
|
Velocity = velocity,
|
||||||
Speed = speed,
|
|
||||||
LifeTime = lifeTime,
|
LifeTime = lifeTime,
|
||||||
Age = 0f,
|
Age = 0f,
|
||||||
Active = true,
|
Active = true
|
||||||
RemainingLifetime = lifeTime,
|
|
||||||
State = ProjectileStateActive
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,11 +95,34 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
EntityId = pickupEntity.Id,
|
EntityId = pickupEntity.Id,
|
||||||
Position = pickupEntity.CachedTransform.position,
|
Position = pickupEntity.CachedTransform.position,
|
||||||
PickupRadius = 0.35f,
|
PickupRadius = 0.35f
|
||||||
State = 0
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Component Conversion
|
||||||
|
|
||||||
|
private static float3 ToFloat3(Vector3 value)
|
||||||
|
{
|
||||||
|
return new float3(value.x, value.y, value.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 ToVector3(float3 value)
|
||||||
|
{
|
||||||
|
return new Vector3(value.x, value.y, value.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static quaternion ToQuaternion(Quaternion value)
|
||||||
|
{
|
||||||
|
return new quaternion(value.x, value.y, value.z, value.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Quaternion ToQuaternion(quaternion value)
|
||||||
|
{
|
||||||
|
return new Quaternion(value.value.x, value.value.y, value.value.z, value.value.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -50,21 +50,21 @@ namespace SepCore.Simulation
|
|||||||
ClearPlayerMovementState();
|
ClearPlayerMovementState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector3 ResolvePlayerPositionForTick(in SimulationTickContext context)
|
private Vector3 ResolvePlayerPositionForTick(Vector3 fallbackPlayerPosition, float deltaTime)
|
||||||
{
|
{
|
||||||
if (_playerMovementTransform == null)
|
if (_playerMovementTransform == null)
|
||||||
{
|
{
|
||||||
return context.PlayerPosition;
|
return fallbackPlayerPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_playerMovementActive || _playerMovementSpeed <= 0f ||
|
if (!_playerMovementActive || _playerMovementSpeed <= 0f ||
|
||||||
_playerMovementDirection.sqrMagnitude <= Mathf.Epsilon || context.DeltaTime <= 0f)
|
_playerMovementDirection.sqrMagnitude <= Mathf.Epsilon || deltaTime <= 0f)
|
||||||
{
|
{
|
||||||
_playerMovementPosition = _playerMovementTransform.position;
|
_playerMovementPosition = _playerMovementTransform.position;
|
||||||
return _playerMovementPosition;
|
return _playerMovementPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
_playerMovementPosition += _playerMovementDirection * (_playerMovementSpeed * context.DeltaTime);
|
_playerMovementPosition += _playerMovementDirection * (_playerMovementSpeed * deltaTime);
|
||||||
_playerMovementPosition.y = _playerMovementTransform.position.y;
|
_playerMovementPosition.y = _playerMovementTransform.position.y;
|
||||||
_playerMovementTransform.position = _playerMovementPosition;
|
_playerMovementTransform.position = _playerMovementPosition;
|
||||||
return _playerMovementPosition;
|
return _playerMovementPosition;
|
||||||
|
|||||||
+10
-28
@@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Unity.Jobs;
|
using Unity.Jobs;
|
||||||
using Unity.Mathematics;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
@@ -17,23 +16,14 @@ namespace SepCore.Simulation
|
|||||||
private readonly CollisionPipelineRuntimeState _collisionPipelineRuntime = new();
|
private readonly CollisionPipelineRuntimeState _collisionPipelineRuntime = new();
|
||||||
private readonly TargetSelectionRuntimeState _targetSelectionRuntime = new();
|
private readonly TargetSelectionRuntimeState _targetSelectionRuntime = new();
|
||||||
|
|
||||||
private List<EnemySimData> _enemies => _simulationState.Enemies;
|
private EnemyStore _enemies => _simulationState.Enemies;
|
||||||
private List<ProjectileSimData> _projectiles => _simulationState.Projectiles;
|
private ProjectileStore _projectiles => _simulationState.Projectiles;
|
||||||
private List<PickupSimData> _pickups => _simulationState.Pickups;
|
private PickupStore _pickups => _simulationState.Pickups;
|
||||||
private List<int> _projectileRecycleEntityIds => _simulationState.ProjectileRecycleEntityIds;
|
private List<int> _projectileRecycleEntityIds => _simulationState.ProjectileRecycleEntityIds;
|
||||||
private HashSet<int> _projectileResolvedEntityIds => _collisionPipelineRuntime.ProjectileResolvedEntityIds;
|
private HashSet<int> _projectileResolvedEntityIds => _collisionPipelineRuntime.ProjectileResolvedEntityIds;
|
||||||
|
|
||||||
private EntityBinding EnemyBinding => _simulationState.EnemyBinding;
|
private ref NativeList<EnemyTransformSim> _enemySeparationTransforms => ref _jobDataRuntime.EnemySeparationTransforms;
|
||||||
private EntityBinding ProjectileBinding => _simulationState.ProjectileBinding;
|
private ref NativeList<int> _collectedPickups => ref _jobDataRuntime.CollectedPickups;
|
||||||
private EntityBinding PickupBinding => _simulationState.PickupBinding;
|
|
||||||
|
|
||||||
private ref NativeList<EnemyJobInputData> _enemyJobInputs => ref _jobDataRuntime.EnemyJobInputs;
|
|
||||||
private ref NativeList<EnemyJobOutputData> _enemyJobOutputs => ref _jobDataRuntime.EnemyJobOutputs;
|
|
||||||
private ref NativeList<EnemyJobOutputData> _enemyJobSeparationOutputs => ref _jobDataRuntime.EnemyJobSeparationOutputs;
|
|
||||||
private ref NativeList<float2> _enemySeparationPreviousPushes => ref _jobDataRuntime.EnemySeparationPreviousPushes;
|
|
||||||
private ref NativeList<float2> _enemySeparationCurrentPushes => ref _jobDataRuntime.EnemySeparationCurrentPushes;
|
|
||||||
private ref NativeList<ProjectileJobInputData> _projectileJobInputs => ref _jobDataRuntime.ProjectileJobInputs;
|
|
||||||
private ref NativeList<ProjectileJobOutputData> _projectileJobOutputs => ref _jobDataRuntime.ProjectileJobOutputs;
|
|
||||||
private ref NativeList<CollisionCandidateData> _collisionCandidates => ref _jobDataRuntime.CollisionCandidates;
|
private ref NativeList<CollisionCandidateData> _collisionCandidates => ref _jobDataRuntime.CollisionCandidates;
|
||||||
private ref NativeParallelMultiHashMap<long, int> _enemySeparationBuckets => ref _jobDataRuntime.EnemySeparationBuckets;
|
private ref NativeParallelMultiHashMap<long, int> _enemySeparationBuckets => ref _jobDataRuntime.EnemySeparationBuckets;
|
||||||
private ref NativeParallelMultiHashMap<long, int> _enemyCollisionBuckets => ref _jobDataRuntime.EnemyCollisionBuckets;
|
private ref NativeParallelMultiHashMap<long, int> _enemyCollisionBuckets => ref _jobDataRuntime.EnemyCollisionBuckets;
|
||||||
@@ -101,24 +91,16 @@ namespace SepCore.Simulation
|
|||||||
|
|
||||||
private sealed class SimulationStateStore
|
private sealed class SimulationStateStore
|
||||||
{
|
{
|
||||||
public readonly List<EnemySimData> Enemies = new();
|
public readonly EnemyStore Enemies = new();
|
||||||
public readonly List<ProjectileSimData> Projectiles = new();
|
public readonly ProjectileStore Projectiles = new();
|
||||||
public readonly List<PickupSimData> Pickups = new();
|
public readonly PickupStore Pickups = new();
|
||||||
public readonly List<int> ProjectileRecycleEntityIds = new();
|
public readonly List<int> ProjectileRecycleEntityIds = new();
|
||||||
public readonly EntityBinding EnemyBinding = new();
|
|
||||||
public readonly EntityBinding ProjectileBinding = new();
|
|
||||||
public readonly EntityBinding PickupBinding = new();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class JobDataRuntimeState
|
private sealed class JobDataRuntimeState
|
||||||
{
|
{
|
||||||
public NativeList<EnemyJobInputData> EnemyJobInputs;
|
public NativeList<EnemyTransformSim> EnemySeparationTransforms;
|
||||||
public NativeList<EnemyJobOutputData> EnemyJobOutputs;
|
public NativeList<int> CollectedPickups;
|
||||||
public NativeList<EnemyJobOutputData> EnemyJobSeparationOutputs;
|
|
||||||
public NativeList<float2> EnemySeparationPreviousPushes;
|
|
||||||
public NativeList<float2> EnemySeparationCurrentPushes;
|
|
||||||
public NativeList<ProjectileJobInputData> ProjectileJobInputs;
|
|
||||||
public NativeList<ProjectileJobOutputData> ProjectileJobOutputs;
|
|
||||||
public NativeList<CollisionCandidateData> CollisionCandidates;
|
public NativeList<CollisionCandidateData> CollisionCandidates;
|
||||||
public NativeParallelMultiHashMap<long, int> EnemySeparationBuckets;
|
public NativeParallelMultiHashMap<long, int> EnemySeparationBuckets;
|
||||||
public NativeParallelMultiHashMap<long, int> EnemyCollisionBuckets;
|
public NativeParallelMultiHashMap<long, int> EnemyCollisionBuckets;
|
||||||
|
|||||||
+222
-114
@@ -1,4 +1,5 @@
|
|||||||
using SepCore.Entity;
|
using SepCore.Entity;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
{
|
{
|
||||||
@@ -16,11 +17,8 @@ namespace SepCore.Simulation
|
|||||||
_areaCollisionRequests.Clear();
|
_areaCollisionRequests.Clear();
|
||||||
_areaCollisionHitEvents.Clear();
|
_areaCollisionHitEvents.Clear();
|
||||||
_areaCollisionHitDedupKeys.Clear();
|
_areaCollisionHitDedupKeys.Clear();
|
||||||
|
_fixedStepAccumulator = 0d;
|
||||||
ClearJobDataChannels();
|
ClearJobDataChannels();
|
||||||
|
|
||||||
EnemyBinding.Clear();
|
|
||||||
ProjectileBinding.Clear();
|
|
||||||
PickupBinding.Clear();
|
|
||||||
ClearPlayerMovementState();
|
ClearPlayerMovementState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,46 +26,96 @@ namespace SepCore.Simulation
|
|||||||
|
|
||||||
#region Enemy Simulation State
|
#region Enemy Simulation State
|
||||||
|
|
||||||
private int AddEnemy(in EnemySimData simData)
|
public int EnemyCount => _enemies.Count;
|
||||||
{
|
|
||||||
int simulationIndex = _enemies.Count;
|
|
||||||
_enemies.Add(simData);
|
|
||||||
EnemyBinding.Bind(simData.EntityId, simulationIndex);
|
|
||||||
OnEnemyAddedToSeparationTemporalBuffers();
|
|
||||||
MarkEnemyTargetSpatialIndexDirty();
|
|
||||||
return simulationIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int UpsertEnemy(in EnemySimData simData)
|
public bool TryGetEnemySnapshot(int entityId, out EnemySimData snapshot)
|
||||||
{
|
{
|
||||||
if (!EnemyBinding.TryGetSimulationIndex(simData.EntityId, out int simulationIndex))
|
if (!_enemies.TryGetSimulationIndex(entityId, out int index))
|
||||||
{
|
{
|
||||||
return AddEnemy(simData);
|
snapshot = default;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_enemies[simulationIndex] = simData;
|
EnemyTransformSim transform = _enemies.Transform[index];
|
||||||
|
EnemyMoveInputSim moveInput = _enemies.MoveInput[index];
|
||||||
|
EnemyMoveConfigSim moveConfig = _enemies.MoveConfig[index];
|
||||||
|
EnemyAttackSim attack = _enemies.Attack[index];
|
||||||
|
EnemyBehaviorSim behavior = _enemies.Behavior[index];
|
||||||
|
snapshot = new EnemySimData
|
||||||
|
{
|
||||||
|
EntityId = entityId,
|
||||||
|
Position = ToVector3(transform.Position),
|
||||||
|
Forward = ToVector3(transform.Forward),
|
||||||
|
Rotation = ToQuaternion(transform.Rotation),
|
||||||
|
Speed = moveInput.Speed,
|
||||||
|
AttackRange = attack.AttackRange,
|
||||||
|
AvoidEnemyOverlap = moveConfig.AvoidOverlap,
|
||||||
|
EnemyBodyRadius = moveConfig.BodyRadius,
|
||||||
|
SeparationIterations = moveConfig.SeparationIterations,
|
||||||
|
State = behavior.State
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AddEnemy(in EnemySimData simData)
|
||||||
|
{
|
||||||
|
InitializeJobDataChannels();
|
||||||
|
|
||||||
|
int index = _enemies.Add(
|
||||||
|
new EnemyIdentitySim { EntityId = simData.EntityId },
|
||||||
|
new EnemyTransformSim
|
||||||
|
{
|
||||||
|
Position = ToFloat3(simData.Position),
|
||||||
|
Forward = ToFloat3(simData.Forward),
|
||||||
|
Rotation = ToQuaternion(simData.Rotation)
|
||||||
|
},
|
||||||
|
new EnemyMoveInputSim { Speed = simData.Speed },
|
||||||
|
new EnemyMoveConfigSim
|
||||||
|
{
|
||||||
|
AvoidOverlap = simData.AvoidEnemyOverlap,
|
||||||
|
BodyRadius = simData.EnemyBodyRadius,
|
||||||
|
SeparationIterations = simData.SeparationIterations
|
||||||
|
},
|
||||||
|
new EnemyAttackSim { AttackRange = simData.AttackRange },
|
||||||
|
new EnemyBehaviorSim { State = simData.State });
|
||||||
MarkEnemyTargetSpatialIndexDirty();
|
MarkEnemyTargetSpatialIndexDirty();
|
||||||
return simulationIndex;
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int UpsertEnemy(in EnemySimData simData)
|
||||||
|
{
|
||||||
|
if (!_enemies.TryGetSimulationIndex(simData.EntityId, out int index))
|
||||||
|
{
|
||||||
|
return AddEnemy(in simData);
|
||||||
|
}
|
||||||
|
|
||||||
|
_enemies.Identity[index] = new EnemyIdentitySim { EntityId = simData.EntityId };
|
||||||
|
_enemies.Transform[index] = new EnemyTransformSim
|
||||||
|
{
|
||||||
|
Position = ToFloat3(simData.Position),
|
||||||
|
Forward = ToFloat3(simData.Forward),
|
||||||
|
Rotation = ToQuaternion(simData.Rotation)
|
||||||
|
};
|
||||||
|
_enemies.MoveInput[index] = new EnemyMoveInputSim { Speed = simData.Speed };
|
||||||
|
_enemies.MoveConfig[index] = new EnemyMoveConfigSim
|
||||||
|
{
|
||||||
|
AvoidOverlap = simData.AvoidEnemyOverlap,
|
||||||
|
BodyRadius = simData.EnemyBodyRadius,
|
||||||
|
SeparationIterations = simData.SeparationIterations
|
||||||
|
};
|
||||||
|
_enemies.Attack[index] = new EnemyAttackSim { AttackRange = simData.AttackRange };
|
||||||
|
_enemies.Behavior[index] = new EnemyBehaviorSim { State = simData.State };
|
||||||
|
MarkEnemyTargetSpatialIndexDirty();
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool RemoveEnemyByEntityId(int entityId)
|
private bool RemoveEnemyByEntityId(int entityId)
|
||||||
{
|
{
|
||||||
if (!EnemyBinding.TryGetSimulationIndex(entityId, out int simulationIndex))
|
if (!_enemies.RemoveByEntityId(entityId))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int lastIndex = _enemies.Count - 1;
|
|
||||||
if (simulationIndex != lastIndex)
|
|
||||||
{
|
|
||||||
EnemySimData movedData = _enemies[lastIndex];
|
|
||||||
_enemies[simulationIndex] = movedData;
|
|
||||||
EnemyBinding.RemapIndex(movedData.EntityId, simulationIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
_enemies.RemoveAt(lastIndex);
|
|
||||||
OnEnemyRemovedFromSeparationTemporalBuffers(simulationIndex);
|
|
||||||
EnemyBinding.UnbindByEntityId(entityId);
|
|
||||||
MarkEnemyTargetSpatialIndexDirty();
|
MarkEnemyTargetSpatialIndexDirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -88,85 +136,122 @@ namespace SepCore.Simulation
|
|||||||
RemoveEnemyByEntityId(entityId);
|
RemoveEnemyByEntityId(entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetEnemyData(int entityId, out EnemySimData enemyData)
|
public void SyncEnemyMovementInput(int entityId, bool isMoving, in Vector3 direction, float speed,
|
||||||
{
|
|
||||||
if (!EnemyBinding.TryGetSimulationIndex(entityId, out int simulationIndex) || simulationIndex < 0 ||
|
|
||||||
simulationIndex >= _enemies.Count)
|
|
||||||
{
|
|
||||||
enemyData = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
enemyData = _enemies[simulationIndex];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SyncEnemyMovementInput(int entityId, bool isMoving, in UnityEngine.Vector3 direction, float speed,
|
|
||||||
bool avoidEnemyOverlap, float enemyBodyRadius, int separationIterations)
|
bool avoidEnemyOverlap, float enemyBodyRadius, int separationIterations)
|
||||||
{
|
{
|
||||||
if (!EnemyBinding.TryGetSimulationIndex(entityId, out int simulationIndex) || simulationIndex < 0 ||
|
if (!_enemies.TryGetSimulationIndex(entityId, out int index))
|
||||||
simulationIndex >= _enemies.Count)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnemySimData enemyData = _enemies[simulationIndex];
|
_enemies.MoveInput[index] = new EnemyMoveInputSim
|
||||||
enemyData.Speed = isMoving ? UnityEngine.Mathf.Max(0f, speed) : 0f;
|
|
||||||
enemyData.AvoidEnemyOverlap = avoidEnemyOverlap;
|
|
||||||
enemyData.EnemyBodyRadius = UnityEngine.Mathf.Max(0.01f, enemyBodyRadius);
|
|
||||||
enemyData.SeparationIterations = UnityEngine.Mathf.Max(1, separationIterations);
|
|
||||||
|
|
||||||
UnityEngine.Vector3 planarDirection = direction;
|
|
||||||
planarDirection.y = 0f;
|
|
||||||
if (planarDirection.sqrMagnitude > UnityEngine.Mathf.Epsilon)
|
|
||||||
{
|
{
|
||||||
enemyData.Forward = planarDirection.normalized;
|
Speed = isMoving ? Mathf.Max(0f, speed) : 0f
|
||||||
}
|
};
|
||||||
|
_enemies.MoveConfig[index] = new EnemyMoveConfigSim
|
||||||
|
{
|
||||||
|
AvoidOverlap = avoidEnemyOverlap,
|
||||||
|
BodyRadius = Mathf.Max(0.01f, enemyBodyRadius),
|
||||||
|
SeparationIterations = Mathf.Max(1, separationIterations)
|
||||||
|
};
|
||||||
|
|
||||||
_enemies[simulationIndex] = enemyData;
|
Vector3 planarDirection = direction;
|
||||||
|
planarDirection.y = 0f;
|
||||||
|
if (planarDirection.sqrMagnitude > Mathf.Epsilon)
|
||||||
|
{
|
||||||
|
planarDirection.Normalize();
|
||||||
|
EnemyTransformSim transform = _enemies.Transform[index];
|
||||||
|
transform.Forward = ToFloat3(planarDirection);
|
||||||
|
_enemies.Transform[index] = transform;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Projectile Simulation State
|
#region Projectile Simulation State
|
||||||
|
|
||||||
private int AddProjectile(in ProjectileSimData simData)
|
public int ProjectileCount => _projectiles.Count;
|
||||||
{
|
|
||||||
int simulationIndex = _projectiles.Count;
|
|
||||||
_projectiles.Add(simData);
|
|
||||||
ProjectileBinding.Bind(simData.EntityId, simulationIndex);
|
|
||||||
return simulationIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int UpsertProjectile(in ProjectileSimData simData)
|
public bool TryGetProjectileSnapshot(int entityId, out ProjectileSimData snapshot)
|
||||||
{
|
{
|
||||||
if (!ProjectileBinding.TryGetSimulationIndex(simData.EntityId, out int simulationIndex))
|
if (!_projectiles.TryGetSimulationIndex(entityId, out int index))
|
||||||
{
|
{
|
||||||
return AddProjectile(simData);
|
snapshot = default;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_projectiles[simulationIndex] = simData;
|
ProjectileIdentitySim identity = _projectiles.Identity[index];
|
||||||
return simulationIndex;
|
ProjectileTransformSim transform = _projectiles.Transform[index];
|
||||||
|
ProjectileVelocitySim velocity = _projectiles.Velocity[index];
|
||||||
|
ProjectileLifeSim life = _projectiles.Life[index];
|
||||||
|
snapshot = new ProjectileSimData
|
||||||
|
{
|
||||||
|
EntityId = identity.EntityId,
|
||||||
|
OwnerEntityId = identity.OwnerEntityId,
|
||||||
|
Position = ToVector3(transform.Position),
|
||||||
|
Forward = ToVector3(transform.Forward),
|
||||||
|
Velocity = ToVector3(velocity.Velocity),
|
||||||
|
LifeTime = life.LifeTime,
|
||||||
|
Age = life.Age,
|
||||||
|
Active = life.Active
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AddProjectile(in ProjectileSimData simData)
|
||||||
|
{
|
||||||
|
InitializeJobDataChannels();
|
||||||
|
|
||||||
|
return _projectiles.Add(
|
||||||
|
new ProjectileIdentitySim
|
||||||
|
{
|
||||||
|
EntityId = simData.EntityId,
|
||||||
|
OwnerEntityId = simData.OwnerEntityId
|
||||||
|
},
|
||||||
|
new ProjectileTransformSim
|
||||||
|
{
|
||||||
|
Position = ToFloat3(simData.Position),
|
||||||
|
Forward = ToFloat3(simData.Forward)
|
||||||
|
},
|
||||||
|
new ProjectileVelocitySim { Velocity = ToFloat3(simData.Velocity) },
|
||||||
|
new ProjectileLifeSim
|
||||||
|
{
|
||||||
|
Age = simData.Age,
|
||||||
|
LifeTime = simData.LifeTime,
|
||||||
|
Active = simData.Active
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int UpsertProjectile(in ProjectileSimData simData)
|
||||||
|
{
|
||||||
|
if (!_projectiles.TryGetSimulationIndex(simData.EntityId, out int index))
|
||||||
|
{
|
||||||
|
return AddProjectile(in simData);
|
||||||
|
}
|
||||||
|
|
||||||
|
_projectiles.Identity[index] = new ProjectileIdentitySim
|
||||||
|
{
|
||||||
|
EntityId = simData.EntityId,
|
||||||
|
OwnerEntityId = simData.OwnerEntityId
|
||||||
|
};
|
||||||
|
_projectiles.Transform[index] = new ProjectileTransformSim
|
||||||
|
{
|
||||||
|
Position = ToFloat3(simData.Position),
|
||||||
|
Forward = ToFloat3(simData.Forward)
|
||||||
|
};
|
||||||
|
_projectiles.Velocity[index] = new ProjectileVelocitySim { Velocity = ToFloat3(simData.Velocity) };
|
||||||
|
_projectiles.Life[index] = new ProjectileLifeSim
|
||||||
|
{
|
||||||
|
Age = simData.Age,
|
||||||
|
LifeTime = simData.LifeTime,
|
||||||
|
Active = simData.Active
|
||||||
|
};
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool RemoveProjectileByEntityId(int entityId)
|
private bool RemoveProjectileByEntityId(int entityId)
|
||||||
{
|
{
|
||||||
if (!ProjectileBinding.TryGetSimulationIndex(entityId, out int simulationIndex))
|
return _projectiles.RemoveByEntityId(entityId);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lastIndex = _projectiles.Count - 1;
|
|
||||||
if (simulationIndex != lastIndex)
|
|
||||||
{
|
|
||||||
ProjectileSimData movedData = _projectiles[lastIndex];
|
|
||||||
_projectiles[simulationIndex] = movedData;
|
|
||||||
ProjectileBinding.RemapIndex(movedData.EntityId, simulationIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
_projectiles.RemoveAt(lastIndex);
|
|
||||||
ProjectileBinding.UnbindByEntityId(entityId);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterProjectileLifecycle(EntityBase projectileEntity, object userData)
|
private void RegisterProjectileLifecycle(EntityBase projectileEntity, object userData)
|
||||||
@@ -188,43 +273,66 @@ namespace SepCore.Simulation
|
|||||||
|
|
||||||
#region Pickup Simulation State
|
#region Pickup Simulation State
|
||||||
|
|
||||||
private int AddPickup(in PickupSimData simData)
|
public int PickupCount => _pickups.Count;
|
||||||
{
|
|
||||||
int simulationIndex = _pickups.Count;
|
|
||||||
_pickups.Add(simData);
|
|
||||||
PickupBinding.Bind(simData.EntityId, simulationIndex);
|
|
||||||
return simulationIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int UpsertPickup(in PickupSimData simData)
|
public bool TryGetPickupSnapshot(int entityId, out PickupSimData snapshot)
|
||||||
{
|
{
|
||||||
if (!PickupBinding.TryGetSimulationIndex(simData.EntityId, out int simulationIndex))
|
if (!_pickups.TryGetSimulationIndex(entityId, out int index))
|
||||||
{
|
{
|
||||||
return AddPickup(simData);
|
snapshot = default;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pickups[simulationIndex] = simData;
|
PickupTransformSim transform = _pickups.Transform[index];
|
||||||
return simulationIndex;
|
PickupRadiusSim radius = _pickups.Radius[index];
|
||||||
|
PickupStateSim state = _pickups.State[index];
|
||||||
|
snapshot = new PickupSimData
|
||||||
|
{
|
||||||
|
EntityId = entityId,
|
||||||
|
Position = ToVector3(transform.Position),
|
||||||
|
PickupRadius = radius.Radius,
|
||||||
|
State = state.State
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AddPickup(in PickupSimData simData)
|
||||||
|
{
|
||||||
|
InitializeJobDataChannels();
|
||||||
|
|
||||||
|
return _pickups.Add(
|
||||||
|
new PickupIdentitySim { EntityId = simData.EntityId },
|
||||||
|
new PickupTransformSim { Position = ToFloat3(simData.Position) },
|
||||||
|
new PickupRadiusSim { Radius = simData.PickupRadius },
|
||||||
|
new PickupStateSim { State = simData.State });
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int UpsertPickup(in PickupSimData simData)
|
||||||
|
{
|
||||||
|
if (!_pickups.TryGetSimulationIndex(simData.EntityId, out int index))
|
||||||
|
{
|
||||||
|
return AddPickup(in simData);
|
||||||
|
}
|
||||||
|
|
||||||
|
_pickups.Identity[index] = new PickupIdentitySim { EntityId = simData.EntityId };
|
||||||
|
_pickups.Transform[index] = new PickupTransformSim { Position = ToFloat3(simData.Position) };
|
||||||
|
_pickups.Radius[index] = new PickupRadiusSim { Radius = simData.PickupRadius };
|
||||||
|
_pickups.State[index] = new PickupStateSim { State = simData.State };
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TriggerFullMapMagnet()
|
||||||
|
{
|
||||||
|
int pickupCount = _pickups.Count;
|
||||||
|
for (int i = 0; i < pickupCount; i++)
|
||||||
|
{
|
||||||
|
_pickups.State[i] = new PickupStateSim { State = PickupStateSim.StateAbsorbing };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool RemovePickupByEntityId(int entityId)
|
private bool RemovePickupByEntityId(int entityId)
|
||||||
{
|
{
|
||||||
if (!PickupBinding.TryGetSimulationIndex(entityId, out int simulationIndex))
|
return _pickups.RemoveByEntityId(entityId);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lastIndex = _pickups.Count - 1;
|
|
||||||
if (simulationIndex != lastIndex)
|
|
||||||
{
|
|
||||||
PickupSimData movedData = _pickups[lastIndex];
|
|
||||||
_pickups[simulationIndex] = movedData;
|
|
||||||
PickupBinding.RemapIndex(movedData.EntityId, simulationIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
_pickups.RemoveAt(lastIndex);
|
|
||||||
PickupBinding.UnbindByEntityId(entityId);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterPickupLifecycle(EntityBase pickupEntity)
|
private void RegisterPickupLifecycle(EntityBase pickupEntity)
|
||||||
|
|||||||
+13
-22
@@ -1,5 +1,6 @@
|
|||||||
using SepCore.Debugger;
|
using SepCore.Debugger;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
|
using Unity.Mathematics;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
@@ -17,8 +18,8 @@ namespace SepCore.Simulation
|
|||||||
BuildEnemyTargetSpatialIndexIfNeeded();
|
BuildEnemyTargetSpatialIndexIfNeeded();
|
||||||
|
|
||||||
float cellSize = GetTargetSelectionCellSize();
|
float cellSize = GetTargetSelectionCellSize();
|
||||||
int centerCellX = ToCell(origin.x, cellSize);
|
int centerCellX = SimulationCell.ToCell(origin.x, cellSize);
|
||||||
int centerCellZ = ToCell(origin.z, cellSize);
|
int centerCellZ = SimulationCell.ToCell(origin.z, cellSize);
|
||||||
float range = Mathf.Sqrt(maxSqrRange);
|
float range = Mathf.Sqrt(maxSqrRange);
|
||||||
int queryRange = Mathf.Max(1, Mathf.CeilToInt(range / cellSize));
|
int queryRange = Mathf.Max(1, Mathf.CeilToInt(range / cellSize));
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
for (int dz = -queryRange; dz <= queryRange; dz++)
|
for (int dz = -queryRange; dz <= queryRange; dz++)
|
||||||
{
|
{
|
||||||
long key = CellKey(centerCellX + dx, centerCellZ + dz);
|
long key = SimulationCell.Key(centerCellX + dx, centerCellZ + dz);
|
||||||
if (!_enemyTargetBuckets.TryGetFirstValue(key, out int enemyIndex,
|
if (!_enemyTargetBuckets.TryGetFirstValue(key, out int enemyIndex,
|
||||||
out NativeParallelMultiHashMapIterator<long> iterator))
|
out NativeParallelMultiHashMapIterator<long> iterator))
|
||||||
{
|
{
|
||||||
@@ -45,17 +46,17 @@ namespace SepCore.Simulation
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnemySimData enemy = _enemies[enemyIndex];
|
float3 enemyPosition = _enemies.Transform[enemyIndex].Position;
|
||||||
Vector3 delta = enemy.Position - origin;
|
float deltaX = enemyPosition.x - origin.x;
|
||||||
delta.y = 0f;
|
float deltaZ = enemyPosition.z - origin.z;
|
||||||
float sqrDistance = delta.sqrMagnitude;
|
float sqrDistance = deltaX * deltaX + deltaZ * deltaZ;
|
||||||
if (sqrDistance >= minSqrDistance)
|
if (sqrDistance >= minSqrDistance)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
minSqrDistance = sqrDistance;
|
minSqrDistance = sqrDistance;
|
||||||
enemyEntityId = enemy.EntityId;
|
enemyEntityId = _enemies.Identity[enemyIndex].EntityId;
|
||||||
found = true;
|
found = true;
|
||||||
} while (_enemyTargetBuckets.TryGetNextValue(out enemyIndex, ref iterator));
|
} while (_enemyTargetBuckets.TryGetNextValue(out enemyIndex, ref iterator));
|
||||||
}
|
}
|
||||||
@@ -125,10 +126,10 @@ namespace SepCore.Simulation
|
|||||||
|
|
||||||
for (int i = 0; i < enemyCount; i++)
|
for (int i = 0; i < enemyCount; i++)
|
||||||
{
|
{
|
||||||
EnemySimData enemy = _enemies[i];
|
float3 enemyPosition = _enemies.Transform[i].Position;
|
||||||
int cellX = ToCell(enemy.Position.x, cellSize);
|
int cellX = SimulationCell.ToCell(enemyPosition.x, cellSize);
|
||||||
int cellZ = ToCell(enemy.Position.z, cellSize);
|
int cellZ = SimulationCell.ToCell(enemyPosition.z, cellSize);
|
||||||
_enemyTargetBuckets.Add(CellKey(cellX, cellZ), i);
|
_enemyTargetBuckets.Add(SimulationCell.Key(cellX, cellZ), i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,15 +140,5 @@ namespace SepCore.Simulation
|
|||||||
{
|
{
|
||||||
return Mathf.Max(0.1f, _targetSelectionCellSize);
|
return Mathf.Max(0.1f, _targetSelectionCellSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ToCell(float value, float cellSize)
|
|
||||||
{
|
|
||||||
return Mathf.FloorToInt(value / cellSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long CellKey(int x, int z)
|
|
||||||
{
|
|
||||||
return ((long)x << 32) ^ (uint)z;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,45 @@
|
|||||||
using System.Collections.Generic;
|
using SepCore.Debugger;
|
||||||
using SepCore.Debugger;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
namespace SepCore.Simulation
|
namespace SepCore.Simulation
|
||||||
{
|
{
|
||||||
public sealed partial class SimulationWorld : GameFrameworkComponent
|
public sealed partial class SimulationWorld : GameFrameworkComponent, ISimulationWorld
|
||||||
{
|
{
|
||||||
// Partial layout:
|
// Partial layout:
|
||||||
// - SimulationWorld.cs: 鏍稿績鐘舵€併€佸父閲忓拰 Unity 鐢熷懡鍛ㄦ湡鍏ュ彛鐐广€?
|
// - SimulationWorld.cs: 核心状态、常量和 Unity 生命周期入口点。
|
||||||
// - SimulationWorld.RuntimeModules.cs: 杩愯鏃跺煙瀵硅薄銆侀厤缃拰鐘舵€佷唬鐞嗐€?
|
// - SimulationWorld.RuntimeModules.cs: 运行时域对象、配置和状态代理。
|
||||||
// - SimulationWorld.SimEntityState.cs: 妯℃嫙鐘舵€佺殑澧炲垹鏀规煡鍜岀敓鍛藉懆鏈熸敞鍐屻€?
|
// - SimulationWorld.SimEntityState.cs: 模拟状态的增删改查和生命周期注册。
|
||||||
// - SimulationWorld.EntityToSimData.cs: Unity 瀹炰綋鍒?sim data 鐨勫垵濮嬪寲閫傞厤銆?
|
// - SimulationWorld.EntityToSimData.cs: Unity 实体与组件列/快照之间的边界转换。
|
||||||
// - SimulationWorld.EntitySync.cs: GameFramework 瀹炰綋 show/hide 浜嬩欢妗ャ€?
|
// - SimulationWorld.EntitySync.cs: GameFramework 实体 show/hide 事件桥。
|
||||||
// - SimulationWorld.TargetSelectionSpatialIndex.cs: 鏈€杩戞晫绌洪棿绱㈠紩鏌ヨ銆?
|
// - SimulationWorld.TargetSelectionSpatialIndex.cs: 最近敌空间索引查询。
|
||||||
// - Presentation/SimulationWorld.TransformSync.cs: late-update transform 鍚屾妗ャ€?
|
// - SimulationWorld.PlayerMovement.cs: 玩家模拟移动输入与步进。
|
||||||
// - Presentation/SimulationWorld.HitPresentation.cs: 鎶曞皠鐗╁懡涓簨浠惰〃鐜版ˉ銆?
|
// - Components/*: 敌人/投射物/掉落物 SoA 组件列与存储。
|
||||||
// - DataChannel/SimulationWorld.JobDataChannel.cs: Job 閫氶亾鍏变韩瀛楁銆佸父閲忓拰杩愯鏃剁姸鎬併€?
|
// - Presentation/SimulationWorld.TransformSync.cs: late-update transform 同步桥。
|
||||||
// - DataChannel/SimulationWorld.JobDataLifecycle.cs: Native 閫氶亾鍒濆鍖栥€佹竻鐞嗗拰 clear銆?
|
// - Presentation/SimulationWorld.HitPresentation.cs: 投射物命中事件表现桥。
|
||||||
// - DataChannel/SimulationWorld.JobDataConversion.cs: sim/job 鏁版嵁杞崲涓庤緭鍏ヨ緭鍑虹紦鍐插噯澶囥€?
|
// - DataChannel/*: Job 通道与帧间临时状态。
|
||||||
// - DataChannel/SimulationWorld.CollisionTransient.cs: 纰版挒涓存椂閫氶亾鍜岃繍琛屾椂缁熻銆?
|
// - Systems/*: 独立数据并行系统(移动/分离/碰撞查询)。
|
||||||
// - DataChannel/SimulationWorld.EnemySeparationTemporal.cs: 鏁屼汉鍒嗙鐨勫抚闂翠复鏃剁姸鎬併€?
|
// - Jobs/*: 模拟管线编排、碰撞调度与结算。
|
||||||
// - DataChannel/SimulationWorld.JobOutputCommit.cs: Job 杈撳嚭鍥炲啓涓诲鍣ㄣ€?
|
// - JobStruct/*: 面向 job 的瞬时数据结构。
|
||||||
// - Jobs/SimulationWorld.EnemyJobs.cs: 妯℃嫙閫氶亾 缂栨帓 + 鏁屼汉绉诲姩/鍒嗙 椤哄簭鎵ц
|
// 模拟数据以组件列(SoA)为唯一存储,XxxSimData 只作为生成/查询边界的快照结构。
|
||||||
// - Jobs/SimulationWorld.ProjectileJobs.cs: 鎶曞皠鐗╃Щ鍔ㄤ笌鍥炴敹
|
private const float MinFixedStepSeconds = 0.0001f;
|
||||||
// - Jobs/SimulationWorld.CollisionPipeline.cs: 纰版挒绠$嚎鍏变韩閰嶇疆鍜岀姸鎬?
|
private const double FixedStepEpsilonScale = 1e-4;
|
||||||
// - Jobs/SimulationWorld.CollisionRequests.cs: area/sector 璇锋眰缂撳啿
|
|
||||||
// - Jobs/SimulationWorld.CollisionBroadPhase.cs: broad-phase 鍊欓€夋瀯寤哄拰 Job 璋冨害
|
[Header("固定步长模拟")]
|
||||||
// - Jobs/SimulationWorld.CollisionResolve.cs: 涓荤嚎绋嬪懡涓粨绠椾笌 area settle
|
[Tooltip("开启后按固定步长累积时间,一帧内可能执行多次模拟步进。")]
|
||||||
// - Jobs/SimulationWorld.CollisionPresentation.cs: 鍛戒腑琛ㄧ幇浜嬩欢鍜屽疄浣?impact 瑙f瀽
|
[SerializeField]
|
||||||
// - JobStruct/*.cs: burst job 鍐呮牳鍜岄潰鍚?job 鐨勬暟鎹粨鏋?
|
private bool _fixedStepEnabled = true;
|
||||||
private const float DefaultAttackRange = 1f;
|
|
||||||
private const int EnemyStateIdle = 0;
|
[Tooltip("固定步长(秒)。")]
|
||||||
private const int EnemyStateChasing = 1;
|
[SerializeField]
|
||||||
private const int EnemyStateInAttackRange = 2;
|
private float _fixedStepSeconds = 1f / 60f;
|
||||||
private const int ProjectileStateActive = 0;
|
|
||||||
private const int ProjectileStateExpired = 1;
|
private double _fixedStepAccumulator;
|
||||||
|
|
||||||
private EntitySync _entitySync;
|
private EntitySync _entitySync;
|
||||||
private TransformSync _transformSync;
|
private TransformSync _transformSync;
|
||||||
private HitPresentation _hitPresentation;
|
private HitPresentation _hitPresentation;
|
||||||
|
|
||||||
public IReadOnlyList<EnemySimData> Enemies => _enemies;
|
|
||||||
public IReadOnlyList<ProjectileSimData> Projectiles => _projectiles;
|
|
||||||
public IReadOnlyList<PickupSimData> Pickups => _pickups;
|
|
||||||
|
|
||||||
#region Lifecycle
|
#region Lifecycle
|
||||||
|
|
||||||
protected override void Awake()
|
protected override void Awake()
|
||||||
@@ -64,9 +59,30 @@ namespace SepCore.Simulation
|
|||||||
|
|
||||||
public void Tick(in SimulationTickContext context)
|
public void Tick(in SimulationTickContext context)
|
||||||
{
|
{
|
||||||
Vector3 playerPosition = ResolvePlayerPositionForTick(in context);
|
if (!_fixedStepEnabled)
|
||||||
SimulationTickContext resolvedContext =
|
{
|
||||||
new SimulationTickContext(context.DeltaTime, context.RealDeltaTime, playerPosition);
|
TickSimulationStep(context.PlayerPosition, context.DeltaTime);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double fixedStep = Mathf.Max(MinFixedStepSeconds, _fixedStepSeconds);
|
||||||
|
double stepEpsilon = fixedStep * FixedStepEpsilonScale;
|
||||||
|
_fixedStepAccumulator += context.DeltaTime;
|
||||||
|
while (_fixedStepAccumulator + stepEpsilon >= fixedStep)
|
||||||
|
{
|
||||||
|
TickSimulationStep(context.PlayerPosition, (float)fixedStep);
|
||||||
|
_fixedStepAccumulator -= fixedStep;
|
||||||
|
if (_fixedStepAccumulator < 0d)
|
||||||
|
{
|
||||||
|
_fixedStepAccumulator = 0d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TickSimulationStep(Vector3 fallbackPlayerPosition, float deltaTime)
|
||||||
|
{
|
||||||
|
Vector3 playerPosition = ResolvePlayerPositionForTick(fallbackPlayerPosition, deltaTime);
|
||||||
|
SimulationTickContext resolvedContext = new SimulationTickContext(deltaTime, playerPosition);
|
||||||
using (CustomProfilerMarker.TickEnemies.Auto())
|
using (CustomProfilerMarker.TickEnemies.Auto())
|
||||||
{
|
{
|
||||||
TickSimulationPipeline(in resolvedContext);
|
TickSimulationPipeline(in resolvedContext);
|
||||||
@@ -91,4 +107,3 @@ namespace SepCore.Simulation
|
|||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 57a5e62d1ac18754e98d0ebf2f2c12c8
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+215
@@ -0,0 +1,215 @@
|
|||||||
|
using SepCore.Debugger;
|
||||||
|
using Unity.Burst;
|
||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Jobs;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal static class CollisionQuerySystem
|
||||||
|
{
|
||||||
|
// 读写集:R EnemyTransform / EnemyMoveConfig / EnemyIdentity / CollisionQueryData(瞬时)
|
||||||
|
// W CollisionCandidateData(瞬时) / 敌人碰撞分桶
|
||||||
|
public static void BuildEnemyCollisionBuckets(EnemyStore enemies,
|
||||||
|
NativeParallelMultiHashMap<long, int> buckets, float cellSize)
|
||||||
|
{
|
||||||
|
buckets.Clear();
|
||||||
|
int enemyCount = enemies.Count;
|
||||||
|
if (enemyCount <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildCollisionBucketsJob job = new BuildCollisionBucketsJob
|
||||||
|
{
|
||||||
|
Transforms = enemies.Transform.Data.AsArray(),
|
||||||
|
Buckets = buckets.AsParallelWriter(),
|
||||||
|
CellSize = cellSize
|
||||||
|
};
|
||||||
|
|
||||||
|
using (CustomProfilerMarker.TickEnemies_Complete.Auto())
|
||||||
|
{
|
||||||
|
job.Schedule(enemyCount, 64).Complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ScheduleCandidateQuery(NativeList<CollisionQueryData> queries,
|
||||||
|
NativeList<CollisionCandidateData> candidates, NativeParallelMultiHashMap<long, int> enemyBuckets,
|
||||||
|
EnemyStore enemies, bool hasEnemyTargets, bool hasPlayerTarget, int playerTargetEntityId,
|
||||||
|
float3 playerPosition, float cellSize, out JobHandle handle)
|
||||||
|
{
|
||||||
|
handle = default;
|
||||||
|
if (!queries.IsCreated || !candidates.IsCreated)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.Clear();
|
||||||
|
int queryCount = queries.Length;
|
||||||
|
if (queryCount == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryCollisionCandidatesJob job = new QueryCollisionCandidatesJob
|
||||||
|
{
|
||||||
|
Queries = queries.AsArray(),
|
||||||
|
EnemyBuckets = enemyBuckets,
|
||||||
|
EnemyTransforms = enemies.Transform.Data.AsArray(),
|
||||||
|
EnemyConfigs = enemies.MoveConfig.Data.AsArray(),
|
||||||
|
EnemyIdentities = enemies.Identity.Data.AsArray(),
|
||||||
|
Candidates = candidates.AsParallelWriter(),
|
||||||
|
HasEnemyTargets = hasEnemyTargets,
|
||||||
|
HasPlayerTarget = hasPlayerTarget,
|
||||||
|
PlayerTargetEntityId = playerTargetEntityId,
|
||||||
|
PlayerPosition = playerPosition,
|
||||||
|
CellSize = cellSize
|
||||||
|
};
|
||||||
|
|
||||||
|
handle = job.Schedule(queryCount, 64);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BurstCompile]
|
||||||
|
internal struct BuildCollisionBucketsJob : IJobParallelFor
|
||||||
|
{
|
||||||
|
[ReadOnly] public NativeArray<EnemyTransformSim> Transforms;
|
||||||
|
public NativeParallelMultiHashMap<long, int>.ParallelWriter Buckets;
|
||||||
|
public float CellSize;
|
||||||
|
|
||||||
|
public void Execute(int index)
|
||||||
|
{
|
||||||
|
float3 position = Transforms[index].Position;
|
||||||
|
int cellX = SimulationCell.ToCell(position.x, CellSize);
|
||||||
|
int cellZ = SimulationCell.ToCell(position.z, CellSize);
|
||||||
|
Buckets.Add(SimulationCell.Key(cellX, cellZ), index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BurstCompile]
|
||||||
|
internal struct QueryCollisionCandidatesJob : IJobParallelFor
|
||||||
|
{
|
||||||
|
[ReadOnly] public NativeArray<CollisionQueryData> Queries;
|
||||||
|
[ReadOnly] public NativeParallelMultiHashMap<long, int> EnemyBuckets;
|
||||||
|
[ReadOnly] public NativeArray<EnemyTransformSim> EnemyTransforms;
|
||||||
|
[ReadOnly] public NativeArray<EnemyMoveConfigSim> EnemyConfigs;
|
||||||
|
[ReadOnly] public NativeArray<EnemyIdentitySim> EnemyIdentities;
|
||||||
|
public NativeList<CollisionCandidateData>.ParallelWriter Candidates;
|
||||||
|
public bool HasEnemyTargets;
|
||||||
|
public bool HasPlayerTarget;
|
||||||
|
public int PlayerTargetEntityId;
|
||||||
|
public float3 PlayerPosition;
|
||||||
|
public float CellSize;
|
||||||
|
|
||||||
|
public void Execute(int index)
|
||||||
|
{
|
||||||
|
CollisionQueryData query = Queries[index];
|
||||||
|
int centerCellX = SimulationCell.ToCell(query.Position.x, CellSize);
|
||||||
|
int centerCellZ = SimulationCell.ToCell(query.Position.z, CellSize);
|
||||||
|
int queryRange = math.max(1, (int)math.ceil(query.Radius / CellSize));
|
||||||
|
int selectedCount = 0;
|
||||||
|
bool reachedLimit = false;
|
||||||
|
|
||||||
|
if (HasPlayerTarget && query.SourceEntityId != PlayerTargetEntityId &&
|
||||||
|
query.SourceOwnerEntityId != PlayerTargetEntityId)
|
||||||
|
{
|
||||||
|
float3 playerPosition = PlayerPosition;
|
||||||
|
if (query.SourceType == SimulationCollision.SourceTypeArea)
|
||||||
|
{
|
||||||
|
playerPosition.y = query.Position.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 playerDelta = playerPosition - query.Position;
|
||||||
|
float playerSqrDistance = math.lengthsq(playerDelta);
|
||||||
|
float playerRadiusSqr = query.Radius * query.Radius;
|
||||||
|
if (playerSqrDistance <= playerRadiusSqr)
|
||||||
|
{
|
||||||
|
Candidates.AddNoResize(new CollisionCandidateData
|
||||||
|
{
|
||||||
|
QueryId = query.QueryId,
|
||||||
|
SourceType = query.SourceType,
|
||||||
|
SourceEntityId = query.SourceEntityId,
|
||||||
|
SourceOwnerEntityId = query.SourceOwnerEntityId,
|
||||||
|
TargetEntityId = PlayerTargetEntityId,
|
||||||
|
SqrDistance = playerSqrDistance
|
||||||
|
});
|
||||||
|
|
||||||
|
selectedCount++;
|
||||||
|
if (selectedCount >= query.MaxTargets)
|
||||||
|
{
|
||||||
|
reachedLimit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HasEnemyTargets || reachedLimit)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int dx = -queryRange; dx <= queryRange && !reachedLimit; dx++)
|
||||||
|
{
|
||||||
|
for (int dz = -queryRange; dz <= queryRange && !reachedLimit; dz++)
|
||||||
|
{
|
||||||
|
long key = SimulationCell.Key(centerCellX + dx, centerCellZ + dz);
|
||||||
|
if (!EnemyBuckets.TryGetFirstValue(key, out int enemyIndex,
|
||||||
|
out NativeParallelMultiHashMapIterator<long> iterator))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (enemyIndex < 0 || enemyIndex >= EnemyTransforms.Length)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyIdentitySim identity = EnemyIdentities[enemyIndex];
|
||||||
|
if (identity.EntityId == query.SourceOwnerEntityId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyTransformSim enemyTransform = EnemyTransforms[enemyIndex];
|
||||||
|
float deltaY = query.SourceType == SimulationCollision.SourceTypeArea
|
||||||
|
? 0f
|
||||||
|
: enemyTransform.Position.y - query.Position.y;
|
||||||
|
float3 delta = new float3(
|
||||||
|
enemyTransform.Position.x - query.Position.x,
|
||||||
|
deltaY,
|
||||||
|
enemyTransform.Position.z - query.Position.z);
|
||||||
|
float sqrDistance = math.lengthsq(delta);
|
||||||
|
float targetRadius = query.SourceType == SimulationCollision.SourceTypeArea
|
||||||
|
? math.max(0f, EnemyConfigs[enemyIndex].BodyRadius)
|
||||||
|
: 0f;
|
||||||
|
float effectiveRadius = query.Radius + targetRadius;
|
||||||
|
if (sqrDistance > effectiveRadius * effectiveRadius)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Candidates.AddNoResize(new CollisionCandidateData
|
||||||
|
{
|
||||||
|
QueryId = query.QueryId,
|
||||||
|
SourceType = query.SourceType,
|
||||||
|
SourceEntityId = query.SourceEntityId,
|
||||||
|
SourceOwnerEntityId = query.SourceOwnerEntityId,
|
||||||
|
TargetEntityId = identity.EntityId,
|
||||||
|
SqrDistance = sqrDistance
|
||||||
|
});
|
||||||
|
|
||||||
|
selectedCount++;
|
||||||
|
|
||||||
|
if (selectedCount >= query.MaxTargets)
|
||||||
|
{
|
||||||
|
reachedLimit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (EnemyBuckets.TryGetNextValue(out enemyIndex, ref iterator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d4cc79b0ecd0c248ac2028cd928dd7c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
using Unity.Burst;
|
||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Jobs;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace SepCore.Simulation
|
||||||
|
{
|
||||||
|
internal static class EnemyMovementSystem
|
||||||
|
{
|
||||||
|
// 读写集:R EnemyTransform / EnemyMoveInput / EnemyAttack;W EnemyTransform / EnemyBehavior
|
||||||
|
public static JobHandle Schedule(EnemyStore enemies, float deltaTime, float3 playerPosition,
|
||||||
|
JobHandle dependency)
|
||||||
|
{
|
||||||
|
int enemyCount = enemies.Count;
|
||||||
|
if (enemyCount == 0)
|
||||||
|
{
|
||||||
|
return dependency;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyMovementJob job = new EnemyMovementJob
|
||||||
|
{
|
||||||
|
Transforms = enemies.Transform.Data.AsArray(),
|
||||||
|
MoveInputs = enemies.MoveInput.Data.AsArray(),
|
||||||
|
Attacks = enemies.Attack.Data.AsArray(),
|
||||||
|
Behaviors = enemies.Behavior.Data.AsArray(),
|
||||||
|
DeltaTime = deltaTime,
|
||||||
|
PlayerPosition = playerPosition
|
||||||
|
};
|
||||||
|
return job.Schedule(enemyCount, 64, dependency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BurstCompile]
|
||||||
|
internal struct EnemyMovementJob : IJobParallelFor
|
||||||
|
{
|
||||||
|
public NativeArray<EnemyTransformSim> Transforms;
|
||||||
|
[ReadOnly] public NativeArray<EnemyMoveInputSim> MoveInputs;
|
||||||
|
[ReadOnly] public NativeArray<EnemyAttackSim> Attacks;
|
||||||
|
public NativeArray<EnemyBehaviorSim> Behaviors;
|
||||||
|
public float DeltaTime;
|
||||||
|
public float3 PlayerPosition;
|
||||||
|
|
||||||
|
public void Execute(int index)
|
||||||
|
{
|
||||||
|
EnemyMoveInputSim moveInput = MoveInputs[index];
|
||||||
|
EnemyAttackSim attack = Attacks[index];
|
||||||
|
EnemyTransformSim transform = Transforms[index];
|
||||||
|
|
||||||
|
float attackRange = attack.AttackRange > 0f ? attack.AttackRange : EnemyAttackSim.DefaultAttackRange;
|
||||||
|
float attackRangeSqr = attackRange * attackRange;
|
||||||
|
|
||||||
|
float3 currentPosition = transform.Position;
|
||||||
|
float3 horizontalPosition = new float3(currentPosition.x, 0f, currentPosition.z);
|
||||||
|
float3 toPlayer = PlayerPosition - horizontalPosition;
|
||||||
|
float sqrDistance = math.lengthsq(toPlayer);
|
||||||
|
bool isInAttackRange = sqrDistance <= attackRangeSqr;
|
||||||
|
bool canChase = !isInAttackRange && moveInput.Speed > 0f && sqrDistance > float.Epsilon;
|
||||||
|
|
||||||
|
float3 forward = transform.Forward;
|
||||||
|
float3 desiredPosition = currentPosition;
|
||||||
|
quaternion rotation = transform.Rotation;
|
||||||
|
|
||||||
|
if (canChase)
|
||||||
|
{
|
||||||
|
forward = math.normalizesafe(toPlayer, forward);
|
||||||
|
desiredPosition = currentPosition + forward * moveInput.Speed * DeltaTime;
|
||||||
|
if (math.lengthsq(forward) > float.Epsilon)
|
||||||
|
{
|
||||||
|
rotation = quaternion.LookRotationSafe(forward, math.up());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextState;
|
||||||
|
if (isInAttackRange)
|
||||||
|
{
|
||||||
|
nextState = EnemyBehaviorSim.StateInAttackRange;
|
||||||
|
}
|
||||||
|
else if (canChase)
|
||||||
|
{
|
||||||
|
nextState = EnemyBehaviorSim.StateChasing;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextState = EnemyBehaviorSim.StateIdle;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform.Position = desiredPosition;
|
||||||
|
transform.Forward = forward;
|
||||||
|
transform.Rotation = rotation;
|
||||||
|
Transforms[index] = transform;
|
||||||
|
Behaviors[index] = new EnemyBehaviorSim { State = nextState };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user