305 lines
8.5 KiB
C#
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] public 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);
|
|
}
|
|
}
|
|
} |