Unity RuntimeHandle

在Unity的编辑器模式下,因为Handle的存在,编辑物体的位置方向以及大小非常方便,近期需要在运行时实现对物体的编辑,研究了一下,本文用unity的GL和Graphics两个类实现一个简易的Handle,最后效果图如下,左图为Unity编辑器下的效果,右图为运行时实现的效果。旋转和缩放都实现了,就不都截图了。不想看我废话的可以直接到我的Github上下载源码https://github.com/xdedzl/RuntimeHandle,里面有Demo(代码和博客相比有了一些变化)

     

一、相关知识点

在编写RuntimeHandle类之前,首先介绍一些相关的知识点。

1.Matrix4x4矩阵

绘图中经常需要构造变换矩阵,Matrix4x4这个类为我们提供一个构造矩阵的方法

Matrix4x4 transform = Matrix4x4.TRS(position, rotation, scale);

传入,位置,朝向和大小可以返回一个变换矩阵,这个矩阵可以将一个点的本地坐标系位置转化成世界空间坐标系

2.GL

GL是一个底层图形库,unity对OpenGL进行了封装,用来绘制2D和3D图形。GL代码推荐写在OnPostRender中,这个函数在相机完成场景渲染之后被调用,注意,写有这个函数的类必须要挂在相机下才会起作用,下面用一个画线段的例子简单讲一下,以后有时间专门写一篇来说GL。

lineMaterial.SetPass(0);     // 设置绘制图形的材质  lineMaterial是Material类

GL.PushMatrix();
GL.MultMatrix(transform);   // 在绘制的时候GL会用这个矩阵转换坐标,transform是一个变换矩阵
GL.Begin(GL.LINES);         //  GL.Lines表示之后绘制的是线段
GL.Color(Color.red);           //   设置颜色
GL.Vertex(lineStart);           //   起点
GL.Vertex(lineEnd);            //    终点

GL.End()

GL.PopMatrix();

 GL.PushMatrix();GL.MultMatrix(transform);GL.PopMatrix();这三行,要么都写,要么都不写,有这三行GL.Vertex中传入的是本地坐标,绘制时会自动利用设置的transform矩阵变换坐标,否则GL.Vertex需要传入世界坐标。

当然,除了GL.MultMatrix(transform)还有其他设置变换矩阵的方式,这里不多讲,暂且当它是必须的吧

3. Graphics

Graphics也是绘制的图形,不同的是它是在绘制网格,这里只介绍它的一个函数,传入要绘制的图形的网格,位置和四元数

Graphics.DrawMeshNow(Mesh, Position, Quaternion);

二、RunTimHandle的实现

·运行时手柄主要包含两块,一个是手柄显示,一个是手柄控制以及鼠标和手柄碰撞的判断,手柄的显示写在RunTimeHandle这个类中,手柄控制和碰撞判断写在BaseHandle的派生类中,基本控制包括位置,旋转和缩放。

RunTimeHandle的代码中会涉及到BaseHandle类的函数,可以先不管,后面会讲解的

1.枚举

/// <summary>
/// 鼠标选择轴的类型
/// </summary>
public enum RuntimeHandleAxis
{
    None,
    X,
    Y,
    Z,
    XY,
    XZ,
    YZ,
    XYZ,
}

/// <summary>
/// 控制模式
/// </summary>
public enum TransformMode
{
    Position,
    Rotation,
    Scale,
}

2.RunTimeHandle的实现

这个类有两个作用,一是充当整个运行时手柄的管理者,二是负责手柄的显示,本来是想把显示剥离的,但是感觉没什么必要,就没弄了,由于这个类实现了OnPostRender,所以一定要挂在相机下。

首先是成员变量和Awake函数,因为变量的意义基本是就是英文翻译,就没写几个注释了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 挂在相机上
/// </summary>
public class RuntimeHandle : MonoBehaviour
{
    private float handleScale = 1;
    private float quadScale = 0.2f;    // 方块长度和轴长度的比例
    private float arrowScale = 1f;
    private float cubeScale = 0.15f;
    private float circleRadius = 0.6f;

    public static Vector3 quadDir = Vector3.one;

    private static bool lockX = false;
    private static bool lockY = false;
    private static bool lockZ = false;
    private bool mouseDonw = false;    // 鼠标左键是否按下

    private RuntimeHandleAxis selectedAxis = RuntimeHandleAxis.None; // 当前有碰撞的轴
    private TransformMode transformMode = TransformMode.Position;    // 当前控制类型
    private BaseHandle currentHandle;

    private readonly PositionHandle positionHandle = new PositionHandle();
    private readonly RotationHandle rotationHandle = new RotationHandle();
    private readonly ScaleHandle scaleHandle = new ScaleHandle();

    private Color selectedColor = Color.yellow;
    private Color selectedColorA = new Color(1, 0.92f, 0.016f, 0.2f);
    private Color redA = new Color(1, 0, 0, 0.2f);
    private Color greenA = new Color(0, 1, 0, 0.2f);
    private Color blueA = new Color(0, 0, 1, 0.2f);

    private Material lineMaterial;
    private Material quadeMaterial;
    private Material shapesMaterial;

    public static Matrix4x4 localToWorld { get; private set; }
    public static float screenScale { get; private set; }
    public static Transform target { get; private set; }
    public static Camera camera { get; private set; }

    public static Vector3[] circlePosX;
    public static Vector3[] circlePosY;
    public static Vector3[] circlePosZ;

    private void Awake()
    {

        lineMaterial = new Material(Shader.Find("RunTimeHandles/VertexColor"));
        lineMaterial.color = Color.white;
        quadeMaterial = new Material(Shader.Find("RunTimeHandles/VertexColor"));
        quadeMaterial.color = Color.white;
        shapesMaterial = new Material(Shader.Find("RunTimeHandles/Shape"));
        shapesMaterial.color = Color.white;

        camera = GetComponent<Camera>();
        currentHandle = positionHandle;

        //SetTarget(GameObject.Find("Cube").transform);
    }

然后是几个外部调用接口和一个获取比例的函数,这个比例后面会经常用到


    // ------------- Tools -------------- //


    /// <summary>
    /// 通过一个世界坐标和相机获取比例
    /// </summary>
    private float GetScreenScale(Vector3 position, Camera camera)
    {
        float h = camera.pixelHeight;
        if (camera.orthographic)
        {
            return camera.orthographicSize * 2f / h * 90;
        }

        Transform transform = camera.transform;
        float distance = Vector3.Dot(position - transform.position, transform.forward);       // Position位置的深度距离
        float scale = 2.0f * distance * Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad); // 在Position的深度上,每个像素点对应的y轴距离
        return scale / h * 90; // 90为自定义系数
    }


    // ---------------- 外部调用 ------------------- //

    public static void SetTarget(Transform _target)
    {
        target = _target;
    }

    public static void ConfigFreeze(bool _lockX = false, bool _lockY = false, bool _locKZ = false)
    {
        lockX = _lockX;
        lockY = _lockY;
        lockZ = _locKZ;
    }

    public static void Disable()
    {
        target = null;
    }

 接下来是重头戏之一,手柄的显示,一下子三百多行没了,但是,我懒,所以还是先把显示写在这里吧

    void OnPostRender()
    {
        if (target)
        {
            screenScale = GetScreenScale(target.position, camera);
            localToWorld = Matrix4x4.TRS(target.position, target.rotation, Vector3.one * screenScale);
            DrawHandle(target);
        }
    }

    /// <summary>
    /// 根据变换模式绘制不同的手柄
    /// </summary>
    private void DrawHandle(Transform target)
    {
        switch (transformMode)
        {
            case TransformMode.Position:
                DoPosition(target);
                break;
            case TransformMode.Rotation:
                DoRotation(target);
                break;
            case TransformMode.Scale:
                DoSacle(target);
                break;
            default:
                break;
        }
    }

    /// <summary>
    /// 绘制位移手柄
    /// </summary>
    private void DoPosition(Transform target)
    {
        DrawCoordinate(target, true);
        DrawCoordinateArrow(target);
    }

    /// <summary>
    /// 绘制旋转手柄
    /// </summary>
    /// <param name="target"></param>
    private void DoRotation(Transform target)
    {
        Matrix4x4 transform = Matrix4x4.TRS(target.position, target.rotation, Vector3.one * screenScale);

        lineMaterial.SetPass(0);
        GL.PushMatrix();
        GL.MultMatrix(transform);
        GL.Begin(GL.LINES);

        DrawCircle(target, Vector3.right, circleRadius, selectedAxis == RuntimeHandleAxis.X ? selectedColor : Color.red);
        DrawCircle(target, Vector3.up, circleRadius, selectedAxis == RuntimeHandleAxis.Y ? selectedColor : Color.green);
        DrawCircle(target, Vector3.forward, circleRadius, selectedAxis == RuntimeHandleAxis.Z ? selectedColor : Color.blue);

        GL.End();
        GL.PopMatrix();
    }

    /// <summary>
    /// 绘制比例手柄
    /// </summary>
    private void DoSacle(Transform target)
    {
        DrawCoordinate(target, false);
        DrawCoordinateCube(target);
    }

    /// <summary>
    /// 绘制坐标系
    /// </summary>
    private void DrawCoordinate(Transform target, bool hasQuad)
    {
        Vector3 position = target.position;
        Matrix4x4 transform = Matrix4x4.TRS(target.position, target.rotation, Vector3.one * screenScale);

        lineMaterial.SetPass(0);
        Vector3 x = Vector3.right * handleScale;
        Vector3 y = Vector3.up * handleScale;
        Vector3 z = Vector3.forward * handleScale;
        Vector3 xy = x + y;
        Vector3 xz = x + z;
        Vector3 yz = y + z;
        Vector3 o = Vector3.zero;

        GL.PushMatrix();
        GL.MultMatrix(transform);   // 在绘制的时候GL会用这个矩阵转换坐标

        // 画三个坐标轴线段
        GL.Begin(GL.LINES);
        GL.Color(selectedAxis == RuntimeHandleAxis.X ? selectedColor : Color.red);
        GL.Vertex(o);
        GL.Vertex(x);
        GL.Color(selectedAxis == RuntimeHandleAxis.Y ? selectedColor : Color.green);
        GL.Vertex(o);
        GL.Vertex(y);
        GL.Color(selectedAxis == RuntimeHandleAxis.Z ? selectedColor : Color.blue);
        GL.Vertex(o);
        GL.Vertex(z);
        GL.End();

        Vector3 dir = position - camera.transform.position;
        float angleX = Vector3.Angle(target.right, dir);
        float angleY = Vector3.Angle(target.up, dir);
        float angleZ = Vector3.Angle(target.forward, dir);

        bool signX = angleX >= 90 && angleX < 270;
        bool signY = angleY >= 90 && angleY < 270;
        bool signZ = angleZ >= 90 && angleZ < 270;

        quadDir = Vector3.one;
        if (!signX)
        {
            x = -x;
            quadDir.x = -1;
        }
        if (!signY)
        {
            y = -y;
            quadDir.y = -1;
        }
        if (!signZ)
        {
            z = -z;
            quadDir.z = -1;
        }

        // 画方块的边框线
        if (hasQuad)
        {
            GL.Begin(GL.LINES);
            GL.Color(Color.red);
            GL.Vertex(y * quadScale);
            GL.Vertex((y + z) * quadScale);
            GL.Vertex((y + z) * quadScale);
            GL.Vertex(z * quadScale);
            GL.Color(Color.green);
            GL.Vertex(x * quadScale);
            GL.Vertex((x + z) * quadScale);
            GL.Vertex((x + z) * quadScale);
            GL.Vertex(z * quadScale);
            GL.Color(Color.blue);
            GL.Vertex(x * quadScale);
            GL.Vertex((x + y) * quadScale);
            GL.Vertex((x + y) * quadScale);
            GL.Vertex(y * quadScale);
            GL.End();

            // 画三个小方块
            GL.Begin(GL.QUADS);
            GL.Color(selectedAxis == RuntimeHandleAxis.YZ ? selectedColorA : redA);
            GL.Vertex(o * quadScale);
            GL.Vertex(y * quadScale);
            GL.Vertex((y + z) * quadScale);
            GL.Vertex(z * quadScale);
            GL.Color(selectedAxis == RuntimeHandleAxis.XZ ? selectedColorA : greenA);
            GL.Vertex(o * quadScale);
            GL.Vertex(x * quadScale);
            GL.Vertex((x + z) * quadScale);
            GL.Vertex(z * quadScale);
            GL.Color(selectedAxis == RuntimeHandleAxis.XY ? selectedColorA : blueA);
            GL.Vertex(o * quadScale);
            GL.Vertex(x * quadScale);
            GL.Vertex((x + y) * quadScale);
            GL.Vertex(y * quadScale);
            GL.End();
        }

        GL.PopMatrix();
    }

    /// <summary>
    /// 画一个空心圆
    /// </summary>
    private void DrawCircle(Transform target, Vector3 axis, float radius, Color color)
    {
        int detlaAngle = 10;
        float x;
        float y;
        GL.Color(color);

        Vector3 start;
        if (axis.x == 1)
            start = Vector3.up * radius;
        else
            start = Vector3.right * radius;
        Vector3[] circlePos;

        circlePos = new Vector3[360 / detlaAngle];
        
        GL.Vertex(start);
        circlePos[0] = start;
        for (int i = 1; i < 360 / detlaAngle; i++)
        {
            x = Mathf.Cos(i * detlaAngle * Mathf.Deg2Rad) * radius;
            y = Mathf.Sin(i * detlaAngle * Mathf.Deg2Rad) * radius;

            Vector3 temp;
            if (axis.x == 1)
                temp = new Vector3(0, x, y);
            else if (axis.y == 1)
                temp = new Vector3(x, 0, y);
            else
                temp = new Vector3(x, y, 0);
            GL.Vertex(temp);
            GL.Vertex(temp);
            circlePos[i] = temp;
        }
        GL.Vertex(start);

        if (axis.x == 1)
            circlePosX = circlePos;
        else if (axis.y == 1)
            circlePosY = circlePos;
        else
            circlePosZ = circlePos;
    }

    /// <summary>
    /// 绘制坐标系箭头
    /// </summary>
    private void DrawCoordinateArrow(Transform target)
    {
        Vector3 position = target.position;
        Vector3 euler = target.eulerAngles;
        // 画坐标轴的箭头 (箭头的锥顶不是它自身坐标的forword)
        Mesh meshX = CreateArrow(selectedAxis == RuntimeHandleAxis.X ? selectedColor : Color.red, arrowScale * screenScale);
        Graphics.DrawMeshNow(meshX, position + target.right * handleScale * screenScale, target.rotation * Quaternion.Euler(0, 0, -90));
        Mesh meshY = CreateArrow(selectedAxis == RuntimeHandleAxis.Y ? selectedColor : Color.green, arrowScale * screenScale);
        Graphics.DrawMeshNow(meshY, position + target.up * handleScale * screenScale, target.rotation);
        Mesh meshZ = CreateArrow(selectedAxis == RuntimeHandleAxis.Z ? selectedColor : Color.blue, arrowScale * screenScale);
        Graphics.DrawMeshNow(meshZ, position + target.forward * handleScale * screenScale, target.rotation * Quaternion.Euler(90, 0, 0));
    }

    /// <summary>
    /// 创建一个轴的箭头网格
    /// </summary>
    private Mesh CreateArrow(Color color, float scale)
    {
        int segmentsCount = 12;  // 侧面三角形数量
        float size = 1.0f / 5;
        size *= scale;

        Vector3[] vertices = new Vector3[segmentsCount + 2];
        int[] triangles = new int[segmentsCount * 6];
        Color[] colors = new Color[vertices.Length];
        for (int i = 0; i < colors.Length; ++i)
        {
            // 顶点颜色
            colors[i] = color;
        }

        float radius = size / 2.6f; // 地面半径
        float height = size;        // 高
        float deltaAngle = Mathf.PI * 2.0f / segmentsCount;

        float y = -height;

        vertices[vertices.Length - 1] = new Vector3(0, -height, 0); // 圆心点
        vertices[vertices.Length - 2] = Vector3.zero;               // 锥顶

        // 地面圆上的点
        for (int i = 0; i < segmentsCount; i++)
        {
            float angle = i * deltaAngle;
            float x = Mathf.Cos(angle) * radius;
            float z = Mathf.Sin(angle) * radius;

            vertices[i] = new Vector3(x, y, z);
        }

        for (int i = 0; i < segmentsCount; i++)
        {
            // 底面三角形排序
            triangles[i * 6] = vertices.Length - 1;
            triangles[i * 6 + 1] = i;
            triangles[i * 6 + 2] = (i + 1) % segmentsCount;

            // 侧面三角形排序
            triangles[i * 6 + 3] = vertices.Length - 2;
            triangles[i * 6 + 4] = i;
            triangles[i * 6 + 5] = (i + 1) % segmentsCount;
        }

        Mesh cone = new Mesh
        {
            name = "Cone",
            vertices = vertices,
            triangles = triangles,
            colors = colors
        };

        return cone;
    }

    /// <summary>
    /// 绘制坐标系小正方体
    /// </summary>
    private void DrawCoordinateCube(Transform target)
    {
        Vector3 position = target.position;
        Vector3 euler = target.eulerAngles;
        // 画坐标轴的小方块
        shapesMaterial.SetPass(0);
        Mesh meshX = CreateCubeMesh(selectedAxis == RuntimeHandleAxis.X ? selectedColor : Color.red, Vector3.zero, cubeScale * screenScale);
        Graphics.DrawMeshNow(meshX, position + target.right * handleScale * screenScale, target.rotation * Quaternion.Euler(0, 0, -90));
        Mesh meshY = CreateCubeMesh(selectedAxis == RuntimeHandleAxis.Y ? selectedColor : Color.green, Vector3.zero, cubeScale * screenScale);
        Graphics.DrawMeshNow(meshY, position + target.up * handleScale * screenScale, target.rotation);
        Mesh meshZ = CreateCubeMesh(selectedAxis == RuntimeHandleAxis.Z ? selectedColor : Color.blue, Vector3.zero, cubeScale * screenScale);
        Graphics.DrawMeshNow(meshZ, position + target.forward * handleScale * screenScale, target.rotation * Quaternion.Euler(90, 0, 0));

        Mesh meshO = CreateCubeMesh(selectedAxis == RuntimeHandleAxis.XYZ ? selectedColor : Color.white, Vector3.zero, cubeScale * screenScale);
        Graphics.DrawMeshNow(meshO, position, target.rotation);
    }

    /// <summary>
    /// 创建一个方块网格 
    /// </summary>
    public Mesh CreateCubeMesh(Color color, Vector3 center, float scale, float cubeLength = 1, float cubeWidth = 1, float cubeHeight = 1)
    {
        cubeHeight *= scale;
        cubeWidth *= scale;
        cubeLength *= scale;

        Vector3 vertice_0 = center + new Vector3(-cubeLength * .5f, -cubeWidth * .5f, cubeHeight * .5f);
        Vector3 vertice_1 = center + new Vector3(cubeLength * .5f, -cubeWidth * .5f, cubeHeight * .5f);
        Vector3 vertice_2 = center + new Vector3(cubeLength * .5f, -cubeWidth * .5f, -cubeHeight * .5f);
        Vector3 vertice_3 = center + new Vector3(-cubeLength * .5f, -cubeWidth * .5f, -cubeHeight * .5f);
        Vector3 vertice_4 = center + new Vector3(-cubeLength * .5f, cubeWidth * .5f, cubeHeight * .5f);
        Vector3 vertice_5 = center + new Vector3(cubeLength * .5f, cubeWidth * .5f, cubeHeight * .5f);
        Vector3 vertice_6 = center + new Vector3(cubeLength * .5f, cubeWidth * .5f, -cubeHeight * .5f);
        Vector3 vertice_7 = center + new Vector3(-cubeLength * .5f, cubeWidth * .5f, -cubeHeight * .5f);
        Vector3[] vertices = new[]
        {
                // Bottom Polygon
                vertice_0, vertice_1, vertice_2, vertice_3,
                // Left Polygon
                vertice_7, vertice_4, vertice_0, vertice_3,
                // Front Polygon
                vertice_4, vertice_5, vertice_1, vertice_0,
                // Back Polygon
                vertice_6, vertice_7, vertice_3, vertice_2,
                // Right Polygon
                vertice_5, vertice_6, vertice_2, vertice_1,
                // Top Polygon
                vertice_7, vertice_6, vertice_5, vertice_4
            };

        int[] triangles = new[]
        {
                // Cube Bottom Side Triangles
                3, 1, 0,
                3, 2, 1,    
                // Cube Left Side Triangles
                3 + 4 * 1, 1 + 4 * 1, 0 + 4 * 1,
                3 + 4 * 1, 2 + 4 * 1, 1 + 4 * 1,
                // Cube Front Side Triangles
                3 + 4 * 2, 1 + 4 * 2, 0 + 4 * 2,
                3 + 4 * 2, 2 + 4 * 2, 1 + 4 * 2,
                // Cube Back Side Triangles
                3 + 4 * 3, 1 + 4 * 3, 0 + 4 * 3,
                3 + 4 * 3, 2 + 4 * 3, 1 + 4 * 3,
                // Cube Rigth Side Triangles
                3 + 4 * 4, 1 + 4 * 4, 0 + 4 * 4,
                3 + 4 * 4, 2 + 4 * 4, 1 + 4 * 4,
                // Cube Top Side Triangles
                3 + 4 * 5, 1 + 4 * 5, 0 + 4 * 5,
                3 + 4 * 5, 2 + 4 * 5, 1 + 4 * 5,
            };

        Color[] colors = new Color[vertices.Length];
        for (int i = 0; i < colors.Length; ++i)
        {
            colors[i] = color;
        }

        Mesh cubeMesh = new Mesh();
        cubeMesh.name = "cube";
        cubeMesh.vertices = vertices;
        cubeMesh.triangles = triangles;
        cubeMesh.colors = colors;
        cubeMesh.RecalculateNormals();
        return cubeMesh;
    }

最后是控制,当鼠标左键按下时,会在BaseHandle 的派生类中判断是否有碰撞以及是和哪个轴有碰撞,然后根据轴以及鼠标在按下时的移动控制目标物体的变换。

    private void Update()
    {
        if (target)
        {
            if (Input.GetKeyDown(KeyCode.W))
            {
                currentHandle = positionHandle;
                transformMode = TransformMode.Position;
            }
            else if (Input.GetKeyDown(KeyCode.E))
            {
                currentHandle = rotationHandle;
                transformMode = TransformMode.Rotation;
            }
            else if (Input.GetKeyDown(KeyCode.R))
            {
                currentHandle = scaleHandle;
                transformMode = TransformMode.Scale;
            }

            if (!mouseDonw)
                selectedAxis = currentHandle.SelectedAxis();

            ControlTarget();
        }
    }

    /// <summary>
    /// 控制目标
    /// </summary>
    private void ControlTarget()
    {
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            mouseDonw = true;
        }
        if (Input.GetKey(KeyCode.Mouse0))
        {
            float inputX = Input.GetAxis("Mouse X");
            float inputY = Input.GetAxis("Mouse Y");

            float x = 0;
            float y = 0;
            float z = 0;

            switch (selectedAxis)
            {
                case RuntimeHandleAxis.None:
                    break;
                case RuntimeHandleAxis.X:
                    if (!lockX)
                        x = currentHandle.GetTransformAxis(new Vector2(inputX, inputY), target.right);
                    break;
                case RuntimeHandleAxis.Y:
                    if (!lockY)
                        y = currentHandle.GetTransformAxis(new Vector2(inputX, inputY), target.up);
                    break;
                case RuntimeHandleAxis.Z:
                    if (!lockZ)
                        z = currentHandle.GetTransformAxis(new Vector2(inputX, inputY), target.forward);
                    break;
                case RuntimeHandleAxis.XY:
                    if (!lockX)
                        x = currentHandle.GetTransformAxis(new Vector2(inputX, inputY), target.right);
                    if (!lockY)
                        y = currentHandle.GetTransformAxis(new Vector2(inputX, inputY), target.up);
                    break;
                case RuntimeHandleAxis.XZ:
                    if (!lockX)
                        x = currentHandle.GetTransformAxis(new Vector2(inputX, inputY), target.right);
                    if (!lockZ)
                        z = currentHandle.GetTransformAxis(new Vector2(inputX, inputY), target.forward);
                    break;
                case RuntimeHandleAxis.YZ:
                    if (!lockY)
                        y = currentHandle.GetTransformAxis(new Vector2(inputX, inputY), target.up);
                    if (!lockZ)
                        z = currentHandle.GetTransformAxis(new Vector2(inputX, inputY), target.forward);
                    break;
                case RuntimeHandleAxis.XYZ:
                    x = y = z = inputX;
                    if (lockX)
                        x = 0;
                    if (lockY)
                        y = 0;
                    if (lockZ)
                        z = 0;
                    break;
                default:
                    break;
            }

            currentHandle.Transform(new Vector3(x, y, z) * screenScale);
        }
        if (Input.GetKeyUp(KeyCode.Mouse0))
        {
            mouseDonw = false;
        }
    }

3.BaseHandle

四个属性的声明只是为了少些几次RunTimeHandle

派生类主要要实现三个函数

SelectedAxis返回鼠标当前与哪个轴有碰撞

GetTransformAxis传入鼠标输入返回不同类型的Handle想要的值

Transform是控制目标物体位置,旋转和缩放

/// <summary>
/// 手柄基类
/// </summary>
public class BaseHandle
{
    protected Camera camera { get { return RuntimeHandle.camera; } }
    protected Transform target { get { return RuntimeHandle.target; } }
    protected float screenScale{ get { return RuntimeHandle.screenScale; } }
    protected Matrix4x4 localToWorld { get { return RuntimeHandle.localToWorld; } }

    protected float colliderPixel = 10;  // 鼠标距离轴多少时算有碰撞(单位:像素)

    public virtual float GetTransformAxis(Vector2 inputDir, Vector3 axis) { return 0; }
    public virtual void Transform(Vector3 value) { }

    /// <summary>
    /// 最基本的碰撞选择
    /// </summary>
    public virtual RuntimeHandleAxis SelectedAxis()
    {
        float distanceX, distanceY, distanceZ;
        bool hit = HitAxis(Vector3.right, out distanceX);
        hit |= HitAxis(Vector3.up, out distanceY);
        hit |= HitAxis(Vector3.forward, out distanceZ);

        if (hit)
        {
            if (distanceX < distanceY && distanceX < distanceZ)
            {
                return RuntimeHandleAxis.X;
            }
            else if (distanceY < distanceZ)
            {
                return RuntimeHandleAxis.Y;
            }
            else
            {
                return RuntimeHandleAxis.Z;
            }
        }

        return RuntimeHandleAxis.None;
    }

    /// <summary>
    /// 是否和手柄有碰撞
    /// </summary>
    /// <param name="axis"></param>
    /// <param name="localToWorlad">手柄坐标系转换矩阵</param>
    /// <param name="distanceAxis"></param>
    /// <returns></returns>
    public bool HitAxis(Vector3 axis, out float distanceToAxis)
    {
        // 把坐标轴本地坐标转为世界坐标
        axis = localToWorld.MultiplyPoint(axis);

        // 坐标轴转屏幕坐标(有问题)
        Vector2 screenVectorBegin = camera.WorldToScreenPoint(target.position);
        Vector2 screenVectorEnd = camera.WorldToScreenPoint(axis);
        Vector2 screenVector = screenVectorEnd - screenVectorBegin;
        float screenVectorMag = screenVector.magnitude;
        screenVector.Normalize();

        if (screenVector != Vector2.zero)
        {
            Vector2 perp = PerpendicularClockwise(screenVector).normalized;
            Vector2 mousePosition = Input.mousePosition;
            Vector2 relMousePositon = mousePosition - screenVectorBegin;    // 鼠标相对轴远点位置
            distanceToAxis = Mathf.Abs(Vector2.Dot(perp, relMousePositon)); // 在屏幕坐标系中,鼠标到轴的距离

            Vector2 hitPoint = (relMousePositon - perp * distanceToAxis);
            float vectorSpaceCoord = Vector2.Dot(screenVector, hitPoint);

            bool result = vectorSpaceCoord >= 0 && hitPoint.magnitude <= screenVectorMag && distanceToAxis < colliderPixel;
            return result;
        }
        else  // 坐标轴正对屏幕
        {
            Vector2 mousePosition = Input.mousePosition;

            distanceToAxis = (screenVectorBegin - mousePosition).magnitude;
            bool result = distanceToAxis <= colliderPixel;
            if (!result)
            {
                distanceToAxis = float.PositiveInfinity;
            }
            else
            {
                distanceToAxis = 0.0f;
            }
            return result;
        }
    }

    /// <summary>
    /// 获取顺时针的垂直向量
    /// </summary>
    protected Vector2 PerpendicularClockwise(Vector2 vector2)
    {
        return new Vector2(-vector2.y, vector2.x);
    }

}

4.PositionHandle

显示层面上位移旋转和缩放都实现了,控制层面就只实现一个位移了,都差不多,把轴在世界空间中的位置转换成屏幕坐标系,再和鼠标的位置进行一些列的数学运算。旋转和缩放的控制Github中的源码里有,想要的可以去下载。

/// <summary>
/// 位置修改手柄
/// </summary>
public class PositionHandle : BaseHandle
{
    /// <summary>
    /// 返回鼠标和手柄的碰撞信息
    /// </summary>
    public override RuntimeHandleAxis SelectedAxis()
    {
        float scale = screenScale/* * 0.2f*/;
        // TODO 方块的位置是会变化的
        if(HitQuad(target.position,target.right + target.up, camera))
        {
            return RuntimeHandleAxis.XY;
        }
        else if(HitQuad(target.position, target.right + target.forward, camera))
        {
            return RuntimeHandleAxis.XZ;
        }
        else if (HitQuad(target.position, target.up + target.forward, camera))
        { 
            return RuntimeHandleAxis.YZ;
        }

        return base.SelectedAxis();
    }

    public override float GetTransformAxis(Vector2 inputDir, Vector3 axis)
    {
        Vector2 screenStart = camera.WorldToScreenPoint(target.position);
        Vector2 screenEnd = camera.WorldToScreenPoint(target.position + axis);
        Vector2 screenDir = (screenEnd - screenStart).normalized;

        return Vector2.Dot(screenDir, inputDir);
    }

    public override void Transform(Vector3 value)
    {
        target.Translate(value * Time.deltaTime * 20, Space.Self);
    }

    /// <summary>
    /// 是否和小方块有碰撞
    /// </summary>
    /// <param name="axis"></param>
    /// <param name="matrix"></param>
    /// <param name="size"></param>
    /// <returns></returns>
    private bool HitQuad(Vector3 origin, Vector3 offset, Camera camera)
    {
        offset.x *= RuntimeHandle.quadDir.x;
        offset.y *= RuntimeHandle.quadDir.y;
        offset.z *= RuntimeHandle.quadDir.z;
        Vector2 mousePos = Input.mousePosition;
        Vector2 screenOrigin = camera.WorldToScreenPoint(origin);
        Vector2 screenOffset = camera.WorldToScreenPoint(origin + offset);

        if (mousePos.x > Mathf.Max(screenOrigin.x, screenOffset.x) ||
            mousePos.x < Mathf.Min(screenOrigin.x, screenOffset.x) ||
            mousePos.y > Mathf.Max(screenOrigin.y, screenOffset.y) ||
            mousePos.y < Mathf.Min(screenOrigin.y, screenOffset.y))
            return false;
        else
            return true;
    }
}

 

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值