简单重构拆分 SimulationWorld
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using SepCore.Components;
|
||||
using SepCore.Simulation;
|
||||
@@ -8,15 +7,6 @@ namespace SepCore.Tests.Editor
|
||||
{
|
||||
public class SimulationWorldTickTests
|
||||
{
|
||||
private static readonly BindingFlags NonPublicInstance =
|
||||
BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
|
||||
private static readonly MethodInfo UpsertEnemyMethod =
|
||||
typeof(SimulationWorld).GetMethod("UpsertEnemy", NonPublicInstance);
|
||||
|
||||
private static readonly MethodInfo UpsertProjectileMethod =
|
||||
typeof(SimulationWorld).GetMethod("UpsertProjectile", NonPublicInstance);
|
||||
|
||||
private GameObject _worldObject;
|
||||
private SimulationWorld _world;
|
||||
|
||||
@@ -45,7 +35,7 @@ namespace SepCore.Tests.Editor
|
||||
MovementComponent movement = moverObject.AddComponent<MovementComponent>();
|
||||
moverObject.transform.position = new Vector3(1f, 0f, 2f);
|
||||
|
||||
movement.OnInit(5f, moverObject.transform);
|
||||
movement.OnInit(5f, moverObject.transform, null, false, 0.45f, 2);
|
||||
movement.SetMove(true);
|
||||
movement.SetDirection(Vector3.right);
|
||||
|
||||
@@ -64,7 +54,7 @@ namespace SepCore.Tests.Editor
|
||||
[Test]
|
||||
public void Tick_AdvancesEnemyAndUpdatesNearestTargetQuery()
|
||||
{
|
||||
UpsertEnemy(new EnemySimData
|
||||
_world.UpsertEnemy(new EnemySimData
|
||||
{
|
||||
EntityId = 1001,
|
||||
Position = new Vector3(0f, 0f, 0f),
|
||||
@@ -77,10 +67,10 @@ namespace SepCore.Tests.Editor
|
||||
SeparationIterations = 2
|
||||
});
|
||||
|
||||
_world.Tick(new SimulationTickContext(1f, 1f, new Vector3(10f, 0f, 0f)));
|
||||
_world.Tick(new SimulationTickContext(1f, new Vector3(10f, 0f, 0f)));
|
||||
|
||||
Assert.That(_world.Enemies.Count, Is.EqualTo(1));
|
||||
EnemySimData enemy = _world.Enemies[0];
|
||||
Assert.That(_world.EnemyCount, Is.EqualTo(1));
|
||||
Assert.That(_world.TryGetEnemySnapshot(1001, out EnemySimData enemy), Is.True);
|
||||
Assert.That(enemy.State, Is.EqualTo(1));
|
||||
Assert.That(enemy.Position.x, Is.EqualTo(3f).Within(0.001f));
|
||||
Assert.That(_world.TryGetNearestEnemyEntityId(new Vector3(3.2f, 0f, 0f), 1f, out int enemyEntityId),
|
||||
@@ -88,10 +78,37 @@ namespace SepCore.Tests.Editor
|
||||
Assert.That(enemyEntityId, Is.EqualTo(1001));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tick_AccumulatesTimeAcrossTicksBeforeRunningFixedStep()
|
||||
{
|
||||
_world.UpsertEnemy(new EnemySimData
|
||||
{
|
||||
EntityId = 1003,
|
||||
Position = Vector3.zero,
|
||||
Forward = Vector3.forward,
|
||||
Rotation = Quaternion.identity,
|
||||
Speed = 6f,
|
||||
AttackRange = 1f,
|
||||
AvoidEnemyOverlap = false,
|
||||
EnemyBodyRadius = 0.45f,
|
||||
SeparationIterations = 1
|
||||
});
|
||||
|
||||
_world.Tick(new SimulationTickContext(0.01f, new Vector3(10f, 0f, 0f)));
|
||||
|
||||
Assert.That(_world.TryGetEnemySnapshot(1003, out EnemySimData enemy), Is.True);
|
||||
Assert.That(enemy.Position.x, Is.EqualTo(0f).Within(0.0001f));
|
||||
|
||||
_world.Tick(new SimulationTickContext(0.01f, new Vector3(10f, 0f, 0f)));
|
||||
|
||||
Assert.That(_world.TryGetEnemySnapshot(1003, out enemy), Is.True);
|
||||
Assert.That(enemy.Position.x, Is.EqualTo(0.1f).Within(0.0001f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SyncEnemyMovementInput_DisablesEnemyMovement()
|
||||
{
|
||||
UpsertEnemy(new EnemySimData
|
||||
_world.UpsertEnemy(new EnemySimData
|
||||
{
|
||||
EntityId = 1002,
|
||||
Position = Vector3.zero,
|
||||
@@ -105,9 +122,9 @@ namespace SepCore.Tests.Editor
|
||||
});
|
||||
|
||||
_world.SyncEnemyMovementInput(1002, false, Vector3.left, 4f, true, 0.45f, 2);
|
||||
_world.Tick(new SimulationTickContext(1f, 1f, new Vector3(10f, 0f, 0f)));
|
||||
_world.Tick(new SimulationTickContext(1f, new Vector3(10f, 0f, 0f)));
|
||||
|
||||
EnemySimData enemy = _world.Enemies[0];
|
||||
Assert.That(_world.TryGetEnemySnapshot(1002, out EnemySimData enemy), Is.True);
|
||||
Assert.That(enemy.State, Is.EqualTo(0));
|
||||
Assert.That(enemy.Position.x, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(enemy.Position.y, Is.EqualTo(0f).Within(0.0001f));
|
||||
@@ -117,50 +134,192 @@ namespace SepCore.Tests.Editor
|
||||
[Test]
|
||||
public void Tick_ExpiresProjectileThroughSimulationLifetime()
|
||||
{
|
||||
UpsertProjectile(new ProjectileSimData
|
||||
_world.UpsertProjectile(new ProjectileSimData
|
||||
{
|
||||
EntityId = 2001,
|
||||
OwnerEntityId = 1001,
|
||||
Position = Vector3.zero,
|
||||
Forward = Vector3.right,
|
||||
Velocity = Vector3.right * 4f,
|
||||
Speed = 4f,
|
||||
LifeTime = 0.25f,
|
||||
Age = 0f,
|
||||
Active = true,
|
||||
RemainingLifetime = 0.25f,
|
||||
State = 0
|
||||
Active = true
|
||||
});
|
||||
|
||||
_world.Tick(new SimulationTickContext(0.5f, 0.5f, Vector3.zero));
|
||||
_world.Tick(new SimulationTickContext(0.5f, Vector3.zero));
|
||||
|
||||
Assert.That(_world.Projectiles, Is.Empty);
|
||||
Assert.That(_world.ProjectileCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tick_WithIdenticalInput_ProducesEquivalentSnapshots()
|
||||
{
|
||||
const int tickCount = 30;
|
||||
GameObject firstWorldObject = new GameObject("SimulationWorldDeterminismA");
|
||||
GameObject secondWorldObject = new GameObject("SimulationWorldDeterminismB");
|
||||
try
|
||||
{
|
||||
SimulationWorld firstWorld = firstWorldObject.AddComponent<SimulationWorld>();
|
||||
SimulationWorld secondWorld = secondWorldObject.AddComponent<SimulationWorld>();
|
||||
SeedDeterminismScene(firstWorld);
|
||||
SeedDeterminismScene(secondWorld);
|
||||
|
||||
SimulationTickContext tickContext =
|
||||
new SimulationTickContext(1f / 30f, new Vector3(3.5f, 0f, -2.5f));
|
||||
for (int tick = 0; tick < tickCount; tick++)
|
||||
{
|
||||
firstWorld.Tick(in tickContext);
|
||||
secondWorld.Tick(in tickContext);
|
||||
}
|
||||
|
||||
Assert.That(firstWorld.EnemyCount, Is.EqualTo(DeterminismEnemyIds.Length));
|
||||
Assert.That(secondWorld.EnemyCount, Is.EqualTo(firstWorld.EnemyCount));
|
||||
for (int i = 0; i < DeterminismEnemyIds.Length; i++)
|
||||
{
|
||||
Assert.That(firstWorld.TryGetEnemySnapshot(DeterminismEnemyIds[i], out EnemySimData first),
|
||||
Is.True);
|
||||
Assert.That(secondWorld.TryGetEnemySnapshot(DeterminismEnemyIds[i], out EnemySimData second),
|
||||
Is.True);
|
||||
Assert.That(second.Position.x, Is.EqualTo(first.Position.x).Within(0.01f));
|
||||
Assert.That(second.Position.z, Is.EqualTo(first.Position.z).Within(0.01f));
|
||||
Assert.That(second.State, Is.EqualTo(first.State));
|
||||
}
|
||||
|
||||
Assert.That(secondWorld.ProjectileCount, Is.EqualTo(firstWorld.ProjectileCount));
|
||||
for (int i = 0; i < DeterminismProjectileIds.Length; i++)
|
||||
{
|
||||
Assert.That(
|
||||
firstWorld.TryGetProjectileSnapshot(DeterminismProjectileIds[i], out ProjectileSimData first),
|
||||
Is.True);
|
||||
Assert.That(
|
||||
secondWorld.TryGetProjectileSnapshot(DeterminismProjectileIds[i], out ProjectileSimData second),
|
||||
Is.True);
|
||||
Assert.That(second.Position.x, Is.EqualTo(first.Position.x).Within(0.01f));
|
||||
Assert.That(second.Position.z, Is.EqualTo(first.Position.z).Within(0.01f));
|
||||
Assert.That(second.Age, Is.EqualTo(first.Age).Within(0.001f));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(firstWorldObject);
|
||||
Object.DestroyImmediate(secondWorldObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClearSimulationState_ClearsPublicStateContainers()
|
||||
{
|
||||
UpsertEnemy(new EnemySimData { EntityId = 1, Position = Vector3.zero, Forward = Vector3.forward });
|
||||
UpsertProjectile(new ProjectileSimData { EntityId = 2, Active = true, State = 0 });
|
||||
_world.UpsertEnemy(new EnemySimData { EntityId = 1, Position = Vector3.zero, Forward = Vector3.forward });
|
||||
_world.UpsertProjectile(new ProjectileSimData { EntityId = 2, Active = true });
|
||||
|
||||
_world.ClearSimulationState();
|
||||
|
||||
Assert.That(_world.Enemies, Is.Empty);
|
||||
Assert.That(_world.Projectiles, Is.Empty);
|
||||
Assert.That(_world.Pickups, Is.Empty);
|
||||
Assert.That(_world.EnemyCount, Is.EqualTo(0));
|
||||
Assert.That(_world.ProjectileCount, Is.EqualTo(0));
|
||||
Assert.That(_world.PickupCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
private void UpsertEnemy(EnemySimData simData)
|
||||
[Test]
|
||||
public void Pickup_WhenOutsideAbsorbRange_DoesNotMove()
|
||||
{
|
||||
Assert.NotNull(UpsertEnemyMethod);
|
||||
UpsertEnemyMethod.Invoke(_world, new object[] { simData });
|
||||
_world.UpsertPickup(new PickupSimData
|
||||
{
|
||||
EntityId = 7001,
|
||||
Position = new Vector3(10f, 0f, 10f),
|
||||
PickupRadius = 0.35f,
|
||||
State = 0
|
||||
});
|
||||
|
||||
_world.SyncPlayerAbsorbSettings(3f, 5f, 0.5f);
|
||||
_world.Tick(new SimulationTickContext(0.1f, Vector3.zero));
|
||||
|
||||
Assert.That(_world.TryGetPickupSnapshot(7001, out PickupSimData snapshot), Is.True);
|
||||
Assert.That(snapshot.Position.x, Is.EqualTo(10f).Within(0.001f));
|
||||
Assert.That(snapshot.Position.z, Is.EqualTo(10f).Within(0.001f));
|
||||
Assert.That(snapshot.State, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
private void UpsertProjectile(ProjectileSimData simData)
|
||||
[Test]
|
||||
public void Pickup_WhenInsideAbsorbRange_MovesTowardsPlayerAndTransitionsState()
|
||||
{
|
||||
Assert.NotNull(UpsertProjectileMethod);
|
||||
UpsertProjectileMethod.Invoke(_world, new object[] { simData });
|
||||
_world.UpsertPickup(new PickupSimData
|
||||
{
|
||||
EntityId = 7002,
|
||||
Position = new Vector3(2f, 0f, 0f),
|
||||
PickupRadius = 0.2f,
|
||||
State = 0
|
||||
});
|
||||
|
||||
_world.SyncPlayerAbsorbSettings(5f, 4f, 0.5f);
|
||||
_world.Tick(new SimulationTickContext(0.25f, Vector3.zero));
|
||||
|
||||
Assert.That(_world.TryGetPickupSnapshot(7002, out PickupSimData snapshot), Is.True);
|
||||
// Moved from 2.0 towards 0 by 4.0 * 0.25 = 1.0 -> remaining distance is 1.0
|
||||
Assert.That(snapshot.Position.x, Is.EqualTo(1.0f).Within(0.01f));
|
||||
Assert.That(snapshot.Position.z, Is.EqualTo(0f).Within(0.01f));
|
||||
Assert.That(snapshot.State, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pickup_TriggerFullMapMagnet_ActivatesDistantPickups()
|
||||
{
|
||||
_world.UpsertPickup(new PickupSimData
|
||||
{
|
||||
EntityId = 7003,
|
||||
Position = new Vector3(50f, 0f, 0f),
|
||||
PickupRadius = 0.2f,
|
||||
State = 0
|
||||
});
|
||||
|
||||
_world.SyncPlayerAbsorbSettings(5f, 10f, 0.5f);
|
||||
_world.TriggerFullMapMagnet();
|
||||
|
||||
Assert.That(_world.TryGetPickupSnapshot(7003, out PickupSimData snapshotBefore), Is.True);
|
||||
Assert.That(snapshotBefore.State, Is.EqualTo(1));
|
||||
|
||||
_world.Tick(new SimulationTickContext(0.5f, Vector3.zero));
|
||||
|
||||
Assert.That(_world.TryGetPickupSnapshot(7003, out PickupSimData snapshotAfter), Is.True);
|
||||
// Moved from 50 towards 0 by 10 * 0.5 = 5 -> remaining distance is 45
|
||||
Assert.That(snapshotAfter.Position.x, Is.EqualTo(45f).Within(0.01f));
|
||||
}
|
||||
|
||||
private static readonly int[] DeterminismEnemyIds = { 5001, 5002, 5003, 5004, 5005, 5006 };
|
||||
private static readonly int[] DeterminismProjectileIds = { 6001, 6002, 6003 };
|
||||
|
||||
private static void SeedDeterminismScene(SimulationWorld world)
|
||||
{
|
||||
for (int i = 0; i < DeterminismEnemyIds.Length; i++)
|
||||
{
|
||||
float angle = i * 0.9f;
|
||||
world.UpsertEnemy(new EnemySimData
|
||||
{
|
||||
EntityId = DeterminismEnemyIds[i],
|
||||
Position = new Vector3(Mathf.Cos(angle) * 1.5f, 0f, Mathf.Sin(angle) * 1.5f),
|
||||
Forward = Vector3.forward,
|
||||
Rotation = Quaternion.identity,
|
||||
Speed = 2f + i * 0.25f,
|
||||
AttackRange = 0.5f,
|
||||
AvoidEnemyOverlap = true,
|
||||
EnemyBodyRadius = 0.4f + i * 0.05f,
|
||||
SeparationIterations = 2
|
||||
});
|
||||
}
|
||||
|
||||
for (int i = 0; i < DeterminismProjectileIds.Length; i++)
|
||||
{
|
||||
world.UpsertProjectile(new ProjectileSimData
|
||||
{
|
||||
EntityId = DeterminismProjectileIds[i],
|
||||
OwnerEntityId = DeterminismEnemyIds[i],
|
||||
Position = new Vector3(0f, 1f, 0f),
|
||||
Forward = Vector3.right,
|
||||
Velocity = Vector3.right * (3f + i),
|
||||
LifeTime = 5f,
|
||||
Age = 0f,
|
||||
Active = true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace SepCore.Tests.PlayMode
|
||||
playerObject.transform.position = Vector3.zero;
|
||||
|
||||
_world.SyncPlayerMovementInput(playerObject.transform, true, Vector3.right, 6f);
|
||||
_world.Tick(new SimulationTickContext(0.5f, 0.5f, playerObject.transform.position));
|
||||
_world.Tick(new SimulationTickContext(0.5f, playerObject.transform.position));
|
||||
|
||||
yield return null;
|
||||
|
||||
@@ -104,33 +104,30 @@ namespace SepCore.Tests.PlayMode
|
||||
{
|
||||
_world.SyncEnemyMovementInput(4001, false, Vector3.left, 5f, true, 0.45f, 2);
|
||||
|
||||
MethodInfo upsertEnemyMethod = typeof(SimulationWorld).GetMethod("UpsertEnemy", NonPublicInstance);
|
||||
upsertEnemyMethod.Invoke(_world, new object[]
|
||||
_world.UpsertEnemy(new EnemySimData
|
||||
{
|
||||
new EnemySimData
|
||||
{
|
||||
EntityId = 4001,
|
||||
Position = Vector3.zero,
|
||||
Forward = Vector3.forward,
|
||||
Rotation = Quaternion.identity,
|
||||
Speed = 5f,
|
||||
AttackRange = 0.5f,
|
||||
AvoidEnemyOverlap = true,
|
||||
EnemyBodyRadius = 0.45f,
|
||||
SeparationIterations = 2
|
||||
}
|
||||
EntityId = 4001,
|
||||
Position = Vector3.zero,
|
||||
Forward = Vector3.forward,
|
||||
Rotation = Quaternion.identity,
|
||||
Speed = 5f,
|
||||
AttackRange = 0.5f,
|
||||
AvoidEnemyOverlap = true,
|
||||
EnemyBodyRadius = 0.45f,
|
||||
SeparationIterations = 2
|
||||
});
|
||||
|
||||
_world.SyncEnemyMovementInput(4001, false, Vector3.left, 5f, true, 0.45f, 2);
|
||||
_world.Tick(new SimulationTickContext(1f, 1f, new Vector3(8f, 0f, 0f)));
|
||||
_world.Tick(new SimulationTickContext(1f, new Vector3(8f, 0f, 0f)));
|
||||
|
||||
yield return null;
|
||||
|
||||
Assert.That(_world.Enemies.Count, Is.EqualTo(1));
|
||||
Assert.That(_world.Enemies[0].Position.x, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(_world.Enemies[0].Position.y, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(_world.Enemies[0].Position.z, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(_world.Enemies[0].State, Is.EqualTo(0));
|
||||
Assert.That(_world.EnemyCount, Is.EqualTo(1));
|
||||
Assert.That(_world.TryGetEnemySnapshot(4001, out EnemySimData enemy), Is.True);
|
||||
Assert.That(enemy.Position.x, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(enemy.Position.y, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(enemy.Position.z, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(enemy.State, Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user