169 lines
5.1 KiB
C#
169 lines
5.1 KiB
C#
|
|
using UnityEngine;
|
||
|
|
using UnityEngine.Events;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 核心交互系统:射线检测 + 点击/拖拽处理。
|
||
|
|
/// 挂在主相机或专用 GameObject 上。
|
||
|
|
/// </summary>
|
||
|
|
public class InteractionSystem : MonoBehaviour
|
||
|
|
{
|
||
|
|
[Header("射线配置")]
|
||
|
|
[SerializeField] private Camera _mainCamera;
|
||
|
|
[SerializeField] private LayerMask _interactableMask = ~0;
|
||
|
|
[SerializeField] private float _maxRayDistance = 100f;
|
||
|
|
|
||
|
|
[Header("事件 - 由 StepManager 绑定")]
|
||
|
|
public UnityEvent<string> OnObjectClicked; // 点击到 Tag 时触发,传 Tag
|
||
|
|
public UnityEvent OnAnywhereClicked; // 点击空白处触发
|
||
|
|
|
||
|
|
[Header("拖拽配置")]
|
||
|
|
[SerializeField] private float _dragPlaneDistance = 2f;
|
||
|
|
[SerializeField] private float _snapDistance = 0.3f;
|
||
|
|
|
||
|
|
private GameObject _draggedObject;
|
||
|
|
private Vector3 _dragOffset;
|
||
|
|
private float _dragZ;
|
||
|
|
private bool _isDragging;
|
||
|
|
|
||
|
|
// 考核模式序列点击
|
||
|
|
private int _sequenceClickCount;
|
||
|
|
private string[] _expectedSequence;
|
||
|
|
|
||
|
|
private void Awake()
|
||
|
|
{
|
||
|
|
if (_mainCamera == null)
|
||
|
|
_mainCamera = Camera.main;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void Update()
|
||
|
|
{
|
||
|
|
HandleClick();
|
||
|
|
HandleDrag();
|
||
|
|
}
|
||
|
|
|
||
|
|
#region 点击处理
|
||
|
|
|
||
|
|
private void HandleClick()
|
||
|
|
{
|
||
|
|
if (!Input.GetMouseButtonDown(0)) return;
|
||
|
|
if (UnityEngine.EventSystems.EventSystem.current != null &&
|
||
|
|
UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
|
||
|
|
return;
|
||
|
|
|
||
|
|
Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
|
||
|
|
if (Physics.Raycast(ray, out RaycastHit hit, _maxRayDistance, _interactableMask))
|
||
|
|
{
|
||
|
|
string tag = hit.collider.tag;
|
||
|
|
if (!string.IsNullOrEmpty(tag))
|
||
|
|
{
|
||
|
|
Debug.Log($"InteractionSystem: 点击到物体 Tag={tag}");
|
||
|
|
OnObjectClicked?.Invoke(tag);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
OnAnywhereClicked?.Invoke();
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
#region 拖拽处理
|
||
|
|
|
||
|
|
private void HandleDrag()
|
||
|
|
{
|
||
|
|
if (Input.GetMouseButtonDown(0))
|
||
|
|
{
|
||
|
|
if (UnityEngine.EventSystems.EventSystem.current != null &&
|
||
|
|
UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
|
||
|
|
return;
|
||
|
|
|
||
|
|
Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
|
||
|
|
if (Physics.Raycast(ray, out RaycastHit hit, _maxRayDistance, _interactableMask))
|
||
|
|
{
|
||
|
|
var draggable = hit.collider.GetComponent<DraggableObject>();
|
||
|
|
if (draggable != null && draggable.IsDraggable)
|
||
|
|
{
|
||
|
|
_draggedObject = hit.collider.gameObject;
|
||
|
|
_dragZ = _mainCamera.WorldToScreenPoint(_draggedObject.transform.position).z;
|
||
|
|
_isDragging = true;
|
||
|
|
draggable.OnDragStart();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Input.GetMouseButton(0) && _isDragging && _draggedObject != null)
|
||
|
|
{
|
||
|
|
Vector3 screenPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, _dragZ);
|
||
|
|
Vector3 worldPos = _mainCamera.ScreenToWorldPoint(screenPos);
|
||
|
|
_draggedObject.transform.position = worldPos;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Input.GetMouseButtonUp(0) && _isDragging && _draggedObject != null)
|
||
|
|
{
|
||
|
|
var draggable = _draggedObject.GetComponent<DraggableObject>();
|
||
|
|
|
||
|
|
if (draggable != null && draggable.SnapTarget != null)
|
||
|
|
{
|
||
|
|
float dist = Vector3.Distance(_draggedObject.transform.position, draggable.SnapTarget.position);
|
||
|
|
if (dist <= _snapDistance)
|
||
|
|
{
|
||
|
|
_draggedObject.transform.position = draggable.SnapTarget.position;
|
||
|
|
_draggedObject.transform.rotation = draggable.SnapTarget.rotation;
|
||
|
|
draggable.OnSnapped();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
draggable?.OnDragEnd();
|
||
|
|
_draggedObject = null;
|
||
|
|
_isDragging = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
#region 考核序列点击
|
||
|
|
|
||
|
|
public void BeginSequenceCheck(string[] expectedTags)
|
||
|
|
{
|
||
|
|
_expectedSequence = expectedTags;
|
||
|
|
_sequenceClickCount = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool TrySequenceClick(string clickedTag, out bool isComplete)
|
||
|
|
{
|
||
|
|
isComplete = false;
|
||
|
|
if (_expectedSequence == null || _sequenceClickCount >= _expectedSequence.Length)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if (clickedTag == _expectedSequence[_sequenceClickCount])
|
||
|
|
{
|
||
|
|
_sequenceClickCount++;
|
||
|
|
if (_sequenceClickCount >= _expectedSequence.Length)
|
||
|
|
{
|
||
|
|
isComplete = true;
|
||
|
|
_expectedSequence = null;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>获取当前序列已正确命中的步数(用于 proportion 计分)</summary>
|
||
|
|
public int GetSequenceHitCount()
|
||
|
|
{
|
||
|
|
return _sequenceClickCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
#region 公共方法
|
||
|
|
|
||
|
|
public void SetInteractionEnabled(bool enabled)
|
||
|
|
{
|
||
|
|
this.enabled = enabled;
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
}
|