創作內容

20 GP

【Unity C#】應用三角函數運算Input.GetAxis("Vertical")及("Horizontal")實作角色轉向

作者:羅吉│2018-01-16 22:58:55│巴幣:1,536│人氣:8167
大家好,小弟最近我研究小東西,在這邊分享一下我的研究成果。
對了!這邊預告一下,小弟我數學不好QAQ都還給高職數學老師了XD
如有錯誤的部分或是有更好的方法,各位大大們還多多指教喔(=w=)a


個人分享偏好內容只聚焦在主題,覺得這樣簡單直覺會比較好消化。如果有功能不夠的部分,可能要請大家自己額外添加(=w=)a


簡介

這個研究簡單來說是應用三角函數運算Input.GetAxis("Vertical")及("Horizontal")獲得角度,角度是用來決定角色轉向角度或是移動方向等等,總之就看個人需求瞜。

因為最近想做出通用於類比搖桿和按鍵的移動腳本,目前想到是應用Input.GetAxis("Vertical")及("Horizontal")來實現,所以這研究算是前置作業。

想要完整程式或目前最佳化程式可以到最下面找到。

那廢說不多說了,讓我們開始這篇的主題吧!

Input.GetAxis("Vertical")、("Horizontal")

首先,實作第一步我們先來了解Input.GetAxis是什麼吧?

Input.GetAxis是Unity內建偵測輸入的函式,會回傳Float或是Bool給我們,
想要設定Input.GetAxis可以到Edit->Project Setting->Input打開InputManager介面設定



而我們的主角之一Input.GetAxis("Vertical"),會回傳-1到1之間的浮點數
按W鍵回傳值會遞增,按S鍵則會遞減,不按鍵會漸漸回復到零
Input.GetAxis("Horizontal")是用來偵測A鍵和D鍵



然後我們來寫一段程式測試一下這是不是我們要的結果。
我們先宣告兩個Float變數來接Input.GetAxis("Vertical")及("Horizontal")的回傳值分別命名為input_V及input_H並放到FixedUpdate ()重複更新

/// <summary>
/// 垂直輸入量
/// </summary>
[SerializeField]
[Header("垂直輸入量")]
private float input_V;

/// <summary>
/// 水平輸入量
/// </summary>
[SerializeField]
[Header("水平輸入量")]
private float input_H;

void FixedUpdate ()//固定頻率重複執行
{
    //接Input.GetAxis("Vertical")("Horizontal")的回傳值
    input_V = Input.GetAxis ("Vertical");
    input_H = Input.GetAxis ("Horizontal");
}


上面影片我可以看到input_V及input_H的變化規則是長這樣。

為了方便解釋我們先只看input_H。



上圖的意思是當你按住D鍵,input_H遞增到1,但下一瞬間你按A鍵,這時input_H會回歸到零才開始遞減。

也就是說遇到反向輸入時,Input.GetAxis("Vertical")及("Horizontal")會歸零才開始計算。

像這樣數值突然的大量變動會造成計算角色轉向後會有瞬間轉向的問題。

不過別擔心,只要到InputManager介面把Horizontal和Vertical展開,找到Snap,再來把勾勾拿掉,就不會歸零才開始計算了。




應用三角函數轉換成角度

接著我們要input_Vinput_H去做三角函數運算啦!!

首先,我們先分析一下我們的需求。

我們先input_Vinput_H當作直角三角形的兩邊,接著我們要利用這已知的兩邊去求我們想要知道的夾角θ(角色轉向角度),如下圖。



再來Google大神告訴我們只知道鄰邊、對邊要求夾角θ的話要帶這個公式

θ = atan(對邊 / 鄰邊) / (π / 180)

鄰邊:夾角θ鄰近的邊。
對邊:夾角θ對面的邊。



然後有問題出現了!
input_V小於0的時候求出來了夾角θ會相差180°

由於小弟我功力不足,沒辦法為大家解釋,總之大家遇到input_V小於0時夾角θ+180°

知道了這些需求之後,我們接著把它轉換成程式吧!

/// <summary>
/// 垂直輸入量
/// </summary>
[SerializeField]
[Header("垂直輸入量")]
private float input_V;

/// <summary>
/// 水平輸入量
/// </summary>
[SerializeField]
[Header("水平輸入量")]
private float input_H;

/// <summary>
/// 結果角度
/// </summary>
[SerializeField]
[Header("結果角度")]
private float angle_Sum;

void FixedUpdate ()//固定頻率重複執行
{
    //Input.GetAxis("Vertical")("Horizontal")的回傳值
    input_V = Input.GetAxis ("Vertical");
    input_H = Input.GetAxis ("Horizontal");

    //三角函數計算
    angle_Sum = Mathf.Atan (input_H / input_V) / (Mathf.PI / 180);
    angle_Sum = input_V < 0 ? angle_Sum + 180 : angle_Sum;

    //角色轉向
    transform.eulerAngles = new Vector3(transform.eulerAngles.x, angle_Sum, transform.eulerAngles.z);
}



額外處理

再來又有問題出現了XD
從上面影片可以看出兩個問題

第一個問題
input_V及input_H都等於0時,input_V / input_H會變成,0 / 0會得到NaN(無窮值)。

解決方法:
用float.IsNaN()函式判斷angle_Sum是不是NaN,如果是讓它等於0。

if(float.IsNaN(angle_Sum))
angle_Sum = 0;

第二個問題
因為在放開按鍵時,input_V及input_H都會漸漸回歸到0,所以運算獲得的角度會漸漸回到0。

解決方法:
如果按住WSAD任何一按鍵,才執行以下程式。
因為沒按鍵時不執行程式,所以角度會停留放開前的狀態。

if(Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D))

所以我們可以將程式改寫成

void FixedUpdate ()//固定頻率重複執行
{
    //如果按住WSAD任何一按鍵,才執行以下程式
    if(Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D))
    {
        //Input.GetAxis("Vertical")("Horizontal")的回傳值
        input_V = Input.GetAxis ("Vertical");
        input_H = Input.GetAxis ("Horizontal");

        //三角函數計算
        angle_Sum = Mathf.Atan (input_H / input_V) / (Mathf.PI / 180);
        angle_Sum = input_V < 0 ? angle_Sum + 180 : angle_Sum;

        //如果角度是NaN,讓他變成0

        if(float.IsNaN(angle_Sum))
        angle_Sum = 0;

        //角色轉向
        transform.eulerAngles = new Vector3(transform.eulerAngles.x, angle_Sum, transform.eulerAngles.z);
    }
}


因為考慮到文章篇幅,就先寫到這邊告一個段落,還請多多包涵瞜(=w=)a


結論

這次研究嘗試使用Input.GetAxis("Vertical")及("Horizontal")來實作轉向,以往土炮用按鍵if else判斷比起來,角度有更多的細節變化。

雖然轉向經過補間處理後兩者效果可能看起來差不多XD。

不過應用在實作任何方位移動速度一致,就很符合我要的效果。
(因為移動程式之後想再寫文章解釋,所以這邊先不分享了)


不過考慮到未來想實作出通用於類比搖桿和按鍵的移動腳本,這前置的研究是不可或缺的,之後有順利實作出來在分享給大家。


完整程式碼

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Character_Angle : MonoBehaviour
  5. {
  6.     /// <summary>
  7.     /// 垂直輸入量
  8.     /// </summary>
  9.     [SerializeField]
  10.     [Header("垂直輸入量")]
  11.     private float input_V;
  12.     /// <summary>
  13.     /// 水平輸入量
  14.     /// </summary>
  15.     [SerializeField]
  16.     [Header("水平輸入量")]
  17.     private float input_H;
  18.     /// <summary>
  19.     /// 結果角度
  20.     /// </summary>
  21.     [SerializeField]
  22.     [Header("結果角度")]
  23.     private float angle_Sum;
  24.     void FixedUpdate ()//固定頻率重複執行
  25.     {
  26.         //Input.GetAxis("Vertical")("Horizontal")的回傳值
  27.         input_V = Input.GetAxis ("Vertical");
  28.         input_H = Input.GetAxis ("Horizontal");
  29.         //如果按住WSAD任何一按鍵,才執行以下程式
  30.         if(Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.S))
  31.         {
  32.             //三角函數計算
  33.             angle_Sum = Mathf.Atan (input_H / input_V) / (Mathf.PI / 180);
  34.             angle_Sum = input_V < 0 ? angle_Sum + 180 : angle_Sum;
  35.             //如果角度是NaN,讓他變成0
  36.             if(float.IsNaN(angle_Sum))
  37.             angle_Sum = 0;
  38.             //角色轉向
  39.             transform.eulerAngles = new Vector3(transform.eulerAngles.x, angle_Sum, transform.eulerAngles.z);
  40.         }
  41.     }
  42. }


目前最佳化程式碼

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Character_Angle : MonoBehaviour
  5. {
  6.     /// <summary>
  7.     /// 垂直輸入量
  8.     /// </summary>
  9.     [SerializeField]
  10.     [Header("垂直輸入量")]
  11.     private float input_V;

  12.     /// <summary>
  13.     /// 水平輸入量
  14.     /// </summary>
  15.     [SerializeField]
  16.     [Header("水平輸入量")]
  17.     private float input_H;

  18.     /// <summary>
  19.     /// 結果角度
  20.     /// </summary>
  21.     [SerializeField]
  22.     [Header("結果角度")]
  23.     private float angle_Sum;

  24.     void FixedUpdate ()//固定頻率重複執行
  25.     {
  26.         //Input.GetAxis("Vertical")("Horizontal")的回傳值
  27.         input_V = Input.GetAxis ("Vertical");
  28.         input_H = Input.GetAxis ("Horizontal");

  29.         //GetAxisRaw判斷是否按到移動鍵,是的話執行以下程式,放開可以保留角度狀態,也能避免NaN的狀況
  30.         if(Input.GetAxisRaw("Vertical") != 0 || Input.GetAxisRaw ("Horizontal") != 0)
  31.         {
  32.             //三角函數計算
  33.             angle_Sum = Mathf.Atan2 (input_H, input_V) / (Mathf.PI / 180);

  34.             //角色轉向
  35.             transform.eulerAngles = new Vector3(transform.eulerAngles.x, angle_Sum, transform.eulerAngles.z);
  36.         }
  37.     }
  38. }
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=3856855
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:Unity|三角函數|角色轉向

留言共 5 篇留言

Kiloyd
用數學函式運算還真的沒試過OAO))滿有趣的
角色轉向可以試試看使用Quaternion.Euler的函式~給樓主參考XD

01-20 17:14

羅吉
哦哦!!之後研究看看,感謝喔ww01-20 20:04
達卡狼
很實用!! 謝大哥:3
想想之前算不出來都是用Lookat跟Vector3.forward去轉呢...

01-21 13:36

羅吉
能幫上忙我很榮幸[e24]01-22 21:45
風夜
Input.GetAxis兩個數值拿到後,拼成Vector2向量
這個向量就是角色的forward了,以你的視角來說
應該這樣寫就是同樣的效果了
transform.forward = new Vector3(input_H ,0, input_V)

01-29 18:25

羅吉
恩恩,用你的方法實作後,也能做到聽相同的效果ww
transform.forward = new Vector3(input_H ,0, input_V)
會把角度和向量都計算完,真的太方便了!!

不過在純左右移動的時候,transform.forward.z不是等於0,但是移動好像沒有問題,請問大大知道這會有什麼問題和怎麼解決嗎?

影片:
https://www.youtube.com/watch?v=ChkjZULpMNI&feature=youtu.be

程式碼:
https://docs.google.com/document/d/1qYcSkdOFKODbz1IMsuXNaL65x0fSonqbsaybYFyFPvY/edit01-30 20:37
風夜
=>不過在純左右移動的時候,transform.forward.z不是等於0,但是移動好像沒有問題,請問大大知道這會有什麼問題和怎麼解決嗎?

A:這是浮點誤差,你左邊的寬度拉不夠多沒看到後面,它寫的是-1.192093e-07,也就是-1.192乘以10的-7次方 = -0.000001192093,這個數字可以視為0。

簡單說明一下浮點誤差,浮點數的規範來自IEEE754(IEEE二進位浮點數算術標準),它無法準確的表示所有的數值,所以有時運算結果會有誤差,誤差都在小數點以下6位以後不用擔心,基本沒影響不需要理會。詳細可以參考這篇http://www.aspphp.online/bianchen/dnet/cxiapu/cxprm/201701/191021.html

====================

另外直接寫transform.forward = new Vector3(input_H ,0, input_V)
你應該會看到console一直冒訊息"look rotation viewing vector is zero"

那是因為有時候input_H,input_V都是0,沒辦法要求tranform.forward = (0,0,0),Unity會直接忽略你的這個命令,如果不想看到這條訊息可以再加個判斷擋掉(0,0,0)的狀態

01-31 12:20

羅吉
講解的很詳細,感謝大大解答!01-31 13:58
賽普魯斯

我在網上看到這段能圓滑的轉彎,用起來真是相當好用,

MoveSnake(_transform.position + _transform.forward * Speed);

float angel = Input.GetAxis("Horizontal") * 4 ;
_transform.Rotate(0, angel, 0);


可遊戲需要暫停時,

public void OnPause()//点击“暂停”时执行此方法
{
Time.timeScale = 0;
ingameMenu.SetActive(true);
}

它就完全不甩我的直直駛直直駛出去,請問要怎麼讓暫停的功能生效?

06-24 15:26

我要留言提醒:您尚未登入,請先登入再留言

20喜歡★logic850329 可決定是否刪除您的留言,請勿發表違反站規文字。

後一篇:【DD】NPC女化、部分...

追蹤私訊切換新版閱覽

作品資料夾

yvonne40528歡迎來看小說ゝω・
🌕《虛月舞曲》|架空、奇幻、戰鬥、愛情看更多我要大聲說4小時前


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】