接入新逻辑
# 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
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,29 +0,0 @@
|
|||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
[CustomEditor(typeof(Rope))]
|
|
||||||
public class RopeFishLineEditor : Editor
|
|
||||||
{
|
|
||||||
private Rope _target;
|
|
||||||
|
|
||||||
void OnEnable()
|
|
||||||
{
|
|
||||||
_target = target as Rope;
|
|
||||||
// lookAtPoint = serializedObject.FindProperty("lookAtPoint");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
|
||||||
{
|
|
||||||
base.OnInspectorGUI();
|
|
||||||
|
|
||||||
|
|
||||||
if (GUILayout.Button("打印总长度"))
|
|
||||||
{
|
|
||||||
_target.DebugLength();
|
|
||||||
// Debug.Log($"总长度={_target.GetCurrentLength()} 目标长度={_target.GetTargetLength()} smoot={_target.GetLengthSmoothVel()} relLen={_target.GetLengthByPoints()} PolylineLength={_target.GetPhysicsPolylineLength()}");
|
|
||||||
}
|
|
||||||
// serializedObject.Update();
|
|
||||||
// EditorGUILayout.PropertyField(lookAtPoint);
|
|
||||||
// serializedObject.ApplyModifiedProperties();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: f24add4ba0ae4e76acd98c4a5512c366
|
|
||||||
timeCreated: 1771850618
|
|
||||||
138
Assets/Scripts/Fishing/New/View/FishingLine/FLineLogicNode.cs
Normal file
138
Assets/Scripts/Fishing/New/View/FishingLine/FLineLogicNode.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NBF
|
||||||
|
{
|
||||||
|
public enum FLineLogicNodeType
|
||||||
|
{
|
||||||
|
Start,
|
||||||
|
Bobber,
|
||||||
|
End
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 硬线节点组件 - 可选,用于更方便的管理
|
||||||
|
/// </summary>
|
||||||
|
[RequireComponent(typeof(Rigidbody))]
|
||||||
|
public class FLineLogicNode : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("节点设置")] public FLineLogicNodeType NodeType = FLineLogicNodeType.Bobber;
|
||||||
|
[SerializeField] private bool isKinematic = false;
|
||||||
|
[SerializeField] private float mass = 1f;
|
||||||
|
|
||||||
|
[Header("到下一个节点的段配置")] [SerializeField, Min(0.01f)]
|
||||||
|
private float nextSegmentMaxLength = 1f;
|
||||||
|
|
||||||
|
[SerializeField, Min(0f)] private float nextSegmentMinLength = 0f;
|
||||||
|
|
||||||
|
private Rigidbody rb;
|
||||||
|
private FLine parentCable;
|
||||||
|
|
||||||
|
public Rigidbody Rigidbody
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!rb)
|
||||||
|
{
|
||||||
|
rb = GetComponent<Rigidbody>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return rb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float NextSegmentMaxLength
|
||||||
|
{
|
||||||
|
get => nextSegmentMaxLength;
|
||||||
|
set => nextSegmentMaxLength = Mathf.Max(0.01f, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float NextSegmentMinLength
|
||||||
|
{
|
||||||
|
get => nextSegmentMinLength;
|
||||||
|
set => nextSegmentMinLength = Mathf.Max(0f, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
rb = Rigidbody;
|
||||||
|
InitializeNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnValidate()
|
||||||
|
{
|
||||||
|
mass = Mathf.Max(0.0001f, mass);
|
||||||
|
nextSegmentMaxLength = Mathf.Max(0.01f, nextSegmentMaxLength);
|
||||||
|
nextSegmentMinLength = Mathf.Max(0f, nextSegmentMinLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeNode()
|
||||||
|
{
|
||||||
|
if (Rigidbody)
|
||||||
|
{
|
||||||
|
Rigidbody.isKinematic = isKinematic;
|
||||||
|
Rigidbody.mass = Mathf.Max(0.0001f, mass);
|
||||||
|
Rigidbody.useGravity = !isKinematic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttachToCable(FLine cable)
|
||||||
|
{
|
||||||
|
parentCable = cable;
|
||||||
|
|
||||||
|
// // 计算到前一个节点的距离
|
||||||
|
// var bodies = parentCable.GetConnectedBodies();
|
||||||
|
// int myIndex = bodies.IndexOf(rb);
|
||||||
|
//
|
||||||
|
// if (myIndex > 0 && bodies[myIndex - 1] != null)
|
||||||
|
// {
|
||||||
|
// float distanceToPrevious = Vector3.Distance(
|
||||||
|
// transform.position,
|
||||||
|
// bodies[myIndex - 1].position
|
||||||
|
// );
|
||||||
|
// parentCable.SetSegmentLength(myIndex - 1, distanceToPrevious);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 固定/释放此节点
|
||||||
|
/// </summary>
|
||||||
|
public void SetFixed(bool fixed_)
|
||||||
|
{
|
||||||
|
if (Rigidbody)
|
||||||
|
{
|
||||||
|
isKinematic = fixed_;
|
||||||
|
Rigidbody.isKinematic = fixed_;
|
||||||
|
Rigidbody.useGravity = !fixed_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 施加力到此节点(会影响相邻节点)
|
||||||
|
/// </summary>
|
||||||
|
public void ApplyForce(Vector3 force, ForceMode mode = ForceMode.Force)
|
||||||
|
{
|
||||||
|
if (parentCable)
|
||||||
|
{
|
||||||
|
parentCable.ApplyForceAtBody(Rigidbody, force, mode);
|
||||||
|
}
|
||||||
|
else if (Rigidbody)
|
||||||
|
{
|
||||||
|
Rigidbody.AddForce(force, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSegmentLengths(float maxLength, float minLength)
|
||||||
|
{
|
||||||
|
NextSegmentMaxLength = maxLength;
|
||||||
|
NextSegmentMinLength = minLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetAdjacentBodies(out Rigidbody previousBody, out Rigidbody nextBody)
|
||||||
|
{
|
||||||
|
previousBody = null;
|
||||||
|
nextBody = null;
|
||||||
|
|
||||||
|
return parentCable && parentCable.TryGetAdjacentBodies(this, out previousBody, out nextBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 324bd710e0c5460ea880c6314ae95058
|
||||||
|
timeCreated: 1776948853
|
||||||
1006
Assets/Scripts/Fishing/New/View/FishingLine/FLineRenderer.cs
Normal file
1006
Assets/Scripts/Fishing/New/View/FishingLine/FLineRenderer.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9e43f5ab12b64bb9a47c0c674a3177f2
|
||||||
|
timeCreated: 1776960000
|
||||||
349
Assets/Scripts/Fishing/New/View/FishingLine/FLineTest.cs
Normal file
349
Assets/Scripts/Fishing/New/View/FishingLine/FLineTest.cs
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NBF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 硬线系统测试脚本,直接读取 FLine 已配置的节点。
|
||||||
|
/// </summary>
|
||||||
|
public class FLineTest : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("测试控制")] [SerializeField] private KeyCode fixMiddleKey = KeyCode.M;
|
||||||
|
[SerializeField] private KeyCode applyForceKey = KeyCode.F;
|
||||||
|
[SerializeField] private Vector3 testForce = new Vector3(0f, 10f, 0f);
|
||||||
|
|
||||||
|
[Header("动态间距控制")]
|
||||||
|
// [SerializeField] private KeyCode extendKey = KeyCode.UpArrow;
|
||||||
|
// [SerializeField] private KeyCode contractKey = KeyCode.DownArrow;
|
||||||
|
[SerializeField]
|
||||||
|
private KeyCode resetKey = KeyCode.Alpha0;
|
||||||
|
|
||||||
|
[SerializeField] private KeyCode pullFirstKey = KeyCode.UpArrow;
|
||||||
|
[SerializeField] private KeyCode relaxFirstKey = KeyCode.DownArrow;
|
||||||
|
[SerializeField, Min(0.01f)] private float extendAmount = 0.5f;
|
||||||
|
[SerializeField, Min(0.01f)] private float holdAdjustSpeed = 1f;
|
||||||
|
[SerializeField, Min(0.01f)] private float transitionSpeed = 2f;
|
||||||
|
[SerializeField] private bool smoothTransition = true;
|
||||||
|
|
||||||
|
[SerializeField] private FLine line;
|
||||||
|
private float[] initialLengths;
|
||||||
|
|
||||||
|
private void OnValidate()
|
||||||
|
{
|
||||||
|
extendAmount = Mathf.Max(0.01f, extendAmount);
|
||||||
|
holdAdjustSpeed = Mathf.Max(0.01f, holdAdjustSpeed);
|
||||||
|
transitionSpeed = Mathf.Max(0.01f, transitionSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
RefreshInitialLengths(true);
|
||||||
|
line.OnLineBreakRequested += OnLineBreakRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLineBreakRequested(FLine lineSolver)
|
||||||
|
{
|
||||||
|
Debug.LogError($"当前拉力达到极限,切线,极限时间={lineSolver.LimitStateTime}");
|
||||||
|
|
||||||
|
var nodes = line.GetLineNodes();
|
||||||
|
nodes[^1].Rigidbody.isKinematic = false;
|
||||||
|
// line.Body.isKinematic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!EnsureCable())
|
||||||
|
return;
|
||||||
|
|
||||||
|
RefreshInitialLengths();
|
||||||
|
HandleInput();
|
||||||
|
|
||||||
|
if (line.CurrentBreakStretchPercent > 10f)
|
||||||
|
{
|
||||||
|
Debug.LogError(
|
||||||
|
$"当前极限情况,CurrentBreakStretchPercent={line.CurrentBreakStretchPercent} CurrentStretchLength={line.CurrentStretchLength} TotalLength={line.TotalLength} LimitStateTime={line.LimitStateTime}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool EnsureCable()
|
||||||
|
{
|
||||||
|
if (line)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
line = GetComponent<FLine>();
|
||||||
|
return line != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FLineLogicNode> GetNodes()
|
||||||
|
{
|
||||||
|
return line != null ? line.GetLineNodes() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshInitialLengths(bool force = false)
|
||||||
|
{
|
||||||
|
List<FLineLogicNode> nodes = GetNodes();
|
||||||
|
int segmentCount = nodes != null ? Mathf.Max(0, nodes.Count - 1) : 0;
|
||||||
|
|
||||||
|
if (!force && initialLengths != null && initialLengths.Length == segmentCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
initialLengths = new float[segmentCount];
|
||||||
|
for (int i = 0; i < segmentCount; i++)
|
||||||
|
{
|
||||||
|
float configuredLength = line.GetSegmentMaxLength(i);
|
||||||
|
if (configuredLength <= 0f)
|
||||||
|
configuredLength = Mathf.Max(0.01f, line.GetCurrentSegmentLength(i));
|
||||||
|
|
||||||
|
initialLengths[i] = Mathf.Max(0.01f, configuredLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetCurrentMaxLength(int segmentIndex)
|
||||||
|
{
|
||||||
|
float length = line.GetSegmentMaxLength(segmentIndex);
|
||||||
|
return Mathf.Max(0.01f, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetTargetMaxLength(int segmentIndex)
|
||||||
|
{
|
||||||
|
float length = line.GetTargetMaxLength(segmentIndex);
|
||||||
|
if (length <= 0f)
|
||||||
|
length = line.GetSegmentMaxLength(segmentIndex);
|
||||||
|
|
||||||
|
return Mathf.Max(0.01f, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetSegmentCount()
|
||||||
|
{
|
||||||
|
return initialLengths != null ? initialLengths.Length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleInput()
|
||||||
|
{
|
||||||
|
HandleOriginalControls();
|
||||||
|
HandleDynamicDistanceControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleOriginalControls()
|
||||||
|
{
|
||||||
|
List<FLineLogicNode> nodes = GetNodes();
|
||||||
|
if (nodes == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Input.GetKeyDown(fixMiddleKey) && nodes.Count >= 3)
|
||||||
|
{
|
||||||
|
int middleIndex = nodes.Count / 2;
|
||||||
|
FLineLogicNode middleNode = nodes[middleIndex];
|
||||||
|
Rigidbody middleRb = middleNode ? middleNode.Rigidbody : null;
|
||||||
|
|
||||||
|
if (middleNode && middleRb)
|
||||||
|
{
|
||||||
|
bool newState = !middleRb.isKinematic;
|
||||||
|
middleNode.SetFixed(newState);
|
||||||
|
Debug.Log($"中间节点({middleIndex}) {(newState ? "固定" : "释放")} - 观察其他节点变化");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Input.GetKeyDown(applyForceKey) && nodes.Count > 2)
|
||||||
|
{
|
||||||
|
int randomIndex = Random.Range(1, nodes.Count - 1);
|
||||||
|
FLineLogicNode targetNode = nodes[randomIndex];
|
||||||
|
Rigidbody targetRb = targetNode ? targetNode.Rigidbody : null;
|
||||||
|
|
||||||
|
if (targetNode && targetRb && !targetRb.isKinematic)
|
||||||
|
{
|
||||||
|
targetNode.ApplyForce(testForce, ForceMode.Impulse);
|
||||||
|
Debug.Log($"对节点{randomIndex}施加力: {testForce} - 观察传递效果");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDynamicDistanceControls()
|
||||||
|
{
|
||||||
|
if (line == null || initialLengths == null || initialLengths.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// HandleLengthAdjustInput();
|
||||||
|
|
||||||
|
if (Input.GetKeyDown(resetKey))
|
||||||
|
ResetAllSegments();
|
||||||
|
|
||||||
|
if (Input.GetKeyDown(pullFirstKey))
|
||||||
|
PullFirstSegment(extendAmount * 0.5f);
|
||||||
|
|
||||||
|
if (Input.GetKeyDown(relaxFirstKey))
|
||||||
|
RelaxFirstSegment(extendAmount * 0.5f);
|
||||||
|
|
||||||
|
if (Input.mouseScrollDelta.y != 0f && Input.GetKey(KeyCode.LeftShift))
|
||||||
|
AdjustNearestSegment(Input.mouseScrollDelta.y * 0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void ApplySegmentTargetLength(int segmentIndex, float targetLength)
|
||||||
|
{
|
||||||
|
line.SetSegmentMaxLength(segmentIndex, targetLength, transitionSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetAllSegments()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < GetSegmentCount(); i++)
|
||||||
|
{
|
||||||
|
ApplySegmentTargetLength(i, initialLengths[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log("重置绳索到 FLine 初始节点配置");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PullFirstSegment(float amount)
|
||||||
|
{
|
||||||
|
if (GetSegmentCount() <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float targetLength = Mathf.Max(0.1f, GetTargetMaxLength(0) - amount);
|
||||||
|
ApplySegmentTargetLength(0, targetLength);
|
||||||
|
Debug.Log($"拉紧第一段到 {targetLength:F2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RelaxFirstSegment(float amount)
|
||||||
|
{
|
||||||
|
if (GetSegmentCount() <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float targetLength = GetTargetMaxLength(0) + amount;
|
||||||
|
ApplySegmentTargetLength(0, targetLength);
|
||||||
|
Debug.Log($"放松第一段到 {targetLength:F2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AdjustNearestSegment(float amount)
|
||||||
|
{
|
||||||
|
int nearestSegment = FindNearestSegmentToCamera();
|
||||||
|
if (nearestSegment < 0 || nearestSegment >= GetSegmentCount())
|
||||||
|
return;
|
||||||
|
|
||||||
|
float targetLength = Mathf.Max(0.1f, GetTargetMaxLength(nearestSegment) + amount);
|
||||||
|
ApplySegmentTargetLength(nearestSegment, targetLength);
|
||||||
|
Debug.Log($"调整段{nearestSegment}到 {targetLength:F2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindNearestSegmentToCamera()
|
||||||
|
{
|
||||||
|
Camera cam = Camera.main;
|
||||||
|
List<FLineLogicNode> nodes = GetNodes();
|
||||||
|
if (cam == null || nodes == null || nodes.Count < 2)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int nearestSegment = -1;
|
||||||
|
float nearestDistance = float.MaxValue;
|
||||||
|
|
||||||
|
for (int i = 0; i < nodes.Count - 1; i++)
|
||||||
|
{
|
||||||
|
Rigidbody bodyA = nodes[i] ? nodes[i].Rigidbody : null;
|
||||||
|
Rigidbody bodyB = nodes[i + 1] ? nodes[i + 1].Rigidbody : null;
|
||||||
|
if (!bodyA || !bodyB)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Vector3 midPoint = (bodyA.position + bodyB.position) * 0.5f;
|
||||||
|
Vector3 screenPoint = cam.WorldToScreenPoint(midPoint);
|
||||||
|
Vector3 mousePos = Input.mousePosition;
|
||||||
|
|
||||||
|
float distance = Vector2.Distance(
|
||||||
|
new Vector2(screenPoint.x, screenPoint.y),
|
||||||
|
new Vector2(mousePos.x, mousePos.y)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (distance < nearestDistance)
|
||||||
|
{
|
||||||
|
nearestDistance = distance;
|
||||||
|
nearestSegment = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearestSegment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
if (!EnsureCable())
|
||||||
|
return;
|
||||||
|
|
||||||
|
RefreshInitialLengths();
|
||||||
|
|
||||||
|
List<FLineLogicNode> nodes = GetNodes();
|
||||||
|
int nodeCount = nodes != null ? nodes.Count : 0;
|
||||||
|
|
||||||
|
GUILayout.BeginArea(new Rect(10f, 10f, 360f, 260f));
|
||||||
|
GUILayout.Label("=== 硬线系统测试控制 ===");
|
||||||
|
GUILayout.Label("原始控制:");
|
||||||
|
GUILayout.Label($" {fixMiddleKey} - 固定/释放中间节点");
|
||||||
|
GUILayout.Label($" {applyForceKey} - 随机施加力");
|
||||||
|
GUILayout.Space(10f);
|
||||||
|
GUILayout.Label("动态间距控制:");
|
||||||
|
GUILayout.Label($" {resetKey} - 重置到初始节点配置");
|
||||||
|
GUILayout.Label($" {pullFirstKey} - 拉紧第一段");
|
||||||
|
GUILayout.Label($" {relaxFirstKey} - 放松第一段");
|
||||||
|
GUILayout.Label(" Shift+滚轮 - 调整最近段");
|
||||||
|
GUILayout.Space(10f);
|
||||||
|
GUILayout.Label("设置:");
|
||||||
|
GUILayout.Label($" 节点数: {nodeCount}");
|
||||||
|
GUILayout.Label(" 初始长度来源: FLine 节点配置");
|
||||||
|
GUILayout.Label($" 过渡模式: {(smoothTransition ? "平滑" : "即时")}");
|
||||||
|
if (smoothTransition)
|
||||||
|
GUILayout.Label($" 过渡速度: {transitionSpeed:F1}");
|
||||||
|
|
||||||
|
GUILayout.EndArea();
|
||||||
|
|
||||||
|
GUILayout.BeginArea(new Rect(10f, 280f, 360f, 220f));
|
||||||
|
GUILayout.Label("=== 各段实际长度 ===");
|
||||||
|
|
||||||
|
for (int i = 0; i < Mathf.Min(GetSegmentCount(), 10); i++)
|
||||||
|
{
|
||||||
|
Rigidbody bodyA = nodes[i] ? nodes[i].Rigidbody : null;
|
||||||
|
Rigidbody bodyB = nodes[i + 1] ? nodes[i + 1].Rigidbody : null;
|
||||||
|
if (!bodyA || !bodyB)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float actualDistance = Vector3.Distance(bodyA.position, bodyB.position);
|
||||||
|
float currentLimit = GetCurrentMaxLength(i);
|
||||||
|
float targetLimit = GetTargetMaxLength(i);
|
||||||
|
|
||||||
|
string segmentInfo = $"段{i}: {actualDistance:F2} (限制: {currentLimit:F2}";
|
||||||
|
if (line.IsSegmentTransitioning(i))
|
||||||
|
segmentInfo += $" -> {targetLimit:F2}";
|
||||||
|
|
||||||
|
segmentInfo += ")";
|
||||||
|
|
||||||
|
if (actualDistance > targetLimit * 1.1f)
|
||||||
|
{
|
||||||
|
GUI.color = Color.red;
|
||||||
|
}
|
||||||
|
else if (line.IsSegmentTransitioning(i))
|
||||||
|
{
|
||||||
|
GUI.color = Color.yellow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUI.color = Color.green;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.Label(segmentInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.color = Color.white;
|
||||||
|
|
||||||
|
bool anyTransitioning = false;
|
||||||
|
for (int i = 0; i < GetSegmentCount(); i++)
|
||||||
|
{
|
||||||
|
if (line.IsSegmentTransitioning(i))
|
||||||
|
{
|
||||||
|
anyTransitioning = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyTransitioning)
|
||||||
|
GUILayout.Label("状态: 过渡中...");
|
||||||
|
|
||||||
|
GUILayout.EndArea();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c2c33030c494448488a01acae5f2ba54
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 72431056211140f1bdbf95503df5f198
|
||||||
|
timeCreated: 1777005389
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5ff7196c0f1441d4807ea7be8f921eb6
|
||||||
|
timeCreated: 1777011145
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using UnityEngine;
|
using NBF;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
public class SimpleWaterSurfaceProvider : MonoBehaviour, IWaterSurfaceProvider
|
public class SimpleWaterSurfaceProvider : MonoBehaviour, IWaterSurfaceProvider
|
||||||
{
|
{
|
||||||
@@ -44,11 +44,6 @@ namespace NBF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LateUpdate()
|
|
||||||
{
|
|
||||||
// SyncPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
SyncPosition();
|
SyncPosition();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace NBF
|
|||||||
|
|
||||||
public struct ThrowAnimationRequest
|
public struct ThrowAnimationRequest
|
||||||
{
|
{
|
||||||
public LureController Lure;
|
public FLineLogicNode Lure;
|
||||||
public Vector3 ThrowOriginPosition;
|
public Vector3 ThrowOriginPosition;
|
||||||
public Vector3 StartPosition;
|
public Vector3 StartPosition;
|
||||||
public Vector3 Forward;
|
public Vector3 Forward;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace NBF
|
|||||||
private float _castElapsedTime;
|
private float _castElapsedTime;
|
||||||
private Vector3 _castStartPos;
|
private Vector3 _castStartPos;
|
||||||
private Vector3 _castTargetPos;
|
private Vector3 _castTargetPos;
|
||||||
private LureController _castingLure;
|
private FLineLogicNode _castingLure;
|
||||||
|
|
||||||
public bool IsPlaying => _castingLure != null;
|
public bool IsPlaying => _castingLure != null;
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ namespace NBF
|
|||||||
_chargedProgress = Mathf.Clamp01(request.ChargedProgress);
|
_chargedProgress = Mathf.Clamp01(request.ChargedProgress);
|
||||||
_castElapsedTime = 0f;
|
_castElapsedTime = 0f;
|
||||||
|
|
||||||
var lureBody = request.Lure.RBody;
|
var lureBody = request.Lure.Rigidbody;
|
||||||
_castStartPos = request.StartPosition;
|
_castStartPos = request.StartPosition;
|
||||||
|
|
||||||
Vector3 forward = GetHorizontalForward(request.Forward);
|
Vector3 forward = GetHorizontalForward(request.Forward);
|
||||||
@@ -81,7 +81,7 @@ namespace NBF
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var lureBody = _castingLure.RBody;
|
var lureBody = _castingLure.Rigidbody;
|
||||||
if (snapToTarget)
|
if (snapToTarget)
|
||||||
{
|
{
|
||||||
_castingLure.transform.position = _castTargetPos;
|
_castingLure.transform.position = _castTargetPos;
|
||||||
|
|||||||
@@ -101,9 +101,11 @@ namespace NBF
|
|||||||
var handItemView = Player.HandItem.GetComponent<PlayerItemView>();
|
var handItemView = Player.HandItem.GetComponent<PlayerItemView>();
|
||||||
if (handItemView != null && handItemView.Rod != null)
|
if (handItemView != null && handItemView.Rod != null)
|
||||||
{
|
{
|
||||||
if (handItemView.Rod.Line.PinchController != null)
|
var endNode = handItemView.Rod.Line.GetNode(FLineLogicNodeType.End);
|
||||||
|
var pinch = endNode.gameObject.GetComponent<JointPinchController>();
|
||||||
|
if (pinch != null)
|
||||||
{
|
{
|
||||||
handItemView.Rod.Line.PinchController.StartPinch(view.Unity.ModelAsset.Pinch);
|
pinch.StartPinch(view.Unity.ModelAsset.Pinch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,9 +120,12 @@ namespace NBF
|
|||||||
var handItemView = Player.HandItem.GetComponent<PlayerItemView>();
|
var handItemView = Player.HandItem.GetComponent<PlayerItemView>();
|
||||||
if (handItemView != null && handItemView.Rod != null)
|
if (handItemView != null && handItemView.Rod != null)
|
||||||
{
|
{
|
||||||
if (handItemView.Rod.Line.PinchController != null)
|
var endNode = handItemView.Rod.Line.GetNode(FLineLogicNodeType.End);
|
||||||
|
var pinch = endNode.gameObject.GetComponent<JointPinchController>();
|
||||||
|
if (pinch != null)
|
||||||
{
|
{
|
||||||
handItemView.Rod.Line.PinchController.ReleasePinch();
|
pinch.ReleasePinch();
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,18 +61,19 @@ namespace NBF
|
|||||||
PlayerView.Unity.ModelAsset.PlayerAnimator.StartThrow = false;
|
PlayerView.Unity.ModelAsset.PlayerAnimator.StartThrow = false;
|
||||||
|
|
||||||
var rod = GetRod();
|
var rod = GetRod();
|
||||||
if (rod == null || rod.Line == null || rod.Line.Lure == null)
|
if (rod == null || rod.Line == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var endNode = rod.Line.GetNode(FLineLogicNodeType.End);
|
||||||
_throwAnimation = CreateThrowAnimation(rod);
|
_throwAnimation = CreateThrowAnimation(rod);
|
||||||
_throwAnimation.Player = Player;
|
_throwAnimation.Player = Player;
|
||||||
_throwAnimation?.Play(new ThrowAnimationRequest
|
_throwAnimation?.Play(new ThrowAnimationRequest
|
||||||
{
|
{
|
||||||
Lure = rod.Line.Lure,
|
Lure = endNode,
|
||||||
ThrowOriginPosition = PlayerView.Unity.transform.position,
|
ThrowOriginPosition = PlayerView.Unity.transform.position,
|
||||||
StartPosition = rod.Line.Lure.RBody.position,
|
StartPosition = endNode.Rigidbody.position,
|
||||||
Forward = PlayerView.Unity.transform.forward,
|
Forward = PlayerView.Unity.transform.forward,
|
||||||
ChargedProgress = ChargedProgress
|
ChargedProgress = ChargedProgress
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ namespace NBF
|
|||||||
protected override void OnInit()
|
protected override void OnInit()
|
||||||
{
|
{
|
||||||
// transform.position = Rod.lineHandler.LineConnector_1.transform.position;
|
// transform.position = Rod.lineHandler.LineConnector_1.transform.position;
|
||||||
SetParent(Rod.Line.Bobber.transform);
|
var endNode = Rod.Line.GetNode(FLineLogicNodeType.Bobber);
|
||||||
|
SetParent(endNode.transform);
|
||||||
transform.localPosition = Vector3.zero;
|
transform.localPosition = Vector3.zero;
|
||||||
// var buoyancy = GetComponentInParent<CapsuleBuoyancyStable>();
|
// var buoyancy = GetComponentInParent<CapsuleBuoyancyStable>();
|
||||||
// buoyancy.InitBobber();
|
// buoyancy.InitBobber();
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ namespace NBF
|
|||||||
|
|
||||||
protected override void OnInit()
|
protected override void OnInit()
|
||||||
{
|
{
|
||||||
|
|
||||||
// transform.position = Rod.lineHandler.LineConnector_2.transform.position;
|
// transform.position = Rod.lineHandler.LineConnector_2.transform.position;
|
||||||
// transform.rotation = Rod.lineHandler.LineConnector_2.transform.rotation; // 确保旋转也同步
|
// transform.rotation = Rod.lineHandler.LineConnector_2.transform.rotation; // 确保旋转也同步
|
||||||
// SetParent(Rod.lineHandler.LineConnector_2.transform);
|
// SetParent(Rod.lineHandler.LineConnector_2.transform);
|
||||||
|
|
||||||
|
var endNode = Rod.Line.GetNode(FLineLogicNodeType.End);
|
||||||
SetParent(Rod.Line.Lure.transform);
|
SetParent(endNode.transform);
|
||||||
transform.localPosition = Vector3.zero;
|
transform.localPosition = Vector3.zero;
|
||||||
|
|
||||||
// var target = lineHandler.LineConnector_2.GetComponent<Rigidbody>();
|
// var target = lineHandler.LineConnector_2.GetComponent<Rigidbody>();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: c0403ffd74ce46fab8bd4ef057e51432
|
guid: c7095cf554c345839173044e4786b0ba
|
||||||
timeCreated: 1766582567
|
timeCreated: 1776948821
|
||||||
@@ -13,8 +13,8 @@ namespace NBF
|
|||||||
// LureHookWaterDisplacement = Lure.GetComponent<FWaterDisplacement>();
|
// LureHookWaterDisplacement = Lure.GetComponent<FWaterDisplacement>();
|
||||||
|
|
||||||
// SetParent(Rod.lineHandler.LineConnector_1.transform);
|
// SetParent(Rod.lineHandler.LineConnector_1.transform);
|
||||||
|
var endNode = Rod.Line.GetNode(FLineLogicNodeType.End);
|
||||||
SetParent(Rod.Line.Lure.transform);
|
SetParent(endNode.transform);
|
||||||
transform.localPosition = Vector3.zero;
|
transform.localPosition = Vector3.zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,24 +72,14 @@ namespace NBF
|
|||||||
if (Line.LineType == LineType.Spinning)
|
if (Line.LineType == LineType.Spinning)
|
||||||
{
|
{
|
||||||
//没有浮漂类型
|
//没有浮漂类型
|
||||||
Line.Lure.SetJointDistance(PlayerItem.LineLength);
|
Line.SetLenght(PlayerItem.LineLength);
|
||||||
if (PlayerItem.StretchRope)
|
|
||||||
{
|
|
||||||
// Line.SetTargetLength(PlayerItem.Tension > 0f ? 0f : PlayerItem.LineLength);
|
|
||||||
Line.SetTargetLength(PlayerItem.LineLength);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//有浮漂
|
//有浮漂
|
||||||
Line.Lure.SetJointDistance(PlayerItem.FloatLength);
|
Line.SetSegmentMaxLength(0, PlayerItem.LineLength - PlayerItem.FloatLength);
|
||||||
Line.Bobber.SetJointDistance(PlayerItem.LineLength - PlayerItem.FloatLength);
|
//浮漂位置
|
||||||
if (PlayerItem.StretchRope)
|
Line.SetSegmentMaxLength(1, PlayerItem.FloatLength);
|
||||||
{
|
|
||||||
// Line.SetTargetLength(PlayerItem.Tension > 0f ? 0f : PlayerItem.LineLength - PlayerItem.FloatLength);
|
|
||||||
Line.SetTargetLength(PlayerItem.LineLength - PlayerItem.FloatLength);
|
|
||||||
Line.SetLureLength(PlayerItem.FloatLength);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,12 +328,12 @@ namespace NBF
|
|||||||
|
|
||||||
var state = PlayerItem.Owner.State;
|
var state = PlayerItem.Owner.State;
|
||||||
|
|
||||||
|
var endNode = Line.GetNode(FLineLogicNodeType.End);
|
||||||
Vector3 vector = Line.Lure.transform.position;
|
Vector3 vector = endNode.transform.position;
|
||||||
|
|
||||||
// 当前物体的朝向与指向 Lure 的方向之间的夹角,在 0(完全对齐)到 1(完全相反)之间的一个比例值
|
// 当前物体的朝向与指向 Lure 的方向之间的夹角,在 0(完全对齐)到 1(完全相反)之间的一个比例值
|
||||||
float headingAlignment = Vector3.Angle(base.transform.forward,
|
float headingAlignment = Vector3.Angle(base.transform.forward,
|
||||||
(Line.Lure.transform.position - transform.position).normalized) / 180f;
|
(endNode.transform.position - transform.position).normalized) / 180f;
|
||||||
// 经过朝向调制后的有效张力
|
// 经过朝向调制后的有效张力
|
||||||
var effectiveTension = Mathf.Clamp(CurrentTension01 * headingAlignment, 0f, 1f);
|
var effectiveTension = Mathf.Clamp(CurrentTension01 * headingAlignment, 0f, 1f);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 98ba9d435a0e49c9bb527c34cc91894d
|
|
||||||
timeCreated: 1766759607
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 2d71eb3d89064cc4aab3317e49dc3979
|
|
||||||
timeCreated: 1772097895
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
|
|
||||||
public interface IWaterProvider
|
|
||||||
{
|
|
||||||
float GetWaterHeight(Vector3 worldPos);
|
|
||||||
Vector3 GetWaterNormal(Vector3 worldPos);
|
|
||||||
Vector3 GetWaterVelocity(Vector3 worldPos);
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// /// <summary>
|
|
||||||
// /// 稳定优先的浮力(只支持 CapsuleCollider / SphereCollider)
|
|
||||||
// /// - 竖直方向:目标吃水深度 + PD 控制(稳定,不抖、不弹飞)
|
|
||||||
// /// - 姿态:Righting Torque 扶正(受 Rigidbody.centerOfMass 影响)
|
|
||||||
// /// - 入水比例:带平滑(避免水面附近开关抖动)
|
|
||||||
// /// </summary>
|
|
||||||
// [DisallowMultipleComponent]
|
|
||||||
// [RequireComponent(typeof(Rigidbody))]
|
|
||||||
// public class BuoyancyCapsuleSphere : MonoBehaviour
|
|
||||||
// {
|
|
||||||
// [Header("Collider (choose one)")] public CapsuleCollider capsule;
|
|
||||||
// public SphereCollider sphere;
|
|
||||||
//
|
|
||||||
// [Header("Water")] public MonoBehaviour waterProviderBehaviour; // 可选
|
|
||||||
// private IWaterProvider waterProvider;
|
|
||||||
//
|
|
||||||
// [Tooltip("没有 provider 时的水面高度")] public float waterLevel = 0f;
|
|
||||||
//
|
|
||||||
// [Header("Density -> Draft")] [Tooltip("水密度 kg/m^3(淡水约1000)")]
|
|
||||||
// public float waterDensity = 1000f;
|
|
||||||
//
|
|
||||||
// [Tooltip("物体等效密度 kg/m^3。越小越浮。浮漂可 80~400 之间调")]
|
|
||||||
// public float objectDensity = 250f;
|
|
||||||
//
|
|
||||||
// [Tooltip("额外浮力比例(手感调整)。1=按密度算")] public float buoyancyScale = 1f;
|
|
||||||
//
|
|
||||||
// [Header("Vertical PD (Stability key)")] [Tooltip("竖直弹簧强度(越大越“顶住”目标吃水)")]
|
|
||||||
// public float verticalKp = 35f;
|
|
||||||
//
|
|
||||||
// [Tooltip("竖直阻尼(越大越不弹、不抖)")] public float verticalKd = 12f;
|
|
||||||
//
|
|
||||||
// [Tooltip("最大向上加速度 m/s^2(防止从高处落下入水被顶飞)")]
|
|
||||||
// public float maxUpAccel = 25f;
|
|
||||||
//
|
|
||||||
// [Tooltip("最大向下加速度 m/s^2(防止强行拉下去造成抖动)")]
|
|
||||||
// public float maxDownAccel = 10f;
|
|
||||||
//
|
|
||||||
// [Header("Submergence smoothing")] [Tooltip("入水比例变化速度(1/s)。越大越快响应,越小越稳")]
|
|
||||||
// public float submergenceSpeed = 8f;
|
|
||||||
//
|
|
||||||
// [Tooltip("水面外的缓冲(m),让浮力更平滑接管")] public float surfaceMargin = 0.01f;
|
|
||||||
//
|
|
||||||
// [Header("Righting (Rotation)")] [Tooltip("扶正强度(把 transform.up 拉向水面法线/世界上)")]
|
|
||||||
// public float rightingKp = 8f;
|
|
||||||
//
|
|
||||||
// [Tooltip("扶正阻尼(抑制旋转抖动)")] public float rightingKd = 3f;
|
|
||||||
//
|
|
||||||
// [Header("Water drag (optional but helpful)")] [Tooltip("入水时额外线阻尼(通过 rb.drag 混合)")]
|
|
||||||
// public float extraLinearDragInWater = 2.5f;
|
|
||||||
//
|
|
||||||
// [Tooltip("入水时额外角阻尼(通过 rb.angularDrag 混合)")]
|
|
||||||
// public float extraAngularDragInWater = 2.0f;
|
|
||||||
//
|
|
||||||
// [Header("Center of Mass")] [Tooltip("本地重心偏移:例如 (0,-0.02,0) 让底部更重、更容易站漂")]
|
|
||||||
// public Vector3 centerOfMassOffset = Vector3.zero;
|
|
||||||
//
|
|
||||||
// private Rigidbody rb;
|
|
||||||
// private float baseDrag, baseAngularDrag;
|
|
||||||
//
|
|
||||||
// // 关键:入水比例必须有“记忆”(滤波),否则水面边界必抖
|
|
||||||
// private float subFiltered = 0f;
|
|
||||||
//
|
|
||||||
// void Reset()
|
|
||||||
// {
|
|
||||||
// rb = GetComponent<Rigidbody>();
|
|
||||||
// rb.useGravity = true;
|
|
||||||
// rb.interpolation = RigidbodyInterpolation.Interpolate;
|
|
||||||
// rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
|
||||||
//
|
|
||||||
// capsule = GetComponent<CapsuleCollider>();
|
|
||||||
// sphere = GetComponent<SphereCollider>();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void Awake()
|
|
||||||
// {
|
|
||||||
// rb = GetComponent<Rigidbody>();
|
|
||||||
// rb.centerOfMass = centerOfMassOffset;
|
|
||||||
//
|
|
||||||
// baseDrag = rb.linearDamping;
|
|
||||||
// baseAngularDrag = rb.angularDamping;
|
|
||||||
//
|
|
||||||
// waterProvider = waterProviderBehaviour as IWaterProvider;
|
|
||||||
//
|
|
||||||
// // 只允许一个
|
|
||||||
// if (capsule != null && sphere != null)
|
|
||||||
// sphere = null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void OnValidate()
|
|
||||||
// {
|
|
||||||
// objectDensity = Mathf.Max(1e-3f, objectDensity);
|
|
||||||
// waterDensity = Mathf.Max(1e-3f, waterDensity);
|
|
||||||
// submergenceSpeed = Mathf.Max(0.1f, submergenceSpeed);
|
|
||||||
// surfaceMargin = Mathf.Max(0f, surfaceMargin);
|
|
||||||
// maxUpAccel = Mathf.Max(0f, maxUpAccel);
|
|
||||||
// maxDownAccel = Mathf.Max(0f, maxDownAccel);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void FixedUpdate()
|
|
||||||
// {
|
|
||||||
// if (!capsule && !sphere) return;
|
|
||||||
//
|
|
||||||
// waterProvider = waterProviderBehaviour as IWaterProvider;
|
|
||||||
//
|
|
||||||
// GetCenterAndHeight(out var centerWorld, out var shapeHeight);
|
|
||||||
//
|
|
||||||
// // 水面信息
|
|
||||||
// float waterH = waterProvider?.GetWaterHeight(centerWorld) ?? waterLevel;
|
|
||||||
// Vector3 waterN = waterProvider?.GetWaterNormal(centerWorld) ?? Vector3.up;
|
|
||||||
// if (waterN.sqrMagnitude < 1e-6f) waterN = Vector3.up;
|
|
||||||
// waterN.Normalize();
|
|
||||||
//
|
|
||||||
// // 当前中心“浸没深度”(>0 表示中心在水下)
|
|
||||||
// float centerDepth = waterH - centerWorld.y;
|
|
||||||
//
|
|
||||||
// // 近似入水比例:centerDepth = -H/2 -> 0; centerDepth = +H/2 -> 1
|
|
||||||
// float rawSub = Mathf.Clamp01((centerDepth + (shapeHeight * 0.5f) + surfaceMargin) /
|
|
||||||
// (shapeHeight + 2f * surfaceMargin));
|
|
||||||
//
|
|
||||||
// // 入水比例滤波(非常关键)
|
|
||||||
// float dt = Time.fixedDeltaTime;
|
|
||||||
// subFiltered = Mathf.MoveTowards(subFiltered, rawSub, submergenceSpeed * dt);
|
|
||||||
//
|
|
||||||
// // 混合拖拽(让水中更稳)
|
|
||||||
// rb.linearDamping = Mathf.Lerp(baseDrag, baseDrag + extraLinearDragInWater, subFiltered);
|
|
||||||
// rb.angularDamping = Mathf.Lerp(baseAngularDrag, baseAngularDrag + extraAngularDragInWater, subFiltered);
|
|
||||||
//
|
|
||||||
// if (subFiltered <= 1e-4f)
|
|
||||||
// return; // 基本没入水,不做任何浮力/扶正
|
|
||||||
//
|
|
||||||
// // 目标吃水比例:理想静态平衡 ≈ objectDensity / waterDensity(<1 才会浮)
|
|
||||||
// float desiredSub = Mathf.Clamp01((objectDensity / waterDensity) * buoyancyScale);
|
|
||||||
//
|
|
||||||
// // 把 desiredSub 转成目标中心深度
|
|
||||||
// // desiredSub=0 -> centerDepthTarget = -H/2(完全出水)
|
|
||||||
// // desiredSub=1 -> centerDepthTarget = +H/2(完全入水)
|
|
||||||
// float centerDepthTarget = desiredSub * shapeHeight - shapeHeight * 0.5f;
|
|
||||||
//
|
|
||||||
// // 竖直 PD:只沿“世界上/重力反方向”控制,最稳
|
|
||||||
// Vector3 up = (-Physics.gravity).sqrMagnitude > 1e-6f ? (-Physics.gravity).normalized : Vector3.up;
|
|
||||||
// float vUp = Vector3.Dot(rb.linearVelocity, up);
|
|
||||||
//
|
|
||||||
// float error = centerDepth - centerDepthTarget; // 深了为正 -> 需要向上推
|
|
||||||
// float accelUp = (-verticalKp * error) - (verticalKd * vUp);
|
|
||||||
//
|
|
||||||
// // 限制上下加速度,避免顶飞或强拉抖动
|
|
||||||
// accelUp = Mathf.Clamp(accelUp, -maxDownAccel, maxUpAccel);
|
|
||||||
//
|
|
||||||
// // 随入水比例渐入(避免水面边界突然接管)
|
|
||||||
// accelUp *= subFiltered;
|
|
||||||
//
|
|
||||||
// // 施加竖直加速度(Acceleration 不受质量影响,更稳定)
|
|
||||||
// rb.AddForce(up * accelUp, ForceMode.Acceleration);
|
|
||||||
//
|
|
||||||
// // 扶正力矩:把物体 up 拉向 waterN(平静水就是 Vector3.up)
|
|
||||||
// // 注意:这个扶正与重心偏移一起工作,会形成“站漂/躺漂”的稳定姿态
|
|
||||||
// Vector3 currentUp = transform.up;
|
|
||||||
// Vector3 axis = Vector3.Cross(currentUp, waterN);
|
|
||||||
//
|
|
||||||
// // 小角度近似:axis 的大小约等于 sin(theta)
|
|
||||||
// // 扶正加速度型扭矩(同样用 Acceleration,减少质量/惯量差带来的抖动)
|
|
||||||
// Vector3 angVel = rb.angularVelocity;
|
|
||||||
// Vector3 torqueAccel = axis * rightingKp - angVel * rightingKd;
|
|
||||||
//
|
|
||||||
// torqueAccel *= subFiltered;
|
|
||||||
//
|
|
||||||
// rb.AddTorque(torqueAccel, ForceMode.Acceleration);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void GetCenterAndHeight(out Vector3 centerWorld, out float heightWorld)
|
|
||||||
// {
|
|
||||||
// if (sphere)
|
|
||||||
// {
|
|
||||||
// // sphere:center + 半径*缩放(近似取最大缩放)
|
|
||||||
// Transform t = sphere.transform;
|
|
||||||
// centerWorld = t.TransformPoint(sphere.center);
|
|
||||||
//
|
|
||||||
// Vector3 s = t.lossyScale;
|
|
||||||
// float r = sphere.radius * Mathf.Max(Mathf.Abs(s.x), Mathf.Abs(s.y), Mathf.Abs(s.z));
|
|
||||||
// heightWorld = Mathf.Max(1e-6f, r * 2f);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // capsule
|
|
||||||
// Transform ct = capsule.transform;
|
|
||||||
// centerWorld = ct.TransformPoint(capsule.center);
|
|
||||||
//
|
|
||||||
// Vector3 ls = ct.lossyScale;
|
|
||||||
// float sx = Mathf.Abs(ls.x), sy = Mathf.Abs(ls.y), sz = Mathf.Abs(ls.z);
|
|
||||||
//
|
|
||||||
// float heightScale = capsule.direction switch
|
|
||||||
// {
|
|
||||||
// 0 => sx,
|
|
||||||
// 1 => sy,
|
|
||||||
// _ => sz,
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// heightWorld = Mathf.Max(1e-6f, capsule.height * heightScale);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: bad586cd447d4271b97ce1cf1c81897a
|
|
||||||
timeCreated: 1772460028
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
using WaveHarmonic.Crest;
|
|
||||||
|
|
||||||
public class BuoyancyWaterProvider : MonoBehaviour, IWaterProvider
|
|
||||||
{
|
|
||||||
public WaterRenderer Water;
|
|
||||||
readonly SampleFlowHelper _SampleFlowHelper = new();
|
|
||||||
public float waterLevel = 0f;
|
|
||||||
|
|
||||||
public float GetWaterHeight(Vector3 worldPos)
|
|
||||||
{
|
|
||||||
return waterLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector3 GetWaterNormal(Vector3 worldPos)
|
|
||||||
{
|
|
||||||
return Vector3.up;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector3 GetWaterVelocity(Vector3 worldPos)
|
|
||||||
{
|
|
||||||
return Vector3.zero; // 关键!不要乱给
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 06d107cece7c4cbb9825557923be567f
|
|
||||||
timeCreated: 1772380723
|
|
||||||
@@ -1,268 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using WaveHarmonic.Crest;
|
|
||||||
|
|
||||||
[DisallowMultipleComponent]
|
|
||||||
[RequireComponent(typeof(Rigidbody))]
|
|
||||||
public class CapsuleBuoyancyStable : MonoBehaviour
|
|
||||||
{
|
|
||||||
[Header("Buoyancy")] [Tooltip("完全浸没时总浮力 = mass*g*buoyancyScale。>1 更浮。")]
|
|
||||||
public float buoyancyScale = 1.6f;
|
|
||||||
|
|
||||||
[Tooltip("沿胶囊轴向采样点数量(建议 7~11)。")] [Range(3, 15)]
|
|
||||||
public int samplePoints = 9;
|
|
||||||
|
|
||||||
[Tooltip("浸没比例曲线(0=刚碰水, 1=充分在水下)。")] public AnimationCurve submergenceCurve = AnimationCurve.Linear(0, 0, 1, 1);
|
|
||||||
|
|
||||||
[Header("Damping")] [Tooltip("上浮方向速度阻尼(越大越不弹)。本版本:只在“浮力中心”施加一次,不再在每个采样点施加,避免90°附近转不动。")]
|
|
||||||
public float verticalDamping = 0.6f;
|
|
||||||
|
|
||||||
[Tooltip("整体角速度阻尼(只施加一次,不要太大)。")] public float angularDamping = 0.6f;
|
|
||||||
|
|
||||||
[Header("Optional Upright Stabilizer (Recommended for bobber)")] [Tooltip("让胶囊轴向更倾向于对齐世界Up。0=关闭。")]
|
|
||||||
public float uprightSpring = 0.0f;
|
|
||||||
|
|
||||||
[Tooltip("upright 的角速度阻尼。")] public float uprightDamping = 0.5f;
|
|
||||||
|
|
||||||
[Tooltip("胶囊轴向:0=X,1=Y,2=Z(通常 CapsuleCollider.direction 也一样)。")]
|
|
||||||
public int uprightAxis = 1;
|
|
||||||
|
|
||||||
[Header("Water Drag")] public float extraDragInWater = 0.8f;
|
|
||||||
public float extraAngularDragInWater = 0.8f;
|
|
||||||
|
|
||||||
[Header("Anti-stiction near upright")]
|
|
||||||
[Tooltip("在接近竖直(例如90->80度附近)时,降低vertical damping,避免“粘住”。0=关闭。")]
|
|
||||||
[Range(0f, 1f)]
|
|
||||||
public float nearUprightDampingReduce = 0.6f;
|
|
||||||
|
|
||||||
[Tooltip("接近竖直的判定角度(度)。例如 12 表示在 |angle| < 12° 附近逐步降低阻尼。")] [Range(1f, 30f)]
|
|
||||||
public float nearUprightAngleDeg = 12f;
|
|
||||||
|
|
||||||
#region Crest5相关信息
|
|
||||||
|
|
||||||
public WaterRenderer _waterRenderer;
|
|
||||||
|
|
||||||
[Tooltip("要瞄准哪一层水的碰撞层。")] [SerializeField]
|
|
||||||
CollisionLayer _Layer = CollisionLayer.AfterAnimatedWaves;
|
|
||||||
|
|
||||||
[Header("波响应")] [Tooltip("用于物理计算的物体宽度。\n\n此值越大,波响应的滤波效果和平滑程度就越高。如果无法对较大波长进行滤波,则应增加 LOD 级别。")] [SerializeField]
|
|
||||||
float _ObjectWidth = 3f;
|
|
||||||
|
|
||||||
readonly SampleFlowHelper _SampleFlowHelper = new();
|
|
||||||
|
|
||||||
Vector3[] _QueryPoints;
|
|
||||||
Vector3[] _QueryResultDisplacements;
|
|
||||||
Vector3[] _QueryResultVelocities;
|
|
||||||
Vector3[] _QueryResultNormal;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
[Header("Debug")] public bool drawDebug = false;
|
|
||||||
|
|
||||||
Rigidbody _rb;
|
|
||||||
CapsuleCollider _cap;
|
|
||||||
float _baseDrag, _baseAngularDrag;
|
|
||||||
|
|
||||||
[SerializeField] private bool _init = false;
|
|
||||||
|
|
||||||
void Awake()
|
|
||||||
{
|
|
||||||
_rb = GetComponent<Rigidbody>();
|
|
||||||
_baseDrag = _rb.linearDamping;
|
|
||||||
_baseAngularDrag = _rb.angularDamping;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Start()
|
|
||||||
{
|
|
||||||
int length = Mathf.Max(3, samplePoints);
|
|
||||||
_QueryPoints = new Vector3[length];
|
|
||||||
_QueryResultDisplacements = new Vector3[length];
|
|
||||||
_QueryResultVelocities = new Vector3[length];
|
|
||||||
_QueryResultNormal = new Vector3[length];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InitBobber()
|
|
||||||
{
|
|
||||||
if (_waterRenderer == null && SceneSettings.Instance)
|
|
||||||
{
|
|
||||||
_waterRenderer = SceneSettings.Instance.Water;
|
|
||||||
}
|
|
||||||
_cap = GetComponentInChildren<CapsuleCollider>();
|
|
||||||
_init = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FixedUpdate()
|
|
||||||
{
|
|
||||||
if (!_init) return;
|
|
||||||
if (!_waterRenderer) return;
|
|
||||||
|
|
||||||
GetWorldCapsule(out Vector3 a, out Vector3 b, out float radius);
|
|
||||||
|
|
||||||
int n = Mathf.Max(3, samplePoints);
|
|
||||||
if (_QueryPoints == null || _QueryPoints.Length != n)
|
|
||||||
{
|
|
||||||
_QueryPoints = new Vector3[n];
|
|
||||||
_QueryResultDisplacements = new Vector3[n];
|
|
||||||
_QueryResultVelocities = new Vector3[n];
|
|
||||||
_QueryResultNormal = new Vector3[n];
|
|
||||||
}
|
|
||||||
|
|
||||||
float fullBuoyancy = _rb.mass * Physics.gravity.magnitude * buoyancyScale;
|
|
||||||
float perPointMax = fullBuoyancy / n;
|
|
||||||
|
|
||||||
// 采样点
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
float t = (float)i / (n - 1);
|
|
||||||
_QueryPoints[i] = Vector3.Lerp(a, b, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crest 查询
|
|
||||||
var collisions = _waterRenderer.AnimatedWavesLod.Provider;
|
|
||||||
collisions.Query(GetHashCode(), _ObjectWidth, _QueryPoints, _QueryResultDisplacements,
|
|
||||||
_QueryResultNormal, _QueryResultVelocities, _Layer);
|
|
||||||
|
|
||||||
float subSum = 0f;
|
|
||||||
int wetCount = 0;
|
|
||||||
|
|
||||||
// 用于计算“浮力中心”(Center of Buoyancy)与水流速度平均
|
|
||||||
Vector3 cobSum = Vector3.zero;
|
|
||||||
Vector3 wvSum = Vector3.zero;
|
|
||||||
float cobW = 0f;
|
|
||||||
|
|
||||||
// 1) 多点只加浮力(不再在每点加vertical damping)
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
Vector3 p = _QueryPoints[i];
|
|
||||||
|
|
||||||
float waterH = _QueryResultDisplacements[i].y + _waterRenderer.SeaLevel;
|
|
||||||
float depth = waterH - p.y;
|
|
||||||
|
|
||||||
float sub = Mathf.InverseLerp(-radius, radius, depth);
|
|
||||||
if (sub <= 0f) continue;
|
|
||||||
|
|
||||||
sub = Mathf.Clamp01(submergenceCurve.Evaluate(sub));
|
|
||||||
|
|
||||||
subSum += sub;
|
|
||||||
wetCount++;
|
|
||||||
|
|
||||||
cobSum += p * sub;
|
|
||||||
wvSum += _QueryResultVelocities[i] * sub;
|
|
||||||
cobW += sub;
|
|
||||||
|
|
||||||
Vector3 buoyForce = Vector3.up * (perPointMax * sub);
|
|
||||||
_rb.AddForceAtPosition(buoyForce, p, ForceMode.Force);
|
|
||||||
|
|
||||||
if (drawDebug)
|
|
||||||
{
|
|
||||||
Debug.DrawLine(p, p + buoyForce / (_rb.mass * 10f), Color.cyan, 0f, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float subAvg = (wetCount > 0) ? (subSum / wetCount) : 0f;
|
|
||||||
|
|
||||||
// 2) vertical damping:只在“浮力中心”施加一次(关键修复:不再产生抑制旋转的力矩)
|
|
||||||
if (subAvg > 0f && cobW > 1e-6f)
|
|
||||||
{
|
|
||||||
Vector3 cob = cobSum / cobW;
|
|
||||||
Vector3 waterVelAvg = wvSum / cobW;
|
|
||||||
|
|
||||||
// 接近竖直时降低vertical damping,避免90->80度“粘住”
|
|
||||||
float vdScale = 1f;
|
|
||||||
if (nearUprightDampingReduce > 0f)
|
|
||||||
{
|
|
||||||
Vector3 axisWorld = GetAxisWorld(uprightAxis);
|
|
||||||
float angleFromUp = Vector3.Angle(axisWorld, Vector3.up); // 0=竖直
|
|
||||||
float t = Mathf.Clamp01(angleFromUp / Mathf.Max(0.001f, nearUprightAngleDeg));
|
|
||||||
// t=0(很竖直) -> 1(离开竖直)
|
|
||||||
vdScale = Mathf.Lerp(1f - nearUprightDampingReduce, 1f, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 pointVel = _rb.GetPointVelocity(cob);
|
|
||||||
Vector3 relVel = pointVel - waterVelAvg;
|
|
||||||
float vUp = Vector3.Dot(relVel, Vector3.up);
|
|
||||||
|
|
||||||
Vector3 dampForce = -Vector3.up * (vUp * verticalDamping * _rb.mass * subAvg * vdScale);
|
|
||||||
_rb.AddForceAtPosition(dampForce, cob, ForceMode.Force);
|
|
||||||
|
|
||||||
if (drawDebug)
|
|
||||||
{
|
|
||||||
Debug.DrawLine(cob, cob + dampForce / (_rb.mass * 10f), Color.yellow, 0f, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) 角阻尼:只加一次
|
|
||||||
if (subAvg > 0f)
|
|
||||||
{
|
|
||||||
_rb.AddTorque(-_rb.angularVelocity * (angularDamping * _rb.mass * subAvg), ForceMode.Force);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4) upright(保持你原逻辑)
|
|
||||||
if (subAvg > 0f && uprightSpring > 0f)
|
|
||||||
{
|
|
||||||
Vector3 axisWorld = GetAxisWorld(uprightAxis);
|
|
||||||
Vector3 targetUp = Vector3.up;
|
|
||||||
|
|
||||||
Vector3 errorAxis = Vector3.Cross(axisWorld, targetUp);
|
|
||||||
float errorMag = errorAxis.magnitude;
|
|
||||||
|
|
||||||
if (errorMag > 1e-6f)
|
|
||||||
{
|
|
||||||
errorAxis /= errorMag;
|
|
||||||
|
|
||||||
Vector3 springTorque = errorAxis * (uprightSpring * errorMag * _rb.mass);
|
|
||||||
Vector3 dampTorque = -_rb.angularVelocity * (uprightDamping * _rb.mass);
|
|
||||||
|
|
||||||
_rb.AddTorque((springTorque + dampTorque) * subAvg, ForceMode.Force);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5) 入水 drag
|
|
||||||
if (subAvg > 0.001f)
|
|
||||||
{
|
|
||||||
_rb.linearDamping = _baseDrag + extraDragInWater * subAvg;
|
|
||||||
_rb.angularDamping = _baseAngularDrag + extraAngularDragInWater * subAvg;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_rb.linearDamping = _baseDrag;
|
|
||||||
_rb.angularDamping = _baseAngularDrag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 GetAxisWorld(int axis)
|
|
||||||
{
|
|
||||||
return axis switch
|
|
||||||
{
|
|
||||||
0 => transform.right,
|
|
||||||
2 => transform.forward,
|
|
||||||
_ => transform.up,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void GetWorldCapsule(out Vector3 a, out Vector3 b, out float radius)
|
|
||||||
{
|
|
||||||
Vector3 lossy = transform.lossyScale;
|
|
||||||
int dir = _cap.direction;
|
|
||||||
|
|
||||||
float scaleAlong = (dir == 0) ? Mathf.Abs(lossy.x) : (dir == 1) ? Mathf.Abs(lossy.y) : Mathf.Abs(lossy.z);
|
|
||||||
|
|
||||||
float scaleR;
|
|
||||||
if (dir == 0) scaleR = Mathf.Max(Mathf.Abs(lossy.y), Mathf.Abs(lossy.z));
|
|
||||||
else if (dir == 1) scaleR = Mathf.Max(Mathf.Abs(lossy.x), Mathf.Abs(lossy.z));
|
|
||||||
else scaleR = Mathf.Max(Mathf.Abs(lossy.x), Mathf.Abs(lossy.y));
|
|
||||||
|
|
||||||
radius = _cap.radius * scaleR;
|
|
||||||
|
|
||||||
Vector3 center = transform.TransformPoint(_cap.center);
|
|
||||||
Vector3 axisWorld = (dir == 0) ? transform.right : (dir == 1) ? transform.up : transform.forward;
|
|
||||||
|
|
||||||
float heightWorld = Mathf.Max(0f, _cap.height * scaleAlong);
|
|
||||||
float cylinderLen = Mathf.Max(0f, heightWorld - 2f * radius);
|
|
||||||
|
|
||||||
Vector3 half = axisWorld * (cylinderLen * 0.5f);
|
|
||||||
a = center - half;
|
|
||||||
b = center + half;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: f91c9d873c83492ca6d5e3e3a67c1760
|
|
||||||
timeCreated: 1772522093
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
public class CollisionTest : MonoBehaviour
|
|
||||||
{
|
|
||||||
private void OnCollisionEnter(Collision other)
|
|
||||||
{
|
|
||||||
Debug.Log($"OnCollisionEnter:{other.gameObject.name}");
|
|
||||||
}
|
|
||||||
private void OnCollisionExit(Collision other)
|
|
||||||
{
|
|
||||||
Debug.Log($"OnCollisionExit:{other.gameObject.name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: d5bfa3f53f894e7a87315e5cbc220e12
|
|
||||||
timeCreated: 1772767361
|
|
||||||
@@ -1,337 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 鱼线张力参考模型
|
|
||||||
///
|
|
||||||
/// 这个脚本不负责真正的鱼线渲染,也不负责完整鱼AI,
|
|
||||||
/// 只负责根据“竿尖 - 鱼”的关系,计算一个较合理的张力模型。
|
|
||||||
///
|
|
||||||
/// 适合先拿来验证:
|
|
||||||
/// 1. 当前张力是否合理
|
|
||||||
/// 2. 鱼竿弯曲是否合理
|
|
||||||
/// 3. 卸力出线是否自然
|
|
||||||
/// 4. 断线判定是否符合预期
|
|
||||||
/// </summary>
|
|
||||||
public class FishingLineTensionModel : MonoBehaviour
|
|
||||||
{
|
|
||||||
[Header("References")] [Tooltip("鱼竿竿尖挂点")]
|
|
||||||
public Transform rodTip;
|
|
||||||
|
|
||||||
[Tooltip("鱼对象(位置来源)")] public Transform fishTarget;
|
|
||||||
|
|
||||||
[Tooltip("鱼刚体,用来取速度并施加线的拉力")] public Rigidbody fishRb;
|
|
||||||
|
|
||||||
[Tooltip("玩家/竿尖所在刚体,可选。若为空则竿尖速度按0处理")] public Rigidbody rodTipRb;
|
|
||||||
|
|
||||||
[Header("Line State")] [Tooltip("当前放线长度(米)")]
|
|
||||||
public float lineLength = 5f;
|
|
||||||
|
|
||||||
[Tooltip("最短允许线长,防止收线收到过小")] public float minLineLength = 0.5f;
|
|
||||||
|
|
||||||
[Tooltip("最长允许线长")] public float maxLineLength = 100f;
|
|
||||||
|
|
||||||
[Tooltip("手动收线速度(米/秒),这里只是参考参数")] public float reelInSpeed = 1.5f;
|
|
||||||
|
|
||||||
[Tooltip("手动放线速度(米/秒),这里只是参考参数")] public float reelOutSpeed = 3f;
|
|
||||||
|
|
||||||
[Header("Line Tension")] [Tooltip("鱼线系统等效刚度。越大,拉直后越硬")]
|
|
||||||
public float lineStiffness = 120f;
|
|
||||||
|
|
||||||
[Tooltip("沿鱼线方向的阻尼。鱼外冲越快,附加张力越大")] public float lineDamping = 20f;
|
|
||||||
|
|
||||||
[Tooltip("接近绷直时保留的一点最小张力,避免完全没手感")] public float minTensionWhenNearTight = 1f;
|
|
||||||
|
|
||||||
[Tooltip("进入预张力区域的比例。比如0.95表示距离达到线长95%就开始有一点点手感")] [Range(0.7f, 1f)]
|
|
||||||
public float nearTightRatio = 0.95f;
|
|
||||||
|
|
||||||
[Header("Rod Buffer / Flex")] [Tooltip("鱼竿最大承载参考值。张力越接近这个值,竿越弯")]
|
|
||||||
public float rodMaxLoad = 40f;
|
|
||||||
|
|
||||||
[Tooltip("鱼竿最大缓冲长度(米)。竿越弯,等效可多“吃掉”一些长度")]
|
|
||||||
public float rodFlexMax = 0.35f;
|
|
||||||
|
|
||||||
[Header("Drag / Spool")] [Tooltip("绕线轮卸力阈值。张力超过它就开始自动出线")]
|
|
||||||
public float dragThreshold = 18f;
|
|
||||||
|
|
||||||
[Tooltip("超过卸力阈值后,每多1单位张力,对应的自动出线速度倍率")]
|
|
||||||
public float dragSpoolFactor = 0.08f;
|
|
||||||
|
|
||||||
[Header("Break / Damage")] [Tooltip("安全张力。超过它开始累计损伤")]
|
|
||||||
public float safeTension = 22f;
|
|
||||||
|
|
||||||
[Tooltip("绝对极限张力。超过它可以直接判定断线")] public float breakTension = 35f;
|
|
||||||
|
|
||||||
[Tooltip("超安全张力时的损伤累计速度倍率")] public float lineDamageRate = 1.5f;
|
|
||||||
|
|
||||||
[Tooltip("累计损伤达到此值后断线")] public float lineDamageLimit = 10f;
|
|
||||||
|
|
||||||
[Header("Fish Force")] [Tooltip("是否把张力反向施加给鱼刚体")]
|
|
||||||
public bool applyForceToFish = true;
|
|
||||||
|
|
||||||
[Tooltip("对鱼施加拉力时的倍率,用来调手感")] public float forceToFishScale = 1f;
|
|
||||||
|
|
||||||
[Header("Runtime Debug (Read Only)")] [SerializeField]
|
|
||||||
private float currentDistance;
|
|
||||||
|
|
||||||
[SerializeField] private float currentRelativeSpeedAlongLine;
|
|
||||||
[SerializeField] private float currentRodFlexOffset;
|
|
||||||
[SerializeField] private float currentOverStretch;
|
|
||||||
[SerializeField] private float currentTension;
|
|
||||||
[SerializeField] private float currentRodBend01;
|
|
||||||
[SerializeField] private float currentLineDamage;
|
|
||||||
[SerializeField] private bool isLineBroken;
|
|
||||||
[SerializeField] private bool isAutoSpooling;
|
|
||||||
|
|
||||||
/// <summary> 当前张力,对外只读 </summary>
|
|
||||||
public float CurrentTension => currentTension;
|
|
||||||
|
|
||||||
/// <summary> 当前鱼竿弯曲比例(0~1) </summary>
|
|
||||||
public float CurrentRodBend01 => currentRodBend01;
|
|
||||||
|
|
||||||
/// <summary> 当前是否断线 </summary>
|
|
||||||
public bool IsLineBroken => isLineBroken;
|
|
||||||
|
|
||||||
/// <summary> 当前累计损伤 </summary>
|
|
||||||
public float CurrentLineDamage => currentLineDamage;
|
|
||||||
|
|
||||||
private void Reset()
|
|
||||||
{
|
|
||||||
lineLength = 5f;
|
|
||||||
minLineLength = 0.5f;
|
|
||||||
maxLineLength = 100f;
|
|
||||||
|
|
||||||
lineStiffness = 120f;
|
|
||||||
lineDamping = 20f;
|
|
||||||
minTensionWhenNearTight = 1f;
|
|
||||||
nearTightRatio = 0.95f;
|
|
||||||
|
|
||||||
rodMaxLoad = 40f;
|
|
||||||
rodFlexMax = 0.35f;
|
|
||||||
|
|
||||||
dragThreshold = 18f;
|
|
||||||
dragSpoolFactor = 0.08f;
|
|
||||||
|
|
||||||
safeTension = 22f;
|
|
||||||
breakTension = 35f;
|
|
||||||
lineDamageRate = 1.5f;
|
|
||||||
lineDamageLimit = 10f;
|
|
||||||
|
|
||||||
applyForceToFish = true;
|
|
||||||
forceToFishScale = 1f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
// 这里只演示输入,实际项目你可能会改成输入系统控制
|
|
||||||
HandleManualLineInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FixedUpdate()
|
|
||||||
{
|
|
||||||
if (isLineBroken)
|
|
||||||
{
|
|
||||||
currentTension = 0f;
|
|
||||||
currentRodBend01 = 0f;
|
|
||||||
currentRodFlexOffset = 0f;
|
|
||||||
currentOverStretch = 0f;
|
|
||||||
currentRelativeSpeedAlongLine = 0f;
|
|
||||||
isAutoSpooling = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rodTip == null || fishTarget == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 1. 计算竿尖到鱼的几何关系
|
|
||||||
// --------------------------------------------------
|
|
||||||
Vector3 delta = fishTarget.position - rodTip.position;
|
|
||||||
float distance = delta.magnitude;
|
|
||||||
Vector3 lineDir = distance > 0.0001f ? delta / distance : Vector3.forward;
|
|
||||||
|
|
||||||
currentDistance = distance;
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 2. 计算沿鱼线方向的相对速度
|
|
||||||
// > 0 代表鱼在远离竿尖,拉力应增加
|
|
||||||
// < 0 代表鱼在靠近竿尖,拉力应减小
|
|
||||||
// --------------------------------------------------
|
|
||||||
Vector3 fishVel = fishRb != null ? fishRb.linearVelocity : Vector3.zero;
|
|
||||||
Vector3 tipVel = rodTipRb != null ? rodTipRb.linearVelocity : Vector3.zero;
|
|
||||||
|
|
||||||
float relativeSpeedAlongLine = Vector3.Dot(fishVel - tipVel, lineDir);
|
|
||||||
currentRelativeSpeedAlongLine = relativeSpeedAlongLine;
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 3. 根据“上一帧张力”估算当前鱼竿弯曲带来的缓冲长度
|
|
||||||
// 张力越大,竿越弯,可额外缓冲一些长度
|
|
||||||
// --------------------------------------------------
|
|
||||||
currentRodBend01 = rodMaxLoad > 0.0001f
|
|
||||||
? Mathf.Clamp01(currentTension / rodMaxLoad)
|
|
||||||
: 0f;
|
|
||||||
|
|
||||||
currentRodFlexOffset = currentRodBend01 * rodFlexMax;
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 4. 计算“超限量”
|
|
||||||
// 当距离 <= 线长 + 竿缓冲时,认为线没有真正被硬拉伸
|
|
||||||
// 当距离 > 线长 + 竿缓冲时,多出来的部分转化为张力
|
|
||||||
// --------------------------------------------------
|
|
||||||
float effectiveLength = lineLength + currentRodFlexOffset;
|
|
||||||
float overStretch = Mathf.Max(0f, distance - effectiveLength);
|
|
||||||
currentOverStretch = overStretch;
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 5. 计算基础张力
|
|
||||||
// T = 刚度项 + 阻尼项
|
|
||||||
// --------------------------------------------------
|
|
||||||
float tension = 0f;
|
|
||||||
|
|
||||||
// 刚度项:超过可承受长度才会产生明显张力
|
|
||||||
tension += lineStiffness * overStretch;
|
|
||||||
|
|
||||||
// 阻尼项:只有“往外冲”的速度才增加张力
|
|
||||||
if (relativeSpeedAlongLine > 0f)
|
|
||||||
{
|
|
||||||
tension += lineDamping * relativeSpeedAlongLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 6. 接近绷直时给一点最小预张力
|
|
||||||
// 避免 D 接近 lineLength 时,手感突然从0跳到有力
|
|
||||||
// --------------------------------------------------
|
|
||||||
float nearTightDistance = lineLength * nearTightRatio;
|
|
||||||
if (distance >= nearTightDistance)
|
|
||||||
{
|
|
||||||
tension = Mathf.Max(tension, minTensionWhenNearTight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 线完全松很多时,可直接视为无有效张力
|
|
||||||
if (distance < nearTightDistance && overStretch <= 0f)
|
|
||||||
{
|
|
||||||
tension = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTension = Mathf.Max(0f, tension);
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 7. 卸力:当张力超过绕线轮设定值时,自动出线
|
|
||||||
// 这样大鱼冲刺时不会硬顶到瞬间爆线
|
|
||||||
// --------------------------------------------------
|
|
||||||
isAutoSpooling = false;
|
|
||||||
|
|
||||||
if (currentTension > dragThreshold)
|
|
||||||
{
|
|
||||||
float extraTension = currentTension - dragThreshold;
|
|
||||||
float autoSpoolSpeed = extraTension * dragSpoolFactor;
|
|
||||||
|
|
||||||
lineLength += autoSpoolSpeed * Time.fixedDeltaTime;
|
|
||||||
lineLength = Mathf.Clamp(lineLength, minLineLength, maxLineLength);
|
|
||||||
|
|
||||||
isAutoSpooling = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 8. 断线逻辑
|
|
||||||
// 8.1 超过极限值:可直接断
|
|
||||||
// 8.2 超过安全值:持续累计损伤
|
|
||||||
// --------------------------------------------------
|
|
||||||
if (currentTension >= breakTension)
|
|
||||||
{
|
|
||||||
BreakLine();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentTension > safeTension)
|
|
||||||
{
|
|
||||||
float overload = currentTension - safeTension;
|
|
||||||
currentLineDamage += overload * lineDamageRate * Time.fixedDeltaTime;
|
|
||||||
|
|
||||||
if (currentLineDamage >= lineDamageLimit)
|
|
||||||
{
|
|
||||||
BreakLine();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 张力安全时,损伤缓慢恢复一点
|
|
||||||
currentLineDamage -= Time.fixedDeltaTime;
|
|
||||||
currentLineDamage = Mathf.Max(0f, currentLineDamage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 9. 把鱼线张力反向施加给鱼
|
|
||||||
// 鱼越往外冲,线张力越大,反向拉回鱼的力也越大
|
|
||||||
// --------------------------------------------------
|
|
||||||
if (applyForceToFish && fishRb != null && currentTension > 0f)
|
|
||||||
{
|
|
||||||
Vector3 pullForce = -lineDir * currentTension * forceToFishScale;
|
|
||||||
fishRb.AddForce(pullForce, ForceMode.Force);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 10. 重新根据当前张力更新鱼竿弯曲值(给外部表现层用)
|
|
||||||
// --------------------------------------------------
|
|
||||||
currentRodBend01 = rodMaxLoad > 0.0001f
|
|
||||||
? Mathf.Clamp01(currentTension / rodMaxLoad)
|
|
||||||
: 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 示例输入:
|
|
||||||
/// R = 收线
|
|
||||||
/// F = 放线
|
|
||||||
/// 实际项目建议接你自己的输入系统
|
|
||||||
/// </summary>
|
|
||||||
private void HandleManualLineInput()
|
|
||||||
{
|
|
||||||
if (isLineBroken)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Input.GetKey(KeyCode.R))
|
|
||||||
{
|
|
||||||
lineLength -= reelInSpeed * Time.deltaTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Input.GetKey(KeyCode.F))
|
|
||||||
{
|
|
||||||
lineLength += reelOutSpeed * Time.deltaTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
lineLength = Mathf.Clamp(lineLength, minLineLength, maxLineLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 断线
|
|
||||||
/// </summary>
|
|
||||||
private void BreakLine()
|
|
||||||
{
|
|
||||||
isLineBroken = true;
|
|
||||||
currentTension = 0f;
|
|
||||||
currentRodBend01 = 0f;
|
|
||||||
currentRodFlexOffset = 0f;
|
|
||||||
currentOverStretch = 0f;
|
|
||||||
isAutoSpooling = false;
|
|
||||||
|
|
||||||
Debug.Log("鱼线断了");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 外部调用:修复鱼线
|
|
||||||
/// </summary>
|
|
||||||
public void RepairLine(float repairedLength)
|
|
||||||
{
|
|
||||||
isLineBroken = false;
|
|
||||||
currentLineDamage = 0f;
|
|
||||||
lineLength = Mathf.Clamp(repairedLength, minLineLength, maxLineLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 外部调用:直接设置线长
|
|
||||||
/// </summary>
|
|
||||||
public void SetLineLength(float length)
|
|
||||||
{
|
|
||||||
lineLength = Mathf.Clamp(length, minLineLength, maxLineLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 1a6c0b4a2030426c807730fe3837b9b9
|
|
||||||
timeCreated: 1775379112
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: bc8dcefa76944b33bc63ffe5f0280067
|
|
||||||
timeCreated: 1774185300
|
|
||||||
@@ -1,936 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
|
|
||||||
using WaveHarmonic.Crest;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 简单水面接口。你可以替换成自己的水系统。
|
|
||||||
/// </summary>
|
|
||||||
public interface IWaterSurfaceProvider
|
|
||||||
{
|
|
||||||
float GetWaterHeight(Vector3 worldPos);
|
|
||||||
Vector3 GetWaterNormal(Vector3 worldPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 浮漂控制模式:
|
|
||||||
/// 1. AirPhysics:空中/未入水,使用刚体物理
|
|
||||||
/// 2. WaterPresentation:入水后,关闭重力,Y 和旋转由脚本控制
|
|
||||||
/// </summary>
|
|
||||||
public enum BobberControlMode
|
|
||||||
{
|
|
||||||
AirPhysics,
|
|
||||||
WaterPresentation
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 漂像事件类型
|
|
||||||
/// </summary>
|
|
||||||
public enum BobberBiteType
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
Tap, // 轻点
|
|
||||||
SlowSink, // 缓沉
|
|
||||||
Lift, // 送漂
|
|
||||||
BlackDrift // 黑漂/快速拖入
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BobberPosture
|
|
||||||
{
|
|
||||||
Lying,
|
|
||||||
Tilted,
|
|
||||||
Upright
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisallowMultipleComponent]
|
|
||||||
[RequireComponent(typeof(Rigidbody))]
|
|
||||||
public class BobberPresentationController : MonoBehaviour
|
|
||||||
{
|
|
||||||
[Header("Water")] [Tooltip("没有水提供器时使用固定水位")]
|
|
||||||
public float fallbackWaterLevel = 0f;
|
|
||||||
|
|
||||||
[Tooltip("Crest 水体。为空时会尝试从 SceneSettings 读取")]
|
|
||||||
public WaterRenderer waterRenderer;
|
|
||||||
|
|
||||||
[Tooltip("Crest 查询层级")] public CollisionLayer waterCollisionLayer = CollisionLayer.AfterAnimatedWaves;
|
|
||||||
|
|
||||||
[Tooltip("Crest 波面查询宽度(参考 BobberFloating)")]
|
|
||||||
public float waterQueryObjectWidth = 0.5f;
|
|
||||||
|
|
||||||
[Tooltip("可选:挂实现了 IWaterSurfaceProvider 的组件")]
|
|
||||||
public MonoBehaviour waterProviderBehaviour;
|
|
||||||
|
|
||||||
[Header("Enter Water")] [Tooltip("底部进入水面多少米后切换为漂像控制")]
|
|
||||||
public float enterWaterDepth = 0.002f;
|
|
||||||
|
|
||||||
[Tooltip("离开水面多少米后回到空中物理。一般给负值做滞回")] public float exitWaterDepth = -0.01f;
|
|
||||||
|
|
||||||
[Header("Geometry")] [Tooltip("浮漂总高度(米)")]
|
|
||||||
public float floatHeight = 0.08f;
|
|
||||||
|
|
||||||
[Tooltip("如果 Pivot 在浮漂底部,这里填 0;如果 Pivot 在模型中心,就填底部相对 Pivot 的本地 Y")]
|
|
||||||
public float bottomOffsetLocalY = 0f;
|
|
||||||
|
|
||||||
[Header("Base Float")] [Tooltip("基础吃铅比例,决定静止时有多少在水下")] [Range(0.05f, 0.95f)]
|
|
||||||
public float baseSubmergeRatio = 0.28f;
|
|
||||||
|
|
||||||
[Tooltip("Y 轴平滑时间,越小响应越快")] public float ySmoothTime = 0.08f;
|
|
||||||
|
|
||||||
[Tooltip("最大竖直速度限制(用于 SmoothDamp)")] public float maxYSpeed = 2f;
|
|
||||||
|
|
||||||
[Tooltip("静止小死区,减少微抖")] public float yDeadZone = 0.0005f;
|
|
||||||
|
|
||||||
[Header("Surface Motion")] [Tooltip("是否启用轻微水面起伏")]
|
|
||||||
public bool enableSurfaceBobbing = true;
|
|
||||||
|
|
||||||
[Tooltip("水面轻微起伏振幅(米)")] public float surfaceBobAmplitude = 0.0015f;
|
|
||||||
|
|
||||||
[Tooltip("水面轻微起伏频率")] public float surfaceBobFrequency = 1.2f;
|
|
||||||
|
|
||||||
[Header("XZ Motion")] [Tooltip("入水后是否锁定 XZ 到入水点附近")]
|
|
||||||
public bool lockXZAroundAnchor = true;
|
|
||||||
|
|
||||||
[Tooltip("XZ 跟随平滑时间")] public float xzSmoothTime = 0.15f;
|
|
||||||
|
|
||||||
[Tooltip("水流/拖拽带来的额外平面偏移最大值")] public float maxPlanarOffset = 0.15f;
|
|
||||||
|
|
||||||
[Header("Sink By Weight / Tension")] [Tooltip("外部向下拉力映射为下沉量的系数。你可以把钩/铅/线组的等效向下拉力喂进来")]
|
|
||||||
public float downForceToSink = 0.0025f;
|
|
||||||
|
|
||||||
[Tooltip("向下拉力下沉的最大附加量")] public float maxExtraSink = 0.08f;
|
|
||||||
|
|
||||||
[Header("Bottom Touch")] [Tooltip("触底时是否启用修正")]
|
|
||||||
public bool enableBottomTouchAdjust = true;
|
|
||||||
|
|
||||||
[Tooltip("触底后减少的下沉量(例如铅坠到底,漂会回升一点)")] public float bottomTouchLift = 0.01f;
|
|
||||||
|
|
||||||
[Header("Posture Source")] [Tooltip("下方 Lure / 钩组 / 铅坠的刚体。姿态主要根据它和浮漂的相对位置判断")]
|
|
||||||
public Rigidbody lureBody;
|
|
||||||
|
|
||||||
[Tooltip("用于归一化的参考长度。一般填:浮漂到 Lure 在“正常拉直”时的大致长度")]
|
|
||||||
public float referenceLength = 0.30f;
|
|
||||||
|
|
||||||
[Header("Posture Threshold")] [Tooltip("最小入水比例。不够时优先躺漂")]
|
|
||||||
public float minSubmergeToStand = 0.16f;
|
|
||||||
|
|
||||||
[Tooltip("垂直分量比低于该值时,优先躺漂")] public float verticalLieThreshold = 0.18f;
|
|
||||||
|
|
||||||
[Tooltip("垂直分量比高于该值,且水平分量较小时,允许立漂")] public float verticalUprightThreshold = 0.75f;
|
|
||||||
|
|
||||||
[Tooltip("水平分量比高于该值时,不允许完全立漂")] public float planarTiltThreshold = 0.30f;
|
|
||||||
|
|
||||||
[Tooltip("水平分量明显大于垂直分量时,优先躺漂")] public float planarDominanceMultiplier = 1.20f;
|
|
||||||
|
|
||||||
[Tooltip("姿态切换滞回")] public float postureHysteresis = 0.04f;
|
|
||||||
|
|
||||||
[Header("Posture Stability")] [Tooltip("候选姿态需持续多久才真正切换")]
|
|
||||||
public float postureConfirmTime = 0.08f;
|
|
||||||
|
|
||||||
[Tooltip("姿态切换后的最短冷却时间,避免来回闪烁")]
|
|
||||||
public float postureSwitchCooldown = 0.10f;
|
|
||||||
|
|
||||||
[Header("Posture Rotation")] [Tooltip("倾斜状态角度")]
|
|
||||||
public float tiltedAngle = 38f;
|
|
||||||
|
|
||||||
[Tooltip("躺漂角度")] public float lyingAngle = 88f;
|
|
||||||
|
|
||||||
[Tooltip("立漂时允许的最大附加倾角")] public float uprightMaxTiltAngle = 8f;
|
|
||||||
|
|
||||||
[Tooltip("平面方向对立漂/斜漂附加倾角的影响强度")] public float planarTiltFactor = 120f;
|
|
||||||
|
|
||||||
[Tooltip("平面方向死区,小于该值时保持上一帧方向")] public float planarDirectionDeadZone = 0.01f;
|
|
||||||
|
|
||||||
[Tooltip("平面方向平滑速度")] public float planarDirectionLerpSpeed = 10f;
|
|
||||||
|
|
||||||
[Tooltip("姿态平滑速度")] public float rotationLerpSpeed = 8f;
|
|
||||||
|
|
||||||
[Header("Debug Input")] [Tooltip("调试:按 R 恢复默认")]
|
|
||||||
public bool debugResetKey = true;
|
|
||||||
|
|
||||||
[Tooltip("调试:按 T 触发轻点")] public bool debugTapKey = true;
|
|
||||||
|
|
||||||
[Tooltip("调试:按 G 触发缓沉")] public bool debugSlowSinkKey = true;
|
|
||||||
|
|
||||||
[Tooltip("调试:按 H 触发送漂")] public bool debugLiftKey = true;
|
|
||||||
|
|
||||||
[Tooltip("调试:按 B 触发黑漂")] public bool debugBlackDriftKey = true;
|
|
||||||
|
|
||||||
[Header("Debug")] public bool drawDebug = false;
|
|
||||||
|
|
||||||
public bool UseTestPosture;
|
|
||||||
public BobberPosture TestPosture;
|
|
||||||
|
|
||||||
public BobberControlMode CurrentMode => _mode;
|
|
||||||
public BobberPosture CurrentPosture => _posture;
|
|
||||||
public float CurrentVerticalRatio => _verticalRatio;
|
|
||||||
public float CurrentPlanarRatio => _planarRatio;
|
|
||||||
|
|
||||||
/// <summary>外部可写:等效向下拉力(不是必须是真实力,作为输入信号即可)</summary>
|
|
||||||
public float ExternalDownForce { get; set; }
|
|
||||||
|
|
||||||
/// <summary>外部可写:是否触底</summary>
|
|
||||||
public bool IsBottomTouched { get; set; }
|
|
||||||
|
|
||||||
/// <summary>外部可写:额外平面偏移(例如风、水流、拖拽)</summary>
|
|
||||||
public Vector2 ExternalPlanarOffset { get; set; }
|
|
||||||
|
|
||||||
private Rigidbody _rb;
|
|
||||||
private IWaterSurfaceProvider _waterProvider;
|
|
||||||
private BobberControlMode _mode = BobberControlMode.AirPhysics;
|
|
||||||
private BobberPosture _posture = BobberPosture.Lying;
|
|
||||||
|
|
||||||
private float _defaultLinearDamping;
|
|
||||||
private float _defaultAngularDamping;
|
|
||||||
private bool _defaultUseGravity;
|
|
||||||
|
|
||||||
private Vector3 _waterAnchorPos;
|
|
||||||
private Vector3 _xzSmoothVelocity;
|
|
||||||
private float _ySmoothVelocity;
|
|
||||||
|
|
||||||
private float _biteOffsetY;
|
|
||||||
private float _biteOffsetYVelocity;
|
|
||||||
|
|
||||||
private Quaternion _targetRotation;
|
|
||||||
|
|
||||||
// bite event runtime
|
|
||||||
private BobberBiteType _activeBiteType = BobberBiteType.None;
|
|
||||||
private float _biteTimer;
|
|
||||||
private float _biteDuration;
|
|
||||||
private float _biteAmplitude;
|
|
||||||
private Vector3 _blackDriftDirection;
|
|
||||||
|
|
||||||
// posture runtime
|
|
||||||
private float _verticalRatio;
|
|
||||||
private float _planarRatio;
|
|
||||||
private float _verticalDistance;
|
|
||||||
private float _planarDistance;
|
|
||||||
private BobberPosture _pendingPosture;
|
|
||||||
private float _pendingPostureTimer;
|
|
||||||
private float _postureCooldownTimer;
|
|
||||||
private Vector3 _stablePlanarDir = Vector3.forward;
|
|
||||||
|
|
||||||
private bool _hasCrestSampleThisFrame;
|
|
||||||
private readonly Vector3[] _waterQueryPoints = new Vector3[1];
|
|
||||||
private readonly Vector3[] _waterQueryResultDisplacements = new Vector3[1];
|
|
||||||
private readonly Vector3[] _waterQueryResultVelocities = new Vector3[1];
|
|
||||||
private readonly Vector3[] _waterQueryResultNormal = new Vector3[1];
|
|
||||||
|
|
||||||
private void Awake()
|
|
||||||
{
|
|
||||||
_rb = GetComponent<Rigidbody>();
|
|
||||||
|
|
||||||
_defaultLinearDamping = _rb.linearDamping;
|
|
||||||
_defaultAngularDamping = _rb.angularDamping;
|
|
||||||
_defaultUseGravity = _rb.useGravity;
|
|
||||||
|
|
||||||
if (waterProviderBehaviour != null)
|
|
||||||
_waterProvider = waterProviderBehaviour as IWaterSurfaceProvider;
|
|
||||||
|
|
||||||
if (waterRenderer == null && SceneSettings.Instance != null)
|
|
||||||
waterRenderer = SceneSettings.Instance.Water;
|
|
||||||
|
|
||||||
_pendingPosture = _posture;
|
|
||||||
_pendingPostureTimer = 0f;
|
|
||||||
_postureCooldownTimer = 0f;
|
|
||||||
_stablePlanarDir = Vector3.ProjectOnPlane(transform.forward, Vector3.up);
|
|
||||||
if (_stablePlanarDir.sqrMagnitude < 1e-6f)
|
|
||||||
_stablePlanarDir = Vector3.forward;
|
|
||||||
else
|
|
||||||
_stablePlanarDir.Normalize();
|
|
||||||
|
|
||||||
_targetRotation = transform.rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
HandleDebugKeys();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FixedUpdate()
|
|
||||||
{
|
|
||||||
float waterY = GetWaterHeight(transform.position);
|
|
||||||
Vector3 bottomWorld = GetBottomWorldPosition();
|
|
||||||
float submergeDepth = waterY - bottomWorld.y;
|
|
||||||
|
|
||||||
switch (_mode)
|
|
||||||
{
|
|
||||||
case BobberControlMode.AirPhysics:
|
|
||||||
UpdateAirPhysics(submergeDepth);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BobberControlMode.WaterPresentation:
|
|
||||||
UpdateWaterPresentation(waterY, submergeDepth);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawDebug)
|
|
||||||
{
|
|
||||||
DrawDebug(waterY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Main Update
|
|
||||||
|
|
||||||
private void UpdateAirPhysics(float submergeDepth)
|
|
||||||
{
|
|
||||||
RestoreAirPhysicsState();
|
|
||||||
|
|
||||||
if (submergeDepth > enterWaterDepth)
|
|
||||||
{
|
|
||||||
EnterWaterPresentationMode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateWaterPresentation(float waterY, float submergeDepth)
|
|
||||||
{
|
|
||||||
if (submergeDepth < exitWaterDepth)
|
|
||||||
{
|
|
||||||
ExitWaterPresentationMode();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 完全关闭刚体干扰
|
|
||||||
_rb.useGravity = false;
|
|
||||||
_rb.linearVelocity = Vector3.zero;
|
|
||||||
_rb.angularVelocity = Vector3.zero;
|
|
||||||
_rb.linearDamping = 999f;
|
|
||||||
_rb.angularDamping = 999f;
|
|
||||||
|
|
||||||
UpdateBiteAnimation();
|
|
||||||
|
|
||||||
Vector3 pos = transform.position;
|
|
||||||
|
|
||||||
// 1. 算目标 Y
|
|
||||||
float targetY = CalculateTargetY(waterY);
|
|
||||||
|
|
||||||
if (Mathf.Abs(pos.y - targetY) < yDeadZone)
|
|
||||||
{
|
|
||||||
pos.y = targetY;
|
|
||||||
_ySmoothVelocity = 0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pos.y = Mathf.SmoothDamp(
|
|
||||||
current: pos.y,
|
|
||||||
target: targetY,
|
|
||||||
currentVelocity: ref _ySmoothVelocity,
|
|
||||||
smoothTime: Mathf.Max(0.0001f, ySmoothTime),
|
|
||||||
maxSpeed: maxYSpeed,
|
|
||||||
deltaTime: Time.fixedDeltaTime
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 算目标 XZ
|
|
||||||
Vector3 targetXZ = CalculateTargetXZ();
|
|
||||||
Vector3 planarPos = new Vector3(pos.x, 0f, pos.z);
|
|
||||||
Vector3 planarTarget = new Vector3(targetXZ.x, 0f, targetXZ.z);
|
|
||||||
|
|
||||||
planarPos = Vector3.SmoothDamp(
|
|
||||||
planarPos,
|
|
||||||
planarTarget,
|
|
||||||
ref _xzSmoothVelocity,
|
|
||||||
Mathf.Max(0.0001f, xzSmoothTime),
|
|
||||||
Mathf.Infinity,
|
|
||||||
Time.fixedDeltaTime
|
|
||||||
);
|
|
||||||
|
|
||||||
pos.x = planarPos.x;
|
|
||||||
pos.z = planarPos.z;
|
|
||||||
|
|
||||||
transform.position = pos;
|
|
||||||
|
|
||||||
// 3. 姿态判定 + 目标旋转
|
|
||||||
EvaluatePostureByComponents(waterY);
|
|
||||||
UpdateTargetRotationByPosture();
|
|
||||||
|
|
||||||
transform.rotation = Quaternion.Slerp(
|
|
||||||
transform.rotation,
|
|
||||||
_targetRotation,
|
|
||||||
1f - Mathf.Exp(-rotationLerpSpeed * Time.fixedDeltaTime)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Mode Switch
|
|
||||||
|
|
||||||
private void EnterWaterPresentationMode()
|
|
||||||
{
|
|
||||||
_mode = BobberControlMode.WaterPresentation;
|
|
||||||
|
|
||||||
_waterAnchorPos = transform.position;
|
|
||||||
_ySmoothVelocity = 0f;
|
|
||||||
_xzSmoothVelocity = Vector3.zero;
|
|
||||||
_biteOffsetY = 0f;
|
|
||||||
_biteOffsetYVelocity = 0f;
|
|
||||||
_activeBiteType = BobberBiteType.None;
|
|
||||||
_biteTimer = 0f;
|
|
||||||
|
|
||||||
_posture = BobberPosture.Lying;
|
|
||||||
_verticalRatio = 0f;
|
|
||||||
_planarRatio = 0f;
|
|
||||||
_verticalDistance = 0f;
|
|
||||||
_planarDistance = 0f;
|
|
||||||
_pendingPosture = _posture;
|
|
||||||
_pendingPostureTimer = 0f;
|
|
||||||
_postureCooldownTimer = 0f;
|
|
||||||
_stablePlanarDir = Vector3.ProjectOnPlane(transform.forward, Vector3.up);
|
|
||||||
if (_stablePlanarDir.sqrMagnitude < 1e-6f)
|
|
||||||
_stablePlanarDir = Vector3.forward;
|
|
||||||
else
|
|
||||||
_stablePlanarDir.Normalize();
|
|
||||||
|
|
||||||
_rb.useGravity = false;
|
|
||||||
_rb.linearVelocity = Vector3.zero;
|
|
||||||
_rb.angularVelocity = Vector3.zero;
|
|
||||||
_rb.linearDamping = 999f;
|
|
||||||
_rb.angularDamping = 999f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExitWaterPresentationMode()
|
|
||||||
{
|
|
||||||
_mode = BobberControlMode.AirPhysics;
|
|
||||||
RestoreAirPhysicsState();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RestoreAirPhysicsState()
|
|
||||||
{
|
|
||||||
_rb.useGravity = _defaultUseGravity;
|
|
||||||
_rb.linearDamping = _defaultLinearDamping;
|
|
||||||
_rb.angularDamping = _defaultAngularDamping;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Target Calculation
|
|
||||||
|
|
||||||
private float CalculateTargetY(float waterY)
|
|
||||||
{
|
|
||||||
float baseSinkDepth = floatHeight * Mathf.Clamp01(baseSubmergeRatio);
|
|
||||||
|
|
||||||
float sinkByForce = Mathf.Clamp(
|
|
||||||
ExternalDownForce * downForceToSink,
|
|
||||||
0f,
|
|
||||||
maxExtraSink
|
|
||||||
);
|
|
||||||
|
|
||||||
float bottomAdjust = 0f;
|
|
||||||
if (enableBottomTouchAdjust && IsBottomTouched)
|
|
||||||
{
|
|
||||||
bottomAdjust -= bottomTouchLift;
|
|
||||||
}
|
|
||||||
|
|
||||||
float surfaceBob = 0f;
|
|
||||||
if (enableSurfaceBobbing && !_hasCrestSampleThisFrame)
|
|
||||||
{
|
|
||||||
surfaceBob = Mathf.Sin(Time.time * surfaceBobFrequency * Mathf.PI * 2f) * surfaceBobAmplitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
float totalSink = baseSinkDepth + sinkByForce + bottomAdjust;
|
|
||||||
float targetBottomY = waterY - totalSink;
|
|
||||||
float targetPivotY = targetBottomY - bottomOffsetLocalY + surfaceBob + _biteOffsetY;
|
|
||||||
|
|
||||||
return targetPivotY;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector3 CalculateTargetXZ()
|
|
||||||
{
|
|
||||||
Vector2 planarOffset = Vector2.ClampMagnitude(ExternalPlanarOffset, maxPlanarOffset);
|
|
||||||
|
|
||||||
Vector3 basePos = lockXZAroundAnchor ? _waterAnchorPos : transform.position;
|
|
||||||
|
|
||||||
if (_activeBiteType == BobberBiteType.BlackDrift)
|
|
||||||
{
|
|
||||||
float t = Mathf.Clamp01(_biteDuration > 0f ? _biteTimer / _biteDuration : 1f);
|
|
||||||
float drift = Mathf.SmoothStep(0f, 1f, t) * 0.08f;
|
|
||||||
Vector3 blackDrift = _blackDriftDirection * drift;
|
|
||||||
basePos += new Vector3(blackDrift.x, 0f, blackDrift.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Vector3(
|
|
||||||
basePos.x + planarOffset.x,
|
|
||||||
transform.position.y,
|
|
||||||
basePos.z + planarOffset.y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EvaluatePostureByComponents(float waterY)
|
|
||||||
{
|
|
||||||
float submergeRatio = Mathf.Clamp01(
|
|
||||||
(waterY - GetBottomWorldPosition().y) / Mathf.Max(0.0001f, floatHeight)
|
|
||||||
);
|
|
||||||
|
|
||||||
bool hasLure = lureBody != null;
|
|
||||||
|
|
||||||
if (lureBody == null)
|
|
||||||
{
|
|
||||||
_verticalDistance = 0f;
|
|
||||||
_planarDistance = 0f;
|
|
||||||
_verticalRatio = 0f;
|
|
||||||
_planarRatio = 0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Vector3 bobberPos = _rb.worldCenterOfMass;
|
|
||||||
Vector3 lurePos = lureBody.worldCenterOfMass;
|
|
||||||
Vector3 delta = lurePos - bobberPos;
|
|
||||||
|
|
||||||
_verticalDistance = Mathf.Max(0f, Vector3.Dot(delta, Vector3.down));
|
|
||||||
_planarDistance = Vector3.ProjectOnPlane(delta, Vector3.up).magnitude;
|
|
||||||
|
|
||||||
float refLen = Mathf.Max(0.0001f, referenceLength);
|
|
||||||
_verticalRatio = _verticalDistance / refLen;
|
|
||||||
_planarRatio = _planarDistance / refLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
BobberPosture desiredPosture = DeterminePostureState(submergeRatio, hasLure);
|
|
||||||
ApplyPostureWithStability(desiredPosture);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 只在这个函数里写姿态状态判断。
|
|
||||||
/// 你后续要改逻辑,改这里即可,外面只负责采样数据和旋转平滑。
|
|
||||||
/// </summary>
|
|
||||||
private BobberPosture DeterminePostureState(float submergeRatio, bool hasLure)
|
|
||||||
{
|
|
||||||
if (UseTestPosture)
|
|
||||||
{
|
|
||||||
return TestPosture;
|
|
||||||
}
|
|
||||||
// 没有 Lure 时,保留简单兜底规则。
|
|
||||||
if (!hasLure)
|
|
||||||
{
|
|
||||||
if (submergeRatio < minSubmergeToStand)
|
|
||||||
return BobberPosture.Lying;
|
|
||||||
|
|
||||||
if (ExternalPlanarOffset.magnitude > 0.01f)
|
|
||||||
return BobberPosture.Tilted;
|
|
||||||
|
|
||||||
return BobberPosture.Upright;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 这里是完整状态判断入口(可按你的需求自由改)。
|
|
||||||
switch (_posture)
|
|
||||||
{
|
|
||||||
case BobberPosture.Lying:
|
|
||||||
{
|
|
||||||
bool canStandUpright =
|
|
||||||
submergeRatio >= minSubmergeToStand &&
|
|
||||||
_verticalRatio > verticalUprightThreshold + postureHysteresis &&
|
|
||||||
_planarRatio < planarTiltThreshold - postureHysteresis;
|
|
||||||
|
|
||||||
bool canTilt =
|
|
||||||
submergeRatio >= minSubmergeToStand * 0.8f &&
|
|
||||||
_verticalRatio > verticalLieThreshold + postureHysteresis;
|
|
||||||
|
|
||||||
if (canStandUpright)
|
|
||||||
return BobberPosture.Upright;
|
|
||||||
if (canTilt)
|
|
||||||
return BobberPosture.Tilted;
|
|
||||||
return BobberPosture.Lying;
|
|
||||||
}
|
|
||||||
|
|
||||||
case BobberPosture.Tilted:
|
|
||||||
{
|
|
||||||
bool shouldLie =
|
|
||||||
submergeRatio < minSubmergeToStand * 0.75f ||
|
|
||||||
_verticalRatio < verticalLieThreshold - postureHysteresis ||
|
|
||||||
_planarDistance > _verticalDistance * planarDominanceMultiplier;
|
|
||||||
|
|
||||||
bool shouldStand =
|
|
||||||
submergeRatio >= minSubmergeToStand &&
|
|
||||||
_verticalRatio > verticalUprightThreshold + postureHysteresis &&
|
|
||||||
_planarRatio < planarTiltThreshold - postureHysteresis;
|
|
||||||
|
|
||||||
if (shouldLie)
|
|
||||||
return BobberPosture.Lying;
|
|
||||||
if (shouldStand)
|
|
||||||
return BobberPosture.Upright;
|
|
||||||
return BobberPosture.Tilted;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
bool shouldLie =
|
|
||||||
submergeRatio < minSubmergeToStand * 0.75f ||
|
|
||||||
_verticalRatio < verticalLieThreshold - postureHysteresis ||
|
|
||||||
_planarDistance > _verticalDistance * (planarDominanceMultiplier + 0.15f);
|
|
||||||
|
|
||||||
bool shouldTilt =
|
|
||||||
_verticalRatio < verticalUprightThreshold - postureHysteresis ||
|
|
||||||
_planarRatio > planarTiltThreshold + postureHysteresis;
|
|
||||||
|
|
||||||
if (shouldLie)
|
|
||||||
return BobberPosture.Lying;
|
|
||||||
if (shouldTilt)
|
|
||||||
return BobberPosture.Tilted;
|
|
||||||
return BobberPosture.Upright;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyPostureWithStability(BobberPosture desiredPosture)
|
|
||||||
{
|
|
||||||
_postureCooldownTimer = Mathf.Max(0f, _postureCooldownTimer - Time.fixedDeltaTime);
|
|
||||||
|
|
||||||
if (desiredPosture == _posture)
|
|
||||||
{
|
|
||||||
_pendingPosture = _posture;
|
|
||||||
_pendingPostureTimer = 0f;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_postureCooldownTimer > 0f)
|
|
||||||
{
|
|
||||||
_pendingPosture = desiredPosture;
|
|
||||||
_pendingPostureTimer = 0f;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_pendingPosture != desiredPosture)
|
|
||||||
{
|
|
||||||
_pendingPosture = desiredPosture;
|
|
||||||
_pendingPostureTimer = 0f;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pendingPostureTimer += Time.fixedDeltaTime;
|
|
||||||
if (_pendingPostureTimer >= Mathf.Max(0f, postureConfirmTime))
|
|
||||||
{
|
|
||||||
_posture = desiredPosture;
|
|
||||||
_pendingPosture = _posture;
|
|
||||||
_pendingPostureTimer = 0f;
|
|
||||||
_postureCooldownTimer = Mathf.Max(0f, postureSwitchCooldown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateTargetRotationByPosture()
|
|
||||||
{
|
|
||||||
Vector3 candidateDir = Vector3.zero;
|
|
||||||
|
|
||||||
if (lureBody != null)
|
|
||||||
{
|
|
||||||
Vector3 delta = lureBody.worldCenterOfMass - _rb.worldCenterOfMass;
|
|
||||||
candidateDir = Vector3.ProjectOnPlane(delta, Vector3.up);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidateDir.sqrMagnitude < 1e-6f)
|
|
||||||
{
|
|
||||||
candidateDir = new Vector3(_xzSmoothVelocity.x, 0f, _xzSmoothVelocity.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidateDir.sqrMagnitude < 1e-6f)
|
|
||||||
{
|
|
||||||
candidateDir = new Vector3(ExternalPlanarOffset.x, 0f, ExternalPlanarOffset.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_stablePlanarDir.sqrMagnitude < 1e-6f)
|
|
||||||
{
|
|
||||||
_stablePlanarDir = Vector3.ProjectOnPlane(transform.forward, Vector3.up);
|
|
||||||
if (_stablePlanarDir.sqrMagnitude < 1e-6f)
|
|
||||||
_stablePlanarDir = Vector3.forward;
|
|
||||||
}
|
|
||||||
_stablePlanarDir.Normalize();
|
|
||||||
|
|
||||||
float dirDeadZone = Mathf.Max(0.0001f, planarDirectionDeadZone);
|
|
||||||
if (candidateDir.sqrMagnitude > dirDeadZone * dirDeadZone)
|
|
||||||
{
|
|
||||||
candidateDir.Normalize();
|
|
||||||
|
|
||||||
// 保持与上一帧同向,避免 180 度翻转造成左右闪。
|
|
||||||
if (Vector3.Dot(candidateDir, _stablePlanarDir) < 0f)
|
|
||||||
candidateDir = -candidateDir;
|
|
||||||
|
|
||||||
float k = 1f - Mathf.Exp(-Mathf.Max(0.01f, planarDirectionLerpSpeed) * Time.fixedDeltaTime);
|
|
||||||
_stablePlanarDir = Vector3.Slerp(_stablePlanarDir, candidateDir, k);
|
|
||||||
_stablePlanarDir.Normalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 planarDir = _stablePlanarDir;
|
|
||||||
|
|
||||||
Vector3 tiltAxis = Vector3.Cross(Vector3.up, planarDir);
|
|
||||||
if (tiltAxis.sqrMagnitude < 1e-6f)
|
|
||||||
{
|
|
||||||
tiltAxis = transform.right;
|
|
||||||
}
|
|
||||||
|
|
||||||
float angle = _posture switch
|
|
||||||
{
|
|
||||||
BobberPosture.Lying => lyingAngle,
|
|
||||||
BobberPosture.Tilted => tiltedAngle,
|
|
||||||
_ => 0f
|
|
||||||
};
|
|
||||||
|
|
||||||
_targetRotation = Quaternion.AngleAxis(angle, tiltAxis.normalized);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Bite Presentation
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 轻点:快速下顿再回弹
|
|
||||||
/// </summary>
|
|
||||||
public void PlayTap(float amplitude = 0.008f, float duration = 0.18f)
|
|
||||||
{
|
|
||||||
StartBite(BobberBiteType.Tap, amplitude, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 缓沉:在持续时间内逐渐下沉
|
|
||||||
/// </summary>
|
|
||||||
public void PlaySlowSink(float amplitude = 0.025f, float duration = 1.2f)
|
|
||||||
{
|
|
||||||
StartBite(BobberBiteType.SlowSink, amplitude, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 送漂:向上抬
|
|
||||||
/// </summary>
|
|
||||||
public void PlayLift(float amplitude = 0.015f, float duration = 1.2f)
|
|
||||||
{
|
|
||||||
StartBite(BobberBiteType.Lift, amplitude, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 黑漂:快速下沉,并可配合平面拖拽
|
|
||||||
/// </summary>
|
|
||||||
public void PlayBlackDrift(float amplitude = 0.06f, float duration = 0.8f, Vector3? driftDirection = null)
|
|
||||||
{
|
|
||||||
StartBite(BobberBiteType.BlackDrift, amplitude, duration);
|
|
||||||
_blackDriftDirection = (driftDirection ?? transform.forward).normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopBite()
|
|
||||||
{
|
|
||||||
_activeBiteType = BobberBiteType.None;
|
|
||||||
_biteTimer = 0f;
|
|
||||||
_biteDuration = 0f;
|
|
||||||
_biteAmplitude = 0f;
|
|
||||||
_biteOffsetY = 0f;
|
|
||||||
_biteOffsetYVelocity = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartBite(BobberBiteType type, float amplitude, float duration)
|
|
||||||
{
|
|
||||||
if (_mode != BobberControlMode.WaterPresentation)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_activeBiteType = type;
|
|
||||||
_biteTimer = 0f;
|
|
||||||
_biteDuration = Mathf.Max(0.01f, duration);
|
|
||||||
_biteAmplitude = amplitude;
|
|
||||||
_biteOffsetYVelocity = 0f;
|
|
||||||
|
|
||||||
if (type == BobberBiteType.BlackDrift && _blackDriftDirection.sqrMagnitude < 1e-6f)
|
|
||||||
{
|
|
||||||
_blackDriftDirection =
|
|
||||||
transform.forward.sqrMagnitude > 1e-6f ? transform.forward.normalized : Vector3.forward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBiteAnimation()
|
|
||||||
{
|
|
||||||
if (_activeBiteType == BobberBiteType.None)
|
|
||||||
{
|
|
||||||
_biteOffsetY = Mathf.SmoothDamp(
|
|
||||||
_biteOffsetY,
|
|
||||||
0f,
|
|
||||||
ref _biteOffsetYVelocity,
|
|
||||||
0.08f,
|
|
||||||
Mathf.Infinity,
|
|
||||||
Time.fixedDeltaTime
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_biteTimer += Time.fixedDeltaTime;
|
|
||||||
float t = Mathf.Clamp01(_biteTimer / _biteDuration);
|
|
||||||
|
|
||||||
float targetOffset = 0f;
|
|
||||||
|
|
||||||
switch (_activeBiteType)
|
|
||||||
{
|
|
||||||
case BobberBiteType.Tap:
|
|
||||||
if (t < 0.35f)
|
|
||||||
{
|
|
||||||
float k = t / 0.35f;
|
|
||||||
targetOffset = -Mathf.SmoothStep(0f, _biteAmplitude, k);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
float k = (t - 0.35f) / 0.65f;
|
|
||||||
targetOffset = -Mathf.Lerp(_biteAmplitude, 0f, k);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BobberBiteType.SlowSink:
|
|
||||||
targetOffset = -Mathf.SmoothStep(0f, _biteAmplitude, t);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BobberBiteType.Lift:
|
|
||||||
targetOffset = Mathf.SmoothStep(0f, _biteAmplitude, t);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BobberBiteType.BlackDrift:
|
|
||||||
targetOffset = -Mathf.SmoothStep(0f, _biteAmplitude, t);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_biteOffsetY = Mathf.SmoothDamp(
|
|
||||||
_biteOffsetY,
|
|
||||||
targetOffset,
|
|
||||||
ref _biteOffsetYVelocity,
|
|
||||||
0.03f,
|
|
||||||
Mathf.Infinity,
|
|
||||||
Time.fixedDeltaTime
|
|
||||||
);
|
|
||||||
|
|
||||||
if (_biteTimer >= _biteDuration)
|
|
||||||
{
|
|
||||||
if (_activeBiteType == BobberBiteType.SlowSink || _activeBiteType == BobberBiteType.BlackDrift)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_activeBiteType = BobberBiteType.None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Utilities
|
|
||||||
|
|
||||||
private float GetWaterHeight(Vector3 worldPos)
|
|
||||||
{
|
|
||||||
if (_waterProvider != null)
|
|
||||||
{
|
|
||||||
_hasCrestSampleThisFrame = false;
|
|
||||||
return _waterProvider.GetWaterHeight(worldPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
waterRenderer != null
|
|
||||||
&& waterRenderer.AnimatedWavesLod != null
|
|
||||||
&& waterRenderer.AnimatedWavesLod.Provider != null
|
|
||||||
)
|
|
||||||
{
|
|
||||||
_waterQueryPoints[0] = worldPos;
|
|
||||||
waterRenderer.AnimatedWavesLod.Provider.Query(
|
|
||||||
GetHashCode(),
|
|
||||||
Mathf.Max(0.001f, waterQueryObjectWidth),
|
|
||||||
_waterQueryPoints,
|
|
||||||
_waterQueryResultDisplacements,
|
|
||||||
_waterQueryResultNormal,
|
|
||||||
_waterQueryResultVelocities,
|
|
||||||
waterCollisionLayer
|
|
||||||
);
|
|
||||||
|
|
||||||
_hasCrestSampleThisFrame = true;
|
|
||||||
return _waterQueryResultDisplacements[0].y + waterRenderer.SeaLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
_hasCrestSampleThisFrame = false;
|
|
||||||
return fallbackWaterLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector3 GetBottomWorldPosition()
|
|
||||||
{
|
|
||||||
return transform.TransformPoint(new Vector3(0f, bottomOffsetLocalY, 0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleDebugKeys()
|
|
||||||
{
|
|
||||||
if (!Application.isPlaying)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (debugResetKey && Input.GetKeyDown(KeyCode.R))
|
|
||||||
{
|
|
||||||
StopBite();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debugTapKey && Input.GetKeyDown(KeyCode.T))
|
|
||||||
PlayTap();
|
|
||||||
|
|
||||||
if (debugSlowSinkKey && Input.GetKeyDown(KeyCode.G))
|
|
||||||
PlaySlowSink();
|
|
||||||
|
|
||||||
if (debugLiftKey && Input.GetKeyDown(KeyCode.H))
|
|
||||||
PlayLift();
|
|
||||||
|
|
||||||
if (debugBlackDriftKey && Input.GetKeyDown(KeyCode.B))
|
|
||||||
PlayBlackDrift();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawDebug(float waterY)
|
|
||||||
{
|
|
||||||
Vector3 p = transform.position;
|
|
||||||
Vector3 b = GetBottomWorldPosition();
|
|
||||||
|
|
||||||
Debug.DrawLine(
|
|
||||||
new Vector3(p.x - 0.05f, waterY, p.z),
|
|
||||||
new Vector3(p.x + 0.05f, waterY, p.z),
|
|
||||||
Color.cyan
|
|
||||||
);
|
|
||||||
|
|
||||||
Debug.DrawLine(b, b + Vector3.up * floatHeight, Color.yellow);
|
|
||||||
|
|
||||||
if (_mode == BobberControlMode.WaterPresentation)
|
|
||||||
{
|
|
||||||
Vector3 a = _waterAnchorPos;
|
|
||||||
Debug.DrawLine(a + Vector3.left * 0.03f, a + Vector3.right * 0.03f, Color.green);
|
|
||||||
Debug.DrawLine(a + Vector3.forward * 0.03f, a + Vector3.back * 0.03f, Color.green);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lureBody != null)
|
|
||||||
{
|
|
||||||
Vector3 bobber = _rb.worldCenterOfMass;
|
|
||||||
Vector3 lure = lureBody.worldCenterOfMass;
|
|
||||||
Debug.DrawLine(bobber, lure, Color.magenta);
|
|
||||||
|
|
||||||
Vector3 verticalEnd = bobber + Vector3.down * _verticalDistance;
|
|
||||||
Debug.DrawLine(bobber, verticalEnd, Color.red);
|
|
||||||
|
|
||||||
Vector3 planar = Vector3.ProjectOnPlane(lure - bobber, Vector3.up);
|
|
||||||
Debug.DrawLine(verticalEnd, verticalEnd + planar, Color.blue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
private void OnValidate()
|
|
||||||
{
|
|
||||||
floatHeight = Mathf.Max(0.001f, floatHeight);
|
|
||||||
ySmoothTime = Mathf.Max(0.001f, ySmoothTime);
|
|
||||||
maxYSpeed = Mathf.Max(0.01f, maxYSpeed);
|
|
||||||
xzSmoothTime = Mathf.Max(0.001f, xzSmoothTime);
|
|
||||||
rotationLerpSpeed = Mathf.Max(0.01f, rotationLerpSpeed);
|
|
||||||
|
|
||||||
maxPlanarOffset = Mathf.Max(0f, maxPlanarOffset);
|
|
||||||
downForceToSink = Mathf.Max(0f, downForceToSink);
|
|
||||||
maxExtraSink = Mathf.Max(0f, maxExtraSink);
|
|
||||||
surfaceBobAmplitude = Mathf.Max(0f, surfaceBobAmplitude);
|
|
||||||
surfaceBobFrequency = Mathf.Max(0f, surfaceBobFrequency);
|
|
||||||
waterQueryObjectWidth = Mathf.Max(0.001f, waterQueryObjectWidth);
|
|
||||||
yDeadZone = Mathf.Max(0f, yDeadZone);
|
|
||||||
|
|
||||||
referenceLength = Mathf.Max(0.0001f, referenceLength);
|
|
||||||
minSubmergeToStand = Mathf.Clamp01(minSubmergeToStand);
|
|
||||||
verticalLieThreshold = Mathf.Clamp(verticalLieThreshold, 0f, 2f);
|
|
||||||
verticalUprightThreshold = Mathf.Max(verticalLieThreshold, verticalUprightThreshold);
|
|
||||||
planarTiltThreshold = Mathf.Clamp(planarTiltThreshold, 0f, 2f);
|
|
||||||
planarDominanceMultiplier = Mathf.Max(0.1f, planarDominanceMultiplier);
|
|
||||||
postureHysteresis = Mathf.Clamp(postureHysteresis, 0f, 0.3f);
|
|
||||||
postureConfirmTime = Mathf.Max(0f, postureConfirmTime);
|
|
||||||
postureSwitchCooldown = Mathf.Max(0f, postureSwitchCooldown);
|
|
||||||
|
|
||||||
tiltedAngle = Mathf.Clamp(tiltedAngle, 0f, 89f);
|
|
||||||
lyingAngle = Mathf.Clamp(lyingAngle, tiltedAngle, 89.9f);
|
|
||||||
uprightMaxTiltAngle = Mathf.Clamp(uprightMaxTiltAngle, 0f, tiltedAngle);
|
|
||||||
planarTiltFactor = Mathf.Max(0f, planarTiltFactor);
|
|
||||||
planarDirectionDeadZone = Mathf.Max(0.0001f, planarDirectionDeadZone);
|
|
||||||
planarDirectionLerpSpeed = Mathf.Max(0.01f, planarDirectionLerpSpeed);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 2dedfafdc2d747d98c682cde3e28e513
|
|
||||||
timeCreated: 1774185233
|
|
||||||
229
Assets/TerrainDef.mat
Normal file
229
Assets/TerrainDef.mat
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!21 &2100000
|
||||||
|
Material:
|
||||||
|
serializedVersion: 8
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: TerrainDef
|
||||||
|
m_Shader: {fileID: 4800000, guid: 69c1f799e772cb6438f56c23efccb782, type: 3}
|
||||||
|
m_Parent: {fileID: 0}
|
||||||
|
m_ModifiedSerializedProperties: 0
|
||||||
|
m_ValidKeywords: []
|
||||||
|
m_InvalidKeywords: []
|
||||||
|
m_LightmapFlags: 4
|
||||||
|
m_EnableInstancingVariants: 1
|
||||||
|
m_DoubleSidedGI: 0
|
||||||
|
m_CustomRenderQueue: -1
|
||||||
|
stringTagMap: {}
|
||||||
|
disabledShaderPasses:
|
||||||
|
- MOTIONVECTORS
|
||||||
|
m_LockedProperties:
|
||||||
|
m_SavedProperties:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TexEnvs:
|
||||||
|
- _BaseMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _BumpMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Control:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Control0:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailAlbedoMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailMask:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailNormalMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Diffuse:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _EmissionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MainTex:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Mask0:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Mask1:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Mask2:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Mask3:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MetallicGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Normal0:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Normal1:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Normal2:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Normal3:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _NormalSAO:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _OcclusionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _ParallaxMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _PerPixelNormal:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _PerTexProps:
|
||||||
|
m_Texture: {fileID: -6551348070273646885, guid: c661901a630dc2544a9128ea4330e178, type: 2}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _SpecGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Splat0:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Splat1:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Splat2:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _Splat3:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _TerrainHolesTexture:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- unity_Lightmaps:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- unity_LightmapsInd:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- unity_ShadowMasks:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Ints: []
|
||||||
|
m_Floats:
|
||||||
|
- _AddPrecomputedVelocity: 0
|
||||||
|
- _AlphaClip: 0
|
||||||
|
- _AlphaToMask: 0
|
||||||
|
- _Blend: 0
|
||||||
|
- _BlendModePreserveSpecular: 1
|
||||||
|
- _BumpScale: 1
|
||||||
|
- _ClearCoatMask: 0
|
||||||
|
- _ClearCoatSmoothness: 0
|
||||||
|
- _Contrast: 0.4
|
||||||
|
- _Cull: 2
|
||||||
|
- _Cutoff: 0.5
|
||||||
|
- _DetailAlbedoMapScale: 1
|
||||||
|
- _DetailNormalMapScale: 1
|
||||||
|
- _DstBlend: 0
|
||||||
|
- _DstBlendAlpha: 0
|
||||||
|
- _EnableHeightBlend: 0
|
||||||
|
- _EnableInstancedPerPixelNormal: 1
|
||||||
|
- _EnvironmentReflections: 1
|
||||||
|
- _GlossMapScale: 0
|
||||||
|
- _Glossiness: 0
|
||||||
|
- _GlossyReflections: 0
|
||||||
|
- _HeightTransition: 0
|
||||||
|
- _HybridHeightBlendDistance: 300
|
||||||
|
- _Metallic: 0
|
||||||
|
- _Metallic0: 0
|
||||||
|
- _Metallic1: 0
|
||||||
|
- _Metallic2: 0
|
||||||
|
- _Metallic3: 0
|
||||||
|
- _NumLayersCount: 1
|
||||||
|
- _OcclusionStrength: 1
|
||||||
|
- _Parallax: 0.005
|
||||||
|
- _QueueOffset: 0
|
||||||
|
- _ReceiveShadows: 1
|
||||||
|
- _Shininess: 0.078125
|
||||||
|
- _Smoothness: 0.5
|
||||||
|
- _Smoothness0: 0.5
|
||||||
|
- _Smoothness1: 0.5
|
||||||
|
- _Smoothness2: 0.5
|
||||||
|
- _Smoothness3: 0.5
|
||||||
|
- _SmoothnessTextureChannel: 0
|
||||||
|
- _SpecularHighlights: 1
|
||||||
|
- _SrcBlend: 1
|
||||||
|
- _SrcBlendAlpha: 1
|
||||||
|
- _Surface: 0
|
||||||
|
- _WorkflowMode: 1
|
||||||
|
- _XRMotionVectorsPass: 1
|
||||||
|
- _ZWrite: 1
|
||||||
|
m_Colors:
|
||||||
|
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||||
|
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||||
|
- _TriplanarUVScale: {r: 1, g: 1, b: 0, a: 0}
|
||||||
|
- _UVScale: {r: 45, g: 45, b: 0, a: 0}
|
||||||
|
m_BuildTextureStacks: []
|
||||||
|
m_AllowLocking: 1
|
||||||
|
--- !u!114 &4095775999777943649
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 11
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
|
||||||
|
version: 10
|
||||||
8
Assets/TerrainDef.mat.meta
Normal file
8
Assets/TerrainDef.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 95965ffe54835394fbe1c387dac39df6
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 2100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
16
Assets/TerrainDef_keywords.asset
Normal file
16
Assets/TerrainDef_keywords.asset
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 007677a8d215f46f78fdc4a57e84fb09, type: 3}
|
||||||
|
m_Name: TerrainDef_keywords
|
||||||
|
m_EditorClassIdentifier: JBooth.MicroSplat.Core::JBooth.MicroSplat.MicroSplatKeywords
|
||||||
|
keywords:
|
||||||
|
- _TERRAIN_INSTANCED_PERPIXEL_NORMAL
|
||||||
8
Assets/TerrainDef_keywords.asset.meta
Normal file
8
Assets/TerrainDef_keywords.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6f7a96b20e24fd24d8669431f1e31fc6
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
1153
Assets/TerrainDef_propdata.asset
Normal file
1153
Assets/TerrainDef_propdata.asset
Normal file
File diff suppressed because one or more lines are too long
8
Assets/TerrainDef_propdata.asset.meta
Normal file
8
Assets/TerrainDef_propdata.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c661901a630dc2544a9128ea4330e178
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -13,3 +13,4 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_LastMaterialVersion: 10
|
m_LastMaterialVersion: 10
|
||||||
|
m_ProjectSettingFolderPath: URPDefaultResources
|
||||||
|
|||||||
Reference in New Issue
Block a user