This commit is contained in:
2026-06-04 10:42:23 +07:00
parent e7e90790c9
commit 9be2242378
4166 changed files with 53005 additions and 11401 deletions

View File

@@ -0,0 +1,120 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
namespace Invector
{
public class vAudioSurface : ScriptableObject
{
public AudioSource audioSource;
public AudioMixerGroup audioMixerGroup; // The AudioSource that will play the clips.
public List<string> TextureOrMaterialNames; // The tag on the surfaces that play these sounds.
public List<AudioClip> audioClips; // The different clips that can be played on this surface.
public GameObject particleObject;
private vFisherYatesRandom randomSource = new vFisherYatesRandom(); // For randomly reordering clips.
public bool useStepMark;
[vHideInInspector("useStepMark")]
public GameObject stepMark;
[vHideInInspector("useStepMark")]
public LayerMask stepLayer;
[vHideInInspector("useStepMark")]
public float timeToDestroy = 5f;
public vAudioSurface()
{
audioClips = new List<AudioClip>();
TextureOrMaterialNames = new List<string>();
}
/// <summary>
/// Spawn surface effect
/// </summary>
/// <param name="footStepObject">step object surface info</param>
/// <param name="playSound">Spawn sound effect</param>
/// <param name="spawnParticle">Spawn particle effect</param>
/// <param name="spawnStepMark">Spawn step Mark effect</param>
public virtual void SpawnSurfaceEffect(FootStepObject footStepObject)
{
// initialize variable if not already started
if (randomSource == null)
{
randomSource = new vFisherYatesRandom();
}
///Create audio Effect
if (footStepObject.spawnSoundEffect)
{
PlaySound(footStepObject);
}
///Create particle Effect
if (footStepObject.spawnParticleEffect && particleObject && footStepObject.ground && stepLayer.ContainsLayer(footStepObject.ground.gameObject.layer))
{
SpawnParticle(footStepObject);
}
///Create Step Mark Effect
if (footStepObject.spawnStepMarkEffect && useStepMark)
{
StepMark(footStepObject);
}
}
/// <summary>
/// Spawn Sound effect
/// </summary>
/// <param name="footStepObject">Step object surface info</param>
protected virtual void PlaySound(FootStepObject footStepObject)
{
// if there are no clips to play return.
if (audioClips == null || audioClips.Count == 0)
{
return;
}
AudioSource source = null;
if (audioSource != null)
{
source = Instantiate(audioSource, footStepObject.sender.position, Quaternion.identity);
source.transform.SetParent(vObjectContainer.root, true);
}
if (audioSource)
{
if (audioMixerGroup != null)
{
source.outputAudioMixerGroup = audioMixerGroup;
}
}
int index = randomSource.Next(audioClips.Count);
source.PlayOneShot(audioClips[index], footStepObject.volume);
}
/// <summary>
/// Spawn Particle effect
/// </summary>
/// <param name="footStepObject">Step object surface info</param>
protected virtual void SpawnParticle(FootStepObject footStepObject)
{
var obj = Instantiate(particleObject, footStepObject.sender.position, footStepObject.sender.rotation);
obj.transform.SetParent(vObjectContainer.root, true);
}
/// <summary>
/// Spawn Step Mark effect
/// </summary>
/// <param name="footStepObject">Step object surface info</param>
protected virtual void StepMark(FootStepObject footStep)
{
RaycastHit hit;
if (Physics.Raycast(footStep.sender.transform.position + new Vector3(0, 0.25f, 0), Vector3.down, out hit, 1f, stepLayer))
{
if (stepMark)
{
var angle = Quaternion.FromToRotation(footStep.sender.up, hit.normal);
var step = Instantiate(stepMark, hit.point, angle * footStep.sender.rotation);
step.transform.SetParent(vObjectContainer.root, true);
Destroy(step, timeToDestroy);
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: af1c89896f5f7f6439ef1a93a23a0f3d
timeCreated: 1458347752
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: c98dd48f519049f42a092e26c1049b5b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using UnityEngine;
using UnityEngine.Audio;
namespace Invector
{
[RequireComponent(typeof(AudioSource))]
public class vAudioSurfaceControl : MonoBehaviour
{
AudioSource source;
bool isWorking;
/// <summary>
/// Play One Shot in Audio Source Component
/// </summary>
/// <param name="clip"></param>
public void PlayOneShot(AudioClip clip, float volume)
{
if (!source) source = GetComponent<AudioSource>();
source.volume = volume;
source.PlayOneShot(clip, volume);
isWorking = true;
}
void Update()
{
if (isWorking && !source.isPlaying)
{
Destroy(gameObject);
}
}
public AudioMixerGroup outputAudioMixerGroup
{
set
{
if (!source) source = GetComponent<AudioSource>();
source.outputAudioMixerGroup = value;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 669210f9e4e67104ca935da3d7059cbe
timeCreated: 1458351075
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,89 @@
using UnityEngine;
namespace Invector
{
// Fisher-Yates shuffle -- makes sure all items are selected with equal probability and that the same item is not selected twice in a row.
public class vFisherYatesRandom
{
private int[] randomIndices = null;
private int randomIndex = 0;
private int prevValue = -1;
public int Next(int len)
{
if (len <= 1)
return 0;
if (randomIndices == null || randomIndices.Length != len)
{
randomIndices = new int[len];
for (int i = 0; i < randomIndices.Length; i++)
randomIndices[i] = i;
}
if (randomIndex == 0)
{
int count = 0;
do
{
for (int i = 0; i < len - 1; i++)
{
int j = Random.Range(i, len);
if (j != i)
{
int tmp = randomIndices[i];
randomIndices[i] = randomIndices[j];
randomIndices[j] = tmp;
}
}
} while (prevValue == randomIndices[0] && ++count < 10); // Make sure the new first element is different from the last one we played
}
int value = randomIndices[randomIndex];
if (++randomIndex >= randomIndices.Length)
randomIndex = 0;
prevValue = value;
return value;
}
public int Range(int min, int max)
{
var len = (max - min) + 1;
if (len <= 1)
return max;
if (randomIndices == null || randomIndices.Length != len)
{
randomIndices = new int[len];
for (int i = 0; i < randomIndices.Length; i++)
randomIndices[i] = min + i;
}
if (randomIndex == 0)
{
int count = 0;
do
{
for (int i = 0; i < len - 1; i++)
{
int j = Random.Range(i, len);
if (j != i)
{
int tmp = randomIndices[i];
randomIndices[i] = randomIndices[j];
randomIndices[j] = tmp;
}
}
} while (prevValue == randomIndices[0] && ++count < 10); // Make sure the new first element is different from the last one we played
}
int value = randomIndices[randomIndex];
if (++randomIndex >= randomIndices.Length)
randomIndex = 0;
prevValue = value;
return value;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a158be35be5a63d4f838b1ac8b794ee1
timeCreated: 1424098475
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
using UnityEngine;
using System.Collections.Generic;
namespace Invector
{
[System.Serializable]
public abstract class vFootStepBase : MonoBehaviour
{
// The different surfaces and their sounds.
public vAudioSurface defaultSurface;
public List<vAudioSurface> customSurfaces;
/// <summary>
/// Play a foot step effect passing the <seealso cref="FootStepObject"/> to determine what surface is stepping
/// </summary>
/// <param name="footStepObject">Foot Step object with surface information</param>
/// <param name="spawnParticle">Spawn Particle ?</param>
/// <param name="spawnStepMark">Spwan Step Mark ?</param>
/// <param name="volume">Audio effect volume</param>
public virtual void SpawnSurfaceEffect(FootStepObject footStepObject)
{
if (footStepObject != null)
for (int i = 0; i < customSurfaces.Count; i++)
if (customSurfaces[i] != null && ContainsTexture(footStepObject.name, customSurfaces[i]))
{
customSurfaces[i].SpawnSurfaceEffect(footStepObject);
return;
}
if (defaultSurface != null)
{
defaultSurface.SpawnSurfaceEffect(footStepObject);
}
}
/// <summary>
/// Ccheck if AudioSurface Contains texture in TextureName List
/// </summary>
/// <param name="name"></param>
/// <param name="surface"></param>
/// <returns></returns>
protected virtual bool ContainsTexture(string name, vAudioSurface surface)
{
for (int i = 0; i < surface.TextureOrMaterialNames.Count; i++)
if (name.Contains(surface.TextureOrMaterialNames[i]))
return true;
return false;
}
/// <summary>
/// Step on Terrain
/// </summary>
/// <param name="footStepObject"></param>
public abstract void StepOnTerrain(FootStepObject footStepObject);
/// <summary>
/// Step on Mesh
/// </summary>
/// <param name="footStepObject"></param>
public abstract void StepOnMesh(FootStepObject footStepObject);
/// <summary>
/// Play foot Step sound
/// </summary>
public abstract void PlayFootStepEffect();
/// <summary>
/// Play Foot Step Effect directly using animation Event
/// </summary>
/// <param name="evt"></param>
public virtual void PlayFootStep(AnimationEvent evt) { }
/// <summary>
/// Play Left Foot Step Effect directly using animation Event
/// </summary>
/// <param name="evt"></param>
public virtual void PlayFootStepLeft(AnimationEvent evt) { }
/// <summary>
/// Play Right Foot Step Effect directly using animation Event
/// </summary>
/// <param name="evt"></param>
public virtual void PlayFootStepRight(AnimationEvent evt) { }
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7f7e8bb50a29daf42978b386b314983c
timeCreated: 1425028583
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,298 @@
using System.Collections.Generic;
using UnityEngine;
namespace Invector
{
public class vFootStep : vFootStepBase
{
public AnimationType animationType = AnimationType.Humanoid;
public bool debugTextureName;
[SerializeField, Range(0, 1f)] protected float _volume = 1f;
[vHelpBox("Enable or disable spawn particle when foot step is triggered")]
[SerializeField] protected bool _spawnParticle = true;
[vHelpBox("Enable or disable spawn step mark when foot step is triggered")]
[SerializeField] protected bool _spawnStepMark = true;
[vHelpBox("The step effect is spawned from on trigger enter event of the Foot Step Triggers. If you need to play step sound only by external events you need to disable this variable.<b>\n*Disable this to play step sound using animation events</b>")]
[SerializeField] protected bool _useTriggerEnter = true;
public float Volume { get { return _volume; } set { _volume = value; } }
public bool SpawnParticle { get { return _spawnParticle; } set { _spawnParticle = value; } }
public bool SpawnStepMark { get { return _spawnStepMark; } set { _spawnStepMark = value; } }
protected int surfaceIndex = 0;
protected Terrain terrain;
protected TerrainCollider terrainCollider;
protected TerrainData terrainData;
protected Vector3 terrainPos;
public vFootStepTrigger leftFootTrigger;
public vFootStepTrigger rightFootTrigger;
public Transform currentStep;
public List<vFootStepTrigger> footStepTriggers;
protected FootStepObject currentFootStep;
protected virtual void Start()
{
InitFootStep();
}
public virtual void InitFootStep()
{
var colls = GetComponentsInChildren<Collider>();
if (animationType == AnimationType.Humanoid)
{
if (leftFootTrigger == null && rightFootTrigger == null)
{
Debug.Log("Missing FootStep Sphere Trigger, please unfold the FootStep Component to create the triggers.");
return;
}
else
{
leftFootTrigger.trigger.isTrigger = true;
rightFootTrigger.trigger.isTrigger = true;
Physics.IgnoreCollision(leftFootTrigger.trigger, rightFootTrigger.trigger);
for (int i = 0; i < colls.Length; i++)
{
var coll = colls[i];
if (coll.enabled && coll.gameObject != leftFootTrigger.gameObject)
{
Physics.IgnoreCollision(leftFootTrigger.trigger, coll);
}
if (coll.enabled && coll.gameObject != rightFootTrigger.gameObject)
{
Physics.IgnoreCollision(rightFootTrigger.trigger, coll);
}
}
}
}
else
{
for (int i = 0; i < colls.Length; i++)
{
var coll = colls[i];
for (int a = 0; a < footStepTriggers.Count; a++)
{
var trigger = footStepTriggers[a];
trigger.trigger.isTrigger = true;
if (coll.enabled && coll.gameObject != trigger.gameObject)
{
Physics.IgnoreCollision(trigger.trigger, coll);
}
}
}
}
}
protected virtual void UpdateTerrainInfo(Terrain newTerrain)
{
if (terrain == null || terrain != newTerrain)
{
terrain = newTerrain;
if (terrain != null)
{
terrainData = terrain.terrainData;
terrainPos = terrain.transform.position;
terrainCollider = terrain.GetComponent<TerrainCollider>();
}
}
}
protected virtual float[] GetTextureMix(FootStepObject footStepObj)
{
// returns an array containing the relative mix of textures
// on the main terrain at this world position.
// The number of values in the array will equal the number
// of textures added to the terrain.
UpdateTerrainInfo(footStepObj.terrain);
// calculate which splat map cell the worldPos falls within (ignoring y)
var worldPos = footStepObj.sender.position;
int mapX = (int)(((worldPos.x - terrainPos.x) / terrainData.size.x) * terrainData.alphamapWidth);
int mapZ = (int)(((worldPos.z - terrainPos.z) / terrainData.size.z) * terrainData.alphamapHeight);
// get the splat data for this cell as a 1x1xN 3d array (where N = number of textures)
if (!terrainCollider.bounds.Contains(worldPos))
{
return new float[0];
}
float[,,] splatmapData = terrainData.GetAlphamaps(mapX, mapZ, 1, 1);
// extract the 3D array data to a 1D array:
float[] cellMix = new float[splatmapData.GetUpperBound(2) + 1];
for (int n = 0; n < cellMix.Length; n++)
{
cellMix[n] = splatmapData[0, 0, n];
}
return cellMix;
}
protected virtual int GetMainTexture(FootStepObject footStepObj)
{
// returns the zero-based index of the most dominant texture
// on the main terrain at this world position.
float[] mix = GetTextureMix(footStepObj);
if (mix == null)
{
return -1;
}
float maxMix = 0;
int maxIndex = 0;
// loop through each mix value and find the maximum
for (int n = 0; n < mix.Length; n++)
{
if (mix[n] > maxMix)
{
maxIndex = n;
maxMix = mix[n];
}
}
return maxIndex;
}
protected virtual void OnDestroy()
{
if (leftFootTrigger != null)
{
Destroy(leftFootTrigger.gameObject);
}
if (rightFootTrigger != null)
{
Destroy(rightFootTrigger.gameObject);
}
if (footStepTriggers != null && footStepTriggers.Count > 0)
{
foreach (var comp in footStepTriggers)
{
Destroy(comp.gameObject);
}
}
}
/// <summary>
/// Step on Terrain
/// </summary>
/// <param name="footStepObject"></param>
public override void StepOnTerrain(FootStepObject footStepObject)
{
if (currentStep != null && currentStep == footStepObject.sender && _useTriggerEnter)
{
return;
}
currentStep = footStepObject.sender;
surfaceIndex = GetMainTexture(footStepObject);
if (surfaceIndex != -1)
{
#if UNITY_2018_3_OR_NEWER
var name = (terrainData != null && terrainData.terrainLayers.Length > 0) ? (terrainData.terrainLayers[surfaceIndex]).diffuseTexture.name : "";
#else
var name = (terrainData != null && terrainData.splatPrototypes.Length > 0) ? (terrainData.splatPrototypes[surfaceIndex]).texture.name : "";
#endif
footStepObject.name = name;
currentFootStep = footStepObject;
if (_useTriggerEnter)
{
PlayFootStepEffect();
if (debugTextureName)
{
Debug.Log(terrain.name + " " + name);
}
}
}
}
/// <summary>
/// Step on Mesh
/// </summary>
/// <param name="footStepObject"></param>
public override void StepOnMesh(FootStepObject footStepObject)
{
if (currentStep != null && currentStep == footStepObject.sender && _useTriggerEnter)
{
return;
}
currentStep = footStepObject.sender;
currentFootStep = footStepObject;
if (_useTriggerEnter)
{
PlayFootStepEffect();
if (debugTextureName)
{
Debug.Log(footStepObject.name);
}
}
}
/// <summary>
/// Play foot Step effect
/// </summary>
public override void PlayFootStepEffect()
{
if (currentFootStep != null)
{
currentFootStep.volume = Volume;
currentFootStep.spawnParticleEffect = SpawnParticle;
currentFootStep.spawnStepMarkEffect = SpawnStepMark;
SpawnSurfaceEffect(currentFootStep);
}
}
/// <summary>
/// Play foot step effect from animation event
/// </summary>
/// <param name="evt"></param>
public override void PlayFootStep(AnimationEvent evt)
{
if (evt.animatorClipInfo.weight > 0.5)
{
PlayFootStepEffect();
}
}
/// <summary>
/// Play left foot step effect from animation event
/// </summary>
/// <param name="evt"></param>
public override void PlayFootStepLeft(AnimationEvent evt)
{
if (evt.animatorClipInfo.weight > 0.5)
{
currentFootStep.sender = leftFootTrigger.transform;
PlayFootStepEffect();
}
}
/// <summary>
/// Play right foot step effect from animation event
/// </summary>
/// <param name="evt"></param>
public override void PlayFootStepRight(AnimationEvent evt)
{
if (evt.animatorClipInfo.weight > 0.15)
{
currentFootStep.sender = rightFootTrigger.transform;
PlayFootStepEffect();
}
}
}
public enum AnimationType
{
Humanoid, Generic
}
}

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 19ce6a4d1f67f494f8e871355031f21c
timeCreated: 1500492219
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences:
- defaultSurface: {fileID: 11400000, guid: 710c74baecce50746bd56b9711c851e7, type: 2}
- leftFootTrigger: {instanceID: 0}
- rightFootTrigger: {instanceID: 0}
- currentStep: {instanceID: 0}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using UnityEngine;
namespace Invector
{
public class vFootStepHandler : MonoBehaviour
{
[Tooltip("Use this to select a specific material or texture if your mesh has multiple materials, the footstep will play only the selected index.")]
[SerializeField]
private int materialIndex = 0;
public int material_ID
{
get
{
return materialIndex;
}
}
public StepHandleType stepHandleType;
public enum StepHandleType
{
materialName,
textureName
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 153bfba017503c74d919ab3c55d40277
timeCreated: 1445535775
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,141 @@
using UnityEngine;
using UnityEngine.Events;
namespace Invector
{
public class vFootStepTrigger : MonoBehaviour
{
protected Collider _trigger;
protected vFootStepBase _fT;
public UnityEvent OnStep;
void OnDrawGizmos()
{
if (!trigger) return;
Color color = Color.green;
color.a = 0.5f;
Gizmos.color = color;
if (trigger is SphereCollider)
{
Gizmos.DrawSphere((trigger.bounds.center), (trigger as SphereCollider).radius);
}
}
void Start()
{
_fT = GetComponentInParent<vFootStepBase>();
var r = gameObject.GetComponent<Rigidbody>();
if (r == null)
gameObject.AddComponent<Rigidbody>().isKinematic = true;
else
r.isKinematic = true;
if (_fT == null)
{
Debug.Log(gameObject.name + " can't find the FootStepFromTexture");
gameObject.SetActive(false);
}
else
{
var colliders = _fT.gameObject.GetComponentsInChildren<Collider>(true);
for (int i = 0; i < colliders.Length; i++)
{
var c = colliders[i];
if (c!=null && c.gameObject != trigger.gameObject)
{
Physics.IgnoreCollision(c, trigger, true);
}
}
}
}
public Collider trigger
{
get
{
if (_trigger == null) _trigger =gameObject.GetComponent<Collider>();
return _trigger;
}
}
protected Collider lastCollider;
internal FootStepObject footstepObj;
void OnTriggerEnter(Collider other)
{
if (_fT == null) return;
if ((lastCollider == null || lastCollider != other) || footstepObj == null)
{
footstepObj = new FootStepObject(transform, other);
lastCollider = other;
}
if (footstepObj.isTerrain) //Check if trigger objet is a terrain
{
_fT.StepOnTerrain(footstepObj);
OnStep.Invoke();
}
else
{
_fT.StepOnMesh(footstepObj);
OnStep.Invoke();
}
}
}
/// <summary>
/// Foot step Object work with FootStepFromTexture
/// </summary>
public class FootStepObject
{
public string name;
public Transform sender;
public Collider ground;
public Terrain terrain;
public bool isTerrain { get { return terrain != null; } }
public vFootStepHandler stepHandle;
public Renderer renderer;
public bool spawnSoundEffect;
public bool spawnStepMarkEffect;
public bool spawnParticleEffect;
public float volume;
public FootStepObject(Transform sender, Collider ground)
{
this.name = "";
this.sender = sender;
this.ground = ground;
this.terrain = ground.GetComponent<Terrain>();
this.stepHandle = ground.GetComponent<vFootStepHandler>();
this.renderer = ground.GetComponent<Renderer>();
spawnSoundEffect = true;
spawnStepMarkEffect = true;
spawnParticleEffect = true;
volume = 1;
if (renderer != null && renderer.material != null)
{
var index = 0;
this.name = string.Empty;
if (stepHandle != null && stepHandle.material_ID > 0)// if trigger contains a StepHandler to pass material ID. Default is (0)
index = stepHandle.material_ID;
if (stepHandle)
{
// check stepHandlerType
switch (stepHandle.stepHandleType)
{
case vFootStepHandler.StepHandleType.materialName:
this.name = renderer.materials[index].name;
break;
case vFootStepHandler.StepHandleType.textureName:
this.name = renderer.materials[index].mainTexture.name;
break;
}
}
else
this.name = renderer.materials[index].name;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1b04f4ac1b776ff48b5f78a97b57f776
timeCreated: 1429924045
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: