356 lines
12 KiB
C#
356 lines
12 KiB
C#
using SepCore.Entity;
|
|
using UnityEngine;
|
|
|
|
namespace SepCore.Simulation
|
|
{
|
|
public sealed partial class SimulationWorld
|
|
{
|
|
#region Simulation State Lifecycle
|
|
|
|
public void ClearSimulationState()
|
|
{
|
|
_enemies.Clear();
|
|
_projectiles.Clear();
|
|
_pickups.Clear();
|
|
_projectileRecycleEntityIds.Clear();
|
|
_projectileResolvedEntityIds.Clear();
|
|
_areaCollisionRequests.Clear();
|
|
_areaCollisionHitEvents.Clear();
|
|
_areaCollisionHitDedupKeys.Clear();
|
|
_fixedStepAccumulator = 0d;
|
|
ClearJobDataChannels();
|
|
ClearPlayerMovementState();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#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)
|
|
{
|
|
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 index;
|
|
}
|
|
|
|
internal int UpsertEnemy(in EnemySimData simData)
|
|
{
|
|
if (!_enemies.TryGetSimulationIndex(simData.EntityId, out int index))
|
|
{
|
|
return AddEnemy(in simData);
|
|
}
|
|
|
|
_enemies.Identity[index] = new EnemyIdentitySim { EntityId = simData.EntityId };
|
|
_enemies.Transform[index] = new EnemyTransformSim
|
|
{
|
|
Position = ToFloat3(simData.Position),
|
|
Forward = ToFloat3(simData.Forward),
|
|
Rotation = ToQuaternion(simData.Rotation)
|
|
};
|
|
_enemies.MoveInput[index] = new EnemyMoveInputSim { Speed = simData.Speed };
|
|
_enemies.MoveConfig[index] = new EnemyMoveConfigSim
|
|
{
|
|
AvoidOverlap = simData.AvoidEnemyOverlap,
|
|
BodyRadius = simData.EnemyBodyRadius,
|
|
SeparationIterations = simData.SeparationIterations
|
|
};
|
|
_enemies.Attack[index] = new EnemyAttackSim { AttackRange = simData.AttackRange };
|
|
_enemies.Behavior[index] = new EnemyBehaviorSim { State = simData.State };
|
|
MarkEnemyTargetSpatialIndexDirty();
|
|
return index;
|
|
}
|
|
|
|
private bool RemoveEnemyByEntityId(int entityId)
|
|
{
|
|
if (!_enemies.RemoveByEntityId(entityId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
MarkEnemyTargetSpatialIndexDirty();
|
|
return true;
|
|
}
|
|
|
|
private void RegisterEnemyLifecycle(EnemyBase enemy, object userData)
|
|
{
|
|
if (enemy == null || enemy.CachedTransform == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EnemyData enemyData = userData as EnemyData;
|
|
UpsertEnemy(CreateEnemyInitialSimData(enemy, enemyData));
|
|
}
|
|
|
|
private void UnregisterEnemyLifecycle(int entityId)
|
|
{
|
|
RemoveEnemyByEntityId(entityId);
|
|
}
|
|
|
|
public void SyncEnemyMovementInput(int entityId, bool isMoving, in Vector3 direction, float speed,
|
|
bool avoidEnemyOverlap, float enemyBodyRadius, int separationIterations)
|
|
{
|
|
if (!_enemies.TryGetSimulationIndex(entityId, out int index))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_enemies.MoveInput[index] = new EnemyMoveInputSim
|
|
{
|
|
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)
|
|
};
|
|
|
|
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)
|
|
{
|
|
InitializeJobDataChannels();
|
|
|
|
return _projectiles.Add(
|
|
new ProjectileIdentitySim
|
|
{
|
|
EntityId = simData.EntityId,
|
|
OwnerEntityId = simData.OwnerEntityId
|
|
},
|
|
new ProjectileTransformSim
|
|
{
|
|
Position = ToFloat3(simData.Position),
|
|
Forward = ToFloat3(simData.Forward)
|
|
},
|
|
new ProjectileVelocitySim { Velocity = ToFloat3(simData.Velocity) },
|
|
new ProjectileLifeSim
|
|
{
|
|
Age = simData.Age,
|
|
LifeTime = simData.LifeTime,
|
|
Active = simData.Active
|
|
});
|
|
}
|
|
|
|
internal int UpsertProjectile(in ProjectileSimData simData)
|
|
{
|
|
if (!_projectiles.TryGetSimulationIndex(simData.EntityId, out int index))
|
|
{
|
|
return AddProjectile(in simData);
|
|
}
|
|
|
|
_projectiles.Identity[index] = new ProjectileIdentitySim
|
|
{
|
|
EntityId = simData.EntityId,
|
|
OwnerEntityId = simData.OwnerEntityId
|
|
};
|
|
_projectiles.Transform[index] = new ProjectileTransformSim
|
|
{
|
|
Position = ToFloat3(simData.Position),
|
|
Forward = ToFloat3(simData.Forward)
|
|
};
|
|
_projectiles.Velocity[index] = new ProjectileVelocitySim { Velocity = ToFloat3(simData.Velocity) };
|
|
_projectiles.Life[index] = new ProjectileLifeSim
|
|
{
|
|
Age = simData.Age,
|
|
LifeTime = simData.LifeTime,
|
|
Active = simData.Active
|
|
};
|
|
return index;
|
|
}
|
|
|
|
private bool RemoveProjectileByEntityId(int entityId)
|
|
{
|
|
return _projectiles.RemoveByEntityId(entityId);
|
|
}
|
|
|
|
private void RegisterProjectileLifecycle(EntityBase projectileEntity, object userData)
|
|
{
|
|
if (projectileEntity == null || projectileEntity.CachedTransform == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpsertProjectile(CreateProjectileInitialSimData(projectileEntity, userData));
|
|
}
|
|
|
|
private void UnregisterProjectileLifecycle(int entityId)
|
|
{
|
|
RemoveProjectileByEntityId(entityId);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#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)
|
|
{
|
|
InitializeJobDataChannels();
|
|
|
|
return _pickups.Add(
|
|
new PickupIdentitySim { EntityId = simData.EntityId },
|
|
new PickupTransformSim { Position = ToFloat3(simData.Position) },
|
|
new PickupRadiusSim { Radius = simData.PickupRadius },
|
|
new PickupStateSim { State = simData.State });
|
|
}
|
|
|
|
internal int UpsertPickup(in PickupSimData simData)
|
|
{
|
|
if (!_pickups.TryGetSimulationIndex(simData.EntityId, out int index))
|
|
{
|
|
return AddPickup(in simData);
|
|
}
|
|
|
|
_pickups.Identity[index] = new PickupIdentitySim { EntityId = simData.EntityId };
|
|
_pickups.Transform[index] = new PickupTransformSim { Position = ToFloat3(simData.Position) };
|
|
_pickups.Radius[index] = new PickupRadiusSim { Radius = simData.PickupRadius };
|
|
_pickups.State[index] = new PickupStateSim { State = simData.State };
|
|
return index;
|
|
}
|
|
|
|
public void TriggerFullMapMagnet()
|
|
{
|
|
int pickupCount = _pickups.Count;
|
|
for (int i = 0; i < pickupCount; i++)
|
|
{
|
|
_pickups.State[i] = new PickupStateSim { State = PickupStateSim.StateAbsorbing };
|
|
}
|
|
}
|
|
|
|
private bool RemovePickupByEntityId(int entityId)
|
|
{
|
|
return _pickups.RemoveByEntityId(entityId);
|
|
}
|
|
|
|
private void RegisterPickupLifecycle(EntityBase pickupEntity)
|
|
{
|
|
if (pickupEntity == null || pickupEntity.CachedTransform == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpsertPickup(CreatePickupInitialSimData(pickupEntity));
|
|
}
|
|
|
|
private void UnregisterPickupLifecycle(int entityId)
|
|
{
|
|
RemovePickupByEntityId(entityId);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|