需求

当游戏显示3d场景及其UI的时候。玩家左右晃动手机的时候,UI界面会随之左右偏移。上下晃动的时候,3D场景会随之上下偏移。手机停止晃动的时候,如若偏移的UI或场景,停顿一会后自动恢复到初始默认位置。

github:https://github.com/luckyWjr/Demo

分析

首先本文功能应对的是横屏游戏(竖屏游戏的话也差不多一样,大家自己拓展下),假设当我们拿起手机玩游戏,手机会有四个部位,分别为左手拿的左手边和右手拿的右边,以及屏幕内容的上方和下方(下文中会用左手边,右手边,上方,下方来描述)。每个部位的倾斜都会造成UI或场景的偏移效果

我们可以先用一个枚举来定义这四个部位的倾斜情况

public enum EGyroType
{NoRotate,//不旋转ToUp,//手机下方向上倾斜ToDown,//手机下方向下倾斜ToLeft,//左手边向下倾斜ToRight,//右手边向下倾斜
}

接着我们可以使用Unity的陀螺仪接口Input.gyro的一些属性,来判断当前手机的倾斜状态,Gyroscope有如下属性:

rotationRate

Returns rotation rate as measured by the device's gyroscope.
返回设备陀螺仪测量的旋转速率。
rotationRateUnbiased Returns unbiased rotation rate as measured by the device's gyroscope.
返回由设备陀螺仪测量的无偏旋转速率。

gravity

Returns the gravity acceleration vector expressed in the device's reference frame.
返回在设备参考帧的重力加速度。
userAcceleration Returns the acceleration that the user is giving to the device.
返回用户提供 给设备的加速度。
attitude Returns the attitude of the device. 返回设备的姿态。
enabled Sets or retrieves status of this gyroscope. 设置或检索该陀螺仪的状态。
updateInterval Sets or retrieves gyroscope interval in seconds.
设置或检索该陀螺仪的间隔,以秒为单位。

我用到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();//Landscapeif (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,变大则为ToRightif (mCurrentLandscapeGyroValue.x < mLastLandscapeGyroValue.x){LandscapeEGyroType = EGyroType.ToLeft;}else{LandscapeEGyroType = EGyroType.ToRight;}}else if (mCurrentLandscapeGyroValue.y > 0 && mLastLandscapeGyroValue.y > 0){//当 z > 0,x的值变大则为ToLeft,变小则为ToRightif (mCurrentLandscapeGyroValue.x < mLastLandscapeGyroValue.x){LandscapeEGyroType = EGyroType.ToRight;}else{LandscapeEGyroType = EGyroType.ToLeft;}}else{if (mCurrentLandscapeGyroValue.y < mLastLandscapeGyroValue.y){//当 z < 0 变为 z > 0,若 x < 0 则为ToLeft,否则则为ToRightif (mCurrentLandscapeGyroValue.x > 0){LandscapeEGyroType = EGyroType.ToLeft;}else{LandscapeEGyroType = EGyroType.ToRight;}}else{//当 z > 0 变为 z<0,若 x< 0 则为ToRight,否则则为ToLeftif (mCurrentLandscapeGyroValue.x < 0){LandscapeEGyroType = EGyroType.ToLeft;}else{LandscapeEGyroType = EGyroType.ToRight;}}}}mLastLandscapeGyroValue = mCurrentLandscapeGyroValue;SetPortraitValue();//Portraitif (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,变小则为ToDownif (mCurrentPortraitGyroValue.x < mLastPortraitGyroValue.x){PortraitEGyroType = EGyroType.ToDown;}else{PortraitEGyroType = EGyroType.ToUp;}}else if (mCurrentPortraitGyroValue.y > 0 && mLastPortraitGyroValue.y > 0){//当 z > 0,y的值变大则为ToDown,变小则为ToUpif (mCurrentPortraitGyroValue.x < mLastPortraitGyroValue.x){PortraitEGyroType = EGyroType.ToUp;}else{PortraitEGyroType = EGyroType.ToDown;}}else{//当 z<0 变为 z > 0,则为ToDown,反之则为ToUpif (mCurrentPortraitGyroValue.y < mLastPortraitGyroValue.y){//>0 变 <0PortraitEGyroType = 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.deltaTimebool 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场景(跟随手机陀螺仪)的晃动效果相关推荐

  1. 【Unity】UI或3D场景自动设置渐变色背景

       给定一组色值(或者多组色值,每次随机取一组),初始化时创建图片并赋值给UI的Image或3D场景的Sprite.从下/左往上/右,按曲线渐变 using System.Collections; ...

  2. unity像素风3D场景-后处理

    先创建一个C#脚本"PixelateImageEffect",不要再Editor目录下创建C#脚本. 复制如下代码: using System.Collections; using ...

  3. Unity UI或3d模型的动画控制(Animation类)

    文章目录 Animation动画控制类 一.动画设置: 二.模型的动作选择: 三.关键方法: 四:实践展示: 五.控制模型移动: Animation动画控制类 在Unity中,我们可以使用2D的Spi ...

  4. Unity基础(三)3D场景搭建

    目录 一.下载新手资源 二.创建基本地形 三.添加场景细节 四,添加水 五,其他 一.下载新手资源 选择窗口->资源商店 点击按钮,打开unity资源商店网站,搜索(Starter Assets ...

  5. 【UI3D】当UI亲吻3D—浅谈手机UI发展

    2010年的岁末年初,一部<阿凡达>用3D的热情感染了整个世界:影片上映之初,有的国家甚至因为一票难求出现了连夜排队购票的事情.时至今日,3D技术已成为<复仇者联盟>.< ...

  6. 当UI亲吻3D——浅谈手机UI发展

    2010年的岁末年初,一部<阿凡达>用3D的热情感染了整个世界:影片上映之初,有的国家甚至因为一票难求出现了连夜排队购票的事情.时至今日,3D技术已成为<复仇者联盟>.< ...

  7. Threejs实现3D场景浏览器内存消耗过高导致浏览器卡顿崩溃刷新等问题解决办法以及3D场景在手机浏览器中画质不高的原因

    个人主页: 左本Web3D,更多案例预览请点击==> 在线案例 个人简介:专注Web3D使用ThreeJS实现3D效果技巧和学习案例

  8. unity 3D场景摄像机跟随人物

    3D场景摄像机跟随人物 2d场景和3d场景跟随不一样,但是有些还是相同的,就因为3d场景中人物旋转方向导致摄像机不能始终和人物方向一致很麻烦.所以下面提供一种挺不错的方法来帮助你. 效果如下: 效果就 ...

  9. Unity UI适配不同比例分辨率的设置

    至前我参与的小游戏开发的过程中,在适配不同分辨率比例的手机上UI遇到了一些问题.下面对解决这些问题的方法进行记录(适配不同分辨率的方法主要在第"二"和"三"中) ...

最新文章

  1. 团队项目第一阶段冲刺站立会议11(4月28日)
  2. Science Robotics:新型多足机器人可自行组装,零件损坏时也能继续运动
  3. Java Web开发中路径问题小结
  4. 算法提高课-动态规划-树形DP-AcWing 1072. 树的最长路径:dfs写法
  5. c# java gt;gt;gt;,相同的字节数组=gt; Java和C#中的不同BigInteger值
  6. visual studio可以开发app吗_个人能开发App软件吗?从想法到App开发完成,我只用了三天...
  7. python excel库 linux_用python写一个简单的excel表格获取当时的linux系统信息
  8. python 异常处理 变量_Python基础入门:从变量到异常处理
  9. 算法移植优化(四)c++11 多线程
  10. 从视图到控制器的传值方法(表单)
  11. java语言程式设计——异常处理语法 3
  12. NULL和空字符的区别
  13. php 5.5.1,PHP5.3.1 不再支持ISAPI
  14. [BZOJ1085][SCOI2005]骑士精神
  15. ubuntu系统共享桌面的使用和配置
  16. (一)VirtualBox安装增强功能
  17. php设计超级玛丽人物,面向对象实现简单版的超级马里奥小游戏
  18. CITA环境搭建与运行
  19. Windows10怎么设置双屏?双屏显示设置有哪些?
  20. 常用计算机系统包括,常用的保护计算机系统的方法有()。

热门文章

  1. java ee是什么_java ee与java的区别是什么
  2. 谷歌欲将Android系统应用到眼球设备
  3. 2015,6月 嘉杰信息杯比赛总结
  4. 解析kernel 2.6.24使用NMI中断对Hard lock的处理
  5. 字体的故事:简单为美的 Helvetica
  6. [CF1705E Mark and Professor Koro]
  7. NET程序员讨论技术群
  8. Spring AOP 基本概念
  9. ntpd自动启动java_ntpd 使用NTPD设置时间服务器
  10. apnicIp筛选中国地区IP