Abstract MonoBehaviour class than can be used as utility for easier water sampling from your game object component.
You can treat is as a MonoBehaviour facade to WaterSampler.
It uses WaterSampler asyncrhonously, so it always generates one frame delay while sampling water, which is totally fine in most cases.
public abstract class WaterBehaviour : MonoBehaviour { // used to configure component from inspector [Serializable] public class WaterSamplerConfig { /* .. */ } // access to config that is exposed for configuration via inspector public WaterSamplerConfig SamplerConfig {get; }; public int FrameDividerOffset { get; }; // check this inside InnerFixedUpdate if you want to schedule protected bool isSchedulingFrane { get; }; // direct access to array with points you are going to sample protected Vector3[] samplingPoints { get; }; // Unity callbacks protected virtual void Awake(); protected virtual void OnEnable(); protected virtual void OnDisable(); protected virtual void OnDestroy(); protected virtual void OnFixedUpdate(); // in most cases, do not override this function (see InnerFixedUpdate) // prepare sampler side before settingg any points protected void PrepareSamplingPointsSize(int size); protected void SetSamplingPoint(int index, Vector3 point); protected void SetSamplingPoints(Vector3[] points); protected int GetResultCount(); // return amount of available results protected WaterSampler.HitResult GetResult(int index); protected void GetResults(out ArraySegment<WaterSampler.HitResult> results); // override this function for actual fixed update behaviour // only inside this function you can schedule points protected virtual void InnerFixedUpdate() { } }
FloatingBody is built-in implementation of WaterBehaviour.
public class FloatingBody : WaterBehaviour { // getters and setters for all properties you see in the inspector public Vector3 CenterOfMass { get; set; } public float BuoyancyForce { get; set; } public bool IgnoreMass { get; set; } public float NormalAlignment { get; set; } public float Instability { get; set; } public float SubmergeDrag { get; set; } public float SubmergeAngularDrag { get; set; } public float CurrentSubmergeLevel { get; } // range 0-1 thats means fraction of points that are under water public Rigidbody Rigidbody { get; } // return attached rigidbody // modify contact points (all in local space of game object) public Vector3 GetContactPoint(int index); public void SetContactPoint(int index, Vector3 point); public void RemoveContactPoint(int index); public void AddContactPoint(Vector3 point); // return last hit result for point with given index // useful e.g. to check if given part of body is above or under water without creating additional sampler or object for that public WaterSampler.HitResult GetContactPointHit(int index); }
This implementation shows how to make cannon ball detecting collision with water.
It is used in attached Dragon Water Demo game.
[RequireComponent(typeof(Rigidbody))] public class CannonBall : WaterBehaviour { [SerializeField] float speed; [SerializeField] ParticleSystem trailParticle; [SerializeField] GameObject explosionMediumPrefab; [SerializeField] GameObject explosionLargePrefab; [SerializeField] GameObject splashPrefab; [SerializeField] float shipExplosionForce; [SerializeField] float shipExplosionRadius; Rigidbody _rigidbody; protected override void Awake() { base.Awake(); _rigidbody = GetComponent<Rigidbody>(); } private void Start() { _rigidbody.velocity = transform.forward * speed; } protected override void InnerFixedUpdate() { if (GetResultCount() > 0) { var hit = GetResult(0); if (hit.HasHit && hit.IsUnderwater) { HitWater(); } } if (isSchedulingFrane) { PrepareSamplingPointsSize(1); SetSamplingPoint(0, transform.position); } } private void OnCollisionEnter(Collision collision) { var ship = collision.gameObject.GetComponentInParent<ShipController>(); if (ship != null) { HitShip(ship); } else { HitOther(); } } private void HitShip(ShipController ship) { ship.FloatingBody.Rigidbody.AddExplosionForce(shipExplosionForce, transform.position, shipExplosionRadius); Instantiate(explosionLargePrefab, transform.position, Quaternion.identity); DestroyBall(); } private void HitOther() { Instantiate(explosionMediumPrefab, transform.position, Quaternion.identity); DestroyBall(); } private void HitWater() { Instantiate(splashPrefab, transform.position, Quaternion.identity); DestroyBall(); } private void DestroyBall() { var scale = trailParticle.transform.localScale; trailParticle.transform.SetParent(null, true); trailParticle.transform.localScale = scale; trailParticle.Stop(); trailParticle.AddComponent<SelfDestroy>().delay = 5; Destroy(gameObject); } }