【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的核心原理(爲何,還須要進一步探索,也許擴展性更強)
反正被坑很不爽,不過也怪不了別人,仍是本身才疏學淺(不是天才,就使勁幹)。下一篇 繼續探索角色的系列目標點的移動