1 需求实现
绘制魔方 中基于OpenGL ES 实现了魔方的绘制,实现较复杂,本文基于 Unity3D 实现了 2 ~ 10 阶魔方的整体旋转和局部旋转,详细需求如下:
- 用户通过选择魔方阶数,渲染指定阶数的魔方,并且可以自动打乱魔方;
- 用户通过 Scroll 或 Ctrl + Scroll,控制魔方放大和缩小;
- 用户通过 Drag 空白处(或 Ctrl + Drag,或 Alt + Drag,或右键 Drag,或 Ctrl + 右键 Drag,或方向键),控制魔方整体旋转;
- 用户通过 Drag 魔方相邻的两个方块,实现旋转该层(局部旋转);
- 从魔方界面点击返回按钮,可以返回到选择阶数界面;
- 用户每进行一次局部旋转,记步加 1;
- 显示计时器。
选择阶数界面如下:
魔方界面如下:
本文完整代码资源见→基于 Unity3D 的 2 ~ 10 阶魔方实现。
2 原理介绍
2.1 渲染原理
在 Hierarchy 窗口新建一个空对象,重命名为 Cube,在 Cube 下创建 6 个 Quad 对象,分别重命名为 Left (x = -0.5)、Right (x = 0.5)、Down (y = -0.5)、Up (y = 0.5)、Back (z = -0.5)、Forward (z = 0.5),调整位置和旋转角度,使得它们围成一个小立方体,将 Cube 拖拽到 Assets 窗口作为预设体。
在创建一个 order 阶魔方时,新建一个空对象,重命名为 Rubik,复制 order^3 个 Cube 作为 Rubik 的子对象,调整所有 Cube 的位置使其拼成魔方结构,根据立方体和方块位置,为每个方块设置纹理图片,如下:
说明:对于任意小方块 Square,Square.forward 始终指向小立方体中心,该结论在旋转层检测中会用到。
2.2 整体旋转原理
通过调整相机前进和后退,控制魔方放大和缩小;通过调整相机的位置和姿态,使得相机绕魔方旋转,实现魔方整体旋转。详情见缩放、平移、旋转场景。
2.3 旋转层检测原理
1)旋转层编码
旋转层由旋转轴和层序两个变量决定,本文定义了 RubikLayer 类描述旋转层,里面包含 axis (旋转轴)和 seq (层序)两个变量。axis 取值有 0、1、2,分别表示 x 轴、y 轴、z 轴,seq 取值有 0 ~ (order - 1),order 表示魔方阶数。
2)旋转轴检测
假设屏幕射线检测到的两个相邻方块分别为 square1、square2。
- 如果 square1 与 square2 在同一个小立方体里,square1.forward 与 square2.forward 叉乘的向量就是旋转轴方向向量;
- 如果 square1 与 square2 在相邻小立方体里,square1.forward 与 (square1.position - square2.position) 叉乘的向量就是旋转轴方向向量;
假设叉乘后的向量的单位向量为 crossDir,我们将 crossDir 与每个坐标轴的单位方向向量(假设为 axisDir)进行点乘,如果 Dot(crossDir, axisDir) > 0.99(夹角小于 8°),就选取该轴作为旋转轴,如果每个轴的点乘结果都小于 0.99,说明屏幕射线拾取的两个方块不在同一旋转层,舍弃局部旋转。
3)层序检测
坐标分量与层序的映射关系如下,其中 order 为魔方阶数,seq 为层序,pos 为坐标分量,cubeSide 为小立方体的边长。由于频繁使用到 pos 与 seq 的映射,建议将 0 ~ order 层的层序 seq 对应的 pos 存储在数组中,方便快速查找。
square1 与 square2 在旋转轴方向上的坐标分量一致,假设为 pos(如果旋转轴是 x 轴,pos 指 square1 或 square1 中心坐标的 x 值),由上述公式就可以推导出层序 seq。
2.4 局部旋转原理
1)待旋转的小立方体检测
对于每个小立方体,使用数组 loc[] 存储了小立方体在 x、y、z 轴方向上的层序,每次旋转结束后,根据小立方体的中心坐标可以重写计算出 loc 数组。
假设检测到的旋转轴为 axis,旋转层为 seq,所有 loc[axis] 等于 seq 的小立方体都是需要旋转的小立方体。
2)局部旋转
在 Rubik 对象下创建一个空对象,重命名为 RotateLayer,将 RotateLayer 移至坐标原点,旋转角度全部置 0。
将处于旋转层的小立方体的 parent 都设置为 RotateLayer,对 RotateLayer 进行旋转,旋转结束后,将这些小立方体的 parent 重置为 Rubik,RotateLayer 的旋转角度重置为 0,根据小立方体中心的 position 更新 loc 数组。
2.5 魔方打乱原理
选择阶数后,渲染一个已经拼好的魔方,随机选择一个旋转轴和旋转层序,随机旋转 90° 的整数倍,实现打乱魔方。随机旋转次数建议不少于 order^3 次,约束下限和上限分别为:100、400。
为了保持用户玩魔方的习惯(如:多数用户习惯蓝色中心朝上,绿色中心朝下),建议随机旋转时不要旋转中间层,如:3 阶魔方的第 2 层,4 阶魔方的第 2 层,5 阶魔方的第 3 层。
3 代码实现
3.1 代码结构
3.2 魔方结构
Rubik.cs
using UnityEngine;
/*
* 魔方
*/
public class Rubik
{
public Transform self; // 魔方对象
private static RubikInfo rubikInfo; // 魔方信息
private Cube[][][] cubes; // 立方体
public Rubik(Transform self)
{
this.self = self;
Init();
}
public static void SetRubikInfo(RubikInfo rubikInfo)
{ // 设置魔方信息
Rubik.rubikInfo = rubikInfo;
}
public static RubikInfo Info()
{ // 获取魔方信息
return rubikInfo;
}
public void Rotate(RubikLayer rubikLayer, float angle)
{ // 旋转魔方
}
private void Init()
{ // 初始化
if (rubikInfo == null)
{
return;
}
int index = 0;
cubes = new Cube[rubikInfo.order][][];
for (int i = 0; i < cubes.Length; i++)
{
cubes[i] = new Cube[rubikInfo.order][];
for (int j = 0; j < cubes[i].Length; j++)
{
cubes[i][j] = new Cube[rubikInfo.order];
for (int k = 0; k < cubes[i][j].Length; k++)
{
Transform cube = GameObject.Instantiate(RubikRes.CUBE_PREFAB).transform;
cubes[i][j][k] = new Cube(cube, index++);
cube.SetParent(self);
}
}
}
}
public Cube[] GetRotateCubes(RubikLayer rubikLayer)
{ // 获取旋转的小立方体
int index = 0;
Cube[] rotateCubes = new Cube[rubikInfo.order2];
for (int i = 0; i < cubes.Length; i++)
{
for (int j = 0; j < cubes[i].Length; j++)
{
for (int k = 0; k < cubes[i][j].Length; k++)
{
if (cubes[i][j][k].loc[rubikLayer.axis] == rubikLayer.seq)
{
rotateCubes[index++] = cubes[i][j][k];
}
}
}
}
return rotateCubes;
}
public void Destroy()
{ // 销毁魔方
if (cubes == null)
{
return;
}
for (int i = 0; i < cubes.Length; i++)
{
for (int j = 0; j < cubes[i].Length; j++)
{
for (int k = 0; k < cubes[i][j].Length; k++)
{
cubes[i][j][k].Destroy();
}
}
}
cubes = null;
self = null;
}
}
Cube.cs
using UnityEngine;
/*
* 小立方体
* squares的索引对应的面: 0-left, 1-right, 2-down, 3-up, 4-back, 5-forward
*/
public class Cube
{
// 小立方体的位置序号(以魔方的左下后为坐标原点, 向右、向上、向前分别为x轴、y轴、z轴, 小立方体的边长为单位刻度)
public int[] loc;
public Transform self; // 小立方体对象
private const int COUNT = 6; // 面数
private int id; // 唯一标识, 根据初始位置进行编码
private Transform[] squares; // 每个面的方块
private Texture[] textures; // 每个面的纹理图片
public Cube(Transform self, int id)
{
this.self = self;
this.id = id;
GetSquars();
GetLoc();
GetPos();
GetTextures();
}
public void UpdateLoc()
{ // 更新小立方体的位置序号
loc = Rubik.Info().GetSeq(self.position);
}
public void Destroy()
{ // 销毁对象
if (squares == null)
{
return;
}
for (int i = 0; i < COUNT; i++)
{
GameObject.Destroy(squares[i].gameObject);
}
squares = null;
GameObject.Destroy(self.gameObject);
self = null;
}
private void GetSquars()
{ // 获取方块
squares = new Transform[COUNT];
for (int i = 0; i < COUNT; i++)
{
squares[i] = self.GetChild(i);
}
}
private void GetLoc()
{ // 获取小立方体的初始位置(以魔方的左下后为坐标原点, 向右、向上、向前分别为x轴、y轴、z轴, 小立方体的边长为单位刻度)
loc = new int[3];
loc[0] = id % Rubik.Info().order2 % Rubik.Info().order;
loc[1] = id % Rubik.Info().order2 / Rubik.Info().order;
loc[2] = id / Rubik.Info().order2;
self.name = "Cube-" + loc[0] + "_" + loc[1] + "_" + loc[2];
}
private void GetPos()
{ // 小立方体的中心坐标
self.localScale = Vector3.one * Rubik.Info().cubeSide;
self.position = Rubik.Info().GetPos(loc);
}
private void GetTextures()
{ // 获取纹理
textures = new Texture[COUNT];
for (int i = 0; i < COUNT; i++)
{
textures[i] = RubikRes.INSET_TEXTURE;
}
for(int i = 0; i < COUNT; i++)
{
int axis = i / 2;
if (loc[axis] == 0 && i == axis * 2 || loc[axis] == Rubik.Info().order - 1 && i == axis * 2 + 1)
{
textures[i] = RubikRes.TEXTURES[i];
}
squares[i].GetComponent<Renderer>().material.mainTexture = textures[i];
}
}
}
3.3 魔方信息
RubikRes.cs
using UnityEngine;
/*
* 魔方资源
*/
public class RubikRes
{
// 小立方体预设体
public static readonly GameObject CUBE_PREFAB = Resources.Load<GameObject>("Prefabs/Cube");
// 魔方外部小方块的纹理
public static readonly Texture[] TEXTURES = new Texture[] {
Resources.Load<Texture>("Textures/red"), // 0-left
Resources.Load<Texture>("Textures/orange"), // 1-right
Resources.Load<Texture>("Textures/green"), // 2-down
Resources.Load<Texture>("Textures/blue"), // 3-up
Resources.Load<Texture>("Textures/white"), // 4-back
Resources.Load<Texture>("Textures/yellow"), // 5-forward
};
// 魔方内部小方块的纹理
public static readonly Texture INSET_TEXTURE = Resources.Load<Texture>("Textures/inside");
}
RubikInfo.cs
using UnityEngine;
/*
* 魔方信息
* 依赖于魔方阶数和尺寸的相关固定信息, 避免重复计算, 节省性能
*/
public class RubikInfo
{
public int order = 3; // 魔方阶数
public int order2 = 9; // 魔方阶数的平方
public float size = 3f; // 魔方尺寸
public float cubeSide = 1f; // 魔方中小立方体的边长
public float halfCubeSide = 0.5f; // 魔方中小立方体的半边长
public float quaCubeSide = 0.25f; // 魔方中小立方体的四分之一边长
public float offset = 1f; // 位置与坐标的偏移
public int shuffle = 27; // 打乱魔方需要旋转的次数
public int centerSeq = 1; // 中心序号
private float[] seqPos; // 某轴(x、y或z轴)方向序列的位置
public RubikInfo() {}
public RubikInfo(int order)
{
this.order = order;
InitOtherValue();
}
public RubikInfo(int order, float size)
{
this.order = order;
this.size = size;
InitOtherValue();
}
public float GetPos(int seq)
{ // 获取seq对应的pos
return seqPos[seq];
}
public Vector3 GetPos(int[] seq)
{ // 获取seq对应的pos
return new Vector3(seqPos[seq[0]], seqPos[seq[1]], seqPos[seq[2]]);
}
public int GetSeq(float pos)
{ // 获取pos对应的seq
int seq1 = (int) (pos / cubeSide + offset);
float len1 = float.MaxValue;
if (seq1 >=0 && seq1 < order)
{
len1 = Mathf.Abs(seqPos[seq1] - pos);
}
int seq2 = seq1 + 1;
float len2 = float.MaxValue;
if (seq2 >=0 && seq2 < order)
{
len2 = Mathf.Abs(seqPos[seq2] - pos);
}
if (len1 < len2 && len1 < quaCubeSide)
{
return seq1;
}
if (len2 < len1 && len2 < quaCubeSide)
{
return seq2;
}
return -1;
}
public int[] GetSeq(Vector3 pos)
{ // 获取pos对应的seq
int[] seq = new int[3];
for (int i = 0; i < 3; i++)
{
seq[i] = GetSeq(pos[i]);
}
return seq;
}
private void InitOtherValue()
{ // 初始化其他变量
order2 = order * order;
cubeSide = size / order;
halfCubeSide = cubeSide / 2;
quaCubeSide = halfCubeSide / 2;
shuffle = Mathf.Min(Mathf.Max(order * order2, 100), 400);
centerSeq = order / 2;
GetSeqPos();
}
private void GetSeqPos()
{ // 获取序列位置
offset = (order - 1) / 2f;
seqPos = new float[order];
for (int i = 0; i < order; i++)
{
seqPos[i] = (i - offset) * cubeSide;
}
}
}
RubikLayer.cs
using System;
/*
* 魔方层 / 旋转层
* 由旋转轴和层序决定
*/
public class RubikLayer
{
public int axis; // 旋转轴, 0-x, 1-y, 2-z
public int seq; // 层序
private static int lastAxis = -1; // 上一次的随机旋转轴
public RubikLayer() {}
public RubikLayer(int axis, int seq)
{
this.axis = axis;
this.seq = seq;
}
public bool Equals(RubikLayer other)
{ // 判断两个旋转层是否相等
return other != null && axis == other.axis && seq == other.seq;
}
public static RubikLayer GetRandomRubikLayer(Random rnd)
{
RubikLayer rubikLayer = new RubikLayer();
int axis = rnd.Next(0, 3);
while (axis == lastAxis)
{
axis = rnd.Next(0, 3);
}
lastAxis = rubikLayer.axis;
rubikLayer.axis = axis;
rubikLayer.seq = rnd.Next(0, Rubik.Info().order);
while (rubikLayer.seq == Rubik.Info().centerSeq)
{
rubikLayer.seq = rnd.Next(0, Rubik.Info().order);
}
return rubikLayer;
}
}
3.4 魔方启动器
RubikStarter.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
/*
* 魔方启动器
* 负责启动魔方、更新步数、更新时间
*/
public class RubikStarter : MonoBehaviour
{
private static RubikStarter instance; // 实体
private Rubik rubik; // 魔方
private Button backButton; // 返回按钮
private Text timeText; // 时间文本
private Text stepText; // 步数文本
private int time; // 时间
private int step = 0; // 步数
private WaitForSeconds waitForSeconds;
public static RubikStarter Instance()
{ // 获取实例
return instance;
}
public void IncreaseStep()
{ // 增加步数
step++;
stepText.text = step.ToString();
}
private void Awake()
{
instance = this;
gameObject.layer = LayerMask.NameToLayer("Rubik");
rubik = new Rubik(transform);
backButton = GameObject.Find("Canvas/Back").GetComponent<Button>();
timeText = GameObject.Find("Canvas/Time").GetComponent<Text>();
stepText = GameObject.Find("Canvas/Step").GetComponent<Text>();
backButton.onClick.AddListener(OnBack);
waitForSeconds = new WaitForSeconds(1);
}
private void Start()
{
RotateController.Instance()?.SetRubik(rubik);
RotateController.Instance()?.Shuffle();
StartCoroutine(UpdateTime());
}
private IEnumerator UpdateTime()
{ // 更新时间
while(true)
{
time += 1;
int hour = time / 3600;
int minus = time % 3600 / 60;
int second = time % 60;
timeText.text = hour.ToString("00") + ":" + minus.ToString("00") + ":" + second.ToString("00");
yield return waitForSeconds;
}
}
private void OnBack()
{ // 返回按钮事件回调
if (rubik != null)
{
rubik.Destroy();
}
SceneManager.LoadScene("Select");
}
}
说明:RubikStarter 脚本挂在 Rubik 对象上。
3.5 整体旋转
CameraController.cs
using UnityEngine;
/*
* 相机控制(整体旋转)
* 缩放场景: Scroll/CtrlScroll
* 旋转场景: Drag空白处/CtrlDrag/AltDrag/RightDrag/CtrlRightDrag/方向键
*/
public class CameraController : MonoBehaviour
{
private Transform cam; // 相机
private void Awake()
{
cam = Camera.main.transform;
}
private void Update()
{
ScaleScene();
RotateScene();
}
private void ScaleScene()
{ // 缩放场景
if (GlobalEvent.IsOnlyScrollEvent() || GlobalEvent.IsCtrlScrollEvent())
{
cam.position += cam.forward * GlobalEvent.scroll * 3;
}
}
private void RotateScene()
{ // 旋转场景(Drag空白处/CtrlDrag/AltDrag/RightDrag/CtrlRightDrag/方向键)
if (!(GlobalEvent.IsOnlyDragEvent(0) && GlobalEvent.target == null)
&& !GlobalEvent.IsCtrlDragEvent(0) && !GlobalEvent.IsAltDragEvent(0)
&& !GlobalEvent.IsOnlyDragEvent(1) && !GlobalEvent.IsCtrlDragEvent(1)
&& !GlobalEvent.IsDireKeyEvent())
{
return;
}
Vector2 offset = GetRotateOffset();
if (!offset.Equals(Vector2.zero))
{
cam.RotateAround(Vector3.zero, Vector3.up, offset.x); // 相机绕旋转中心水平旋转
cam.RotateAround(Vector3.zero, cam.right, offset.y); // 相机绕旋转中心竖直旋转
}
}
private Vector2 GetRotateOffset()
{ // 获取旋转偏移量
Vector2 offset = Vector2.zero;
if (Mathf.Abs(GlobalEvent.mouseMove.x) > float.Epsilon || Mathf.Abs(GlobalEvent.mouseMove.y) > float.Epsilon)
{
offset = new Vector2(GlobalEvent.mouseMove.x * 5, -GlobalEvent.mouseMove.y * 5);
}
else if (Mathf.Abs(GlobalEvent.direMove.x) > float.Epsilon || Mathf.Abs(GlobalEvent.direMove.y) > float.Epsilon)
{
offset = new Vector2(GlobalEvent.direMove.x * 1.5f, -GlobalEvent.direMove.y * 1.5f);
}
if (!offset.Equals(Vector2.zero))
{ // 当相机仰视观察魔方时, 水平旋转方向相反
offset.x *= Mathf.Sign(Vector3.Dot(Vector3.up, cam.up));
}
return offset;
}
}
说明:CameraController 脚本挂在相机对象上。
3.6 局部旋转
RotateController.cs
using UnityEngine;
/*
* 旋转控制器(局部旋转)
*/
public class RotateController : MonoBehaviour
{
private static RotateController instance; // 实例
private Rubik rubik; // 魔方
private Transform rotateLayer; // 旋转层对象
private Cube[] rotateCubes; // 需要旋转的小立方体
private Transform startSquare; // 开始滑动的方块
private Transform endSquare; // 结束滑动的方块
private RubikLayer rubikLayer; // 魔方层
private float angle; // 旋转角度
private Vector2 moveDire; // 滑动方向
private System.Random random;
public static RotateController Instance()
{ // 获取实例
return instance;
}
public void SetRubik(Rubik rubik)
{ // 设置魔方对象
this.rubik = rubik;
}
public void Shuffle()
{ // 打乱魔方
for (int i = 0; i < Rubik.Info().shuffle; i++)
{
rubikLayer = RubikLayer.GetRandomRubikLayer(random);
angle = random.Next(-1, 3) * 90;
GetRotateCubes();
Rotate(angle);
UpdateLoc();
ResetRotateCubes();
rubikLayer = null;
rotateCubes = null;
angle = 0;
}
}
private void Awake()
{
instance = this;
rotateLayer = gameObject.transform;
random = new System.Random();
}
private void Update()
{
if (GlobalEvent.eventType == MyEventType.BeginDrag && IsHitRubik())
{
startSquare = GlobalEvent.hitObj.transform;
}
else if (GlobalEvent.eventType == MyEventType.Drag)
{
if (startSquare != null && rubikLayer == null && IsHitRubik())
{
endSquare = GlobalEvent.hitObj.transform;
rubikLayer = RotateHelper.GetLayer(startSquare, endSquare);
GetRotateCubes();
if (rubikLayer != null)
{
moveDire = RotateHelper.GetPositiveScreenMoveDir(startSquare.position,
endSquare.position, -startSquare.forward, rubikLayer.axis);
}
}
else if (rubikLayer != null)
{
angle += Vector2.Dot(GlobalEvent.mouseMove, moveDire) * 12;
Rotate(angle);
}
}
else if (GlobalEvent.eventType == MyEventType.EndDrag && rubikLayer != null)
{
FinalRotate(angle);
UpdateLoc();
ResetRotateCubes();
RubikStarter.Instance()?.IncreaseStep();
startSquare = null;
endSquare = null;
rubikLayer = null;
rotateCubes = null;
angle = 0;
}
}
private void Rotate(float angle)
{ // 旋转
Vector3 axis, rotateStartDir, rotateEndDir;
GetRotateInfo(out axis, out rotateStartDir, out rotateEndDir);
float currAngle = Vector3.SignedAngle(rotateStartDir, rotateEndDir, axis);
rotateLayer.RotateAround(Vector3.zero, axis, angle - currAngle);
}
private void FinalRotate(float angle)
{ // 拖拽结束后, 更新旋转角度, 使得旋转层对齐魔方
angle = RotateHelper.GetNearAngle(angle);
Rotate(angle);
}
private void UpdateLoc()
{ // 更新小立方体的位置序号
if (rotateCubes != null)
{
for (int i = 0; i < rotateCubes.Length; i++)
{
rotateCubes[i].UpdateLoc();
}
}
}
private void GetRotateInfo(out Vector3 axis, out Vector3 rotateStartDir, out Vector3 rotateEndDir)
{ // 获取旋转轴
if (rubikLayer.axis == 0)
{
axis = Vector3.right;
rotateStartDir = Vector3.up;
rotateEndDir = rotateLayer.up;
}
else if (rubikLayer.axis == 1)
{
axis = Vector3.up;
rotateStartDir = Vector3.forward;
rotateEndDir = rotateLayer.forward;
}
else
{
axis = Vector3.forward;
rotateStartDir = Vector3.right;
rotateEndDir = rotateLayer.right;
}
}
private void GetRotateCubes()
{ // 获取需要旋转的小立方体
if (rubikLayer != null)
{
rotateCubes = rubik.GetRotateCubes(rubikLayer);
for (int i = 0; i < rotateCubes.Length; i++)
{
rotateCubes[i].self.SetParent(rotateLayer);
}
}
}
private void ResetRotateCubes()
{ // 恢复旋转的立方体
if (rotateCubes != null)
{
for (int i = 0; i < rotateCubes.Length; i++)
{
rotateCubes[i].self.SetParent(rubik.self);
}
}
rotateLayer.rotation = Quaternion.identity;
}
private bool IsHitRubik()
{ // 屏幕射线是否投射到魔方上
if (GlobalEvent.hitObj == null || GlobalEvent.hitObj.layer != LayerMask.NameToLayer("Rubik"))
{
return false;
}
return true;
}
}
说明:RotateController 挂在 RotateLayer 对象上。
RotateHelper.cs
using UnityEngine;
/*
* 旋转助手
* 分担了RotateController的部分功能
*/
public class RotateHelper
{
public static RubikLayer GetLayer(Transform square1, Transform square2)
{ // 获取两个方块所处的魔方图层
if (square1 == null || square2 == null || square1 == square2)
{
return null;
}
int axis = GetAxis(square1, square2);
if (axis < 0)
{
return null;
}
int seq = Rubik.Info().GetSeq(square1.position[axis]);
if (seq >= 0)
{
return new RubikLayer(axis, seq);
}
return null;
}
public static float GetNearAngle(float angle)
{ // 获取离angle较近的90度整数倍度数
if (Mathf.Abs(angle) < 45 || Mathf.Abs(angle) > 315)
{
return 0;
}
else if (Mathf.Abs(angle) < 135)
{
return Mathf.Sign(angle) * 90;
}
else if (Mathf.Abs(angle) < 225)
{
return 180;
}
return Mathf.Sign(angle) * 270;
}
public static Vector3 GetAxis(int axis)
{ // 根据轴编号获取对应的轴
switch(axis)
{
case 0:
return Vector3.right;
case 1:
return Vector3.up;
case 2:
return Vector3.forward;
}
return Vector3.zero;
}
public static Vector2 GetPositiveScreenMoveDir(Vector3 pos1, Vector3 pos2, Vector3 forward, int axis)
{ // 获取正向屏幕移动方向向量
Vector2 scrPos1 = Camera.main.WorldToScreenPoint(pos1);
Vector2 scrPos2 = Camera.main.WorldToScreenPoint(pos2);
Vector2 dir = (scrPos2 - scrPos1).normalized;
Vector3 axisDir = GetAxis(axis);
if (Vector3.Dot(Vector3.Cross(forward, pos2 - pos1), axisDir) < 0)
{
return -dir;
}
return dir;
}
private static int GetAxis(Transform square1, Transform square2)
{ // 获取square1和square2的共同轴
Vector3 dire = Vector3.zero;
if (square1.parent == square2.parent)
{
dire = Vector3.Cross(square1.forward, square2.forward).normalized;
}
else
{
dire = Vector3.Cross(square1.forward, square1.position - square2.position).normalized;
}
if (Mathf.Abs(Vector3.Dot(dire, Vector3.right)) > 0.99f)
{
return 0;
}
if (Mathf.Abs(Vector3.Dot(dire, Vector3.up)) > 0.99f)
{
return 1;
}
if (Mathf.Abs(Vector3.Dot(dire, Vector3.forward)) > 0.99f)
{
return 2;
}
return -1;
}
}
3.7 选择阶数
LoadRubik.cs
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
/*
* 加载魔方(选择阶数)
*/
public class LoadRubik : MonoBehaviour
{
private Transform orderParent; // 阶数父对象
private Button[] orderButtons; // 阶数按钮
private void Awake()
{
orderParent = GameObject.Find("Canvas/Select/Order").transform;
orderButtons = orderParent.GetComponentsInChildren<Button>();
for (int i = 0; i < orderButtons.Length; i++)
{
int order = i + 2;
orderButtons[i].onClick.AddListener(() => {
OnClick(order);
});
}
}
private void OnClick(int order) {
Rubik.SetRubikInfo(new RubikInfo(order));
SceneManager.LoadScene("Rubik");
}
}
说明:LoadRubik 脚本挂在第一个场景的 “Canvas / Select / Order” 对象上。
3.8 事件模块
GlobalEvent.cs
using UnityEngine;
/*
* 全局事件
* 事件类型的获取, 建议从这里获取, 不要直接调用Input.GetXXX方法获取事件
*/
public class GlobalEvent
{
public static MyEventType eventType = MyEventType.None; // 事件类型
public static MyKeyCode keyCode = MyKeyCode.None; // 按键编码
public static Vector3 mousePos; // 当前鼠标位置
public static Vector2 mouseMove; // 相比上一帧, 鼠标的偏移量(取值-1~1)
public static Vector2 mouseScreenMove; // 相比上一帧, 鼠标的在屏幕上的偏移量
public static Vector2 direMove; // 方向按键或A、D、W、S按键变化量
public static Vector3 hitPoint; // 屏幕射线碰撞到的物体表面的点
public static GameObject hitObj; // 屏幕射线碰撞到的物体
public static float scroll; // 滑轮滑动刻度
public static GameObject target; // 事件作用的目标对象
public static GameObject lastTarget; // 上一帧事件作用的目标对象
public static bool IsOnlyDragEvent(int flag)
{ // 是否是拖拽事件(单事件, flag取值0,1,2, 分别表示左键、右键、中键)
if (!EventTypeUtils.IsOnlyDragEvent(eventType))
{
return false;
}
if (flag == 0 && EventTypeUtils.IsLeftDragEvent(eventType))
{
return true;
}
if (flag == 1 && EventTypeUtils.IsRightDragEvent(eventType))
{
return true;
}
if (flag == 2 && EventTypeUtils.IsMiddleDragEvent(eventType))
{
return true;
}
return false;
}
public static bool IsCtrlDragEvent(int flag)
{ // 是否是拖拽事件(Ctrl复合事件, flag取值0,1,2, 分别表示左键、右键、中键)
if (!EventTypeUtils.IsCtrlDragEvent(eventType))
{
return false;
}
if (flag == 0 && EventTypeUtils.IsLeftDragEvent(eventType))
{
return true;
}
if (flag == 1 && EventTypeUtils.IsRightDragEvent(eventType))
{
return true;
}
if (flag == 2 && EventTypeUtils.IsMiddleDragEvent(eventType))
{
return true;
}
return false;
}
public static bool IsAltDragEvent(int flag)
{ // 是否是拖拽事件(Alt复合事件, flag取值0,1,2, 分别表示左键、右键、中键)
if (!EventTypeUtils.IsAltDragEvent(eventType))
{
return false;
}
if (flag == 0 && EventTypeUtils.IsLeftDragEvent(eventType))
{
return true;
}
if (flag == 1 && EventTypeUtils.IsRightDragEvent(eventType))
{
return true;
}
if (flag == 2 && EventTypeUtils.IsMiddleDragEvent(eventType))
{
return true;
}
return false;
}
public static bool IsOnlyScrollEvent()
{ // 是否是缩放事件(单事件)
return EventTypeUtils.IsOnlyScrollEvent(eventType);
}
public static bool IsCtrlScrollEvent()
{ // 是否是缩放事件(Ctrl复合事件)
return EventTypeUtils.IsCtrlScrollEvent(eventType);
}
public static bool IsAltScrollEvent()
{ // 是否是缩放事件(Alt复合事件)
return EventTypeUtils.IsAltScrollEvent(eventType);
}
public static bool IsDireKeyEvent()
{ // 方向键事件(上下左右箭头或W、S、A、D按键)
return EventTypeUtils.IsDireKeyEvent(eventType, keyCode);
}
}
EventDetector.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
/*
* 事件检测器
*/
public class EventDetector : MonoBehaviour
{
private const float PAUSE_SCROLL = 0.5f; // 滑轮的最小时间间隔将被识别为滑轮结束事件
private List<RaycastResult> raycastResults = new List<RaycastResult>(); // 检测鼠标是否悬浮在UI上
private PointerEventData eventData; // UI事件检测数据
private Dictionary<MyKeyCode, KeyCode[]> keysMap; // 按键字典
private Vector3 hitPoint; // 射线检测碰撞点位置
private MyEventType eventType; // 事件类型
private MyKeyCode keyCode; // 按键编码
private GameObject hitObj; // 射线检测到的物体
private Vector2 mouseMove; // 相比上一帧, 鼠标的偏移量
private Vector2 direMove; // 方向按键或A、D、W、S按键变化量
private float scroll; // 滑轮滑动刻度
private float lastScrollTime; // 上次滑轮时间(用于识别结束滑轮)
private bool isContinueEvent = false; // 是否是可延续的事件(拖拽、滑轮、单击、悬浮事件)
private void Awake()
{
EventTypeUtils.Init();
keysMap = KeyUtils.GetKeyMap();
eventData = new PointerEventData(EventSystem.current);
}
private void Update()
{
ResetStatus();
PreSetValue();
DetectContinueEvent();
DetectDragEvent();
DetectClickEvent();
DetectScrollEvent();
DetectKeyEvent();
DetectHorverEvent();
SetGlobalEvent();
}
private void ResetStatus()
{ // 重置状态
eventType = MyEventType.None;
keyCode = MyKeyCode.None;
hitObj = null;
mouseMove = Vector2.zero;
scroll = 0;
isContinueEvent = false;
}
private void PreSetValue()
{ // 预设变量
mouseMove = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y")); // -1~1之间
direMove = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")); // -1~1之间
float hor = Input.GetAxis("Horizontal");
float ver = Input.GetAxis("Vertical");
GetHoverObject();
}
private void DetectContinueEvent()
{ // 检测延续事件(如果是拖拽事件, 会延续)
if (GlobalEvent.eventType == MyEventType.None || EventTypeUtils.IsKeyEvent(GlobalEvent.eventType)
|| EventTypeUtils.IsHorverEvent(GlobalEvent.eventType))
{ // 空事件、按键事件、Horver事件不需要延续
return;
}
if (EventTypeUtils.IsBeginDragEvent(GlobalEvent.eventType) || EventTypeUtils.IsBeginScrollEvent(GlobalEvent.eventType))
{ // Begin Drag -> Drag, Begin Scroll -> Scroll
isContinueEvent = true;
eventType = EventStatusUtils.GetNextEvent(GlobalEvent.eventType);
return;
}
if (EventTypeUtils.IsDraggingEvent(GlobalEvent.eventType))
{
isContinueEvent = true;
if (EventTypeUtils.IsLeftDragEvent(GlobalEvent.eventType) && Input.GetMouseButton(0) && !Input.GetMouseButtonUp(0)
|| EventTypeUtils.IsRightDragEvent(GlobalEvent.eventType) && Input.GetMouseButton(1) && !Input.GetMouseButtonUp(1)
|| EventTypeUtils.IsMiddleDragEvent(GlobalEvent.eventType) && Input.GetMouseButton(2) && !Input.GetMouseButtonUp(2))
{ // Drag -> End Drag, Scroll -> End Scroll
eventType = GlobalEvent.eventType;
return;
}
eventType = EventStatusUtils.GetNextDragEvent(GlobalEvent.eventType); // Drag
return;
}
if (EventTypeUtils.IsScrollingEvent(GlobalEvent.eventType))
{
isContinueEvent = true;
scroll = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Abs(scroll) < float.Epsilon && Time.realtimeSinceStartup - lastScrollTime > PAUSE_SCROLL)
{ // Scroll -> End Scroll
eventType = EventStatusUtils.GetNextScrollEvent(GlobalEvent.eventType);
return;
}
if (Mathf.Abs(scroll) > float.Epsilon)
{
lastScrollTime = Time.realtimeSinceStartup;
}
eventType = GlobalEvent.eventType; // Scroll
return;
}
}
private void DetectDragEvent()
{ // 检查拖拽事件
if (eventType != MyEventType.None)
{
return;
}
bool clicked = Input.GetMouseButton(0) || Input.GetMouseButton(1) || Input.GetMouseButton(2);
if (clicked && (Mathf.Abs(mouseMove.x) > 0.1f || Mathf.Abs(mouseMove.y) > 0.1f))
{
if (Input.GetMouseButton(0))
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
eventType = MyEventType.BeginCtrlDrag;
}
else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))
{
eventType = MyEventType.BeginAltDrag;
}
else
{
eventType = MyEventType.BeginDrag;
}
}
else if (Input.GetMouseButton(1))
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
eventType = MyEventType.BeginCtrlRightDrag;
}
else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))
{
eventType = MyEventType.BeginAltRightDrag;
}
else
{
eventType = MyEventType.BeginRightDrag;
}
}
else if (Input.GetMouseButton(2))
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
eventType = MyEventType.BeginCtrlMiddleDrag;
}
else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))
{
eventType = MyEventType.BeginAltMiddleDrag;
}
else
{
eventType = MyEventType.BeginMiddleDrag;
}
}
}
}
private void DetectClickEvent()
{ // 检查单击事件
if (eventType != MyEventType.None)
{
return;
}
if (EventTypeUtils.IsClickEvent(GlobalEvent.eventType))
{
if (EventTypeUtils.IsClickDownEvent(GlobalEvent.eventType))
{
isContinueEvent = true;
eventType = EventStatusUtils.GetNextClickEvent(GlobalEvent.eventType);
return;
}
if (EventTypeUtils.IsClickingEvent(GlobalEvent.eventType))
{
if (Input.GetMouseButtonUp(0) || Input.GetMouseButtonUp(1) || Input.GetMouseButtonUp(2))
{
isContinueEvent = true;
eventType = EventStatusUtils.GetNextClickEvent(GlobalEvent.eventType);
return;
}
if (Input.GetMouseButton(0) || Input.GetMouseButton(1) || Input.GetMouseButton(2))
{
isContinueEvent = true;
eventType = GlobalEvent.eventType;
return;
}
}
}
if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2))
{
if (Input.GetMouseButtonDown(0))
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
eventType = MyEventType.CtrlClickDown;
}
else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))
{
eventType = MyEventType.AltClickDown;
}
else
{
eventType = MyEventType.ClickDown;
}
}
else if (Input.GetMouseButtonDown(1))
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
eventType = MyEventType.CtrlRightClickDown;
}
else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))
{
eventType = MyEventType.AltRightClickDown;
}
else
{
eventType = MyEventType.RightClickDown;
}
}
else if (Input.GetMouseButtonDown(2))
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
eventType = MyEventType.CtrlMiddleClickDown;
}
else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))
{
eventType = MyEventType.AltMiddleClickDown;
}
else
{
eventType = MyEventType.MiddleClickDown;
}
}
}
}
private void DetectScrollEvent()
{ // 检查滑轮事件
if (eventType != MyEventType.None)
{
return;
}
scroll = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Abs(scroll) > float.Epsilon)
{
lastScrollTime = Time.realtimeSinceStartup;
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
eventType = MyEventType.BeginCtrlScroll;
}
else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))
{
eventType = MyEventType.BeginAltScroll;
}
else
{
eventType = MyEventType.BeginScroll;
}
}
}
private void DetectKeyEvent()
{ // 检测按键事件
if (eventType != MyEventType.None)
{
return;
}
foreach (var entry in keysMap)
{
bool find = true;
foreach (var keyCode in entry.Value)
{
if (!Input.GetKey(keyCode))
{
find = false;
break;
}
}
if (find)
{
KeyCode lastKey = entry.Value[entry.Value.Length - 1];
if (Input.GetKeyDown(lastKey))
{
eventType = MyEventType.PressKeyDown;
}
else if (Input.GetKeyUp(lastKey))
{
eventType = MyEventType.PressKeyUp;
}
else
{
eventType = MyEventType.PressKey;
}
keyCode = entry.Key;
break;
}
}
}
private void DetectHorverEvent()
{ // 检测Horver事件
if (eventType != MyEventType.None || GlobalEvent.target == null && hitObj == null)
{
return;
}
if (GlobalEvent.target == hitObj)
{
eventType = MyEventType.Horver;
}
else
{
eventType = MyEventType.EnterHorver;
}
}
private void SetGlobalEvent()
{ // 设置全局事件
GlobalEvent.eventType = eventType;
GlobalEvent.keyCode = keyCode;
GlobalEvent.mouseScreenMove = Input.mousePosition - GlobalEvent.mousePos;
GlobalEvent.mousePos = Input.mousePosition;
GlobalEvent.mouseMove = mouseMove;
GlobalEvent.direMove = direMove;
GlobalEvent.hitPoint = hitPoint;
GlobalEvent.hitObj = hitObj;
GlobalEvent.scroll = scroll;
GlobalEvent.lastTarget = GlobalEvent.target;
if (!isContinueEvent)
{
GlobalEvent.target = hitObj;
}
}
private void GetHoverObject()
{ // 获取鼠标悬浮的对象
hitObj = GetHoverUIObject();
if (hitObj == null)
{
hitObj = GetHover3DObject();
}
}
private GameObject GetHoverUIObject()
{ // 获取鼠标悬浮的UI对象
raycastResults.Clear();
eventData.position = Input.mousePosition;
if (EventSystem.current != null)
{
EventSystem.current.RaycastAll(eventData, raycastResults);
}
if (raycastResults.Count > 0)
{
hitPoint = raycastResults[0].worldPosition;
return raycastResults[0].gameObject;
}
return null;
}
private GameObject GetHover3DObject()
{ // 获取鼠标悬浮的3D对象
RaycastHit hitInfo;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hitInfo))
{
hitPoint = hitInfo.point;
return hitInfo.collider.gameObject;
}
return null;
}
}
说明:EventDetector 脚本挂在相机对象上。
MyEventType.cs
using System.Collections.Generic;
/*
* 自定义事件类型
*/
public enum MyEventType
{ // 事件类型
None = 0, // 空事件
// 鼠标单击事件(单事件)
ClickDown = 1, // 鼠标按下(左键)
Click = 2, // 鼠标点击(左键)
ClickUp = 3, // 鼠标抬起(左键)
RightClickDown = 4, // 鼠标按下(右键)
RightClick = 5, // 鼠标点击(右键)
RightClickUp = 6, // 鼠标抬起(右键)
MiddleClickDown = 7, // 鼠标按下(中键)
MiddleClick = 8, // 鼠标点击(中键)
MiddleClickUp = 9, // 鼠标抬起(中键)
// 鼠标单击事件(混合Ctrl按键事件)
CtrlClickDown = 10, // Ctrl+鼠标按下(左键)
CtrlClick = 11, // Ctrl+鼠标点击(左键)
CtrlClickUp = 12, // Ctrl+鼠标抬起(左键)
CtrlRightClickDown = 13, // Ctrl+鼠标按下(右键)
CtrlRightClick = 14, // Ctrl+鼠标点击(右键)
CtrlRightClickUp = 15, // Ctrl+鼠标抬起(右键)
CtrlMiddleClickDown = 16, // Ctrl+鼠标按下(中键)
CtrlMiddleClick = 17, // Ctrl+鼠标点击(中键)
CtrlMiddleClickUp = 18, // Ctrl+鼠标抬起(中键)
// 鼠标单击事件(混合Alt按键事件)
AltClickDown = 20, // Alt+鼠标按下(左键)
AltClick = 21, // Alt+鼠标点击(左键)
AltClickUp = 22, // Alt+鼠标抬起(左键)
AltRightClickDown = 23, // Alt+鼠标按下(右键)
AltRightClick = 24, // Alt+鼠标点击(右键)
AltRightClickUp = 25, // Alt+鼠标抬起(右键)
AltMiddleClickDown = 26, // Alt+鼠标按下(中键)
AltMiddleClick = 27, // Alt+鼠标点击(中键)
AltMiddleClickUp = 28, // Alt+鼠标抬起(中键)
//--------------------------------------------------------------------------------------
// 鼠标拖拽事件(单事件)
BeginDrag = 30, // 开始拖拽(左键)
Drag = 31, // 拖拽中(左键)
EndDrag = 32, // 结束拖拽(左键)
BeginRightDrag = 33, // 开始拖拽(右键)
RightDrag = 34, // 拖拽中(右键)
EndRightDrag = 35, // 结束拖拽(右键)
BeginMiddleDrag = 36, // 开始拖拽(中键)
MiddleDrag = 37, // 拖拽中(中键)
EndMiddleDrag = 38, // 结束拖拽(中键)
// 鼠标拖拽事件(混合Ctrl按键事件)
BeginCtrlDrag = 40, // 开始Ctrl拖拽(左键)
CtrlDrag = 41, // Ctrl拖拽中(左键)
EndCtrlDrag = 42, // 结束Ctrl拖拽(左键)
BeginCtrlRightDrag = 43, // 开始Ctrl拖拽(右键)
CtrlRightDrag = 44, // Ctrl拖拽中(右键)
EndCtrlRightDrag = 45, // Ctrl结束拖拽(右键)
BeginCtrlMiddleDrag = 46, // 开始Ctrl拖拽(中键)
CtrlMiddleDrag = 47, // Ctrl拖拽中(中键)
EndCtrlMiddleDrag = 48, // 结束Ctrl拖拽(中键)
// 鼠标拖拽事件(混合Alt按键事件)
BeginAltDrag = 50, // 开始Alt拖拽(左键)
AltDrag = 51, // Alt拖拽中(左键)
EndAltDrag = 52, // 结束Alt拖拽(左键)
BeginAltRightDrag = 53, // 开始Alt拖拽(右键)
AltRightDrag = 54, // Alt拖拽中(右键)
EndAltRightDrag = 55, // Alt结束拖拽(右键)
BeginAltMiddleDrag = 56, // 开始Alt拖拽(中键)
AltMiddleDrag = 57, // Alt拖拽中(中键)
EndAltMiddleDrag = 58, // 结束Alt拖拽(中键)
//--------------------------------------------------------------------------------------
// 滑轮事件
BeginScroll = 60, // 开始滑轮
Scroll = 61, // 滑轮中
EndScroll = 62, // 结束滑轮
BeginCtrlScroll = 63, // 开始Ctrl滑轮
CtrlScroll = 64, // Ctrl滑轮中
EndCtrlScroll = 65, // 结束Ctrl滑轮
BeginAltScroll = 66, // 开始Alt滑轮
AltScroll = 67, // Alt滑轮中
EndAltScroll = 68, // 结束Alt滑轮
//--------------------------------------------------------------------------------------
// 按键事件
PressKeyDown = 70, // 按下按键
PressKey = 71, // 按住按键
PressKeyUp = 72, // 抬起按键
//--------------------------------------------------------------------------------------
// horver事件
EnterHorver = 80, // 鼠标进入悬浮
Horver = 81, // 鼠标悬浮中
LeftHorver = 82, // 鼠标离开悬浮
}
MyKeyCode.cs
using System.Collections.Generic;
using UnityEngine;
/*
* 自定义按键编码
*/
public enum MyKeyCode
{
None = 0, // 无按键事件
// 方向键
A = 1,
D = 2,
W = 3,
S = 4,
UpArrow = 5,
DownArrow = 6,
LeftArrow = 7,
RightArrow = 8,
// 单键(字母)
B = 10,
C = 11,
E = 12,
F = 13,
Q = 14,
R = 15,
T = 16,
V = 17,
X = 18,
Z = 19,
// 单键(其他)
ESC = 30,
Delete = 31,
Space = 32,
Backspace = 33,
Ctrl = 34,
Shift = 35,
Alt = 36,
// 双键
CtrlA = 50,
CtrlC = 51,
CtrlD = 52,
CtrlF = 53,
CtrlS = 54,
CtrlV = 55,
CtrlX = 56,
CtrlZ = 57,
CtrlSpace = 58,
// 三键
CtrlShiftZ = 70,
}
/*
* 按键映射
*/
public class KeyUtils
{
private static Dictionary<MyKeyCode, KeyCode[]> keysMap;
public static Dictionary<MyKeyCode, KeyCode[]> GetKeyMap()
{
if (keysMap != null)
{
return keysMap;
}
keysMap = new Dictionary<MyKeyCode, KeyCode[]>();
keysMap.Add(MyKeyCode.CtrlShiftZ, new KeyCode[] {KeyCode.LeftControl, KeyCode.LeftShift, KeyCode.Z});
keysMap.Add(MyKeyCode.CtrlA, new KeyCode[] {KeyCode.LeftControl, KeyCode.A});
keysMap.Add(MyKeyCode.CtrlC, new KeyCode[] {KeyCode.LeftControl, KeyCode.C});
keysMap.Add(MyKeyCode.CtrlD, new KeyCode[] {KeyCode.LeftControl, KeyCode.D});
keysMap.Add(MyKeyCode.CtrlF, new KeyCode[] {KeyCode.LeftControl, KeyCode.F});
keysMap.Add(MyKeyCode.CtrlS, new KeyCode[] {KeyCode.LeftControl, KeyCode.S});
keysMap.Add(MyKeyCode.CtrlV, new KeyCode[] {KeyCode.LeftControl, KeyCode.V});
keysMap.Add(MyKeyCode.CtrlX, new KeyCode[] {KeyCode.LeftControl, KeyCode.X});
keysMap.Add(MyKeyCode.CtrlZ, new KeyCode[] {KeyCode.LeftControl, KeyCode.Z});
keysMap.Add(MyKeyCode.CtrlSpace, new KeyCode[] {KeyCode.LeftControl, KeyCode.Space});
keysMap.Add(MyKeyCode.A, new KeyCode[] {KeyCode.A});
keysMap.Add(MyKeyCode.B, new KeyCode[] {KeyCode.B});
keysMap.Add(MyKeyCode.C, new KeyCode[] {KeyCode.C});
keysMap.Add(MyKeyCode.D, new KeyCode[] {KeyCode.D});
keysMap.Add(MyKeyCode.E, new KeyCode[] {KeyCode.E});
keysMap.Add(MyKeyCode.F, new KeyCode[] {KeyCode.F});
keysMap.Add(MyKeyCode.Q, new KeyCode[] {KeyCode.Q});
keysMap.Add(MyKeyCode.R, new KeyCode[] {KeyCode.R});
keysMap.Add(MyKeyCode.S, new KeyCode[] {KeyCode.S});
keysMap.Add(MyKeyCode.T, new KeyCode[] {KeyCode.T});
keysMap.Add(MyKeyCode.V, new KeyCode[] {KeyCode.V});
keysMap.Add(MyKeyCode.W, new KeyCode[] {KeyCode.W});
keysMap.Add(MyKeyCode.X, new KeyCode[] {KeyCode.X});
keysMap.Add(MyKeyCode.Z, new KeyCode[] {KeyCode.Z});
keysMap.Add(MyKeyCode.UpArrow, new KeyCode[] {KeyCode.UpArrow});
keysMap.Add(MyKeyCode.DownArrow, new KeyCode[] {KeyCode.DownArrow});
keysMap.Add(MyKeyCode.LeftArrow, new KeyCode[] {KeyCode.LeftArrow});
keysMap.Add(MyKeyCode.RightArrow, new KeyCode[] {KeyCode.RightArrow});
keysMap.Add(MyKeyCode.ESC, new KeyCode[] {KeyCode.Escape});
keysMap.Add(MyKeyCode.Delete, new KeyCode[] {KeyCode.Delete});
keysMap.Add(MyKeyCode.Space, new KeyCode[] {KeyCode.Space});
keysMap.Add(MyKeyCode.Backspace, new KeyCode[] {KeyCode.Backspace});
keysMap.Add(MyKeyCode.Ctrl, new KeyCode[] {KeyCode.LeftControl});
keysMap.Add(MyKeyCode.Shift, new KeyCode[] {KeyCode.LeftShift});
keysMap.Add(MyKeyCode.Alt, new KeyCode[] {KeyCode.LeftAlt});
return keysMap;
}
}
EventTypeUtils.cs
using System.Collections.Generic;
/*
* 事件类型工具类
*/
public class EventTypeUtils
{
private static SortedSet<MyEventType> clickDownEvent; // 点击按下事件
private static SortedSet<MyEventType> clickingEvent; // 点击中事件
private static SortedSet<MyEventType> clickUpEvent; // 点击抬起事件
private static SortedSet<MyEventType> beginDragEvent; // 开始拖拽事件
private static SortedSet<MyEventType> draggingEvent; // 拖拽进行中事件
private static SortedSet<MyEventType> endDragEvent; // 拖拽结束事件
public static void Init()
{
clickDownEvent = new SortedSet<MyEventType>(new MyEventType[] {
MyEventType.ClickDown, MyEventType.CtrlClickDown, MyEventType.AltClickDown,
MyEventType.RightClickDown, MyEventType.CtrlRightClickDown, MyEventType.AltRightClickDown,
MyEventType.MiddleClickDown, MyEventType.CtrlMiddleClickDown, MyEventType.AltMiddleClickDown
});
clickingEvent = new SortedSet<MyEventType>(new MyEventType[] {
MyEventType.Click, MyEventType.CtrlClick, MyEventType.AltClick,
MyEventType.RightClick, MyEventType.CtrlRightClick, MyEventType.AltRightClick,
MyEventType.MiddleClick, MyEventType.CtrlMiddleClick, MyEventType.AltMiddleClick
});
clickUpEvent = new SortedSet<MyEventType>(new MyEventType[] {
MyEventType.ClickUp, MyEventType.CtrlClickUp, MyEventType.AltClickUp,
MyEventType.RightClickUp, MyEventType.CtrlRightClickUp, MyEventType.AltRightClickUp,
MyEventType.MiddleClickUp, MyEventType.CtrlMiddleClickUp, MyEventType.AltMiddleClickUp
});
beginDragEvent = new SortedSet<MyEventType>(new MyEventType[] {
MyEventType.BeginDrag, MyEventType.BeginCtrlDrag, MyEventType.BeginAltDrag,
MyEventType.BeginRightDrag, MyEventType.BeginCtrlRightDrag, MyEventType.BeginAltRightDrag,
MyEventType.BeginMiddleDrag, MyEventType.BeginCtrlMiddleDrag, MyEventType.BeginAltMiddleDrag
});
draggingEvent = new SortedSet<MyEventType>(new MyEventType[] {
MyEventType.Drag, MyEventType.CtrlDrag, MyEventType.AltDrag,
MyEventType.RightDrag, MyEventType.CtrlRightDrag, MyEventType.AltRightDrag,
MyEventType.MiddleDrag, MyEventType.CtrlMiddleDrag, MyEventType.AltMiddleDrag
});
endDragEvent = new SortedSet<MyEventType>(new MyEventType[] {
MyEventType.EndDrag, MyEventType.EndCtrlDrag, MyEventType.EndAltDrag,
MyEventType.EndRightDrag, MyEventType.EndCtrlRightDrag, MyEventType.EndAltRightDrag,
MyEventType.EndMiddleDrag, MyEventType.EndCtrlMiddleDrag, MyEventType.EndAltMiddleDrag
});
}
// 点击事件--------------------------------------------------------------------------------------
public static bool IsClickEvent(MyEventType eventType)
{ // 是否是点击事件
return eventType >= MyEventType.ClickDown && eventType <= MyEventType.MiddleClickUp
|| eventType >= MyEventType.CtrlClickDown && eventType <= MyEventType.CtrlMiddleClickUp
|| eventType >= MyEventType.AltClickDown && eventType <= MyEventType.AltMiddleClickUp;
}
public static bool IsOnlyClickEvent(MyEventType eventType)
{ // 是否是点击事件(单事件)
return eventType >= MyEventType.ClickDown && eventType <= MyEventType.MiddleClickUp;
}
public static bool IsCtrlClickEvent(MyEventType eventType)
{ // 是否是Ctrl点击事件
return eventType >= MyEventType.CtrlClickDown && eventType <= MyEventType.CtrlMiddleClickUp;
}
public static bool IsAltClickEvent(MyEventType eventType)
{ // 是否是Alt点击事件
return eventType >= MyEventType.AltClickDown && eventType <= MyEventType.AltMiddleClickUp;
}
public static bool IsLeftClickEvent(MyEventType eventType)
{ // 是否是左键点击事件
return eventType >= MyEventType.ClickDown && eventType <= MyEventType.ClickUp
|| eventType >= MyEventType.CtrlClickDown && eventType <= MyEventType.CtrlClickUp
|| eventType >= MyEventType.AltClickDown && eventType <= MyEventType.AltClickUp;
}
public static bool IsRightClickEvent(MyEventType eventType)
{ // 是否是右键点击事件
return eventType >= MyEventType.RightClickDown && eventType <= MyEventType.RightClickUp
|| eventType >= MyEventType.CtrlRightClickDown && eventType <= MyEventType.CtrlRightClickUp
|| eventType >= MyEventType.AltRightClickDown && eventType <= MyEventType.AltRightClickUp;
}
public static bool IsMiddleClickEvent(MyEventType eventType)
{ // 是否是中键点击事件
return eventType >= MyEventType.MiddleClickDown && eventType <= MyEventType.MiddleClickUp
|| eventType >= MyEventType.CtrlMiddleClickDown && eventType <= MyEventType.CtrlMiddleClickUp
|| eventType >= MyEventType.AltMiddleClickDown && eventType <= MyEventType.AltMiddleClickUp;
}
public static bool IsClickDownEvent(MyEventType eventType)
{ // 是否是点击按下事件
return clickDownEvent.Contains(eventType);
}
public static bool IsClickingEvent(MyEventType eventType)
{ // 是否是点击进行中事件
return clickingEvent.Contains(eventType);
}
public static bool IsClickUpEvent(MyEventType eventType)
{ // 是否是点击抬起事件
return clickUpEvent.Contains(eventType);
}
// 拖拽事件--------------------------------------------------------------------------------------
public static bool IsDragEvent(MyEventType eventType)
{ // 是否是拖拽事件
return eventType >= MyEventType.BeginDrag && eventType <= MyEventType.EndMiddleDrag
|| eventType >= MyEventType.BeginCtrlDrag && eventType <= MyEventType.EndCtrlMiddleDrag
|| eventType >= MyEventType.BeginAltDrag && eventType <= MyEventType.EndAltMiddleDrag;
}
public static bool IsOnlyDragEvent(MyEventType eventType)
{ // 是否是拖拽事件(单事件)
return eventType >= MyEventType.BeginDrag && eventType <= MyEventType.EndMiddleDrag;
}
public static bool IsCtrlDragEvent(MyEventType eventType)
{ // 是否是Ctrl拖拽事件
return eventType >= MyEventType.BeginCtrlDrag && eventType <= MyEventType.EndCtrlMiddleDrag;
}
public static bool IsAltDragEvent(MyEventType eventType)
{ // 是否是Alt拖拽事件
return eventType >= MyEventType.BeginAltDrag && eventType <= MyEventType.EndAltMiddleDrag;
}
public static bool IsLeftDragEvent(MyEventType eventType)
{ // 是否是左键拖拽事件
return eventType >= MyEventType.BeginDrag && eventType <= MyEventType.EndDrag
|| eventType >= MyEventType.BeginCtrlDrag && eventType <= MyEventType.EndCtrlDrag
|| eventType >= MyEventType.BeginAltDrag && eventType <= MyEventType.EndAltDrag;
}
public static bool IsRightDragEvent(MyEventType eventType)
{ // 是否是右键拖拽事件
return eventType >= MyEventType.BeginRightDrag && eventType <= MyEventType.EndRightDrag
|| eventType >= MyEventType.BeginCtrlRightDrag && eventType <= MyEventType.EndCtrlRightDrag
|| eventType >= MyEventType.BeginAltRightDrag && eventType <= MyEventType.EndAltRightDrag;
}
public static bool IsMiddleDragEvent(MyEventType eventType)
{ // 是否是中键拖拽事件
return eventType >= MyEventType.BeginMiddleDrag && eventType <= MyEventType.EndMiddleDrag
|| eventType >= MyEventType.BeginCtrlMiddleDrag && eventType <= MyEventType.EndCtrlMiddleDrag
|| eventType >= MyEventType.BeginAltMiddleDrag && eventType <= MyEventType.EndAltMiddleDrag;
}
public static bool IsBeginDragEvent(MyEventType eventType)
{ // 是否是开始拖拽事件
return beginDragEvent.Contains(eventType);
}
public static bool IsDraggingEvent(MyEventType eventType)
{ // 是否是拖拽进行中事件
return draggingEvent.Contains(eventType);
}
public static bool IsEndDragEvent(MyEventType eventType)
{ // 是否是结束拖拽事件
return endDragEvent.Contains(eventType);
}
// 滑轮事件--------------------------------------------------------------------------------------
public static bool IsScrollEvent(MyEventType eventType)
{ // 是否是滑轮事件
return eventType >= MyEventType.BeginScroll && eventType <= MyEventType.EndAltScroll;
}
public static bool IsOnlyScrollEvent(MyEventType eventType)
{ // 是否是滑轮事件(单事件)
return eventType >= MyEventType.BeginScroll && eventType <= MyEventType.EndScroll;
}
public static bool IsCtrlScrollEvent(MyEventType eventType)
{ // 是否是Ctrl滑轮事件
return eventType >= MyEventType.BeginCtrlScroll && eventType <= MyEventType.EndCtrlScroll;
}
public static bool IsAltScrollEvent(MyEventType eventType)
{ // 是否是Alt滑轮事件
return eventType >= MyEventType.BeginAltScroll && eventType <= MyEventType.EndAltScroll;
}
public static bool IsBeginScrollEvent(MyEventType eventType)
{ // 是否是开始滑轮事件
return eventType == MyEventType.BeginScroll || eventType == MyEventType.BeginCtrlScroll || eventType == MyEventType.BeginAltScroll;
}
public static bool IsScrollingEvent(MyEventType eventType)
{ // 是否是滑轮进行中事件
return eventType == MyEventType.Scroll || eventType == MyEventType.CtrlScroll || eventType == MyEventType.AltScroll;
}
public static bool IsEndScrollEvent(MyEventType eventType)
{ // 是否是结束滑轮事件
return eventType == MyEventType.EndScroll || eventType == MyEventType.EndCtrlScroll || eventType == MyEventType.EndAltScroll;
}
// 按键事件--------------------------------------------------------------------------------------
public static bool IsKeyEvent(MyEventType eventType)
{ // 是否是按键事件
return eventType >= MyEventType.PressKeyDown && eventType <= MyEventType.PressKeyUp;
}
public static bool IsDireKeyEvent(MyEventType eventType, MyKeyCode keyCode)
{ // 方向键事件(上下左右箭头或W、S、A、D按键)
return IsKeyEvent(eventType) && keyCode >= MyKeyCode.A && keyCode <= MyKeyCode.RightArrow;
}
// 悬浮事件--------------------------------------------------------------------------------------
public static bool IsHorverEvent(MyEventType eventType)
{ // 是否是鼠标悬浮事件
return eventType >= MyEventType.EnterHorver && eventType <= MyEventType.LeftHorver;
}
}
EventStatusUtils.cs
/*
* 事件状态切换工具类
*/
public class EventStatusUtils
{
public static MyEventType GetNextEvent(MyEventType eventType)
{ // 获取下一个事件
if (EventTypeUtils.IsClickEvent(eventType))
{
return GetNextClickEvent(eventType);
}
if (EventTypeUtils.IsDragEvent(eventType))
{
return GetNextDragEvent(eventType);
}
if (EventTypeUtils.IsScrollEvent(eventType))
{
return GetNextScrollEvent(eventType);
}
return MyEventType.None;
}
public static MyEventType GetNextClickEvent(MyEventType eventType)
{ // 获取下一个点击事件
if (EventTypeUtils.IsClickDownEvent(eventType))
{
switch(eventType) {
case MyEventType.ClickDown:
return MyEventType.Click;
case MyEventType.CtrlClickDown:
return MyEventType.CtrlClick;
case MyEventType.AltClickDown:
return MyEventType.AltClick;
case MyEventType.RightClickDown:
return MyEventType.RightClick;
case MyEventType.CtrlRightClickDown:
return MyEventType.CtrlRightClick;
case MyEventType.AltRightClickDown:
return MyEventType.AltRightClick;
case MyEventType.MiddleClickDown:
return MyEventType.MiddleClick;
case MyEventType.CtrlMiddleClickDown:
return MyEventType.CtrlMiddleClick;
case MyEventType.AltMiddleClickDown:
return MyEventType.AltMiddleClick;
}
}
if (EventTypeUtils.IsClickingEvent(eventType))
{
switch(eventType) {
case MyEventType.Click:
return MyEventType.ClickUp;
case MyEventType.CtrlClick:
return MyEventType.CtrlClickUp;
case MyEventType.AltClick:
return MyEventType.AltClickUp;
case MyEventType.RightClick:
return MyEventType.RightClickUp;
case MyEventType.CtrlRightClick:
return MyEventType.CtrlRightClickUp;
case MyEventType.AltRightClick:
return MyEventType.AltRightClickUp;
case MyEventType.MiddleClick:
return MyEventType.MiddleClickUp;
case MyEventType.CtrlMiddleClick:
return MyEventType.CtrlMiddleClickUp;
case MyEventType.AltMiddleClick:
return MyEventType.AltMiddleClickUp;
}
}
return MyEventType.None;
}
public static MyEventType GetNextDragEvent(MyEventType eventType)
{ // 获取下一个拖拽事件
if (EventTypeUtils.IsBeginDragEvent(eventType))
{
switch(eventType) {
case MyEventType.BeginDrag:
return MyEventType.Drag;
case MyEventType.BeginCtrlDrag:
return MyEventType.CtrlDrag;
case MyEventType.BeginAltDrag:
return MyEventType.AltDrag;
case MyEventType.BeginRightDrag:
return MyEventType.RightDrag;
case MyEventType.BeginCtrlRightDrag:
return MyEventType.CtrlRightDrag;
case MyEventType.BeginAltRightDrag:
return MyEventType.AltRightDrag;
case MyEventType.BeginMiddleDrag:
return MyEventType.MiddleDrag;
case MyEventType.BeginCtrlMiddleDrag:
return MyEventType.CtrlMiddleDrag;
case MyEventType.BeginAltMiddleDrag:
return MyEventType.AltMiddleDrag;
}
}
if (EventTypeUtils.IsDraggingEvent(eventType))
{
switch(eventType) {
case MyEventType.Drag:
return MyEventType.EndDrag;
case MyEventType.CtrlDrag:
return MyEventType.EndCtrlDrag;
case MyEventType.AltDrag:
return MyEventType.EndAltDrag;
case MyEventType.RightDrag:
return MyEventType.EndRightDrag;
case MyEventType.CtrlRightDrag:
return MyEventType.EndCtrlRightDrag;
case MyEventType.AltRightDrag:
return MyEventType.EndAltRightDrag;
case MyEventType.MiddleDrag:
return MyEventType.EndMiddleDrag;
case MyEventType.CtrlMiddleDrag:
return MyEventType.EndCtrlMiddleDrag;
case MyEventType.AltMiddleDrag:
return MyEventType.EndAltMiddleDrag;
}
}
return MyEventType.None;
}
public static MyEventType GetNextScrollEvent(MyEventType eventType)
{ // 获取下一个滑轮事件
switch(eventType) {
case MyEventType.BeginScroll:
return MyEventType.Scroll;
case MyEventType.BeginCtrlScroll:
return MyEventType.CtrlScroll;
case MyEventType.BeginAltScroll:
return MyEventType.AltScroll;
case MyEventType.Scroll:
return MyEventType.EndScroll;
case MyEventType.CtrlScroll:
return MyEventType.EndCtrlScroll;
case MyEventType.AltScroll:
return MyEventType.EndAltScroll;
}
return MyEventType.None;
}
}