Unity UI或3D場景實現(xiàn)跟隨手機陀螺儀的晃動效果
需求
當游戲顯示3d場景及其UI的時候。玩家左右晃動手機的時候,UI界面會隨之左右偏移。上下晃動的時候,3D場景會隨之上下偏移。手機停止晃動的時候,如若偏移的UI或場景,停頓一會后自動恢復到初始默認位置。
分析
首先本文功能應對的是橫屏游戲(豎屏游戲的話也差不多一樣,大家自己拓展下),假設當我們拿起手機玩游戲,手機會有四個部位,分別為左手拿的左手邊和右手拿的右邊,以及屏幕內(nèi)容的上方和下方(下文中會用左手邊,右手邊,上方,下方來描述)。每個部位的傾斜都會造成UI或場景的偏移效果
我們可以先用一個枚舉來定義這四個部位的傾斜情況
public enum EGyroType
{
NoRotate,//不旋轉(zhuǎn)
ToUp,//手機下方向上傾斜
ToDown,//手機下方向下傾斜
ToLeft,//左手邊向下傾斜
ToRight,//右手邊向下傾斜
}
接著我們可以使用Unity的陀螺儀接口Input.gyro的一些屬性,來判斷當前手機的傾斜狀態(tài),Gyroscope有如下屬性:
我用到enabled和gravity兩個屬性,enabled用于打開或者關閉陀螺儀功能,而gravity返回的是一個Vector3變量,具體情況對應的返回值,通過打印Log在android手機上顯示如下(橫屏游戲,紀錄了某種情況下的某個不特定的角度的gravity值):
當手機橫著屏幕朝上水平放置在桌上的時候,返回值為:(0.0, 0.0, -1.0)
上下傾斜:
當手機下方向上傾斜時,某個角度(轉(zhuǎn)角小于90度)的返回值為:(0.0, 0.4, -0.9),角度再大的話屏幕的內(nèi)容會翻轉(zhuǎn)過來。
當手機下方向下傾斜時,某個角度(轉(zhuǎn)角小于90度)的返回值為:(0.0, -0.5, -0.9),轉(zhuǎn)角為90度時:(0.0, -1.0, 0.0),轉(zhuǎn)角在90度到180度中時:(0.0, -0.8, 0.6),180度時即屏幕正朝下為:(0.0, 0.0, 1.0),若角度再大一點為:(0.0, 0.3, 0.9),直至屏幕內(nèi)容翻轉(zhuǎn)過來。
我們可以發(fā)現(xiàn)
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 變?yōu)?z > 0,則為ToDown,反之則為ToUp
前四條總結下來就是,當 z < 0,y的值變大則為ToUp,變小則為ToDown。當 z > 0,y的值變大則為ToDown,變小則為ToUp
左右傾斜:
當手機左手邊向下傾斜時,某個角度(轉(zhuǎn)角小于90度)的返回值為:(-0.2, 0.0, -1.0),轉(zhuǎn)角為90度時:(-1.0, 0.0, 0.0),轉(zhuǎn)角在90度到180度中時:(-0.6, 0.0, 0.8)
當手機右手邊向下傾斜時,某個角度(轉(zhuǎn)角小于90度)的返回值為:(0.6, 0.0, -0.8),轉(zhuǎn)角為90度時:(1.0, 0.0, 0.0),轉(zhuǎn)角在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 變?yōu)?nbsp;z > 0,若 x < 0 則為ToLeft,否則則為ToRight
6.當 z > 0 變?yōu)?nbsp;z < 0,若 x < 0 則為ToRight,否則則為ToLeft
然后我們可以根據(jù)這些性質(zhì)推斷出手機的當前狀態(tài),然后去執(zhí)行我們想要執(zhí)行的操作。
根據(jù)需求,無論是移動物體,還是轉(zhuǎn)動攝像機來達到偏移的效果,都會有一個最大偏移值,偏移速度,不轉(zhuǎn)動的時候等待的一個間隔時間,這幾個參數(shù)需要設置。
具體實現(xiàn)
首先我們寫一個腳本GyroManager,掛載在場景的一個GameObject上(也可以處理成為單例,在別處調(diào)用里面的Start,Update方法),用來每幀檢測當前的手機狀態(tài),并調(diào)用對應狀態(tài)的注冊事件。
using System;
using UnityEngine;
public enum EGyroType
{
NoRotate,//不旋轉(zhuǎn)
ToUp,//手機下方向上傾斜
ToDown,//手機下方向下傾斜
ToLeft,//左手邊向下傾斜
ToRight,//右手邊向下傾斜
}
public class GyroManager : MonoBehaviour
{
Gyroscope mGyro;//陀螺儀
Vector2 mCurrentLandscapeGyroValue, mCurrentPortraitGyroValue;//當前的水平垂直的gravity值
Vector2 mLastLandscapeGyroValue, mLastPortraitGyroValue;//上一次的水平垂直的gravity值
public EGyroType LandscapeEGyroType, PortraitEGyroType;//手機的水平垂直狀態(tài)
float mPrecision = 0.015f;//精度,若前后兩次gravity值在精度內(nèi),則認為當前沒有旋轉(zhuǎn)
public int LandscapeGyroDifference, PortraitGyroDifference;//模擬的一個旋轉(zhuǎn)速度,gravity值差異越大,則該值越大
bool mIsEnable;//是否開啟陀螺儀
private void Start()
{
mGyro = Input.gyro;
SetGyroEnable(true);
}
//每種狀態(tài)下需要執(zhí)行的事件
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();
//根據(jù)解析出來的手機狀態(tài),執(zhí)行對應事件
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;
}
}
//解析當前手機狀態(tài)
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 變?yōu)?z > 0,若 x < 0 則為ToLeft,否則則為ToRight
if (mCurrentLandscapeGyroValue.x > 0)
{
LandscapeEGyroType = EGyroType.ToLeft;
}
else
{
LandscapeEGyroType = EGyroType.ToRight;
}
}
else
{
//當 z > 0 變?yōu)?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 變?yōu)?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用于掛載在需要根據(jù)手機狀態(tài)偏移的組件上,用于設置偏移的參數(shù),以及對應狀態(tài)下計算偏移的量
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;//檢測手機水平轉(zhuǎn)動還是垂直轉(zhuǎn)動
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;
}
}
//重設回彈參數(shù)
void ResetBackProperty()
{
if (!mIsResetBackProperty)
{
mIsResetBackProperty = true;
mBackSpeed = Speed * 0.8f;
mCurrentDuringTime = 0;
}
}
//手機沒轉(zhuǎn)動的時候,超過間隔時間則減速回彈至默認位置
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場景會隨手機的垂直轉(zhuǎn)動而上下偏移,我們可以通過旋轉(zhuǎn)攝像機的x軸來實現(xiàn),我們只需寫個簡單的腳本掛載在攝像機上即可
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都會隨手機水平翻轉(zhuǎn)而轉(zhuǎn)動,所以就不能直接通過攝像頭來解決,而需要移動需要偏移的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);
}
}
這樣就大致實現(xiàn)了需要的效果了。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持我們。
上一篇:C# char[]與string byte[]與string之間的轉(zhuǎn)換詳解
欄 目:C#教程
本文標題:Unity UI或3D場景實現(xiàn)跟隨手機陀螺儀的晃動效果
本文地址:http://www.jygsgssxh.com/a1/C_jiaocheng/4603.html
您可能感興趣的文章
- 01-10C#實現(xiàn)遠程關閉計算機或重啟計算機的方法
- 01-10C#獲取進程或線程相關信息的方法
- 01-10C#刪除只讀文件或文件夾(解決File.Delete無法刪除文件)
- 01-10Unity3d獲取系統(tǒng)時間
- 01-10Unity3D獲取當前鍵盤按鍵及Unity3D鼠標、鍵盤的基本操作
- 01-10輕松學習C#的String類
- 01-10輕松學習C#的屬性
- 01-10C#比較二個數(shù)組并找出相同或不同元素的方法
- 01-10在C#的類或結構中重寫ToString方法的用法簡介
- 01-10詳解C#編程中構造函數(shù)的使用


閱讀排行
本欄相關
- 01-10C#通過反射獲取當前工程中所有窗體并
- 01-10關于ASP網(wǎng)頁無法打開的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻播放器左下角滾動新
- 01-10C#停止線程的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#通過重寫Panel改變邊框顏色與寬度的
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當前操作系統(tǒng)已
隨機閱讀
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-11ajax實現(xiàn)頁面的局部加載
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 04-02jquery與jsp,用jquery
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 01-10delphi制作wav文件的方法
- 01-10C#中split用法實例總結


