Files
aibis-dream/Assets/Scripts/Framework/EventSystemKit/WorldScrollRect.cs
T

499 lines
16 KiB
C#

using UnityEngine;
using UnityEngine.EventSystems;
namespace AibisDream.Framework
{
/// <summary>
/// 世界坐标下的滚动组件,类似ScrollRect但兼容SpriteRenderer
/// </summary>
[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<EventTriggerEx>();
_collider = GetComponent<Collider2D>();
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();
}
}
/// <summary>
/// 计算内容边界和视口边界
/// </summary>
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<Renderer>();
if (selfRenderer != null)
{
bounds = selfRenderer.bounds;
first = false;
}
// 遍历所有子对象
foreach (Transform child in targetTransform)
{
Renderer renderer = child.GetComponent<Renderer>();
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);
}
}
/// <summary>
/// 开始拖拽
/// </summary>
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;
}
}
/// <summary>
/// 拖拽中
/// </summary>
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;
}
/// <summary>
/// 结束拖拽
/// </summary>
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;
}
}
/// <summary>
/// 应用惯性滚动
/// </summary>
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;
}
}
/// <summary>
/// 应用弹性边界
/// </summary>
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);
}
}
/// <summary>
/// 处理鼠标滚轮
/// </summary>
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;
}
/// <summary>
/// 限制位置在边界内
/// </summary>
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;
}
/// <summary>
/// 设置目标Transform
/// </summary>
public void SetTargetTransform(Transform target)
{
targetTransform = target;
if (target != null)
{
_initialTargetPosition = target.position;
}
}
/// <summary>
/// 设置内容边界
/// </summary>
public void SetContentBounds(Bounds bounds)
{
contentBounds = bounds;
useAutoBounds = false;
}
/// <summary>
/// 设置视口边界
/// </summary>
public void SetViewportBounds(Bounds bounds)
{
viewportBounds = bounds;
useAutoBounds = false;
}
/// <summary>
/// 重新计算边界
/// </summary>
public void RecalculateBounds()
{
CalculateBounds();
}
/// <summary>
/// 停止滚动
/// </summary>
public void StopMovement()
{
_velocity = Vector3.zero;
_isDragging = false;
}
/// <summary>
/// 重置到初始位置
/// </summary>
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
}
}