Added Feel plugin

This commit is contained in:
journaliciouz
2025-12-11 14:49:16 +01:00
parent 97dce4aaf6
commit 1942a531d4
2820 changed files with 257786 additions and 9 deletions

View File

@@ -0,0 +1,157 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace MoreMountains.Tools
{
/// <summary>
/// This class lets you randomly spawn objects within its bounds (defined by a 3D collider)
/// </summary>
[RequireComponent(typeof(Collider))]
public class MMRandomBoundsInstantiator : MonoBehaviour
{
/// the possible start modes
public enum StartModes { Awake, Start, None }
/// the possible scale modes you can use to rescale objects
public enum ScaleModes { Uniform, Vector3 }
[Header("Random instantiation")]
/// whether this instantiator should auto trigger on Awake, Start, or never
public StartModes StartMode = StartModes.Awake;
/// the name to give to the instantiated object
public string InstantiatedObjectName = "RandomInstantiated";
/// if this is true, the instantiated object will be parented to the spawner
public bool ParentInstantiatedToThisObject = true;
/// if this is true, every time InstantiateRandomObject is called, any previously instantiated object will be destroyed
public bool DestroyPreviouslyInstantiatedObjects = true;
[Header("Spawn")]
/// the list containing all the objects that can potentially be instantiated
public List<GameObject> RandomPool;
/// the min and max bounds to use to determine a random quantity of objects to spawn
[MMVector("Min", "Max")]
public Vector2Int Quantity = new Vector2Int(1, 1);
[Header("Scale")]
/// the scale mode to use (uniform scales the whole object, Vector3 randomizes x, y and z scale elements
public ScaleModes ScaleMode = ScaleModes.Uniform;
/// the min scale to use in uniform mode
[MMEnumCondition("ScaleMode", (int)ScaleModes.Uniform)]
public float MinScale = 1f;
/// the max scale to use in uniform mode
[MMEnumCondition("ScaleMode", (int)ScaleModes.Uniform)]
public float MaxScale = 1f;
/// the min scale to use in vector3 mode
[MMEnumCondition("ScaleMode", (int)ScaleModes.Vector3)]
public Vector3 MinVectorScale = Vector3.one;
/// the max scale to use in vector3 mode
[MMEnumCondition("ScaleMode", (int)ScaleModes.Vector3)]
public Vector3 MaxVectorScale = Vector3.one;
[Header("Test")]
/// a test button for your inspector
[MMInspectorButton("Instantiate")]
public bool InstantiateButton;
protected Collider _collider;
protected List<GameObject> _instantiatedGameObjects;
protected Vector3 _newScale = Vector3.zero;
/// <summary>
/// On awake we instantiate if needed
/// </summary>
protected virtual void Awake()
{
_collider = this.gameObject.GetComponent<Collider>();
if (StartMode == StartModes.Awake)
{
Instantiate();
}
}
/// <summary>
/// On Start we instantiate if needed
/// </summary>
protected virtual void Start()
{
if (StartMode == StartModes.Start)
{
Instantiate();
}
}
/// <summary>
/// Instantiates as many objects as needed, clearing previously existing ones if needed
/// </summary>
protected virtual void Instantiate()
{
if (_instantiatedGameObjects == null)
{
_instantiatedGameObjects = new List<GameObject>();
}
// we destroy our previous object if needed
if (DestroyPreviouslyInstantiatedObjects)
{
foreach(GameObject go in _instantiatedGameObjects)
{
DestroyImmediate(go);
}
_instantiatedGameObjects.Clear();
}
int random = Random.Range(Quantity.x, Quantity.y);
for (int i = 0; i < random; i++)
{
InstantiateRandomObject();
}
}
/// <summary>
/// Spawns a random object from the pool of choices
/// </summary>
public virtual void InstantiateRandomObject()
{
// if the pool is empty we do nothing and exit
if (RandomPool.Count == 0)
{
return;
}
// pick a random object and instantiates it
int randomIndex = Random.Range(0, RandomPool.Count);
GameObject obj = Instantiate(RandomPool[randomIndex], this.transform.position, this.transform.rotation);
SceneManager.MoveGameObjectToScene(obj.gameObject, this.gameObject.scene);
// we pick a random point within the bounds then move it to account for rotation/scale
obj.transform.position = MMBoundsExtensions.MMRandomPointInBounds(_collider.bounds);
obj.transform.position = _collider.ClosestPoint(obj.transform.position);
// we name and parent our object
obj.name = InstantiatedObjectName;
if (ParentInstantiatedToThisObject)
{
obj.transform.SetParent(this.transform);
}
// we rescale the object
switch (ScaleMode)
{
case ScaleModes.Uniform:
float newScale = Random.Range(MinScale, MaxScale);
obj.transform.localScale = Vector3.one * newScale;
break;
case ScaleModes.Vector3:
_newScale = MMMaths.RandomVector3(MinVectorScale, MaxVectorScale);
obj.transform.localScale = _newScale;
break;
}
// we add it to our list
_instantiatedGameObjects.Add(obj);
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 290476dd0718b944c9de1d397bfa0f4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Accessories/MMInstantiation/MMRandomBoundsInstantiator.cs
uploadId: 830868

View File

@@ -0,0 +1,90 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace MoreMountains.Tools
{
/// <summary>
/// Add this class to an empty object, bind a few prefabs into its RandomPool slots, and it'll instantiate one of them at its position/rotation on Start or Awake
/// You can also call its InstantiateRandomObject method at any time, and it'll instantiate another random object on demand,
/// potentially destroying the previous one if you decide so
/// </summary>
public class MMRandomInstantiator : MonoBehaviour
{
/// the possible start modes
public enum StartModes { Awake, Start, None }
[Header("Random instantiation")]
/// whether this instantiator should auto trigger on Awake, Start, or never
public StartModes StartMode = StartModes.Awake;
/// the name to give to the instantiated object
public string InstantiatedObjectName = "RandomInstantiated";
/// if this is true, the instantiated object will be parented to the spawner
public bool ParentInstantiatedToThisObject = true;
/// if this is true, every time InstantiateRandomObject is called, any previously instantiated object will be destroyed
public bool DestroyPreviouslyInstantiatedObject = true;
/// the list containing all the objects that can potentially be instantiated
public List<GameObject> RandomPool;
[Header("Test")]
/// a test button for your inspector
[MMInspectorButton("InstantiateRandomObject")]
public bool InstantiateButton;
protected GameObject _instantiatedGameObject;
/// <summary>
/// On awake we instantiate if needed
/// </summary>
protected virtual void Awake()
{
if (StartMode == StartModes.Awake)
{
InstantiateRandomObject();
}
}
/// <summary>
/// On Start we instantiate if needed
/// </summary>
protected virtual void Start()
{
if (StartMode == StartModes.Start)
{
InstantiateRandomObject();
}
}
/// <summary>
/// Spawns a random object from the pool of choices
/// </summary>
public virtual void InstantiateRandomObject()
{
// if the pool is empty we do nothing and exit
if (RandomPool.Count == 0)
{
return;
}
// we destroy our previous object if needed
if (DestroyPreviouslyInstantiatedObject)
{
if (_instantiatedGameObject != null)
{
DestroyImmediate(_instantiatedGameObject);
}
}
// pick a random object and instantiates it
int randomIndex = Random.Range(0, RandomPool.Count);
_instantiatedGameObject = Instantiate(RandomPool[randomIndex], this.transform.position, this.transform.rotation);
SceneManager.MoveGameObjectToScene(_instantiatedGameObject, this.gameObject.scene);
_instantiatedGameObject.name = InstantiatedObjectName;
if (ParentInstantiatedToThisObject)
{
_instantiatedGameObject.transform.SetParent(this.transform);
}
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4c62e684cc894124e8d7b2992e2118b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Accessories/MMInstantiation/MMRandomInstantiator.cs
uploadId: 830868

View File

@@ -0,0 +1,233 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
/// <summary>
/// This class is used to describe spawn properties, to be used by the MMSpawnAround class.
/// It's meant to be exposed and used by classes that are designed to spawn objects, typically loot systems
/// </summary>
[System.Serializable]
public class MMSpawnAroundProperties
{
/// the possible shapes objects can be spawned within
public enum MMSpawnAroundShapes { Sphere, Cube }
/// the shape within which objects should spawn
[Header("Shape")]
[Tooltip("the shape within which objects should spawn")]
public MMSpawnAroundShapes Shape = MMSpawnAroundShapes.Sphere;
[Header("Position")]
/// the minimum distance to the origin of the spawn at which objects can be spawned
[Tooltip("the minimum distance to the origin of the spawn at which objects can be spawned")]
[MMEnumCondition("Shape", (int)MMSpawnAroundShapes.Sphere)]
public float MinimumSphereRadius = 1f;
/// the maximum distance to the origin of the spawn at which objects can be spawned
[Tooltip("the maximum distance to the origin of the spawn at which objects can be spawned")]
[MMEnumCondition("Shape", (int)MMSpawnAroundShapes.Sphere)]
public float MaximumSphereRadius = 2f;
/// the minimum size of the cube's base
[Tooltip("the minimum size of the cube's base")]
[MMEnumCondition("Shape", (int)MMSpawnAroundShapes.Cube)]
public Vector3 MinimumCubeBaseSize = Vector3.one;
/// the maximum size of the cube's base
[Tooltip("the maximum size of the cube's base")]
[MMEnumCondition("Shape", (int)MMSpawnAroundShapes.Cube)]
public Vector3 MaximumCubeBaseSize = new Vector3(2f, 2f, 2f);
[Header("Plane")]
/// if this is true, spawn will be constrained to the plane defined by the NormalToSpawnPlane property
[Tooltip("if this is true, spawn will be constrained to the plane defined by the NormalToSpawnPlane property")]
public bool ForcePlane = true;
/// a Vector3 that specifies the normal to the plane you want to spawn objects on (if you want to spawn objects on the x/z plane, the normal to that plane would be the y axis (0,1,0)
[Tooltip("a Vector3 that specifies the normal to the plane you want to spawn objects on (if you want to spawn objects on the x/z plane, the normal to that plane would be the y axis (0,1,0)")]
public Vector3 NormalToSpawnPlane = Vector3.up;
[Header("NormalAxisOffset")]
/// the minimum offset to apply on the normal axis
[Tooltip("the minimum offset to apply on the normal axis")]
public float MinimumNormalAxisOffset = 0f;
/// the maximum offset to apply on the normal axis
[Tooltip("the maximum offset to apply on the normal axis")]
public float MaximumNormalAxisOffset = 0f;
[Header("NormalAxisOffsetCurve")]
/// whether or not to use a curve to offset the object's spawn position along the spawn plane
[Tooltip("whether or not to use a curve to offset the object's spawn position along the spawn plane")]
public bool UseNormalAxisOffsetCurve = false;
/// a curve used to define how distance to the origin should be altered (potentially above min/max distance)
[Tooltip("a curve used to define how distance to the origin should be altered (potentially above min/max distance)")]
[MMCondition("UseNormalAxisOffsetCurve",true)]
public AnimationCurve NormalOffsetCurve = new AnimationCurve(new Keyframe(0, 1f), new Keyframe(1, 1f));
/// the value to which the curve's zero should be remapped to
[Tooltip("the value to which the curve's zero should be remapped to")]
[MMCondition("UseNormalAxisOffsetCurve",true)]
public float NormalOffsetCurveRemapZero = 0f;
/// the value to which the curve's one should be remapped to
[Tooltip("the value to which the curve's one should be remapped to")]
[MMCondition("UseNormalAxisOffsetCurve",true)]
public float NormalOffsetCurveRemapOne = 1f;
/// whether or not to invert the curve (horizontally)
[Tooltip("whether or not to invert the curve (horizontally)")]
[MMCondition("UseNormalAxisOffsetCurve",true)]
public bool InvertNormalOffsetCurve = false;
[Header("Rotation")]
/// the minimum random rotation to apply (in degrees)
[Tooltip("the minimum random rotation to apply (in degrees)")]
public Vector3 MinimumRotation = Vector3.zero;
/// the maximum random rotation to apply (in degrees)
[Tooltip("the maximum random rotation to apply (in degrees)")]
public Vector3 MaximumRotation = Vector3.zero;
[Header("Scale")]
/// the minimum random scale to apply
[Tooltip("the minimum random scale to apply")]
public Vector3 MinimumScale = Vector3.one;
/// the maximum random scale to apply
[Tooltip("the maximum random scale to apply")]
public Vector3 MaximumScale = Vector3.one;
}
/// <summary>
/// This static class is a spawn helper, useful to randomize position, rotation and scale when you need to
/// instantiate objects
/// </summary>
public static class MMSpawnAround
{
public static void ApplySpawnAroundProperties(GameObject instantiatedObj, MMSpawnAroundProperties props, Vector3 origin)
{
// we randomize the position
instantiatedObj.transform.position = SpawnAroundPosition(props, origin);
// we randomize the rotation
instantiatedObj.transform.rotation = SpawnAroundRotation(props);
// we randomize the scale
instantiatedObj.transform.localScale = SpawnAroundScale(props);
}
/// <summary>
/// Returns the position at which the object should spawn
/// </summary>
/// <param name="props"></param>
/// <param name="origin"></param>
/// <returns></returns>
public static Vector3 SpawnAroundPosition(MMSpawnAroundProperties props, Vector3 origin)
{
// we get the position of the object based on the defined plane and distance
Vector3 newPosition;
if (props.Shape == MMSpawnAroundProperties.MMSpawnAroundShapes.Sphere)
{
float distance = Random.Range(props.MinimumSphereRadius, props.MaximumSphereRadius);
newPosition = Random.insideUnitSphere;
if (props.ForcePlane)
{
newPosition = Vector3.Cross(newPosition, props.NormalToSpawnPlane);
}
newPosition.Normalize();
newPosition *= distance;
}
else
{
newPosition = PickPositionInsideCube(props);
if (props.ForcePlane)
{
newPosition = Vector3.Cross(newPosition, props.NormalToSpawnPlane);
}
}
float randomOffset = Random.Range(props.MinimumNormalAxisOffset, props.MaximumNormalAxisOffset);
// we correct the position based on the NormalOffsetCurve
if (props.UseNormalAxisOffsetCurve)
{
float normalizedOffset = 0f;
if (randomOffset != 0)
{
if (props.InvertNormalOffsetCurve)
{
normalizedOffset = MMMaths.Remap(randomOffset, props.MinimumNormalAxisOffset, props.MaximumNormalAxisOffset, 1f, 0f);
}
else
{
normalizedOffset = MMMaths.Remap(randomOffset, props.MinimumNormalAxisOffset, props.MaximumNormalAxisOffset, 0f, 1f);
}
}
float offset = props.NormalOffsetCurve.Evaluate(normalizedOffset);
offset = MMMaths.Remap(offset, 0f, 1f, props.NormalOffsetCurveRemapZero, props.NormalOffsetCurveRemapOne);
newPosition *= offset;
}
// we apply the normal offset
newPosition += props.NormalToSpawnPlane.normalized * randomOffset;
// relative position
newPosition += origin;
return newPosition;
}
public static Vector3 PickPositionInsideCube(MMSpawnAroundProperties props)
{
int iterationsCount = 0;
int maxIterationsCount = 1000;
while (iterationsCount < maxIterationsCount)
{
float randomX = Random.Range(0f, props.MaximumCubeBaseSize.x);
float randomY = Random.Range(0f, props.MaximumCubeBaseSize.y);
float randomZ = Random.Range(0f, props.MaximumCubeBaseSize.z);
if (randomX < props.MinimumCubeBaseSize.x && randomY < props.MinimumCubeBaseSize.y && randomZ < props.MinimumCubeBaseSize.z)
{
iterationsCount++;
continue;
}
else
{
randomX = MMMaths.RollADice(2) > 1 ? -randomX : randomX;
randomY = MMMaths.RollADice(2) > 1 ? -randomY : randomY;
randomZ = MMMaths.RollADice(2) > 1 ? -randomZ : randomZ;
return new Vector3(randomX, randomY, randomZ);
}
}
return Vector3.zero;
}
/// <summary>
/// Returns the scale at which the object should spawn
/// </summary>
/// <param name="props"></param>
/// <returns></returns>
public static Vector3 SpawnAroundScale(MMSpawnAroundProperties props)
{
return MMMaths.RandomVector3(props.MinimumScale, props.MaximumScale);
}
/// <summary>
/// Returns the rotation at which the object should spawn
/// </summary>
/// <param name="props"></param>
/// <returns></returns>
public static Quaternion SpawnAroundRotation(MMSpawnAroundProperties props)
{
return Quaternion.Euler(MMMaths.RandomVector3(props.MinimumRotation, props.MaximumRotation));
}
/// <summary>
/// Draws gizmos to show the shape of the spawn area
/// </summary>
/// <param name="props"></param>
/// <param name="origin"></param>
/// <param name="quantity"></param>
/// <param name="size"></param>
public static void DrawGizmos(MMSpawnAroundProperties props, Vector3 origin, int quantity, float size, Color gizmosColor)
{
Gizmos.color = gizmosColor;
for (int i = 0; i < quantity; i++)
{
Gizmos.DrawCube(SpawnAroundPosition(props, origin), SpawnAroundScale(props) * size);
}
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b9f71c3d801e5f84b88c2cb7256503ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Accessories/MMInstantiation/MMSpawnAround.cs
uploadId: 830868

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace MoreMountains.Tools
{
/// <summary>
/// A tester class used to show how the MMSpawnAround class can be used
/// </summary>
public class MMSpawnAroundTester : MonoBehaviour
{
/// a GameObject to instantiate and position around this object
public GameObject ObjectToInstantiate;
/// the spawn properties to consider when spawning the ObjectToInstantiate
public MMSpawnAroundProperties SpawnProperties;
[Header("Debug")]
/// the amount of objects to spawn
public int DebugQuantity = 10000;
/// a test button
[MMInspectorButton("DebugSpawn")]
public bool DebugSpawnButton;
[Header("Gizmos")]
/// whether or not to draw gizmos to show the shape of the spawn area
public bool DrawGizmos = false;
/// the amount of gizmos to draw
public int GizmosQuantity = 1000;
/// the size at which to draw the gizmos
public float GizmosSize = 1f;
protected GameObject _gameObject;
/// <summary>
/// A test method that spawns DebugQuantity objects
/// </summary>
public virtual void DebugSpawn()
{
for (int i = 0; i < DebugQuantity; i++)
{
Spawn();
}
}
/// <summary>
/// Spawns a single object and positions it correctly
/// </summary>
public virtual void Spawn()
{
_gameObject = Instantiate(ObjectToInstantiate);
SceneManager.MoveGameObjectToScene(_gameObject, this.gameObject.scene);
MMSpawnAround.ApplySpawnAroundProperties(_gameObject, SpawnProperties, this.transform.position);
}
/// <summary>
/// OnDrawGizmos, we draw the shape of the area within which objects will spawn
/// </summary>
protected virtual void OnDrawGizmos()
{
if (DrawGizmos)
{
MMSpawnAround.DrawGizmos(SpawnProperties, this.transform.position, GizmosQuantity, GizmosSize, Color.gray);
}
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 25aad3ee60dba574fb749c6ea78d559d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Accessories/MMInstantiation/MMSpawnAroundTester.cs
uploadId: 830868