Files
Fishing2/Assets/Scripts/Fishing~/New/View/Player/FishingLine/FLine.cs
2026-04-29 12:42:00 +08:00

586 lines
20 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 System.Collections;
using System.Collections.Generic;
using System.Linq;
using NBC;
// using Obi;
using UnityEngine;
namespace NBF
{
public enum LineType
{
Hand,
HandDouble,
Spinning,
SpinningFloat,
}
/// <summary>
/// 线模式
/// </summary>
public enum LineMode
{
Joint,
Constraint
}
public class FLine : FGearBase
{
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;
[SerializeField] private bool showDebugInfo = true;
[Header("动态间距设置")] [SerializeField] private float defaultTransitionSpeed = 2f; // 默认长度变化速度(单位/秒)
private LineMode _lineMode = LineMode.Joint;
// [SerializeField] private bool isLureConnect;
//
// [SerializeField] private RodLine rodLine;
//
// /// <summary>
// /// 主线
// /// </summary>
// [SerializeField] private Rope fishingRope;
//
// /// <summary>
// /// 浮漂和鱼钩线
// /// </summary>
// [SerializeField] private Rope bobberRope;
// public LureController Lure;
// public BobberController Bobber;
//
// public JointPinchController PinchController;
private readonly List<ConnectionConstraint> _constraints = new List<ConnectionConstraint>();
public FLineLogicNode StartNode { get; private set; }
public FLineLogicNode BobberNode => GetNode(FLineLogicNodeType.Bobber);
public FLineLogicNode EndNode { get; private set; }
public LineMode LineMode => _lineMode;
public float LinelenghtDiferent;
[System.Serializable]
public class ConnectionConstraint
{
public FLineLogicNodeType NodeType;
public Rigidbody bodyA;
public Rigidbody bodyB;
public float maxDistance;
public float minDistance;
public float currentDistance;
public Vector3 direction;
// 动态目标距离(用于平滑过渡)
public float targetMaxDistance;
public bool hasPendingTransition;
public bool hasPendingMaxTransition;
public float maxTransitionSpeed;
public ConnectionConstraint(Rigidbody a, Rigidbody b, float maxDist, float minDist = 0f)
{
bodyA = a;
bodyB = b;
maxDistance = maxDist;
minDistance = minDist;
targetMaxDistance = maxDist;
hasPendingTransition = false;
hasPendingMaxTransition = false;
maxTransitionSpeed = 0f;
}
public void UpdateCurrentState()
{
if (bodyA && bodyB)
{
Vector3 delta = bodyB.position - bodyA.position;
currentDistance = delta.magnitude;
direction = currentDistance > 0.0001f ? delta.normalized : Vector3.right;
}
}
}
protected override void OnInit()
{
if (anchorTransform == null)
{
var tipRb = Rod.Asset.LineConnectorRigidbody;
anchorTransform = tipRb.transform;
}
}
private void Start()
{
BuildConstraints();
StartNode = GetNode(FLineLogicNodeType.Start);
EndNode = GetNode(FLineLogicNodeType.End);
}
private void Update()
{
LinelenghtDiferent = GetLineDistance();
//非钓鱼状态
if (Rod) Rod.PlayerItem.Tension = Mathf.Clamp(LinelenghtDiferent, 0f, 0.05f);
}
private void FixedUpdate()
{
UpdateAnchorNode();
FixedUpdateConstraints();
}
public void ChangeMode(LineMode mode)
{
_lineMode = mode;
foreach (var fLineLogicNode in lineNodes)
{
fLineLogicNode.ChangeMode(mode);
}
}
public List<FLineLogicNode> GetLineNodes()
{
return new List<FLineLogicNode>(lineNodes);
}
public void Print()
{
// Log.Info($"当前线情况 TotalLength={TotalLength} CurrentStretchLength={CurrentStretchLength}");
}
#region
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;
}
}
/// <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 void SetLenght(float lenght, FLineLogicNodeType type = FLineLogicNodeType.Bobber)
{
var node = GetNode(type);
if (node != null)
{
if (_lineMode == LineMode.Joint)
{
node.SetLenght(lenght);
}
else
{
SetSegmentMaxLength(lenght, type);
}
}
}
#endregion
#region
private void FixedUpdateConstraints()
{
if (_lineMode != LineMode.Constraint) return;
if (!enabled || lineNodes.Count < 2) return;
// 更新动态过渡
UpdateTransitions();
for (int iteration = 0; iteration < constraintIterations; iteration++)
{
ApplyDistanceConstraints();
}
ApplyDamping();
}
private void BuildConstraints()
{
_constraints.Clear();
if (lineNodes.Count < 2) return;
// 创建约束
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,
nextNode.Lenght
);
constraint.NodeType = nextNode.NodeType;
_constraints.Add(constraint);
}
}
}
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 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);
}
}
}
/// <summary>
/// 按速度过渡某段的最大距离
/// </summary>
private void SetSegmentMaxLength(float targetLength, FLineLogicNodeType type = FLineLogicNodeType.Bobber,
float transitionSpeed = 2f)
{
var constraint = _constraints.Find(t => t.NodeType == type);
if (constraint == null) return;
targetLength = Mathf.Max(0.01f, targetLength);
float speed = Mathf.Max(0.01f, transitionSpeed > 0 ? transitionSpeed : defaultTransitionSpeed);
constraint.targetMaxDistance = targetLength;
constraint.maxTransitionSpeed = speed;
constraint.hasPendingMaxTransition = Mathf.Abs(constraint.maxDistance - targetLength) >= 0.0001f;
constraint.hasPendingTransition = constraint.hasPendingMaxTransition;
}
/// <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;
var node = GetNode(constraint.NodeType);
// SyncSegmentMaxLength(i, nextMaxDistance);
node.SetLenght(nextMaxDistance);
if (Mathf.Abs(nextMaxDistance - constraint.targetMaxDistance) < 0.0001f)
{
constraint.maxDistance = constraint.targetMaxDistance;
constraint.hasPendingMaxTransition = false;
// SyncSegmentMaxLength(i, constraint.targetMaxDistance);
node.SetLenght(constraint.targetMaxDistance);
}
}
constraint.hasPendingTransition = constraint.hasPendingMaxTransition;
}
}
/// <summary>
/// 获取某个段是否正在进行过渡
/// </summary>
public bool IsSegmentTransitioning(int segmentIndex)
{
if (segmentIndex >= 0 && segmentIndex < _constraints.Count)
{
return _constraints[segmentIndex].hasPendingTransition;
}
return false;
}
#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)
// {
// TotalLength += node.Lenght;
// }
//
// var realLen = Vector3.Distance(startNode.transform.position, endNode.transform.position);
// CurrentStretchLength = realLen - TotalLength;
// if (CurrentStretchLength < 0f)
// {
// CurrentStretchLength = 0f;
// }
//
// SetLimitState(CurrentStretchLength > lengthLimitTolerance);
// if (LineMode != LineMode.Constraint) return;
//
// 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 Tension
private float GetLineDistance()
{
//第一个节点到竿稍的位置-第一段鱼线长度
return Vector3.Distance(StartNode.transform.position, BobberNode.transform.position) -
BobberNode.Lenght;
}
public float GetTension(float weight)
{
return weight * GetLineDistance();
}
#endregion
}
}