系列目录

【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人物移动-总结篇的更多相关文章

  1. 时光煮雨 Unity3D实现2D人物动画② Unity2D 动画系统&资源效率

    系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...

  2. 时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画

    系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...

  3. 时光煮雨 Unity3d 序列目标点的移动①

    系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...

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

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

  5. Unity3D 之2D动画机

    这里来讲解一下2D动画机的使用 2D的时候,默认的情况下,可以调用默认的站立之类的动画,然后通过触发,可以变化自己的动画. 一:将一个图切成一些一个元素 二:创建一个精灵,给精灵添加一个动画机 三:给 ...

  6. 强大的游戏开发工具Unity3D推出2D开发工具,unity将混合3D与2D开发

    2013 Unity全球开发者大会(Unite 2013)于2013年8月28日在温哥华隆重开幕,会上Unity全球CEO David Helgason在Keynote上宣布Unity 4.3版本即将 ...

  7. Unity3D摄像机尾随人物

    这里的镜头主要是从人物的背后尾随的. 首先新建一个C#脚本,命名为MyFollow,然后把下面代码粘贴进去.保存: using UnityEngine; using System.Collection ...

  8. 我发现了Unity3D的2D Light Renderer, 随后就把它抄了过来

    . 前几个月,偶然在群里看到有人讨论Unity3D光照,于是我又萌生了一个新的目标----把它抄过来! . 众所周知,3D渲染的整个流水线都跟光照密不可分,相关的技术更是数不甚数,而2D游戏的光照通常 ...

  9. 【日常记录】【unity3d】 2D跳跃过快导致角色某帧陷入地面

    如果角色运动过快会导致嵌入地面再反弹出来 : 可以使用更高质量的检测方式 "Continuous" :就可以解决这个问题

随机推荐

  1. JavaScript一些基础技巧和注意事项,你了解这些吗?

    总结了一些JavaScript在开发编码中的使用技巧,如有不对,欢迎指正. 一.JavaScript在HTML和XHTML的使用 使用<script>元素有两种方式:直接在页面中嵌入Jav ...

  2. Android studio 如何查看当前git 分支的4种方式

    1.第一种       2.第二种       3.第三种 4.第四种       前面3种都是通过android studio 操作的. 第四种是通过命令行操作.(可以在 git bash 中输入命 ...

  3. Android Studio Gradle Build Running 特别慢的问题探讨

    本文的本本win7 64bit 6G android studio2.1 在运行程序的时候Gradle Build Running 特别慢,一个helloworld都快2min了 1.开启gradle ...

  4. Loader加载器

    今天学到了这个Loader,浅谈一下自己的看法: 1.定义 Loader是一个加载器,可以用来它访问数据,可以看做访问数据的机器(好比挖掘机).装再器从android3.0开始引进,它使得在activ ...

  5. Android Activity生命周期详讲

    管理 Activity 生命周期 通过实现回调方法管理 Activity 的生命周期对开发强大而又灵活的应用至关重要. Activity 的生命周期会直接受到 Activity 与其他 Activit ...

  6. 【代码笔记】iOS-电影上的花絮,自动滚动

    一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController ...

  7. android 界面设计基本知识Ⅲ

    本章继续讲述在android界面设计中相关的知识点.介绍内容包括BroadcastReceiver(广播),Service(服务),Widget(小部件),WebView(网页加载控件). 1.Bro ...

  8. Zend Studio 12 安装及破解

    安装: 1.下载最新版本Zend Studio:http://downloads.zend.com/studio-eclipse/12.0.0/ZendStudio-12.0.0-win32.win3 ...

  9. uniqid函数产生唯一id,减少碰撞几率

    $uuid = str_replace(".","",uniqid(mt_rand(100000,999999),true)); //基于当前时间微妙数,与mt ...

  10. WebServer中异步操作的一些总结

    1.异步操作本身不会改善IO的性能 2.当任务多为IO操作时普通的工作线程将会减少,使CPU对工作线程的维护降低,从而提高CPU对其它任务的利用率 3.如果专用的IO线程,需要执行的专用任务较多时,专 ...