Files
Fishing2/Assets/Scripts/Fishing/New/View/FishingLine/FishingLineNode.cs
2026-04-12 18:37:24 +08:00

305 lines
8.5 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
namespace NBF
{
public class FishingLineNode : MonoBehaviour
{
public enum NodeType
{
Start,
Float,
Weight,
Tail,
Virtual,
}
private FishingLineSolver _solver;
[Header("Node")] [SerializeField] private NodeType nodeType = NodeType.Tail;
[SerializeField] private Rigidbody body;
[SerializeField] private MonoBehaviour interaction;
[Header("Segment To Next Logical Node")] [Min(0f)] [SerializeField]
private float segmentLengthToNext = 0.5f;
[Min(0)] [SerializeField] private int fixedVirtualNodesToNext = 2;
[Header("Runtime")] [SerializeField] private bool runtimeVirtualNode;
[SerializeField] private int runtimeChainIndex = -1;
[Header("Debug")] [SerializeField] private bool drawDebugGizmo = true;
[Min(0.001f)] [SerializeField] private float debugGizmoRadius = 0.03f;
[SerializeField] private Color logicalNodeColor = new(0.2f, 0.9f, 0.2f, 1f);
[SerializeField] private Color virtualNodeColor = new(1f, 0.6f, 0.1f, 1f);
[SerializeField] private List<FishingLineNodeFeature> features = new();
[SerializeField] private List<FishingLineNodeMotionFeature> motionFeatures = new();
private bool featureCacheReady;
[SerializeField] private FishingLineNodeMotionFeature activeMotionFeature;
/// <summary>
/// 当前正在接管节点运动的组件。
/// </summary>
public FishingLineNodeMotionFeature ActiveMotionFeature => activeMotionFeature;
public NodeType Type
{
get => nodeType;
set => nodeType = value;
}
public Rigidbody Body => body;
public MonoBehaviour Interaction => interaction;
public float SegmentLengthToNext
{
get => segmentLengthToNext;
set => segmentLengthToNext = Mathf.Max(0f, value);
}
public int FixedVirtualNodesToNext
{
get => fixedVirtualNodesToNext;
set => fixedVirtualNodesToNext = Mathf.Max(0, value);
}
public bool IsRuntimeVirtualNode => runtimeVirtualNode;
public int RuntimeChainIndex => runtimeChainIndex;
public Vector3 Position => transform.position;
public string GetDebugName()
{
return runtimeVirtualNode
? $"V[{runtimeChainIndex}]"
: $"{nodeType}[{runtimeChainIndex}]";
}
public string GetDebugSummary()
{
var bodySummary = body == null
? "NoBody"
: $"v:{body.linearVelocity.magnitude:F2}";
return $"{GetDebugName()} pos:{Position} next:{segmentLengthToNext:F2} {bodySummary}";
}
private void Reset()
{
TryGetComponent(out body);
}
private void Awake()
{
_solver = GetComponentInParent<FishingLineSolver>();
EnsureFeatureCache();
}
private void Start()
{
BindFeatures(_solver);
}
private void FixedUpdate()
{
EnsureFeatureCache();
UpdateMotionControl(Time.fixedDeltaTime);
}
private void OnValidate()
{
if (body == null)
{
TryGetComponent(out body);
}
segmentLengthToNext = Mathf.Max(0f, segmentLengthToNext);
fixedVirtualNodesToNext = Mathf.Max(0, fixedVirtualNodesToNext);
}
public void SetRuntimeVirtual(bool isVirtual, int chainIndex)
{
runtimeVirtualNode = isVirtual;
runtimeChainIndex = chainIndex;
if (isVirtual)
{
nodeType = NodeType.Virtual;
}
}
public void SetVisualPosition(Vector3 position)
{
transform.position = position;
}
#region Feature
/// <summary>
/// 获取节点上的第一个指定类型功能组件。
/// </summary>
public T GetFeature<T>() where T : FishingLineNodeFeature
{
EnsureFeatureCache();
for (var i = 0; i < features.Count; i++)
{
if (features[i] is T result)
{
return result;
}
}
return null;
}
/// <summary>
/// 尝试获取节点上的指定类型功能组件。
/// </summary>
public bool TryGetFeature<T>(out T feature) where T : FishingLineNodeFeature
{
feature = GetFeature<T>();
return feature != null;
}
/// <summary>
/// 刷新并重新绑定当前节点上的功能组件。
/// </summary>
public void BindFeatures(FishingLineSolver solver)
{
EnsureFeatureCache();
foreach (var t in features)
{
t.Bind(this, solver);
}
ResolveMotionFeature(forceRefresh: true);
}
/// <summary>
/// 通知当前节点上的所有功能组件,鱼线已重建完成。
/// </summary>
public void NotifyLineBuilt()
{
EnsureFeatureCache();
foreach (var t in features)
{
t.OnLineBuilt();
}
ResolveMotionFeature(forceRefresh: true);
}
/// <summary>
/// 通知当前节点上的所有功能组件,鱼线已经达到断线条件。
/// </summary>
public void NotifyLineBreakRequested()
{
EnsureFeatureCache();
foreach (var t in features)
{
t.OnLineBreakRequested();
}
}
private void EnsureFeatureCache()
{
if (!featureCacheReady)
{
RefreshFeatures();
}
}
private void RefreshFeatures()
{
features.Clear();
motionFeatures.Clear();
GetComponents(features);
for (var i = 0; i < features.Count; i++)
{
if (features[i] is FishingLineNodeMotionFeature motionFeature)
{
motionFeatures.Add(motionFeature);
}
}
activeMotionFeature = null;
featureCacheReady = true;
}
private void UpdateMotionControl(float deltaTime)
{
var motionFeature = ResolveMotionFeature(forceRefresh: false);
if (motionFeature == null)
{
return;
}
motionFeature.TickMotion(deltaTime);
}
private FishingLineNodeMotionFeature ResolveMotionFeature(bool forceRefresh)
{
EnsureFeatureCache();
var bestMotionFeature = default(FishingLineNodeMotionFeature);
var bestPriority = int.MinValue;
foreach (var motionFeature in motionFeatures)
{
var r = !motionFeature.IsSupportedNode(this);
var n = !motionFeature.CanControl();
if (motionFeature == null || !motionFeature.IsSupportedNode(this) || !motionFeature.CanControl())
{
continue;
}
if (bestMotionFeature != null && motionFeature.Priority <= bestPriority)
{
continue;
}
bestMotionFeature = motionFeature;
bestPriority = motionFeature.Priority;
}
if (!forceRefresh && ReferenceEquals(activeMotionFeature, bestMotionFeature))
{
return activeMotionFeature;
}
if (activeMotionFeature != null && !ReferenceEquals(activeMotionFeature, bestMotionFeature))
{
activeMotionFeature.OnMotionDeactivated();
}
activeMotionFeature = bestMotionFeature;
if (activeMotionFeature != null)
{
activeMotionFeature.OnMotionActivated();
}
return activeMotionFeature;
}
#endregion
private void OnDrawGizmos()
{
if (!drawDebugGizmo)
{
return;
}
Gizmos.color = runtimeVirtualNode ? virtualNodeColor : logicalNodeColor;
Gizmos.DrawSphere(transform.position, debugGizmoRadius);
}
}
}