projectEli/Assets/NeoFPS/Samples/SinglePlayer/Scenes/FeatureDemos/FiringRange/FiringRangeSequencer.cs
2022-11-06 20:28:33 -05:00

391 lines
12 KiB
C#

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using NeoSaveGames.Serialization;
using NeoSaveGames;
namespace NeoFPS.Samples.SinglePlayer
{
[HelpURL("https://docs.neofps.com/manual/samplesref-mb-firingrangesequencer.html")]
public class FiringRangeSequencer : MonoBehaviour, INeoSerializableComponent
{
[SerializeField, Range (1.5f, 10f), Tooltip("The pause in between each wave.")]
private float m_TimeBetweenWaves = 3f;
[SerializeField, Tooltip("The targets for each wave.")]
private TargetGroup[] m_Targets = new TargetGroup[0];
[SerializeField, Tooltip("An event that is invoked when a target is hit.")]
private IntEvent m_OnHitsChanged = new IntEvent();
[SerializeField, Tooltip("An event that is invoked when a target is missed.")]
private IntEvent m_OnMissesChanged = new IntEvent();
[SerializeField, Tooltip("The audio source for playing one shot firing range audio clips.")]
private AudioSource m_AudioSource = null;
[SerializeField, Tooltip("The audio clip to play when the sequence starts.")]
private AudioClip m_AudioStart = null;
[SerializeField, Tooltip("The audio clip to play when the sequence is cancelled.")]
private AudioClip m_AudioCancel = null;
[SerializeField, Tooltip("The audio clip to play when a target is hit.")]
private AudioClip m_AudioHit = null;
private Coroutine m_SequenceCoroutine = null;
private int m_Wave = 0;
private int m_Spawned = 0;
private int m_TargetCount = 0;
private float m_Timer = 0f;
private float m_ButtonCooldown = 0f;
private SequenceState m_State = SequenceState.Stopped;
private enum SequenceState
{
Stopped,
WaveStart,
WavePhase,
Reset,
Waiting
}
[Serializable]
public class IntEvent : UnityEvent<int> {}
[Serializable]
public class TargetGroup
{
[Tooltip("The targets for this wave.")]
public FiringRangeTarget[] targets;
[Tooltip("The total number of targets to pop up this wave.")]
public int total = 5;
[Tooltip("The number of targets to pop up for each step of the wave.")]
public int perStep = 1;
[Tooltip("Should the targets be chosen at random or in sequence.")]
public bool randomise = false;
[Tooltip("The duration a target should stay up.")]
public float duration = 5f;
[Tooltip("The delay between steps.")]
public float delay = 6f;
public void OnValidate ()
{
if (total < 1)
total = 1;
if (perStep < 1)
perStep = 1;
if (duration < 0.5f)
duration = 0.5f;
if (delay < 0.5f)
delay = 0.5f;
}
}
#if UNITY_EDITOR
void OnValidate()
{
if (m_Targets != null)
{
for (int i = 0; i < m_Targets.Length; ++i)
m_Targets[i].OnValidate();
}
}
#endif
private int m_Hits = 0;
public int hits
{
get { return m_Hits; }
private set
{
m_Hits = value;
m_OnHitsChanged.Invoke(m_Hits);
}
}
private int m_Misses = 0;
public int misses
{
get { return m_Misses; }
private set
{
m_Misses = value;
m_OnMissesChanged.Invoke(m_Misses);
}
}
public bool interactable
{
get { return m_SequenceCoroutine == null && m_ButtonCooldown <= 0f; }
}
void Start()
{
FiringRangeTarget[] collected = GetComponentsInChildren<FiringRangeTarget>(true);
for (int i = 0; i < collected.Length; ++i)
collected[i].Initialise(this);
if (!m_Initialised)
{
hits = 0;
misses = 0;
}
else
{
m_OnHitsChanged.Invoke(m_Hits);
m_OnMissesChanged.Invoke(m_Misses);
}
}
void Update()
{
if (m_ButtonCooldown > 0f)
{
m_ButtonCooldown -= Time.deltaTime;
if (m_ButtonCooldown < 0f)
m_ButtonCooldown = 0f;
}
}
public void AddHit ()
{
++hits;
m_AudioSource.PlayOneShot(m_AudioHit);
}
public void AddMiss ()
{
++misses;
}
public void OnButtonPush ()
{
if (m_ButtonCooldown <= 0f)
{
// If ongoing, stop
if (m_SequenceCoroutine != null)
{
if (m_State != SequenceState.Reset)
{
StopAllCoroutines();
m_SequenceCoroutine = StartCoroutine(ResetTargets());
m_AudioSource.PlayOneShot(m_AudioCancel, 0.25f);
}
}
else
{
// Else start
m_Wave = 0;
hits = 0;
misses = 0;
m_SequenceCoroutine = StartCoroutine(WaveStart(m_TimeBetweenWaves));
m_AudioSource.PlayOneShot(m_AudioStart, 0.25f);
}
m_ButtonCooldown = 3f;
}
}
private IEnumerator WaveStart(float timer)
{
m_State = SequenceState.WaveStart;
m_Timer = timer;
yield return null;
// Play wave start audio 1 sec before timer ends
if (m_Timer > 1f)
{
while (m_Timer > 1f)
{
yield return null;
m_Timer -= Time.deltaTime;
}
m_AudioSource.PlayOneShot(m_AudioStart, 0.25f);
}
// Wait for timer
while (m_Timer > 0f)
{
yield return null;
m_Timer -= Time.deltaTime;
}
m_Spawned = 0;
m_TargetCount = m_Targets[m_Wave].perStep;
m_SequenceCoroutine = StartCoroutine(WavePhase(0f));
}
private IEnumerator WavePhase(float timer)
{
m_State = SequenceState.WavePhase;
var group = m_Targets[m_Wave];
yield return null;
// Wait for step timer
m_Timer = timer;
while (m_Timer > 0f)
{
yield return null;
m_Timer -= Time.deltaTime;
}
// Spawn targets
if (group.randomise)
{
// Pick at random
while (m_Spawned < m_TargetCount)
{
int i = UnityEngine.Random.Range(0, group.targets.Length);
if (group.targets[i] != null && group.targets[i].hidden)
{
// Trigger
group.targets[i].Popup(group.duration);
++m_Spawned;
}
else
yield return null; // Yield to prevent endless loop
}
}
else
{
// Trigger sequentially
while (m_Spawned < m_TargetCount)
{
int index = m_Spawned;
while (index >= group.targets.Length)
index -= group.targets.Length;
if (group.targets[index] != null)
group.targets[index].Popup(group.duration);
++m_Spawned;
}
}
if (m_Spawned < group.total)
{
m_TargetCount += group.perStep;
m_SequenceCoroutine = StartCoroutine(WavePhase(group.delay));
}
else
{
m_SequenceCoroutine = StartCoroutine(WaitForReset(m_Wave + 1 >= m_Targets.Length));
}
}
private IEnumerator ResetTargets ()
{
for (int i = 0; i < m_Targets[m_Wave].targets.Length; ++i)
{
FiringRangeTarget t = m_Targets[m_Wave].targets[i];
if (t != null && !t.hidden)
t.ResetTarget();
}
return WaitForReset(true);
}
private IEnumerator WaitForReset(bool completed)
{
if (completed)
m_State = SequenceState.Reset;
else
m_State = SequenceState.Waiting;
yield return null;
bool allTargetsHidden = false;
while (!allTargetsHidden)
{
yield return null;
allTargetsHidden = true;
for (int i = 0; i < m_Targets[m_Wave].targets.Length; ++i)
{
FiringRangeTarget t = m_Targets[m_Wave].targets[i];
if (t != null && !t.hidden)
{
allTargetsHidden = false;
break;
}
}
}
// Reset the sequence if completed or start next wave
if (completed)
{
m_Wave = 0;
m_State = SequenceState.Stopped;
m_SequenceCoroutine = null;
}
else
{
++m_Wave;
m_SequenceCoroutine = StartCoroutine(WaveStart(m_TimeBetweenWaves));
}
}
private static readonly NeoSerializationKey k_HitsKey = new NeoSerializationKey("hits");
private static readonly NeoSerializationKey k_MissesKey = new NeoSerializationKey("misses");
private static readonly NeoSerializationKey k_StateKey = new NeoSerializationKey("state");
private static readonly NeoSerializationKey k_TimerKey = new NeoSerializationKey("timer");
private static readonly NeoSerializationKey k_WaveKey = new NeoSerializationKey("wave");
private static readonly NeoSerializationKey k_SpawnedKey = new NeoSerializationKey("spawned");
private static readonly NeoSerializationKey k_TargetCountKey = new NeoSerializationKey("targetCount");
private bool m_Initialised = false;
public void WriteProperties(INeoSerializer writer, NeoSerializedGameObject nsgo, SaveMode saveMode)
{
// Write properties
writer.WriteValue(k_HitsKey, m_Hits);
writer.WriteValue(k_MissesKey, m_Misses);
// Write coroutines
if (m_SequenceCoroutine != null)
{
writer.WriteValue(k_StateKey, (int)m_State);
writer.WriteValue(k_TimerKey, m_Timer);
writer.WriteValue(k_WaveKey, m_Wave);
writer.WriteValue(k_SpawnedKey, m_Spawned);
writer.WriteValue(k_TargetCountKey, m_TargetCount);
}
}
public void ReadProperties(INeoDeserializer reader, NeoSerializedGameObject nsgo)
{
reader.TryReadValue(k_HitsKey, out m_Hits, m_Hits);
reader.TryReadValue(k_MissesKey, out m_Misses, m_Misses);
int state = 0;
if (reader.TryReadValue(k_StateKey, out state, 0))
{
m_State = (SequenceState)state;
reader.TryReadValue(k_TimerKey, out m_Timer, m_Timer);
reader.TryReadValue(k_WaveKey, out m_Wave, m_Wave);
reader.TryReadValue(k_SpawnedKey, out m_Spawned, m_Spawned);
reader.TryReadValue(k_TargetCountKey, out m_TargetCount, m_TargetCount);
switch (m_State)
{
case SequenceState.WaveStart:
m_SequenceCoroutine = StartCoroutine(WaveStart(m_Timer));
break;
case SequenceState.WavePhase:
m_SequenceCoroutine = StartCoroutine(WavePhase(m_Timer));
break;
case SequenceState.Waiting:
m_SequenceCoroutine = StartCoroutine(WaitForReset(false));
break;
case SequenceState.Reset:
m_SequenceCoroutine = StartCoroutine(WaitForReset(true));
break;
}
}
m_Initialised = true;
}
}
}