接入新逻辑
# 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
|
||||
{
|
||||
@@ -20,7 +20,7 @@ namespace NBF
|
||||
|
||||
|
||||
public bool isPinched { get; private set; }
|
||||
|
||||
|
||||
private bool moveToTargetDone;
|
||||
private float _speed;
|
||||
|
||||
@@ -44,11 +44,6 @@ namespace NBF
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
// SyncPosition();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
SyncPosition();
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace NBF
|
||||
|
||||
public struct ThrowAnimationRequest
|
||||
{
|
||||
public LureController Lure;
|
||||
public FLineLogicNode Lure;
|
||||
public Vector3 ThrowOriginPosition;
|
||||
public Vector3 StartPosition;
|
||||
public Vector3 Forward;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NBF
|
||||
private float _castElapsedTime;
|
||||
private Vector3 _castStartPos;
|
||||
private Vector3 _castTargetPos;
|
||||
private LureController _castingLure;
|
||||
private FLineLogicNode _castingLure;
|
||||
|
||||
public bool IsPlaying => _castingLure != null;
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace NBF
|
||||
_chargedProgress = Mathf.Clamp01(request.ChargedProgress);
|
||||
_castElapsedTime = 0f;
|
||||
|
||||
var lureBody = request.Lure.RBody;
|
||||
var lureBody = request.Lure.Rigidbody;
|
||||
_castStartPos = request.StartPosition;
|
||||
|
||||
Vector3 forward = GetHorizontalForward(request.Forward);
|
||||
@@ -81,7 +81,7 @@ namespace NBF
|
||||
return;
|
||||
}
|
||||
|
||||
var lureBody = _castingLure.RBody;
|
||||
var lureBody = _castingLure.Rigidbody;
|
||||
if (snapToTarget)
|
||||
{
|
||||
_castingLure.transform.position = _castTargetPos;
|
||||
@@ -150,4 +150,4 @@ namespace NBF
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,9 +101,11 @@ namespace NBF
|
||||
var handItemView = Player.HandItem.GetComponent<PlayerItemView>();
|
||||
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>();
|
||||
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;
|
||||
|
||||
var rod = GetRod();
|
||||
if (rod == null || rod.Line == null || rod.Line.Lure == null)
|
||||
if (rod == null || rod.Line == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var endNode = rod.Line.GetNode(FLineLogicNodeType.End);
|
||||
_throwAnimation = CreateThrowAnimation(rod);
|
||||
_throwAnimation.Player = Player;
|
||||
_throwAnimation?.Play(new ThrowAnimationRequest
|
||||
{
|
||||
Lure = rod.Line.Lure,
|
||||
Lure = endNode,
|
||||
ThrowOriginPosition = PlayerView.Unity.transform.position,
|
||||
StartPosition = rod.Line.Lure.RBody.position,
|
||||
StartPosition = endNode.Rigidbody.position,
|
||||
Forward = PlayerView.Unity.transform.forward,
|
||||
ChargedProgress = ChargedProgress
|
||||
});
|
||||
@@ -108,4 +109,4 @@ namespace NBF
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ namespace NBF
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 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;
|
||||
// var buoyancy = GetComponentInParent<CapsuleBuoyancyStable>();
|
||||
// buoyancy.InitBobber();
|
||||
|
||||
@@ -10,18 +10,17 @@ namespace NBF
|
||||
{
|
||||
hookAsset = GetComponent<HookAsset>();
|
||||
}
|
||||
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
|
||||
// transform.position = Rod.lineHandler.LineConnector_2.transform.position;
|
||||
// transform.rotation = Rod.lineHandler.LineConnector_2.transform.rotation; // 确保旋转也同步
|
||||
// SetParent(Rod.lineHandler.LineConnector_2.transform);
|
||||
|
||||
|
||||
SetParent(Rod.Line.Lure.transform);
|
||||
|
||||
var endNode = Rod.Line.GetNode(FLineLogicNodeType.End);
|
||||
SetParent(endNode.transform);
|
||||
transform.localPosition = Vector3.zero;
|
||||
|
||||
|
||||
// var target = lineHandler.LineConnector_2.GetComponent<Rigidbody>();
|
||||
// var joint = Hook.gameObject.GetComponent<ConfigurableJoint>();
|
||||
// joint.connectedBody = target;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0403ffd74ce46fab8bd4ef057e51432
|
||||
timeCreated: 1766582567
|
||||
guid: c7095cf554c345839173044e4786b0ba
|
||||
timeCreated: 1776948821
|
||||
@@ -13,8 +13,8 @@ namespace NBF
|
||||
// LureHookWaterDisplacement = Lure.GetComponent<FWaterDisplacement>();
|
||||
|
||||
// SetParent(Rod.lineHandler.LineConnector_1.transform);
|
||||
|
||||
SetParent(Rod.Line.Lure.transform);
|
||||
var endNode = Rod.Line.GetNode(FLineLogicNodeType.End);
|
||||
SetParent(endNode.transform);
|
||||
transform.localPosition = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,24 +72,14 @@ namespace NBF
|
||||
if (Line.LineType == LineType.Spinning)
|
||||
{
|
||||
//没有浮漂类型
|
||||
Line.Lure.SetJointDistance(PlayerItem.LineLength);
|
||||
if (PlayerItem.StretchRope)
|
||||
{
|
||||
// Line.SetTargetLength(PlayerItem.Tension > 0f ? 0f : PlayerItem.LineLength);
|
||||
Line.SetTargetLength(PlayerItem.LineLength);
|
||||
}
|
||||
Line.SetLenght(PlayerItem.LineLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
//有浮漂
|
||||
Line.Lure.SetJointDistance(PlayerItem.FloatLength);
|
||||
Line.Bobber.SetJointDistance(PlayerItem.LineLength - PlayerItem.FloatLength);
|
||||
if (PlayerItem.StretchRope)
|
||||
{
|
||||
// Line.SetTargetLength(PlayerItem.Tension > 0f ? 0f : PlayerItem.LineLength - PlayerItem.FloatLength);
|
||||
Line.SetTargetLength(PlayerItem.LineLength - PlayerItem.FloatLength);
|
||||
Line.SetLureLength(PlayerItem.FloatLength);
|
||||
}
|
||||
Line.SetSegmentMaxLength(0, PlayerItem.LineLength - PlayerItem.FloatLength);
|
||||
//浮漂位置
|
||||
Line.SetSegmentMaxLength(1, PlayerItem.FloatLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,12 +328,12 @@ namespace NBF
|
||||
|
||||
var state = PlayerItem.Owner.State;
|
||||
|
||||
|
||||
Vector3 vector = Line.Lure.transform.position;
|
||||
var endNode = Line.GetNode(FLineLogicNodeType.End);
|
||||
Vector3 vector = endNode.transform.position;
|
||||
|
||||
// 当前物体的朝向与指向 Lure 的方向之间的夹角,在 0(完全对齐)到 1(完全相反)之间的一个比例值
|
||||
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);
|
||||
|
||||
|
||||
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_EditorClassIdentifier:
|
||||
m_LastMaterialVersion: 10
|
||||
m_ProjectSettingFolderPath: URPDefaultResources
|
||||
|
||||
Reference in New Issue
Block a user