当前位置: 移动技术网 > IT编程>开发语言>c# > Unity UI或3D场景实现跟随手机陀螺仪的晃动效果

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

2020年03月09日  | 移动技术网IT编程  | 我要评论
需求 当游戏显示3d场景及其ui的时候。玩家左右晃动手机的时候,ui界面会随之左右偏移。上下晃动的时候,3d场景会随之上下偏移。手机停止晃动的时候,如若偏移的ui或场景,停顿一会后自

需求

当游戏显示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);
 }
}

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

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

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网