简单重构拆分 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 等。
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
- 不做静态回退(不用默认值/兜底逻辑静默继续);异常路径直接 `Log.Error(...)` 抛出(UGF 的 `Log`,支持格式串,如 `Log.Error("Entity data is invalid.")`)。
|
||||
- UI 的序列化字段(`[SerializeField]` 及 View 生成的引用)**不做判空检查**,默认已在 Inspector 中赋值。
|
||||
- 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
|
||||
guid: 59ae2375305641a6a36d1bea0bf1abc2
|
||||
guid: 35d153869faa1494c9fbb7218bbdd5b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
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
|
||||
guid: b2c8675358384b21925e6f660ce61a79
|
||||
guid: 23b79d0c84ef6904588ff8da30bf4108
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
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
|
||||
guid: 57a3f582d9c04803908205e0cd4b5b75
|
||||
guid: f89e25340a7475048a94caab1c77e745
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
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
|
||||
|
||||
public int CollisionCandidateCount => _collisionCandidates.IsCreated ? _collisionCandidates.Length : 0;
|
||||
public int PendingAreaCollisionRequestCount => _areaCollisionRequests.Count;
|
||||
public int LastCollisionQueryCount => _lastCollisionQueryCount;
|
||||
public int LastProjectileCollisionQueryCount => _lastProjectileCollisionQueryCount;
|
||||
public int LastAreaCollisionQueryCount => _lastAreaCollisionQueryCount;
|
||||
public int LastCollisionCandidateCount => _lastCollisionCandidateCount;
|
||||
public int LastProjectileCollisionCandidateCount => _lastProjectileCollisionCandidateCount;
|
||||
public int LastAreaCollisionCandidateCount => _lastAreaCollisionCandidateCount;
|
||||
public int LastResolvedAreaHitCount => _lastResolvedAreaHitCount;
|
||||
public float LastCollisionCellSize => _lastCollisionCellSize;
|
||||
public bool LastCollisionHasEnemyTargets => _lastCollisionHasEnemyTargets;
|
||||
public SimulationStats Stats => new SimulationStats(
|
||||
_collisionCandidates.IsCreated ? _collisionCandidates.Length : 0,
|
||||
_areaCollisionRequests.Count,
|
||||
_lastCollisionQueryCount,
|
||||
_lastProjectileCollisionQueryCount,
|
||||
_lastAreaCollisionQueryCount,
|
||||
_lastCollisionCandidateCount,
|
||||
_lastProjectileCollisionCandidateCount,
|
||||
_lastAreaCollisionCandidateCount,
|
||||
_lastResolvedAreaHitCount,
|
||||
_lastCollisionCellSize,
|
||||
_lastCollisionHasEnemyTargets);
|
||||
|
||||
private void ResetCollisionRuntimeStats()
|
||||
{
|
||||
@@ -45,8 +46,8 @@ namespace SepCore.Simulation
|
||||
_enemyCollisionBuckets.Clear();
|
||||
}
|
||||
|
||||
private void AddProjectileCollisionQuery(int queryId, in ProjectileJobOutputData projectile, float radius,
|
||||
int maxTargets = 1)
|
||||
private void AddProjectileCollisionQuery(int queryId, in ProjectileTransformSim transform,
|
||||
in ProjectileIdentitySim identity, float radius, int maxTargets = 1)
|
||||
{
|
||||
if (!_collisionQueryInputs.IsCreated || radius <= 0f)
|
||||
{
|
||||
@@ -56,14 +57,14 @@ namespace SepCore.Simulation
|
||||
_collisionQueryInputs.Add(new CollisionQueryData
|
||||
{
|
||||
QueryId = queryId,
|
||||
SourceType = CollisionSourceTypeProjectile,
|
||||
SourceEntityId = projectile.EntityId,
|
||||
SourceOwnerEntityId = projectile.OwnerEntityId,
|
||||
SourceType = SimulationCollision.SourceTypeProjectile,
|
||||
SourceEntityId = identity.EntityId,
|
||||
SourceOwnerEntityId = identity.OwnerEntityId,
|
||||
SourceWasActiveAtQueryTime = true,
|
||||
Position = projectile.Position,
|
||||
Position = transform.Position,
|
||||
Radius = radius,
|
||||
MaxTargets = math.max(1, maxTargets),
|
||||
ShapeType = CollisionShapeCircle,
|
||||
ShapeType = SimulationCollision.ShapeCircle,
|
||||
Direction = new float3(0f, 0f, 1f),
|
||||
HalfAngleDeg = 180f,
|
||||
HalfWidth = 0f,
|
||||
@@ -94,7 +95,7 @@ namespace SepCore.Simulation
|
||||
_collisionQueryInputs.Add(new CollisionQueryData
|
||||
{
|
||||
QueryId = queryId,
|
||||
SourceType = CollisionSourceTypeArea,
|
||||
SourceType = SimulationCollision.SourceTypeArea,
|
||||
SourceEntityId = sourceEntityId,
|
||||
SourceOwnerEntityId = sourceOwnerEntityId,
|
||||
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
|
||||
{
|
||||
// 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 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 Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
{
|
||||
@@ -16,70 +15,44 @@ namespace SepCore.Simulation
|
||||
}
|
||||
|
||||
DisposeJobDataChannels();
|
||||
_enemyJobInputs = new NativeList<EnemyJobInputData>(64, Allocator.Persistent);
|
||||
_enemyJobOutputs = new NativeList<EnemyJobOutputData>(64, Allocator.Persistent);
|
||||
_enemyJobSeparationOutputs = new NativeList<EnemyJobOutputData>(64, Allocator.Persistent);
|
||||
_enemySeparationPreviousPushes = new NativeList<float2>(64, Allocator.Persistent);
|
||||
_enemySeparationCurrentPushes = new NativeList<float2>(64, Allocator.Persistent);
|
||||
_projectileJobInputs = new NativeList<ProjectileJobInputData>(64, Allocator.Persistent);
|
||||
_projectileJobOutputs = new NativeList<ProjectileJobOutputData>(64, Allocator.Persistent);
|
||||
_enemies.Initialize(64);
|
||||
_projectiles.Initialize(64);
|
||||
_pickups.Initialize(32);
|
||||
_enemySeparationTransforms = new NativeList<EnemyTransformSim>(64, Allocator.Persistent);
|
||||
_collectedPickups = new NativeList<int>(64, Allocator.Persistent);
|
||||
_enemySeparationBuckets = new NativeParallelMultiHashMap<long, int>(256, Allocator.Persistent);
|
||||
_collisionQueryInputs = new NativeList<CollisionQueryData>(64, 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);
|
||||
InitializeEnemyTargetSpatialIndex();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
_enemySeparationBuckets = default;
|
||||
|
||||
if (_collisionQueryInputs.IsCreated)
|
||||
{
|
||||
@@ -95,13 +68,6 @@ namespace SepCore.Simulation
|
||||
|
||||
_collisionCandidates = default;
|
||||
|
||||
if (_enemySeparationBuckets.IsCreated)
|
||||
{
|
||||
_enemySeparationBuckets.Dispose();
|
||||
}
|
||||
|
||||
_enemySeparationBuckets = default;
|
||||
|
||||
if (_enemyCollisionBuckets.IsCreated)
|
||||
{
|
||||
_enemyCollisionBuckets.Dispose();
|
||||
@@ -127,24 +93,14 @@ namespace SepCore.Simulation
|
||||
return;
|
||||
}
|
||||
|
||||
if (_enemyJobInputs.IsCreated)
|
||||
if (_enemySeparationTransforms.IsCreated)
|
||||
{
|
||||
_enemyJobInputs.Clear();
|
||||
_enemySeparationTransforms.Clear();
|
||||
}
|
||||
|
||||
if (_enemyJobOutputs.IsCreated)
|
||||
if (_collectedPickups.IsCreated)
|
||||
{
|
||||
_enemyJobOutputs.Clear();
|
||||
}
|
||||
|
||||
if (_projectileJobInputs.IsCreated)
|
||||
{
|
||||
_projectileJobInputs.Clear();
|
||||
}
|
||||
|
||||
if (_projectileJobOutputs.IsCreated)
|
||||
{
|
||||
_projectileJobOutputs.Clear();
|
||||
_collectedPickups.Clear();
|
||||
}
|
||||
|
||||
if (_collisionQueryInputs.IsCreated)
|
||||
@@ -157,21 +113,6 @@ namespace SepCore.Simulation
|
||||
_collisionCandidates.Clear();
|
||||
}
|
||||
|
||||
if (_enemyJobSeparationOutputs.IsCreated)
|
||||
{
|
||||
_enemyJobSeparationOutputs.Clear();
|
||||
}
|
||||
|
||||
if (_enemySeparationPreviousPushes.IsCreated)
|
||||
{
|
||||
_enemySeparationPreviousPushes.Clear();
|
||||
}
|
||||
|
||||
if (_enemySeparationCurrentPushes.IsCreated)
|
||||
{
|
||||
_enemySeparationCurrentPushes.Clear();
|
||||
}
|
||||
|
||||
if (_enemySeparationBuckets.IsCreated)
|
||||
{
|
||||
_enemySeparationBuckets.Clear();
|
||||
@@ -191,13 +132,11 @@ namespace SepCore.Simulation
|
||||
|
||||
private bool AreJobDataChannelsUsable()
|
||||
{
|
||||
return IsNativeListUsable(_enemyJobInputs) &&
|
||||
IsNativeListUsable(_enemyJobOutputs) &&
|
||||
IsNativeListUsable(_enemyJobSeparationOutputs) &&
|
||||
IsNativeListUsable(_enemySeparationPreviousPushes) &&
|
||||
IsNativeListUsable(_enemySeparationCurrentPushes) &&
|
||||
IsNativeListUsable(_projectileJobInputs) &&
|
||||
IsNativeListUsable(_projectileJobOutputs) &&
|
||||
return _enemies.IsCreated &&
|
||||
_projectiles.IsCreated &&
|
||||
_pickups.IsCreated &&
|
||||
IsNativeListUsable(_enemySeparationTransforms) &&
|
||||
IsNativeListUsable(_collectedPickups) &&
|
||||
IsNativeListUsable(_collisionQueryInputs) &&
|
||||
IsNativeListUsable(_collisionCandidates) &&
|
||||
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:
|
||||
+1
-4
@@ -1,8 +1,6 @@
|
||||
namespace SepCore.Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct AreaCollisionHitEventData
|
||||
internal struct AreaCollisionHitEventData
|
||||
{
|
||||
public int QueryId;
|
||||
public int SourceEntityId;
|
||||
@@ -11,4 +9,3 @@ namespace SepCore.Simulation
|
||||
public float SqrDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -2,9 +2,7 @@ using UnityEngine;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct AreaCollisionRequestData
|
||||
internal struct AreaCollisionRequestData
|
||||
{
|
||||
public int SourceEntityId;
|
||||
public int SourceOwnerEntityId;
|
||||
@@ -19,4 +17,3 @@ namespace SepCore.Simulation
|
||||
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
|
||||
+1
-4
@@ -1,8 +1,6 @@
|
||||
namespace SepCore.Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct CollisionCandidateData
|
||||
internal struct CollisionCandidateData
|
||||
{
|
||||
public int QueryId;
|
||||
public int SourceType;
|
||||
@@ -12,4 +10,3 @@ namespace SepCore.Simulation
|
||||
public float SqrDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -2,9 +2,7 @@ using Unity.Mathematics;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct CollisionQueryData
|
||||
internal struct CollisionQueryData
|
||||
{
|
||||
public int QueryId;
|
||||
public int SourceType;
|
||||
@@ -21,4 +19,3 @@ namespace SepCore.Simulation
|
||||
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.Entity;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -24,7 +21,7 @@ namespace SepCore.Simulation
|
||||
return;
|
||||
}
|
||||
|
||||
int projectileCount = _projectileJobOutputs.Length;
|
||||
int projectileCount = _projectiles.Count;
|
||||
int areaQueryCount = _areaCollisionRequests.Count;
|
||||
if (projectileCount == 0 && areaQueryCount == 0)
|
||||
{
|
||||
@@ -42,13 +39,15 @@ namespace SepCore.Simulation
|
||||
{
|
||||
for (int i = 0; i < projectileCount; i++)
|
||||
{
|
||||
ProjectileJobOutputData projectile = _projectileJobOutputs[i];
|
||||
if (!projectile.Active || projectile.State != ProjectileStateActive)
|
||||
if (!_projectiles.Life[i].Active)
|
||||
{
|
||||
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++;
|
||||
projectileQueryCount++;
|
||||
}
|
||||
@@ -80,14 +79,14 @@ namespace SepCore.Simulation
|
||||
_lastProjectileCollisionCandidateCount = 0;
|
||||
_lastAreaCollisionCandidateCount = 0;
|
||||
_lastCollisionCellSize = 0f;
|
||||
_lastCollisionHasEnemyTargets = _enemyJobOutputs.Length > 0;
|
||||
_lastCollisionHasEnemyTargets = _enemies.Count > 0;
|
||||
return;
|
||||
}
|
||||
|
||||
float autoCellSize = maxQueryRadius * 2f;
|
||||
float configuredCellSize = _projectileCollisionCellSize > 0f ? _projectileCollisionCellSize : autoCellSize;
|
||||
float cellSize = Mathf.Max(0.1f, configuredCellSize);
|
||||
bool hasEnemyTargets = _enemyJobOutputs.Length > 0;
|
||||
bool hasEnemyTargets = _enemies.Count > 0;
|
||||
_lastCollisionCellSize = cellSize;
|
||||
_lastCollisionHasEnemyTargets = hasEnemyTargets;
|
||||
|
||||
@@ -95,13 +94,17 @@ namespace SepCore.Simulation
|
||||
{
|
||||
if (hasEnemyTargets)
|
||||
{
|
||||
BuildEnemyCollisionBuckets(cellSize);
|
||||
CollisionQuerySystem.BuildEnemyCollisionBuckets(_enemies, _enemyCollisionBuckets, cellSize);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -120,78 +123,6 @@ namespace SepCore.Simulation
|
||||
_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)
|
||||
{
|
||||
projectileCandidateCount = 0;
|
||||
@@ -205,11 +136,11 @@ namespace SepCore.Simulation
|
||||
for (int i = 0; i < _collisionCandidates.Length; i++)
|
||||
{
|
||||
CollisionCandidateData candidate = _collisionCandidates[i];
|
||||
if (candidate.SourceType == CollisionSourceTypeProjectile)
|
||||
if (candidate.SourceType == SimulationCollision.SourceTypeProjectile)
|
||||
{
|
||||
projectileCandidateCount++;
|
||||
}
|
||||
else if (candidate.SourceType == CollisionSourceTypeArea)
|
||||
else if (candidate.SourceType == SimulationCollision.SourceTypeArea)
|
||||
{
|
||||
areaCandidateCount++;
|
||||
}
|
||||
|
||||
+3
-3
@@ -11,14 +11,14 @@ namespace SepCore.Simulation
|
||||
float radius, int maxTargets = 16)
|
||||
{
|
||||
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,
|
||||
float radius, in Vector3 direction, float halfAngleDeg, int maxTargets = 16)
|
||||
{
|
||||
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,
|
||||
@@ -28,7 +28,7 @@ namespace SepCore.Simulation
|
||||
float safeHalfLength = Mathf.Max(0.01f, halfLength);
|
||||
float boundingRadius = Mathf.Sqrt(safeHalfWidth * safeHalfWidth + safeHalfLength * safeHalfLength);
|
||||
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,
|
||||
|
||||
+22
-34
@@ -36,7 +36,7 @@ namespace SepCore.Simulation
|
||||
for (int i = 0; i < _collisionCandidates.Length; i++)
|
||||
{
|
||||
CollisionCandidateData candidate = _collisionCandidates[i];
|
||||
if (candidate.SourceType == CollisionSourceTypeProjectile)
|
||||
if (candidate.SourceType == SimulationCollision.SourceTypeProjectile)
|
||||
{
|
||||
int projectileEntityId = candidate.SourceEntityId;
|
||||
if (_projectileResolvedEntityIds.Contains(projectileEntityId))
|
||||
@@ -44,7 +44,7 @@ namespace SepCore.Simulation
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryGetActiveProjectileSimData(projectileEntityId, out _, out ProjectileSimData projectile))
|
||||
if (!TryGetActiveProjectileIndex(projectileEntityId, out int projectileIndex))
|
||||
{
|
||||
_projectileResolvedEntityIds.Add(projectileEntityId);
|
||||
continue;
|
||||
@@ -53,14 +53,15 @@ namespace SepCore.Simulation
|
||||
bool shouldExpireProjectile = true;
|
||||
bool shouldDispatchPresentation = false;
|
||||
int damage = 0;
|
||||
Vector3 hitPosition = projectile.Position;
|
||||
Vector3 projectilePosition = ToVector3(_projectiles.Transform[projectileIndex].Position);
|
||||
Vector3 hitPosition = projectilePosition;
|
||||
if (TryGetAliveTargetableEntity(candidate.TargetEntityId, out TargetableObject target))
|
||||
{
|
||||
EntityBase sourceEntity = TryGetEntityById(candidate.SourceEntityId);
|
||||
EntityBase ownerEntity = TryGetEntityById(candidate.SourceOwnerEntityId);
|
||||
shouldExpireProjectile = ResolveProjectileHitAgainstTarget(target, sourceEntity,
|
||||
ownerEntity,
|
||||
in projectile,
|
||||
in projectilePosition,
|
||||
out damage, out hitPosition, out shouldDispatchPresentation);
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ namespace SepCore.Simulation
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidate.SourceType == CollisionSourceTypeArea)
|
||||
if (candidate.SourceType == SimulationCollision.SourceTypeArea)
|
||||
{
|
||||
long dedupKey = (((long)candidate.QueryId) << 32) ^ (uint)candidate.TargetEntityId;
|
||||
if (!_areaCollisionHitDedupKeys.Add(dedupKey))
|
||||
@@ -112,11 +113,11 @@ namespace SepCore.Simulation
|
||||
|
||||
private bool ResolveProjectileHitAgainstTarget(TargetableObject target, EntityBase sourceEntity,
|
||||
EntityBase ownerEntity,
|
||||
in ProjectileSimData projectile, out int damage, out Vector3 hitPosition,
|
||||
in Vector3 projectilePosition, out int damage, out Vector3 hitPosition,
|
||||
out bool shouldDispatchPresentation)
|
||||
{
|
||||
damage = 0;
|
||||
hitPosition = projectile.Position;
|
||||
hitPosition = projectilePosition;
|
||||
shouldDispatchPresentation = false;
|
||||
|
||||
if (target == null || !target.Available || target.IsDead)
|
||||
@@ -167,7 +168,7 @@ namespace SepCore.Simulation
|
||||
{
|
||||
AreaCollisionHitEventData hitEvent = _areaCollisionHitEvents[i];
|
||||
if (!TryGetCollisionQueryByQueryId(hitEvent.QueryId, out CollisionQueryData query) ||
|
||||
query.SourceType != CollisionSourceTypeArea)
|
||||
query.SourceType != SimulationCollision.SourceTypeArea)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -238,9 +239,9 @@ namespace SepCore.Simulation
|
||||
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>();
|
||||
@@ -267,7 +268,7 @@ namespace SepCore.Simulation
|
||||
return false;
|
||||
}
|
||||
|
||||
if (query.ShapeType == CollisionShapeRectangle)
|
||||
if (query.ShapeType == SimulationCollision.ShapeRectangle)
|
||||
{
|
||||
Vector3 forwardRect = new Vector3(query.Direction.x, query.Direction.y, query.Direction.z);
|
||||
forwardRect.y = 0f;
|
||||
@@ -288,7 +289,7 @@ namespace SepCore.Simulation
|
||||
return Mathf.Abs(forwardDistance) <= halfLength && Mathf.Abs(lateralDistance) <= halfWidth;
|
||||
}
|
||||
|
||||
if (query.ShapeType != CollisionShapeSector)
|
||||
if (query.ShapeType != SimulationCollision.ShapeSector)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -314,47 +315,34 @@ namespace SepCore.Simulation
|
||||
return angle <= halfAngle;
|
||||
}
|
||||
|
||||
private bool TryGetActiveProjectileSimData(int projectileEntityId, out int simulationIndex,
|
||||
out ProjectileSimData projectile)
|
||||
private bool TryGetActiveProjectileIndex(int projectileEntityId, out int simulationIndex)
|
||||
{
|
||||
simulationIndex = -1;
|
||||
projectile = default;
|
||||
|
||||
if (!ProjectileBinding.TryGetSimulationIndex(projectileEntityId, out int foundIndex) || foundIndex < 0 ||
|
||||
foundIndex >= _projectiles.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectileSimData data = _projectiles[foundIndex];
|
||||
if (!data.Active || data.State != ProjectileStateActive)
|
||||
if (!_projectiles.TryGetSimulationIndex(projectileEntityId, out int foundIndex) ||
|
||||
!_projectiles.Life[foundIndex].Active)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
simulationIndex = foundIndex;
|
||||
projectile = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool MarkProjectileAsExpired(int projectileEntityId)
|
||||
{
|
||||
if (!ProjectileBinding.TryGetSimulationIndex(projectileEntityId, out int simulationIndex) ||
|
||||
simulationIndex < 0 || simulationIndex >= _projectiles.Count)
|
||||
if (!_projectiles.TryGetSimulationIndex(projectileEntityId, out int simulationIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectileSimData projectile = _projectiles[simulationIndex];
|
||||
projectile.Active = false;
|
||||
projectile.State = ProjectileStateExpired;
|
||||
projectile.RemainingLifetime = 0f;
|
||||
if (projectile.LifeTime > 0f && projectile.Age < projectile.LifeTime)
|
||||
ProjectileLifeSim life = _projectiles.Life[simulationIndex];
|
||||
life.Active = false;
|
||||
if (life.LifeTime > 0f && life.Age < life.LifeTime)
|
||||
{
|
||||
projectile.Age = projectile.LifeTime;
|
||||
life.Age = life.LifeTime;
|
||||
}
|
||||
|
||||
_projectiles[simulationIndex] = projectile;
|
||||
_projectiles.Life[simulationIndex] = life;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+173
-127
@@ -1,5 +1,5 @@
|
||||
using SepCore.Debugger;
|
||||
using Unity.Collections;
|
||||
using SepCore.Debugger;
|
||||
using SepCore.Entity;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
@@ -8,7 +8,36 @@ namespace SepCore.Simulation
|
||||
{
|
||||
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("敌人互斥参数")]
|
||||
[Tooltip("敌人互斥分桶使用的网格尺寸。小于等于 0 时,将根据敌人体积半径自动计算。")]
|
||||
[SerializeField]
|
||||
@@ -46,32 +75,31 @@ namespace SepCore.Simulation
|
||||
JobHandle enemySeparationHandle = default;
|
||||
bool hasEnemySeparationJob = false;
|
||||
bool hasEnemySeparationCandidates = false;
|
||||
int enemySeparationCount = 0;
|
||||
bool hasMovingEnemy = false;
|
||||
int enemyCount = 0;
|
||||
float enemySeparationMaxRadius = 0.45f;
|
||||
|
||||
// 2. BuildInput 阶段:
|
||||
// 把 _enemies/_projectiles 同步到 Native 输入,准备输出缓冲;
|
||||
// 统计是否需要敌人分离 Job;
|
||||
// 预估并准备碰撞查询/候选缓冲。
|
||||
// 扫描敌人组件统计是否需要分离系统以及碰撞查询容量。
|
||||
using (CustomProfilerMarker.TickEnemies_BuildInput.Auto())
|
||||
{
|
||||
SyncSimulationStateToJobInputs();
|
||||
int enemyCount = _enemyJobInputs.Length;
|
||||
int projectileCount = _projectileJobInputs.Length;
|
||||
PrepareEnemyJobOutputBuffer(enemyCount);
|
||||
PrepareProjectileJobOutputBuffer(projectileCount);
|
||||
enemyCount = _enemies.Count;
|
||||
|
||||
enemySeparationCount = enemyCount;
|
||||
for (int i = 0; i < enemyCount; i++)
|
||||
{
|
||||
EnemyJobInputData input = _enemyJobInputs[i];
|
||||
if (!input.AvoidEnemyOverlap)
|
||||
if (_enemies.MoveInput[i].Speed > 0f)
|
||||
{
|
||||
hasMovingEnemy = true;
|
||||
}
|
||||
|
||||
EnemyMoveConfigSim config = _enemies.MoveConfig[i];
|
||||
if (!config.AvoidOverlap)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
hasEnemySeparationCandidates = true;
|
||||
float radius = input.EnemyBodyRadius > 0f ? input.EnemyBodyRadius : 0.45f;
|
||||
float radius = config.BodyRadius > 0f ? config.BodyRadius : 0.45f;
|
||||
if (radius > enemySeparationMaxRadius)
|
||||
{
|
||||
enemySeparationMaxRadius = radius;
|
||||
@@ -90,34 +118,53 @@ namespace SepCore.Simulation
|
||||
int projectileExpectedCount = projectileQueryCount * Mathf.Max(1, _projectileMaxCandidatesPerQuery);
|
||||
int areaExpectedCount = EstimatePendingAreaCollisionCandidateCountFromRequests();
|
||||
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);
|
||||
|
||||
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())
|
||||
{
|
||||
enemyMovementHandle = ExecuteEnemyMovementJob(in context);
|
||||
projectileMovementHandle = ExecuteProjectileMovementJob(in context);
|
||||
float3 planarPlayerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
||||
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)
|
||||
// 如果有需要分离的敌人,调度:
|
||||
// BuildEnemySeparationBucketsBurstJob(依赖 EnemyMovement)
|
||||
// EnemySeparationBurstJob(依赖分桶 Job)
|
||||
// 然后把“敌人链路 handle”和“投射物移动 handle”合并。
|
||||
// 4. Schedule 阶段:可选敌人分离系统
|
||||
JobHandle simulationHandle;
|
||||
using (CustomProfilerMarker.TickEnemies_Schedule.Auto())
|
||||
{
|
||||
hasEnemySeparationJob = TryScheduleEnemySeparationFromJobOutput(
|
||||
hasEnemySeparationJob = TryScheduleEnemySeparationJob(
|
||||
in context,
|
||||
enemyMovementHandle,
|
||||
hasEnemySeparationCandidates,
|
||||
enemySeparationCount,
|
||||
enemyCount,
|
||||
enemySeparationMaxRadius,
|
||||
out enemySeparationHandle);
|
||||
JobHandle enemyHandle = hasEnemySeparationJob ? enemySeparationHandle : enemyMovementHandle;
|
||||
simulationHandle = JobHandle.CombineDependencies(enemyHandle, projectileMovementHandle);
|
||||
JobHandle movementHandle = JobHandle.CombineDependencies(enemyHandle, projectileMovementHandle);
|
||||
simulationHandle = JobHandle.CombineDependencies(movementHandle, pickupMovementHandle);
|
||||
}
|
||||
|
||||
// 5. Complete 阶段:等待上述 Job 全部完成。
|
||||
@@ -126,14 +173,19 @@ namespace SepCore.Simulation
|
||||
simulationHandle.Complete();
|
||||
}
|
||||
|
||||
// 6. 主线程后处理阶段:
|
||||
// - 把分离结果覆盖回敌人输出(如果有分离 Job)
|
||||
// - 构建碰撞候选(投射物查询 + area 请求查询 + 网格筛选)
|
||||
// 6. 主线程提交阶段:
|
||||
// - 分离结果写回敌人 Transform 组件
|
||||
// - 有位移时标记目标选择索引失效
|
||||
using (CustomProfilerMarker.TickEnemies_MainThreadCommit.Auto())
|
||||
{
|
||||
if (hasEnemySeparationJob)
|
||||
{
|
||||
CommitEnemySeparationFromJobOutput(enemySeparationCount);
|
||||
EnemySeparationSystem.Commit(_enemies, _enemySeparationTransforms, enemyCount);
|
||||
}
|
||||
|
||||
if (hasMovingEnemy || hasEnemySeparationJob)
|
||||
{
|
||||
MarkEnemyTargetSpatialIndexDirty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,137 +196,131 @@ namespace SepCore.Simulation
|
||||
}
|
||||
|
||||
// 7. MainThreadCommit 阶段
|
||||
// - ApplyJobOutputsToSimulationState(写回 _enemies/_projectiles)
|
||||
// - ResolveCollisionCandidatesOnMainThread(命中结算、事件、范围碰撞)
|
||||
// - RecycleInactiveAndExpiredProjectiles(隐藏并移除失效投射物)
|
||||
using (CustomProfilerMarker.TickEnemies_WriteBack.Auto())
|
||||
{
|
||||
using (CustomProfilerMarker.TickEnemies_MainThreadCommit.Auto())
|
||||
{
|
||||
ApplyJobOutputsToSimulationState();
|
||||
ResolveCollisionCandidatesOnMainThread();
|
||||
RecycleInactiveAndExpiredProjectiles();
|
||||
ResolveCollectedPickupsOnMainThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JobHandle ExecuteEnemyMovementJob(in SimulationTickContext context)
|
||||
private void ResolveCollectedPickupsOnMainThread()
|
||||
{
|
||||
int enemyCount = _enemyJobInputs.Length;
|
||||
if (enemyCount == 0)
|
||||
if (!_collectedPickups.IsCreated || _collectedPickups.Length == 0)
|
||||
{
|
||||
return default;
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.DeltaTime <= 0f)
|
||||
Player player = TryGetPlayer();
|
||||
for (int i = 0; i < _collectedPickups.Length; i++)
|
||||
{
|
||||
CopyEnemyInputsToOutputs();
|
||||
return default;
|
||||
int entityId = _collectedPickups[i];
|
||||
EntityBase entity = TryGetEntityById(entityId);
|
||||
if (entity == null || !entity.Available)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
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
|
||||
if (entity is CoinEntity coin)
|
||||
{
|
||||
Inputs = inputArray,
|
||||
Outputs = outputArray,
|
||||
DeltaTime = context.DeltaTime,
|
||||
PlayerPosition = playerPosition
|
||||
};
|
||||
return burstJob.Schedule(enemyCount, 64);
|
||||
if (player != null)
|
||||
{
|
||||
coin.TryCollect(player);
|
||||
}
|
||||
|
||||
private void CopyEnemyInputsToOutputs()
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < _enemyJobInputs.Length; i++)
|
||||
var entityComponent = GameEntry.Entity;
|
||||
if (entityComponent != null)
|
||||
{
|
||||
EnemyJobInputData input = _enemyJobInputs[i];
|
||||
_enemyJobOutputs[i] = new EnemyJobOutputData
|
||||
entityComponent.HideEntity(coin);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (entity is ExpEntity exp)
|
||||
{
|
||||
EntityId = input.EntityId,
|
||||
Position = input.Position,
|
||||
Forward = input.Forward,
|
||||
Rotation = input.Rotation,
|
||||
Speed = input.Speed,
|
||||
AttackRange = input.AttackRange,
|
||||
AvoidEnemyOverlap = input.AvoidEnemyOverlap,
|
||||
EnemyBodyRadius = input.EnemyBodyRadius,
|
||||
SeparationIterations = input.SeparationIterations,
|
||||
TargetType = input.TargetType,
|
||||
State = input.State
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryScheduleEnemySeparationFromJobOutput(in SimulationTickContext context, JobHandle dependency,
|
||||
_collectedPickups.Clear();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
separationHandle = dependency;
|
||||
if (enemyCount <= 0 || !hasSeparationCandidates)
|
||||
{
|
||||
separationHandle = dependency;
|
||||
return false;
|
||||
}
|
||||
|
||||
float autoCellSize = maxRadius * 2f;
|
||||
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);
|
||||
float pushDamping = Mathf.Clamp(_enemySeparationPushDamping, 0f, 2f);
|
||||
float maxStepScale = Mathf.Max(0.1f, _enemySeparationMaxStepScale);
|
||||
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);
|
||||
separationHandle = EnemySeparationSystem.Schedule(_enemies, _enemySeparationBuckets,
|
||||
_enemySeparationTransforms, in settings, playerPosition, dependency);
|
||||
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 Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
{
|
||||
@@ -16,45 +13,6 @@ namespace SepCore.Simulation
|
||||
[SerializeField]
|
||||
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
|
||||
|
||||
private void RecycleInactiveAndExpiredProjectiles()
|
||||
@@ -62,13 +20,13 @@ namespace SepCore.Simulation
|
||||
_projectileRecycleEntityIds.Clear();
|
||||
for (int i = 0; i < _projectiles.Count; i++)
|
||||
{
|
||||
ProjectileSimData projectile = _projectiles[i];
|
||||
if (!ShouldRecycleProjectileSimData(projectile))
|
||||
ProjectileLifeSim life = _projectiles.Life[i];
|
||||
if (life.Active && (life.LifeTime <= 0f || life.Age < life.LifeTime))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_projectileRecycleEntityIds.Add(projectile.EntityId);
|
||||
_projectileRecycleEntityIds.Add(_projectiles.Identity[i].EntityId);
|
||||
}
|
||||
|
||||
if (_projectileRecycleEntityIds.Count == 0)
|
||||
@@ -91,21 +49,6 @@ namespace SepCore.Simulation
|
||||
_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
|
||||
}
|
||||
}
|
||||
|
||||
+26
-13
@@ -32,40 +32,53 @@ namespace SepCore.Simulation
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_world.TryGetEnemyData(enemyEntity.Id, out EnemySimData enemyData))
|
||||
if (!_world._enemies.TryGetSimulationIndex(enemyEntity.Id, out int simulationIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyEnemyPresentation(enemyEntity, enemyData);
|
||||
EnemyTransformSim enemyData = _world._enemies.Transform[simulationIndex];
|
||||
ApplyEnemyPresentation(enemyEntity, in enemyData);
|
||||
}
|
||||
}
|
||||
|
||||
var projectiles = _world._projectiles;
|
||||
for (int i = 0; i < projectiles.Count; i++)
|
||||
{
|
||||
ProjectileSimData projectileData = projectiles[i];
|
||||
if (!projectileData.Active || projectileData.State != ProjectileStateActive)
|
||||
if (!projectiles.Life[i].Active)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EntityBase projectileEntity = TryGetEntityById(projectileData.EntityId);
|
||||
EntityBase projectileEntity = TryGetEntityById(projectiles.Identity[i].EntityId);
|
||||
if (projectileEntity == null || !projectileEntity.Available)
|
||||
{
|
||||
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;
|
||||
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) +
|
||||
Mathf.Abs(rotation.w);
|
||||
if (rotationMagnitude > float.Epsilon)
|
||||
@@ -74,7 +87,7 @@ namespace SepCore.Simulation
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 forward = enemyData.Forward;
|
||||
Vector3 forward = ToVector3(enemyData.Forward);
|
||||
forward.y = 0f;
|
||||
if (forward.sqrMagnitude > float.Epsilon)
|
||||
{
|
||||
@@ -83,7 +96,7 @@ namespace SepCore.Simulation
|
||||
}
|
||||
|
||||
private static void ApplyProjectilePresentation(EntityBase projectileEntity,
|
||||
in ProjectileSimData projectileData)
|
||||
in ProjectileTransformSim projectileData)
|
||||
{
|
||||
Transform projectileTransform = projectileEntity.CachedTransform;
|
||||
if (projectileTransform == null)
|
||||
@@ -91,8 +104,8 @@ namespace SepCore.Simulation
|
||||
return;
|
||||
}
|
||||
|
||||
projectileTransform.position = projectileData.Position;
|
||||
Vector3 forward = projectileData.Forward;
|
||||
projectileTransform.position = ToVector3(projectileData.Position);
|
||||
Vector3 forward = ToVector3(projectileData.Forward);
|
||||
if (forward.sqrMagnitude <= float.Epsilon)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace SepCore.Simulation
|
||||
public bool AvoidEnemyOverlap;
|
||||
public float EnemyBodyRadius;
|
||||
public int SeparationIterations;
|
||||
public int TargetType;
|
||||
public int State;
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -9,11 +9,8 @@ namespace SepCore.Simulation
|
||||
public Vector3 Position;
|
||||
public Vector3 Forward;
|
||||
public Vector3 Velocity;
|
||||
public float Speed;
|
||||
public float LifeTime;
|
||||
public float Age;
|
||||
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 SimulationTickContext(float deltaTime, float realDeltaTime, Vector3 playerPosition)
|
||||
public SimulationTickContext(float deltaTime, Vector3 playerPosition)
|
||||
{
|
||||
DeltaTime = deltaTime;
|
||||
RealDeltaTime = realDeltaTime;
|
||||
PlayerPosition = playerPosition;
|
||||
}
|
||||
|
||||
public float DeltaTime { get; }
|
||||
public float RealDeltaTime { get; }
|
||||
public Vector3 PlayerPosition { get; }
|
||||
}
|
||||
}
|
||||
|
||||
+29
-9
@@ -1,5 +1,6 @@
|
||||
using SepCore.Components;
|
||||
using SepCore.Entity;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
@@ -25,7 +26,7 @@ namespace SepCore.Simulation
|
||||
|
||||
float attackRange = enemy.AttackRange > 0f
|
||||
? enemy.AttackRange
|
||||
: DefaultAttackRange;
|
||||
: EnemyAttackSim.DefaultAttackRange;
|
||||
|
||||
return new EnemySimData
|
||||
{
|
||||
@@ -38,8 +39,7 @@ namespace SepCore.Simulation
|
||||
AvoidEnemyOverlap = movementComponent != null && movementComponent.AvoidEnemyOverlap,
|
||||
EnemyBodyRadius = movementComponent != null ? movementComponent.EnemyBodyRadius : 0.45f,
|
||||
SeparationIterations = movementComponent != null ? movementComponent.SeparationIterations : 2,
|
||||
TargetType = 0,
|
||||
State = EnemyStateIdle
|
||||
State = EnemyBehaviorSim.StateIdle
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,12 +83,9 @@ namespace SepCore.Simulation
|
||||
Position = projectileEntity.CachedTransform.position,
|
||||
Forward = forward,
|
||||
Velocity = velocity,
|
||||
Speed = speed,
|
||||
LifeTime = lifeTime,
|
||||
Age = 0f,
|
||||
Active = true,
|
||||
RemainingLifetime = lifeTime,
|
||||
State = ProjectileStateActive
|
||||
Active = true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -98,11 +95,34 @@ namespace SepCore.Simulation
|
||||
{
|
||||
EntityId = pickupEntity.Id,
|
||||
Position = pickupEntity.CachedTransform.position,
|
||||
PickupRadius = 0.35f,
|
||||
State = 0
|
||||
PickupRadius = 0.35f
|
||||
};
|
||||
}
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
private Vector3 ResolvePlayerPositionForTick(in SimulationTickContext context)
|
||||
private Vector3 ResolvePlayerPositionForTick(Vector3 fallbackPlayerPosition, float deltaTime)
|
||||
{
|
||||
if (_playerMovementTransform == null)
|
||||
{
|
||||
return context.PlayerPosition;
|
||||
return fallbackPlayerPosition;
|
||||
}
|
||||
|
||||
if (!_playerMovementActive || _playerMovementSpeed <= 0f ||
|
||||
_playerMovementDirection.sqrMagnitude <= Mathf.Epsilon || context.DeltaTime <= 0f)
|
||||
_playerMovementDirection.sqrMagnitude <= Mathf.Epsilon || deltaTime <= 0f)
|
||||
{
|
||||
_playerMovementPosition = _playerMovementTransform.position;
|
||||
return _playerMovementPosition;
|
||||
}
|
||||
|
||||
_playerMovementPosition += _playerMovementDirection * (_playerMovementSpeed * context.DeltaTime);
|
||||
_playerMovementPosition += _playerMovementDirection * (_playerMovementSpeed * deltaTime);
|
||||
_playerMovementPosition.y = _playerMovementTransform.position.y;
|
||||
_playerMovementTransform.position = _playerMovementPosition;
|
||||
return _playerMovementPosition;
|
||||
|
||||
+10
-28
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
@@ -17,23 +16,14 @@ namespace SepCore.Simulation
|
||||
private readonly CollisionPipelineRuntimeState _collisionPipelineRuntime = new();
|
||||
private readonly TargetSelectionRuntimeState _targetSelectionRuntime = new();
|
||||
|
||||
private List<EnemySimData> _enemies => _simulationState.Enemies;
|
||||
private List<ProjectileSimData> _projectiles => _simulationState.Projectiles;
|
||||
private List<PickupSimData> _pickups => _simulationState.Pickups;
|
||||
private EnemyStore _enemies => _simulationState.Enemies;
|
||||
private ProjectileStore _projectiles => _simulationState.Projectiles;
|
||||
private PickupStore _pickups => _simulationState.Pickups;
|
||||
private List<int> _projectileRecycleEntityIds => _simulationState.ProjectileRecycleEntityIds;
|
||||
private HashSet<int> _projectileResolvedEntityIds => _collisionPipelineRuntime.ProjectileResolvedEntityIds;
|
||||
|
||||
private EntityBinding EnemyBinding => _simulationState.EnemyBinding;
|
||||
private EntityBinding ProjectileBinding => _simulationState.ProjectileBinding;
|
||||
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<EnemyTransformSim> _enemySeparationTransforms => ref _jobDataRuntime.EnemySeparationTransforms;
|
||||
private ref NativeList<int> _collectedPickups => ref _jobDataRuntime.CollectedPickups;
|
||||
private ref NativeList<CollisionCandidateData> _collisionCandidates => ref _jobDataRuntime.CollisionCandidates;
|
||||
private ref NativeParallelMultiHashMap<long, int> _enemySeparationBuckets => ref _jobDataRuntime.EnemySeparationBuckets;
|
||||
private ref NativeParallelMultiHashMap<long, int> _enemyCollisionBuckets => ref _jobDataRuntime.EnemyCollisionBuckets;
|
||||
@@ -101,24 +91,16 @@ namespace SepCore.Simulation
|
||||
|
||||
private sealed class SimulationStateStore
|
||||
{
|
||||
public readonly List<EnemySimData> Enemies = new();
|
||||
public readonly List<ProjectileSimData> Projectiles = new();
|
||||
public readonly List<PickupSimData> Pickups = new();
|
||||
public readonly EnemyStore Enemies = new();
|
||||
public readonly ProjectileStore Projectiles = new();
|
||||
public readonly PickupStore Pickups = 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
|
||||
{
|
||||
public NativeList<EnemyJobInputData> EnemyJobInputs;
|
||||
public NativeList<EnemyJobOutputData> EnemyJobOutputs;
|
||||
public NativeList<EnemyJobOutputData> EnemyJobSeparationOutputs;
|
||||
public NativeList<float2> EnemySeparationPreviousPushes;
|
||||
public NativeList<float2> EnemySeparationCurrentPushes;
|
||||
public NativeList<ProjectileJobInputData> ProjectileJobInputs;
|
||||
public NativeList<ProjectileJobOutputData> ProjectileJobOutputs;
|
||||
public NativeList<EnemyTransformSim> EnemySeparationTransforms;
|
||||
public NativeList<int> CollectedPickups;
|
||||
public NativeList<CollisionCandidateData> CollisionCandidates;
|
||||
public NativeParallelMultiHashMap<long, int> EnemySeparationBuckets;
|
||||
public NativeParallelMultiHashMap<long, int> EnemyCollisionBuckets;
|
||||
|
||||
+212
-104
@@ -1,4 +1,5 @@
|
||||
using SepCore.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
{
|
||||
@@ -16,11 +17,8 @@ namespace SepCore.Simulation
|
||||
_areaCollisionRequests.Clear();
|
||||
_areaCollisionHitEvents.Clear();
|
||||
_areaCollisionHitDedupKeys.Clear();
|
||||
_fixedStepAccumulator = 0d;
|
||||
ClearJobDataChannels();
|
||||
|
||||
EnemyBinding.Clear();
|
||||
ProjectileBinding.Clear();
|
||||
PickupBinding.Clear();
|
||||
ClearPlayerMovementState();
|
||||
}
|
||||
|
||||
@@ -28,46 +26,96 @@ namespace SepCore.Simulation
|
||||
|
||||
#region Enemy Simulation State
|
||||
|
||||
public int EnemyCount => _enemies.Count;
|
||||
|
||||
public bool TryGetEnemySnapshot(int entityId, out EnemySimData snapshot)
|
||||
{
|
||||
if (!_enemies.TryGetSimulationIndex(entityId, out int index))
|
||||
{
|
||||
snapshot = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
int simulationIndex = _enemies.Count;
|
||||
_enemies.Add(simData);
|
||||
EnemyBinding.Bind(simData.EntityId, simulationIndex);
|
||||
OnEnemyAddedToSeparationTemporalBuffers();
|
||||
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();
|
||||
return simulationIndex;
|
||||
return index;
|
||||
}
|
||||
|
||||
private int UpsertEnemy(in EnemySimData simData)
|
||||
internal int UpsertEnemy(in EnemySimData simData)
|
||||
{
|
||||
if (!EnemyBinding.TryGetSimulationIndex(simData.EntityId, out int simulationIndex))
|
||||
if (!_enemies.TryGetSimulationIndex(simData.EntityId, out int index))
|
||||
{
|
||||
return AddEnemy(simData);
|
||||
return AddEnemy(in simData);
|
||||
}
|
||||
|
||||
_enemies[simulationIndex] = 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 simulationIndex;
|
||||
return index;
|
||||
}
|
||||
|
||||
private bool RemoveEnemyByEntityId(int entityId)
|
||||
{
|
||||
if (!EnemyBinding.TryGetSimulationIndex(entityId, out int simulationIndex))
|
||||
if (!_enemies.RemoveByEntityId(entityId))
|
||||
{
|
||||
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();
|
||||
return true;
|
||||
}
|
||||
@@ -88,85 +136,122 @@ namespace SepCore.Simulation
|
||||
RemoveEnemyByEntityId(entityId);
|
||||
}
|
||||
|
||||
private bool TryGetEnemyData(int entityId, out EnemySimData enemyData)
|
||||
{
|
||||
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,
|
||||
public void SyncEnemyMovementInput(int entityId, bool isMoving, in Vector3 direction, float speed,
|
||||
bool avoidEnemyOverlap, float enemyBodyRadius, int separationIterations)
|
||||
{
|
||||
if (!EnemyBinding.TryGetSimulationIndex(entityId, out int simulationIndex) || simulationIndex < 0 ||
|
||||
simulationIndex >= _enemies.Count)
|
||||
if (!_enemies.TryGetSimulationIndex(entityId, out int index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnemySimData enemyData = _enemies[simulationIndex];
|
||||
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)
|
||||
_enemies.MoveInput[index] = new EnemyMoveInputSim
|
||||
{
|
||||
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
|
||||
|
||||
#region Projectile Simulation State
|
||||
|
||||
public int ProjectileCount => _projectiles.Count;
|
||||
|
||||
public bool TryGetProjectileSnapshot(int entityId, out ProjectileSimData snapshot)
|
||||
{
|
||||
if (!_projectiles.TryGetSimulationIndex(entityId, out int index))
|
||||
{
|
||||
snapshot = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectileIdentitySim identity = _projectiles.Identity[index];
|
||||
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)
|
||||
{
|
||||
int simulationIndex = _projectiles.Count;
|
||||
_projectiles.Add(simData);
|
||||
ProjectileBinding.Bind(simData.EntityId, simulationIndex);
|
||||
return simulationIndex;
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
private int UpsertProjectile(in ProjectileSimData simData)
|
||||
internal int UpsertProjectile(in ProjectileSimData simData)
|
||||
{
|
||||
if (!ProjectileBinding.TryGetSimulationIndex(simData.EntityId, out int simulationIndex))
|
||||
if (!_projectiles.TryGetSimulationIndex(simData.EntityId, out int index))
|
||||
{
|
||||
return AddProjectile(simData);
|
||||
return AddProjectile(in simData);
|
||||
}
|
||||
|
||||
_projectiles[simulationIndex] = simData;
|
||||
return simulationIndex;
|
||||
_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)
|
||||
{
|
||||
if (!ProjectileBinding.TryGetSimulationIndex(entityId, out int simulationIndex))
|
||||
{
|
||||
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;
|
||||
return _projectiles.RemoveByEntityId(entityId);
|
||||
}
|
||||
|
||||
private void RegisterProjectileLifecycle(EntityBase projectileEntity, object userData)
|
||||
@@ -188,43 +273,66 @@ namespace SepCore.Simulation
|
||||
|
||||
#region Pickup Simulation State
|
||||
|
||||
public int PickupCount => _pickups.Count;
|
||||
|
||||
public bool TryGetPickupSnapshot(int entityId, out PickupSimData snapshot)
|
||||
{
|
||||
if (!_pickups.TryGetSimulationIndex(entityId, out int index))
|
||||
{
|
||||
snapshot = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
PickupTransformSim transform = _pickups.Transform[index];
|
||||
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)
|
||||
{
|
||||
int simulationIndex = _pickups.Count;
|
||||
_pickups.Add(simData);
|
||||
PickupBinding.Bind(simData.EntityId, simulationIndex);
|
||||
return simulationIndex;
|
||||
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 });
|
||||
}
|
||||
|
||||
private int UpsertPickup(in PickupSimData simData)
|
||||
internal int UpsertPickup(in PickupSimData simData)
|
||||
{
|
||||
if (!PickupBinding.TryGetSimulationIndex(simData.EntityId, out int simulationIndex))
|
||||
if (!_pickups.TryGetSimulationIndex(simData.EntityId, out int index))
|
||||
{
|
||||
return AddPickup(simData);
|
||||
return AddPickup(in simData);
|
||||
}
|
||||
|
||||
_pickups[simulationIndex] = simData;
|
||||
return simulationIndex;
|
||||
_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)
|
||||
{
|
||||
if (!PickupBinding.TryGetSimulationIndex(entityId, out int simulationIndex))
|
||||
{
|
||||
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;
|
||||
return _pickups.RemoveByEntityId(entityId);
|
||||
}
|
||||
|
||||
private void RegisterPickupLifecycle(EntityBase pickupEntity)
|
||||
|
||||
+13
-22
@@ -1,5 +1,6 @@
|
||||
using SepCore.Debugger;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
@@ -17,8 +18,8 @@ namespace SepCore.Simulation
|
||||
BuildEnemyTargetSpatialIndexIfNeeded();
|
||||
|
||||
float cellSize = GetTargetSelectionCellSize();
|
||||
int centerCellX = ToCell(origin.x, cellSize);
|
||||
int centerCellZ = ToCell(origin.z, cellSize);
|
||||
int centerCellX = SimulationCell.ToCell(origin.x, cellSize);
|
||||
int centerCellZ = SimulationCell.ToCell(origin.z, cellSize);
|
||||
float range = Mathf.Sqrt(maxSqrRange);
|
||||
int queryRange = Mathf.Max(1, Mathf.CeilToInt(range / cellSize));
|
||||
|
||||
@@ -31,7 +32,7 @@ namespace SepCore.Simulation
|
||||
{
|
||||
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,
|
||||
out NativeParallelMultiHashMapIterator<long> iterator))
|
||||
{
|
||||
@@ -45,17 +46,17 @@ namespace SepCore.Simulation
|
||||
continue;
|
||||
}
|
||||
|
||||
EnemySimData enemy = _enemies[enemyIndex];
|
||||
Vector3 delta = enemy.Position - origin;
|
||||
delta.y = 0f;
|
||||
float sqrDistance = delta.sqrMagnitude;
|
||||
float3 enemyPosition = _enemies.Transform[enemyIndex].Position;
|
||||
float deltaX = enemyPosition.x - origin.x;
|
||||
float deltaZ = enemyPosition.z - origin.z;
|
||||
float sqrDistance = deltaX * deltaX + deltaZ * deltaZ;
|
||||
if (sqrDistance >= minSqrDistance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
minSqrDistance = sqrDistance;
|
||||
enemyEntityId = enemy.EntityId;
|
||||
enemyEntityId = _enemies.Identity[enemyIndex].EntityId;
|
||||
found = true;
|
||||
} while (_enemyTargetBuckets.TryGetNextValue(out enemyIndex, ref iterator));
|
||||
}
|
||||
@@ -125,10 +126,10 @@ namespace SepCore.Simulation
|
||||
|
||||
for (int i = 0; i < enemyCount; i++)
|
||||
{
|
||||
EnemySimData enemy = _enemies[i];
|
||||
int cellX = ToCell(enemy.Position.x, cellSize);
|
||||
int cellZ = ToCell(enemy.Position.z, cellSize);
|
||||
_enemyTargetBuckets.Add(CellKey(cellX, cellZ), i);
|
||||
float3 enemyPosition = _enemies.Transform[i].Position;
|
||||
int cellX = SimulationCell.ToCell(enemyPosition.x, cellSize);
|
||||
int cellZ = SimulationCell.ToCell(enemyPosition.z, cellSize);
|
||||
_enemyTargetBuckets.Add(SimulationCell.Key(cellX, cellZ), i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,15 +140,5 @@ namespace SepCore.Simulation
|
||||
{
|
||||
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 UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld : GameFrameworkComponent
|
||||
public sealed partial class SimulationWorld : GameFrameworkComponent, ISimulationWorld
|
||||
{
|
||||
// Partial layout:
|
||||
// - SimulationWorld.cs: 鏍稿績鐘舵€併€佸父閲忓拰 Unity 鐢熷懡鍛ㄦ湡鍏ュ彛鐐广€?
|
||||
// - SimulationWorld.RuntimeModules.cs: 杩愯鏃跺煙瀵硅薄銆侀厤缃拰鐘舵€佷唬鐞嗐€?
|
||||
// - SimulationWorld.SimEntityState.cs: 妯℃嫙鐘舵€佺殑澧炲垹鏀规煡鍜岀敓鍛藉懆鏈熸敞鍐屻€?
|
||||
// - SimulationWorld.EntityToSimData.cs: Unity 瀹炰綋鍒?sim data 鐨勫垵濮嬪寲閫傞厤銆?
|
||||
// - SimulationWorld.EntitySync.cs: GameFramework 瀹炰綋 show/hide 浜嬩欢妗ャ€?
|
||||
// - SimulationWorld.TargetSelectionSpatialIndex.cs: 鏈€杩戞晫绌洪棿绱㈠紩鏌ヨ銆?
|
||||
// - Presentation/SimulationWorld.TransformSync.cs: late-update transform 鍚屾妗ャ€?
|
||||
// - Presentation/SimulationWorld.HitPresentation.cs: 鎶曞皠鐗╁懡涓簨浠惰〃鐜版ˉ銆?
|
||||
// - DataChannel/SimulationWorld.JobDataChannel.cs: Job 閫氶亾鍏变韩瀛楁銆佸父閲忓拰杩愯鏃剁姸鎬併€?
|
||||
// - DataChannel/SimulationWorld.JobDataLifecycle.cs: Native 閫氶亾鍒濆鍖栥€佹竻鐞嗗拰 clear銆?
|
||||
// - DataChannel/SimulationWorld.JobDataConversion.cs: sim/job 鏁版嵁杞崲涓庤緭鍏ヨ緭鍑虹紦鍐插噯澶囥€?
|
||||
// - DataChannel/SimulationWorld.CollisionTransient.cs: 纰版挒涓存椂閫氶亾鍜岃繍琛屾椂缁熻銆?
|
||||
// - DataChannel/SimulationWorld.EnemySeparationTemporal.cs: 鏁屼汉鍒嗙鐨勫抚闂翠复鏃剁姸鎬併€?
|
||||
// - DataChannel/SimulationWorld.JobOutputCommit.cs: Job 杈撳嚭鍥炲啓涓诲鍣ㄣ€?
|
||||
// - Jobs/SimulationWorld.EnemyJobs.cs: 妯℃嫙閫氶亾 缂栨帓 + 鏁屼汉绉诲姩/鍒嗙 椤哄簭鎵ц
|
||||
// - Jobs/SimulationWorld.ProjectileJobs.cs: 鎶曞皠鐗╃Щ鍔ㄤ笌鍥炴敹
|
||||
// - Jobs/SimulationWorld.CollisionPipeline.cs: 纰版挒绠$嚎鍏变韩閰嶇疆鍜岀姸鎬?
|
||||
// - Jobs/SimulationWorld.CollisionRequests.cs: area/sector 璇锋眰缂撳啿
|
||||
// - Jobs/SimulationWorld.CollisionBroadPhase.cs: broad-phase 鍊欓€夋瀯寤哄拰 Job 璋冨害
|
||||
// - Jobs/SimulationWorld.CollisionResolve.cs: 涓荤嚎绋嬪懡涓粨绠椾笌 area settle
|
||||
// - Jobs/SimulationWorld.CollisionPresentation.cs: 鍛戒腑琛ㄧ幇浜嬩欢鍜屽疄浣?impact 瑙f瀽
|
||||
// - JobStruct/*.cs: burst job 鍐呮牳鍜岄潰鍚?job 鐨勬暟鎹粨鏋?
|
||||
private const float DefaultAttackRange = 1f;
|
||||
private const int EnemyStateIdle = 0;
|
||||
private const int EnemyStateChasing = 1;
|
||||
private const int EnemyStateInAttackRange = 2;
|
||||
private const int ProjectileStateActive = 0;
|
||||
private const int ProjectileStateExpired = 1;
|
||||
// - SimulationWorld.cs: 核心状态、常量和 Unity 生命周期入口点。
|
||||
// - SimulationWorld.RuntimeModules.cs: 运行时域对象、配置和状态代理。
|
||||
// - SimulationWorld.SimEntityState.cs: 模拟状态的增删改查和生命周期注册。
|
||||
// - SimulationWorld.EntityToSimData.cs: Unity 实体与组件列/快照之间的边界转换。
|
||||
// - SimulationWorld.EntitySync.cs: GameFramework 实体 show/hide 事件桥。
|
||||
// - SimulationWorld.TargetSelectionSpatialIndex.cs: 最近敌空间索引查询。
|
||||
// - SimulationWorld.PlayerMovement.cs: 玩家模拟移动输入与步进。
|
||||
// - Components/*: 敌人/投射物/掉落物 SoA 组件列与存储。
|
||||
// - Presentation/SimulationWorld.TransformSync.cs: late-update transform 同步桥。
|
||||
// - Presentation/SimulationWorld.HitPresentation.cs: 投射物命中事件表现桥。
|
||||
// - DataChannel/*: Job 通道与帧间临时状态。
|
||||
// - Systems/*: 独立数据并行系统(移动/分离/碰撞查询)。
|
||||
// - Jobs/*: 模拟管线编排、碰撞调度与结算。
|
||||
// - JobStruct/*: 面向 job 的瞬时数据结构。
|
||||
// 模拟数据以组件列(SoA)为唯一存储,XxxSimData 只作为生成/查询边界的快照结构。
|
||||
private const float MinFixedStepSeconds = 0.0001f;
|
||||
private const double FixedStepEpsilonScale = 1e-4;
|
||||
|
||||
[Header("固定步长模拟")]
|
||||
[Tooltip("开启后按固定步长累积时间,一帧内可能执行多次模拟步进。")]
|
||||
[SerializeField]
|
||||
private bool _fixedStepEnabled = true;
|
||||
|
||||
[Tooltip("固定步长(秒)。")]
|
||||
[SerializeField]
|
||||
private float _fixedStepSeconds = 1f / 60f;
|
||||
|
||||
private double _fixedStepAccumulator;
|
||||
|
||||
private EntitySync _entitySync;
|
||||
private TransformSync _transformSync;
|
||||
private HitPresentation _hitPresentation;
|
||||
|
||||
public IReadOnlyList<EnemySimData> Enemies => _enemies;
|
||||
public IReadOnlyList<ProjectileSimData> Projectiles => _projectiles;
|
||||
public IReadOnlyList<PickupSimData> Pickups => _pickups;
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
protected override void Awake()
|
||||
@@ -64,9 +59,30 @@ namespace SepCore.Simulation
|
||||
|
||||
public void Tick(in SimulationTickContext context)
|
||||
{
|
||||
Vector3 playerPosition = ResolvePlayerPositionForTick(in context);
|
||||
SimulationTickContext resolvedContext =
|
||||
new SimulationTickContext(context.DeltaTime, context.RealDeltaTime, playerPosition);
|
||||
if (!_fixedStepEnabled)
|
||||
{
|
||||
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())
|
||||
{
|
||||
TickSimulationPipeline(in resolvedContext);
|
||||
@@ -91,4 +107,3 @@ namespace SepCore.Simulation
|
||||
#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