您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Unity中UI或3D場景怎么實現跟隨手機陀螺儀的晃動效果的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
需求
當游戲顯示3d場景及其UI的時候。玩家左右晃動手機的時候,UI界面會隨之左右偏移。上下晃動的時候,3D場景會隨之上下偏移。手機停止晃動的時候,如若偏移的UI或場景,停頓一會后自動恢復到初始默認位置。
分析
首先本文功能應對的是橫屏游戲(豎屏游戲的話也差不多一樣,大家自己拓展下),假設當我們拿起手機玩游戲,手機會有四個部位,分別為左手拿的左手邊和右手拿的右邊,以及屏幕內容的上方和下方(下文中會用左手邊,右手邊,上方,下方來描述)。每個部位的傾斜都會造成UI或場景的偏移效果
我們可以先用一個枚舉來定義這四個部位的傾斜情況
public enum EGyroType { NoRotate,//不旋轉 ToUp,//手機下方向上傾斜 ToDown,//手機下方向下傾斜 ToLeft,//左手邊向下傾斜 ToRight,//右手邊向下傾斜 }
接著我們可以使用Unity的陀螺儀接口Input.gyro的一些屬性,來判斷當前手機的傾斜狀態,Gyroscope有如下屬性:
我用到enabled和gravity兩個屬性,enabled用于打開或者關閉陀螺儀功能,而gravity返回的是一個Vector3變量,具體情況對應的返回值,通過打印Log在android手機上顯示如下(橫屏游戲,紀錄了某種情況下的某個不特定的角度的gravity值):
當手機橫著屏幕朝上水平放置在桌上的時候,返回值為:(0.0, 0.0, -1.0)
上下傾斜:
當手機下方向上傾斜時,某個角度(轉角小于90度)的返回值為:(0.0, 0.4, -0.9),角度再大的話屏幕的內容會翻轉過來。
當手機下方向下傾斜時,某個角度(轉角小于90度)的返回值為:(0.0, -0.5, -0.9),轉角為90度時:(0.0, -1.0, 0.0),轉角在90度到180度中時:(0.0, -0.8, 0.6),180度時即屏幕正朝下為:(0.0, 0.0, 1.0),若角度再大一點為:(0.0, 0.3, 0.9),直至屏幕內容翻轉過來。
我們可以發現
1.當 z < 0 , y > 0:當y的值變大則為ToUp,變小則為ToDown
2.當 z < 0 , y < 0:當y的值變大則為ToUp,變小則為ToDown
3.當 z > 0 , y < 0:當y的值變大則為ToDown,變小則為ToUp
4.當 z > 0 , y > 0:當y的值變大則為ToDown,變小則為ToUp
5.當 z < 0 變為 z > 0,則為ToDown,反之則為ToUp
前四條總結下來就是,當 z < 0,y的值變大則為ToUp,變小則為ToDown。當 z > 0,y的值變大則為ToDown,變小則為ToUp
左右傾斜:
當手機左手邊向下傾斜時,某個角度(轉角小于90度)的返回值為:(-0.2, 0.0, -1.0),轉角為90度時:(-1.0, 0.0, 0.0),轉角在90度到180度中時:(-0.6, 0.0, 0.8)
當手機右手邊向下傾斜時,某個角度(轉角小于90度)的返回值為:(0.6, 0.0, -0.8),轉角為90度時:(1.0, 0.0, 0.0),轉角在90度到180度中時:(0.8, 0.0, 0.5)
可以總結出
1.當 z < 0 , x < 0:當x的值變小則為ToLeft,變大則為ToRight
2.當 z > 0 , x < 0:當x的值變大則為ToLeft,變小則為ToRight
3.當 z < 0 , x > 0:當x的值變大則為ToRight,變小則為ToLeft
4.當 z > 0 , x > 0:當x的值變小則為ToRight,變大則為ToLeft
即,當 z < 0,x的值變小則為ToLeft,變大則為ToRight。當 z > 0,x的值變大則為ToLeft,變小則為ToRight
5.當 z < 0 變為 z > 0,若 x < 0 則為ToLeft,否則則為ToRight
6.當 z > 0 變為 z < 0,若 x < 0 則為ToRight,否則則為ToLeft
然后我們可以根據這些性質推斷出手機的當前狀態,然后去執行我們想要執行的操作。
根據需求,無論是移動物體,還是轉動攝像機來達到偏移的效果,都會有一個最大偏移值,偏移速度,不轉動的時候等待的一個間隔時間,這幾個參數需要設置。
具體實現
首先我們寫一個腳本GyroManager,掛載在場景的一個GameObject上(也可以處理成為單例,在別處調用里面的Start,Update方法),用來每幀檢測當前的手機狀態,并調用對應狀態的注冊事件。
using System; using UnityEngine; public enum EGyroType { NoRotate,//不旋轉 ToUp,//手機下方向上傾斜 ToDown,//手機下方向下傾斜 ToLeft,//左手邊向下傾斜 ToRight,//右手邊向下傾斜 } public class GyroManager : MonoBehaviour { Gyroscope mGyro;//陀螺儀 Vector2 mCurrentLandscapeGyroValue, mCurrentPortraitGyroValue;//當前的水平垂直的gravity值 Vector2 mLastLandscapeGyroValue, mLastPortraitGyroValue;//上一次的水平垂直的gravity值 public EGyroType LandscapeEGyroType, PortraitEGyroType;//手機的水平垂直狀態 float mPrecision = 0.015f;//精度,若前后兩次gravity值在精度內,則認為當前沒有旋轉 public int LandscapeGyroDifference, PortraitGyroDifference;//模擬的一個旋轉速度,gravity值差異越大,則該值越大 bool mIsEnable;//是否開啟陀螺儀 private void Start() { mGyro = Input.gyro; SetGyroEnable(true); } //每種狀態下需要執行的事件 public Action LandscapeTransToDefault; public Action<int> LandscapeTransToAdd; public Action<int> LandscapeTransToReduce; public Action PortraitTransToDefault; public Action<int> PortraitTransToAdd; public Action<int> PortraitTransToReduce; public void ResetLandscape() { LandscapeEGyroType = EGyroType.NoRotate; SetLandScapeValue(); mLastLandscapeGyroValue = mCurrentLandscapeGyroValue; LandscapeGyroDifference = 0; } public void ResetPortrait() { PortraitEGyroType = EGyroType.NoRotate; SetPortraitValue(); mLastPortraitGyroValue = Vector2.zero; PortraitGyroDifference = 0; } void Update() { if (mIsEnable) { GetEGyroType(); //根據解析出來的手機狀態,執行對應事件 if (LandscapeEGyroType == EGyroType.ToLeft) { LandscapeTransToReduce?.Invoke(LandscapeGyroDifference); } else if (LandscapeEGyroType == EGyroType.ToRight) { LandscapeTransToAdd?.Invoke(LandscapeGyroDifference); } else { LandscapeTransToDefault?.Invoke(); } if (PortraitEGyroType == EGyroType.ToDown) { PortraitTransToReduce?.Invoke(PortraitGyroDifference); } else if (PortraitEGyroType == EGyroType.ToUp) { PortraitTransToAdd?.Invoke(PortraitGyroDifference); } else { PortraitTransToDefault?.Invoke(); } } } //開啟或關閉陀螺儀 public void SetGyroEnable(bool isEnable) { if (mIsEnable != isEnable) { mIsEnable = isEnable; ResetLandscape(); ResetPortrait(); mGyro.enabled = isEnable; } } //解析當前手機狀態 public void GetEGyroType() { SetLandScapeValue(); //Landscape if (IsEquals(mCurrentLandscapeGyroValue.x, mLastLandscapeGyroValue.x, true)) { LandscapeEGyroType = EGyroType.NoRotate; LandscapeGyroDifference = 0; } else { LandscapeGyroDifference = (int)(Mathf.Abs(mCurrentLandscapeGyroValue.x - mLastLandscapeGyroValue.x) * 60); if (mCurrentLandscapeGyroValue.y < 0 && mLastLandscapeGyroValue.y < 0) { //當 z < 0,x的值變小則為ToLeft,變大則為ToRight if (mCurrentLandscapeGyroValue.x < mLastLandscapeGyroValue.x) { LandscapeEGyroType = EGyroType.ToLeft; } else { LandscapeEGyroType = EGyroType.ToRight; } } else if (mCurrentLandscapeGyroValue.y > 0 && mLastLandscapeGyroValue.y > 0) { //當 z > 0,x的值變大則為ToLeft,變小則為ToRight if (mCurrentLandscapeGyroValue.x < mLastLandscapeGyroValue.x) { LandscapeEGyroType = EGyroType.ToRight; } else { LandscapeEGyroType = EGyroType.ToLeft; } } else { if (mCurrentLandscapeGyroValue.y < mLastLandscapeGyroValue.y) { //當 z < 0 變為 z > 0,若 x < 0 則為ToLeft,否則則為ToRight if (mCurrentLandscapeGyroValue.x > 0) { LandscapeEGyroType = EGyroType.ToLeft; } else { LandscapeEGyroType = EGyroType.ToRight; } } else { //當 z > 0 變為 z<0,若 x< 0 則為ToRight,否則則為ToLeft if (mCurrentLandscapeGyroValue.x < 0) { LandscapeEGyroType = EGyroType.ToLeft; } else { LandscapeEGyroType = EGyroType.ToRight; } } } } mLastLandscapeGyroValue = mCurrentLandscapeGyroValue; SetPortraitValue(); //Portrait if (IsEquals(mCurrentPortraitGyroValue.x, mLastPortraitGyroValue.x, false)) { PortraitEGyroType = EGyroType.NoRotate; PortraitGyroDifference = 0; } else { PortraitGyroDifference = (int)(Mathf.Abs(mCurrentPortraitGyroValue.x - mLastPortraitGyroValue.x) * 60); if (mCurrentPortraitGyroValue.y < 0 && mLastPortraitGyroValue.y < 0) { //當 z< 0,y的值變大則為ToUp,變小則為ToDown if (mCurrentPortraitGyroValue.x < mLastPortraitGyroValue.x) { PortraitEGyroType = EGyroType.ToDown; } else { PortraitEGyroType = EGyroType.ToUp; } } else if (mCurrentPortraitGyroValue.y > 0 && mLastPortraitGyroValue.y > 0) { //當 z > 0,y的值變大則為ToDown,變小則為ToUp if (mCurrentPortraitGyroValue.x < mLastPortraitGyroValue.x) { PortraitEGyroType = EGyroType.ToUp; } else { PortraitEGyroType = EGyroType.ToDown; } } else { //當 z<0 變為 z > 0,則為ToDown,反之則為ToUp if (mCurrentPortraitGyroValue.y < mLastPortraitGyroValue.y) { //>0 變 <0 PortraitEGyroType = EGyroType.ToUp; } else { PortraitEGyroType = EGyroType.ToDown; } } } mLastPortraitGyroValue = mCurrentPortraitGyroValue; } //讀取gravity值 public void SetLandScapeValue() { mCurrentLandscapeGyroValue.x = mGyro.gravity.x; mCurrentLandscapeGyroValue.y = mGyro.gravity.z; } public void SetPortraitValue() { mCurrentPortraitGyroValue.x = mGyro.gravity.y; mCurrentPortraitGyroValue.y = mGyro.gravity.z; } //前后兩次是否相等 bool IsEquals(float a, float b, bool isLandscape) { if ((isLandscape && LandscapeEGyroType == EGyroType.NoRotate) || (!isLandscape && PortraitEGyroType == EGyroType.NoRotate)) { if (Mathf.Abs(a - b) < 0.025f) { return true; } } if (Mathf.Abs(a - b) < mPrecision) { return true; } return false; } }
接著我們寫個腳本GyroBase用于掛載在需要根據手機狀態偏移的組件上,用于設置偏移的參數,以及對應狀態下計算偏移的量
using System; using UnityEngine; public class GyroBase { public float MaxValue;//最大偏移值 public float DefaultValue;//初始位置 float mCurrentValue;//當前偏移量 public float Speed;//速度 public float DuringTime;//等待間隔 float mCurrentDuringTime;//當前時間間隔 public Action<float> ValueChanged;//偏移事件 public GyroManager mManager; float mBackSpeed;//回彈速度(一個減速過程) float BackSpeed { get { if (mBackSpeed > mMinSpeed) { mBackSpeed = Mathf.Max(mBackSpeed - Speed * mDeltaTime, mMinSpeed); } return mBackSpeed; } } float mMinSpeed;//最小速度 float mDeltaTime;//Time.deltaTime bool mIsLandScape;//檢測手機水平轉動還是垂直轉動 bool mIsResetBackProperty = false; //初始化賦值 public void Init(float maxValue, float defaultValue, float speed, float duringTime, bool isLandscape, Action<float> action) { MaxValue = maxValue; DefaultValue = defaultValue; Speed = speed; DuringTime = duringTime; mMinSpeed = Speed * 0.2f; mCurrentValue = DefaultValue; mIsLandScape = isLandscape; if (mIsLandScape) { mManager.LandscapeTransToDefault += TransToDefault; mManager.LandscapeTransToAdd += TransToAdd; mManager.LandscapeTransToReduce += TransToReduce; } else { mManager.PortraitTransToDefault += TransToDefault; mManager.PortraitTransToAdd += TransToAdd; mManager.PortraitTransToReduce += TransToReduce; } ValueChanged = action; } //事件清除 public void Clear() { if (mIsLandScape) { mManager.LandscapeTransToDefault -= TransToDefault; mManager.LandscapeTransToAdd -= TransToAdd; mManager.LandscapeTransToReduce -= TransToReduce; } else { mManager.PortraitTransToDefault -= TransToDefault; mManager.PortraitTransToAdd -= TransToAdd; mManager.PortraitTransToReduce -= TransToReduce; } } //重設回彈參數 void ResetBackProperty() { if (!mIsResetBackProperty) { mIsResetBackProperty = true; mBackSpeed = Speed * 0.8f; mCurrentDuringTime = 0; } } //手機沒轉動的時候,超過間隔時間則減速回彈至默認位置 void TransToDefault() { mIsResetBackProperty = false; mDeltaTime = Time.deltaTime; mCurrentDuringTime += mDeltaTime; if (mCurrentDuringTime > 1) { ValueToDefault(); ValueChanged?.Invoke(mCurrentValue); } } //偏移增加 void TransToAdd(int difference) { ResetBackProperty(); ValueAddSpeed(difference); ValueChanged?.Invoke(mCurrentValue); } //偏移減小 void TransToReduce(int difference) { ResetBackProperty(); ValueReduceSpeed(difference); ValueChanged?.Invoke(mCurrentValue); } void ValueToDefault() { if (mCurrentValue > DefaultValue) { mCurrentValue = Mathf.Max(mCurrentValue - BackSpeed * mDeltaTime, DefaultValue); } else if (mCurrentValue < DefaultValue) { mCurrentValue = Mathf.Min(mCurrentValue + BackSpeed * mDeltaTime, DefaultValue); } } void ValueAddSpeed(int difference) { if (mCurrentValue < DefaultValue + MaxValue) { mCurrentValue = Mathf.Min(mCurrentValue + Speed * mDeltaTime * difference, DefaultValue + MaxValue); } } void ValueReduceSpeed(int difference) { if (mCurrentValue > DefaultValue - MaxValue) { mCurrentValue = Mathf.Max(mCurrentValue - Speed * mDeltaTime * difference, DefaultValue - MaxValue); } } }
使用
例如,我們3D場景會隨手機的垂直轉動而上下偏移,我們可以通過旋轉攝像機的x軸來實現,我們只需寫個簡單的腳本掛載在攝像機上即可
public class CameraGyro : MonoBehaviour { public GyroManager mManager; Transform mTransform; Vector3 mCameraAngle; GyroBase mGyroBase; void Start() { mTransform = transform; mCameraAngle = Vector3.zero; mGyroBase = new GyroBase(); mGyroBase.mManager = mManager; mGyroBase.Init(5, 0, 5, 1, false, Change); } void Change(float value) { mCameraAngle.x = value; mTransform.localEulerAngles = mCameraAngle; } }
因為自己工程的UI場景并不是所有UI都會隨手機水平翻轉而轉動,所以就不能直接通過攝像頭來解決,而需要移動需要偏移的UI部分,所以我們可以寫個組件只掛載在需要偏移的UI部分上
public class UIGyro : MonoBehaviour { public GyroManager mManager; void Start() { GyroBase mGyroBase = new GyroBase(); mGyroBase.mManager = mManager; mGyroBase.Init(80, transform.localPosition.x, 80, 1, true, Change); } void Change(float value) { transform.localPosition = new Vector3(value, transform.localPosition.y); } }
這樣就大致實現了需要的效果了。
感謝各位的閱讀!關于“Unity中UI或3D場景怎么實現跟隨手機陀螺儀的晃動效果”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。