using UnityEngine; using UnityEngine.Splines; using Unity.Mathematics; using System.Linq; using System.Collections; using System.Collections.Generic; public class MultiSplineFollower : MonoBehaviour { [Header("Tuyến đường 1")] public SplineRoute[] routes; [Header("Tuyến đường 2")] public SplineRoute[] routes1; [Header("Tuyến đường 3")] public SplineRoute[] routes2; [Header("Cài đặt chuyển động")] public float speed = 2f; public float rotationSpeed = 180f; public float angleThreshold = 30f; [Header("Cài đặt quét TCIA")] public float scanRadius = 0.5f; // bán kính quét đối tượng TCIA quanh follower private int currentRouteIndex = 0; private int currentRouteGroup = 0; // 0 = routes, 1 = routes1, 2 = routes2 private float t = 0f; private bool onFirstSpline = true; private bool isDelaying = false; void Start() { if (GetActiveRoutes() == null || GetActiveRoutes().Length == 0) return; var activeRoutes = GetActiveRoutes(); InitRoute(activeRoutes[0]); t = activeRoutes[0].tStart; transform.position = activeRoutes[0].splineStart.EvaluatePosition(0, t); } void Update() { var activeRoutes = GetActiveRoutes(); if (activeRoutes == null || activeRoutes.Length == 0) return; if (currentRouteIndex >= activeRoutes.Length) return; if (isDelaying) return; SplineRoute route = activeRoutes[currentRouteIndex]; if (onFirstSpline) MoveOnSpline(route.splineStart, ref t, route.tCommonA, true, route); else MoveOnSpline(route.splineEnd, ref t, route.tEnd, false, route); } SplineRoute[] GetActiveRoutes() { switch (currentRouteGroup) { case 0: return routes; case 1: return routes1; case 2: return routes2; default: return null; } } void InitRoute(SplineRoute route) { BezierKnot knotStart = route.splineStart.Spline.Knots.ElementAt(route.startKnotIndex); BezierKnot knotEnd = route.splineEnd.Spline.Knots.ElementAt(route.endKnotIndex); Vector3 knotX = route.splineStart.transform.TransformPoint((Vector3)knotStart.Position); Vector3 knotY = route.splineEnd.transform.TransformPoint((Vector3)knotEnd.Position); route.commonPoint = FindCommonPoint(route.splineStart, route.splineEnd); float3 nearest; SplineUtility.GetNearestPoint(route.splineStart.Spline, (float3)route.splineStart.transform.InverseTransformPoint(knotX), out nearest, out route.tStart); SplineUtility.GetNearestPoint(route.splineStart.Spline, (float3)route.splineStart.transform.InverseTransformPoint(route.commonPoint), out nearest, out route.tCommonA); SplineUtility.GetNearestPoint(route.splineEnd.Spline, (float3)route.splineEnd.transform.InverseTransformPoint(route.commonPoint), out nearest, out route.tCommonB); SplineUtility.GetNearestPoint(route.splineEnd.Spline, (float3)route.splineEnd.transform.InverseTransformPoint(knotY), out nearest, out route.tEnd); onFirstSpline = true; } void MoveOnSpline(SplineContainer spline, ref float t, float targetT, bool first, SplineRoute route) { Vector3 pos = spline.EvaluatePosition(0, t); Vector3 tangent = Vector3.Normalize((Vector3)spline.EvaluateTangent(0, t)); if (route.reverseDirection) tangent = -tangent; float angle = Vector3.Angle(transform.forward, tangent); if (angle > angleThreshold) { Quaternion targetRot = Quaternion.LookRotation(tangent, Vector3.up); transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRot, rotationSpeed * Time.deltaTime); return; } float dir = (targetT > t) ? 1f : -1f; t += dir * (Time.deltaTime * speed / spline.CalculateLength(0)); if ((dir > 0 && t >= targetT) || (dir < 0 && t <= targetT)) t = targetT; pos = spline.EvaluatePosition(0, t); tangent = Vector3.Normalize((Vector3)spline.EvaluateTangent(0, t)); if (route.reverseDirection) tangent = -tangent; transform.position = pos; transform.rotation = Quaternion.LookRotation(tangent, Vector3.up); if (first && Mathf.Approximately(t, targetT)) { onFirstSpline = false; t = route.tCommonB; } else if (!first && Mathf.Approximately(t, targetT)) { if (route.delayAtEnd) { StartCoroutine(DelayAndNextRoute(route)); } else { NextRoute(); } } } IEnumerator DelayAndNextRoute(SplineRoute route) { isDelaying = true; if (route.attachTCIAObjects) { Debug.Log($"[{route.splineStart.name}] attachTCIAObjects = TRUE → quét và gắn TCIA"); AttachTCIAChildren(); } else { DetachTCIAChildren(); } yield return new WaitForSeconds(2f); isDelaying = false; NextRoute(); } void AttachTCIAChildren() { GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); foreach (GameObject obj in allObjects) { if (!obj.activeInHierarchy || obj == this.gameObject) continue; if (obj.name.ToUpper().Contains("TCIA")) { float distance = Vector3.Distance(transform.position, obj.transform.position); if (distance <= scanRadius) { obj.transform.SetParent(this.transform, true); } } } } void DetachTCIAChildren() { List childrenToDetach = new List(); foreach (Transform child in transform) { if (child.name.ToUpper().Contains("TCIA")) { childrenToDetach.Add(child); } } foreach (Transform child in childrenToDetach) { child.SetParent(null, true); } } void NextRoute() { var activeRoutes = GetActiveRoutes(); currentRouteIndex++; if (activeRoutes != null && currentRouteIndex < activeRoutes.Length) { InitRoute(activeRoutes[currentRouteIndex]); t = activeRoutes[currentRouteIndex].tStart; transform.position = activeRoutes[currentRouteIndex].splineStart.EvaluatePosition(0, t); } else { // Hết nhóm hiện tại → chuyển sang nhóm tiếp theo currentRouteGroup++; currentRouteIndex = 0; var nextGroupRoutes = GetActiveRoutes(); if (nextGroupRoutes != null && nextGroupRoutes.Length > 0) { Debug.Log($"Chuyển sang nhóm tuyến: {currentRouteGroup}"); InitRoute(nextGroupRoutes[0]); t = nextGroupRoutes[0].tStart; transform.position = nextGroupRoutes[0].splineStart.EvaluatePosition(0, t); } else { Debug.Log("Hoàn thành toàn bộ tuyến đường!"); } } } Vector3 FindCommonPoint(SplineContainer s1, SplineContainer s2) { foreach (var knotA in s1.Spline.Knots) { Vector3 pA = s1.transform.TransformPoint((Vector3)knotA.Position); foreach (var knotB in s2.Spline.Knots) { Vector3 pB = s2.transform.TransformPoint((Vector3)knotB.Position); if (Vector3.Distance(pA, pB) < 0.01f) return pA; } } Debug.LogWarning("Không tìm thấy điểm chung!"); return Vector3.zero; } } [System.Serializable] public class SplineRoute { public SplineContainer splineStart; public int startKnotIndex; public SplineContainer splineEnd; public int endKnotIndex; public bool reverseDirection; public bool delayAtEnd; public bool attachTCIAObjects = true; // true = gắn TCIA, false = tách TCIA khi delay [HideInInspector] public Vector3 commonPoint; [HideInInspector] public float tStart; [HideInInspector] public float tCommonA; [HideInInspector] public float tCommonB; [HideInInspector] public float tEnd; }