781 lines
35 KiB
C#
781 lines
35 KiB
C#
|
#if SSC_ENTITIES
|
|||
|
using System;
|
|||
|
using Unity.Entities;
|
|||
|
using Unity.Transforms;
|
|||
|
using Unity.Mathematics;
|
|||
|
using Unity.Collections;
|
|||
|
using Unity.Jobs;
|
|||
|
using UnityEngine;
|
|||
|
using Unity.Burst;
|
|||
|
|
|||
|
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
|
|||
|
namespace SciFiShipController
|
|||
|
{
|
|||
|
#region ProjectileComponent
|
|||
|
|
|||
|
[Serializable]
|
|||
|
public struct Projectile : IComponentData
|
|||
|
{
|
|||
|
public float3 velocity;
|
|||
|
public float damageAmount;
|
|||
|
public float despawnTime;
|
|||
|
public float despawnTimer;
|
|||
|
public byte useGravity; // 0 = no, 1 = yes
|
|||
|
public float gravity;
|
|||
|
public float3 gravityDirection;
|
|||
|
public float speed;
|
|||
|
public float3 fwdDirection;
|
|||
|
public float3 upDirection;
|
|||
|
public int projectilePrefabID;
|
|||
|
public int effectsObjectPrefabID;
|
|||
|
public int shieldEffectsObjectPrefabID;
|
|||
|
public int sourceShipId;
|
|||
|
public int sourceSquadronId;
|
|||
|
public int damageTypeInt;
|
|||
|
public int _tempIdx;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region ProjectileSystem
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// ProjectileSystem is used to create and move projectiles with the
|
|||
|
/// Data-Orientated Tech Stack (DOTS). It uses C# Jobs, Entities and
|
|||
|
/// the Burst compiler.The default SimulationSystemGroup in Entities
|
|||
|
/// 0.0.12 preview 30 runs at end of Update rather than FixedUpdate.
|
|||
|
/// So disable auto creation and create and update it manually from
|
|||
|
/// SSCManager.
|
|||
|
/// U2019.1 - Entities 0.0.12-preview.30
|
|||
|
/// U2019.2 - Entities 0.1.1 preview
|
|||
|
/// U2019.3 - Entities 0.2.0 preview.18
|
|||
|
/// U2019.4 - Entities 0.5.2 preview.4
|
|||
|
/// U2020.1 - Entities 0.12.0 (untested)
|
|||
|
/// U2020.3 - Entities 0.51.0 preview.32+ (SSC 1.3.4 and earlier Entities 0.17.0-preview.42)
|
|||
|
/// </summary>
|
|||
|
[DisableAutoCreation]
|
|||
|
#if UNITY_2020_3_OR_NEWER
|
|||
|
public partial class ProjectileSystem : SystemBase
|
|||
|
#elif UNITY_2020_1_OR_NEWER
|
|||
|
public class ProjectileSystem : SystemBase
|
|||
|
#else
|
|||
|
public class ProjectileSystem : JobComponentSystem
|
|||
|
#endif
|
|||
|
{
|
|||
|
#region Public Properties
|
|||
|
|
|||
|
// For testing only
|
|||
|
public static int GetTotalProjectiles { get; private set; }
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables
|
|||
|
// EntityQuery aka ComponentGroup pre-ECS 0.27
|
|||
|
private static EntityQuery projectileEntityQuery;
|
|||
|
|
|||
|
private static EntityManager entityManager;
|
|||
|
|
|||
|
private static ComponentType compTypeProjectile;
|
|||
|
|
|||
|
private Vector3 pos, velo, frameMovement;
|
|||
|
|
|||
|
private Entity projectileEntity;
|
|||
|
private int nProjectiles;
|
|||
|
|
|||
|
private NativeArray<RaycastHit> raycastResults;
|
|||
|
private NativeArray<RaycastCommand> raycastCommands;
|
|||
|
private RaycastCommand raycastCommand;
|
|||
|
|
|||
|
private NativeList<Entity> entitiesToDestroy;
|
|||
|
private RaycastHit hitInfo;
|
|||
|
|
|||
|
private SSCManager sscManager;
|
|||
|
|
|||
|
#if SSC_PHYSICS
|
|||
|
private Unity.Physics.Systems.BuildPhysicsWorld buildPhysicsWorld;
|
|||
|
private NativeArray<Unity.Physics.RaycastHit> physicsraycastResults;
|
|||
|
private int numPhysicsRaycastHits;
|
|||
|
private int numPhysicsBodies;
|
|||
|
//private NativeList<Unity.Physics.RaycastHit> physicsraycastResultList;
|
|||
|
#endif
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Projectile Movement Job
|
|||
|
|
|||
|
[BurstCompile]
|
|||
|
struct ProjectileMoveJob : IJobChunk
|
|||
|
{
|
|||
|
public float deltaTime;
|
|||
|
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
// Renamed from ArchetypeChunkComponentType to ComponentTypeHandle in Entities 0.12.0
|
|||
|
public ComponentTypeHandle<Translation> translationType;
|
|||
|
public ComponentTypeHandle<Projectile> projectileType;
|
|||
|
#else
|
|||
|
public ArchetypeChunkComponentType<Translation> translationType;
|
|||
|
public ArchetypeChunkComponentType<Projectile> projectileType;
|
|||
|
#endif
|
|||
|
|
|||
|
public void Execute(ArchetypeChunk archetypeChunk, int chunkIndex, int firstEntityIndex)
|
|||
|
{
|
|||
|
NativeArray<Translation> chunkTranslations = archetypeChunk.GetNativeArray(translationType);
|
|||
|
NativeArray<Projectile> chunkProjectiles = archetypeChunk.GetNativeArray(projectileType);
|
|||
|
|
|||
|
for (int i = 0; i < chunkTranslations.Length; i++)
|
|||
|
{
|
|||
|
Projectile projectile = chunkProjectiles[i];
|
|||
|
Translation position = chunkTranslations[i];
|
|||
|
|
|||
|
// Use Gravity?
|
|||
|
if (projectile.useGravity == (byte)1)
|
|||
|
{
|
|||
|
projectile.velocity += projectile.gravity * deltaTime * projectile.gravityDirection;
|
|||
|
}
|
|||
|
|
|||
|
// Update our current position using our velocity and frame time
|
|||
|
position.Value += projectile.velocity * deltaTime;
|
|||
|
chunkTranslations[i] = position;
|
|||
|
|
|||
|
// Update the timer so that in the OnUpdate we can destroy the entity when required.
|
|||
|
projectile.despawnTimer += deltaTime;
|
|||
|
|
|||
|
chunkProjectiles[i] = projectile;
|
|||
|
|
|||
|
// Update our current rotation using our velocity (currently not required)
|
|||
|
//rotation.Value = quaternion.LookRotationSafe(movement.Velocity, new float3(0f, 1f, 0f));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[BurstCompile]
|
|||
|
struct ProjectileTelePortJob: IJobChunk
|
|||
|
{
|
|||
|
public float3 deltaPosition;
|
|||
|
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
// Renamed from ArchetypeChunkComponentType to ComponentTypeHandle in Entities 0.12.0
|
|||
|
public ComponentTypeHandle<Translation> translationType;
|
|||
|
#else
|
|||
|
public ArchetypeChunkComponentType<Translation> translationType;
|
|||
|
#endif
|
|||
|
|
|||
|
public void Execute(ArchetypeChunk archetypeChunk, int chunkIndex, int firstEntityIndex)
|
|||
|
{
|
|||
|
NativeArray<Translation> chunkTranslations = archetypeChunk.GetNativeArray(translationType);
|
|||
|
|
|||
|
for (int i = 0; i < chunkTranslations.Length; i++)
|
|||
|
{
|
|||
|
Translation translation = chunkTranslations[i];
|
|||
|
translation.Value += deltaPosition;
|
|||
|
chunkTranslations[i] = translation;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Physics Raycast Job
|
|||
|
#if SSC_PHYSICS
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Parallel job for casting rays from projectiles using Unity.Physics
|
|||
|
/// </summary>
|
|||
|
[BurstCompile]
|
|||
|
struct ProjectilePhysicsRayJob : IJobForEach<Translation, Projectile>
|
|||
|
{
|
|||
|
public NativeArray<Unity.Physics.RaycastHit> raycastResultsInJob;
|
|||
|
[ReadOnly] public Unity.Physics.CollisionWorld collisionWorldInJob;
|
|||
|
[ReadOnly] public float deltaTime;
|
|||
|
|
|||
|
public void Execute([ReadOnly] ref Translation position, ref Projectile projectile)
|
|||
|
{
|
|||
|
Unity.Physics.RaycastInput raycastInput = new Unity.Physics.RaycastInput
|
|||
|
{
|
|||
|
Start = position.Value,
|
|||
|
End = position.Value + (projectile.velocity * deltaTime),
|
|||
|
Filter = Unity.Physics.CollisionFilter.Default
|
|||
|
};
|
|||
|
|
|||
|
if (collisionWorldInJob.CastRay(raycastInput, out Unity.Physics.RaycastHit hit))
|
|||
|
{
|
|||
|
if (hit.RigidBodyIndex == 0) { hit.Position = new float3(1f, 1f, 1f); }
|
|||
|
|
|||
|
raycastResultsInJob[projectile._tempIdx] = hit;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Dummy hit
|
|||
|
Unity.Physics.RaycastHit raycastHit = new Unity.Physics.RaycastHit();
|
|||
|
raycastHit.RigidBodyIndex = -1;
|
|||
|
raycastResultsInJob[projectile._tempIdx] = raycastHit;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endif
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Event Methods
|
|||
|
|
|||
|
protected override void OnCreate()
|
|||
|
{
|
|||
|
// Get the current entity manager. If it doesn't exist, create one.
|
|||
|
entityManager = SSCManager.sscWorld.EntityManager;
|
|||
|
|
|||
|
// Cache the component type to avoid ComponentType.op_Implicit in CreateProjectile(..)
|
|||
|
compTypeProjectile = typeof(Projectile);
|
|||
|
|
|||
|
// Projectile entity query code
|
|||
|
// Get all the entities with a Translation and Projectile component
|
|||
|
projectileEntityQuery = GetEntityQuery(new EntityQueryDesc
|
|||
|
{
|
|||
|
All = new ComponentType[] { typeof(Translation), typeof(Rotation), compTypeProjectile }
|
|||
|
});
|
|||
|
|
|||
|
// Initialise raycast command
|
|||
|
raycastCommand = new RaycastCommand(Vector3.zero, Vector3.forward, 1f);
|
|||
|
|
|||
|
// Initialise entities to destroy list
|
|||
|
entitiesToDestroy = new NativeList<Entity>(Allocator.Persistent);
|
|||
|
|
|||
|
sscManager = SSCManager.GetOrCreateManager();
|
|||
|
|
|||
|
#if SSC_PHYSICS
|
|||
|
if (!DOTSHelper.GetBuildPhysicsWorld(SSCManager.sscWorld, ref buildPhysicsWorld))
|
|||
|
{
|
|||
|
#if UNITY_EDITOR
|
|||
|
Debug.Log("ERROR: ProjectileSystem.OnCreate() - could not get physicsworld - PLEASE REPORT");
|
|||
|
#endif
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Create empty resultset. Note the difference between Physics and UnityEngine RaycastHit.
|
|||
|
//physicsraycastResultList = new NativeList<Unity.Physics.RaycastHit>(Allocator.Persistent);
|
|||
|
}
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
protected override void OnDestroy()
|
|||
|
{
|
|||
|
// Dispose of the entities to destroy native list
|
|||
|
entitiesToDestroy.Dispose();
|
|||
|
|
|||
|
#if SSC_PHYSICS
|
|||
|
if (physicsraycastResults.IsCreated) { physicsraycastResults.Dispose(); }
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Update Methods
|
|||
|
|
|||
|
// OnUpdate runs on the main thread.
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
protected override void OnUpdate()
|
|||
|
#else
|
|||
|
protected override JobHandle OnUpdate(JobHandle jobHandle)
|
|||
|
#endif
|
|||
|
{
|
|||
|
//int nProjectiles = 0;
|
|||
|
nProjectiles = 0;
|
|||
|
|
|||
|
#region Physics Updates
|
|||
|
#if SSC_PHYSICS
|
|||
|
// Ensure physics world updates first
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
// FinalJobHandle deprecated in Unity.Physics 0.4.0-preview.5. Replaced with GetOutputDependency().
|
|||
|
this.Dependency = JobHandle.CombineDependencies(this.Dependency, buildPhysicsWorld.GetOutputDependency());
|
|||
|
#else
|
|||
|
jobHandle = JobHandle.CombineDependencies(jobHandle, buildPhysicsWorld.FinalJobHandle);
|
|||
|
#endif
|
|||
|
#endif
|
|||
|
#endregion
|
|||
|
|
|||
|
// Get all of the projectile entities in the scene
|
|||
|
// wrap in using statement to automatically dispose after use
|
|||
|
using (NativeArray<Entity> nativeArray = projectileEntityQuery.ToEntityArray(Allocator.TempJob))
|
|||
|
{
|
|||
|
nProjectiles = nativeArray.Length;
|
|||
|
|
|||
|
// for testing only
|
|||
|
GetTotalProjectiles = nProjectiles;
|
|||
|
}
|
|||
|
|
|||
|
// Gather the types of the components that we want to manipulate
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
// Renamed from ArchetypeChunkComponentType to ComponentTypeHandle in Entities 0.12.0
|
|||
|
// Replace GetArchetypeChunkComponentType<T>() with GetComponentTypeHandle<T>()
|
|||
|
ComponentTypeHandle<Translation> positionType = GetComponentTypeHandle<Translation>();
|
|||
|
ComponentTypeHandle<Rotation> rotationType = GetComponentTypeHandle<Rotation>();
|
|||
|
ComponentTypeHandle<Projectile> projectileType = GetComponentTypeHandle<Projectile>();
|
|||
|
#else
|
|||
|
ArchetypeChunkComponentType<Translation> positionType = GetArchetypeChunkComponentType<Translation>();
|
|||
|
ArchetypeChunkComponentType<Rotation> rotationType = GetArchetypeChunkComponentType<Rotation>();
|
|||
|
ArchetypeChunkComponentType<Projectile> projectileType = GetArchetypeChunkComponentType<Projectile>();
|
|||
|
#endif
|
|||
|
|
|||
|
// Get all the chunks (segments of data) that match this query (that have Translation and Projectile components)
|
|||
|
// The chunks will only contain our projectile entities.
|
|||
|
NativeArray<ArchetypeChunk> chunks = projectileEntityQuery.CreateArchetypeChunkArray(Allocator.TempJob);
|
|||
|
|
|||
|
// Set up the command and result buffers
|
|||
|
raycastCommands = new NativeArray<RaycastCommand>(nProjectiles, Allocator.TempJob);
|
|||
|
raycastResults = new NativeArray<RaycastHit>(nProjectiles, Allocator.TempJob);
|
|||
|
|
|||
|
// Get delta time once
|
|||
|
|
|||
|
// This system is updated from FixedUpdate, so use PhysX time rather than ECS timing.
|
|||
|
float deltaTime = UnityEngine.Time.fixedDeltaTime;
|
|||
|
|
|||
|
//#if UNITY_2019_3_OR_NEWER
|
|||
|
//float deltaTime = Time.DeltaTime;
|
|||
|
//#else
|
|||
|
//float deltaTime = Time.deltaTime;
|
|||
|
//#endif
|
|||
|
|
|||
|
#region Raycast Job
|
|||
|
|
|||
|
// TODO - investigate using a job to populate the raycastCommands NativeArray
|
|||
|
// Iterate through all projectile entities
|
|||
|
int raycastIndex = 0;
|
|||
|
// Loop through the chunks
|
|||
|
int chunksLength = chunks == null ? 0 : chunks.Length;
|
|||
|
for (int chunkIndex = 0; chunkIndex < chunksLength; chunkIndex++)
|
|||
|
{
|
|||
|
// Get the current chunk
|
|||
|
ArchetypeChunk chunk = chunks[chunkIndex];
|
|||
|
|
|||
|
// Get an array of the position components from the entities (that match the projectileEntityQuery) within this chunk
|
|||
|
NativeArray<Translation> positionComponents = chunk.GetNativeArray(positionType);
|
|||
|
// Get an array of the projectile components from the entities within this chunk
|
|||
|
NativeArray<Projectile> projectileComponents = chunk.GetNativeArray(projectileType);
|
|||
|
|
|||
|
// Loop through the entities in this chunk
|
|||
|
int chunkSize = chunk == null ? 0 : chunk.Count;
|
|||
|
for (int entityIndex = 0; entityIndex < chunkSize; entityIndex++)
|
|||
|
{
|
|||
|
// Get the position of this projectile
|
|||
|
pos = positionComponents[entityIndex].Value;
|
|||
|
// Get the velocity of this projectile
|
|||
|
velo = projectileComponents[entityIndex].velocity;
|
|||
|
|
|||
|
// Calculate raycast data
|
|||
|
raycastCommand.from = pos;
|
|||
|
raycastCommand.direction = velo;
|
|||
|
raycastCommand.distance = (velo * deltaTime).magnitude;
|
|||
|
|
|||
|
// Set raycast data
|
|||
|
raycastCommands[raycastIndex] = raycastCommand;
|
|||
|
|
|||
|
// Used in the Unity.Physics raycast job
|
|||
|
Projectile _projectile = projectileComponents[entityIndex];
|
|||
|
_projectile._tempIdx = raycastIndex;
|
|||
|
projectileComponents[entityIndex] = _projectile;
|
|||
|
|
|||
|
//Debug.Log("tempidx: " + _projectile._tempIdx + " actual: " + raycastIndex);
|
|||
|
|
|||
|
// Increment the raycast index - we're doing things this way to make sure that
|
|||
|
// in the second loop the indices all match up correctly
|
|||
|
raycastIndex++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Schedule the batch of raycasts
|
|||
|
// TODO - in Entities 12+, not sure if this a parallel job...
|
|||
|
JobHandle handle = RaycastCommand.ScheduleBatch(raycastCommands, raycastResults, 1, default(JobHandle));
|
|||
|
|
|||
|
// Wait for the batch processing job to complete
|
|||
|
handle.Complete();
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Unity.Physics Raycast Job
|
|||
|
#if SSC_PHYSICS
|
|||
|
physicsraycastResults = new NativeArray<Unity.Physics.RaycastHit>(nProjectiles, Allocator.TempJob);
|
|||
|
|
|||
|
if (physicsraycastResults.IsCreated)
|
|||
|
{
|
|||
|
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
// Check if this is parallel...
|
|||
|
new ProjectilePhysicsRayJob()
|
|||
|
{
|
|||
|
collisionWorldInJob = buildPhysicsWorld.PhysicsWorld.CollisionWorld,
|
|||
|
raycastResultsInJob = physicsraycastResults,
|
|||
|
deltaTime = deltaTime
|
|||
|
}.Schedule(this, this.Dependency).Complete();
|
|||
|
#else
|
|||
|
new ProjectilePhysicsRayJob()
|
|||
|
{
|
|||
|
collisionWorldInJob = buildPhysicsWorld.PhysicsWorld.CollisionWorld,
|
|||
|
raycastResultsInJob = physicsraycastResults,
|
|||
|
deltaTime = deltaTime
|
|||
|
}.Schedule(this, jobHandle).Complete();
|
|||
|
#endif
|
|||
|
|
|||
|
numPhysicsRaycastHits = physicsraycastResults.Length;
|
|||
|
numPhysicsBodies = buildPhysicsWorld.PhysicsWorld.CollisionWorld.NumBodies;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
numPhysicsRaycastHits = 0;
|
|||
|
numPhysicsBodies = 0;
|
|||
|
}
|
|||
|
#endif
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Process Raycasts for collision and despawn old projectiles
|
|||
|
|
|||
|
// Get an archetype for projectiles
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
// Renamed from ArchetypeChunkEntityType to EntityTypeHandle in Entities 0.12.0
|
|||
|
// Replace GetArchetypeChunkEntityType() with GetEntityTypeHandle()
|
|||
|
EntityTypeHandle projectileChunkEntityArchetype = GetEntityTypeHandle();
|
|||
|
#else
|
|||
|
ArchetypeChunkEntityType projectileChunkEntityArchetype = GetArchetypeChunkEntityType();
|
|||
|
#endif
|
|||
|
|
|||
|
// Iterate through all projectile entities again
|
|||
|
// Reset raycastIndex
|
|||
|
raycastIndex = 0;
|
|||
|
Collider other;
|
|||
|
for (int chunkIndex = 0; chunkIndex < chunksLength; chunkIndex++)
|
|||
|
{
|
|||
|
// Get the current chunk of (projectile) entities
|
|||
|
ArchetypeChunk chunk = chunks[chunkIndex];
|
|||
|
|
|||
|
// Get a native array of the entities in this chunk
|
|||
|
NativeArray<Entity> chunkEntities = chunk.GetNativeArray(projectileChunkEntityArchetype);
|
|||
|
|
|||
|
// Get arrays of the projectile and rotations components from the entities within this chunk
|
|||
|
NativeArray<Projectile> projectileComponents = chunk.GetNativeArray(projectileType);
|
|||
|
NativeArray<Rotation> rotationComponents = chunk.GetNativeArray(rotationType);
|
|||
|
|
|||
|
// Loop through the entities in this chunk
|
|||
|
int chunkSize = chunk == null ? 0 : chunk.Count;
|
|||
|
for (int entityIndex = 0; entityIndex < chunkSize; entityIndex++)
|
|||
|
{
|
|||
|
Projectile projectile = projectileComponents[entityIndex];
|
|||
|
Rotation rotation = rotationComponents[entityIndex];
|
|||
|
|
|||
|
#region Process Legacy Physics Raycast results
|
|||
|
hitInfo = raycastResults[raycastIndex];
|
|||
|
other = hitInfo.collider;
|
|||
|
//Translation translation = translationComponents[entityIndex];
|
|||
|
|
|||
|
// If raycastResults[raycastIndex].collider == null there was no hit
|
|||
|
if (other != null)
|
|||
|
{
|
|||
|
|
|||
|
bool isShieldHit = false;
|
|||
|
ShipControlModule shipControlModule = null;
|
|||
|
|
|||
|
// Do we need to check for ship shield hits?
|
|||
|
if (projectile.shieldEffectsObjectPrefabID >= 0 && ProjectileModule.CheckShipHit(hitInfo, projectile.damageAmount, (ProjectileModule.DamageType)projectile.damageTypeInt, projectile.sourceShipId, projectile.sourceSquadronId, projectile.projectilePrefabID, out shipControlModule))
|
|||
|
{
|
|||
|
isShieldHit = shipControlModule.shipInstance.HasActiveShield(hitInfo.point);
|
|||
|
}
|
|||
|
// No shield effects so perform a regular CheckShipHit
|
|||
|
else if (projectile.shieldEffectsObjectPrefabID < 0 && ProjectileModule.CheckShipHit(hitInfo, projectile.damageAmount, (ProjectileModule.DamageType)projectile.damageTypeInt, projectile.sourceShipId, projectile.sourceSquadronId, projectile.projectilePrefabID))
|
|||
|
{
|
|||
|
// No need to do anything else here
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If it hit an object with a DamageReceiver script attached, take appropriate action like call a custom method
|
|||
|
ProjectileModule.CheckObjectHit(hitInfo, projectile.damageAmount, (ProjectileModule.DamageType)projectile.damageTypeInt, projectile.sourceShipId, projectile.sourceSquadronId, projectile.projectilePrefabID);
|
|||
|
}
|
|||
|
|
|||
|
// OLD CODE pre v1.3.5
|
|||
|
// Determine if it has hit a ship
|
|||
|
//if (!ProjectileModule.CheckShipHit(hitInfo, projectile.damageAmount, (ProjectileModule.DamageType)projectile.damageTypeInt, projectile.sourceShipId, projectile.sourceSquadronId, projectile.projectilePrefabID))
|
|||
|
//{
|
|||
|
// // If it hit an object with a DamageReceiver script attached, take appropriate action like call a custom method
|
|||
|
// ProjectileModule.CheckObjectHit(hitInfo, projectile.damageAmount, (ProjectileModule.DamageType)projectile.damageTypeInt, projectile.sourceShipId, projectile.sourceSquadronId, projectile.projectilePrefabID);
|
|||
|
//}
|
|||
|
|
|||
|
// If required, use a shield EffectsObject.
|
|||
|
if (isShieldHit && projectile.shieldEffectsObjectPrefabID >= 0)
|
|||
|
{
|
|||
|
if (sscManager != null)
|
|||
|
{
|
|||
|
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
|
|||
|
{
|
|||
|
effectsObjectPrefabID = projectile.shieldEffectsObjectPrefabID,
|
|||
|
position = hitInfo.point + (hitInfo.normal * 0.0005f),
|
|||
|
rotation = Quaternion.LookRotation(-hitInfo.normal),
|
|||
|
};
|
|||
|
|
|||
|
// For projectiles we don't need to get the effectsObject key from ieParms.
|
|||
|
sscManager.InstantiateEffectsObject(ref ieParms);
|
|||
|
}
|
|||
|
}
|
|||
|
else if (!isShieldHit && projectile.effectsObjectPrefabID >= 0 && sscManager != null)
|
|||
|
{
|
|||
|
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
|
|||
|
{
|
|||
|
effectsObjectPrefabID = projectile.effectsObjectPrefabID,
|
|||
|
position = hitInfo.point,
|
|||
|
rotation = rotation.Value
|
|||
|
};
|
|||
|
|
|||
|
sscManager.InstantiateEffectsObject(ref ieParms);
|
|||
|
}
|
|||
|
|
|||
|
// Mark this (projectile) entity to be destroyed
|
|||
|
entitiesToDestroy.Add(chunkEntities[entityIndex]);
|
|||
|
}
|
|||
|
// Should this entity be despawned? Use and "else" so we don't try to destroy it twice
|
|||
|
else
|
|||
|
{
|
|||
|
// We "could" create a job with a CommandBuffer which would queue all the DestroyEntity requests and then run
|
|||
|
// it at the end of the job on the main thread, that just creates more overhead. It "might" be
|
|||
|
// faster with Burst if there were a zillion projectiles but doing it directly on the main thread
|
|||
|
// is simplier and probably just as performant.
|
|||
|
|
|||
|
// Is the projectile past it's use-by date?
|
|||
|
if (projectile.despawnTimer + deltaTime > projectile.despawnTime)
|
|||
|
{
|
|||
|
entitiesToDestroy.Add(chunkEntities[entityIndex]);
|
|||
|
}
|
|||
|
|
|||
|
// If the Entity is not being destroyed, we "could" update the despawnTimer here on the main thread
|
|||
|
// using the following example code, however, we can do that more efficently in the projectileJob
|
|||
|
// below. Example code: entityManager.SetComponentData(chunkEntities[entityIndex], projectile);
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Process Unity.Physics Raycast results
|
|||
|
#if SSC_PHYSICS
|
|||
|
if (numPhysicsRaycastHits > 0)
|
|||
|
{
|
|||
|
Unity.Physics.RaycastHit raycastHit = physicsraycastResults[projectile._tempIdx];
|
|||
|
|
|||
|
if (raycastHit.RigidBodyIndex >= 0 && raycastHit.RigidBodyIndex < numPhysicsBodies)
|
|||
|
{
|
|||
|
// If the surface normal vector has a length, we must have hit something
|
|||
|
if (math.abs(math.lengthsq(raycastHit.SurfaceNormal)) > 0f)
|
|||
|
{
|
|||
|
//Debug.Log("[DEBUG] PhysicsHit: " + raycastHit.Position.ToString() + " RigidBodyIndex: " + raycastHit.RigidBodyIndex);
|
|||
|
|
|||
|
if (sscManager != null && projectile.effectsObjectPrefabID >= 0)
|
|||
|
{
|
|||
|
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
|
|||
|
{
|
|||
|
effectsObjectPrefabID = projectile.effectsObjectPrefabID,
|
|||
|
position = raycastHit.Position,
|
|||
|
rotation = rotation.Value
|
|||
|
};
|
|||
|
|
|||
|
sscManager.InstantiateEffectsObject(ref ieParms);
|
|||
|
}
|
|||
|
|
|||
|
// Mark this (projectile) entity to be destroyed
|
|||
|
entitiesToDestroy.Add(chunkEntities[entityIndex]);
|
|||
|
|
|||
|
// Potentially destroy the object that was hit.
|
|||
|
//entitiesToDestroy.Add(buildPhysicsWorld.PhysicsWorld.CollisionWorld.Bodies[raycastHit.RigidBodyIndex].Entity);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
#endregion
|
|||
|
|
|||
|
// Increment the raycast index - we're doing things this way to make sure that
|
|||
|
// in the second loop the indices all match up correctly
|
|||
|
// TODO: Need to check that this works - it might not work if, for instance, the chunks
|
|||
|
// get populated in a different order each time
|
|||
|
raycastIndex++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// It is faster to destroy a native array of projectiles, than one at a time
|
|||
|
entityManager.DestroyEntity(entitiesToDestroy.AsArray());
|
|||
|
entitiesToDestroy.Clear();
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
// Dispose of the native array
|
|||
|
if (raycastResults.IsCreated) { raycastResults.Dispose(); }
|
|||
|
if (raycastCommands.IsCreated) { raycastCommands.Dispose(); }
|
|||
|
if (chunks.IsCreated) { chunks.Dispose(); }
|
|||
|
|
|||
|
#region Unity.Physics Raycast Cleanup
|
|||
|
#if SSC_PHYSICS
|
|||
|
if (physicsraycastResults.IsCreated)
|
|||
|
{
|
|||
|
physicsraycastResults.Dispose();
|
|||
|
}
|
|||
|
#endif
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Parallel Job to move the Projectiles
|
|||
|
// Create a new IJobChunk projectile move job, passing in the current frame time as
|
|||
|
// an argument along with the chunk component types.
|
|||
|
// Notice that we cannot used cached chunk component types.
|
|||
|
ProjectileMoveJob projectileMoveJob = new ProjectileMoveJob
|
|||
|
{
|
|||
|
deltaTime = deltaTime,
|
|||
|
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
// Replace GetArchetypeChunkComponentType<T>() with GetComponentTypeHandle<T>() in Entities 0.12.0
|
|||
|
translationType = GetComponentTypeHandle<Translation>(),
|
|||
|
projectileType = GetComponentTypeHandle<Projectile>()
|
|||
|
#else
|
|||
|
translationType = GetArchetypeChunkComponentType<Translation>(),
|
|||
|
projectileType = GetArchetypeChunkComponentType<Projectile>()
|
|||
|
#endif
|
|||
|
};
|
|||
|
|
|||
|
// Schedule the parallel projectile move job
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
this.Dependency = projectileMoveJob.ScheduleParallel(projectileEntityQuery, this.Dependency);
|
|||
|
#else
|
|||
|
return projectileMoveJob.Schedule(projectileEntityQuery, jobHandle);
|
|||
|
#endif
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private and Internal Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Teleport (move) the projectiles a particular amount on x,y,z axes.
|
|||
|
/// </summary>
|
|||
|
/// <param name="deltaPosition"></param>
|
|||
|
internal void TelePortProjectiles(Vector3 deltaPosition)
|
|||
|
{
|
|||
|
// Create a new job
|
|||
|
ProjectileTelePortJob projectileTelePortJob = new ProjectileTelePortJob
|
|||
|
{
|
|||
|
deltaPosition = deltaPosition,
|
|||
|
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
// Replace GetArchetypeChunkComponentType<T>() with GetComponentTypeHandle<T>() in Entities 0.12.0
|
|||
|
translationType = GetComponentTypeHandle<Translation>()
|
|||
|
#else
|
|||
|
translationType = GetArchetypeChunkComponentType<Translation>()
|
|||
|
#endif
|
|||
|
};
|
|||
|
|
|||
|
// Schedule the parallel teleport job
|
|||
|
#if UNITY_2020_1_OR_NEWER
|
|||
|
this.Dependency = projectileTelePortJob.ScheduleParallel(projectileEntityQuery, this.Dependency);
|
|||
|
#else
|
|||
|
JobHandle jobHandle = projectileTelePortJob.Schedule(projectileEntityQuery);
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Static Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Create a new projectile entity in the scene, based on a prefab that has already been converted
|
|||
|
/// from a gameobject prefab to an entity.
|
|||
|
/// Add a Projectile component if it wasn't already attached to the original gameobject prefab.
|
|||
|
/// Update the array of all projectile entities.
|
|||
|
/// It "might" be better to pass the ProjectModule instance reference which would simplify
|
|||
|
/// maintenance.
|
|||
|
/// NOTE: This method runs on the main thread.
|
|||
|
/// </summary>
|
|||
|
/// <param name="position"></param>
|
|||
|
/// <param name="weaponVelocity"></param>
|
|||
|
/// <param name="startFwdDirection"></param>
|
|||
|
/// <param name="startUpDirection"></param>
|
|||
|
/// <param name="startSpeed"></param>
|
|||
|
/// <param name="deltaTime"></param>
|
|||
|
/// <param name="useGravity"></param>
|
|||
|
/// <param name="gravity"></param>
|
|||
|
/// <param name="gravityDirection"></param>
|
|||
|
/// <param name="damageAmount"></param>
|
|||
|
/// <param name="despawnTime"></param>
|
|||
|
/// <param name="projectilePrefabID"></param>
|
|||
|
/// <param name="effectsObjectPrefabID"></param>
|
|||
|
/// <param name="shieldEffectsObjectPrefabID"></param>
|
|||
|
/// <param name="shipId"></param>
|
|||
|
/// <param name="squadronId"></param>
|
|||
|
/// <param name="damageTypeInt"></param>
|
|||
|
/// <param name="projectilePrefabEntity"></param>
|
|||
|
public static void CreateProjectile
|
|||
|
(
|
|||
|
Vector3 position,
|
|||
|
float3 weaponVelocity,
|
|||
|
float3 startFwdDirection,
|
|||
|
float3 startUpDirection,
|
|||
|
float startSpeed,
|
|||
|
float deltaTime,
|
|||
|
bool useGravity,
|
|||
|
float gravity,
|
|||
|
float3 gravityDirection,
|
|||
|
float damageAmount,
|
|||
|
float despawnTime,
|
|||
|
int projectilePrefabID,
|
|||
|
int effectsObjectPrefabID,
|
|||
|
int shieldEffectsObjectPrefabID,
|
|||
|
int shipId,
|
|||
|
int squadronId,
|
|||
|
int damageTypeInt,
|
|||
|
Entity projectilePrefabEntity
|
|||
|
)
|
|||
|
{
|
|||
|
// The prefab is converted to an entity when the ProjectileTemplate is created.
|
|||
|
// Translation, Rotation, and RenderMesh components are automatically added to the input projectPrefabEntity
|
|||
|
// when it is converted from the gameobject prefab. So, we don't need to add them here.
|
|||
|
|
|||
|
Entity entity = entityManager.Instantiate(projectilePrefabEntity);
|
|||
|
|
|||
|
// Add a Projectile component if it wasn't on the template prefab
|
|||
|
if (!entityManager.HasComponent(entity, compTypeProjectile))
|
|||
|
{
|
|||
|
entityManager.AddComponent(entity, compTypeProjectile);
|
|||
|
}
|
|||
|
|
|||
|
//Debug.Log("[DEBUG] has projectilemodule: " + entityManager.HasComponent(entity, typeof(ProjectileModule)));
|
|||
|
//Debug.Log("[DEBUG] has RenderMesh: " + entityManager.HasComponent(entity, typeof(Unity.Rendering.RenderMesh)));
|
|||
|
|
|||
|
float3 _velocity = startFwdDirection * startSpeed;
|
|||
|
|
|||
|
// Set their position, rotation and projectile properties
|
|||
|
// Shift the position forward by the weapon velocity, so that projectiles don't ever end up behind the ship
|
|||
|
entityManager.SetComponentData(entity, new Translation() { Value = (float3)position + (_velocity * deltaTime) });
|
|||
|
entityManager.SetComponentData(entity, new Rotation() { Value = quaternion.LookRotation(startFwdDirection, startUpDirection) });
|
|||
|
|
|||
|
entityManager.SetComponentData(entity, new Projectile()
|
|||
|
{
|
|||
|
// Initialise the velocity based on the forwards direction
|
|||
|
// The forwards direction should have been set correctly prior to enabling the object
|
|||
|
velocity = _velocity + weaponVelocity,
|
|||
|
|
|||
|
// Store the current speed and forwards direction incase we want to change
|
|||
|
// these if gravity is being applied to the projectile
|
|||
|
useGravity = useGravity ? (byte)1 : (byte)0,
|
|||
|
gravity = gravity,
|
|||
|
gravityDirection = gravityDirection,
|
|||
|
damageAmount = damageAmount,
|
|||
|
despawnTime = despawnTime,
|
|||
|
despawnTimer = 0f,
|
|||
|
speed = startSpeed,
|
|||
|
fwdDirection = startFwdDirection,
|
|||
|
upDirection = startUpDirection,
|
|||
|
projectilePrefabID = projectilePrefabID,
|
|||
|
effectsObjectPrefabID = effectsObjectPrefabID,
|
|||
|
shieldEffectsObjectPrefabID = shieldEffectsObjectPrefabID,
|
|||
|
sourceShipId = shipId,
|
|||
|
sourceSquadronId = squadronId,
|
|||
|
damageTypeInt = damageTypeInt,
|
|||
|
_tempIdx = -1
|
|||
|
}
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
#endif
|