Unity3D之空间转换学习笔记(一):场景物体变换

该系列笔记基于Unity3D 5.x的版本学习,部分API使用和4.x不一致。

目前在Unity3D中,除了新的UGUI部分控件外,所有的物体(GameObject)都必带有Transform组件,而Transform组件主要是控制物体在3D空间中的位置、旋转以及缩放。

学习和掌握物体的变换是Unity3D开发者必备的基础知识。

基础变换

最基础的变换就是通过脚本直接对物体的位置旋转缩放等进行变换。

匀速移动

我们下面实现一个匀速移动物体的效果,我们在场景中添加一个Cube物体,把下面的脚本绑定到摄像机上并把Cube拖拽赋予transfrom属性。

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class Demo01Script : MonoBehaviour
 5 {
 6     public Transform myTransform;
 7
 8     void Start()
 9     {
10     }
11
12     void Update()
13     {
14         myTransform.position = new Vector3(myTransform.position.x, myTransform.position.y + 1.0f * Time.deltaTime, myTransform.position.z);
15     }
16 }

运行游戏,我们会发现Cube会匀速上升。我们回到编辑场景,对Cube进行任意的旋转后运行游戏该Cube仍然是向上上升,这是因为位置和旋转是相互独立的,我们直接操作位置的话程序是不会理会旋转属性的,更换为localPosition效果也是一致的。

根据物体方向匀速移动

我们发现如果使用上面的方法来按照物体面向的方向移动物体是不容易的,我们需要根据物体的朝向计算出x、y、z这3个分量的数值再应用回物体中才行,这需要扎实的3维运算功底,不过好在Unity已经给我们提供了大量的属性及方法,方便我们直接调用来达到我们需要的效果。

本地坐标系变量

  • transform.right:物体本地坐标的x轴正方向朝向,1米的单位。
  • transform.up:物体本地坐标的y轴正方向朝向,1米的单位。
  • transform.forward:物体本地坐标的z轴正方向朝向,1米的单位。

由于我们知道了物体本地坐标的信息,所以可以方便的通过这个来按照物体的朝向移动物体了,比如,下面的代码会朝着物体的y轴正方向每秒1米的速度匀速移动:

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class Demo01Script : MonoBehaviour
 5 {
 6     public Transform myTransform;
 7
 8     void Start()
 9     {
10     }
11
12     void Update()
13     {
14         Vector3 pos = myTransform.position;
15         pos.x += myTransform.up.x * 1.0f * Time.deltaTime;
16         pos.y += myTransform.up.y * 1.0f * Time.deltaTime;
17         pos.z += myTransform.up.z * 1.0f * Time.deltaTime;
18         myTransform.position = pos;
19     }
20 }

坐标系转换

由于坐标系存在本地坐标系和世界坐标系两种,那么就需要有方法可以对这两种坐标系进行转换。

  • transform.localToWorldMatrix:本地坐标转世界坐标的矩阵信息。
  • transform.worldToLocalMatrix:世界坐标转本地坐标的矩阵信息。
  • transform.TransformDirection:将方向从本地坐标转换为世界坐标,不受缩放影响。
  • transform.InverseTransformDirection:将方向从世界坐标转换为本地坐标,不受缩放影响。
  • transform.TransformPoint:将位置从本地坐标转换为世界坐标,受缩放影响。
  • transform.InverseTransformPoint:将位置从世界坐标转换为本地坐标,受缩放影响。
  • transform.TransformVector:将坐标点从本地坐标转换为世界坐标,不受位置影响但受缩放影响。
  • transform.InverseTransformVector:将坐标点从世界坐标转换为本地坐标,不受位置影响但受缩放影响。

TransformPoint和TransformVector的区别

下面我们看看这两个方法的区别,首先,我们添加一个空物体到舞台并设置该物体的坐标为(1,1,1),然后把Cube对象拖入该空物体成为其子项设定其坐标为(2,2,2),修改脚本如下:

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class Demo01Script : MonoBehaviour
 5 {
 6     public Transform myTransform;
 7
 8     void Start()
 9     {
10         Vector3 pos = myTransform.TransformPoint(new Vector3(1, 1, 1));
11         Debug.Log(pos);
12         //(4.0, 4.0, 4.0)
13
14         Vector3 vec = myTransform.TransformVector(new Vector3(1, 1, 1));
15         Debug.Log(vec);
16         //(1.0, 1.0, 1.0)
17     }
18
19     void Update()
20     {
21     }
22 }

接下来我们把空物体的尺寸缩小一半看看结果会如何:

结论

TransformPoint转变会受物体的位置和缩放影响转换,而TransformVector仅受物体的缩放影响转换。

Demo01

这里做了一个示例,具体的功能是按下指定的键抓取到场景中的小盒子,使其始终位于屏幕前方,按下另一个键将这个小盒子抛出。

下面我们看核心的实现。

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class Demo01Script : MonoBehaviour
 5 {
 6     public Transform cube;
 7
 8     void Start()
 9     {
10     }
11
12     void Update()
13     {
14         //抓取小盒子
15         if (Input.GetKey(KeyCode.Q))
16         {
17             //设定小盒子的位置到屏幕前方
18             cube.transform.position = transform.TransformPoint(new Vector3(0, 0, 2));
19             //将小盒子设定为读取对象的子对象, 保证跟随运动
20             cube.transform.SetParent(transform);
21             //去掉物理交互
22             cube.GetComponent<Rigidbody>().isKinematic = true;
23         }
24         //扔出小盒子
25         if (Input.GetKey(KeyCode.E))
26         {
27             if (cube.transform.parent == transform)
28             {
29                 //使用掉物理交互
30                 cube.GetComponent<Rigidbody>().isKinematic = false;
31                 //解除所有子物件的绑定关系
32                 transform.DetachChildren();
33                 //获取方向
34                 Vector3 cameraDirect = transform.TransformDirection(0, 0, 5);
35                 //添加缓冲的力
36                 cube.GetComponent<Rigidbody>().AddForce(cameraDirect, ForceMode.Impulse);
37             }
38         }
39     }
40 }

我们先将摄像机前的一个点转换为世界坐标赋予给小盒子的世界坐标使其位于摄像机之前,抛出时把摄像机向前方向的一个向量转换为世界方向赋予小盒子抛出。

位移

Unity3D里提供了方便控制物体位移的属性及方法。

本地和世界坐标

transform.position:设置和获取物件的世界坐标。

transform.localPosition:设置和获取物件的本地坐标,相对于父级的坐标。

注意,在Inspector面板中的Transform里显示的是本地坐标。

Translate

Transform的Translate方法可以更加方便的对物体的位移进行操作,该方法有四个重载:

1 public function Translate(translation: Vector3, relativeTo: Space = Space.Self): void;
2 public function Translate(x: float, y: float, z: float, relativeTo: Space = Space.Self): void;

相对于本地坐标系或世界坐标系对物体进行位移操作。

1 public function Translate(translation: Vector3, relativeTo: Transform): void;
2 public function Translate(x: float, y: float, z: float, relativeTo: Transform): void;

相对于指定物体的坐标进行位移操作。

注意:如果是相对于本地坐标系,则如果向上移动就是朝向物体本身的上方移动,如果是相对于世界坐标系则是向世界的上方向移动,如果是相对于其他物体则是向这指定的物体的上方向移动。

AnimationCurve

AnimationCurve可以用来定义自定义的动画轨迹,我们通过在脚本中声明一个该类型的对象,就可以在编辑器窗口对其进行编辑,然后使我们的物体按照编辑的轨迹进行移动等操作。

比如我们想要得到一个物体在X轴匀速移动,Y轴进行上下循环移动的时候,可以使用下面的脚本:

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class Demo02Script : MonoBehaviour
 5 {
 6     public AnimationCurve myAnimationCurve;
 7
 8     public Transform myTransform;
 9
10     void Start()
11     {
12     }
13
14     void Update()
15     {
16         myTransform.position = new Vector3(
17             myTransform.position.x + 1 * Time.deltaTime,
18             myAnimationCurve.Evaluate(Time.time * 0.5f) * 2,
19             myTransform.position.z);
20     }
21 }

编辑器编辑的曲线如下:

Demo02

在游戏中都会有一个最基本的需求,就是移动到指定的点,下面我们来实现一下这个基本的功能,脚本如下:

 1 using System;
 2 using UnityEngine;
 3 using System.Collections;
 4
 5 public class Demo02Script : MonoBehaviour
 6 {
 7     public Transform myTransform;
 8     public Transform myTarget;
 9
10     private bool _isArrived = true;
11     private Vector3 _origin;
12     private Vector3 _target;
13     private float _speed;
14     private Action _onArrived;
15     private float _allTime;
16     private float _time;
17
18     void Start()
19     {
20         MoveTo(myTarget.position, 1, () => Debug.Log("I am arrived!"));
21     }
22
23     void Update()
24     {
25         if (!_isArrived)
26         {
27             _time += Time.deltaTime;
28             //判断是否抵达终点
29             if (_time >= _allTime)
30             {
31                 //校正位置
32                 myTransform.position = _target;
33                 //标记到达和调用回调
34                 _isArrived = true;
35                 if (_onArrived != null)
36                 {
37                     _onArrived();
38                 }
39             }
40             else
41             {
42                 //这里使用Lerp方法进行差值运算也可以得到相同的效果, 但是我们作为学习还是自己实现
43                 //myTransform.position = Vector3.Lerp(_origin, _target, _time / _allTime);
44
45                 //获取方向的单位向量
46                 Vector3 dirction = _target - _origin;
47                 dirction.Normalize();
48                 //朝方向运动
49                 myTransform.Translate(dirction * Time.deltaTime);
50             }
51         }
52     }
53
54     /// <summary>
55     /// 移动到指定点.
56     /// </summary>
57     /// <param name="targetPosition">目标点.</param>
58     /// <param name="speed">移动速度, 米/秒.</param>
59     /// <param name="onArrived">到达后调用的方法.</param>
60     private void MoveTo(Vector3 targetPosition, float speed, Action onArrived)
61     {
62         _isArrived = false;
63         _origin = myTransform.position;
64         _target = targetPosition;
65         _speed = speed;
66         _onArrived = onArrived;
67
68         //计算总共需要花费的时间
69         _allTime = Vector3.Distance(myTransform.position, _target) / _speed;
70         //重置使用的时间
71         _time = 0;
72     }
73 }

运行后小盒子会想指定的物体进行匀速移动,到达后会输出“I am arrived!”的字符串。

旋转之欧拉角

欧拉角是由3个轴的旋转角度组成的旋转数据,比如我们在Inspector界面的Transform中看到的就是物体本地坐标系的欧拉角:

欧拉角每个轴数字都在0-360之间,表示其旋转的角度。

Rotate

官方提供的旋转方法,其一共有三个重载方法:

1 public function Rotate(eulerAngles: Vector3, relativeTo: Space = Space.Self): void;
2 public function Rotate(xAngle: float, yAngle: float, zAngle: float, relativeTo: Space = Space.Self): void;

指定在本地坐标系或世界坐标系下旋转到指定的角度。

public function Rotate(axis: Vector3, angle: float, relativeTo: Space = Space.Self): void;

指定在本地坐标系或世界坐标系下基于轴axis进行旋转,旋转到angle角度。

RotateAround

我们先看看其参数:

public function RotateAround(point: Vector3, axis: Vector3, angle: float): void;

表示我们的物体围绕指定的点point在轴axis下旋转angle的角度。

LookAt

可以使物体面向指定的点,我们看看其参数:

1 public void LookAt(Transform target, Vector3 worldUp = Vector3.up);
2 public void LookAt(Vector3 worldPosition, Vector3 worldUp = Vector3.up);

即使我们的物体面向指定的物体或点。

旋转之四元数

欧拉角理解和使用都相当的方便,但是在实际进行旋转时存在万向锁的问题,所以引入了比较抽象的四元数的概念,当然我们在Unity中只要直接使用即可,是非常方便的。

这里提供一个视频,可以让大家直观的了解什么是万向锁:http://v.youku.com/v_show/id_XNzkyOTIyMTI=.html

Quaternion

在Transform中,eulerAngles属性是使用欧拉角来表示旋转,而rotation属性则是使用四元数来表示旋转。

四元数提供了许多的静态方法来使我们完成特定需求的效果,点击这里可查看帮助。

Demo03

如果我们想要实现一个效果,物体匀速旋转到指定角度时,使用欧拉角对每个轴进行变换是相当复杂的,同时如果两个轴重合了就会出现万向锁的问题,无法解决,而使用四元数则可以避免这些问题,下面是实现的脚本:

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class Demo03Script : MonoBehaviour
 5 {
 6     public Transform myTransform;
 7     public Transform myTarget;
 8
 9     void Start()
10     {
11     }
12
13     void Update()
14     {
15         RotateToTarget();
16     }
17
18     private void RotateToTarget()
19     {
20         //获取目标方向的单位向量
21         Vector3 dicetion = (myTarget.position - myTransform.position).normalized;
22         //获取目标方向的四元数对象
23         Quaternion targetDicetion = Quaternion.LookRotation(dicetion);
24         //按照每秒 45 度的速度旋转面向目标对象
25         myTransform.rotation = Quaternion.RotateTowards(myTransform.rotation, targetDicetion, 45 * Time.deltaTime);
26     }
27 }

这样我们就可以使我们的物体匀速的转向指定的目标对象了。

缩放与位置关系

缩放

缩放比较简单,没有提供更多的方法。

  • Transform.lossyScale:只读,获取本物体相对于世界坐标的缩放大小。
  • Transform.localScale:设置或获取本物体相对于父级IDE缩放大小。

位置关系

在Unity3D中,所有3D对象是按照树形结构进行组合的,而操作物体之间的位置关系的所有API都存放在Transform对象中,下面我们看看常用的属性及方法。

属性

  • Transform.parent:设置和获取父级对象。
  • Transform.root:获取层次最高的对象。
  • Transform.childCount:获取子级对象的数量。

方法

  • Transform.Find:根据名字寻找子项。
  • Transform.IsChildOf:判断是否为指定Transform对象的子项。
  • Transform.DetachChildren:解除所有子项。
  • Transform.GetChild:根据索引获取子项。
  • Transform.GetSiblingIndex:获取同一级别的物体的索引。
  • Transform.SetAsFirstSibling:设置为同一级别的物体为第一个索引。
  • Transform.SetAsLastSibling:设置为同一级别的物体为最后一个索引。
  • Transform.SetSiblingIndex:设置同一级别的物体的索引。

工程文件下载

敬请期待

时间: 2024-11-08 19:13:44

Unity3D之空间转换学习笔记(一):场景物体变换的相关文章

Unity3D之空间转换学习笔记(二):基础数学

这期笔记我们专注Unity提供的各种数学相关的类来学习. 时间Time API文档地址:http://docs.unity3d.com/ScriptReference/Time.html 时间加/减速 Demo04是一个自由落体的弹性小球从天空落下的一个场景,用于测试下面的示例所用. 这个效果在游戏中也比较常用,比如在战斗游戏中,打败BOSS或我方死亡时都会呈现一种慢镜头的效果. 我们的脚本按下空格将会减速10倍的时间,脚本绑定在摄像机上,代码如下: 1 using UnityEngine; 2

Unity3D之空间转换学习笔记(三):3D数学

3D数学基础 向量 向量可以看做具有方向和大小的一条线段. 比如:我们如果用点A减去点B,则可以得到一个向量,该向量的方向为点B面向点A的方向,而大小为两点的距离.这个方法在游戏开发中经常用到,比如我们要让物体B面向物体A,一般都是使用物体B的位置减去物体A的位置,得到的向量取模. 点积 向量的点积表示一个向量在另一个向量上的投影,如下: 而相互垂直的两个向量点积为0: 我们可以使用这个特性来判断两个向量是否垂直. Unity提供的计算点积的方法如下: public static float D

Cocos2dx 学习笔记整理----场景切换

据说Cocos2dx场景切换的方法有32种:cocos2dx 常见的32种切换场景的动画 无需一一求证,只需要知道切换场景需要怎么做就行了. 作为导演CCDirector,切换场景的事情当然归它管了. 切换场景的接口如下: ? 1 CCDirector::sharedDirector()->replaceScene(cocos2d:CCScene * pScene); 所以,我们只要把需要切换的场景实例传进去就可以了. ? 1 2 CCScene * pScene = GameMain::sce

Unity3d之Hash&amp;Slash学习笔记(一)--角色属性类的构架

角色属性类的构架 角色属性类有8个类,继承关系如下图: 每个类的具体作用见之后的随笔 Unity3d之Hash&Slash学习笔记(一)--角色属性类的构架

GSL 学习笔记(快速傅立叶变换)

GSL 学习笔记(快速傅立叶变换) GNU Scientific Library (GSL)是一个开源的科学计算的函数库,里面实现了大量的数学函数,还提供了方程求解.傅立叶变换等多种功能. GSL 中FFT 的定义如下, 正变换(forward): 逆变换(inverse): 还有一个叫做反向变换: 反变换(backward): 复数FFT,长度为2^N 这是最简单的一种.C89标准中没有定义复数类型,不过gsl 倒是给了个gsl_complex 类型.我们可以使用这个类型,或者直接实部虚部交替

Unity3D 骨骼动画原理学习笔记

最近研究了一下游戏中模型的骨骼动画的原理,做一个学习笔记,便于大家共同学习探讨. ps:最近改bug改的要死要活,博客写的吭哧吭哧的~ 首先列出学习参考的前人的文章,本文较多的参考了其中的表述: 1.骨骼动画详解 :http://blog.csdn.net/ccx1234/article/details/6641944,不过这篇文章的原文已经被csdn封了:D,可以看看对应的转载的文章也行 2.OpenGL10-骨骼动画原理篇:http://www.cnblogs.com/zhanglitong

Unity3d之Hash&amp;Slash学习笔记之(二)--角色基础类的构建

Hash&Slash学习笔记之(二)--角色基础类的构建 BaseStat类的构建 基本成员变量: _baseValue //基础属性值 _buffValue //增加的buff值 _expToLevel //升级所需经验值 _levelModifier //每一级的增长率,即下一等级所需经验为_expToLevel*_levelModifier 成员函数: private int CalculateExpToLevel(){ //计算下一级升级所需经验 return (int)(_expToL

SharpGL学习笔记(七) OpenGL的变换总结

笔者接触OpenGL最大的困难是: 经常调试一份代码时, 屏幕漆黑一片, 也不知道结果对不对,不知道如何是好! 这其实就是关于OpenGL"变换"的基础概念没有掌握好, 以至于对"将三维体正确的显示在屏幕上指定位置"这样的操作都无法完成. OpenGL变换包括计算机图形学中最基本的三维变换,即几何变换.投影变换.裁剪变换.视口变换,以及针对OpenGL的特殊变换概念理解和用法,如相机模拟.矩阵堆栈等,这些基础是开始真正走进三维世界无法绕过的基础. 所以笔者在前面花了

《C#高级编程》【第7章】运算符与类型强制转换 -- 学习笔记

       运算符编程语言的基本元素,它使得我们的代码更加的简洁明了.然而我们为了解决操作数类型不同的问题,我们又有引入了强制转换的概念.我们先看看本章的内容构成吧. 1.运算符 我们来看看一些常见的运算符: <1>条件运算符 其语法如下: Condition ? true_Expression : false_Expression 当条件Condition为真时,其将执行true_Expression,否则执行false_Expression. <2> checked 和