Spline.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. public class SplineDraggableContext
  6. {
  7. public GameObject Dragable;
  8. public float Time;
  9. public bool Invalid;
  10. public GameObject DependentObject;
  11. }
  12. public enum DraggableSplineEndAction
  13. {
  14. Respawn,
  15. Destroy
  16. }
  17. public class Spline : MonoBehaviour
  18. {
  19. public GameObject DraggablePrefab;
  20. public float NewNodeOffset;
  21. public List<SplineSegment> Segments = new List<SplineSegment>();
  22. public float SpawnStartDelay = 0;
  23. public float SpawnDelay = 1;
  24. public int SpawnCount = 0;
  25. public float MoveSpeed = 0.5f;
  26. public DraggableSplineEndAction DraggableSplineEndAction;
  27. public float PreWarmTime = 100f;
  28. private float _spawnTimer;
  29. private float _totalLen;
  30. private int _spawned;
  31. private List<SplineDraggableContext> _contexts = new List<SplineDraggableContext>();
  32. public void AddNode()
  33. {
  34. Vector3 point = transform.position;
  35. Vector3 dir = Vector3.forward;
  36. float len = NewNodeOffset;
  37. if (_lines != null && _lines.Count > 0)
  38. {
  39. dir = (_lines.Last().To - _lines.Last().From).normalized;
  40. point = _lines.Last().From;
  41. }
  42. var obj = new GameObject($"Spline{Segments.Count}");
  43. obj.transform.parent = transform;
  44. obj.transform.position = point + dir * len;
  45. var segment = obj.AddComponent<SplineSegment>();
  46. var lastSegment = Segments.LastOrDefault();
  47. segment.H1 = new GameObject("Handle1").AddComponent<SplineHandle>();
  48. segment.H2 = new GameObject("Handle2").AddComponent<SplineHandle>();
  49. segment.H1.transform.parent = segment.transform;
  50. segment.H1.transform.position = point + dir * (len / 3);
  51. segment.H2.transform.parent = segment.transform;
  52. segment.H2.transform.position = point + dir * ((len / 3) * 2);
  53. Segments.Add(segment);
  54. }
  55. public void Close()
  56. {
  57. var first = Segments.First();
  58. var last = Segments.Last();
  59. last.transform.position = transform.position;
  60. Init();
  61. Segments.First().H1.SnapOppositeToAxis();
  62. }
  63. private bool Changed()
  64. {
  65. return Segments.Select(_ => _.Changed()).ToArray().Any(_ => _);
  66. }
  67. // Start is called before the first frame update
  68. void Start()
  69. {
  70. }
  71. void OnValidate()
  72. {
  73. Init();
  74. }
  75. void DrawDraggable(SplineDraggableContext ctx, float delta)
  76. {
  77. ctx.Time += delta;
  78. var dist = MoveSpeed * ctx.Time;
  79. if (dist > _totalLen)
  80. {
  81. if (DraggableSplineEndAction == DraggableSplineEndAction.Respawn)
  82. {
  83. dist = dist % _totalLen;
  84. var loopTime = _totalLen / MoveSpeed;
  85. ctx.Time = ctx.Time % loopTime;
  86. }
  87. else
  88. {
  89. ctx.Invalid = true;
  90. return;
  91. }
  92. }
  93. for (int i = 0; i < _lines.Count; ++i)
  94. {
  95. if (dist < _lines[i].Length)
  96. {
  97. var dir = (_lines[i].To - _lines[i].From).normalized;
  98. ctx.Dragable.transform.position = _lines[i].From;
  99. ctx.Dragable.transform.LookAt(_lines[i].To);
  100. return;
  101. }
  102. else
  103. dist -= _lines[i].Length;
  104. }
  105. }
  106. void DoPrewarm()
  107. {
  108. while (PreWarmTime > 0)
  109. {
  110. var dt = 0f;
  111. if (SpawnCount == 0 || _spawned < SpawnCount)
  112. {
  113. if (_contexts.Count == 0)
  114. {
  115. var d = SpawnStartDelay;
  116. PreWarmTime -= d;
  117. }
  118. else
  119. {
  120. var d = SpawnDelay;
  121. PreWarmTime -= d;
  122. }
  123. var obj = Instantiate(DraggablePrefab, transform);
  124. var particleSystem = obj.GetComponentInChildren<ParticleSystem>();
  125. _contexts.Add(new SplineDraggableContext()
  126. {
  127. Dragable = obj,
  128. DependentObject = particleSystem != null ? particleSystem.gameObject : null
  129. });
  130. ++_spawned;
  131. if (PreWarmTime > 0)
  132. {
  133. dt = Mathf.Min(SpawnDelay, PreWarmTime);
  134. PreWarmTime -= dt;
  135. }
  136. }
  137. else
  138. {
  139. dt = PreWarmTime;
  140. PreWarmTime = 0;
  141. }
  142. foreach (var context in _contexts)
  143. {
  144. DrawDraggable(context, dt);
  145. if (context.Invalid || context.DependentObject == null)
  146. Destroy(context.Dragable);
  147. }
  148. _contexts.RemoveAll(_ => _.Invalid || _.DependentObject == null);
  149. }
  150. }
  151. // Update is called once per frame
  152. void Update()
  153. {
  154. if(Changed())
  155. Init();
  156. if(Segments.RemoveAll(_ => _ == null) > 0)
  157. Init();
  158. if (PreWarmTime > 0)
  159. {
  160. DoPrewarm();
  161. }
  162. var dt = Time.deltaTime;
  163. if (SpawnCount == 0 || _spawned < SpawnCount)
  164. {
  165. var spawnNew = false;
  166. _spawnTimer += dt;
  167. if (_contexts.Count == 0)
  168. {
  169. if (spawnNew = _spawnTimer > SpawnStartDelay)
  170. {
  171. _spawnTimer -= SpawnStartDelay;
  172. }
  173. }
  174. else
  175. {
  176. if (spawnNew = _spawnTimer > SpawnDelay)
  177. _spawnTimer -= SpawnDelay;
  178. }
  179. if (spawnNew)
  180. {
  181. var obj = Instantiate(DraggablePrefab, transform);
  182. var particleSystem = obj.GetComponentInChildren<ParticleSystem>();
  183. _contexts.Add(new SplineDraggableContext()
  184. {
  185. Dragable = obj,
  186. DependentObject = particleSystem != null ? particleSystem.gameObject : null
  187. });
  188. ++_spawned;
  189. }
  190. }
  191. foreach (var context in _contexts)
  192. {
  193. DrawDraggable(context, dt);
  194. if (context.Invalid || context.DependentObject == null)
  195. Destroy(context.Dragable);
  196. }
  197. _contexts.RemoveAll(_ => _.Invalid || _.DependentObject == null);
  198. PreWarmTime = 0;
  199. }
  200. void Init()
  201. {
  202. _lines.Clear();
  203. _controls.Clear();
  204. _totalLen = 0;
  205. Segments.RemoveAll(_ => _ == null);
  206. var p0 = transform.position;
  207. for(var s = 0; s < Segments.Count; ++s)
  208. {
  209. var segment = Segments[s];
  210. Vector3 p1 = segment.H1.transform.position;
  211. Vector3 p2 = segment.H2.transform.position;
  212. Vector3 p3 = segment.transform.position;
  213. Vector3 v0 = p0;
  214. Gizmos.color = Color.white;
  215. for (int i = 1; i < 1001; i++)
  216. {
  217. var t = i / 1000.0f;
  218. var v1 = Mathf.Pow(1f - t, 3) * p0
  219. + 3 * t * Mathf.Pow(1 - t, 2) * p1
  220. + 3 * Mathf.Pow(t, 2) * (1 - t) * p2
  221. + Mathf.Pow(t, 3) * p3;
  222. var len = Vector3.Distance(v0, v1);
  223. _totalLen += len;
  224. _lines.Add(new Line()
  225. {
  226. From = v0,
  227. To = v1,
  228. Length = len
  229. });
  230. v0 = v1;
  231. }
  232. _controls.Add(new Line()
  233. {
  234. From = p0,
  235. To = p1
  236. });
  237. _controls.Add(new Line()
  238. {
  239. From = p3,
  240. To = p2
  241. });
  242. p0 = p3;
  243. segment.H1.Opposite = null;
  244. segment.H1.Origin = null;
  245. segment.H2.Opposite = null;
  246. segment.H2.Origin = null;
  247. if (s > 0)
  248. {
  249. var prevSegment = Segments[s - 1];
  250. prevSegment.H2.Opposite = segment.H1.gameObject;
  251. prevSegment.H2.Origin = prevSegment.gameObject;
  252. segment.H1.Opposite = prevSegment.H2.gameObject;
  253. segment.H1.Origin = prevSegment.gameObject;
  254. if (s + 1 == Segments.Count)
  255. {
  256. var firstSegment = Segments.First();
  257. var dist = Vector3.Distance(segment.transform.position, transform.position);
  258. if (dist < 0.0000001f)
  259. {
  260. segment.H2.Opposite = firstSegment.H1.gameObject;
  261. segment.H2.Origin = gameObject;
  262. firstSegment.H1.Opposite = segment.H2.gameObject;
  263. firstSegment.H1.Origin = gameObject;
  264. }
  265. }
  266. }
  267. }
  268. Debug.Log($"Full spline len {_totalLen}.");
  269. transform.hasChanged = false;
  270. }
  271. void OnDrawGizmos()
  272. {
  273. if(Segments.RemoveAll(_ => _ == null) > 0)
  274. Init();
  275. if (Changed())
  276. Init();
  277. Gizmos.color = Color.white;
  278. foreach (var line in _lines)
  279. {
  280. Gizmos.DrawLine(line.From, line.To);
  281. }
  282. Gizmos.color = Color.green;
  283. foreach (var line in _controls)
  284. {
  285. Gizmos.DrawLine(line.From, line.To);
  286. }
  287. }
  288. private List<Line> _controls = new List<Line>();
  289. private List<Line> _lines = new List<Line>();
  290. struct Line
  291. {
  292. public Vector3 From;
  293. public Vector3 To;
  294. public float Length;
  295. }
  296. }