using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(LineRenderer))] public class PhysicLineSegment : MonoBehaviour { public float segmentSpacing = 0.1f; public int k = 30; [Header("出线口保护段")] [SerializeField] private float outletGuardLength = 0.15f; [SerializeField] private int outletGuardParticleCount = 4; public List particles = new(); private List sticks = new(); [HideInInspector] public LineRenderer lineRenderer; private int segmentCount; public float gravity = -9.8f; private float originalLength; private Vector3 outletStart; private Vector3 outletDirection = Vector3.right; private int protectedParticleCount = 1; public void Initialize(Vector3 start, Vector3 end) { Initialize(start, end, end - start); } public void Initialize(Vector3 start, Vector3 end, Vector3 startDirection) { particles.Clear(); sticks.Clear(); if (lineRenderer != null) { lineRenderer.positionCount = 0; } originalLength = Vector3.Distance(start, end); outletStart = start; outletDirection = ResolveDirection(startDirection, end - start); float spacing = Mathf.Max(0.001f, segmentSpacing); segmentCount = Mathf.Max(3, Mathf.CeilToInt(originalLength / spacing) + 1); protectedParticleCount = ResolveProtectedParticleCount(segmentCount); lineRenderer = GetComponent(); float guardLength = ResolveGuardLength(); Vector3 guardEnd = start + outletDirection * guardLength; // Initialize particles for (int i = 0; i < segmentCount; i++) { Vector3 position = GetInitialParticlePosition(start, guardEnd, end, i); var particle = new VerletParticle(position) { isLocked = i < protectedParticleCount }; particles.Add(particle); } // Initialize sticks for (int i = 0; i < segmentCount - 1; i++) { sticks.Add(new VerletStick( particles[i], particles[i + 1], Vector3.Distance(particles[i].position, particles[i + 1].position))); } ApplyOutletGuard(); lineRenderer.positionCount = segmentCount; } void FixedUpdate() { UpdateLine(); for (int i = 0; i < segmentCount; i++) { if (lineRenderer != null) { lineRenderer.SetPosition(i, particles[i].position); } } } void UpdateLine() { ApplyOutletGuard(); // Apply gravity to particles for (int i = 0; i < particles.Count; i++) { particles[i].ApplyForce(Vector3.up * gravity * Time.deltaTime); particles[i].UpdatePosition(); } // Update sticks for (int ik = 0; ik < k; ik++) { foreach (var stick in sticks) { stick.UpdateStick(); } } ApplyOutletGuard(); } public void UpdatePlug(Transform plug) { if (plug && particles.Count > 1) { Vector3 plugPosition = particles[^1].position; float plugLength = plug.GetComponent().bounds.size.y; Vector3 direction = particles[^1].position - particles[^2].position; float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg; plug.rotation = Quaternion.Euler(0, 0, angle - 90f); plug.position = plugPosition + direction.normalized * (plugLength / 2); } } public void UpdateStart(Vector3 newStartPosition) { outletStart = newStartPosition; ApplyOutletGuard(); } public void UpdateOutletDirection(Vector3 newOutletDirection) { outletDirection = ResolveDirection(newOutletDirection, outletDirection); ApplyOutletGuard(); } private Vector3 GetInitialParticlePosition(Vector3 start, Vector3 guardEnd, Vector3 end, int index) { if (index < protectedParticleCount) { float t = protectedParticleCount <= 1 ? 0f : index / (float)(protectedParticleCount - 1); return Vector3.Lerp(start, guardEnd, t); } int flexibleCount = segmentCount - protectedParticleCount; float flexT = (index - protectedParticleCount + 1) / (float)flexibleCount; return Vector3.Lerp(guardEnd, end, flexT); } private void ApplyOutletGuard() { if (particles.Count == 0) return; int count = Mathf.Min(protectedParticleCount, particles.Count); float guardLength = ResolveGuardLength(); for (int i = 0; i < count; i++) { float t = count <= 1 ? 0f : i / (float)(count - 1); Vector3 lockedPosition = outletStart + outletDirection * (guardLength * t); particles[i].position = lockedPosition; particles[i].previousPosition = lockedPosition; particles[i].isLocked = true; } } private int ResolveProtectedParticleCount(int count) { int requestedCount = Mathf.Max(1, outletGuardParticleCount); return Mathf.Clamp(requestedCount, 1, Mathf.Max(1, count - 1)); } private float ResolveGuardLength() { float configuredLength = Mathf.Max(0f, outletGuardLength); return Mathf.Min(configuredLength, originalLength * 0.45f); } private static Vector3 ResolveDirection(Vector3 direction, Vector3 fallback) { if (direction.sqrMagnitude > 1e-6f) { return direction.normalized; } if (fallback.sqrMagnitude > 1e-6f) { return fallback.normalized; } return Vector3.right; } }