Brief description of the most important classes and functions.
Almost every parameter in every component can be changed runtime from code.
For exact behaviour and more - check source code.
This is main singleton class that controls everything about the water.
public class DragonWaterManager { // static getter to get singleton instance public static DragonWaterManager Instance { get; } // acces to configuration scriptable object public DragonWaterConfig Config { get; } public float Time { get; set; } // current simulation time - if you disable auto simulation, you''ll have to update this value manually every frame public WaterSurface UnderwaterSurface { get; } // surface that main camera is currently below; null if none public WaterSampler.HitResult CameraHitResult { get; } // last camera hit test against water, used for auto snapping and underwater detection public Camera MainCamera { get; } // refers to Camera.Main // subscribe to this event to receive callback when camera changes underwater state // both, from and to param can be null depending on if you dive, resurface, or change between surface under water public delegate void UnderwaterChangedCallback(WaterSurface from, WaterSurface to); public event UnderwaterChangedCallback OnUnderwaterChanged; // collections public IReadOnlyList<WaterSurface> Surfaces { get; } // currently active surfaces public WaterSurface DefaultSurface { get; } // current default and active surface, null if not set public IReadOnlyList<WaterRippleProjector> { get; } // currently active ripple projectors public IReadOnlyList<LocalWaveArea> { get; } // currently active local wave areas // culling - used internally by WaterSampler public void CullWaterSurfaces(ref Bounds bounds, ref List<WaterSurface> list); public void CullCutoutColliders(ref Bounds bounds, ref List<Collider> list); // sampling water public WaterSampler.HitResult SampleWater(Vector3 position); // calls SampleWater with considerCutouts=true as default public WaterSampler.HitResult SampleWater(Vector3 position, bool considerCutouts); }
The most usefull class.
Create it's object to sample water.
You can reuse this object (keep calling Schedule and Complete) but always remember to Dispose() when no longer needed.
public class WaterSampler : IDisposable { public static IReadOnlyList<WaterSampler> ActiveSamplers { get; } // access to all currently active samplers public enum SurfaceDetectionMode { Default, // use default surface, if its null then fallbacks to AutoCull AutoCull, // automatically scans nearby surfaces Custom // tests only aainst provided surfaces in SurfaceDetectionCustom list } public enum CutoutDetectionMode { AutoCull, // automatically scans nearby colliders to cutout DontCutout, // do not care about cutouts at all - always sample Custom // tests only against provided cutouts in CutoutDetectionCustom list } public int Precision = 0; // amount of iterations; 0 refers to Constants.DefaultSamplerPrecision and 3-4 is most cases is more than enough public SurfaceDetectionMode SurfaceDetection = SurfaceDetectionMode.Default; // how nearby surfaces are detected public CutoutDetectionMode CutoutDetection = CutoutDetectionMode.AutoCull; // how to to process cutout volumes public List<WaterSurface> SurfaceDetectionCustom = null; // you NEED to assign it in case of Custom mode public List<WaterCutoutVolume> CutoutDetectionCustom = null; // you NEED to assign it in case of Custom mode public Bounds CullingBox = default; // if left default, it will automatically create bounding box for all points before scheduling public int MaxSize { get; }; // maximum amount of points this sampler can process; assigned via constructor public bool IsRunning { get; }; // if its running, you shouldn't modify anything here public HitResult[] Results { get; } // access to array of results of last scan - it's size is always MaxSize even if you ran it for less points public int ResultCount { get; } // actual amount of available results in Results array public WaterSampler(int maxSize = 1); // construction - you have to specify max size public void Dispose(); // ALWAYS remember to dispose no longer needed sampler public void Resize(int newMaxSize); // resize to new MaxSize // fill points to test against water public void SetPoint(int index, Vector3 point); // set single point and specified index public void SetPoints(IReadOnlyList<Vector3> points); // set points since index 0 public void SetPoints(ArraySegment<Vector3> points); // set points since index 0 // schedule public void Schedule(); // calls Schedule with MaxSize public void Schedule(int pointsCount); // amount of points to process starting from index 0; can't be larger than MaxSize public void Complete(); // complete processing - recommended to call next frame // caching auto culls // be default AutoCull modes are very expensive - with this functions you can temporarily cache their result so it will act like Custom options public void CacheAutoCull(); // calls CacheAutoCull with MaxSize public void CacheAutoCull(int points); public void ClearAutoCullCache(); // clears sampler until next scheduling, so it acts like not scheduled yet at all with no results public void ClearState(); }
Struct that contains sampling result of a single point against water.
public struct HitResult { public Vector3 sampledPoint; public WaterSurface surface; // null if no surface detected public Vector3 hitPoint; // actual position on water surface, possibly very closely to sampledPoint public Vector3 hitNormal; // normal vector of water here public bool HasHit => surface != null; public bool IsUnderwater => sampledPoint.y < hitPoint.y; public float Depth => hitPoint.y - sampledPoint.y; // positive when under water public float Height => sampledPoint.y - hitPoint.y; // positive when above water public float WaterLevel => hitPoint.y; }
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); }