Checkpoint 1:

- 新增三类 SimData:
    - EnemySimData
    - ProjectileSimData
    - PickupSimData
- 新增 Tick 上下文:SimulationTickContext
- 新增双向索引绑定:EntityBinding
- 新增纯数据容器世界:SimulationWorld
This commit is contained in:
SepComet 2026-02-20 18:27:14 +08:00
parent ed3b37d0f7
commit 3b8e0731f0
14 changed files with 334 additions and 1 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7f519a6e4d7b4cb4ab5eabf95fb55e1b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
using UnityEngine;
namespace Simulation
{
public struct EnemySimData
{
public int EntityId;
public Vector3 Position;
public Vector3 Forward;
public float Speed;
public float AttackRange;
public int TargetType;
public int State;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ef2eea85685544189eef2d9f2cece080
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,80 @@
using System.Collections.Generic;
namespace Simulation
{
public sealed class EntityBinding
{
private readonly Dictionary<int, int> _entityIdToSimulationIndex = new Dictionary<int, int>();
private readonly Dictionary<int, int> _simulationIndexToEntityId = new Dictionary<int, int>();
public int Count => _entityIdToSimulationIndex.Count;
public void Bind(int entityId, int simulationIndex)
{
if (_entityIdToSimulationIndex.TryGetValue(entityId, out int oldSimulationIndex))
{
_simulationIndexToEntityId.Remove(oldSimulationIndex);
}
if (_simulationIndexToEntityId.TryGetValue(simulationIndex, out int oldEntityId))
{
_entityIdToSimulationIndex.Remove(oldEntityId);
}
_entityIdToSimulationIndex[entityId] = simulationIndex;
_simulationIndexToEntityId[simulationIndex] = entityId;
}
public void RemapIndex(int entityId, int newSimulationIndex)
{
if (!_entityIdToSimulationIndex.TryGetValue(entityId, out int oldSimulationIndex))
{
return;
}
_simulationIndexToEntityId.Remove(oldSimulationIndex);
_entityIdToSimulationIndex[entityId] = newSimulationIndex;
_simulationIndexToEntityId[newSimulationIndex] = entityId;
}
public bool TryGetSimulationIndex(int entityId, out int simulationIndex)
{
return _entityIdToSimulationIndex.TryGetValue(entityId, out simulationIndex);
}
public bool TryGetEntityId(int simulationIndex, out int entityId)
{
return _simulationIndexToEntityId.TryGetValue(simulationIndex, out entityId);
}
public bool UnbindByEntityId(int entityId)
{
if (!_entityIdToSimulationIndex.TryGetValue(entityId, out int simulationIndex))
{
return false;
}
_entityIdToSimulationIndex.Remove(entityId);
_simulationIndexToEntityId.Remove(simulationIndex);
return true;
}
public bool UnbindBySimulationIndex(int simulationIndex)
{
if (!_simulationIndexToEntityId.TryGetValue(simulationIndex, out int entityId))
{
return false;
}
_simulationIndexToEntityId.Remove(simulationIndex);
_entityIdToSimulationIndex.Remove(entityId);
return true;
}
public void Clear()
{
_entityIdToSimulationIndex.Clear();
_simulationIndexToEntityId.Clear();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4f6d93561db4b9092ab61182f2983d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@
using UnityEngine;
namespace Simulation
{
public struct PickupSimData
{
public int EntityId;
public Vector3 Position;
public float PickupRadius;
public int State;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ff6e8b75e4af4c3ca9e3b732b1383f95
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
using UnityEngine;
namespace Simulation
{
public struct ProjectileSimData
{
public int EntityId;
public int OwnerEntityId;
public Vector3 Position;
public Vector3 Forward;
public float Speed;
public float RemainingLifetime;
public int State;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2eec1f4389004d69bdce8c4dd95d255e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using UnityEngine;
namespace Simulation
{
public readonly struct SimulationTickContext
{
public SimulationTickContext(float deltaTime, float realDeltaTime, Vector3 playerPosition)
{
DeltaTime = deltaTime;
RealDeltaTime = realDeltaTime;
PlayerPosition = playerPosition;
}
public float DeltaTime { get; }
public float RealDeltaTime { get; }
public Vector3 PlayerPosition { get; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4c57fcf285f488b821c9141a6d0ad09
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,119 @@
using System.Collections.Generic;
namespace Simulation
{
public sealed class SimulationWorld
{
private readonly List<EnemySimData> _enemies = new List<EnemySimData>();
private readonly List<ProjectileSimData> _projectiles = new List<ProjectileSimData>();
private readonly List<PickupSimData> _pickups = new List<PickupSimData>();
public EntityBinding EnemyBinding { get; } = new EntityBinding();
public EntityBinding ProjectileBinding { get; } = new EntityBinding();
public EntityBinding PickupBinding { get; } = new EntityBinding();
public IReadOnlyList<EnemySimData> Enemies => _enemies;
public IReadOnlyList<ProjectileSimData> Projectiles => _projectiles;
public IReadOnlyList<PickupSimData> Pickups => _pickups;
public int AddEnemy(in EnemySimData simData)
{
int simulationIndex = _enemies.Count;
_enemies.Add(simData);
EnemyBinding.Bind(simData.EntityId, simulationIndex);
return simulationIndex;
}
public bool RemoveEnemyByEntityId(int entityId)
{
if (!EnemyBinding.TryGetSimulationIndex(entityId, out int simulationIndex))
{
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);
EnemyBinding.UnbindByEntityId(entityId);
return true;
}
public int AddProjectile(in ProjectileSimData simData)
{
int simulationIndex = _projectiles.Count;
_projectiles.Add(simData);
ProjectileBinding.Bind(simData.EntityId, simulationIndex);
return simulationIndex;
}
public 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;
}
public int AddPickup(in PickupSimData simData)
{
int simulationIndex = _pickups.Count;
_pickups.Add(simData);
PickupBinding.Bind(simData.EntityId, simulationIndex);
return simulationIndex;
}
public 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;
}
public void Tick(in SimulationTickContext context)
{
_ = context;
}
public void Clear()
{
_enemies.Clear();
_projectiles.Clear();
_pickups.Clear();
EnemyBinding.Clear();
ProjectileBinding.Clear();
PickupBinding.Clear();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a558ebbc9cb4d94946ac9f4f27914d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -25,7 +25,7 @@
- 以上问题修正后,核心流程可稳定连续跑 10 分钟无异常日志。 - 以上问题修正后,核心流程可稳定连续跑 10 分钟无异常日志。
## 2. P1 Simulation 分层(为 Job/Burst 做结构准备) ## 2. P1 Simulation 分层(为 Job/Burst 做结构准备)
- [ ] Checkpoint 1搭建 Simulation 基础骨架(仅新增,不改行为) - [x] Checkpoint 1搭建 Simulation 基础骨架(仅新增,不改行为)
- 新建目录:`Assets/GameMain/Scripts/Simulation`。 - 新建目录:`Assets/GameMain/Scripts/Simulation`。
- 新建 `SimulationWorld`,统一持有 `EnemySimData / ProjectileSimData / PickupSimData` 容器。 - 新建 `SimulationWorld`,统一持有 `EnemySimData / ProjectileSimData / PickupSimData` 容器。
- 新建 `EntityBinding`,维护 `EntityId <-> SimulationIndex` 双向映射。 - 新建 `EntityBinding`,维护 `EntityId <-> SimulationIndex` 双向映射。