目录
[1. 装配场景的相机控制](#1. 装配场景的相机控制)
[2. 鼠标拖拽和旋转功能的实现](#2. 鼠标拖拽和旋转功能的实现)
[2.1 鼠标拖拽](#2.1 鼠标拖拽)
[2.2 物体旋转](#2.2 物体旋转)
[3. 零件与装配位置的对应关系](#3. 零件与装配位置的对应关系)
[4. 轴向装配的准备位置](#4. 轴向装配的准备位置)
[5. 装配顺序的实现](#5. 装配顺序的实现)
[5.1 标签提示](#5.1 标签提示)
[5.2 定义一个变量记录步骤数值](#5.2 定义一个变量记录步骤数值)
1. 装配场景的相机控制
开始装配功能时,需要将相机调节成适合于装配的移动方式,万一鼠标拖拽零件时镜头游移到其他地方会相当不舒服。
在本应用中的做法是,建立一个空节点,让镜头始终跟随这个节点移动,类似于第三人称控制器,控制镜头在这个节点周围的有限位置。
需要将相机设置为这个固定节点的子节点,并且调整好高度和角度。效果如下:
相机控制代码如下:
cs
public class CameraControl : MonoBehaviour
{//使用鼠标左键点击画面调整角度
public GameObject mainPerson;//跟随点
private float distance;//相机和主角点的距离
private Quaternion Cam_rotation; //相机的旋转角度
public bool isClickRotate=true; //判断是否有旋转命令
private float xAngle,yAngle;
private Vector2 inputPos;
private Vector3 resultPos;
private Ray ray;
public float rotationSpeed=80; //旋转速度
public float yAngleMin=3, yAngleMax=80;
public float lerp = 5; //位置跟随的范围
void Start()
{
distance = Vector3.Distance(mainPerson.transform.position, transform.position);
Cam_rotation = transform.rotation; //记录相机初始角度
//计算相机初始与X、Y轴的角度
xAngle = Vector3.Angle(Vector3.right, transform.right);
yAngle = Vector3.Angle(Vector3.up, transform.up);
}
void Update()
{//或者使用lateUpdate()
if(!isClickRotate)
{//如果点击到了零件,就不再让相机旋转
return;
}
else
{
isClickRotate = Input.GetMouseButton(0);
inputPos.x = Input.GetAxis("Mouse X");
inputPos.y = Input.GetAxis("Mouse Y");
if (isClickRotate)
{
xAngle += inputPos.x * rotationSpeed * Time.deltaTime;
yAngle -= inputPos.y * rotationSpeed * Time.deltaTime;
yAngle = Mathf.Clamp(yAngle, yAngleMin, yAngleMax);
Cam_rotation = Quaternion.Euler(yAngle, xAngle, 0);
transform.rotation = Quaternion.Lerp(transform.rotation, Cam_rotation, Time.deltaTime * 5);
}
resultPos = mainPerson.transform.position - (Cam_rotation * Vector3.forward * distance);
transform.position = Vector3.Lerp(transform.position, resultPos, Time.deltaTime * lerp);
}
//镜头缩放
if (Input.GetAxis("Mouse ScrollWheel") != 0)
{
//鼠标滚动滑轮
if (Input.GetAxis("Mouse ScrollWheel") < 0)
{
//范围值限定
if (Camera.main.fieldOfView <= 100)
Camera.main.fieldOfView += 2;
if (Camera.main.orthographicSize <= 20)
Camera.main.orthographicSize += 0.5F;
}
//Zoom in
if (Input.GetAxis("Mouse ScrollWheel") > 0)
{
//范围值限定
if (Camera.main.fieldOfView > 2)
Camera.main.fieldOfView -= 2;
if (Camera.main.orthographicSize >= 1)
Camera.main.orthographicSize -= 0.5F;
}
}
}
}
2. 鼠标拖拽和旋转功能的实现
2.1 鼠标拖拽
查了一下各路大仙的做法,使用协程方法的效果比较好,由于鼠标拖动是比较耗时的工作,不可能在一帧执行时间内完成,因此使用协程方法建立一个新线程,不影响主线程的执行。代码如下:
cs
public class onMouseDrag3d : MonoBehaviour
{
//偏移值
private Vector3 _Offset;
//当前物体对应的屏幕坐标
private Vector3 _ScreenPos;
private void Start()
{
StartCoroutine(OnMouseDrag());
}
private IEnumerator OnMouseDrag()
{
//当前物体对应的屏幕坐标
_ScreenPos = Camera.main.WorldToScreenPoint(transform.position);
//偏移值=物体的世界坐标,减去转化之后的鼠标世界坐标(z轴的值为物体屏幕坐标的z值)
_Offset = transform.position - Camera.main.ScreenToWorldPoint(new Vector3
(Input.mousePosition.x, Input.mousePosition.y, _ScreenPos.z));
//当鼠标左键点击
while (Input.GetMouseButton(0))
{
//物体当前坐标
transform.position = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x,
Input.mousePosition.y, _ScreenPos.z)) + _Offset;
//等待固定更新,且循环执行
yield return new WaitForFixedUpdate();
}
}
}
将这个代码挂在需要移动的物体上,能够实现移动的效果。
2.2 物体旋转
将下面的旋转脚本挂在物体上,并且需要将物体的层设置为"WorkPiece"层。
cs
public class onMouseRotate : MonoBehaviour
{
//旋转所需的参数
//上下旋转最大角度限制
public int yMinLimit = -20;
public int yMaxLimit = 80;
//旋转速度
public float xSpeed = 250.0f;//左右旋转速度
public float ySpeed = 120.0f;//上下旋转速度
//旋转角度
private float x = 0.0f;
private float y = 0.0f;
private void Update()
{
RaycastHit hit;//射线检测需要旋转的物体
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Physics.Raycast(ray, out hit, 100, LayerMask.GetMask("WorkPiece"));
//零件需要加入WorkPiece层
if (Input.GetMouseButton(1)&&hit.collider!=null)
{//旋转的判断条件为射线接触到物体,且按下了鼠标右键
x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;
y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;
y = ClampAngle(y, yMinLimit, yMaxLimit);
//欧拉角转化为四元数
target.transform.rotation = Quaternion.Euler(y, x, 0);
}
}
//角度范围值限定
static float ClampAngle(float angle, float min, float max)
{
if (angle < -360)
angle += 360;
if (angle > 360)
angle -= 360;
return Mathf.Clamp(angle, min, max);
}
}
3. 零件与装配位置的对应关系
在一个装配场景中,零件数量从几个到上百个不等,因此,需要根据零件数量定义数据表,如数据表、数组或二维数组、字典等数据形式,必要时需要用数据库存储零件信息。这里使用相对容易理解的数组,设置三个数组,分别存储零件、零件位置和提示标签,让这三者的下标一致作为对应标记。
并且可以在初始化时Start() ,使用for循环给每个零件挂上拖拽和旋转的脚本
cs
for (int i = 0; i < 14; i++)
{ //将所有零件挂上onMouseDrag3d脚本
workPiece[i].AddComponent<onMouseDrag3d>();
if (i == 0)
workPiece[i].GetComponent<pieceToPlace>().ifBase = true;
//第一个零件为基准零件(主轴),其他为轴向装配零件
}
4. 轴向装配的准备位置
在主轴一端设置一个轴向准备位置,以便于轴向装配的零件,自由移动到这个位置后,锁定其他两轴,仅沿着轴向装配。(拆卸场景相反)
实现思想是:当零件被自由拖拽,移动到的位置和这个准备位置的距离,小于一定数值时,将这个准备位置的角度和位置赋值给零件。(装配到最终位置也是这么实现的)
cs
var offset = Vector3.Distance(transform.position, piecePlace.transform.position);
if (offset <= 0.75f)
{//在两者距离较接近时让两者重叠
transform.position = piecePlace.transform.position; //位置一致
transform.rotation = piecePlace.transform.rotation; //方向一致
}
之后仅仅让零件在x方向上被拖动,将上面的协程方法修改为如下,其中正负方向和坐标轴需要根据实际情况而定。
cs
private IEnumerator OnMouseDragX()
{//X方向拖拽
//当前物体对应的屏幕坐标
_ScreenPos = Camera.main.WorldToScreenPoint(transform.position);
//偏移值
_Offset = transform.position - Camera.main.ScreenToWorldPoint(new Vector3
(Input.mousePosition.x, Input.mousePosition.y, _ScreenPos.z));
//当鼠标左键点击
while (Input.GetMouseButton(0))
{//修改:y、z方向不变
transform.position = Camera.main.ScreenToWorldPoint(new Vector3(-Input.mousePosition.x,
transform.position.y, transform.position.z)) + Offset;
//等待固定更新,且循环执行
yield return new WaitForFixedUpdate();
}
}
5. 装配顺序的实现
5.1 标签提示
使用UI制作标签,包含Canvas和Text,其中画布Canvas设置为WorldSpace,相机设置为主相机。
这些标签起到在场景中提示拆装顺序的作用:
5.2 定义一个变量记录步骤数值
比如,可以定义一个记录步骤的整型变量,初始值为0,用这个整型变量与零件的数组下标进行比较,如果选取一个零件的下标与步骤变量相符时,可以顺利实现装配,并且变量值+1;当两者不符合时,则提示装配顺序有误。