Unity UI或3D场景实现跟随手机陀螺仪的晃动效果
需求
当游戏显示3d场景及其UI的时候。玩家左右晃动手机的时候,UI界面会随之左右偏移。上下晃动的时候,3D场景会随之上下偏移。手机停止晃动的时候,如若偏移的UI或场景,停顿一会后自动恢复到初始默认位置。
分析
首先本文功能应对的是横屏游戏(竖屏游戏的话也差不多一样,大家自己拓展下),假设当我们拿起手机玩游戏,手机会有四个部位,分别为左手拿的左手边和右手拿的右边,以及屏幕内容的上方和下方(下文中会用左手边,右手边,上方,下方来描述)。每个部位的倾斜都会造成UI或场景的偏移效果
我们可以先用一个枚举来定义这四个部位的倾斜情况
publicenumEGyroType
{
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方法),用来每帧检测当前的手机状态,并调用对应状态的注册事件。
usingSystem;
usingUnityEngine;
publicenumEGyroType
{
NoRotate,//不旋转
ToUp,//手机下方向上倾斜
ToDown,//手机下方向下倾斜
ToLeft,//左手边向下倾斜
ToRight,//右手边向下倾斜
}
publicclassGyroManager:MonoBehaviour
{
GyroscopemGyro;//陀螺仪
Vector2mCurrentLandscapeGyroValue,mCurrentPortraitGyroValue;//当前的水平垂直的gravity值
Vector2mLastLandscapeGyroValue,mLastPortraitGyroValue;//上一次的水平垂直的gravity值
publicEGyroTypeLandscapeEGyroType,PortraitEGyroType;//手机的水平垂直状态
floatmPrecision=0.015f;//精度,若前后两次gravity值在精度内,则认为当前没有旋转
publicintLandscapeGyroDifference,PortraitGyroDifference;//模拟的一个旋转速度,gravity值差异越大,则该值越大
boolmIsEnable;//是否开启陀螺仪
privatevoidStart()
{
mGyro=Input.gyro;
SetGyroEnable(true);
}
//每种状态下需要执行的事件
publicActionLandscapeTransToDefault;
publicActionLandscapeTransToAdd;
publicActionLandscapeTransToReduce;
publicActionPortraitTransToDefault;
publicActionPortraitTransToAdd;
publicActionPortraitTransToReduce;
publicvoidResetLandscape()
{
LandscapeEGyroType=EGyroType.NoRotate;
SetLandScapeValue();
mLastLandscapeGyroValue=mCurrentLandscapeGyroValue;
LandscapeGyroDifference=0;
}
publicvoidResetPortrait()
{
PortraitEGyroType=EGyroType.NoRotate;
SetPortraitValue();
mLastPortraitGyroValue=Vector2.zero;
PortraitGyroDifference=0;
}
voidUpdate()
{
if(mIsEnable)
{
GetEGyroType();
//根据解析出来的手机状态,执行对应事件
if(LandscapeEGyroType==EGyroType.ToLeft)
{
LandscapeTransToReduce?.Invoke(LandscapeGyroDifference);
}
elseif(LandscapeEGyroType==EGyroType.ToRight)
{
LandscapeTransToAdd?.Invoke(LandscapeGyroDifference);
}
else
{
LandscapeTransToDefault?.Invoke();
}
if(PortraitEGyroType==EGyroType.ToDown)
{
PortraitTransToReduce?.Invoke(PortraitGyroDifference);
}
elseif(PortraitEGyroType==EGyroType.ToUp)
{
PortraitTransToAdd?.Invoke(PortraitGyroDifference);
}
else
{
PortraitTransToDefault?.Invoke();
}
}
}
//开启或关闭陀螺仪
publicvoidSetGyroEnable(boolisEnable)
{
if(mIsEnable!=isEnable)
{
mIsEnable=isEnable;
ResetLandscape();
ResetPortrait();
mGyro.enabled=isEnable;
}
}
//解析当前手机状态
publicvoidGetEGyroType()
{
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.x0&&mLastLandscapeGyroValue.y>0)
{
//当z>0,x的值变大则为ToLeft,变小则为ToRight
if(mCurrentLandscapeGyroValue.x0,若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.x0&&mLastPortraitGyroValue.y>0)
{
//当z>0,y的值变大则为ToDown,变小则为ToUp
if(mCurrentPortraitGyroValue.x0,则为ToDown,反之则为ToUp
if(mCurrentPortraitGyroValue.y 0变<0
PortraitEGyroType=EGyroType.ToUp;
}
else
{
PortraitEGyroType=EGyroType.ToDown;
}
}
}
mLastPortraitGyroValue=mCurrentPortraitGyroValue;
}
//读取gravity值
publicvoidSetLandScapeValue()
{
mCurrentLandscapeGyroValue.x=mGyro.gravity.x;
mCurrentLandscapeGyroValue.y=mGyro.gravity.z;
}
publicvoidSetPortraitValue()
{
mCurrentPortraitGyroValue.x=mGyro.gravity.y;
mCurrentPortraitGyroValue.y=mGyro.gravity.z;
}
//前后两次是否相等
boolIsEquals(floata,floatb,boolisLandscape)
{
if((isLandscape&&LandscapeEGyroType==EGyroType.NoRotate)||(!isLandscape&&PortraitEGyroType==EGyroType.NoRotate))
{
if(Mathf.Abs(a-b)<0.025f)
{
returntrue;
}
}
if(Mathf.Abs(a-b)
接着我们写个脚本GyroBase用于挂载在需要根据手机状态偏移的组件上,用于设置偏移的参数,以及对应状态下计算偏移的量
usingSystem;
usingUnityEngine;
publicclassGyroBase
{
publicfloatMaxValue;//最大偏移值
publicfloatDefaultValue;//初始位置
floatmCurrentValue;//当前偏移量
publicfloatSpeed;//速度
publicfloatDuringTime;//等待间隔
floatmCurrentDuringTime;//当前时间间隔
publicActionValueChanged;//偏移事件
publicGyroManagermManager;
floatmBackSpeed;//回弹速度(一个减速过程)
floatBackSpeed
{
get
{
if(mBackSpeed>mMinSpeed)
{
mBackSpeed=Mathf.Max(mBackSpeed-Speed*mDeltaTime,mMinSpeed);
}
returnmBackSpeed;
}
}
floatmMinSpeed;//最小速度
floatmDeltaTime;//Time.deltaTime
boolmIsLandScape;//检测手机水平转动还是垂直转动
boolmIsResetBackProperty=false;
//初始化赋值
publicvoidInit(floatmaxValue,floatdefaultValue,floatspeed,floatduringTime,boolisLandscape,Actionaction)
{
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;
}
//事件清除
publicvoidClear()
{
if(mIsLandScape)
{
mManager.LandscapeTransToDefault-=TransToDefault;
mManager.LandscapeTransToAdd-=TransToAdd;
mManager.LandscapeTransToReduce-=TransToReduce;
}
else
{
mManager.PortraitTransToDefault-=TransToDefault;
mManager.PortraitTransToAdd-=TransToAdd;
mManager.PortraitTransToReduce-=TransToReduce;
}
}
//重设回弹参数
voidResetBackProperty()
{
if(!mIsResetBackProperty)
{
mIsResetBackProperty=true;
mBackSpeed=Speed*0.8f;
mCurrentDuringTime=0;
}
}
//手机没转动的时候,超过间隔时间则减速回弹至默认位置
voidTransToDefault()
{
mIsResetBackProperty=false;
mDeltaTime=Time.deltaTime;
mCurrentDuringTime+=mDeltaTime;
if(mCurrentDuringTime>1)
{
ValueToDefault();
ValueChanged?.Invoke(mCurrentValue);
}
}
//偏移增加
voidTransToAdd(intdifference)
{
ResetBackProperty();
ValueAddSpeed(difference);
ValueChanged?.Invoke(mCurrentValue);
}
//偏移减小
voidTransToReduce(intdifference)
{
ResetBackProperty();
ValueReduceSpeed(difference);
ValueChanged?.Invoke(mCurrentValue);
}
voidValueToDefault()
{
if(mCurrentValue>DefaultValue)
{
mCurrentValue=Mathf.Max(mCurrentValue-BackSpeed*mDeltaTime,DefaultValue);
}
elseif(mCurrentValueDefaultValue-MaxValue)
{
mCurrentValue=Mathf.Max(mCurrentValue-Speed*mDeltaTime*difference,DefaultValue-MaxValue);
}
}
}
使用
例如,我们3D场景会随手机的垂直转动而上下偏移,我们可以通过旋转摄像机的x轴来实现,我们只需写个简单的脚本挂载在摄像机上即可
publicclassCameraGyro:MonoBehaviour
{
publicGyroManagermManager;
TransformmTransform;
Vector3mCameraAngle;
GyroBasemGyroBase;
voidStart()
{
mTransform=transform;
mCameraAngle=Vector3.zero;
mGyroBase=newGyroBase();
mGyroBase.mManager=mManager;
mGyroBase.Init(5,0,5,1,false,Change);
}
voidChange(floatvalue)
{
mCameraAngle.x=value;
mTransform.localEulerAngles=mCameraAngle;
}
}
因为自己工程的UI场景并不是所有UI都会随手机水平翻转而转动,所以就不能直接通过摄像头来解决,而需要移动需要偏移的UI部分,所以我们可以写个组件只挂载在需要偏移的UI部分上
publicclassUIGyro:MonoBehaviour
{
publicGyroManagermManager;
voidStart()
{
GyroBasemGyroBase=newGyroBase();
mGyroBase.mManager=mManager;
mGyroBase.Init(80,transform.localPosition.x,80,1,true,Change);
}
voidChange(floatvalue)
{
transform.localPosition=newVector3(value,transform.localPosition.y);
}
}
这样就大致实现了需要的效果了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。