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