191 lines
5.8 KiB
C#
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;
|
|
}
|
|
}
|