Files
Fishing2/Assets/Scripts/Fishing/New/View/Player/Tackle/FLine.cs
Bob.Song d432c468b1 接入新逻辑
# Conflicts:
#	Assets/Scenes/RopeTest.unity
#	Assets/Scripts/Fishing/New/View/Player/Tackle/FLine.cs
#	Assets/Scripts/Fishing/Rope/Rope.cs
#	Assets/Scripts/Fishing/Rope/Rope.cs.meta
2026-04-26 14:44:06 +08:00

1044 lines
36 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
/// <summary>
/// 为 FLine 节点提供附加软约束。求解器只依赖这个通用接口,不依赖具体玩法组件。
/// </summary>
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<FLineLogicNode> lineNodes = new List<FLineLogicNode>();
[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<ConnectionConstraint> _constraints = new List<ConnectionConstraint>();
private readonly List<IFLineNodeConstraintProvider[]> _nodeConstraintProviders =
new List<IFLineNodeConstraintProvider[]>();
[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()
{
}
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<IFLineNodeConstraintProvider>());
}
}
// 创建约束
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<IFLineNodeConstraintProvider>();
}
return _nodeConstraintProviders[nodeIndex];
}
private static IFLineNodeConstraintProvider[] CollectNodeConstraintProviders(FLineLogicNode node)
{
MonoBehaviour[] behaviours = node.GetComponents<MonoBehaviour>();
List<IFLineNodeConstraintProvider> providers = new List<IFLineNodeConstraintProvider>();
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;
}
/// <summary>
/// 更新所有活跃的过渡
/// </summary>
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);
}
/// <summary>
/// 按速度过渡某段的最大距离
/// </summary>
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;
}
/// <summary>
/// 按速度过渡某段的最小距离
/// </summary>
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;
}
/// <summary>
/// 同时按速度过渡某段的最大和最小距离
/// </summary>
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;
}
/// <summary>
/// 扩展整个绳索(所有段均匀缩放)
/// </summary>
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);
}
}
/// <summary>
/// 收缩/拉紧绳索(所有段向目标长度过渡)
/// </summary>
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);
}
}
/// <summary>
/// 对特定段施加拉力(减小最大距离)
/// </summary>
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);
}
}
/// <summary>
/// 放松特定段(增加最大距离)
/// </summary>
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);
}
}
/// <summary>
/// 移除指定段的过渡
/// </summary>
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;
}
/// <summary>
/// 取消所有正在进行的过渡
/// </summary>
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;
}
}
/// <summary>
/// 取消指定段的过渡
/// </summary>
public void CancelTransition(int segmentIndex)
{
RemoveExistingTransition(segmentIndex, true, true);
if (segmentIndex < _constraints.Count)
{
_constraints[segmentIndex].hasPendingTransition = false;
}
}
/// <summary>
/// 获取某个段是否正在进行过渡
/// </summary>
public bool IsSegmentTransitioning(int segmentIndex)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
return _constraints[segmentIndex].hasPendingTransition;
}
return false;
}
/// <summary>
/// 获取某段当前的目标最大距离
/// </summary>
public float GetTargetMaxLength(int segmentIndex)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
return _constraints[segmentIndex].targetMaxDistance;
}
return -1f;
}
/// <summary>
/// 获取某段当前的目标最小距离
/// </summary>
public float GetTargetMinLength(int segmentIndex)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
return _constraints[segmentIndex].targetMinDistance;
}
return -1f;
}
/// <summary>
/// 设置默认长度变化速度
/// </summary>
public void SetDefaultTransitionSpeed(float speed)
{
defaultTransitionSpeed = Mathf.Max(0.01f, speed);
}
#endregion
#region
/// <summary>
/// 当前逻辑链总长度超出配置总长度的部分,小于等于零时记为 0。
/// </summary>
[Header("Limit Detection")]
public float CurrentStretchLength { get; private set; }
/// <summary>
/// 总长度
/// </summary>
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;
/// <summary>
/// 当鱼线达到断线条件时发出的一次性消息。
/// 外部可订阅该事件,在回调中执行切线、播放表现或状态切换。
/// </summary>
public event Action<FLine> OnLineBreakRequested;
/// <summary>
/// 当前是否处于极限状态。
/// 只要整链超出总长度容差,或任一逻辑段超出单段容差,即认为到达极限。
/// </summary>
public bool IsAtLimit { get; private set; }
/// <summary>
/// 当前断线候选状态的累计时间。
/// 只有在处于极限状态,且 CurrentStretchLength 大于断线阈值时才会累加;否则重置为 0。
/// </summary>
public float LimitStateTime { get; private set; }
/// <summary>
/// 当前极限断线消息是否已经发出过。
/// 在退出断线候选状态前只会发一次,避免重复通知。
/// </summary>
public bool HasBreakNotificationSent { get; private set; }
/// <summary>
/// 当前拉力极限百分比。
/// 当超长值小于等于 breakStretchPercentMinThreshold 时为 0
/// 当超长值大于等于 breakStretchThreshold 时为 100
/// 中间区间按线性比例映射,供 UI 显示使用。
/// </summary>
public float CurrentBreakStretchPercent => EvaluateBreakStretchPercent(CurrentStretchLength);
/// <summary>
/// 当前是否正在进行断线候选计时。
/// </summary>
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();
}
/// <summary>
/// 发出鱼线达到断线条件的消息。
/// 这里预留给外部订阅,当前不在求解器内部直接执行断线逻辑。
/// </summary>
private void NotifyLineBreakRequested()
{
OnLineBreakRequested?.Invoke(this);
}
private void ResetLimitState()
{
CurrentStretchLength = 0f;
IsAtLimit = false;
LimitStateTime = 0f;
HasBreakNotificationSent = false;
}
#endregion
#region
/// <summary>
/// 获取一个节点
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
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<FLineLogicNode>();
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<Rigidbody> GetConnectedBodies()
{
List<Rigidbody> bodies = new List<Rigidbody>(lineNodes.Count);
for (int i = 0; i < lineNodes.Count; i++)
{
bodies.Add(GetBodyAt(i));
}
return bodies;
}
public List<FLineLogicNode> GetLineNodes()
{
return new List<FLineLogicNode>(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
}
}