From 78bc4dfc53181fd1b45313b72a72bedca2cefbd8 Mon Sep 17 00:00:00 2001 From: BobSong <605277374@qq.com> Date: Sun, 29 Mar 2026 21:30:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=B1=BC=E7=BA=BF=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scripts/Fishing/Rope/Rope.cs | 197 ++++++++++++++++++++++++++-- 1 file changed, 187 insertions(+), 10 deletions(-) diff --git a/Assets/Scripts/Fishing/Rope/Rope.cs b/Assets/Scripts/Fishing/Rope/Rope.cs index 15cac5018..0b26d36e6 100644 --- a/Assets/Scripts/Fishing/Rope/Rope.cs +++ b/Assets/Scripts/Fishing/Rope/Rope.cs @@ -116,6 +116,18 @@ public class Rope : MonoBehaviour [SerializeField, Min(0.0001f)] private float lineWidth = 0.001f; + [Header("Performance")] [SerializeField, Tooltip("远端玩家鱼线不可见时,直接停止整条渲染线的模拟与绘制")] + private bool cullRemoteRopeWhenInvisible = true; + + [SerializeField, Tooltip("本地玩家自己的鱼线始终保持完整计算")] + private bool localOwnerAlwaysSimulate = true; + + [SerializeField, Range(1, 60), Tooltip("每隔多少个 FixedUpdate 重新判断一次可见性")] + private int visibilityCheckEvery = 10; + + [SerializeField, Range(0f, 0.5f), Tooltip("屏幕边缘额外留白,避免刚进视野就闪现")] + private float visibilityViewportPadding = 0.08f; + [Header("Air Drag (Stable)")] [SerializeField, Range(0f, 5f), Tooltip("空气阻力(Y向),指数衰减,越大越不飘")] private float airDrag = 0.9f; @@ -155,9 +167,19 @@ public class Rope : MonoBehaviour private float _dt2; private float _kY; private float _kXZ; + private Transform _cameraTr; + private int _visibilityCheckCounter; + private bool _isCulledByVisibility; + private int _tIdleSubdiv = -1; + private int _tMovingSubdiv = -1; private FRod _rod; - public void Init(FRod rod) => _rod = rod; + public void Init(FRod rod) + { + _rod = rod; + if (Application.isPlaying) + RefreshVisibilityState(true); + } // Catmull t caches(只缓存 idle/moving 两档,减少每帧重复乘法) private struct TCaches @@ -175,18 +197,12 @@ public class Rope : MonoBehaviour _lineRenderer = GetComponent(); _gravity = new Vector3(0f, -gravityStrength, 0f); - _startTr = startAnchor ? startAnchor.transform : null; - _endTr = endAnchor ? endAnchor.transform : null; + RefreshAnchorTransforms(); InitLengthSystem(); AllocateAndInitNodes(); - - int maxSubdiv = Mathf.Max(1, renderSubdivisionsIdle); - _rCapacity = (maxPhysicsNodes - 1) * maxSubdiv + 1; - _rPoints = new Vector3[_rCapacity]; - - BuildTCaches(renderSubdivisionsIdle, ref _tIdle); - BuildTCaches(renderSubdivisionsMoving, ref _tMoving); + EnsureRenderCaches(); + RefreshVisibilityState(true); } private void OnValidate() @@ -217,6 +233,153 @@ public class Rope : MonoBehaviour waterSurfaceOffset = Mathf.Max(0f, waterSurfaceOffset); waterLiftStrength = Mathf.Clamp01(waterLiftStrength); waterPostConstraintIterations = Mathf.Clamp(waterPostConstraintIterations, 0, 8); + visibilityCheckEvery = Mathf.Clamp(visibilityCheckEvery, 1, 60); + visibilityViewportPadding = Mathf.Clamp(visibilityViewportPadding, 0f, 0.5f); + } + + private void RefreshAnchorTransforms() + { + _startTr = startAnchor ? startAnchor.transform : null; + _endTr = endAnchor ? endAnchor.transform : null; + } + + private bool ShouldAlwaysSimulate() + { + if (!localOwnerAlwaysSimulate) + return false; + + var owner = _rod?.PlayerItem?.Owner; + return owner == null || owner.IsSelf; + } + + private Transform GetActiveCameraTransform() + { + Camera main = BaseCamera.Main; + if (main) + { + _cameraTr = main.transform; + return _cameraTr; + } + + if (!_cameraTr) + { + Camera fallback = Camera.main; + if (fallback) + _cameraTr = fallback.transform; + } + + return _cameraTr; + } + + private static bool IsViewportPointVisible(Vector3 viewportPoint, float padding) + { + if (viewportPoint.z <= 0f) + return false; + + return viewportPoint.x >= -padding && viewportPoint.x <= 1f + padding && + viewportPoint.y >= -padding && viewportPoint.y <= 1f + padding; + } + + private bool IsVisibleToMainCamera() + { + Transform camTr = GetActiveCameraTransform(); + if (!camTr) + return true; + + Camera cam = camTr.GetComponent(); + if (!cam) + cam = BaseCamera.Main ? BaseCamera.Main : Camera.main; + if (!cam) + return true; + + Vector3 start = _startTr ? _startTr.position : (startAnchor ? startAnchor.position : transform.position); + Vector3 end = _endTr ? _endTr.position : (endAnchor ? endAnchor.position : transform.position); + Vector3 middle = (start + end) * 0.5f; + float padding = visibilityViewportPadding; + + return IsViewportPointVisible(cam.WorldToViewportPoint(start), padding) || + IsViewportPointVisible(cam.WorldToViewportPoint(end), padding) || + IsViewportPointVisible(cam.WorldToViewportPoint(middle), padding); + } + + private void RefreshVisibilityState(bool force = false) + { + if (!cullRemoteRopeWhenInvisible || ShouldAlwaysSimulate()) + { + _isCulledByVisibility = false; + if (_lineRenderer) + _lineRenderer.enabled = true; + return; + } + + if (!force) + { + _visibilityCheckCounter++; + if (_visibilityCheckCounter < visibilityCheckEvery) + return; + } + + _visibilityCheckCounter = 0; + bool wasCulled = _isCulledByVisibility; + _isCulledByVisibility = !IsVisibleToMainCamera(); + + if (_lineRenderer) + _lineRenderer.enabled = !_isCulledByVisibility; + + if (wasCulled && !_isCulledByVisibility) + SyncVisibleStateAfterCulling(); + } + + private void SyncVisibleStateAfterCulling() + { + _currentLength = Mathf.Max(_targetLength, 0.01f); + UpdateNodesFromLength(); + UpdateHeadRestLenFromCurrentLength(); + ResetNodesBetweenAnchors(); + LockAnchorsHard(); + } + + private void ResetNodesBetweenAnchors() + { + if (_physicsNodes < 2) + return; + + Vector3 start = _startTr ? _startTr.position : (startAnchor ? startAnchor.position : transform.position); + Vector3 end = _endTr ? _endTr.position : (endAnchor ? endAnchor.position : transform.position); + int last = _physicsNodes - 1; + + for (int i = 0; i <= last; i++) + { + float t = (last > 0) ? i / (float)last : 0f; + Vector3 pos = Vector3.Lerp(start, end, t); + _pCurr[i] = pos; + _pPrev[i] = pos; + } + } + + private void EnsureRenderCaches() + { + int idle = Mathf.Max(1, renderSubdivisionsIdle); + if (_tIdleSubdiv != idle) + { + BuildTCaches(idle, ref _tIdle); + _tIdleSubdiv = idle; + } + + int moving = Mathf.Max(1, renderSubdivisionsMoving); + if (_tMovingSubdiv != moving) + { + BuildTCaches(moving, ref _tMoving); + _tMovingSubdiv = moving; + } + + int maxSubdiv = Mathf.Max(idle, moving); + int neededCapacity = (maxPhysicsNodes - 1) * maxSubdiv + 1; + if (_rPoints == null || neededCapacity > _rCapacity) + { + _rCapacity = neededCapacity; + _rPoints = new Vector3[_rCapacity]; + } } private void InitLengthSystem() @@ -284,6 +447,9 @@ public class Rope : MonoBehaviour public float GetLengthByPoints() { + if (!smooth) + return GetPhysicsPolylineLength(); + if (_rPoints == null || _lineRenderer == null) return 0f; int count = _lineRenderer.positionCount; @@ -338,6 +504,11 @@ public class Rope : MonoBehaviour { if (!startAnchor || !endAnchor) return; + RefreshAnchorTransforms(); + RefreshVisibilityState(); + if (_isCulledByVisibility) + return; + _dt = Time.fixedDeltaTime; if (_dt < 1e-6f) _dt = 1e-6f; _dt2 = _dt * _dt; @@ -394,6 +565,12 @@ public class Rope : MonoBehaviour { if (!startAnchor || !endAnchor || _pCurr == null || _physicsNodes < 2) return; + RefreshAnchorTransforms(); + if (_isCulledByVisibility) + return; + + EnsureRenderCaches(); + int last = _physicsNodes - 1; Vector3 s = _startTr.position;