1 需求描述

点选物体、框选物体、绘制外边框 中介绍了物体投影到屏幕上的二维外框绘制方法,本文将介绍物体外框线条盒子绘制方法。

  • 内框:选中物体后,绘制物体的内框(紧贴物体、并与物体姿态一致的内框盒子)
  • 外框:选中物体后,绘制物体的外框(紧贴物体、并与世界坐标系的朝向一致的外框盒子)

​ 内框和外框效果如下,其中,黄色线框是内框,绿色线框是外框。

​ 本文完整代码见→Unity3D绘制物体外框线条盒子

2 需求实现

1)原理

​ 获取物体外框盒子(Bounds)的方法主要有:

Bounds bounds = obj.GetComponent<MeshFilter>().mesh.bounds;
Bounds bounds = obj.GetComponent<Renderer>().bounds;
Bounds bounds = obj.GetComponent<Collider>().bounds;

​ MeshFilter、Render、Collider 获取的 Bounds 区别如下:

  • MeshFilter Bounds:模型原始 mesh 的 Bounds(局部坐标系下的坐标),在 Transform 组件中修改缩放,不会影响其值大小,还原其真实渲染值大小(世界坐标系下的坐标)需要通过transform.TransformPoint(vertex) 变换还原;
  • Renderer Bounds:模型渲染的真实 Bounds(世界坐标系下的坐标),其姿态与世界坐标系的坐标轴朝向保持一致,在 Transform 组件中修改缩放,会影响其值大小;
  • Collider Bounds:模型碰撞体的 Bounds(世界坐标系下的坐标),其姿态与世界坐标系的坐标轴朝向保持一致,在 Transform 组件中修改缩放,会影响其值大小,如果碰撞体与模型表面完全吻合,其 Bounds 与 Renderer 的 Bounds 保持一致。

​ 本文通过 MeshFilter Bounds 绘制内框盒子,通过 Renderer Bounds 绘制外框盒子。

2)场景对象

​ 说明:需要删除 Plane 对象的碰撞体。

3)代码

​ EventDetector.cs

using UnityEngine;

public class EventDetector : MonoBehaviour { // 事件检测器
private MyEventType eventType = MyEventType.None; // 事件类型
private MyEventType lastEventType = MyEventType.None; // 上次事件类型
private float scroll; // 滑轮滑动刻度
private bool detecting; // 事件检测中
private Vector3 clickDownMousePos; // 鼠标按下时的坐标
private const float dragThreshold = 1; // 识别为拖拽的鼠标偏移 private void Update() {
detecting = true;
DetectMouseEvent();
DetectScrollEvent();
UpgradeMouseEvent();
detecting = false;
lastEventType = eventType;
} private void DetectMouseEvent() { // 检测鼠标事件
if (Input.GetMouseButtonDown(0)) { // Click Down
eventType = MyEventType.ClickDown;
clickDownMousePos = Input.mousePosition;
} else if (Input.GetMouseButtonUp(0)) {
if (IsDragEvent(eventType)) { // End Drag
eventType = MyEventType.EndDrag;
} else { // Click Up
eventType = MyEventType.ClickUp;
}
} else if (Input.GetMouseButton(0)) {
if (IsDragEvent(eventType)) { // Drag
eventType = MyEventType.Drag;
} else if (Vector3.Distance(clickDownMousePos, Input.mousePosition) > dragThreshold) { // Begin Drag
eventType = MyEventType.BeginDrag;
} else { // Click
eventType = MyEventType.Click;
}
} else {
eventType = MyEventType.None;
}
} private void DetectScrollEvent() { // 检测滑轮事件
if (eventType != MyEventType.None
&& (!IsBeginEvent(eventType) || lastEventType != MyEventType.None && !IsScrollEvent(lastEventType))) {
scroll = 0;
return;
}
float temScroll = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Abs(scroll) < float.Epsilon && Mathf.Abs(temScroll) > float.Epsilon) { // Begin Scroll
eventType = MyEventType.BeginScroll;
scroll = temScroll;
} else if (Mathf.Abs(scroll) > float.Epsilon && Mathf.Abs(temScroll) < float.Epsilon) { // End Scroll
eventType = MyEventType.EndScroll;
scroll = temScroll;
} else if (Mathf.Abs(temScroll) > float.Epsilon) { // Scroll
eventType = MyEventType.Scroll;
scroll = temScroll;
} else {
scroll = 0;
}
} private void UpgradeMouseEvent() { // 升级鼠标事件(关联键盘事件)
if (eventType == MyEventType.None) {
return;
}
if (IsBeginEvent(eventType)) {
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) {
AddKeyType("Ctrl");
} else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) {
AddKeyType("Alt");
}
} else {
ContinueKeyType(); // 保持按键事件
}
} public MyEventType EventType() { // 事件类型
if (detecting) {
return lastEventType;
}
return eventType;
} public bool HasClickEvent() { // 是否有点击事件
MyEventType type = EventType();
return IsClickEvent(type);
} public bool HasDragEvent() { // 是否有拖拽事件
MyEventType type = EventType();
return IsDragEvent(type);
} public bool HasScrollEvent() { // 是否有滑轮事件
MyEventType type = EventType();
return IsScrollEvent(type);
} public bool HasCtrlScrollEvent() { // 是否有Ctrl滑轮事件
MyEventType type = EventType();
return type >= MyEventType.BeginCtrlScroll && type <= MyEventType.EndCtrlScroll;
} public bool IsBeginDrag() { // 是否是开始拖拽类型事件
MyEventType type = EventType();
return type == MyEventType.BeginDrag || type == MyEventType.BeginCtrlDrag || type == MyEventType.BeginAltDrag;
} public float Scroll() { // 鼠标滑轮滑动刻度
if (HasScrollEvent()) {
return scroll;
}
return 0;
} private bool IsClickEvent(MyEventType type) { // 是否是点击事件
return type >= MyEventType.ClickDown && type <= MyEventType.CtrlClickUp;
} private bool IsDragEvent(MyEventType type) { // 是否是拖拽事件
return type >= MyEventType.BeginDrag && type <= MyEventType.EndAltDrag;
} private bool IsScrollEvent(MyEventType type) { // 是否是滑轮事件
return type >= MyEventType.BeginScroll && type <= MyEventType.EndCtrlScroll;
} private bool IsBeginEvent(MyEventType type) { // 是否是开始类型事件
return type == MyEventType.ClickDown
|| type == MyEventType.BeginDrag
|| type == MyEventType.BeginCtrlDrag
|| type == MyEventType.BeginAltDrag
|| type == MyEventType.BeginScroll
|| type == MyEventType.BeginCtrlScroll;
} private bool HasCtrlKey(MyEventType type) { // 是否有Ctrl按键事件
return type >= MyEventType.CtrlClickDown && type <= MyEventType.CtrlClickUp
|| type >= MyEventType.BeginCtrlDrag && type <= MyEventType.EndCtrlDrag
|| type >= MyEventType.BeginCtrlScroll && type <= MyEventType.EndCtrlScroll;
} private bool HasAltKey(MyEventType type) { // 是否有Alt按键事件
return type >= MyEventType.BeginAltDrag && type <= MyEventType.EndAltDrag;
} private void ContinueKeyType() { // 保持按键事件
if (HasCtrlKey(lastEventType)) {
AddKeyType("Ctrl");
} else if (HasAltKey(lastEventType)) {
AddKeyType("Alt");
}
} private void AddKeyType(string key) { // 添加按键事件
if ("Ctrl".Equals(key)) {
if (eventType == MyEventType.ClickDown) { // 点击事件
eventType = MyEventType.CtrlClickDown;
} else if (eventType == MyEventType.Click) {
eventType = MyEventType.CtrlClick;
} else if (eventType == MyEventType.ClickUp) {
eventType = MyEventType.CtrlClickUp;
} else if (eventType == MyEventType.BeginDrag) { // 拖拽事件
eventType = MyEventType.BeginCtrlDrag;
} else if (eventType == MyEventType.Drag) {
eventType = MyEventType.CtrlDrag;
} else if (eventType == MyEventType.EndDrag) {
eventType = MyEventType.EndCtrlDrag;
} else if (eventType == MyEventType.BeginScroll) { // 滑轮事件
eventType = MyEventType.BeginCtrlScroll;
} else if (eventType == MyEventType.Scroll) {
eventType = MyEventType.CtrlScroll;
} else if (eventType == MyEventType.EndScroll) {
eventType = MyEventType.EndCtrlScroll;
}
} else if ("Alt".Equals(key)) {
if (eventType == MyEventType.BeginDrag) { // 拖拽事件
eventType = MyEventType.BeginAltDrag;
} else if (eventType == MyEventType.Drag) {
eventType = MyEventType.AltDrag;
} else if (eventType == MyEventType.EndDrag) {
eventType = MyEventType.EndAltDrag;
}
}
}
} public enum MyEventType { // 事件类型
None = 0,
ClickDown = 1,
Click = 2,
ClickUp = 3,
CtrlClickDown = 4,
CtrlClick = 5,
CtrlClickUp = 6,
BeginDrag = 10,
Drag = 11,
EndDrag = 12,
BeginCtrlDrag = 13,
CtrlDrag = 14,
EndCtrlDrag = 15,
BeginAltDrag = 16,
AltDrag = 17,
EndAltDrag = 18,
BeginScroll = 20,
Scroll = 21,
EndScroll = 22,
BeginCtrlScroll = 23,
CtrlScroll = 24,
EndCtrlScroll = 25
}

​ 说明: EventDetector 脚本组件挂在相机下,用于统一管理事件。点选物体(ClickUp)、滑动选框(Drag)、场景变换(Ctrl + Drag / Alt + Drag)都有鼠标事件,这些事件相互冲突,不便于在每个类里都去捕获鼠标和键盘事件,因此需要 EventDetector 统一管理事件。

​ ClickSelect.cs

using UnityEngine;

public class ClickSelect : MonoBehaviour {
private EventDetector eventDetector; // 鼠标事件检测器
private LineBoxPainder lineBoxPainder;
private Transform target; // 选中的目标
private RaycastHit hit; // 碰撞信息 private void Awake() {
eventDetector = Camera.main.GetComponent<EventDetector>();
lineBoxPainder = LineBoxPainder.GetInstance();
} private void Update() {
if (eventDetector.EventType() == MyEventType.ClickUp) {
Transform temp = GetHitTrans();
UpdateColor(target, temp);
target = temp;
if (target != null) {
lineBoxPainder.DrawLineBox(target.gameObject);
}
else {
lineBoxPainder.DrawLineBox(null);
}
}
} private void UpdateColor(Transform old, Transform now) { // 更新颜色
if (old != now) {
if (old != null) {
old.GetComponent<Renderer>().material.color = Color.gray;
}
if (now != null) {
now.GetComponent<Renderer>().material.color = Color.red;
}
}
} private Transform GetHitTrans() { // 获取屏幕射线碰撞的物体
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit)) {
return hit.transform;
}
return null;
}
}

​ 说明:ClickSelect 脚本组件挂在 Work 下。

​ LineBoxPainder.cs

using UnityEngine;

public class LineBoxPainder { // 线段盒子渲染器(每个线段盒子由4个矩形组成)
private static LineBoxPainder instance; // 单例
private GameObject lineParent; // 线条盒子父对象
private LineRenderer[][] lineRenderers; // 线段渲染器
private Material lineMaterial; // 线段材质 private LineBoxPainder() {
lineMaterial = new Material(Shader.Find("Hidden/Internal-Colored"));
lineParent = new GameObject("LineBoxPainder");
lineRenderers = new LineRenderer[2][];
lineRenderers[0] = GetLineRenderers("InnerBox", Color.yellow);
lineRenderers[1] = GetLineRenderers("OuterBox", Color.green);
} public static LineBoxPainder GetInstance() { // 获取单例
if (instance == null) {
instance = new LineBoxPainder();
}
return instance;
} public void DrawLineBox(GameObject obj) { // 绘制内框盒子和外框盒子
Vector3[] InnerVertices = BoxProvider.GetInnerBox(obj);
DrawBox(lineRenderers[0], InnerVertices); // 绘制内框盒子
Vector3[] OuterVertices = BoxProvider.GetOuterBox(obj);
DrawBox(lineRenderers[1], OuterVertices); // 绘制外框盒子
} private LineRenderer[] GetLineRenderers(string name, Color color) { // 获取线段渲染器
Material material = new Material(lineMaterial);
material.color = color;
GameObject box = new GameObject(name);
box.transform.parent = lineParent.transform;
LineRenderer[] lines = new LineRenderer[4];
for (int i = 0; i < lines.Length; i++) {
GameObject line = new GameObject("Line" + i);
line.transform.parent = box.transform;
lines[i] = line.AddComponent<LineRenderer>();
lines[i].material = material;
lines[i].textureMode = LineTextureMode.Tile;
lines[i].widthMultiplier = 0.2f;
lines[i].startWidth = 0.05f;
lines[i].endWidth = 0.05f;
lines[i].positionCount = 0;
lines[i].loop = true;
}
return lines;
} private void DrawBox(LineRenderer[] lines, Vector3[] vertices) { // 绘制一个长方体线段盒子, 每个盒子由4个矩形组成
if (vertices == null || vertices.Length == 0) {
for (int i = 0; i < 4; i++) { // 清空线段顶点
lines[i].positionCount = 0;
}
return;
}
else
{
for (int i = 0; i < 4; i++) { // 初始化线段顶点
lines[i].positionCount = 4;
}
}
for (int i = 0; i < 4; i++) { // 计算每个矩形的顶点序列
lines[0].SetPosition(i, vertices[i]);
lines[1].SetPosition(i, vertices[i + 4]);
if (i < 2) {
lines[2].SetPosition(i, vertices[i]);
lines[3].SetPosition(i, vertices[i + 2]);
} else {
lines[2].SetPosition(i, vertices[7 -i]);
lines[3].SetPosition(i, vertices[9 -i]);
}
}
}
}

​ 说明:LineBoxPainder 用于绘制内框和外框线段盒子,每个盒子使用 4 个 LineRenderer 渲染(对应 4 个矩形),每个 LineRenderer 有 4 个顶点,并设置为 loop,用于渲染一个矩形,一个长方体需要 4 个矩形拼成。

​ BoxProvider.cs

using UnityEngine;

public class BoxProvider { // 盒子提供者
public static Vector3[] GetInnerBox(GameObject obj) { // 获取内框盒子8个顶点数据
if (obj == null || obj.GetComponent<MeshFilter>() == null) {
return null;
}
Bounds bounds = obj.GetComponent<MeshFilter>().mesh.bounds;
Vector3[] vertices = GetBoxVertices(bounds);
for (int i = 0; i < vertices.Length; i++) { // 将局部坐标转换为世界坐标
vertices[i] = obj.transform.TransformPoint(vertices[i]);
}
return vertices;
} public static Vector3[] GetOuterBox(GameObject obj) { // 获取外框盒子8个顶点数据
if (obj == null || obj.GetComponent<Renderer>() == null) {
return null;
}
Bounds bounds = obj.GetComponent<Renderer>().bounds;
//Bounds bounds = obj.GetComponent<Collider>().bounds;
Vector3[] vertices = GetBoxVertices(bounds);
return vertices;
} private static Vector3[] GetBoxVertices(Bounds bounds) { // 根据中心坐标和半边长计算8个顶点的数据
Vector3 center = bounds.center;
Vector3 extents = bounds.extents;
Vector3[] vertices = new Vector3[8];
vertices[0] = center + new Vector3(extents.x, extents.y, extents.z);
vertices[1] = center + new Vector3(extents.x, extents.y, -extents.z);
vertices[2] = center + new Vector3(extents.x, -extents.y, -extents.z);
vertices[3] = center + new Vector3(extents.x, -extents.y, extents.z);
vertices[4] = center + new Vector3(-extents.x, extents.y, extents.z);
vertices[5] = center + new Vector3(-extents.x, extents.y, -extents.z);
vertices[6] = center + new Vector3(-extents.x, -extents.y, -extents.z);
vertices[7] = center + new Vector3(-extents.x, -extents.y, extents.z);
return vertices;
}
}

​ 说明:BoxProvider 用于计算物体外框的 8 个顶点序列。本文通过 MeshFilter Bounds 绘制内框盒子,通过 Renderer Bounds 绘制外框盒子。

​ SceneController.cs

using System;
using UnityEngine; public class SceneController : MonoBehaviour { // 场景变换控制器
private EventDetector eventDetector; // 鼠标事件检测器
public Action camChangedHandler; // 相机改变处理器
private Transform cam; // 相机
private float nearPlan; // 近平面
private Vector3 preMousePos; // 上一帧的鼠标坐标 private void Awake() {
cam = Camera.main.transform;
nearPlan = Camera.main.nearClipPlane;
eventDetector = cam.GetComponent<EventDetector>();
} private void Update() { // 更新场景(Ctrl+Scroll: 缩放场景, Ctrl+Drag: 平移场景, Alt+Drag: 旋转场景)
if (eventDetector.HasCtrlScrollEvent()) { // 缩放场景
ScaleScene(eventDetector.Scroll());
} else if (eventDetector.IsBeginDrag()) {
preMousePos = Input.mousePosition;
} else if (eventDetector.HasDragEvent()) {
Vector3 offset = Input.mousePosition - preMousePos;
if (eventDetector.EventType() == MyEventType.CtrlDrag) { // 移动场景
MoveScene(offset);
} else if (eventDetector.EventType() == MyEventType.AltDrag) { // 旋转场景
RotateScene(offset);
}
preMousePos = Input.mousePosition;
}
} private void ScaleScene(float scroll) { // 缩放场景
cam.position += cam.forward * scroll;
camChangedHandler?.Invoke();
} private void MoveScene(Vector3 offset) { // 平移场景
cam.position -= (cam.right * offset.x / 100 + cam.up * offset.y / 100);
camChangedHandler?.Invoke();
} private void RotateScene(Vector3 offset) { // 旋转场景
Vector3 rotateCenter = GetRotateCenter(0);
cam.RotateAround(rotateCenter, Vector3.up, offset.x / 3); // 水平拖拽分量
cam.LookAt(rotateCenter);
cam.RotateAround(rotateCenter, -cam.right, offset.y / 5); // 竖直拖拽分量
camChangedHandler?.Invoke();
} private Vector3 GetRotateCenter(float planeY) { // 获取旋转中心
if (Mathf.Abs(cam.forward.y) < Vector3.kEpsilon || Mathf.Abs(cam.position.y) < float.Epsilon)
{
return cam.position + cam.forward * (nearPlan + 1 / nearPlan);
}
float t = (planeY - cam.position.y) / cam.forward.y;
float x = cam.position.x + t * cam.forward.x;
float z = cam.position.z + t * cam.forward.z;
return new Vector3(x, planeY, z);
}
}

​ SceneController 脚本组件挂在相机下,用于平移、旋转、缩放场景,其原理见→缩放、平移、旋转场景

3 运行效果

4 拓展

​ 本节主要介绍长方体盒子的信息解析,主要解决以下 2 个问题:

  • 已知长方体顶点坐标,求长方体中心坐标、尺寸、旋转角度;
  • 已知长方体中心坐标、尺寸、旋转角度,求长方体顶点坐标。

​ BoxParser.cs

using UnityEngine;

public class BoxParser { // 解析盒子信息
// 已知长方体顶点坐标, 获取长方体中心坐标、尺寸、旋转角度
// 输入的顶点顺序: 右上前、右上后、右下后、右下前、左上前、左上后、左下后、左下前
public static void GetBoxInfo(Vector3[] vertices, out Vector3 center, out Vector3 extents, out Vector3 rotation) {
center = (vertices[0] + vertices[6]) / 2;
float sizeX = Vector3.Distance(vertices[0], vertices[4]); // 上前棱长
float sizeY = Vector3.Distance(vertices[0], vertices[3]); // 右前棱长
float sizeZ = Vector3.Distance(vertices[0], vertices[1]); // 右上棱长
extents = new Vector3(sizeX, sizeY, sizeZ) / 2;
Vector3 forward = (vertices[0] + vertices[7]) / 2 - center; // 本地向前的向量
Vector3 up = (vertices[0] + vertices[5]) / 2 - center; // 本地向上的向量
Quaternion qua = Quaternion.LookRotation(forward, up);
rotation = qua.eulerAngles;
} // 已知长方体中心坐标、尺寸、旋转角度, 获取长方体顶点坐标
public static Vector3[] GetVertices(Vector3 center, Vector3 extents, Vector3 rotation) {
Vector3[] vertices = GetInitVertices(extents);
RotateAndTranslate(vertices, rotation, center);
return vertices;
} private static Vector3[] GetInitVertices(Vector3 extents) { // 根据半边长计算8个顶点的数据
// 输出的顶点顺序: 右上前、右上后、右下后、右下前、左上前、左上后、左下后、左下前
Vector3[] vertices = new Vector3[8];
vertices[0] = new Vector3(extents.x, extents.y, extents.z);
vertices[1] = new Vector3(extents.x, extents.y, -extents.z);
vertices[2] = new Vector3(extents.x, -extents.y, -extents.z);
vertices[3] = new Vector3(extents.x, -extents.y, extents.z);
vertices[4] = new Vector3(-extents.x, extents.y, extents.z);
vertices[5] = new Vector3(-extents.x, extents.y, -extents.z);
vertices[6] = new Vector3(-extents.x, -extents.y, -extents.z);
vertices[7] = new Vector3(-extents.x, -extents.y, extents.z);
return vertices;
} private static void RotateAndTranslate(Vector3[] vertices, Vector3 rotation, Vector3 center) { // 旋转和平移
Quaternion qua = Quaternion.Euler(rotation.x, rotation.y, rotation.z);
Matrix4x4 matrix = Matrix4x4.Rotate(qua);
for (int i = 0; i < vertices.Length; i++) { // 将局部坐标转换为世界坐标
vertices[i] = center + matrix.MultiplyPoint(vertices[i]);
}
}
}

​ 声明:本文转自【Unity3D】绘制物体外框线条盒子

【Unity3D】绘制物体外框线条盒子的更多相关文章

  1. unity3D 游戏物体同时绑定单击、双击事件

    前言 在unity中我们常用的获取鼠标点击的方法有 在3D场景中,一般用在Update方法中,每一帧调用 void Update(){ )){ Debug.log("鼠标左键点击" ...

  2. Three.js之绘制物体的边框及修改lineWidth

    本博文主要记录如何使用three.js绘制物体的边框及修改其lineWidth.three.js是个技术点比较多,查询资料又比较少的框架,单单就这个修改lineWidth就是一个坑.先放一个动态的效果 ...

  3. 绘制扇形效果线条小Bug解决

    绘制线条基本代码: 变量: CPoint m_ptOrigin;//起点坐标 bool m_bTrue;//检查鼠标左键是否按下 CPoint m_ptOldOrigin;//记录上一次绘制终点坐标, ...

  4. Unity3D 角色(物体) 移动方法 合集

    1. 简介 在Unity3D中,有多种方式可以改变物体的坐标,实现移动的目的,其本质是每帧修改物体的position. 2. 通过Transform组件移动物体 Transform 组件用于描述物体在 ...

  5. Mentor_丝印检查——手工绘制丝印线条(标注)到丝印位号距离的检查

    http://www.eda365.com/thread-193942-1-1.html 在此之前丝印的检查基本是停留在丝印与阻焊的距离检查,而器件丝印框和手工绘制的线条与器件位号的检查都不到位,据我 ...

  6. 时光煮雨 Unity3D让物体动起来③—UGUI DoTween&Unity Native2D实现

    本文首发蛮牛,次发博客园.接系列 第一篇,第二篇,本文为第三篇,再次感谢“武装三藏”在前两篇无私且精彩的问题解答 写在最前,时光煮雨,为了怀念 以下引用曾今读过的一些教程文章 其实这3种动画都有它特定 ...

  7. unity3d游戏物体跟着鼠标方向移动

    效果:当点击鼠标左键时,游戏对象会朝鼠标点击的方向移动,类似魔兽争霸一样. 思路:把鼠标的坐标转化成世界坐标(鼠标默认是屏幕坐标),然后当点击鼠标时,物体将朝着鼠标的世界坐标方向移动. 如果你看到这的 ...

  8. unity3d中物体的控制

    一.物体的循环移动和旋转 思路:通过对时间的计算,每隔一段时间让物体旋转,实现来回移动. float TranslateSpeed = 0.02f; float TranslateSpeedTime ...

  9. Unity3d创建物体,寻找物体,加载物体,添加脚本

    GetCreateObject: using UnityEngine; public class GetCreateObject : MonoBehaviour { GameObject emptyG ...

  10. Unity3d 控制物体移动、旋转、缩放

    在Unity中通过利用 Input Manager(输入管理器)可以很简单的实现对一个物体进行移动.旋转.缩放操作. 演示代码: //通过虚拟轴控制物体移动.旋转.缩放 public class Mo ...

随机推荐

  1. Laravel - Eloquent 模型查询

    Laravel 的 Eloquent ORM 提供了漂亮.简洁的 ActiveRecord 实现来和数据库进行交互.每个数据库表都有一个对应的「模型」可用来跟数据表进行交互.你可以通过模型查找数据表内 ...

  2. [转帖]SSH交互式脚本StrictHostKeyChecking选项 benchmode=yes

    https://www.cnblogs.com/klb561/p/11013774.html SSH 公钥检查是一个重要的安全机制,可以防范中间人劫持等黑客攻击.但是在特定情况下,严格的 SSH 公钥 ...

  3. CS5280H 无网络安装KVM虚拟机的过程

    背景 信创海光机器 想进行虚拟化 自带了银河麒麟V10 SP1的操作系统. 但是没有安装virt-manager等工具 会议室里面的网口又都坏了. 所以准备挑战一下无网络安装KVM. 过程1 第一步. ...

  4. [转贴]30 分钟学会 AWK

    30 分钟学会 AWK https://mp.weixin.qq.com/s/X0ire4dYiceC2CzPU6JsSw? Linux爱好者 2017-01-08   (点击上方公众号,可快速关注) ...

  5. 解锁前端新潜能:如何使用 Rust 锈化前端工具链

    ​ 前言 近年来,Rust的受欢迎程度不断上升.首先,在操作系统领域,Rust 已成为 Linux 内核官方认可的开发语言之一,Windows 也宣布将使用 Rust 来重写内核,并重写部分驱动程序. ...

  6. Docker搭建SvnServer

    下载svn-server官方镜像 docker pull garethflowers/svn-server 运行svn-server容器 docker run -v /home/svn:/var/op ...

  7. js加减乘除运算出现精度丢失

    做乘法运算出现精度丢失 let aa= 2106.49 console.log( aa*10000 ) //21064899.999999996 console.log( Math.round(aa* ...

  8. Vue基础系列文章11---router基本使用

    1.系统中引入路由js文件,加两个连接,分别到用户管理和用户注册页面 <router-link to="/user">用户列表</router-link> ...

  9. Go中sync.map使用小结

    sync.map 前言 深入了解下 查看下具体的实现 Load Store Delete LoadOrStore 总结 流程图片 参考 sync.map 前言 Go中的map不是并发安全的,在Go1. ...

  10. 深入探索OCR技术:前沿算法与工业级部署方案揭秘

    深入探索OCR技术:前沿算法与工业级部署方案揭秘 注:以上图片来自网络 1. OCR技术背景 1.1 OCR技术的应用场景 OCR是什么 OCR(Optical Character Recogniti ...