逻辑和表现分离
丑陋的例子 1:拖动逻辑和 UI 表现逻辑放在一起
一开始我的逻辑是很丑陋的
一个被拖动的棋子,它的逻辑和表现被我混合在了一起
例如:
using UnityEngine;
/// <summary>
/// 棋盘上的棋子的基类
/// 基类主要处理 DetailsUI 拖动放置相关的逻辑
/// </summary>
public class Chess : MonoBehaviour
{
protected bool isDraging;
[Header("Detail UI")]
[SerializeField]
private string chessName;
public string ChessName
{
get => chessName;
set
{
chessName = value;
}
}
[SerializeField]
private string details;
public string Details
{
get => details;
set
{
details = value;
}
}
[SerializeField, ReadOnly]
protected bool isShowingDetailsUI;
[SerializeField, ReadOnly]
protected float showDetailsUITime;
[SerializeField, ReadOnly]
protected Vector3 lastToggleDetailsUIPos;
[SerializeField]
protected float showDetailsUIMinTime = 1f;
[SerializeField]
protected float showDetailsUIMaxDisOffset = 1f;
#region Mouse Detection
// 这是 Unity 在内部实现的用鼠标射线与 collider 2d 相交的检测
private void OnMouseDown()
{
OnPressDownChess();
}
private void OnMouseUp()
{
OnPressUpChess();
}
protected void Update()
{
OnPressHoldChess();
}
#endregion
#region Virtual Mouse Detection Behaviour
// 这是提供给子类继承的函数,因为 Unity 自带的 OnMouseDown OnMouseUp 等函数不能继承
protected virtual void OnPressDownChess()
{
showDetailsUITime = 0f;
lastToggleDetailsUIPos = transform.position + Vector3.one * showDetailsUIMaxDisOffset;
}
protected virtual void OnPressUpChess()
{
HideDetailsUI();
}
protected virtual void OnPressHoldChess()
{
if (!isDraging)
return;
if(isShowingDetailsUI)
{
// 显示 UI 时如果又移动就收起来
if ((transform.position - lastToggleDetailsUIPos).magnitude > showDetailsUIMaxDisOffset)
{
HideDetailsUI();
}
}
else
{
showDetailsUITime += Time.deltaTime;
// 时间久了,移动距离也长,不是误触
if (showDetailsUITime > showDetailsUIMinTime && (transform.position - lastToggleDetailsUIPos).magnitude > showDetailsUIMaxDisOffset)
{
ShowDetailsUI();
}
}
}
#endregion
#region Details UI
private void ShowDetailsUI()
{
isShowingDetailsUI = true;
lastToggleDetailsUIPos = transform.position;
Vector3 screenPos = Camera.main.WorldToScreenPoint(transform.position + Vector3.right * GameSpriteController.Instance.GridSize);
GameUI.Instance.ShowDetails(screenPos, ChessName, Details);
}
private void HideDetailsUI()
{
isShowingDetailsUI = false;
lastToggleDetailsUIPos = transform.position;
showDetailsUITime = 0f;
GameUI.Instance.HideDetails();
}
#endregion
}
在这里,虽然明确地创建了虚函数,提供给子类
private void OnMouseDown()
{
OnPressDownChess();
}
private void OnMouseUp()
{
OnPressUpChess();
}
protected void Update()
{
OnPressHoldChess();
}
但是我却在父类中写了关于显示一个 UI 的表现逻辑
我在这里写,主要是因为我一开始以为,这个计时相关的逻辑,必须要获取一个开始一个结尾,那么在触发这个拖动事件的棋子里面写很方便
但是现在一看,确实……很累
丑陋的例子 2:每一个棋子独立处理自己的拖动逻辑
第二个丑陋的例子是,每一个可以移动的方块都可能独立计算自己的拖动逻辑
using System;
using System.Collections;
using DG.Tweening;
using UnityEngine;
public class DraggableChess : Chess
{
[Header("Drag")]
[ReadOnly]
public bool canDrag;
[ReadOnly]
public Vector3 dragBeginPos;
[Header("Drag Behaviour Setting")]
[SerializeField]
protected int reachableSteps = 1;
public int ReachableSteps => reachableSteps;
[Header("Click Behaviour Setting")]
[SerializeField]
[Tooltip("点击的灵敏度,单位 s,表示在这个时间范围内完成按下松开则判断为点击")]
protected float clickSensitivity = 0.2f;
[SerializeField]
[Tooltip("玩家本次按下松开之间的时间间隔")]
protected float clickInterval;
[SerializeField]
[Tooltip("是否正在计时")]
protected bool isCounting;
[SerializeField]
[Tooltip("是否处于被点击状态")]
public bool isClicked;
protected void Start()
{
dragBeginPos = transform.position;
}
protected override void OnPressDownChess()
{
base.OnPressDownChess();
if(!CanBeginDrag())
return;
isCounting = true;
clickInterval = 0f;
}
protected override void OnPressUpChess()
{
base.OnPressUpChess();
if (!canDrag)
return;
isCounting = false;
// 判定为点击
if (clickInterval < clickSensitivity)
{
if(!isClicked) DragChessGameCue.Instance.BeginClick(this);
else DragChessGameCue.Instance.BreakClick(this);
}
// 如果正在拖动棋子,那么终止拖动
if (isDraging) EndDrag();
}
protected void FixedUpdate()
{
if(isCounting)
{
clickInterval += Time.fixedDeltaTime;
if (clickInterval > clickSensitivity)
{
isCounting = false;
if (isClicked)
{
isClicked = false;
}
BeginDrag();
}
}
}
#region Check if can begin darg
protected virtual bool CanBeginDrag()
{
return canDrag;
}
#endregion
#region Drag Behaviour
private void BeginDrag()
{
isDraging = true;
dragBeginPos = transform.position;
// DragChessGameCue
DragChessGameCue.Instance.BeginDrag(this);
}
private void EndDrag()
{
// drag setting
isDraging = false;
// DragChessGameCue
DragChessGameCue.Instance.EndDrag();
}
#endregion
// 提供给资源方块或者道具方块,拖动到某个位置时判断该怎么行动
public virtual IEnumerator WaitForDragTo(Vector3 worldChessPos)
{
yield return null;
}
// 返回原位置
// 不结束玩家可拖动回合
public IEnumerator WaitForReturnPos()
{
// return last pos
yield return StartCoroutine(ChessTweenHelper.WaitForMoveTo(transform, dragBeginPos));
// continue to drag other chess
ChessGameCue.Instance.SetAllResChessesCanDrag(true);
}
}
这里我需要记录一个位置 dragBeginPos,来记录他是从哪里开始被拖动的
结果后面为了维护这个位置,就写成了屎山
比如这里我在子类中,比如资源棋子 ResChess,也要费尽维护
在初始化时,在移动结束时,在每一种移动结束时都是
之后很多 bug 的出现都是因为这个位置维护的有问题
因此这样的问题一看就是,因为在各个流程中都要用到同一个变量,所以这个变量的维护就变得很重要,需要考虑很多情况,要让它在各个使用情况下都不犯错
一轮解耦:将缩放动画,UI 显示等表现与判定鼠标点击和拖动的逻辑分离
首先,基类只是提供一个 collider 2d 的点击射线检测
Chess.cs
using UnityEngine;
/// <summary>
/// 棋盘上的棋子的基类
/// 基类主要处理 DetailsUI 拖动放置相关的逻辑
/// </summary>
public class Chess : MonoBehaviour
{
protected bool isDraging;
[Header("Detail UI")]
[SerializeField]
private string chessName;
public string ChessName
{
get => chessName;
set
{
chessName = value;
}
}
[SerializeField]
private string details;
public string Details
{
get => details;
set
{
details = value;
}
}
#region Mouse Detection
// 这是 Unity 在内部实现的用鼠标射线与 collider 2d 相交的检测
private void OnMouseDown()
{
OnPressDownChess();
}
private void OnMouseUp()
{
OnPressUpChess();
}
protected void Update()
{
OnPressHoldChess();
}
#endregion
#region Virtual Mouse Detection Behaviour
// 这是提供给子类继承的函数,因为 Unity 自带的 OnMouseDown OnMouseUp 等函数不能继承
protected virtual void OnPressDownChess()
{
}
protected virtual void OnPressUpChess()
{
}
protected virtual void OnPressHoldChess()
{
}
#endregion
}
然后子类调用表现方面的逻辑
DraggableChess.cs
using System;
using System.Collections;
using DG.Tweening;
using UnityEngine;
public class DraggableChess : Chess
{
[Header("Drag")]
[ReadOnly]
public bool canDrag;
[ReadOnly]
public Vector3 dragBeginPos;
[Header("Drag Behaviour Setting")]
[SerializeField]
protected int reachableSteps = 1;
public int ReachableSteps => reachableSteps;
[Header("Click Behaviour Setting")]
[SerializeField]
[Tooltip("点击的灵敏度,单位 s,表示在这个时间范围内完成按下松开则判断为点击")]
protected float clickSensitivity = 0.2f;
[SerializeField]
[Tooltip("玩家本次按下松开之间的时间间隔")]
protected float clickInterval;
[SerializeField]
[Tooltip("是否正在计时")]
protected bool isCounting;
[SerializeField]
[Tooltip("是否处于被点击状态")]
public bool isClicked;
protected void Start()
{
dragBeginPos = transform.position;
}
protected override void OnPressDownChess()
{
base.OnPressDownChess();
// 这里使用虚函数是因为,是否能够被拖动,除了一个 canDrag 布尔值之外还可能有其他逻辑
// 例如道具方块是否能移动,还取决于它的数量是否大于 0
if(!CanBeginDrag())
return;
isCounting = true;
clickInterval = 0f;
}
protected override void OnPressUpChess()
{
base.OnPressUpChess();
if (!canDrag)
return;
isCounting = false;
// 判定为点击
if (clickInterval < clickSensitivity)
{
if(!isClicked) DragChessGameCue.Instance.BeginClick(this);
else DragChessGameCue.Instance.BreakClick(this);
}
// 如果正在拖动棋子,那么终止拖动
if (isDraging) EndDrag();
}
protected void FixedUpdate()
{
if(isCounting)
{
clickInterval += Time.fixedDeltaTime;
if (clickInterval > clickSensitivity)
{
isCounting = false;
if (isClicked)
{
isClicked = false;
}
BeginDrag();
}
}
}
#region Check if can begin darg
protected virtual bool CanBeginDrag()
{
return canDrag;
}
#endregion
#region Drag Behaviour
private void BeginDrag()
{
isDraging = true;
dragBeginPos = transform.position;
// DragChessGameCue
DragChessGameCue.Instance.BeginDrag(this);
}
private void EndDrag()
{
// drag setting
isDraging = false;
// DragChessGameCue
DragChessGameCue.Instance.EndDrag();
}
#endregion
// 提供给资源方块或者道具方块,拖动到某个位置时判断该怎么行动
public virtual IEnumerator WaitForDragTo(Vector3 worldChessPos)
{
yield return null;
}
// 返回原位置
// 不结束玩家可拖动回合
public IEnumerator WaitForReturnPos()
{
// return last pos
yield return StartCoroutine(ChessTweenHelper.WaitForMoveTo(transform, dragBeginPos));
// continue to drag other chess
ChessGameCue.Instance.SetAllResChessesCanDrag(true);
}
}
表现方面的一部分逻辑如下
DragChessGameCue.cs
using System;
using System.Collections;
using DG.Tweening;
using UnityEngine;
using Universal;
public class DragChessGameCue : Singleton<DragChessGameCue>
{
[SerializeField]
[Tooltip("被点选的/正在拖动着的棋子")]
private DraggableChess currChess;
private void Update()
{
OnClicking();
OnDragging();
OnShowingDetailsUI();
}
#region Click Behaviour
[SerializeField]
[Tooltip("是否正在点选")]
private bool isClicking;
public event Action OnClickBegin;
public event Action OnClickEnd;
public void BeginClick(DraggableChess chess)
{
isClicking = true;
chess.isClicked = true;
if (currChess != chess) BreakClick(currChess);
print("BeginClick");
// 显示在上层
chess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 1;
ShowReachableTint(chess.transform.position, chess.ReachableSteps);
currChess = chess;
}
public void BreakClick(DraggableChess chess)
{
if (!currChess) return;
isClicking = false;
chess.isClicked = false;
print("BreakClick");
// 恢复
chess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 0;
HideReachableTint();
currChess = null;
}
private void EndClick(Vector3 worldChessPos)
{
print("EndClick");
isClicking = false;
currChess.isClicked = false;
HideReachableTint();
// 恢复
currChess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 0;
// 禁用玩家输入
ChessGameCue.Instance.SetAllResChessesCanDrag(false);
Chessboard.Instance.StartCoroutine(currChess.WaitForDragTo(worldChessPos));
currChess = null;
}
private void OnClicking()
{
if (!isClicking) return;
if (!currChess) return;
// 如果点击到了其他位置,就
if (Input.GetMouseButtonDown(0))
{
Vector3 targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 worldChessPos = ChessPosConverter.WorldPosToWorldChessPos(targetPos);
if ((currChess.transform.position - worldChessPos).magnitude >= 1.0f)
{
EndClick(worldChessPos);
}
}
}
#endregion
在这里其实也是变屎了,因为在表现的脚本中也出现了判定点击相关的逻辑
这个主要是因为,一开始我是纯在棋子 mono 用用 collider 2d 的鼠标检测来管理拖动
但是现在关系到了鼠标点击之后,单个棋子就无法捕捉到棋子之外的点击
于是就变成了丑陋的样子
并且简单的写了一下还会有棋子自己的判定与 GameCue 的判定杂糅的情况
于是玩家输入就真的要独立出来了
但是这个时候还是需要一个事件来告知什么时候开始点击/触摸,什么时候按住鼠标/保持触摸,什么时候松开鼠标/结束触摸
如果直接用 if (Input.GetMouseButtonDown(0)) 的话,首先局限在 Windows 不说,那还要自己写开始保持结束的函数
于是我想到,能不能用一个大的 Collider 2D 来记录
OnMouseDrag 测试
但是我有一个问题是,如果 Collider 2d 不动,鼠标一开始点在 Collider 2d 上面,然后鼠标拖动拖出这个 Collider 2d 的范围,那么 OnMouseDrag 是否还会生效
OnMouseDrag 测试
using UnityEngine;
public class TestMouseHold : MonoBehaviour
{
private void OnMouseDrag()
{
Debug.Log("On Mouse Drag!");
}
}
实际测试是会生效的
一大块的 Collider 2d 作为接受鼠标输入的区域不太可靠
但是实践中发现 Collider 2d 作为接受鼠标输入的区域是不太可靠的,有的时候,上一次触发了 OnMouseDown,下一次在相近的位置上就不能触发 OnMouseDown 了
独立的 PlayerInputController 尝试
这一版的 Ctr 感觉思路上大概是这么一个意思,但是总是会有微小的 BUG
public class PlayerInputController : Singleton<PlayerInputController>
{
[Header("Status")]
[Tooltip("是否接受玩家输入")]
private bool canPlayerInput;
public bool CanPlayerInput
{
set => canPlayerInput = value;
}
[SerializeField]
[Tooltip("是否处于被点击状态")]
public bool isClicking;
[SerializeField]
[Tooltip("是否正在拖动")]
private bool isDragging;
[SerializeField]
[Tooltip("被点选的/正在拖动着的棋子")]
private DraggableChess currChess;
[Header("Click Behaviour Setting")]
[SerializeField]
[Tooltip("点击的灵敏度,单位 s,表示在这个时间范围内完成按下松开则判断为点击")]
private float clickSensitivity = 0.6f;
[SerializeField]
[Tooltip("玩家本次按下松开之间的时间间隔")]
private float clickInterval;
[SerializeField]
[Tooltip("是否正在计时")]
private bool isCounting;
[Header("Drag Behaviour Setting")]
[SerializeField]
[Tooltip("拖拽开始的位置")]
private Vector3 dragBeginPos;
private void Update()
{
if (Input.GetMouseButtonDown(0)) OnMyMouseDown();
else if(Input.GetMouseButtonUp(0)) OnMyMouseUp();
else ClickOrDrag();
}
private void OnMyMouseDown()
{
isCounting = true;
clickInterval = 0f;
print("OnMouseDown");
}
private void OnMyMouseUp()
{
if (!canPlayerInput)
return;
isCounting = false;
// 判定为点击
if (clickInterval < clickSensitivity && !isDragging)
{
clickInterval = 0f;
// 如果不是正在点选,那么点选一个棋子
if(!isClicking)
{
BeginClick(RaycastMovableChess());
}
// 如果正在点选,那么判断点选的新棋子的情况
else
{
BreakClick();
}
}
// 如果正在拖动棋子,那么终止拖动
if (isDragging)
{
clickInterval = 0f;
EndDrag();
}
}
private void ClickOrDrag()
{
if(isCounting)
{
clickInterval += Time.deltaTime;
if (clickInterval > clickSensitivity)
{
isCounting = false;
isClicking = false;
BeginDrag(RaycastMovableChess());
}
}
}
于是我把 Update 那里改了一下,主要是梳理了一下激活点击或者拖动的逻辑
现在基本上是能够正常运行点击和拖拽了
using System;
using UnityEngine;
using Universal;
public class PlayerInputController : Singleton<PlayerInputController>
{
[Header("Status")]
[SerializeField]
[Tooltip("是否接受玩家输入")]
private bool canPlayerInput;
public bool CanPlayerInput
{
set => canPlayerInput = value;
}
[SerializeField]
[Tooltip("是否处于被点击状态")]
public bool hasClickOnce;
[SerializeField]
[Tooltip("是否正在拖动")]
private bool isDragging;
[SerializeField]
[Tooltip("被点选的/正在拖动着的棋子")]
private DraggableChess currChess;
[Header("Click Behaviour Setting")]
[SerializeField]
[Tooltip("点击的灵敏度,单位 s,表示在这个时间范围内完成按下松开则判断为点击")]
private float clickSensitivity = 0.2f;
[SerializeField]
[Tooltip("玩家本次按下松开之间的时间间隔")]
private float clickInterval;
[SerializeField]
[Tooltip("是否正在计时")]
private bool isCounting;
[Header("Drag Behaviour Setting")]
[SerializeField]
[Tooltip("输入刚开始时鼠标点击的世界格子位置")]
private Vector3 inputBeginPos;
private void Update()
{
if (!canPlayerInput)
return;
if (Input.GetMouseButtonDown(0))
{
isCounting = true;
clickInterval = 0f;
Vector3 targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
inputBeginPos = ChessPosConverter.WorldPosToWorldChessPos(targetPos);
}
else if (Input.GetMouseButtonUp(0))
{
// 如果点击和松开之间的时间小于限度,并且不是正在拖动棋子
// 判定为点击
if (clickInterval < clickSensitivity && !isDragging) TryBeginClick();
// 如果不是判定为点击,那么就判定为取消拖拽
else
{
isDragging = false;
EndDrag();
}
isCounting = false;
clickInterval = 0f;
}
if (isCounting)
{
clickInterval += Time.deltaTime;
}
if (clickInterval > clickSensitivity && !isDragging)
{
hasClickOnce = false;
BeginDrag(RaycastMovableChess());
}
}
private void TryBeginClick()
{
// 如果不是正在点选,那么点选一个棋子
if(!hasClickOnce) BeginClick(RaycastMovableChess());
// 如果正在点选,那么判断点选的新棋子的情况
else BreakClick();
}
#region Click Behaviour
public event Action<DraggableChess> OnClickBegin;
public event Action<DraggableChess> OnClickBreak;
private void BeginClick(DraggableChess chess)
{
if (!chess) return;
OnClickBegin?.Invoke(chess);
hasClickOnce = true;
print("BeginClick");
// 显示在上层
chess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 1;
currChess = chess;
}
private void BreakClick()
{
if (!currChess) return;
OnClickBreak?.Invoke(currChess);
hasClickOnce = false;
print("BreakClick");
// 恢复
currChess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 0;
// 不论被点击到的第二个棋子是否为空,都尝试移动到那里
Vector3 targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 worldChessPos = ChessPosConverter.WorldPosToWorldChessPos(targetPos);
// 禁用玩家输入
canPlayerInput = false;
Chessboard.Instance.StartCoroutine(currChess.WaitForDragTo(worldChessPos, currChess.transform.position));
currChess = null;
}
#endregion
#region Drag Behaviour
public event Action<DraggableChess> OnDragBegin;
public event Action<DraggableChess> OnDragEnd;
private DraggableChess RaycastMovableChess()
{
// 通过射线检测,获取鼠标开始点击时的位置下的棋子
DraggableChess dragChess = ChessRaycastHelper.RaycastChess(inputBeginPos) as DraggableChess;
if (!dragChess) return null;
if (!dragChess.CanMove()) return null;
return dragChess;
}
private void BeginDrag(DraggableChess chess)
{
if (!chess) return;
OnDragBegin?.Invoke(chess);
isDragging = true;
// 有些棋子的位置可能不是世界格子坐标,例如道具棋子
// 因此在这里再更新一下 inputBeginPos
inputBeginPos = chess.transform.position;
// 显示在上层
chess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 1;
currChess = chess;
}
private void EndDrag()
{
if (!currChess) return;
print("EndDrag");
OnDragEnd?.Invoke(currChess);
isDragging = false;
// 恢复显示在下层
currChess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 0;
// 禁用玩家输入
canPlayerInput = false;
// drag behaviour
Vector3 targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 worldChessPos = ChessPosConverter.WorldPosToWorldChessPos(targetPos);
Debug.Log(worldChessPos);
// 最主要是这个,开始判断被拖动到的位置是怎样的,该怎么行动
// 不是棋子自己启动协程,因为棋子自己可能在完成拖放动作的时候会被送入对象池,enable = false
// 这会导致棋子自己的协程停止
Chessboard.Instance.StartCoroutine(currChess.WaitForDragTo(worldChessPos, inputBeginPos));
currChess = null;
}
#endregion
}
但是这一版有一个问题是,在一轮移动操作结束之后,再次点击,有可能会出现,明明点击的是位置 B,但是却看上去是点到了位置 A 的情况
这里的问题应该是在判断点击到的棋子的位置时,使用的是上一次的 mouseButtonDownPos,但是上一次点击结束时,可能这一次点击直接进入了 else if (Input.GetMouseButtonUp(0)),因此就没有进入更新 mouseButtonDownPos 的 if (Input.GetMouseButtonDown(0)) 块,那么这个时候 mouseButtonDownPos 保存的仍然是上一次的位置
于是现在改成,对于点击事件,比如要用当前鼠标的位置来打射线,就 ok 了
允许棋子点击移动和长按拖动的 PlayerInputController
总结的没有 BUG 的 Ctr
using System;
using UnityEngine;
using Universal;
public class PlayerInputController : Singleton<PlayerInputController>
{
[Header("Status")]
[SerializeField]
[Tooltip("是否接受玩家输入")]
private bool canPlayerInput;
public bool CanPlayerInput
{
set => canPlayerInput = value;
}
[SerializeField]
[Tooltip("是否处于被点击状态")]
public bool hasClickOnce;
[SerializeField]
[Tooltip("是否正在拖动")]
private bool isDragging;
[SerializeField]
[Tooltip("被点选的/正在拖动着的棋子")]
private DraggableChess currChess;
[Header("Click Behaviour Setting")]
[SerializeField]
[Tooltip("点击的灵敏度,单位 s,表示在这个时间范围内完成按下松开则判断为点击")]
private float clickSensitivity = 0.2f;
[SerializeField]
[Tooltip("玩家本次按下松开之间的时间间隔")]
private float clickInterval;
[SerializeField]
[Tooltip("是否正在计时")]
private bool isCounting;
[Header("Drag Behaviour Setting")]
[SerializeField]
[Tooltip("鼠标按下的世界格子位置")]
private Vector3 mouseButtonDownPos;
[SerializeField]
[Tooltip("拖拽开始时棋子的位置")]
private Vector3 dragChessBeginPos;
private void Update()
{
if (!canPlayerInput)
return;
if (Input.GetMouseButtonDown(0))
{
isCounting = true;
clickInterval = 0f;
Vector3 targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mouseButtonDownPos = ChessPosConverter.WorldPosToWorldChessPos(targetPos);
}
else if (Input.GetMouseButtonUp(0))
{
// 如果点击和松开之间的时间小于限度,并且不是正在拖动棋子
// 判定为点击
if (clickInterval < clickSensitivity && !isDragging) TryBeginClick();
// 如果不是判定为点击,那么就判定为取消拖拽
else
{
isDragging = false;
EndDrag();
}
isCounting = false;
clickInterval = 0f;
}
if (isCounting)
{
clickInterval += Time.deltaTime;
}
if (clickInterval > clickSensitivity && !isDragging)
{
hasClickOnce = false;
BeginDrag(RaycastMovableChess(mouseButtonDownPos));
}
}
private void TryBeginClick()
{
// 如果不是正在点选,那么点选一个棋子
if(!hasClickOnce) BeginClick(RaycastMovableChess(Vector3.negativeInfinity));
// 如果正在点选,那么判断点选的新棋子的情况
else BreakClick();
}
#region Click Behaviour
public event Action<DraggableChess> OnClickBegin;
public event Action<DraggableChess> OnClickBreak;
private void BeginClick(DraggableChess chess)
{
if (!chess) return;
OnClickBegin?.Invoke(chess);
hasClickOnce = true;
print("BeginClick");
// 显示在上层
chess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 1;
currChess = chess;
}
private void BreakClick()
{
if (!currChess) return;
OnClickBreak?.Invoke(currChess);
hasClickOnce = false;
print("BreakClick");
// 恢复
currChess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 0;
// 不论被点击到的第二个棋子是否为空,都尝试移动到那里
Vector3 targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 worldChessPos = ChessPosConverter.WorldPosToWorldChessPos(targetPos);
// 禁用玩家输入
canPlayerInput = false;
Chessboard.Instance.StartCoroutine(currChess.WaitForDragTo(worldChessPos, currChess.transform.position));
currChess = null;
}
#endregion
#region Drag Behaviour
public event Action<DraggableChess> OnDragBegin;
public event Action<DraggableChess> OnDragEnd;
private DraggableChess RaycastMovableChess(Vector3 raycastPos)
{
// 约定 raycastPos 为 Vector3.negativeInfinity 时表示使用当前鼠标位置作为射线检测起点
// 使用向量长度作为判断是否是 Vector3.negativeInfinity
if (raycastPos.magnitude > 10000f)
{
Vector3 targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
raycastPos = ChessPosConverter.WorldPosToWorldChessPos(targetPos);
}
// 通过射线检测,获取鼠标开始点击时的位置下的棋子
DraggableChess dragChess = ChessRaycastHelper.RaycastChess(raycastPos) as DraggableChess;
if (!dragChess) return null;
if (!dragChess.CanMove()) return null;
return dragChess;
}
private void BeginDrag(DraggableChess chess)
{
if (!chess) return;
OnDragBegin?.Invoke(chess);
isDragging = true;
// 有些棋子的位置可能不是世界格子坐标,例如道具棋子
// 因此 mouseButtonDownPos 和 dragChessBeginPos 是不同的
dragChessBeginPos = chess.transform.position;
// 显示在上层
chess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 1;
currChess = chess;
}
private void EndDrag()
{
if (!currChess) return;
print("EndDrag");
OnDragEnd?.Invoke(currChess);
isDragging = false;
// 恢复显示在下层
currChess.GetComponentInChildren<SpriteRenderer>().sortingOrder = 0;
// 禁用玩家输入
canPlayerInput = false;
// drag behaviour
Vector3 targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 worldChessPos = ChessPosConverter.WorldPosToWorldChessPos(targetPos);
Debug.Log(worldChessPos);
// 最主要是这个,开始判断被拖动到的位置是怎样的,该怎么行动
// 不是棋子自己启动协程,因为棋子自己可能在完成拖放动作的时候会被送入对象池,enable = false
// 这会导致棋子自己的协程停止
Chessboard.Instance.StartCoroutine(currChess.WaitForDragTo(worldChessPos, dragChessBeginPos));
currChess = null;
}
#endregion
}