using System;
using UnityEngine;
using System.Collections.Generic;
using NBC;
namespace NBF
{
public struct FLineNodeConstraintState
{
public Vector3 TargetPosition;
public float PlanarStrength;
public float UpwardStrength;
public float DownwardStrength;
public float MaxCorrection;
}
///
/// 为 FLine 节点提供附加软约束。求解器只依赖这个通用接口,不依赖具体玩法组件。
///
public interface IFLineNodeConstraintProvider
{
bool TryGetConstraintState(out FLineNodeConstraintState state);
}
public enum LineType
{
Spinning,
SpinningFloat,
Hand,
HandDouble,
}
public class FLine : FGearBase
{
[Header("基本参数设置")] public LineType LineType;
[Header("连接点配置")] [SerializeField] private Transform anchorTransform;
[SerializeField] private List lineNodes = new List();
[Header("物理参数")] [SerializeField] private float positionCorrectionForce = 100f;
[SerializeField] private float dampingCoefficient = 10f;
[SerializeField] private int constraintIterations = 10;
[SerializeField] private bool useMassWeighting = true;
[Header("动态间距设置")] [SerializeField] private float defaultTransitionSpeed = 2f; // 默认长度变化速度(单位/秒)
[Header("调试")] [SerializeField] private bool showDebugInfo = true;
private readonly List _constraints = new List();
private readonly List _nodeConstraintProviders =
new List();
[System.Serializable]
public class ConnectionConstraint
{
public Rigidbody bodyA;
public Rigidbody bodyB;
public float maxDistance;
public float minDistance;
public float currentDistance;
public Vector3 direction;
// 动态目标距离(用于平滑过渡)
public float targetMaxDistance;
public float targetMinDistance;
public bool hasPendingTransition;
public bool hasPendingMaxTransition;
public bool hasPendingMinTransition;
public float maxTransitionSpeed;
public float minTransitionSpeed;
public ConnectionConstraint(Rigidbody a, Rigidbody b, float maxDist, float minDist = 0f)
{
bodyA = a;
bodyB = b;
maxDistance = maxDist;
minDistance = minDist;
targetMaxDistance = maxDist;
targetMinDistance = minDist;
hasPendingTransition = false;
hasPendingMaxTransition = false;
hasPendingMinTransition = false;
maxTransitionSpeed = 0f;
minTransitionSpeed = 0f;
}
public void UpdateCurrentState()
{
if (bodyA && bodyB)
{
Vector3 delta = bodyB.position - bodyA.position;
currentDistance = delta.magnitude;
direction = currentDistance > 0.0001f ? delta.normalized : Vector3.right;
}
}
}
void Awake()
{
BuildConstraints();
}
protected override void OnInit()
{
anchorTransform = Rod.Asset.lineConnector;
}
void Start()
{
if (_constraints.Count == 0)
{
Debug.LogWarning("FLine需要至少2个有效节点");
enabled = false;
}
}
void FixedUpdate()
{
if (!enabled || _constraints.Count == 0) return;
UpdateAnchorNode();
// 更新动态过渡
UpdateTransitions();
for (int iteration = 0; iteration < constraintIterations; iteration++)
{
ApplyDistanceConstraints();
ApplyNodeConstraints();
}
ApplyDamping();
UpdateBreakCountdown(Time.fixedDeltaTime);
}
private void BuildConstraints()
{
_constraints.Clear();
_nodeConstraintProviders.Clear();
if (lineNodes.Count < 2) return;
for (int i = 0; i < lineNodes.Count; i++)
{
if (lineNodes[i])
{
lineNodes[i].AttachToCable(this);
_nodeConstraintProviders.Add(CollectNodeConstraintProviders(lineNodes[i]));
}
else
{
_nodeConstraintProviders.Add(Array.Empty());
}
}
// 创建约束
for (int i = 0; i < lineNodes.Count - 1; i++)
{
FLineLogicNode currentNode = lineNodes[i];
FLineLogicNode nextNode = lineNodes[i + 1];
Rigidbody bodyA = currentNode ? currentNode.Rigidbody : null;
Rigidbody bodyB = nextNode ? nextNode.Rigidbody : null;
if (bodyA != null && bodyB != null)
{
var constraint = new ConnectionConstraint(
bodyA,
bodyB,
currentNode.NextSegmentMaxLength,
currentNode.NextSegmentMinLength
);
_constraints.Add(constraint);
}
}
}
private FLineLogicNode GetSegmentNode(int segmentIndex)
{
if (segmentIndex < 0 || segmentIndex >= lineNodes.Count - 1)
{
return null;
}
return lineNodes[segmentIndex];
}
private Rigidbody GetBodyAt(int nodeIndex)
{
if (nodeIndex < 0 || nodeIndex >= lineNodes.Count)
{
return null;
}
FLineLogicNode node = lineNodes[nodeIndex];
return node ? node.Rigidbody : null;
}
private IFLineNodeConstraintProvider[] GetNodeConstraintProvidersAt(int nodeIndex)
{
if (nodeIndex < 0 || nodeIndex >= _nodeConstraintProviders.Count)
{
return Array.Empty();
}
return _nodeConstraintProviders[nodeIndex];
}
private static IFLineNodeConstraintProvider[] CollectNodeConstraintProviders(FLineLogicNode node)
{
MonoBehaviour[] behaviours = node.GetComponents();
List providers = new List();
for (int i = 0; i < behaviours.Length; i++)
{
MonoBehaviour behaviour = behaviours[i];
if (behaviour is IFLineNodeConstraintProvider provider)
{
providers.Add(provider);
}
}
return providers.ToArray();
}
private void SyncSegmentMaxLength(int segmentIndex, float maxLength)
{
FLineLogicNode node = GetSegmentNode(segmentIndex);
if (node)
{
node.NextSegmentMaxLength = maxLength;
}
}
private void SyncSegmentMinLength(int segmentIndex, float minLength)
{
FLineLogicNode node = GetSegmentNode(segmentIndex);
if (node)
{
node.NextSegmentMinLength = minLength;
}
}
private bool ContainsBody(Rigidbody targetBody)
{
for (int i = 0; i < lineNodes.Count; i++)
{
if (GetBodyAt(i) == targetBody)
{
return true;
}
}
return false;
}
///
/// 更新所有活跃的过渡
///
private void UpdateTransitions()
{
float deltaTime = Time.fixedDeltaTime;
for (int i = 0; i < _constraints.Count; i++)
{
var constraint = _constraints[i];
if (constraint.hasPendingMaxTransition)
{
float nextMaxDistance = Mathf.MoveTowards(
constraint.maxDistance,
constraint.targetMaxDistance,
constraint.maxTransitionSpeed * deltaTime
);
constraint.maxDistance = nextMaxDistance;
SyncSegmentMaxLength(i, nextMaxDistance);
if (Mathf.Abs(nextMaxDistance - constraint.targetMaxDistance) < 0.0001f)
{
constraint.maxDistance = constraint.targetMaxDistance;
constraint.hasPendingMaxTransition = false;
SyncSegmentMaxLength(i, constraint.targetMaxDistance);
}
}
if (constraint.hasPendingMinTransition)
{
float nextMinDistance = Mathf.MoveTowards(
constraint.minDistance,
constraint.targetMinDistance,
constraint.minTransitionSpeed * deltaTime
);
constraint.minDistance = nextMinDistance;
SyncSegmentMinLength(i, nextMinDistance);
if (Mathf.Abs(nextMinDistance - constraint.targetMinDistance) < 0.0001f)
{
constraint.minDistance = constraint.targetMinDistance;
constraint.hasPendingMinTransition = false;
SyncSegmentMinLength(i, constraint.targetMinDistance);
}
}
constraint.hasPendingTransition =
constraint.hasPendingMaxTransition || constraint.hasPendingMinTransition;
}
}
private void ApplyDistanceConstraints()
{
for (int i = 0; i < _constraints.Count; i++)
{
var constraint = _constraints[i];
if (!constraint.bodyA || !constraint.bodyB) continue;
constraint.UpdateCurrentState();
float currentDist = constraint.currentDistance;
float maxDist = constraint.maxDistance;
float minDist = constraint.minDistance;
float error = 0f;
bool needCorrection = false;
if (currentDist > maxDist)
{
error = currentDist - maxDist;
needCorrection = true;
}
if (!needCorrection || Mathf.Abs(error) < 0.0001f) continue;
float invMassA = constraint.bodyA.isKinematic ? 0f : 1f / constraint.bodyA.mass;
float invMassB = constraint.bodyB.isKinematic ? 0f : 1f / constraint.bodyB.mass;
float totalInvMass = invMassA + invMassB;
if (totalInvMass < 0.0001f) continue;
float weightA = useMassWeighting ? (invMassA / totalInvMass) : 0.5f;
float weightB = useMassWeighting ? (invMassB / totalInvMass) : 0.5f;
Vector3 correction = constraint.direction * error;
Vector3 positionCorrectionA = correction * weightA;
Vector3 positionCorrectionB = -correction * weightB;
constraint.bodyA.position += positionCorrectionA;
constraint.bodyB.position += positionCorrectionB;
Vector3 velocityCorrectionA = positionCorrectionA / Time.fixedDeltaTime;
Vector3 velocityCorrectionB = positionCorrectionB / Time.fixedDeltaTime;
constraint.bodyA.AddForce(velocityCorrectionA * constraint.bodyA.mass, ForceMode.Impulse);
constraint.bodyB.AddForce(velocityCorrectionB * constraint.bodyB.mass, ForceMode.Impulse);
}
}
private void ApplyNodeConstraints()
{
for (int i = 0; i < lineNodes.Count; i++)
{
Rigidbody body = GetBodyAt(i);
IFLineNodeConstraintProvider[] providers = GetNodeConstraintProvidersAt(i);
if (!body || body.isKinematic || providers.Length == 0)
{
continue;
}
for (int providerIndex = 0; providerIndex < providers.Length; providerIndex++)
{
IFLineNodeConstraintProvider provider = providers[providerIndex];
if (provider == null)
{
continue;
}
if (provider is Behaviour behaviour && !behaviour.isActiveAndEnabled)
{
continue;
}
if (!provider.TryGetConstraintState(out FLineNodeConstraintState constraint))
{
continue;
}
ApplyWorldTargetConstraint(body, constraint);
}
}
}
private static void ApplyWorldTargetConstraint(Rigidbody body, FLineNodeConstraintState constraint)
{
Vector3 delta = constraint.TargetPosition - body.position;
Vector3 planarDelta = Vector3.ProjectOnPlane(delta, Vector3.up);
Vector3 correction = planarDelta * Mathf.Clamp01(constraint.PlanarStrength);
float verticalStrength = delta.y >= 0f
? Mathf.Clamp01(constraint.UpwardStrength)
: Mathf.Clamp01(constraint.DownwardStrength);
correction.y = delta.y * verticalStrength;
float maxCorrection = Mathf.Max(0f, constraint.MaxCorrection);
if (maxCorrection > 0f && correction.magnitude > maxCorrection)
{
correction = correction.normalized * maxCorrection;
}
if (correction.sqrMagnitude < 1e-8f)
{
return;
}
body.position += correction;
Vector3 velocityCorrection = correction / Mathf.Max(Time.fixedDeltaTime, 0.0001f);
body.AddForce(velocityCorrection * body.mass, ForceMode.Impulse);
}
private void ApplyDamping()
{
for (int i = 0; i < _constraints.Count; i++)
{
var constraint = _constraints[i];
if (!constraint.bodyA || !constraint.bodyB) continue;
if (constraint.currentDistance <= constraint.maxDistance) continue;
Vector3 relativeVelocity = constraint.bodyB.linearVelocity - constraint.bodyA.linearVelocity;
float velocityInConstraintDir = Vector3.Dot(relativeVelocity, constraint.direction);
if (velocityInConstraintDir > 0)
{
float dampingForce = -velocityInConstraintDir * dampingCoefficient;
Vector3 dampingImpulse = constraint.direction * dampingForce * Time.fixedDeltaTime;
constraint.bodyA.AddForce(-dampingImpulse * constraint.bodyA.mass, ForceMode.Impulse);
constraint.bodyB.AddForce(dampingImpulse * constraint.bodyB.mass, ForceMode.Impulse);
}
}
}
private void UpdateAnchorNode()
{
if (anchorTransform == null || lineNodes.Count < 1)
{
return;
}
var startNode = lineNodes[0].Rigidbody;
startNode.transform.SetPositionAndRotation(anchorTransform.position, anchorTransform.rotation);
if (!startNode.isKinematic)
{
startNode.linearVelocity = Vector3.zero;
startNode.angularVelocity = Vector3.zero;
}
}
#region 动态间距修改接口
public void SetLenght(float length)
{
SetSegmentMaxLength(0, length);
}
///
/// 按速度过渡某段的最大距离
///
public void SetSegmentMaxLength(int segmentIndex, float targetLength, float transitionSpeed = 2f)
{
if (segmentIndex < 0 || segmentIndex >= _constraints.Count) return;
targetLength = Mathf.Max(0.01f, targetLength);
float speed = Mathf.Max(0.01f, transitionSpeed > 0 ? transitionSpeed : defaultTransitionSpeed);
var constraint = _constraints[segmentIndex];
constraint.targetMaxDistance = targetLength;
constraint.maxTransitionSpeed = speed;
constraint.hasPendingMaxTransition = Mathf.Abs(constraint.maxDistance - targetLength) >= 0.0001f;
constraint.hasPendingTransition = constraint.hasPendingMaxTransition || constraint.hasPendingMinTransition;
}
///
/// 按速度过渡某段的最小距离
///
public void SetSegmentMinLength(int segmentIndex, float targetLength, float transitionSpeed = -1)
{
if (segmentIndex < 0 || segmentIndex >= _constraints.Count) return;
targetLength = Mathf.Max(0f, targetLength);
float speed = Mathf.Max(0.01f, transitionSpeed > 0 ? transitionSpeed : defaultTransitionSpeed);
var constraint = _constraints[segmentIndex];
constraint.targetMinDistance = targetLength;
constraint.minTransitionSpeed = speed;
constraint.hasPendingMinTransition = Mathf.Abs(constraint.minDistance - targetLength) >= 0.0001f;
constraint.hasPendingTransition = constraint.hasPendingMaxTransition || constraint.hasPendingMinTransition;
}
///
/// 同时按速度过渡某段的最大和最小距离
///
public void SetSegmentLengths(int segmentIndex, float targetMaxLength, float targetMinLength,
float transitionSpeed = -1)
{
if (segmentIndex < 0 || segmentIndex >= _constraints.Count) return;
targetMaxLength = Mathf.Max(0.01f, targetMaxLength);
targetMinLength = Mathf.Max(0f, targetMinLength);
float speed = Mathf.Max(0.01f, transitionSpeed > 0 ? transitionSpeed : defaultTransitionSpeed);
var constraint = _constraints[segmentIndex];
constraint.targetMaxDistance = targetMaxLength;
constraint.targetMinDistance = targetMinLength;
constraint.maxTransitionSpeed = speed;
constraint.minTransitionSpeed = speed;
constraint.hasPendingMaxTransition = Mathf.Abs(constraint.maxDistance - targetMaxLength) >= 0.0001f;
constraint.hasPendingMinTransition = Mathf.Abs(constraint.minDistance - targetMinLength) >= 0.0001f;
constraint.hasPendingTransition = constraint.hasPendingMaxTransition || constraint.hasPendingMinTransition;
}
///
/// 扩展整个绳索(所有段均匀缩放)
///
public void ExtendAllSegments(float scaleFactor, float transitionSpeed = -1)
{
float speed = transitionSpeed > 0 ? transitionSpeed : defaultTransitionSpeed;
for (int i = 0; i < _constraints.Count; i++)
{
var constraint = _constraints[i];
float newMaxLength = constraint.maxDistance * scaleFactor;
float newMinLength = constraint.minDistance * scaleFactor;
SetSegmentLengths(i, newMaxLength, newMinLength, speed);
}
}
///
/// 收缩/拉紧绳索(所有段向目标长度过渡)
///
public void TightenAllSegments(float targetLength, float transitionSpeed = -1)
{
float speed = transitionSpeed > 0 ? transitionSpeed : defaultTransitionSpeed;
for (int i = 0; i < _constraints.Count; i++)
{
var constraint = _constraints[i];
SetSegmentLengths(i, targetLength, Mathf.Min(constraint.minDistance, targetLength), speed);
}
}
///
/// 对特定段施加拉力(减小最大距离)
///
public void PullSegment(int segmentIndex, float amount, float transitionSpeed = -1)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
var constraint = _constraints[segmentIndex];
float newMaxLength = Mathf.Max(0.01f, constraint.targetMaxDistance - amount);
SetSegmentMaxLength(segmentIndex, newMaxLength, transitionSpeed);
}
}
///
/// 放松特定段(增加最大距离)
///
public void RelaxSegment(int segmentIndex, float amount, float transitionSpeed = -1)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
var constraint = _constraints[segmentIndex];
float newMaxLength = constraint.targetMaxDistance + amount;
SetSegmentMaxLength(segmentIndex, newMaxLength, transitionSpeed);
}
}
///
/// 移除指定段的过渡
///
private void RemoveExistingTransition(int segmentIndex, bool removeMax, bool removeMin)
{
if (segmentIndex < 0 || segmentIndex >= _constraints.Count)
{
return;
}
var constraint = _constraints[segmentIndex];
if (removeMax)
{
constraint.hasPendingMaxTransition = false;
constraint.targetMaxDistance = constraint.maxDistance;
}
if (removeMin)
{
constraint.hasPendingMinTransition = false;
constraint.targetMinDistance = constraint.minDistance;
}
constraint.hasPendingTransition = constraint.hasPendingMaxTransition || constraint.hasPendingMinTransition;
}
///
/// 取消所有正在进行的过渡
///
public void CancelAllTransitions()
{
foreach (var constraint in _constraints)
{
constraint.hasPendingTransition = false;
constraint.hasPendingMaxTransition = false;
constraint.hasPendingMinTransition = false;
constraint.targetMaxDistance = constraint.maxDistance;
constraint.targetMinDistance = constraint.minDistance;
}
}
///
/// 取消指定段的过渡
///
public void CancelTransition(int segmentIndex)
{
RemoveExistingTransition(segmentIndex, true, true);
if (segmentIndex < _constraints.Count)
{
_constraints[segmentIndex].hasPendingTransition = false;
}
}
///
/// 获取某个段是否正在进行过渡
///
public bool IsSegmentTransitioning(int segmentIndex)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
return _constraints[segmentIndex].hasPendingTransition;
}
return false;
}
///
/// 获取某段当前的目标最大距离
///
public float GetTargetMaxLength(int segmentIndex)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
return _constraints[segmentIndex].targetMaxDistance;
}
return -1f;
}
///
/// 获取某段当前的目标最小距离
///
public float GetTargetMinLength(int segmentIndex)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
return _constraints[segmentIndex].targetMinDistance;
}
return -1f;
}
///
/// 设置默认长度变化速度
///
public void SetDefaultTransitionSpeed(float speed)
{
defaultTransitionSpeed = Mathf.Max(0.01f, speed);
}
#endregion
#region 极限判定
///
/// 当前逻辑链总长度超出配置总长度的部分,小于等于零时记为 0。
///
[Header("Limit Detection")]
public float CurrentStretchLength { get; private set; }
///
/// 总长度
///
public float TotalLength { get; private set; }
[Min(0f)]
// 极限判定的长度容差,允许链路在总长或单段长度上存在少量误差。
[SerializeField]
private float lengthLimitTolerance = 0.01f;
[Min(0f)]
// 达到极限后,只有当前超长值大于该阈值时,才开始进入断线候选计时。
[SerializeField]
private float breakStretchThreshold = 0.3f;
[Min(0f)]
// UI 百分比开始起算的最小超长值;低于或等于该值时统一按 0% 处理。
[SerializeField]
private float breakStretchPercentMinThreshold = 0.06f;
[Min(0f)]
// 断线候选状态允许持续的最大时间;超过后会发出一次断线消息。
[SerializeField]
private float breakLimitDuration = 3f;
///
/// 当鱼线达到断线条件时发出的一次性消息。
/// 外部可订阅该事件,在回调中执行切线、播放表现或状态切换。
///
public event Action OnLineBreakRequested;
///
/// 当前是否处于极限状态。
/// 只要整链超出总长度容差,或任一逻辑段超出单段容差,即认为到达极限。
///
public bool IsAtLimit { get; private set; }
///
/// 当前断线候选状态的累计时间。
/// 只有在处于极限状态,且 CurrentStretchLength 大于断线阈值时才会累加;否则重置为 0。
///
public float LimitStateTime { get; private set; }
///
/// 当前极限断线消息是否已经发出过。
/// 在退出断线候选状态前只会发一次,避免重复通知。
///
public bool HasBreakNotificationSent { get; private set; }
///
/// 当前拉力极限百分比。
/// 当超长值小于等于 breakStretchPercentMinThreshold 时为 0;
/// 当超长值大于等于 breakStretchThreshold 时为 100;
/// 中间区间按线性比例映射,供 UI 显示使用。
///
public float CurrentBreakStretchPercent => EvaluateBreakStretchPercent(CurrentStretchLength);
///
/// 当前是否正在进行断线候选计时。
///
public bool IsBreakCountdownActive => IsAtLimit && CurrentStretchLength > breakStretchThreshold;
private float EvaluateBreakStretchPercent(float stretchLength)
{
var percentMinThreshold = Mathf.Max(lengthLimitTolerance, breakStretchPercentMinThreshold);
if (stretchLength <= percentMinThreshold)
{
return 0f;
}
if (stretchLength >= breakStretchThreshold)
{
return 100f;
}
if (breakStretchThreshold <= percentMinThreshold)
{
return 100f;
}
return Mathf.InverseLerp(percentMinThreshold, breakStretchThreshold, stretchLength) * 100f;
}
private void SetLimitState(bool isAtLimit)
{
IsAtLimit = isAtLimit;
}
private void UpdateBreakCountdown(float deltaTime)
{
if (lineNodes.Count < 2)
{
SetLimitState(false);
ResetLimitState();
return;
}
var startNode = lineNodes[0];
var endNode = lineNodes[^1];
TotalLength = 0;
foreach (var node in lineNodes)
{
if (node.NodeType == FLineLogicNodeType.End) continue;
TotalLength += node.NextSegmentMaxLength;
}
var realLen = Vector3.Distance(startNode.transform.position, endNode.transform.position);
CurrentStretchLength = realLen - TotalLength;
if (CurrentStretchLength < 0f)
{
CurrentStretchLength = 0f;
}
SetLimitState(CurrentStretchLength > lengthLimitTolerance);
if (CurrentStretchLength > 1)
{
Log.Error($"水电费 realLen={realLen} TotalLength={TotalLength}");
}
if (!IsBreakCountdownActive)
{
LimitStateTime = 0f;
HasBreakNotificationSent = false;
return;
}
LimitStateTime += Mathf.Max(0f, deltaTime);
if (HasBreakNotificationSent || LimitStateTime < breakLimitDuration)
{
return;
}
HasBreakNotificationSent = true;
NotifyLineBreakRequested();
}
///
/// 发出鱼线达到断线条件的消息。
/// 这里预留给外部订阅,当前不在求解器内部直接执行断线逻辑。
///
private void NotifyLineBreakRequested()
{
OnLineBreakRequested?.Invoke(this);
}
private void ResetLimitState()
{
CurrentStretchLength = 0f;
IsAtLimit = false;
LimitStateTime = 0f;
HasBreakNotificationSent = false;
}
#endregion
#region 公共接口
///
/// 获取一个节点
///
///
///
public FLineLogicNode GetNode(FLineLogicNodeType type)
{
foreach (var node in lineNodes)
{
if (node.NodeType == type)
{
return node;
}
}
return null;
}
public float GetCurrentSegmentLength(int segmentIndex)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
_constraints[segmentIndex].UpdateCurrentState();
return _constraints[segmentIndex].currentDistance;
}
return -1f;
}
public void AddConnectedBody(Rigidbody rb, float maxLength = 1f, float minLength = 0f)
{
if (rb == null)
{
return;
}
FLineLogicNode node = rb.GetComponent();
if (!node)
{
Debug.LogWarning("AddConnectedBody需要目标刚体上挂有FLineLogicNode");
return;
}
if (!lineNodes.Contains(node))
{
if (lineNodes.Count > 0 && lineNodes[lineNodes.Count - 1])
{
lineNodes[lineNodes.Count - 1].SetSegmentLengths(maxLength, minLength);
}
node.AttachToCable(this);
lineNodes.Add(node);
BuildConstraints();
enabled = _constraints.Count > 0;
}
}
public void RemoveConnectedBody(Rigidbody rb)
{
if (rb == null)
{
return;
}
for (int i = 0; i < lineNodes.Count; i++)
{
if (GetBodyAt(i) == rb)
{
lineNodes.RemoveAt(i);
BuildConstraints();
enabled = _constraints.Count > 0;
break;
}
}
}
public List GetConnectedBodies()
{
List bodies = new List(lineNodes.Count);
for (int i = 0; i < lineNodes.Count; i++)
{
bodies.Add(GetBodyAt(i));
}
return bodies;
}
public List GetLineNodes()
{
return new List(lineNodes);
}
public bool TryGetAdjacentBodies(FLineLogicNode node, out Rigidbody previousBody, out Rigidbody nextBody)
{
previousBody = null;
nextBody = null;
if (!node)
{
return false;
}
int index = lineNodes.IndexOf(node);
if (index < 0)
{
return false;
}
if (index > 0 && lineNodes[index - 1])
{
previousBody = lineNodes[index - 1].Rigidbody;
}
if (index < lineNodes.Count - 1 && lineNodes[index + 1])
{
nextBody = lineNodes[index + 1].Rigidbody;
}
return previousBody || nextBody;
}
public void ApplyForceAtBody(Rigidbody targetBody, Vector3 force, ForceMode mode = ForceMode.Force)
{
if (targetBody && ContainsBody(targetBody))
{
targetBody.AddForce(force, mode);
}
}
public float GetSegmentMaxLength(int segmentIndex)
{
FLineLogicNode node = GetSegmentNode(segmentIndex);
if (node)
return node.NextSegmentMaxLength;
return -1f;
}
#endregion
#region 可视化调试
void OnDrawGizmos()
{
if (!showDebugInfo || lineNodes.Count < 2) return;
for (int i = 0; i < lineNodes.Count - 1; i++)
{
Rigidbody bodyA = GetBodyAt(i);
Rigidbody bodyB = GetBodyAt(i + 1);
FLineLogicNode segmentNode = GetSegmentNode(i);
if (bodyA != null && bodyB != null)
{
float currentDist = Vector3.Distance(bodyA.position, bodyB.position);
float maxDist = segmentNode ? segmentNode.NextSegmentMaxLength : 1f;
float minDist = segmentNode ? segmentNode.NextSegmentMinLength : 0f;
// 颜色:如果正在过渡用橙色,绿色=正常范围,红色=超出最大距离,蓝色=小于最小距离
if (IsSegmentTransitioning(i))
Gizmos.color = new Color(1f, 0.5f, 0f); // 橙色表示正在过渡
else if (currentDist > maxDist)
Gizmos.color = Color.red;
else if (currentDist < minDist && minDist > 0)
Gizmos.color = Color.blue;
else
Gizmos.color = Color.green;
Gizmos.DrawLine(bodyA.position, bodyB.position);
Vector3 midPoint = (bodyA.position + bodyB.position) * 0.5f;
Gizmos.DrawWireSphere(midPoint, 0.1f);
#if UNITY_EDITOR
string info = $"距离:{currentDist:F2}";
if (IsSegmentTransitioning(i))
info += $"\n过渡中→{GetTargetMaxLength(i):F2}";
else
info += $" [最大:{maxDist:F2}";
info += $"]";
UnityEditor.Handles.Label(midPoint, info);
#endif
}
}
Gizmos.color = Color.red;
for (int i = 0; i < lineNodes.Count; i++)
{
Rigidbody body = GetBodyAt(i);
if (body != null)
{
Gizmos.DrawSphere(body.position, 0.05f);
}
}
}
#endregion
}
}