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); } }
这样就大致实现了需要的效果了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。
相关文章
- 这篇文章主要介绍了c#从数据库里取得数据并异步更新ui的方法,大家参考使用吧...2020-06-25
No module named ‘win32gui‘ 的解决方法(踩坑之旅)
这篇文章主要介绍了No module named ‘win32gui‘ 的解决方法(踩坑之旅),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-18- 这篇文章主要介绍了解决vant-UI库修改样式无效的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-11-03
- 这篇文章主要为大家详细介绍了vue+element ui实现锚点定位,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-06-29
- 由于业务需要,要求实现树形菜单,且菜单数据由后台返回,下面这篇文章主要给大家介绍了关于js如何构造elementUI树状菜单的数据结构的相关资料,需要的朋友可以参考下...2021-05-13
- 这篇文章主要为大家详细介绍了easyUI下拉列表点击事件的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2017-05-22
详解element-ui 表单校验 Rules 配置 常用黑科技
这篇文章主要介绍了element-ui 表单校验 Rules 配置 常用黑科技,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-11- 这篇文章主要为大家详细介绍了Unity时间戳的使用方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25
jQuery EasyUI编辑DataGrid用combobox实现多级联动
本文给大家分享jQuery EasyUI编辑DataGrid用combobox实现多级联动效果的实例代码,代码简单易懂,非常不错,具有参考借鉴价值,感兴趣的朋友一起看看吧...2016-09-01- SwiftUI是一种使用Swift语言在苹果设备上构建用户界面的创新且简单的方式,下面这篇文章主要给大家介绍了关于SwiftUI图片缩放、拼图等处理的相关资料,需要的朋友可以参考下...2021-08-23
jQuery Easyui使用(二)之可折叠面板动态加载无效果的解决方法
这篇文章主要介绍了jQuery Easyui使用之可折叠面板动态加载无效果的解决方案,非常不错,具有参考借鉴价值,感兴趣的朋友一起看下吧...2016-08-24如何解决easyui自定义标签 datagrid edit combobox 手动输入保存不上
这篇文章主要介绍了如何解决easyui自定义标签 datagrid edit combobox 手动输入保存不上,需要的朋友可以参考下...2015-12-28Unity中 ShaderGraph 实现旋涡传送门效果入门级教程(推荐)
通过Twirl 旋转节点对Gradient Noise 梯度噪声节点进行操作,就可得到一个旋转的旋涡效果。具体实现代码跟随小编一起通过本文学习下吧...2021-07-11- 小米在3月9日下午5点推送了小米5的MIUI8.2更新,这次升级最大的改变是核心变成了Android7.0,因此加入了不少新功能。从MIUI1到MIUI8,历经了好几年了,下面小编为大家整理MIUI八个历史版本的系统图标,来看看吧...2017-07-06
解析element-ui中upload组件传递文件及其他参数的问题
这篇文章主要介绍了element-ui中upload组件如何传递文件及其他参数,分析一下我使用element-ui遇到的问题以及解决方法,需要的朋友可以参考下...2021-11-10vue+elementui实现点击table中的单元格触发事件--弹框
这篇文章主要介绍了vue+elementui实现点击table中的单元格触发事件--弹框,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-18- 本文主要介绍了4种延时执行的方法,主要包括Update计时器,Invoke,协程,DoTween,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-07-07
- 这篇文章主要为大家详细介绍了jQuery UI结合Ajax创建可定制的Web界面,如何利用Ajax和jQuery UI创建具有各种定制功能的高度可定制的UI,感兴趣的小伙伴们可以参考一下...2016-06-24
- 这篇文章主要为大家详细介绍了Unity3D UGUI实现翻书特效,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25
- 这篇文章主要介绍了基于elementUI竖向表格、和并列的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-10-26