using NeoSaveGames; using NeoSaveGames.Serialization; using NeoFPS.Constants; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; namespace NeoFPS { [HelpURL("https://docs.neofps.com/manual/utilitiesref-mb-abilitybasedslowmosystem.html")] public class AbilityBasedSlowMoSystem : CharacterInputBase, ISlowMoSystem, INeoSerializableComponent { [SerializeField, Tooltip("The (real, unscaled) time taken to transition time scales.")] private float m_TransitionDuration = 0.25f; [SerializeField, Tooltip("The amount of charge added per (real, unscaled) second.")] private float m_ChargeRate = 0.2f; [SerializeField, Tooltip("The time scale to use when using the ability.")] private float m_AbilityTimeScale = 0.1f; [SerializeField, Tooltip("The amount of charge lost per second while using ability slow-mo.")] private float m_AbilityDecayRate = 0.1f; [SerializeField, Tooltip("Should the slow-mo be stopped when input focus is lost.")] private bool m_ResetOnLoseFocus = false; [SerializeField, Tooltip("The sound to play on entering slow-mo.")] private AudioClip m_EnterSlowMoAudio = null; [SerializeField, Tooltip("The sound to play on exiting slow-mo.")] private AudioClip m_ExitSlowMoAudio = null; [SerializeField, Tooltip("The looping sound to play while in slow-mo.")] private AudioClip m_SlowMoLoopAudio = null; private static readonly NeoSerializationKey k_ChargeKey = new NeoSerializationKey("charge"); private static readonly NeoSerializationKey k_LerpKey = new NeoSerializationKey("lerp"); private static readonly NeoSerializationKey k_SourceTimeScaleKey = new NeoSerializationKey("sourceTimeScale"); private static readonly NeoSerializationKey k_TargetTimeScaleKey = new NeoSerializationKey("targetTimeScale"); private static readonly NeoSerializationKey k_SourceChargeRateKey = new NeoSerializationKey("sourceChargeRate"); private static readonly NeoSerializationKey k_TargetChargeRateKey = new NeoSerializationKey("targetChargeRate"); private static readonly NeoSerializationKey k_TimeScaleKey = new NeoSerializationKey("timeScale"); private static readonly NeoSerializationKey k_ChargeRateKey = new NeoSerializationKey("chargeRate"); public event UnityAction onChargeChanged; private Coroutine m_LerpTimescaleCoroutine = null; private ICharacterAudioHandler m_CharacterAudioHandler = null; private float m_SourceTimeScale = 1f; private float m_TargetTimeScale = 1f; private float m_SourceChargeRate = 0f; private float m_TargetChargeRate = 0f; private float m_Lerp = 1f; private bool m_TimeIsScaled = false; private float m_Charge = 1f; private float m_CurrentChargeRate = 0f; public float charge { get { return m_Charge; } set { value = Mathf.Clamp01(value); if (m_Charge != value) { m_Charge = value; if (onChargeChanged != null) onChargeChanged(m_Charge); } } } public bool isTimeScaled { get { return m_TimeIsScaled; } } void OnValidate() { m_TransitionDuration = Mathf.Clamp(m_TransitionDuration, 0f, 5f); } protected override void OnAwake() { base.OnAwake(); m_CharacterAudioHandler = GetComponent(); } protected override void OnLoseFocus() { base.OnLoseFocus(); if (m_ResetOnLoseFocus) ResetTimescale(); } protected override void OnDestroy() { base.OnDestroy(); ResetTimescale(); } public void SetTimeScale(float ts, float drainRate = 0f) { ts = Mathf.Clamp(ts, 0.01f, 5f); if (m_Charge > 0f) { m_TimeIsScaled = true; if (m_TransitionDuration < 0.01f) { NeoFpsTimeScale.timeScale = ts; m_CurrentChargeRate = -drainRate; } else { m_SourceTimeScale = NeoFpsTimeScale.timeScale; m_TargetTimeScale = ts; m_SourceChargeRate = m_CurrentChargeRate; m_TargetChargeRate = -drainRate; m_Lerp = 0f; } if (m_CharacterAudioHandler != null) { if (m_EnterSlowMoAudio != null) m_CharacterAudioHandler.PlayClip(m_EnterSlowMoAudio); if (m_SlowMoLoopAudio != null) m_CharacterAudioHandler.StartLoop(m_SlowMoLoopAudio, FpsCharacterAudioSource.Head); } } } public void ResetTimescale() { m_TimeIsScaled = false; if (m_TransitionDuration < 0.01f) { NeoFpsTimeScale.timeScale = 1f; m_CurrentChargeRate = m_ChargeRate; } else { m_SourceTimeScale = NeoFpsTimeScale.timeScale; m_TargetTimeScale = 1f; m_SourceChargeRate = m_CurrentChargeRate; m_TargetChargeRate = m_ChargeRate; m_Lerp = 0f; } if (m_CharacterAudioHandler != null) { if (m_ExitSlowMoAudio != null) m_CharacterAudioHandler.PlayClip(m_ExitSlowMoAudio); if (m_SlowMoLoopAudio != null) m_CharacterAudioHandler.StopLoop(FpsCharacterAudioSource.Head); } } protected override void UpdateInput() { if (GetButtonDown(FpsInputButton.Ability)) { if (isTimeScaled) ResetTimescale(); else { if (charge > 0.01f) SetTimeScale(m_AbilityTimeScale, m_AbilityDecayRate); } } } protected override void Update() { base.Update(); if (Time.timeScale != 0f) { // Transition if required if (m_Lerp < 1f) { m_Lerp += Time.deltaTime / m_TransitionDuration; if (m_Lerp > 1f) m_Lerp = 1f; m_CurrentChargeRate = Mathf.Lerp(m_SourceChargeRate, m_TargetChargeRate, m_Lerp); NeoFpsTimeScale.timeScale = Mathf.Lerp(m_SourceTimeScale, m_TargetTimeScale, m_Lerp); } // Modify charge charge += Time.unscaledDeltaTime * m_CurrentChargeRate; if (charge == 0f && m_TimeIsScaled) ResetTimescale(); } } public void WriteProperties(INeoSerializer writer, NeoSerializedGameObject nsgo, SaveMode saveMode) { writer.WriteValue(k_ChargeKey, m_Charge); // Check if in transition if (m_LerpTimescaleCoroutine != null) { writer.WriteValue(k_LerpKey, m_Lerp); writer.WriteValue(k_SourceTimeScaleKey, m_SourceTimeScale); writer.WriteValue(k_TargetTimeScaleKey, m_TargetTimeScale); } else { if (m_TimeIsScaled) writer.WriteValue(k_TimeScaleKey, Time.timeScale); writer.WriteValue(k_ChargeRateKey, m_CurrentChargeRate); } } public void ReadProperties(INeoDeserializer reader, NeoSerializedGameObject nsgo) { reader.TryReadValue(k_ChargeKey, out m_Charge, m_Charge); reader.TryReadValue(k_ChargeKey, out m_Charge, m_Charge); // Check if in transition if (reader.TryReadValue(k_LerpKey, out m_Lerp, m_Lerp)) { reader.TryReadValue(k_SourceTimeScaleKey, out m_SourceTimeScale, m_SourceTimeScale); reader.TryReadValue(k_TargetTimeScaleKey, out m_TargetTimeScale, m_TargetTimeScale); reader.TryReadValue(k_SourceChargeRateKey, out m_SourceChargeRate, m_SourceChargeRate); reader.TryReadValue(k_TargetChargeRateKey, out m_TargetChargeRate, m_TargetChargeRate); m_CurrentChargeRate = Mathf.Lerp(m_SourceChargeRate, m_TargetChargeRate, m_Lerp); NeoFpsTimeScale.timeScale = Mathf.Lerp(m_SourceTimeScale, m_TargetTimeScale, m_Lerp); } else { // Read the charge rate reader.TryReadValue(k_ChargeRateKey, out m_CurrentChargeRate, m_CurrentChargeRate); // Read and set the time-scale if required float ts = 1f; if (reader.TryReadValue(k_TimeScaleKey, out ts, ts)) { NeoFpsTimeScale.timeScale = ts; m_TimeIsScaled = true; } } } } }