【Unity3D】魔方

1 需求实现

         绘制魔方 中基于OpenGL ES 实现了魔方的绘制,实现较复杂,本文基于 Unity3D 实现了 2 ~ 10 阶魔方的整体旋转和局部旋转,详细需求如下:

  • 用户通过选择魔方阶数,渲染指定阶数的魔方,并且可以自动打乱魔方
  • 用户通过 Scroll 或 Ctrl + Scroll,控制魔方放大和缩小
  • 用户通过 Drag 空白处(或 Ctrl + Drag,或 Alt + Drag,或右键 Drag,或 Ctrl + 右键 Drag,或字母方向键),控制魔方整体旋转
  • 用户按箭头方向键,控制魔方上下左右旋转90°
  • 用户通过 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 System.Collections;
using UnityEngine;
 
 /*
  * 相机控制(整体旋转)
  * 缩放视觉场景: Scroll/CtrlScroll
  * 旋转视觉场景: Drag空白处/CtrlDrag/AltDrag/RightDrag/CtrlRightDrag/字母方向键
  * 切换视觉场景: 箭头方向键
  */
public class CameraController : MonoBehaviour 
{
    private Transform cam; // 相机
    private bool isAniming = false; // 动画中
    private WaitForSeconds waitForSeconds; // 协程等待时间
    private float waitTime = 0.05f; // 等待时间
 
    private void Awake() 
    {
        cam = Camera.main.transform;
        waitForSeconds = new WaitForSeconds(waitTime);
    }
 
    private void Update()
    {
        ScaleScene();
        RotateScene();
        SwitchScene();
    }
 
    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.IsLetterDireKeyEvent())
        {
            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 void SwitchScene() 
    { // 切换视觉场景(箭头方向键)
        if (!GlobalEvent.IsArrowDireKeyEvent() | isAniming)
        {
            return;
        }
        Vector2 offset = GetSwitchOffset();
        if (Mathf.Abs(offset.x) > 0)
        {
            StartCoroutine(SwitchScene(true, offset.x, 0.3f));
        }
        if (Mathf.Abs(offset.y) > 0)
        {
            StartCoroutine(SwitchScene(false, offset.y, 0.3f));
        }
    }

    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;
    }

    private Vector2 GetSwitchOffset()
    { // 获取切换视觉偏移量
        Vector2 offset = Vector2.zero;
        if (GlobalEvent.keyCode == MyKeyCode.LeftArrow)
        {
            offset = new Vector2(-90, 0);
        }
        else if (GlobalEvent.keyCode == MyKeyCode.RightArrow)
        {
            offset = new Vector2(90, 0);
        }
        else if (GlobalEvent.keyCode == MyKeyCode.DownArrow)
        {
            offset = new Vector2(0, 90);
        }
        else if ( GlobalEvent.keyCode == MyKeyCode.UpArrow)
        {
            offset = new Vector2(0, -90);
        }
        if (!offset.Equals(Vector2.zero))
        { // 当相机仰视观察魔方时, 水平旋转方向相反
            offset.x *= Mathf.Sign(Vector3.Dot(Vector3.up, cam.up));
        }
        return offset;
    }

    private IEnumerator SwitchScene(bool isHor, float angle, float second) 
    {
		isAniming = true;
        int count = (int) (second / waitTime);
        float perAngle = angle / count;
        int i = 0;
        while (i < count)
        {
            if (isHor)
            {
                cam.RotateAround(Vector3.zero, Vector3.up, perAngle); // 相机绕旋转中心水平旋转
            }
            else
            {
                cam.RotateAround(Vector3.zero, cam.right, perAngle); // 相机绕旋转中心竖直旋转
            }
            i++;
            yield return waitForSeconds;
        }
        isAniming = false;
	}
}

        说明: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);
    }

    public static bool IsLetterDireKeyEvent()
    { // 字母方向键事件(W、S、A、D按键)
        return EventTypeUtils.IsLetterDireKeyEvent(eventType, keyCode);
    }

    public static bool IsArrowDireKeyEvent()
    { // 箭头方向键事件(上下左右箭头)
        return EventTypeUtils.IsArrowDireKeyEvent(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,

    // 单键(字母)
    B = 10,
    C = 11,
    E = 12,
    F = 13,
    Q = 14,
    R = 15,
    T = 16,
    V = 17,
    X = 18,
    Z = 19,

    // 箭头方向键
    UpArrow = 30,
    DownArrow = 31,
    LeftArrow = 32,
    RightArrow = 33,

    // 单键(其他)
    ESC = 40,
    Delete = 41,
    Space = 42,
    Backspace = 43,
    Ctrl = 44,
    Shift = 45,
    Alt = 46,

    // 双键
    CtrlA = 60,
    CtrlC = 61,
    CtrlD = 62,
    CtrlF = 63,
    CtrlS = 64,
    CtrlV = 65,
    CtrlX = 66,
    CtrlZ = 67,
    CtrlSpace = 68,
    
    // 三键
    CtrlShiftZ = 80,
    CtrlShiftS = 81,
}

/*
 * 按键映射
 */
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.CtrlShiftS, new KeyCode[] {KeyCode.LeftControl, KeyCode.LeftShift, KeyCode.S});
        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 IsLetterDireKeyEvent(eventType, keyCode) || IsArrowDireKeyEvent(eventType, keyCode);
    }

    public static bool IsLetterDireKeyEvent(MyEventType eventType, MyKeyCode keyCode)
    { // 字母方向键事件(W、S、A、D按键)
        return IsKeyEvent(eventType) && keyCode >= MyKeyCode.A && keyCode <= MyKeyCode.S;
    }

    public static bool IsArrowDireKeyEvent(MyEventType eventType, MyKeyCode keyCode)
    { // 箭头方向键事件(上下左右箭头)
        return IsKeyEvent(eventType) && keyCode >= MyKeyCode.UpArrow && 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;
    }
}

4 运行效果

猜你喜欢

转载自blog.csdn.net/m0_37602827/article/details/130210697