【Unity3D基础】让物体动起来①--基于UGUI的鼠标点击移动html
【Unity3D基础】让物体动起来②--UGUI鼠标点击逐帧移动函数
时光煮雨 Unity3D让物体动起来③—UGUI DoTween&Unity Native2D实现动画
时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画this
时光煮雨 Unity3D实现2D人物动画② Unity2D 动画系统&资源效率spa
最近研究Unity3d,2d寻路的实现。因此又一次涉及到了角色坐标位移的问题。系统的对于这个简单问题进行整理和总结。原本就是一个简单的几何问题,结果发现已经有两个小坑,顺便填上,这里作下总结。3d
需求:经过鼠标点击,控制2d角色移动,就是点哪里,角色向移动到哪里调试
问题分解:按照时间进行动画分解,鼠标输入(动画开始)、平移(动画进行)、移动结束(动画结束)日志
前提:这里前面的文章基本解决了一些基础的知识,好比IO获取(鼠标输入),移动的基本方式(Unity中的位置系统transform)orm
坑:一、平移中的平滑移动,二、如何肯定移动了目标点,并使物体中止下来htm
补充知识,关于角色的平移和位置更新,Unity无非就几种方式
A、transform.Translate(new Vector3(1, 1, 1) * moveSpeed * Time.deltaTime); // Translate方法移动不会考虑刚体等碰撞(会直接穿过物体)
// 确保咱们的速度不会超过maxDistanceDelta
B、Vector3.MoveTowards(transform.position, targetPos.position, speed * Time.deltaTime);
// 速度会超过移动速度,像弹簧同样
C、Vector3.Lerp(transform.position, targetPos.position, speed * Time.deltaTime);
D、直接设置transform.Positon,最简单的方式
这个坑,真是坑了不少不少人,目前网上一半以上的教程,从严格意义上都是错误的,这里真的想吐槽一下(太他妈不负责了),这个问题我在群里问过一次,结果还被怀疑是菜鸟,其实焦点仍是 我用红色标出的这个线性插值函数,其实简单的不得了,就是个直线方程。这里能够参考,如下这两篇文章
unity3d问题集 <2> 对Vector3.Lerp 插值的理解
unity3d Vector3.Lerp解析 http://www.cnblogs.com/shenggege/p/5658650.html
分析为何“速度会超过移动速度,像弹簧同样”和 线性插值的函数,后来我仔细想了想,其实仍是本身知识掌握的不够透彻,具体咱们了解之后分析下,经典教程中的函数
public float moveSpeed;
public float turnSpeed;
private Vector3 moveDirection;
// Use this for initialization
void Start () {
moveDirection = Vector3.right;
}
// Update is called once per frame
void Update () {
// 1
Vector3 currentPosition = transform.position;
// 2
if( Input.GetButton("Fire1") ) {
// 3
Vector3 moveToward = Camera.main.ScreenToWorldPoint( Input.mousePosition );
// 4
moveDirection = moveToward - currentPosition;
moveDirection.z = 0;
moveDirection.Normalize();
}
Vector3 target = moveDirection * moveSpeed + currentPosition;
transform.position = Vector3.Lerp( currentPosition, target, Time.deltaTime );
}
}
这里咱们看红色部分的文字,这里之因此不会出现弹簧移动的效果,主要是每次插值都是当前点和这帧将要移动点的位置的插值,其实这里根本没有必要 ,直接设置 transform.position = moveDirection * moveSpeed*Time.deltaTime + currentPosition;(其实自己就是一个 基于时间的线性移动)
还有 自己 Vector3.Lerp(transform.position, targetPos.position, speed * Time.deltaTime); 这么用就有很大的问题
A、speed * Time.deltaTime 当speed设置很大而帧率很低的时候这个系数可能全是1,这样根本就是不插值,
B、当用UGUI时坐标系统是屏幕坐标值很大,这样插值会很不许(这也是我曾经问过的问题,不过没有人回答我)
至此第一个坑填上了,下面我列出使用不一样方式来进行移动的相关代码
第一种,改进型插值移动
/// <summary> /// 使用Vector3的插值进行更新位置 /// </summary> private void MoveByVector3Lerp() { //一、得到当前位置 Vector3 curenPosition = this.transform.position; //二、得到方向 if (Input.GetButton("Fire1")) { Vector3 moveToward = Camera.main.ScreenToWorldPoint(Input.mousePosition); moveTowardPosition = moveToward; moveTowardPosition.z = 0; moveDirection = moveToward - curenPosition; moveDirection.z = 0; moveDirection.Normalize(); } var distance = Vector3.Distance(curenPosition, moveTowardPosition); // Debug.Log(string.Format("curenPosition:{0}, moveTowardPosition{1},distance:{2},speed:{3}", curenPosition, moveTowardPosition, distance, speed * Time.deltaTime)); if (distance < 0.01f) { transform.position = moveTowardPosition; } else { //三、插值移动 //目标位置方向加上速度移动 Vector3 target = moveDirection*speed*Time.deltaTime + curenPosition; target.z = 0; transform.position = target; } }
第二种,MoveTowards进行移动更新
/// <summary> /// 使用Vector3的MoveTowards 直接进行位置更新 /// </summary> private void MoveByVector3MoveTowards() { //一、得到当前位置 Vector3 curenPosition = this.transform.position; //二、得到方向 if (Input.GetButton("Fire1")) { moveTowardPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); moveTowardPosition.z =0; } if (Vector3.Distance(curenPosition, moveTowardPosition) < 0.01f) { transform.position = moveTowardPosition; } else { //三、插值移动 //距离就等于 间隔时间乘以速度便可 float maxDistanceDelta = Time.deltaTime * speed; transform.position = Vector3.MoveTowards(curenPosition, moveTowardPosition, maxDistanceDelta); } }
第三种,transform.Translate
/// <summary> /// 使用Vector3的Translate 直接进行位置更新 /// </summary> private void MoveByTransformTranslate() { //一、得到当前位置 Vector3 curenPosition = this.transform.position; //二、得到方向 if (Input.GetButton("Fire1")) { moveTowardPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); moveTowardPosition.z = 0; moveDirection = moveTowardPosition - curenPosition; moveDirection.z = 0; moveDirection.Normalize(); } //三、插值移动 Vector3 target = moveDirection * speed * Time.deltaTime + curenPosition; target.z = 0; if (Vector3.Distance(curenPosition, moveTowardPosition) < 0.01f) { transform.position = moveTowardPosition; } else { transform.Translate(target - curenPosition); } }
补充知识:其实坑1中列出的三种平移方法,其实并非什么套路,不是什么标准的动画移动方式,虽然他们也是基于时间的,只能概括成一种简单的顺序帧移动,这里我查了不少资料还有一种基于时间线的移动方式。
问题描述:这里先说下坑2是怎么回事,就是咱们但愿角色移动到鼠标点击的点之后停下来,结果发现停不下来,经过调试日志主要的问题在这一行(这也是我之前提出过的一个问题,但无人解答)
if (Vector3.Distance(curenPosition, moveTowardPosition) < 0.01f)
实际上这行代码很是不靠谱,至少有两点
A、单位差别,UGUI中是屏幕坐标也是localPositon像素,Native中是Unit两个单位不一样判断的这个距离常量不同
B、因为speed * Time.deltaTime 每帧移动的距离是与速度和帧率有关的,这个常量(0.01)必须与之匹配须要设置合理的值
C、使用插值计算3维坐标偏差会扩大,这里我用“第一种,改进型插值移动”,“第三种,transform.Translate”都出现了偏差较大的状况,而“第二种,MoveTowards进行移动更新”,就很准确。
因此系统给出的函数
Vector3.MoveTowards(curenPosition, moveTowardPosition, maxDistanceDelta);
不是白给的,这也是不少人推荐使用这个函数的缘由(但不告诉咱们为何)
最后给出我本身写的基于时间线的位移实现
/// <summary> /// 鼠标点击移动,目标点 /// </summary> private Vector3 moveTowardPosition = Vector3.zero; private Vector3 moveStartPosition = Vector3.zero; private float totalTime = 0.0f; private float costTime = 0.0f; private float timePrecent = 0.0f; private bool _isRuning = false; /// <summary> /// 是否正在移动 /// </summary> public bool IsRuning { get { return _isRuning; } set { _isRuning = value; } } private void MoveByTimeline() { /* * 得到移动的最终目标位置,根据移动速度得到一共须要移动的时间 totalTime * 每一帧, * 一、累加 已经逝去的时间,并获得costTime,并得到移动的百分比 precent = costTime/totalTime * 二、得到当前精灵的位置,根据precent 进行位置插值,获得这一帧应该移动的位置 * 三、使用设置移动 * 四、经过precent判断是否<1 来判断是否移动到了目标位置 * 五、若是完成,则调用最后一次移动实现,终点移动偏差,并置为一些标志位 */ //得到当前位置 Vector3 curenPosition = this.transform.position; if (Input.GetButton("Fire1")) { moveStartPosition = curenPosition; //得到移动终点位置 moveTowardPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); moveTowardPosition.z = 0; costTime = 0.0f; //计算记录 var subVector3 = moveTowardPosition - curenPosition; //计算须要移动的总时间 totalTime = subVector3.magnitude / speed; _isRuning = true; } //若是已经移动 if (_isRuning) { //若是时间百分比小于1 说明尚未移动到终点 if (timePrecent < 1) { //累加时间 costTime += Time.deltaTime; timePrecent = costTime/totalTime; Vector3 target = Vector3.Lerp(moveStartPosition, moveTowardPosition, timePrecent); transform.position = target; } else //大于或者等于1 了说明是最后一次移动 { transform.position = moveTowardPosition; _isRuning = false; moveTowardPosition = Vector3.zero; timePrecent = 0.0f; costTime = 0.0f; } } }
这种方法基本排除了,移动到终点的位移偏差问题,缺点是使用的临时变量较多(我不喜欢),而“第二种,MoveTowards进行移动更新”能够基本不使用临时变量。时间线动画实际上这也是一些小的平移组件及itween的核心原理(为何,还须要进一步探索,也许扩展性更强)
反正被坑很不爽,不过也怪不了别人,仍是本身才疏学浅(不是天才,就使劲干)。下一篇 继续探索角色的系列目标点的移动