258 lines
11 KiB
C#
258 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
#if SSC_ENTITIES
|
|
using Unity.Transforms;
|
|
using Unity.Entities;
|
|
using Unity.Mathematics;
|
|
using Unity.Jobs;
|
|
using Unity.Collections;
|
|
using Unity.Rendering;
|
|
#endif
|
|
|
|
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
|
|
namespace SciFiShipController
|
|
{
|
|
/// <summary>
|
|
/// Sample prefab spawner to create something like an asteroid field. The DOTS option
|
|
/// uses currently doesn't support colliders but has the benefit of random rotation.
|
|
/// Data Oriented Technology Stack (DOTS) has the same requirements as DOTS with
|
|
/// Projectiles in Sci-Fi Ship Controller. See the manual for more info on DOTS setup.
|
|
/// </summary>
|
|
[AddComponentMenu("Sci-Fi Ship Controller/Samples/Obstacle Spawner")]
|
|
[HelpURL("http://scsmmedia.com/ssc-documentation")]
|
|
public class SampleObstacleSpawner : MonoBehaviour
|
|
{
|
|
#region Public variables
|
|
|
|
public bool initialiseOnAwake;
|
|
|
|
[Tooltip("Experimental")]
|
|
public bool useDOTS = false;
|
|
public Transform[] obstaclePrefabs;
|
|
|
|
public Vector3 obstacleFieldCentre = new Vector3(500f, 250f, 500f);
|
|
public Vector3 obstacleFieldSize = new Vector3(5000f, 500f, 5000f);
|
|
public int obstacles = 2500;
|
|
public Vector3 obstacleScaleMin = Vector3.one * 5f;
|
|
public Vector3 obstacleScaleMax = Vector3.one * 20f;
|
|
|
|
#if SSC_ENTITIES
|
|
public World asteroidWorld;
|
|
public List<EntityArchetype> asteriodEntityArchetypeList;
|
|
public List<Entity> asteriodPrefabEntityList;
|
|
public AsteroidSystem asteroidSystem;
|
|
#if UNITY_2019_3_OR_NEWER || UNITY_ENTITIES_0_2_0_OR_NEWER
|
|
private BlobAssetStore blobAssetStore;
|
|
#endif
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Private variables
|
|
private List<Transform> obstacleTrmList;
|
|
private List<Vector3> obstacleRotationDirectionList;
|
|
private List<float> obstacleRotationRateList;
|
|
private int numObstaclesToRotate = 0;
|
|
#endregion
|
|
|
|
#region Initialise Methods
|
|
|
|
private void Awake()
|
|
{
|
|
if (initialiseOnAwake) { Initialise(); }
|
|
}
|
|
|
|
public void Initialise()
|
|
{
|
|
int numObstaclePrefabs = obstaclePrefabs == null ? 0 : obstaclePrefabs.Length;
|
|
|
|
if (numObstaclePrefabs > 0)
|
|
{
|
|
float minX = obstacleFieldCentre.x - (obstacleFieldSize.x * 0.5f);
|
|
float maxX = obstacleFieldCentre.x + (obstacleFieldSize.x * 0.5f);
|
|
float minY = obstacleFieldCentre.y - (obstacleFieldSize.y * 0.5f);
|
|
float maxY = obstacleFieldCentre.y + (obstacleFieldSize.y * 0.5f);
|
|
float minZ = obstacleFieldCentre.z - (obstacleFieldSize.z * 0.5f);
|
|
float maxZ = obstacleFieldCentre.z + (obstacleFieldSize.z * 0.5f);
|
|
Vector3 newPosition = Vector3.zero;
|
|
Vector3 newScale = Vector3.one;
|
|
UnityEngine.Random.InitState(0);
|
|
|
|
bool isDOTSConfigured = false;
|
|
|
|
if (useDOTS) { isDOTSConfigured = this.ConvertPrefabsToEntities(); }
|
|
else
|
|
{
|
|
obstacleTrmList = new List<Transform>(obstacles);
|
|
obstacleRotationDirectionList = new List<Vector3>(obstacles);
|
|
obstacleRotationRateList = new List<float>(obstacles);
|
|
}
|
|
|
|
for (int i = 0; i < obstacles; i++)
|
|
{
|
|
newPosition.x = UnityEngine.Random.Range(minX, maxX);
|
|
newPosition.y = UnityEngine.Random.Range(minY, maxY);
|
|
newPosition.z = UnityEngine.Random.Range(minZ, maxZ);
|
|
|
|
newScale = new Vector3(UnityEngine.Random.Range(obstacleScaleMin.x, obstacleScaleMax.x), UnityEngine.Random.Range(obstacleScaleMin.y, obstacleScaleMax.y), UnityEngine.Random.Range(obstacleScaleMin.z, obstacleScaleMax.z));
|
|
|
|
float rotationRate = UnityEngine.Random.Range(0.1f, 1.0f);
|
|
Vector3 rotationDirection = new Vector3(UnityEngine.Random.Range(0.01f, 1.0f), UnityEngine.Random.Range(0.01f, 1.0f), UnityEngine.Random.Range(0.01f, 1.0f));
|
|
|
|
// By design, if the DOTS option is enabled but DOTS is not configured it will render nothing in the scene.
|
|
if (useDOTS)
|
|
{
|
|
if (isDOTSConfigured)
|
|
{
|
|
#if SSC_ENTITIES
|
|
AsteroidSystem.CreateAsteroid(newPosition, Quaternion.identity, newScale, rotationRate, rotationDirection, asteriodPrefabEntityList[UnityEngine.Random.Range(0, numObstaclePrefabs - 1)]);
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// select a prefab from the list and add it to the scene
|
|
Transform obstacleTrfm = Instantiate(obstaclePrefabs[UnityEngine.Random.Range(0, numObstaclePrefabs - 1)], newPosition, Quaternion.identity, transform);
|
|
|
|
if (obstacleTrfm != null)
|
|
{
|
|
obstacleTrfm.localScale = newScale;
|
|
obstacleTrmList.Add(obstacleTrfm);
|
|
obstacleRotationDirectionList.Add(rotationDirection);
|
|
obstacleRotationRateList.Add(rotationRate);
|
|
numObstaclesToRotate++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the set of prefabs into Entities.
|
|
/// Create an instance of the AsteroidSystem.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool ConvertPrefabsToEntities()
|
|
{
|
|
bool isConfigured = false;
|
|
|
|
#if SSC_ENTITIES
|
|
int numObstaclePrefabs = obstaclePrefabs == null ? 0 : obstaclePrefabs.Length;
|
|
|
|
// Create empty lists with sufficient capacity
|
|
asteriodEntityArchetypeList = new List<EntityArchetype>(numObstaclePrefabs > 0 ? numObstaclePrefabs : 1);
|
|
asteriodPrefabEntityList = new List<Entity>(numObstaclePrefabs > 0 ? numObstaclePrefabs : 1);
|
|
|
|
if (numObstaclePrefabs > 0 && useDOTS)
|
|
{
|
|
asteroidWorld = DOTSHelper.GetDefaultWorld();
|
|
|
|
if (asteroidWorld != null)
|
|
{
|
|
#region Construct Entity Archetypes
|
|
|
|
#if UNITY_2019_3_OR_NEWER || UNITY_ENTITIES_0_2_0_OR_NEWER
|
|
blobAssetStore = new BlobAssetStore();
|
|
GameObjectConversionSettings conversionSettings = GameObjectConversionSettings.FromWorld(asteroidWorld, blobAssetStore);
|
|
#endif
|
|
|
|
// Reserve some space for colliders
|
|
List<Collider> colliderList = new List<Collider>(4);
|
|
|
|
for (int pfIdx = 0; pfIdx < numObstaclePrefabs; pfIdx++)
|
|
{
|
|
// NOTE: Currently, if there are Colliders on child gameobjects in the prefab, it doesn't render
|
|
// after being converted to an entity.
|
|
|
|
obstaclePrefabs[pfIdx].GetComponentsInChildren<Collider>(colliderList);
|
|
|
|
int numColliders = colliderList == null ? 0 : colliderList.Count;
|
|
|
|
for (int clIdx = 0; clIdx < numColliders; clIdx++)
|
|
{
|
|
Collider collider = colliderList[clIdx];
|
|
// Ignore colliders on the parent
|
|
if (collider.transform != obstaclePrefabs[pfIdx])
|
|
{
|
|
if (collider.enabled) { collider.enabled = false; }
|
|
//Debug.Log("[DEBUG] collider: " + collider.name);
|
|
}
|
|
}
|
|
|
|
//if (collider != null) { Debug.Log("[DEBUG] collider: " + collider.name); }
|
|
|
|
// U2019.3 introduced Entities 0.2.0
|
|
#if UNITY_2019_3_OR_NEWER || UNITY_ENTITIES_0_2_0_OR_NEWER
|
|
Entity prefabEntity = GameObjectConversionUtility.ConvertGameObjectHierarchy(obstaclePrefabs[pfIdx].gameObject, conversionSettings);
|
|
#else
|
|
// U2019.1, 2019.2 Entities 0.012 preview 33 to 0.1.1 preview.
|
|
Entity prefabEntity = GameObjectConversionUtility.ConvertGameObjectHierarchy(obstaclePrefabs[pfIdx].gameObject, asteroidWorld);
|
|
#endif
|
|
|
|
asteriodPrefabEntityList.Add(prefabEntity);
|
|
|
|
// NOTE: We many only need one of these for all the Asteroid prefabs
|
|
ComponentType[] archetypeComponentTypeArray = new ComponentType[4];
|
|
|
|
// With ConvertGameObjectHierarchy not sure if we need Translation and Rotation here given they are automatically added
|
|
archetypeComponentTypeArray[0] = typeof(Translation);
|
|
archetypeComponentTypeArray[1] = typeof(Rotation);
|
|
archetypeComponentTypeArray[2] = typeof(Asteroid);
|
|
|
|
// NOTE: Requires hybrid renderer
|
|
archetypeComponentTypeArray[3] = typeof(RenderMesh);
|
|
EntityArchetype entityArcheType = asteroidWorld.EntityManager.CreateArchetype(archetypeComponentTypeArray);
|
|
asteriodEntityArchetypeList.Add(entityArcheType);
|
|
}
|
|
|
|
#endregion
|
|
|
|
// Initialise Asteroid system
|
|
if (asteroidSystem == null)
|
|
{
|
|
asteroidSystem = asteroidWorld.GetOrCreateSystem<AsteroidSystem>();
|
|
}
|
|
}
|
|
|
|
isConfigured = asteroidSystem != null;
|
|
}
|
|
#endif
|
|
|
|
return isConfigured;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Methods
|
|
|
|
private void Update()
|
|
{
|
|
if (useDOTS) { return; }
|
|
else
|
|
{
|
|
float deltaTime = Time.deltaTime;
|
|
|
|
// Rotate all the obstacles
|
|
for (int aIdx = 0; aIdx < numObstaclesToRotate; aIdx++)
|
|
{
|
|
obstacleTrmList[aIdx].rotation *= Quaternion.AngleAxis(obstacleRotationRateList[aIdx] * deltaTime * Mathf.Rad2Deg, obstacleRotationDirectionList[aIdx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if SSC_ENTITIES && (UNITY_2019_3_OR_NEWER || UNITY_ENTITIES_0_2_0_OR_NEWER)
|
|
private void OnDestroy()
|
|
{
|
|
// Dispose of the BlobAssetStore, else we're get a message:
|
|
// A Native Collection has not been disposed, resulting in a memory leak.
|
|
if (blobAssetStore != null) { blobAssetStore.Dispose(); }
|
|
}
|
|
#endif
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
|
|
#endregion
|
|
}
|
|
} |