using UnityEngine; using UnityEngine.EventSystems; namespace AibisDream.Framework { /// /// 世界坐标下的滚动组件,类似ScrollRect但兼容SpriteRenderer /// [RequireComponent(typeof(Collider2D))] public class WorldScrollRect : MonoBehaviour, IInteraction { [Header("滚动目标")] [SerializeField] private Transform targetTransform; [Header("滚动方向")] [SerializeField] private bool horizontal = true; [SerializeField] private bool vertical = true; [Header("边界设置")] [SerializeField] private bool useAutoBounds = true; [SerializeField] private Bounds contentBounds; [SerializeField] private Bounds viewportBounds; [Header("惯性滚动")] [SerializeField] private bool inertia = true; [SerializeField] private float decelerationRate = 0.135f; [SerializeField] private float velocityThreshold = 0.1f; [Header("弹性边界")] [SerializeField] private bool elasticity = true; [SerializeField] private float elasticityStrength = 0.1f; [Header("鼠标滚轮")] [SerializeField] private bool scrollWheelEnabled = true; [SerializeField] private float scrollSensitivity = 1f; [Header("交互设置")] [SerializeField] private bool isActive = true; [SerializeField] private bool isAvailable = true; private EventTriggerEx _eventTrigger; private Collider2D _collider; private Vector3 _initialTargetPosition; private Vector3 _lastMouseWorldPos; private Vector3 _velocity; private bool _isDragging; private Vector3[] _velocityHistory = new Vector3[5]; private int _velocityHistoryIndex; private float _lastDragTime; // IInteraction接口实现 public bool IsActive => isActive; public bool IsAvailable => isAvailable; public GameObject GetGameObject() => gameObject; private void Awake() { _eventTrigger = GetComponent(); _collider = GetComponent(); if (targetTransform == null) { targetTransform = transform; } _initialTargetPosition = targetTransform.position; // 注册拖拽事件 _eventTrigger.Register(EventTriggerType.BeginDrag, OnBeginDrag); _eventTrigger.Register(EventTriggerType.Drag, OnDrag); _eventTrigger.Register(EventTriggerType.EndDrag, OnEndDrag); } private void Start() { if (useAutoBounds) { CalculateBounds(); } } private void Update() { if (!isActive || !isAvailable) return; // 处理鼠标滚轮 if (scrollWheelEnabled) { HandleScrollWheel(); } // 处理惯性滚动 if (inertia && !_isDragging && _velocity.magnitude > velocityThreshold) { ApplyInertia(); } // 处理弹性边界 if (elasticity) { ApplyElasticity(); } } /// /// 计算内容边界和视口边界 /// private void CalculateBounds() { // 计算视口边界(从Collider2D获取) if (_collider != null) { viewportBounds = _collider.bounds; } else { viewportBounds = new Bounds(transform.position, Vector3.one * 5f); } // 计算内容边界(从targetTransform的子对象获取) if (targetTransform != null) { Bounds bounds = new Bounds(); bool first = true; // 检查targetTransform本身是否有Renderer Renderer selfRenderer = targetTransform.GetComponent(); if (selfRenderer != null) { bounds = selfRenderer.bounds; first = false; } // 遍历所有子对象 foreach (Transform child in targetTransform) { Renderer renderer = child.GetComponent(); if (renderer != null) { if (first) { bounds = renderer.bounds; first = false; } else { bounds.Encapsulate(renderer.bounds); } } else { // 如果没有Renderer,使用Transform位置 if (first) { bounds = new Bounds(child.position, Vector3.zero); first = false; } else { bounds.Encapsulate(child.position); } } } if (!first) { contentBounds = bounds; } else { // 如果没有任何Renderer,使用targetTransform的位置 contentBounds = new Bounds(targetTransform.position, Vector3.one * 2f); } } else { contentBounds = new Bounds(transform.position, Vector3.one * 2f); } } /// /// 开始拖拽 /// private void OnBeginDrag(BaseEventData data) { if (!IsActive || !IsAvailable) return; if (data is not PointerEventData pointerData) return; _isDragging = true; _velocity = Vector3.zero; _lastMouseWorldPos = CameraKit.GetMouseWorldPos(pointerData.position); _lastDragTime = Time.time; _velocityHistoryIndex = 0; // 清除速度历史 for (int i = 0; i < _velocityHistory.Length; i++) { _velocityHistory[i] = Vector3.zero; } } /// /// 拖拽中 /// private void OnDrag(BaseEventData data) { if (!IsActive || !IsAvailable) return; if (data is not PointerEventData pointerData) return; Vector3 currentMouseWorldPos = CameraKit.GetMouseWorldPos(pointerData.position); Vector3 delta = currentMouseWorldPos - _lastMouseWorldPos; // 根据滚动方向限制delta if (!horizontal) delta.x = 0f; if (!vertical) delta.y = 0f; delta.z = 0f; // 更新目标位置 Vector3 newPosition = targetTransform.position + delta; targetTransform.position = ClampPosition(newPosition); // 记录速度历史 float deltaTime = Time.time - _lastDragTime; if (deltaTime > 0f) { Vector3 velocity = delta / deltaTime; _velocityHistory[_velocityHistoryIndex] = velocity; _velocityHistoryIndex = (_velocityHistoryIndex + 1) % _velocityHistory.Length; } _lastMouseWorldPos = currentMouseWorldPos; _lastDragTime = Time.time; } /// /// 结束拖拽 /// private void OnEndDrag(BaseEventData data) { _isDragging = false; // 计算平均速度 Vector3 totalVelocity = Vector3.zero; int count = 0; for (int i = 0; i < _velocityHistory.Length; i++) { if (_velocityHistory[i].magnitude > 0f) { totalVelocity += _velocityHistory[i]; count++; } } if (count > 0) { _velocity = totalVelocity / count; } else { _velocity = Vector3.zero; } } /// /// 应用惯性滚动 /// private void ApplyInertia() { // 应用衰减 _velocity *= Mathf.Pow(decelerationRate, Time.deltaTime); // 根据滚动方向限制速度 if (!horizontal) _velocity.x = 0f; if (!vertical) _velocity.y = 0f; _velocity.z = 0f; // 更新位置 Vector3 newPosition = targetTransform.position + _velocity * Time.deltaTime; targetTransform.position = ClampPosition(newPosition); // 如果速度太小,停止惯性 if (_velocity.magnitude < velocityThreshold) { _velocity = Vector3.zero; } } /// /// 应用弹性边界 /// private void ApplyElasticity() { if (_isDragging || _velocity.magnitude > velocityThreshold) return; Vector3 currentPos = targetTransform.position; Vector3 clampedPos = ClampPosition(currentPos); Vector3 offset = currentPos - clampedPos; if (offset.magnitude > 0.01f) { // 应用弹性回弹 Vector3 elasticForce = -offset * elasticityStrength; Vector3 newPosition = currentPos + elasticForce * Time.deltaTime; targetTransform.position = ClampPosition(newPosition); } } /// /// 处理鼠标滚轮 /// private void HandleScrollWheel() { Vector2 scrollDelta = Input.mouseScrollDelta; if (scrollDelta.magnitude == 0f) return; // 检查鼠标是否在视口内 Vector3 mouseWorldPos = CameraKit.GetMouseWorldPos(Input.mousePosition); if (!viewportBounds.Contains(mouseWorldPos)) return; Vector3 scrollDelta3D = new Vector3( horizontal ? scrollDelta.x * scrollSensitivity : 0f, vertical ? scrollDelta.y * scrollSensitivity : 0f, 0f ); Vector3 newPosition = targetTransform.position + scrollDelta3D; targetTransform.position = ClampPosition(newPosition); // 停止惯性滚动 _velocity = Vector3.zero; } /// /// 限制位置在边界内 /// private Vector3 ClampPosition(Vector3 position) { if (!useAutoBounds && contentBounds.size.magnitude < 0.01f) { return position; } // 计算targetTransform的偏移量 Vector3 offset = position - _initialTargetPosition; // 计算移动后的内容边界 Bounds movedContentBounds = contentBounds; movedContentBounds.center += offset; Vector3 contentMin = movedContentBounds.min; Vector3 contentMax = movedContentBounds.max; Vector3 viewportMin = viewportBounds.min; Vector3 viewportMax = viewportBounds.max; Vector3 clampedOffset = offset; // 水平方向限制 if (horizontal) { float contentWidth = contentMax.x - contentMin.x; float viewportWidth = viewportMax.x - viewportMin.x; if (contentWidth > viewportWidth) { // 内容大于视口,限制内容边界在视口内 // 内容左边界不能超过视口左边界 if (contentMin.x > viewportMin.x) { clampedOffset.x -= (contentMin.x - viewportMin.x); } // 内容右边界不能超过视口右边界 if (contentMax.x < viewportMax.x) { clampedOffset.x -= (contentMax.x - viewportMax.x); } } // 如果内容小于视口,允许自由移动(不限制) } else { clampedOffset.x = 0f; } // 垂直方向限制 if (vertical) { float contentHeight = contentMax.y - contentMin.y; float viewportHeight = viewportMax.y - viewportMin.y; if (contentHeight > viewportHeight) { // 内容大于视口,限制内容边界在视口内 // 内容下边界不能超过视口下边界 if (contentMin.y > viewportMin.y) { clampedOffset.y -= (contentMin.y - viewportMin.y); } // 内容上边界不能超过视口上边界 if (contentMax.y < viewportMax.y) { clampedOffset.y -= (contentMax.y - viewportMax.y); } } // 如果内容小于视口,允许自由移动(不限制) } else { clampedOffset.y = 0f; } clampedOffset.z = 0f; return _initialTargetPosition + clampedOffset; } /// /// 设置目标Transform /// public void SetTargetTransform(Transform target) { targetTransform = target; if (target != null) { _initialTargetPosition = target.position; } } /// /// 设置内容边界 /// public void SetContentBounds(Bounds bounds) { contentBounds = bounds; useAutoBounds = false; } /// /// 设置视口边界 /// public void SetViewportBounds(Bounds bounds) { viewportBounds = bounds; useAutoBounds = false; } /// /// 重新计算边界 /// public void RecalculateBounds() { CalculateBounds(); } /// /// 停止滚动 /// public void StopMovement() { _velocity = Vector3.zero; _isDragging = false; } /// /// 重置到初始位置 /// public void ResetPosition() { StopMovement(); if (targetTransform != null) { targetTransform.position = _initialTargetPosition; } } private void OnDestroy() { if (_eventTrigger != null) { _eventTrigger.UnRegister(EventTriggerType.BeginDrag, OnBeginDrag); _eventTrigger.UnRegister(EventTriggerType.Drag, OnDrag); _eventTrigger.UnRegister(EventTriggerType.EndDrag, OnEndDrag); } } #if UNITY_EDITOR private void OnDrawGizmosSelected() { // 绘制视口边界 Gizmos.color = Color.green; Gizmos.DrawWireCube(viewportBounds.center, viewportBounds.size); // 绘制内容边界 Gizmos.color = Color.yellow; Gizmos.DrawWireCube(contentBounds.center, contentBounds.size); } #endif } }