using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace SplineMesh { /// /// Example of component to places assets along a spline. This component can be used as-is but will most likely be a base for your own component. /// /// In this example, the user gives the prefab to place, a spacing value between two placements, the prefab scale and an horizontal offset to the spline. /// These three last values have an additional range, allowing to add some randomness. for each placement, the computed value will be between value and value+range. /// /// Prefabs are placed from the start of the spline at computed spacing, unitl there is no lentgh remaining. Prefabs are stored, destroyed /// and built again each time the spline or one of its curves change. /// /// A random seed is used to obtain the same random numbers at each update. The user can specify the seed to test some other random number set. /// /// Place prefab along a spline and deform it easily have a lot of usages if you have some imagination : /// - place trees along a road /// - create a rocky bridge /// - create a footstep track with decals /// - create a path of firefly in the dark /// - create a natural wall with overlapping rocks /// - etc. /// [ExecuteInEditMode] [SelectionBase] [DisallowMultipleComponent] public class ExampleSower : MonoBehaviour { private GameObject generated; private Spline spline = null; private bool toUpdate = true; public GameObject prefab = null; public float scale = 1, scaleRange = 0; public float spacing = 1, spacingRange = 0; public float offset = 0, offsetRange = 0; public bool isRandomYaw = false; public int randomSeed = 0; private void OnEnable() { string generatedName = "generated by "+GetType().Name; var generatedTranform = transform.Find(generatedName); generated = generatedTranform != null ? generatedTranform.gameObject : UOUtility.Create(generatedName, gameObject); spline = GetComponentInParent(); spline.NodeListChanged += (s, e) => { toUpdate = true; foreach (CubicBezierCurve curve in spline.GetCurves()) { curve.Changed.AddListener(() => toUpdate = true); } }; foreach (CubicBezierCurve curve in spline.GetCurves()) { curve.Changed.AddListener(() => toUpdate = true); } } private void OnValidate() { toUpdate = true; } private void Update() { if (toUpdate) { Sow(); toUpdate = false; } } public void Sow() { UOUtility.DestroyChildren(generated); UnityEngine.Random.InitState(randomSeed); if (spacing + spacingRange <= 0 || prefab == null) return; float distance = 0; while (distance <= spline.Length) { CurveSample sample = spline.GetSampleAtDistance(distance); GameObject go; go = Instantiate(prefab, generated.transform); go.transform.localRotation = Quaternion.identity; go.transform.localPosition = Vector3.zero; go.transform.localScale = Vector3.one; // move along spline, according to spacing + random go.transform.localPosition = sample.location; // apply scale + random float rangedScale = scale + UnityEngine.Random.Range(0, scaleRange); go.transform.localScale = new Vector3(rangedScale, rangedScale, rangedScale); // rotate with random yaw if (isRandomYaw) { go.transform.Rotate(0, 0, UnityEngine.Random.Range(-180, 180)); } else { go.transform.rotation = sample.Rotation; } // move orthogonaly to the spline, according to offset + random var binormal = (Quaternion.LookRotation(sample.tangent, sample.up) * Vector3.right).normalized; var localOffset = offset + UnityEngine.Random.Range(0, offsetRange * Math.Sign(offset)); localOffset *= sample.scale.x; binormal *= localOffset; go.transform.position += binormal; distance += spacing + UnityEngine.Random.Range(0, spacingRange); } } } }