时光煮雨 Unity3D实现2D人物移动-总结篇
系列目录
【Unity3D基础】让物体动起来①--基于UGUI的鼠标点击移动
【Unity3D基础】让物体动起来②--UGUI鼠标点击逐帧移动
时光煮雨 Unity3D让物体动起来③—UGUI DoTween&Unity Native2D实现
时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画
时光煮雨 Unity3D实现2D人物动画② Unity2D 动画系统&资源效率
背景
最近研究Unity3d,2d寻路的实现。所以又一次涉及到了角色坐标位移的问题。系统的对于这个简单问题进行整理和总结。本来就是一个简单的几何问题,结果发现已经有两个小坑,顺便填上,这里做下总结。
实现
需求:通过鼠标点击,控制2d角色移动,就是点哪里,角色向移动到哪里
问题分解:按照时间进行动画分解,鼠标输入(动画开始)、平移(动画进行)、移动结束(动画结束)
前提:这里前面的文章基本解决了一些基础的知识,比如IO获取(鼠标输入),移动的基本方式(Unity中的位置系统transform)
坑:1、平移中的平滑移动,2、如何确定移动了目标点,并使物体停止下来
坑1:平移中的平滑移动
补充知识,关于角色的平移和位置更新,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()
{
//1、获得当前位置
Vector3 curenPosition = this.transform.position;
//2、获得方向
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
{
//3、插值移动
//目标位置方向加上速度移动
Vector3 target = moveDirection*speed*Time.deltaTime + curenPosition;
target.z = 0;
transform.position = target;
}
}
第二种,MoveTowards进行移动更新
/// <summary>
/// 使用Vector3的MoveTowards 直接进行位置更新
/// </summary>
private void MoveByVector3MoveTowards()
{
//1、获得当前位置
Vector3 curenPosition = this.transform.position;
//2、获得方向
if (Input.GetButton("Fire1"))
{
moveTowardPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
moveTowardPosition.z =0;
}
if (Vector3.Distance(curenPosition, moveTowardPosition) < 0.01f)
{
transform.position = moveTowardPosition;
}
else
{
//3、插值移动
//距离就等于 间隔时间乘以速度即可
float maxDistanceDelta = Time.deltaTime * speed;
transform.position = Vector3.MoveTowards(curenPosition, moveTowardPosition, maxDistanceDelta);
}
}
第三种,transform.Translate
/// <summary>
/// 使用Vector3的Translate 直接进行位置更新
/// </summary>
private void MoveByTransformTranslate()
{
//1、获得当前位置
Vector3 curenPosition = this.transform.position;
//2、获得方向
if (Input.GetButton("Fire1"))
{
moveTowardPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
moveTowardPosition.z = 0; moveDirection = moveTowardPosition - curenPosition;
moveDirection.z = 0;
moveDirection.Normalize();
}
//3、插值移动
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);
}
}
坑2:如何确定移动了目标点,并使物体停止下来
补充知识:其实坑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
* 每一帧,
* 1、累加 已经逝去的时间,并得到costTime,并获得移动的百分比 precent = costTime/totalTime
* 2、获得当前精灵的位置,根据precent 进行位置插值,得到这一帧应该移动的位置
* 3、使用设置移动
* 4、通过precent判断是否<1 来判断是否移动到了目标位置
* 5、如果完成,则调用最后一次移动实现,终点移动误差,并置为一些标志位
*/
//获得当前位置
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的核心原理(为什么,还需要进一步探索,也许扩展性更强)
总结
反正被坑很不爽,不过也怪不了别人,还是自己才疏学浅(不是天才,就使劲干)。下一篇 继续探索角色的系列目标点的移动
时光煮雨 Unity3D实现2D人物移动-总结篇的更多相关文章
- 时光煮雨 Unity3D实现2D人物动画② Unity2D 动画系统&资源效率
系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...
- 时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画
系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...
- 时光煮雨 Unity3d 序列目标点的移动①
系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...
- 时光煮雨 Unity3D让物体动起来③—UGUI DoTween&Unity Native2D实现
本文首发蛮牛,次发博客园.接系列 第一篇,第二篇,本文为第三篇,再次感谢“武装三藏”在前两篇无私且精彩的问题解答 写在最前,时光煮雨,为了怀念 以下引用曾今读过的一些教程文章 其实这3种动画都有它特定 ...
- Unity3D 之2D动画机
这里来讲解一下2D动画机的使用 2D的时候,默认的情况下,可以调用默认的站立之类的动画,然后通过触发,可以变化自己的动画. 一:将一个图切成一些一个元素 二:创建一个精灵,给精灵添加一个动画机 三:给 ...
- 强大的游戏开发工具Unity3D推出2D开发工具,unity将混合3D与2D开发
2013 Unity全球开发者大会(Unite 2013)于2013年8月28日在温哥华隆重开幕,会上Unity全球CEO David Helgason在Keynote上宣布Unity 4.3版本即将 ...
- Unity3D摄像机尾随人物
这里的镜头主要是从人物的背后尾随的. 首先新建一个C#脚本,命名为MyFollow,然后把下面代码粘贴进去.保存: using UnityEngine; using System.Collection ...
- 我发现了Unity3D的2D Light Renderer, 随后就把它抄了过来
. 前几个月,偶然在群里看到有人讨论Unity3D光照,于是我又萌生了一个新的目标----把它抄过来! . 众所周知,3D渲染的整个流水线都跟光照密不可分,相关的技术更是数不甚数,而2D游戏的光照通常 ...
- 【日常记录】【unity3d】 2D跳跃过快导致角色某帧陷入地面
如果角色运动过快会导致嵌入地面再反弹出来 : 可以使用更高质量的检测方式 "Continuous" :就可以解决这个问题
随机推荐
- angularjs封装bootstrap官网的时间插件datetimepicker
背景:angular与jquery类库的协作 第三方类库中,不得不提的是大名鼎鼎的jquery,现在基本上已经是国内web开发的必修工具了.它灵活的dom操作,让很多web开发人员欲罢不能.再加上已经 ...
- SQLServer表内自关联级联删除
今天处理SQLServer级联删除遇到了很蛋疼的事. SQLServer 不支持表内自关联级联删除,而MySql和Oracle却支持. 貌似原因是SQLServer 会产生循环级联,就不给这样弄.所以 ...
- 【原】iOS动态性(二):运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)
OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...
- Visual Studio 2013 的 Xamarin 安装教程
Xamarin 配置手册和离线包下载 http://pan.baidu.com/s/1eQ3qw8a 具体操作: 安装前提条件 1. 安装Visual Studio 2013,安装过程省略,我这里安 ...
- Android 内容提供者简介
在Android应用中,我们可以使用显式意图(Explicit Intent)来直接访问其他应用的Activity,但是这仅限于Activity的范畴:如果需要使用其他应用的数据,还需要用到另外一种组 ...
- objective-c系列-@Property&点语法
//解释 property后边的圆括号中的修饰词的含义: // nonatomic 非线程安全 非原子操作 特点是: 操作变量的效率高 // atomic ...
- 【代码笔记】iOS-点击任何处,显示出红色的UIView
一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> //头文件 #import "MoreView. ...
- Java 线程通信
线程通信用来保证线程协调运行,一般在做线程同步的时候才需要考虑线程通信的问题. 1.传统的线程通信 通常利用Objeclt类提供的三个方法: wait() 导致当前线程等待,并释放该同步监视器的锁定, ...
- UILabel和NSAttributedString那些事
注:通常的label用来现实普通的文字.但是,你常常会遇到这样的情况:一段文字中不仅有文字,也有图片,甚至文字中的某段文字与其他的文字的appearance不一致的情况,这样的一段文字就可以称得上是富 ...
- 2.2 CMMI2级——项目计划(Project Planning)
大家都明白这样的一个道理:做事情要有计划,有一个不成熟的计划总比没有计划要好,软件开发这么复杂的活动,更加需要计划.那么应该怎样做好一个计划呢? 如果对项目的范围.规模.性质.任务.工作量.费用等都不 ...