Files
2026-07-08 22:31:21 +08:00

191 lines
5.8 KiB
C#

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<VerletParticle> particles = new();
private List<VerletStick> 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<LineRenderer>();
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<SpriteRenderer>().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;
}
}