using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; namespace SplineMesh { /// /// Example of component to bend many meshes along a spline. This component can be used as-is but will most likely be a base for your own component. /// /// This is a more advanced and real-life SplineMesh component. Use it as a source of inspiration. /// /// In this script, you will learn to : /// - preserve baked lightmap when entering playmode, /// - better manage the generated content life cycle to avoid useless calculations /// - create data class to produce richer content along your spline /// /// This is the most complete Example provided in the asset. For further help, information and ideas, please visit /// the officiel thread on Unity forum. /// /// And if you like SplineMesh, please review it on the asset store ! /// /// Now you should be able to bend the world to your will. /// /// Have fun with SplineMesh ! /// /// [ExecuteInEditMode] [SelectionBase] [DisallowMultipleComponent] public class ExampleTrack : MonoBehaviour { private GameObject generated; private Spline spline = null; private bool toUpdate = false; /// /// A list of object that are storing data for each segment of the curve. /// public List segments = new List(); /// /// If true, the generated content will be updated in play mode. /// If false, the content generated and saved to the scene will be used in playmode without modification. /// Usefull to preserve lightmaps baked for static objects. /// public bool updateInPlayMode; 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(); // we listen changes in the spline's node list and we update the list of segment accordingly // this way, if we insert a node between two others, a segment will be inserted too and the data won't shift while (segments.Count < spline.nodes.Count) { segments.Add(new TrackSegment()); } while (segments.Count > spline.nodes.Count) { segments.RemoveAt(segments.Count - 1); } spline.NodeListChanged += (s, e) => { switch (e.type) { case ListChangeType.Add: segments.Add(new TrackSegment()); break; case ListChangeType.Remove: segments.RemoveAt(e.removeIndex); break; case ListChangeType.Insert: segments.Insert(e.insertIndex, new TrackSegment()); break; } toUpdate = true; }; toUpdate = true; } private void OnValidate() { if (spline == null) return; toUpdate = true; } private void Update() { // we can prevent the generated content to be updated during playmode to preserve baked data saved in the scene if (!updateInPlayMode && Application.isPlaying) return; if (toUpdate) { toUpdate = false; CreateMeshes(); } } public void CreateMeshes() { List used = new List(); for (int i = 0; i < spline.GetCurves().Count; i++) { var curve = spline.GetCurves()[i]; foreach (var tm in segments[i].transformedMeshes) { if (tm.mesh == null) { // if there is no mesh specified for this segment, we ignore it. continue; } // we try to find a game object previously generated. this avoids destroying/creating // game objects at each update, wich is faster. var childName = "segment " + i + " mesh " + segments[i].transformedMeshes.IndexOf(tm); var childTransform = generated.transform.Find(childName); GameObject go; if (childTransform == null) { go = UOUtility.Create(childName, generated, typeof(MeshFilter), typeof(MeshRenderer), typeof(MeshBender), typeof(MeshCollider)); go.isStatic = true; } else { go = childTransform.gameObject; } go.GetComponent().material = tm.material; go.GetComponent().material = tm.physicMaterial; // we update the data in the bender. It will decide itself if the bending must be recalculated. MeshBender mb = go.GetComponent(); mb.Source = SourceMesh.Build(tm.mesh) .Translate(tm.translation) .Rotate(Quaternion.Euler(tm.rotation)) .Scale(tm.scale); mb.SetInterval(curve); mb.ComputeIfNeeded(); used.Add(go); } } // finally, we destroy the unused objects foreach (var go in generated.transform .Cast() .Select(child => child.gameObject).Except(used)) { UOUtility.Destroy(go); } } } /// /// This class store any data associated with a spline segment. /// In this example, a list of meshes. /// It is intended to be edited in the inspector. /// [Serializable] public class TrackSegment { public List transformedMeshes = new List(); } /// /// This class stores all needed data to represent a mesh in situation. /// It is intended to be edited in the inspector. /// [Serializable] public class TransformedMesh { public TransformedMesh() { scale = Vector3.one; } public Mesh mesh; public Material material; public PhysicMaterial physicMaterial; public Vector3 translation; public Vector3 rotation; public Vector3 scale = Vector3.one; } }