服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - Unity UI或3D场景实现跟随手机陀螺仪的晃动效果

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

2022-08-08 10:19王王王渣渣 C#

这篇文章主要介绍了Unity UI或3D场景实现跟随手机陀螺仪的晃动效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

需求

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

分析

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

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

?
1
2
3
4
5
6
7
8
public enum EGyroType
{
 NoRotate,//不旋转
 ToUp,//手机下方向上倾斜
 ToDown,//手机下方向下倾斜
 ToLeft,//左手边向下倾斜
 ToRight,//右手边向下倾斜
}

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

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

我用到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方法),用来每帧检测当前的手机状态,并调用对应状态的注册事件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
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用于挂载在需要根据手机状态偏移的组件上,用于设置偏移的参数,以及对应状态下计算偏移的量

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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轴来实现,我们只需写个简单的脚本挂载在摄像机上即可

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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部分上

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
 }
}

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

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

原文链接:https://blog.csdn.net/wangjiangrong/article/details/100895061

延伸 · 阅读

精彩推荐
  • C#C#带你玩扫雷(附源码)

    C#带你玩扫雷(附源码)

    这篇文章主要介绍了C#带你玩扫雷(附源码),详细的介绍实现扫雷的方法,具体一定的参考价值,有兴趣的可以了解一下...

    Cosecant7832022-01-24
  • C#浅谈C# 非模式窗体show()和模式窗体showdialog()的区别

    浅谈C# 非模式窗体show()和模式窗体showdialog()的区别

    下面小编就为大家带来一篇浅谈C# 非模式窗体show()和模式窗体showdialog()的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编...

    C#教程网9812021-12-01
  • C#Unity UGUI实现卡片椭圆方向滚动

    Unity UGUI实现卡片椭圆方向滚动

    这篇文章主要为大家详细介绍了UGUI实现卡片椭圆方向滚动效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    尘世喧嚣7962022-07-09
  • C#C#操作读取、写入XML文档的实用方法

    C#操作读取、写入XML文档的实用方法

    这篇文章主要介绍了C#操作读取、写入XML文档的实用方法,即即用.NET本身提供的Deserialize和Serialize进行反序列化和序列化XML文档,感兴趣的小伙伴们可以参...

    一个人的长征9262021-11-18
  • C#C#基于正则表达式实现获取网页中所有信息的网页抓取类实例

    C#基于正则表达式实现获取网页中所有信息的网页抓取类实例

    这篇文章主要介绍了C#基于正则表达式实现获取网页中所有信息的网页抓取类,结合完整实例形式分析了C#正则网页抓取类与使用技巧,需要的朋友可以参考下...

    roucheng7252022-01-05
  • C#解析C#中的私有构造函数和静态构造函数

    解析C#中的私有构造函数和静态构造函数

    这篇文章主要介绍了C#中的私有构造函数和静态构造函数,是C#入门学习中的基础知识,需要的朋友可以参考下...

    C#教程网5482021-11-09
  • C#C# 程序集和反射详解

    C# 程序集和反射详解

    本文主要介绍了C# 程序集和反射的相关知识。具有一定的参考价值,下面跟着小编一起来看下吧...

    邹琼俊6972021-12-18
  • C#深入解析C#编程中泛型委托的使用

    深入解析C#编程中泛型委托的使用

    这篇文章主要介绍了C#编程中泛型委托的使用,引用泛型委托的代码可以指定类型参数以创建已关闭的构造类型,需要的朋友可以参考下...

    Fastyou4242021-11-12