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);
 }
}

这样就大致实现了需要的效果了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Unity UGUI通过摇杆控制角色移动

    本文实例为大家分享了Unity UGUI通过摇杆控制角色移动的具体代码,供大家参考,具体内容如下 简单版:控制方块的移动. 进阶版:控制人物的移动 知识铺垫: 首先我们必须要知道,在Unity的UGUI中,对UI的操作有八个回调,分别需要实现八个接口.分别是: 鼠标进入,鼠标离开,鼠标点下,鼠标抬起,鼠标开始拖拽,鼠标拖拽中,拖拽结束 如下所示: 我们可以先对这几个接口方法进行一下测试: 测试结束后,大家就会对这些接口方法有一些初步的了解. using UnityEngine; using Un

  • Unity3D手机陀螺仪的使用方法

    使用手机陀螺仪,可以获取手机的3D姿态,这在开发中是很有用的.当然现在的手机内置的陀螺仪都是比较廉价的,精度不高,但是作为实验设备看看效果还是可以的.本文将给出调用手机陀螺仪的简单方法. 首先,我们需要在场景中添加大量方块,作为观察对象. 控制陀螺仪的脚本: using UnityEngine; using System.Collections; public class gyroscope : MonoBehaviour { bool draw = false; bool gyinfo; Gy

  • Unity3D使用陀螺仪控制节点旋转

    本文实例为大家分享了Unity3D陀螺仪控制节点旋转的具体代码,供大家参考,具体内容如下 /******************************************************************** Desc: 陀螺仪对相机的逻辑类. *********************************************************************/ using System; using System.Collections; using

  • Unity UI或3D场景实现跟随手机陀螺仪的晃动效果

    需求 当游戏显示3d场景及其UI的时候.玩家左右晃动手机的时候,UI界面会随之左右偏移.上下晃动的时候,3D场景会随之上下偏移.手机停止晃动的时候,如若偏移的UI或场景,停顿一会后自动恢复到初始默认位置. 分析 首先本文功能应对的是横屏游戏(竖屏游戏的话也差不多一样,大家自己拓展下),假设当我们拿起手机玩游戏,手机会有四个部位,分别为左手拿的左手边和右手拿的右边,以及屏幕内容的上方和下方(下文中会用左手边,右手边,上方,下方来描述).每个部位的倾斜都会造成UI或场景的偏移效果 我们可以先用一个枚

  • Unity UI实现拖拽旋转

    本文实例为大家分享了Unity UI实现拖拽旋转的具体代码,供大家参考,具体内容如下 跟随鼠标旋转 第一种效果是跟随鼠标旋转,原理是计算下鼠标位置与拖拽物体的相对位移 旋转方向即可 注意转换对应空间坐标 新建脚本mono类继承 IBeginDragHandler, IDragHandler, IEndDragHandler 接口 [SerializeField] private Canvas m_Canvas; private Vector3? CalculateWorldToScreenPos

  • Unity实现仿3D轮转图效果

    本文实例为大家分享了Unity实现仿3D轮转图效果的具体代码,供大家参考,具体内容如下 一.效果演示 二.实现思路 ——获取位置:可以将每个item的运行轨迹看作一个椭圆,分为四段进行观察,四个黑点视为四个item,观察四个黑点的位置,比例值为0.125和0.375的位置相同,比例值为0.625和0.875的位置相同,比例值为0.375和0.625的位置相反,可得结论[0,0.25]:轨迹总长度*当前比例值(0.25,0.5]:轨迹总长度 * (0.5 - 当前比例值)(0.5,0.75]:轨迹

  • Unity Shader实现3D翻页效果

    本文实例为大家分享了Unity Shader实现3D翻页效果的具体代码,供大家参考,具体内容如下 参考文章:UnityShader使用Plane实现翻书效果 效果图: 原理:Shader顶点动画 在顶点着色器进行对顶点Y值的偏移(使用了Sin函数模拟翻页时产生的弯曲),对顶点X值的偏移实现纸张在翻页时的收缩(一般是不用收缩),最后对顶点进行围绕Z轴旋转实现Plane翻页(Z轴是本例的旋转轴,请根据你具体情况修改,上面的两个偏移同理). Shader "Unlit/PaperTurnMilkSha

  • Unity游戏开发实现场景切换示例

    目录 引言 一.实现逻辑 二.代码实现 2.1 Transition Manager 2.2 Teleport 2.3 Cursor Manager 引言 在unity中可以将不同场景的背景和道具放置在不同的Scene当中,通过对Scene的加载和卸载来实现场景之间的切换.同时创建一个基础场景(Control Scene)来对整个游戏系统进行管理,在基础场景(Control Scene)中不放置背景图片或者游戏道具而只添加各种控制单元和Canvas. 一.实现逻辑 在场景切换的实现过程中需要定义

  • Cocos2d-x UI开发之场景切换代码实例

    cocos2d-x中的场景切换是通过导演类调用相应的方法完成的,可以通过CCDirector::sharedDirector()->replaceScene()方法切换场景,参数是要切换的新场景,这种方法会释放掉旧的场景.通过pushScene()切换则是将旧的场景压入到栈中,以便通过popScene()函数出栈,继续运行原场景.刚开始程序启动的时候通过runWithScene()运行一个新的场景.下面通过代码来说明.单击下图可以查看效果. 这次代码一共建立了俩个场景,一个是原来的hellowo

  • 基于cssSlidy.js插件实现响应式手机图片轮播效果

    cssSlidy是一款支持手机移动端的焦点图轮播插件,支持标题设置,滑动动画,间隔时间等. 在线实例 实例演示 使用方法 <div id="slidy-container"> <figure id="slidy"> <a href='#' target='_blank'><img src="img/2.jpg" alt="jQuery.nicescroll美化滚动条" data-cap

  • 使用JS实现气泡跟随鼠标移动的动画效果

    气泡跟随鼠标移动,并在每次点击时产生不同的变化 效果如下 <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title> 简单的气泡效果 </title> <style type="text

  • jQuery实现的手机发送验证码倒计时效果代码分享

    这是一款基于jquery实现的手机发送验证码倒计时效果代码,可实现实时显示秒数倒计时的功能,还可实现对手机号码格式验证的功能,是一款常用的网站注册发送手机验证码特效代码. 效果描述: 注册一个网站,当需要发送验证码到手机上的时候,我们经常碰到这样的效果: 首先检测手机是否符合1开头,11位数字的格式: 若不符合,则提示错误信息并返回false: 否则提交给后台,后台确定接收后返回一个值,发送按钮变为灰色并倒计时. 运行效果: --------------------------------效果演

随机推荐