O(∩_∩)O哈哈~大家好,這是本菜雞的第一篇分享,主要介紹如何還原類似崩壞3主界面的陀螺儀旋屏效果。以下實(shí)現(xiàn)僅基于個(gè)人的分析理解邮府,難度挺低的,大佬們湊合看看就好啦溉奕,寫(xiě)的不好的地方歡迎指正哈~
需求分析
崩壞3的主界面陀螺儀效果具備以下兩個(gè)特點(diǎn):
1褂傀、當(dāng)用戶轉(zhuǎn)動(dòng)手機(jī)的時(shí)候,攝像機(jī)的視角會(huì)朝指定的方向旋轉(zhuǎn)加勤。
2仙辟、攝像機(jī)的視角旋轉(zhuǎn)速度和回復(fù)速度的大小與用戶轉(zhuǎn)動(dòng)手機(jī)的速度大小成正比。
設(shè)計(jì)實(shí)現(xiàn)
首先了解下Unity中提供的陀螺儀相關(guān)信息鳄梅。如下圖所示叠国,陀螺儀的坐標(biāo)軸是右手坐標(biāo)系與unity的左手坐標(biāo)系不同,在使用時(shí)需要注意戴尸!
手機(jī)平放粟焊,x軸為右手邊,y軸為正前方,z軸為正上方项棠。
陀螺儀主要參數(shù)詳解:
Input.gyro.enabled:bool類型悲雳,返回陀螺儀的啟用狀態(tài)。
Input.gyro.attitude:Vector3類型沾乘,返回手機(jī)當(dāng)前的姿態(tài)信息怜奖,即與手機(jī)初始姿態(tài)相比在x、y翅阵、z軸上的偏離情況(單位:弧度)歪玲,初始姿態(tài)指的是手機(jī)水平正向放置(此時(shí)Input.gyro.attitude為(0,0,0))。
Input.gyro.rotationRate:Vector3類型掷匠,返回陀螺儀測(cè)量的各軸上的旋轉(zhuǎn)速率的原始值滥崩,單位為“弧度/秒”。
Input.gyro.rotationRateUnbiased:Vector3類型讹语,返回陀螺儀測(cè)量的各軸上的旋轉(zhuǎn)速率的修正值钙皮,單位為“弧度/秒”。
Input.gyro.updateInterval:float類型顽决,返回陀螺儀參數(shù)的更新間隔,單位:秒短条。
Input.gyro.gravity:Vector3類型,返回陀螺儀測(cè)量的重力加速度向量才菠。
Input.gyro.userAcceleration:Vector3類型茸时,返回設(shè)備的加速度向量。
注意:在手機(jī)的Portrait(豎屏)和Landscape(橫屏)模式下赋访,陀螺儀的坐標(biāo)軸并不一致,如果需要的話需要相應(yīng)變換可都。
了解陀螺儀相關(guān)參數(shù)的作用后,我們開(kāi)始分析如何實(shí)現(xiàn)需求蚓耽。首先渠牲,只有當(dāng)用戶轉(zhuǎn)動(dòng)手機(jī)的時(shí)候,攝像機(jī)的視角才會(huì)相應(yīng)變化步悠,說(shuō)明攝像機(jī)的變化跟手機(jī)的當(dāng)前姿態(tài)無(wú)關(guān)签杈。這意味著我們的輸入應(yīng)該是陀螺儀測(cè)量的旋轉(zhuǎn)速率(rotationRate)而非手機(jī)的當(dāng)前姿態(tài)(attitude)信息。由于rotationRateUnbiased是在rotationRate的基礎(chǔ)上進(jìn)行修正鼎兽,測(cè)量結(jié)果更加準(zhǔn)確答姥,我們使用該屬性控制攝像機(jī)視角的旋轉(zhuǎn)。
我們所說(shuō)的攝像機(jī)視角的旋轉(zhuǎn),實(shí)際上是攝像機(jī)基于自身坐標(biāo)軸根據(jù)陀螺儀的rotationRateUnbiased參數(shù)進(jìn)行旋轉(zhuǎn)接奈。結(jié)合圖1各軸的方向分析可知踢涌,手機(jī)橫屏放置時(shí),陀螺儀的x序宦、y軸和攝像機(jī)自身的x睁壁、y軸的正方向是吻合的(z軸正方向相反)。我們僅需要在x、y軸方向上旋轉(zhuǎn)攝像機(jī)潘明,陀螺儀z軸上的旋轉(zhuǎn)變化不應(yīng)對(duì)攝像機(jī)產(chǎn)生影響行剂。
我們可通過(guò)在Update方法里執(zhí)行以下語(yǔ)句控制攝像機(jī)的旋轉(zhuǎn)。
transform.Rotate(vertRotateRate, horiRotateRate, 0);
其中钳降,vertRotateRate和horiRotateRate分別表示陀螺儀在垂直(繞x軸)方向和水平(繞y軸)方向上的旋轉(zhuǎn)速率:
vertRotateRate = Input.gyro.rotationRateUnbiased.x;
horiRotateRate = Input.gyro.rotationRateUnbiased.y;
為了防止旋轉(zhuǎn)速率過(guò)大厚宰,導(dǎo)致攝像機(jī)瞬間旋轉(zhuǎn)較大角度從而影響表現(xiàn)的問(wèn)題,我們需要通過(guò)以下語(yǔ)句限制旋轉(zhuǎn)速率的大兴焯睢:
vertRotateRate = Mathf.Sign(vertRotateRate) * Mathf.Clamp(Mathf.Abs(vertRotateRate), 0, maxRotateRate);
horiRotateRate = Mathf.Sign(horiRotateRate) * Mathf.Clamp(Mathf.Abs(horiRotateRate), 0, maxRotateRate);
其中铲觉,maxRotateRate是我們?cè)O(shè)定的最大旋轉(zhuǎn)速率,其值可設(shè)為Mathf.PI吓坚,即最大為π rad / s撵幽。
實(shí)現(xiàn)攝像機(jī)的旋轉(zhuǎn)后,還要考慮如何在旋轉(zhuǎn)結(jié)束后讓其回復(fù)至初始旋轉(zhuǎn)狀態(tài)礁击,并且使得回復(fù)速率與陀螺儀輸入的旋轉(zhuǎn)速率成正比盐杂,即"轉(zhuǎn)得越快,回得越快"哆窿。為了實(shí)現(xiàn)攝像機(jī)得回復(fù)功能链烈,我們需要用一個(gè)變量initRotation(Quaternion類型)記錄攝像機(jī)的初始旋轉(zhuǎn)狀態(tài)。因此挚躯,我們可以通過(guò)在Update里面對(duì)相機(jī)的當(dāng)前rotation到initRotation進(jìn)行插值以實(shí)現(xiàn)回復(fù)效果强衡,實(shí)現(xiàn)如下:
transform.localRotation = Quaternion.Slerp(transform.localRotation, initRotation, restoreRate + restoreRate * factor);
其中,restoreRate是一個(gè)可設(shè)定的默認(rèn)值秧均,建議取值在0.05左右食侮,factor是一個(gè)縮放因子号涯,跟vertRotateRate 及horiRotateRate 相關(guān)目胡,其值為vertRotateRate 和horiRotateRate 中絕對(duì)值較大者和maxRotateRate的比值(factor的值在[0,1]之間)链快。通過(guò)restoreRate和factor構(gòu)建的簡(jiǎn)單線性關(guān)系誉己,可以實(shí)現(xiàn)回復(fù)速率與旋轉(zhuǎn)速率成正比。factor的計(jì)算如下:
factor = Mathf.Max(Mathf.Abs(vertRotateRate), Mathf.Abs(horiRotateRate)) / maxRotateRate;
需要注意的是域蜗,我們使用transform.localRotation旋轉(zhuǎn)攝像機(jī)巨双,如果你的項(xiàng)目中攝像機(jī)沒(méi)有父對(duì)象,請(qǐng)改為transform.rotation霉祸。
好了筑累,講到這里,我們的實(shí)現(xiàn)邏輯已經(jīng)很清晰了丝蹭,其實(shí)超級(jí)簡(jiǎn)單對(duì)不對(duì)慢宗,但效果還是可以的。值得再提的一點(diǎn)是,我們可以設(shè)置兩個(gè)縮放因子vertRotateRateScale镜沽、horiRotateRateScale來(lái)分別控制攝像機(jī)垂直敏晤、水平方向上旋轉(zhuǎn)速率的大小(使得兩個(gè)方向上的旋轉(zhuǎn)速率差異化),以增強(qiáng)靈活性缅茉。
最后嘴脾,放出我們最終的實(shí)現(xiàn)代碼,畢竟目前水平有限蔬墩,如果您有好的建議或優(yōu)化方案译打,還請(qǐng)不吝賜教哈O(∩_∩)O~感謝!
public class CameraGyroController : MonoBehaviour
{
? ? public Vector3 initEuler = Vector3.zero;? ? ? ? ? ? ? ? ? ? //初始旋轉(zhuǎn)對(duì)應(yīng)歐拉角
? ? private Quaternion initRotation = Quaternion.identity;? ? ? //初始旋轉(zhuǎn)信息
? ? private Vector3 lastInitEuler = Vector3.zero;? ? ? ? ? ? ? //用于判斷初始?xì)W拉角是否變化
? ? public float vertRotateRateScale = 0f;? ? ? ? ? ? ? ? ? ? ? //垂直陀螺儀旋轉(zhuǎn)速率縮放系數(shù)
? ? public float horiRotateRateScale = 0f;? ? ? ? ? ? ? ? ? ? ? //水平陀螺儀旋轉(zhuǎn)速率縮放系數(shù)
? ? public float restoreRate = 0f;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //回復(fù)速率
? ? private float maxRotateRate = Mathf.PI;? ? ? ? ? ? ? ? ? ? //陀螺儀單軸最大旋轉(zhuǎn)速率
? ? private float factor = 0f;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //陀螺儀旋轉(zhuǎn)速率和回復(fù)速率間的關(guān)聯(lián)系數(shù)(旋轉(zhuǎn)速率越大回復(fù)越快)
? ? private float vertRotateRate = 0f;? ? ? ? ? ? ? ? ? ? ? ? ? //垂直方向旋轉(zhuǎn)速率
? ? private float horiRotateRate = 0f;? ? ? ? ? ? ? ? ? ? ? ? ? //水平方向旋轉(zhuǎn)速率
? ? void Init()
? ? {
? ? ? ? initRotation = transform.localRotation;
? ? ? ? initEuler = initRotation.eulerAngles;
? ? ? ? lastInitEuler = initEuler;
? ? ? ? Input.gyro.enabled = true;
? ? }
? ? /// <summary>
? ? /// 根據(jù)陀螺儀控制主界面mainCamera的旋轉(zhuǎn)
? ? /// </summary>
? ? void CameraRotateControl()
? ? {
? ? ? ? if (!Input.gyro.enabled)
? ? ? ? {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? //如果初始旋轉(zhuǎn)角度發(fā)生變化,則更新
? ? ? ? if (lastInitEuler != initEuler)
? ? ? ? {
? ? ? ? ? ? initRotation = Quaternion.Euler(initEuler);
? ? ? ? ? ? lastInitEuler = initEuler;
? ? ? ? }
? ? ? ? vertRotateRate = Input.gyro.rotationRateUnbiased.x;
? ? ? ? vertRotateRate = Mathf.Sign(vertRotateRate) * Mathf.Clamp(Mathf.Abs(vertRotateRate), 0, maxRotateRate);
? ? ? ? horiRotateRate = Input.gyro.rotationRateUnbiased.y;
? ? ? ? horiRotateRate = Mathf.Sign(horiRotateRate) * Mathf.Clamp(Mathf.Abs(horiRotateRate), 0, maxRotateRate);
? ? ? ? factor = Mathf.Max(Mathf.Abs(vertRotateRate), Mathf.Abs(horiRotateRate)) / maxRotateRate;
? ? ? ? //z軸不旋轉(zhuǎn)
? ? ? ? transform.Rotate(vertRotateRateScale * vertRotateRate, horiRotateRateScale * horiRotateRate, 0);
? ? ? ? //逐幀往初始位置回復(fù)
? ? ? ? transform.localRotation = Quaternion.Slerp(transform.localRotation, initRotation, restoreRate + restoreRate * factor);
? ? }
? ? // Use this for initialization
? ? void Start()
? ? {
? ? ? ? Init();
? ? }
? ? // Update is called once per fram
????void Update()
? ? {
? ? ? ? CameraRotateControl();
? ? }
? ? void OnGUI()
? ? {
? ? ? ? GUILayout.Label("rotation rate unbiased:" + Input.gyro.rotationRateUnbiased);
? ? ? ? vertRotateRateScale = GUI.HorizontalSlider(new Rect(75, 400, 200, 40), vertRotateRateScale, 0f, 1f);
? ? ? ? GUI.Label(new Rect(300, 395, 200, 40), "Vert Rotate Rate:" + vertRotateRateScale);
? ? ? ? horiRotateRateScale = GUI.HorizontalSlider(new Rect(75, 475, 200, 40), horiRotateRateScale, 0f, 1f);
? ? ? ? GUI.Label(new Rect(300, 470, 200, 40), "Hori Rotate Rate:" + horiRotateRateScale);
? ? ? ? restoreRate = GUI.HorizontalSlider(new Rect(75, 550, 200, 40), restoreRate, 0f, 0.2f);
? ? ? ? GUI.Label(new Rect(300, 545, 200, 40), "Restore Rate:" + restoreRate);
? ? }
}