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 features = new(); [SerializeField] private List motionFeatures = new(); private bool featureCacheReady; [SerializeField] private FishingLineNodeMotionFeature activeMotionFeature; /// /// 当前正在接管节点运动的组件。 /// 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(); 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 /// /// 获取节点上的第一个指定类型功能组件。 /// public T GetFeature() where T : FishingLineNodeFeature { EnsureFeatureCache(); for (var i = 0; i < features.Count; i++) { if (features[i] is T result) { return result; } } return null; } /// /// 尝试获取节点上的指定类型功能组件。 /// public bool TryGetFeature(out T feature) where T : FishingLineNodeFeature { feature = GetFeature(); return feature != null; } /// /// 刷新并重新绑定当前节点上的功能组件。 /// public void BindFeatures(FishingLineSolver solver) { EnsureFeatureCache(); foreach (var t in features) { t.Bind(this, solver); } ResolveMotionFeature(forceRefresh: true); } /// /// 通知当前节点上的所有功能组件,鱼线已重建完成。 /// public void NotifyLineBuilt() { EnsureFeatureCache(); foreach (var t in features) { t.OnLineBuilt(); } ResolveMotionFeature(forceRefresh: true); } /// /// 通知当前节点上的所有功能组件,鱼线已经达到断线条件。 /// 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); } } }