(Update) Clean Projet

This commit is contained in:
2026-06-24 14:38:51 +02:00
parent 48588ccfed
commit ef01552268
2629 changed files with 1523 additions and 430219 deletions
@@ -0,0 +1,391 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using Unity.Mathematics;
using UnityEngine;
namespace StylizedWater3
{
/// <summary>
/// Samples the water height at 4 points around the Transform, and snaps the Y-position to the average.
/// From these 4 points a normal direction can also be derived, which is used to orient the transform's rotation
/// - This script is a prime example of how the Height Query System may be used.
/// </summary>
[ExecuteInEditMode]
[AddComponentMenu("Stylized Water 3/Align Transform To Water")]
public class AlignToWater : MonoBehaviour
{
//Because there two completely different methods of sampling the water's height, this Interface class provides a way to specify which is to be used.
//In the case of the CPU-method, it contains everything else needed for such a query (eg. water level and material)
public HeightQuerySystem.Interface heightInterface = new HeightQuerySystem.Interface();
public Vector2 surfaceSize = new Vector2(0.5f, 0.5f);
[Tooltip("Assign an optional transform to follow on the XZ axis.\n\nThis may be used if you want to use this component to read the water height at another transform's position.")]
public Transform followTarget;
public float heightOffset;
[Range(0f, 8f)]
[Tooltip("Controls how strongly the transform should rotate to align with the wave curvature")]
public float rollAmount = 0.1f;
[Tooltip("Add a Y-axis rotation to the transform. Note that this technically results in an incorrect alignment.")]
[Range(0f, 360f)]
public float rotation = 0f;
[Tooltip("Smoothly blend towards the newly calculated position and rotation. May be used to combat jittering")]
[Min(0f)]
public float smoothing = 0.1f;
private Vector3 normal;
private float height;
public enum HeightValue
{
Average,
Maximum
}
public HeightValue heightValue;
//A sampler defines:
// • An array of world-space input positions to sample the height at.
// • A float array (of equal size) that stores the output height values.
// Samplers must always be initialized by specifying how many sample points are needed.
private HeightQuerySystem.Sampler heightSampler;
//A request ask the Height Query System to return the water height at every one of the sampler's positions from the GPU
//It contains a callback event that needs to be subscribed to, to let you know when the request is completed
private HeightQuerySystem.AsyncRequest heightRequest;
#pragma warning disable 108,114 //New keyword
public Rigidbody rigidbody;
#pragma warning restore 108,114
private Vector3 m_targetNormal;
private float m_targetHeight;
#if UNITY_EDITOR
public static bool EnableInEditor
{
get { return UnityEditor.EditorPrefs.GetBool("SW3_BUOYANCY_EDITOR_ENABLED", true); }
set { UnityEditor.EditorPrefs.SetBool("SW3_BUOYANCY_EDITOR_ENABLED", value); }
}
#endif
private void Start()
{
rigidbody = GetComponent<Rigidbody>();
if (rigidbody && rigidbody.useGravity)
{
Debug.LogWarning($"[Align Transform To Water] Disabled gravity on RigidBody \"{rigidbody.name}\". Otherwise its position can't be set without it struggling back");
rigidbody.useGravity = false;
}
}
private void OnEnable()
{
#if UNITY_EDITOR
if(Application.isPlaying == false) UnityEditor.SceneView.duringSceneGui += DuringSceneViewUpdate;
#endif
//Sampler uses 4 sampling points, one for each corner of the rectangle/plane
//In the context of a physics-based buoyancy system a sampler will use more than 4 sampling points
heightSampler = new HeightQuerySystem.Sampler();
heightSampler.SetSampleCount(4);
//Only when using the GPU-readback method does the HeightQuerySystem come into play
if (heightInterface.method == HeightQuerySystem.Interface.Method.GPU)
{
//Create a new request with the sampler created above.
//Do this just once! It'll keep going until you call "Dispose()" on it.
//Using the object's hashcode ensures the request gets a unique ID
heightRequest = new HeightQuerySystem.AsyncRequest(this.GetHashCode(), heightSampler, this.name);
//Issue the request so the system starts populating the "heightSampler" with data
heightRequest.Issue();
//True by default. This checks if the returned water height values are valid. If not, the value remains the same as last frame
//This avoids objects that are not above any water surface, or fall outside the camera frustum, from falling down to -1000 heights
heightRequest.invalidateMisses = true;
//You may also call .Withdraw() if you wish to only temporarily remove the request from the system. Then call .Issue() again when needed.
//This is also necessary when looking to call heightSampler.SetSampleCount() again.
//Doing so avoids reallocating its resources but only temporarily takes it out of the running.
//Subscribe to the event that indicates that data was retrieved from the GPU
//Important to note that this may be a few (rendering) frames later than when the request was issued (at least 1)
heightRequest.onCompleted += OnHeightRequestComplete;
}
prevHeight = this.transform.position.y;
height = prevHeight;
}
#if UNITY_EDITOR
void DuringSceneViewUpdate(UnityEditor.SceneView sceneView)
{
FixedUpdate();
}
#endif
private void OnHeightRequestComplete()
{
//Debug.Log("Height request returned");
//Use the data. It pre-presents the water geometry's height value at each of the requested sample positions
float xNeg = heightSampler.heightValues[0];
float xPos = heightSampler.heightValues[1];
float zNeg = heightSampler.heightValues[2];
float zPos = heightSampler.heightValues[3];
float newHeight = 0f;
if (heightValue == HeightValue.Average)
{
newHeight = xNeg + xPos + zNeg + zPos;
newHeight /= 4f;
}
if (heightValue == HeightValue.Maximum)
{
newHeight = Mathf.Max(Mathf.Max(xNeg, xPos), Mathf.Max(zNeg, zPos));
}
newHeight += heightOffset;
if (float.IsNaN(newHeight))
{
#if SWS_DEV
Debug.LogError("Height is NaN");
#endif
//May occur during the first run
newHeight = 0f;
}
if (heightInterface.method == HeightQuerySystem.Interface.Method.GPU)
{
//If all the samples were taking at a point where no water was visible, the height values would be -1000f.
//Avoid setting an invalid height to prevent objects from sinking way down, instead keep them at the last valid height
if (HeightQuerySystem.EqualsVoid(newHeight))
{
UpdateSamplePositions();
return;
}
}
height = newHeight;
//Using 4 samples in a plus-shape pattern, a normal can be derived from the height differences
normal = HeightQuerySystem.DeriveNormal(
xNeg, xPos,
zNeg, zPos,
rollAmount);
//Note: In a physics-based buoyancy system, calculating the normal will not be necessary. The varying upward forces will naturally align an object to the water.
//Reading back the water surface normals directly will not be possible, since no such information is available (requires a deferred rendering water system)
//Only updating the sampling positions here, for the next query. There would be no point in doing so for frames where the height request may not be returned
UpdateSamplePositions();
}
private void Reset()
{
//Auto-assign water object if there is only one
if (heightInterface.waterObject == null && WaterObject.Instances.Count > 0)
{
heightInterface.waterObject = WaterObject.Instances[0];
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(this);
#endif
}
AutoCalculateSurfaceSizeFromMeshes();
}
private void OnDisable()
{
#if UNITY_EDITOR
if(Application.isPlaying == false) UnityEditor.SceneView.duringSceneGui -= DuringSceneViewUpdate;
#endif
if (heightRequest != null)
{
//Unsubscribe
heightRequest.onCompleted -= OnHeightRequestComplete;
//Important! Remove the request from the system so that it de-allocates the memory it uses.
heightRequest.Dispose();
heightRequest = null;
}
//Also dispose the height sampler. This stores two arrays that need to be de-allocated
//Note: When using the GPU method, disposing 'heightRequest' will already do this!
heightSampler.Dispose();
heightSampler = null;
}
public void FixedUpdate()
{
if (!this.enabled) return;
#if UNITY_EDITOR
if (!EnableInEditor && Application.isPlaying == false) return;
#endif
//When using the CPU (wave pattern replication) method
if (heightInterface.method == HeightQuerySystem.Interface.Method.CPU)
{
//The water object contains the material, and may be used to define a water level
heightInterface.GetWaterObject(this.transform.position);
//A reference to a Water Object is required, which in turn contains a material
if (heightInterface.HasMissingReferences()) return;
//This function reproduces the water shader's wave vertex animation.
Gerstner.ComputeHeight(heightSampler, heightInterface);
//At this point the 'heightSampler' has new height values, so can go right ahead and use them
OnHeightRequestComplete();
ApplyTransform();
}
else
{
ApplyTransform();
}
}
//Translates the corners of the bounds to the 4 sampling points (plus-shaped pattern)
//From local-space to world-space.
private void UpdateSamplePositions()
{
//Note: Should the number of sample positions change in realtime, that creates an issue where resources have to be destroyed and recreated. Avoid doing so!
//Should it be needed, do so by:
//• heightRequest.Withdraw(); //Remove the active request for data
//• heightSampler.SetSampleCount(count). //This will automatically reallocate the arrays if the count was changed.
//• heightRequest.Issue(); //Re-issue the request with the new number of samples
// ┤
heightSampler.SetSamplePosition(0, ConvertToWorldSpace(new Vector3(-surfaceSize.x * 0.5f, 0, 0)));
// ├
heightSampler.SetSamplePosition(1, ConvertToWorldSpace(new Vector3(surfaceSize.x * 0.5f, 0, 0)));
// ┬
heightSampler.SetSamplePosition(2, ConvertToWorldSpace(new Vector3(0, 0, -surfaceSize.y * 0.5f)));
// ┴
heightSampler.SetSamplePosition(3, ConvertToWorldSpace(new Vector3(0, 0, surfaceSize.y * 0.5f)));
}
private Vector3 prevNormal = new Vector3(0, 1, 0);
private float prevHeight;
private void ApplyTransform()
{
//Smooth transition to new normal of this frame, particularly reduces jittering on rigid bodies
if (smoothing > 0)
{
m_targetHeight = Mathf.Lerp(prevHeight, height, Time.deltaTime / smoothing);
prevHeight = m_targetHeight;
m_targetNormal = Vector3.Lerp(prevNormal, normal, Time.smoothDeltaTime / smoothing);
prevNormal = m_targetNormal;
}
else
{
prevHeight = height;
prevNormal = normal;
m_targetHeight = height;
m_targetNormal = normal;
}
var position = this.transform.position;
if (followTarget)
{
position.x = followTarget.position.x;
position.z = followTarget.position.z;
}
position.y = m_targetHeight;
//Setting the normal of a transform directly overrides any and all external rotations
//If the component is attached an object with a mesh, move the mesh into a child object and rotate that instead
var newRotation = Quaternion.FromToRotation(Vector3.up, m_targetNormal);
newRotation *= quaternion.RotateY(rotation * Mathf.Deg2Rad);
if (Application.isPlaying && rigidbody)
{
if (rollAmount > 0)
{
rigidbody.Move(position, newRotation);
}
else
{
rigidbody.MovePosition(position);
}
}
else
{
if (rollAmount > 0)
{
this.transform.SetPositionAndRotation(position, newRotation);
}
else
{
this.transform.position = position;
}
}
}
private Vector3 ConvertToWorldSpace(Vector3 position)
{
return this.transform.TransformPoint(position);
}
/// <summary>
/// Automatically grab a starting point for the area size, based on the attached mesh(es)
/// </summary>
public void AutoCalculateSurfaceSizeFromMeshes()
{
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
int meshCount = meshFilters.Length;
if (meshCount > 0)
{
surfaceSize.x = 0f;
surfaceSize.y = 0f;
Bounds bounds = new Bounds();
Vector3 minSum = Vector3.one * Mathf.Infinity;
Vector3 maxSum = Vector3.one * Mathf.NegativeInfinity;
for (int i = 0; i < meshCount; i++)
{
minSum = Vector3.Min(minSum, meshFilters[i].sharedMesh.bounds.min);
maxSum = Vector3.Max(maxSum, meshFilters[i].sharedMesh.bounds.max);
}
bounds.SetMinMax(minSum, maxSum);
surfaceSize.x = bounds.size.x;
surfaceSize.y = bounds.size.z;
}
}
private void OnDrawGizmosSelected()
{
if (heightSampler != null && heightSampler.IsCreated())
{
foreach (Vector3 p in heightSampler.positions)
{
Gizmos.DrawWireCube(p - (Vector3.up * heightOffset), Vector3.one * 0.2f);
}
}
Gizmos.DrawLine(this.transform.position, this.transform.position + (m_targetNormal * 2f));
Gizmos.matrix = this.transform.localToWorldMatrix;
Gizmos.DrawWireCube(Vector3.zero - (Vector3.up * heightOffset), new Vector3(surfaceSize.x, 0f, surfaceSize.y));
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 95144392e6eede14cafa2d339e5c1ee9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 148978298399363526, guid: 0000000000000000d000000000000000, type: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/AlignToWater.cs
uploadId: 895866
@@ -0,0 +1,259 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace StylizedWater3
{
[ExecuteAlways]
[AddComponentMenu("")] //Hide, only to be used with the prefab
public class OceanFollowBehaviour : MonoBehaviour
{
public static OceanFollowBehaviour Instance;
const int LODCount = 7;
private static readonly float[] gridSizes = new[]
{
0.5f, //LOD0
01f, //LOD1
02f, //LOD2
04f, //LOD3
08f, //LOD4
16f, //LOD5
32f, //LOD6
32f //LOD7
};
private const float EXPECTED_MAX_WAVE_HEIGHT = 15f;
public Material material;
[Space]
[Tooltip("Enable to executing the camera following behaviour when outside of Play mode")]
public bool enableInEditMode = true;
[Tooltip("Assign a specific transform to follow on the XZ axis." +
"\n\nIf left empty, the camera currently rendering will be targeted.")]
public Transform followTarget;
[Space]
public static bool ShowWireFrame = true;
[Serializable]
public class LOD
{
public List<GameObject> gameObjects = new List<GameObject>();
public float gridSize = 1f;
}
[SerializeField]
//[HideInInspector]
private LOD[] lods;
//To ensure that each LOD renders behind the one before it, move each one down a tiny bit
private const float heightOffset = 0.01f;
private void Reset()
{
//BuildLODs();
}
#if SWS_DEV
[ContextMenu("Build LODs")]
#endif
//Populates the LOD list and sets the gridsize for each
void BuildLODs()
{
lods = new LOD[LODCount+1];
for (int i = 0; i < lods.Length; i++)
{
lods[i] = new LOD();
lods[i].gridSize = gridSizes[i];
}
MeshRenderer[] childs = this.gameObject.GetComponentsInChildren<MeshRenderer>(true);
for (int i = 0; i < childs.Length; i++)
{
childs[i].gameObject.layer = WaterObject.WaterLayer;
string objName = childs[i].name;
int lodIndex = 0;
if(objName.EndsWith("0")) lodIndex = 0;
if(objName.EndsWith("1")) lodIndex = 1;
if(objName.EndsWith("2")) lodIndex = 2;
if(objName.EndsWith("3")) lodIndex = 3;
if(objName.EndsWith("4")) lodIndex = 4;
if(objName.EndsWith("5")) lodIndex = 5;
if(objName.EndsWith("6")) lodIndex = 6;
if(objName.EndsWith("7")) lodIndex = 7;
#if UNITY_EDITOR
childs[i].scaleInLightmap = Mathf.Lerp(1, 0.01f, (float)lodIndex / LODCount);
#endif
lods[lodIndex].gameObjects.Add(childs[i].gameObject);
}
}
private Vector3 targetPosition;
private void SetPosition(Transform target)
{
float height = this.transform.position.y;
for (int i = 0; i < lods.Length; i++)
{
for (int j = 0; j < lods[i].gameObjects.Count; j++)
{
targetPosition = WaterGrid.SnapToGrid(target.position, this.transform.lossyScale.x * lods[i].gridSize);
//targetPosition = target.position; //No snapping
//Progressively lower the height of each LOD a small amount, this helps ensure transparency sorting will be correct.
targetPosition.y = height - (heightOffset * i);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (lods[i].gameObjects[j] == false) throw new Exception("[Ocean] A child GameObject was deleted, these should not be touched!");
#endif
lods[i].gameObjects[j].transform.position = targetPosition;
}
}
}
public void ApplyMaterial()
{
for (int i = 0; i < lods.Length; i++)
{
foreach (var lod in lods[i].gameObjects)
{
MeshRenderer r = lod.GetComponent<MeshRenderer>();
if (r)
{
r.sharedMaterial = material;
PadBounds(r);
}
}
}
}
//Pad the bounds so that meshes don't get unintentionally culled when using high waves
private void PadBounds()
{
for (int i = 0; i < lods.Length; i++)
{
foreach (var lod in lods[i].gameObjects)
{
MeshRenderer r = lod.GetComponent<MeshRenderer>();
if (r)
{
r.sharedMaterial = material;
PadBounds(r);
}
}
}
}
private void PadBounds(MeshRenderer m_renderer)
{
Bounds bounds = m_renderer.localBounds;
bounds.Expand(Vector3.up * (EXPECTED_MAX_WAVE_HEIGHT * 0.5f));
m_renderer.localBounds = bounds;
}
private void OnEnable()
{
Instance = this;
PadBounds();
#if URP
RenderPipelineManager.beginCameraRendering += OnCameraRender;
#endif
}
private void LateUpdate()
{
if (followTarget)
{
SetPosition(followTarget);
}
}
#if URP
private void OnCameraRender(ScriptableRenderContext context, Camera targetCamera)
{
if (targetCamera.cameraType == CameraType.Preview) return;
//Component set up to follow a specific target
if (followTarget != null) return;
#if UNITY_EDITOR
//Skip if disabled in scene-view
if (targetCamera.cameraType == CameraType.SceneView && (enableInEditMode == false || Application.isPlaying)) return;
#endif
SetPosition(targetCamera.transform);
}
#endif
private void OnDisable()
{
Instance = null;
#if URP
RenderPipelineManager.beginCameraRendering -= OnCameraRender;
#endif
}
private void OnDrawGizmosSelected()
{
if (!ShowWireFrame) return;
MeshFilter[] meshes = GetComponentsInChildren<MeshFilter>();
Gizmos.color = new Color(0,0,0,0.25f);
for (int i = 0; i < meshes.Length; i++)
{
Gizmos.matrix = meshes[i].transform.localToWorldMatrix;
Gizmos.DrawWireMesh(meshes[i].sharedMesh);
}
/*
Gizmos.color = new Color(1,1,0,0.25f);
Gizmos.matrix = Matrix4x4.identity;
for (int i = 0; i < lods.Length; i++)
{
foreach (var lod in lods[i].gameObjects)
{
MeshRenderer r = lod.GetComponent<MeshRenderer>();
Gizmos.DrawWireCube(r.bounds.center, r.bounds.size);
}
}
*/
}
public bool InvalidSetup()
{
if(lods == null) return true;
if(lods.Length == 0) return true;
for (int i = 0; i < lods.Length; i++)
{
foreach (var lod in lods[i].gameObjects)
{
if(!lod.gameObject) return true;
}
}
return false;
}
}
}
@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 59d166d8583159b44ae4a88bf823b554
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- material: {fileID: 2100000, guid: d9bee4d86b9f55b46a4288109fb2b181, type: 2}
- followTarget: {instanceID: 0}
executionOrder: 0
icon: {fileID: -7633815037825259808, guid: 0000000000000000d000000000000000, type: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/OceanFollowBehaviour.cs
uploadId: 895866
@@ -0,0 +1,124 @@
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace StylizedWater3
{
/// <summary>
/// Emulates the particle system "Rate over Distance" emission behaviour, but with accurate support for RigidBody's
/// </summary>
[ExecuteInEditMode]
[AddComponentMenu("Effects/Particle Trail Emitter")]
public class ParticleTrailEmitter : MonoBehaviour
{
#pragma warning disable 108,114 //New keyword
[Tooltip("The Emission module on this particle system should have its Rate Over Distance value set 0.")]
public ParticleSystem particleSystem;
private ParticleSystem.EmissionModule emissionModule;
#pragma warning restore 108,114
[Tooltip("If this particle system is parented under a RigidBody, then assign it here for correct positional tracking")]
public Rigidbody rigidBody;
[Space]
public float spawnRatePerUnit = 1f;
private float distanceAccumulation = 0f;
private Vector3 previousPosition;
void Reset()
{
particleSystem = GetComponent<ParticleSystem>();
if (particleSystem)
{
emissionModule = particleSystem.emission;
if (emissionModule.rateOverDistance.constant > 0f)
{
ParticleSystem.MinMaxCurve rateOverDistance = emissionModule.rateOverDistance;
rateOverDistance.constant = 0f;
emissionModule.rateOverDistance = rateOverDistance;
Debug.LogWarning($"The Rate Over Distance has been set to 0 on the particle system \"{particleSystem.name}\". This is because the Particle Trail Emitter component will be responsible for emission");
}
}
rigidBody = GetComponentInParent<Rigidbody>();
}
private void Start()
{
previousPosition = this.transform.position;
if (particleSystem)
{
ParticleSystem.MainModule main = particleSystem.main;
main.playOnAwake = false;
}
}
private float GetDistance()
{
float distanceThisFrame = 0;
if(rigidBody)
{
//Distance = speed * deltaTime
float movementSpeed = Mathf.Max(rigidBody.linearVelocity.magnitude, rigidBody.angularVelocity.magnitude);
distanceThisFrame = movementSpeed * Time.deltaTime;
}
else
{
distanceThisFrame = Vector3.Distance(transform.position, previousPosition);
previousPosition = this.transform.position;
}
return distanceThisFrame;
}
public void FixedUpdate()
{
if (!particleSystem || !enabled) return;
emissionModule = particleSystem.emission;
if (emissionModule.enabled == false) return;
float distance = GetDistance();
//Teleportation safeguard
if (distance > 50f) return;
distanceAccumulation += distance;
var particlesToEmit = Mathf.CeilToInt(distanceAccumulation * spawnRatePerUnit);
if (particlesToEmit > 0)
{
particleSystem.Emit(particlesToEmit);
distanceAccumulation -= particlesToEmit / spawnRatePerUnit;
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(ParticleTrailEmitter))]
class ParticleTrailEmitterInspector : Editor
{
private ParticleTrailEmitter component;
private void OnEnable()
{
component = (ParticleTrailEmitter)target;
}
private void OnSceneGUI()
{
component.FixedUpdate();
}
}
#endif
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 6febe12fcf8d2e149842da43eb923880
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/ParticleTrailEmitter.cs
uploadId: 895866
@@ -0,0 +1,165 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace StylizedWater3
{
[ExecuteAlways]
[AddComponentMenu("Stylized Water 3/Water Custom Time")]
public class SetCustomWaterTime : MonoBehaviour
{
public enum Mode
{
None,
Interval,
Time,
EditorTime,
Speed,
[InspectorName("System Time (UTC)")]
SystemTime,
Custom
}
public Mode mode = Mode.Custom;
//Parameters for different modes
[Min(0.02f)]
public float interval = 0.2f;
public float speed = 0f;
[Min(0f)]
public float customTime = 0f;
private float elapsedTime;
private DateTime _startTime;
private void OnEnable()
{
RenderPipelineManager.beginContextRendering += OnBeginFrame;
_startTime = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day, 0, 0, 0);
}
private void OnBeginFrame(ScriptableRenderContext context, List<Camera> cams)
{
SetTime();
}
private void SetTime()
{
#if UNITY_EDITOR
if (mode == Mode.EditorTime)
{
WaterObject.CustomTime = (float)UnityEditor.EditorApplication.timeSinceStartup;
return;
}
#endif
if (mode == Mode.None)
{
ResetTime();
return;
}
if (mode == Mode.Interval)
{
elapsedTime += Time.deltaTime;
if (elapsedTime >= interval)
{
elapsedTime = 0;
WaterObject.CustomTime = Time.time;
}
}
else if (mode == Mode.Time)
{
WaterObject.CustomTime = Time.time;
}
else if (mode == Mode.Speed)
{
elapsedTime += Time.deltaTime * speed;
WaterObject.CustomTime = elapsedTime;
}
else if (mode == Mode.SystemTime)
{
WaterObject.CustomTime = (float)(DateTime.UtcNow - _startTime).TotalMilliseconds * 0.001f;
}
else if (mode == Mode.Custom)
{
WaterObject.CustomTime = customTime;
}
}
private void ResetTime()
{
elapsedTime = 0f;
//Revert to using normal time
WaterObject.CustomTime = -1;
}
private void OnDisable()
{
RenderPipelineManager.beginContextRendering -= OnBeginFrame;
ResetTime();
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(SetCustomWaterTime))]
public class SetCustomWaterTimeEditor : Editor
{
private SerializedProperty mode;
private SerializedProperty interval;
private SerializedProperty speed;
private SerializedProperty customTime;
private void OnEnable()
{
mode = serializedObject.FindProperty("mode");
interval = serializedObject.FindProperty("interval");
speed = serializedObject.FindProperty("speed");
customTime = serializedObject.FindProperty("customTime");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(mode);
if (mode.intValue == (int)SetCustomWaterTime.Mode.Interval)
{
EditorGUILayout.PropertyField(interval);
}
else if (mode.intValue == (int)SetCustomWaterTime.Mode.Speed)
{
EditorGUILayout.PropertyField(speed);
}
else if (mode.intValue == (int)SetCustomWaterTime.Mode.Custom)
{
EditorGUILayout.PropertyField(customTime);
}
if(WaterObject.CustomTime > 0 && mode.intValue != (int)SetCustomWaterTime.Mode.Custom) EditorGUILayout.HelpBox($"Time: {WaterObject.CustomTime}", MessageType.None);
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 33cfa047dbc543ea9a51492ad11fce6e
timeCreated: 1719917866
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/SetCustomWaterTime.cs
uploadId: 895866
@@ -0,0 +1,22 @@
using System;
using UnityEngine;
namespace StylizedWater3
{
[ExecuteAlways]
[AddComponentMenu("Stylized Water 3/Global Wave Origin Offset")]
public class SetGlobalWaveOriginOffset : MonoBehaviour
{
private readonly int _GlobalWaveOriginOffset = Shader.PropertyToID("_GlobalWaveOriginOffset");
private void Update()
{
Shader.SetGlobalVector(_GlobalWaveOriginOffset, this.transform.position);
}
private void OnDisable()
{
Shader.SetGlobalVector(_GlobalWaveOriginOffset, Vector3.zero);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 3290db5725a149f09da1024eb11ed2e1
timeCreated: 1747397807
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/SetGlobalWaveOriginOffset.cs
uploadId: 895866
@@ -0,0 +1,28 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using UnityEngine;
namespace StylizedWater3
{
[ExecuteAlways]
[AddComponentMenu("Stylized Water 3/Water Position Offset")]
public class SetWaterPositionOffset : MonoBehaviour
{
public bool negate;
private void Update()
{
//Note: in a floating origin system, apply the value after offsetting all the transforms!
//Otherwise the water geometry gets shifted in one frame, and this offset is applied the next. This induces a jitter.
StylizedWater3.WaterObject.PositionOffset = negate ? -this.transform.position : this.transform.position;
}
private void OnDisable()
{
StylizedWater3.WaterObject.PositionOffset = Vector3.zero;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ff38353f302f4942bdccd7cb32b19d37
timeCreated: 1728977343
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/SetWaterPositionOffset.cs
uploadId: 895866
@@ -0,0 +1,238 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
namespace StylizedWater3
{
[ExecuteInEditMode]
[AddComponentMenu("Stylized Water 3/Water Grid")]
public class WaterGrid : MonoBehaviour
{
[Tooltip("Material used on the tile meshes")]
public Material material;
[Tooltip("When not in play-mode, the water will follow the scene-view camera position.")]
public bool followSceneCamera = false;
[Tooltip("If enabled, the object with the \"MainCamera\" tag will be assigned as the follow target when entering play mode")]
public bool autoAssignCamera;
[Tooltip("The grid will follow this Transform's position on the XZ axis. Ideally set to the camera's transform.")]
public Transform followTarget;
[Tooltip("Scale of the entire grid in the length and width")]
public float scale = 500f;
[Range(0.15f, 10f)]
[Tooltip("Distance between vertices, rather higher than lower")]
public float vertexDistance = 2f;
[Min(0)]
public int rowsColumns = 4;
[HideInInspector]
public int m_rowsColumns = 4;
[SerializeField]
[HideInInspector]
private Mesh mesh;
[SerializeField]
[HideInInspector]
private List<WaterObject> objects = new List<WaterObject>();
[NonSerialized]
private float tileSize;
[NonSerialized]
private WaterObject m_waterObject = null;
[NonSerialized]
private Transform actualFollowTarget;
[NonSerialized]
private Vector3 targetPosition;
#if UNITY_EDITOR
public static bool DisplayGrid = true;
public static bool DisplayWireframe;
#endif
private void Reset()
{
Recreate();
}
private void Start()
{
if (autoAssignCamera) followTarget = Camera.main ? Camera.main.transform : followTarget;
}
private void OnEnable()
{
#if UNITY_EDITOR
UnityEditor.SceneView.duringSceneGui += OnSceneGUI;
#endif
m_rowsColumns = rowsColumns;
//Mesh is serialized with the scene, if component is used as a prefab, regenerate it
if (mesh == null)
{
RecreateMesh();
ReassignMesh();
}
}
#if UNITY_EDITOR
private void OnDisable()
{
UnityEditor.SceneView.duringSceneGui -= OnSceneGUI;
}
#endif
void Update()
{
if (Application.isPlaying) actualFollowTarget = followTarget;
if (actualFollowTarget)
{
targetPosition = actualFollowTarget.transform.position;
targetPosition = SnapToGrid(targetPosition, vertexDistance);
targetPosition.y = this.transform.position.y;
this.transform.position = targetPosition;
}
}
public void Recreate()
{
RecreateMesh();
bool requireRecreate = (m_rowsColumns != rowsColumns) || objects.Count < (rowsColumns * rowsColumns);
if (requireRecreate) m_rowsColumns = rowsColumns;
//Only destroy/recreate objects if grid subdivision has changed
if (requireRecreate && objects.Count > 0)
{
foreach (WaterObject obj in objects)
{
if (obj) DestroyImmediate(obj.gameObject);
}
objects.Clear();
}
int index = 0;
for (int x = 0; x < rowsColumns; x++)
{
for (int z = 0; z < rowsColumns; z++)
{
if (requireRecreate)
{
m_waterObject = WaterObject.New(material, mesh);
objects.Add(m_waterObject);
m_waterObject.transform.parent = this.transform;
m_waterObject.name = "WaterTile_x" + x + "z" + z;
}
else
{
m_waterObject = objects[index];
m_waterObject.AssignMesh(mesh);
m_waterObject.AssignMaterial(material);
}
m_waterObject.transform.localPosition = GridLocalCenterPosition(x, z);
m_waterObject.transform.localScale = Vector3.one;
index++;
}
}
}
private void CalculateTileSize()
{
rowsColumns = Mathf.Max(rowsColumns, 0);
float m_scale = scale * this.transform.lossyScale.x;
tileSize = Mathf.Max(1f, m_scale / rowsColumns);
}
private void RecreateMesh()
{
CalculateTileSize();
float m_vertexDistance = vertexDistance * this.transform.lossyScale.x;
//Value should never be larger than an individual tile
m_vertexDistance = Mathf.Min(m_vertexDistance, tileSize);
mesh = WaterMesh.Create(WaterMesh.Shape.Rectangle, tileSize, m_vertexDistance, tileSize);
}
private void ReassignMesh()
{
foreach (WaterObject obj in objects)
{
obj.AssignMesh(mesh);
}
}
private Vector3 GridLocalCenterPosition(int x, int z)
{
return new Vector3(x * tileSize - ((tileSize * (rowsColumns)) * 0.5f) + (tileSize * 0.5f), 0f,
z * tileSize - ((tileSize * (rowsColumns)) * 0.5f) + (tileSize * 0.5f));
}
public static Vector3 SnapToGrid(Vector3 position, float cellSize)
{
return new Vector3(SnapToGrid(position.x, cellSize), SnapToGrid(position.y, cellSize), SnapToGrid(position.z, cellSize));
}
private static float SnapToGrid(float position, float cellSize)
{
return Mathf.FloorToInt(position / cellSize) * (cellSize) + (cellSize * 0.5f);
}
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
if (DisplayWireframe)
{
Gizmos.matrix = this.transform.localToWorldMatrix;
Gizmos.color = new Color(0, 0, 0, 0.5f);
foreach (WaterObject waterObject in objects)
{
if(waterObject.meshFilter.sharedMesh) Gizmos.DrawWireMesh(waterObject.meshFilter.sharedMesh, waterObject.transform.localPosition);
}
}
if (DisplayGrid)
{
if (tileSize <= 0) CalculateTileSize();
Gizmos.color = new Color(1f, 0.25f, 0.25f, 0.5f);
Gizmos.matrix = this.transform.localToWorldMatrix;
for (int x = 0; x < rowsColumns; x++)
{
for (int z = 0; z < rowsColumns; z++)
{
Vector3 pos = GridLocalCenterPosition(x, z);
Gizmos.DrawWireCube(pos, new Vector3(tileSize, 0f, tileSize));
}
}
}
}
private void OnSceneGUI(UnityEditor.SceneView sceneView)
{
if (followSceneCamera)
{
actualFollowTarget = sceneView.camera.transform;
Update();
}
else
{
actualFollowTarget = null;
}
}
#endif
}
}
@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: bfcfc08716cec9c4ea8d75126995f02e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- material: {fileID: 2100000, guid: e8333e151973f1c4188ff534979c823b, type: 2}
- followTarget: {instanceID: 0}
executionOrder: 0
icon: {fileID: 5243786984396574768, guid: 0000000000000000d000000000000000, type: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/WaterGrid.cs
uploadId: 895866
@@ -0,0 +1,205 @@
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace StylizedWater3
{
/// <summary>
/// Attached to every mesh using the Stylized Water 3 shader
/// Provides a generic way of identifying water objects and accessing their properties
/// </summary>
[ExecuteInEditMode]
[AddComponentMenu("Stylized Water 3/Water Object")]
[DisallowMultipleComponent]
public class WaterObject : MonoBehaviour
{
/// <summary>
/// The default Water layer index in Unity
/// </summary>
public const int WaterLayer = 4;
/// <summary>
/// Collection of all available WaterObject instances. Instances (un)register themselves in the OnEnable/OnDisable functions.
/// </summary>
public static readonly List<WaterObject> Instances = new List<WaterObject>();
public Material material;
public MeshFilter meshFilter;
public MeshRenderer meshRenderer;
private static Vector3 s_PositionOffset;
private static readonly int _WaterPositionOffset = Shader.PropertyToID("_WaterPositionOffset");
/// <summary>
/// For use with floating-point origin systems. In the shader, the world-position (used for UV coordinates) will be offset by this value.
/// Buoyancy calculations will also be offset to stay in sync.
/// </summary>
public static Vector3 PositionOffset
{
set
{
s_PositionOffset = value;
Shader.SetGlobalVector(_WaterPositionOffset, s_PositionOffset);
}
internal get => s_PositionOffset;
}
private static float m_customTimeValue = -1f;
private static readonly int CustomTimeID = Shader.PropertyToID("_CustomTime");
/// <summary>
/// Pass in any time value, any kind of animations will use this as a time index, including wave animations (and thus height sampling as well).
/// This is typically used for network synchronized waves or cutscenes.
/// To revert to using normal <see cref="Time.time"/>, pass in a value lower than <c>0</c>.
/// </summary>
/// <param name="value"></param>
public static float CustomTime
{
set
{
m_customTimeValue = value;
Shader.SetGlobalFloat(CustomTimeID, m_customTimeValue);
}
get => m_customTimeValue;
}
private MaterialPropertyBlock _props;
public MaterialPropertyBlock props
{
get
{
//Fetch when required, execution order makes it unreliable otherwise
if (_props == null)
{
CreatePropertyBlock(meshRenderer);
}
return _props;
}
private set => _props = value;
}
private void CreatePropertyBlock(Renderer sourceRenderer)
{
_props = new MaterialPropertyBlock();
sourceRenderer.GetPropertyBlock(_props);
}
private void Reset()
{
meshRenderer = GetComponent<MeshRenderer>();
CreatePropertyBlock(meshRenderer);
meshFilter = GetComponent<MeshFilter>();
}
private void OnEnable()
{
Instances.Add(this);
}
private void OnDisable()
{
Instances.Remove(this);
}
private void OnValidate()
{
if (!meshRenderer) meshRenderer = GetComponent<MeshRenderer>();
if (!meshFilter) meshFilter = GetComponent<MeshFilter>();
FetchWaterMaterial();
}
/// <summary>
/// Grabs the material from the attached Mesh Renderer
/// </summary>
public Material FetchWaterMaterial()
{
if (meshRenderer)
{
material = meshRenderer.sharedMaterial;
return material;
}
return null;
}
/// <summary>
/// Applies to changes made to the Material Property Blocks ('props' property)
/// </summary>
public void ApplyInstancedProperties()
{
if(props != null) meshRenderer.SetPropertyBlock(props);
}
public void AssignMesh(Mesh mesh)
{
if (meshFilter) meshFilter.sharedMesh = mesh;
}
public void AssignMaterial(Material newMaterial)
{
if (meshRenderer) meshRenderer.sharedMaterial = newMaterial;
material = newMaterial;
}
/// <summary>
/// Creates a new GameObject with a MeshFilter, MeshRenderer and WaterObject component
/// </summary>
/// <param name="waterMaterial">If assigned, this material is automatically added to the MeshRenderer</param>
/// <returns></returns>
public static WaterObject New(Material waterMaterial = null, Mesh mesh = null)
{
GameObject go = new GameObject("Water Object", typeof(MeshFilter), typeof(MeshRenderer), typeof(WaterObject));
go.layer = LayerMask.NameToLayer("Water");
#if UNITY_EDITOR
UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Created Water Object");
#endif
WaterObject waterObject = go.GetComponent<WaterObject>();
waterObject.meshRenderer = waterObject.gameObject.GetComponent<MeshRenderer>();
waterObject.meshFilter = waterObject.gameObject.GetComponent<MeshFilter>();
waterObject.meshFilter.sharedMesh = mesh;
waterObject.meshRenderer.sharedMaterial = waterMaterial;
waterObject.meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
waterObject.material = waterMaterial;
return waterObject;
}
/// <summary>
/// Attempt to find the WaterObject above or below the position. Checks against the bounds of ALL Water Object meshes by raycasting on the XZ plane
/// </summary>
/// <param name="position">Position in world-space (height is not relevant)</param>
/// <param name="rotationSupport">Unless this is true, water rotated on the Y-axis will yield incorrect results (but is faster)</param>
/// <returns></returns>
public static WaterObject Find(Vector3 position, bool rotationSupport)
{
Ray ray = new Ray(position + (Vector3.up * 1000f), Vector3.down);
foreach (WaterObject obj in Instances)
{
if (rotationSupport)
{
//Local space
ray.origin = obj.transform.InverseTransformPoint(ray.origin);
if (obj.meshFilter.sharedMesh.bounds.IntersectRay(ray)) return obj;
}
else
{
//Axis-aligned bounds
if (obj.meshRenderer.bounds.IntersectRay(ray)) return obj;
}
}
return null;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 079d48f7ea5dc054a8b2cd9151030ee5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 22661e393f063dd4085f7fe57377fbc7, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 287769
packageName: Stylized Water 3
packageVersion: 3.2.6
assetPath: Assets/Stylized Water 3/Runtime/Components/WaterObject.cs
uploadId: 895866