// Crest Ocean System
// Copyright 2020 Wave Harmonic Ltd
using UnityEngine;
using UnityEngine.Events;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Crest.Examples
{
///
/// Emits useful events (UnityEvents) based on the sampled height of the ocean surface.
///
[AddComponentMenu(Crest.Internal.Constants.MENU_PREFIX_EXAMPLE + "Ocean Sample Height Events")]
public class OceanSampleHeightEvents : CustomMonoBehaviour
{
///
/// The version of this asset. Can be used to migrate across versions. This value should
/// only be changed when the editor upgrades the version.
///
[SerializeField, HideInInspector]
#pragma warning disable 414
int _version = 0;
#pragma warning restore 414
[Header("Settings For All Events")]
[Tooltip("The higher the value, the more smaller waves will be ignored when sampling the ocean surface.")]
[SerializeField] float _minimumWaveLength = 1f;
[Header("Distance From Ocean Surface")]
[Tooltip("A normalised distance from ocean surface will be between zero and one.")]
[SerializeField] bool _normaliseDistance = true;
[Tooltip("The maximum distance passed to function. Always use a real distance value (not a normalised one).")]
[SerializeField] float _maximumDistance = 100f;
[Tooltip("Apply a curve to the distance passed to the function.")]
[SerializeField] AnimationCurve _distanceCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f);
[Header("Events")]
[SerializeField] UnityEvent _onBelowOceanSurface = new UnityEvent();
public UnityEvent OnBelowOceanSurface => _onBelowOceanSurface;
[SerializeField] UnityEvent _onAboveOceanSurface = new UnityEvent();
public UnityEvent OnAboveOceanSurface => _onAboveOceanSurface;
[SerializeField] FloatEvent _distanceFromOceanSurface = new FloatEvent();
public FloatEvent DistanceFromOceanSurface => _distanceFromOceanSurface;
// Store state
bool _isAboveSurface = false;
bool _isFirstUpdate = true;
readonly SampleHeightHelper _sampleHeightHelper = new SampleHeightHelper();
// Dynamic UnityEvent definitions.
[System.Serializable] public class FloatEvent : UnityEvent { }
void Update()
{
_sampleHeightHelper.Init(transform.position, 2f * _minimumWaveLength);
if (_sampleHeightHelper.Sample(out var height))
{
var distance = transform.position.y - height;
var isAboveSurface = distance > 0;
// Has the below/above ocean surface state changed?
if (_isAboveSurface != isAboveSurface || _isFirstUpdate)
{
_isAboveSurface = isAboveSurface;
_isFirstUpdate = false;
if (_isAboveSurface)
{
_onAboveOceanSurface.Invoke();
}
else
{
_onBelowOceanSurface.Invoke();
}
}
// Save some processing when not being used.
if (_distanceFromOceanSurface.GetPersistentEventCount() > 0)
{
// Normalise distance so we can use the curve.
var distanceFromOceanSurface = _distanceCurve.Evaluate(1f - Mathf.Abs(distance) / _maximumDistance);
// Restore raw distance if desired.
if (!_normaliseDistance)
{
distanceFromOceanSurface = _maximumDistance - distanceFromOceanSurface * _maximumDistance;
}
_distanceFromOceanSurface.Invoke(distanceFromOceanSurface);
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(OceanSampleHeightEvents))]
public class OceanSampleHeightEventsEditor : CustomBaseEditor
{
public override void OnInspectorGUI()
{
EditorGUILayout.Space();
EditorGUILayout.HelpBox
(
"For the Above/Below Ocean Surface Events, whenever this game object goes below or above the ocean " +
"surface, the appropriate event is fired once per state change. It can be used to trigger audio to " +
"play underwater and much more. For the Distance From Ocean Surface event, it will pass the " +
"distance every frame (passing normalised distance to audio volume as an example).",
MessageType.Info
);
EditorGUILayout.Space();
base.OnInspectorGUI();
}
}
#endif
}
}