Godot4.0でジャイロセンサーでボールを転がしてみた

　ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
　2023/4/4リリースのGodot_v4.0.2-stable_win64を使用しています。

# 目的
　スマホの傾きをジャイロセンサーで読み取ることができたので、床の上にボールを置いてスマホを傾けると傾けたほうにボールが転がるようなアプリを作りたいと思います。
![目的.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/51b1d059-fb2b-0eb1-7de8-87e6179ad17c.png)

# プロジェクト設定でスマホを縦で使うように設定する
　プロジェクトメニューからプロジェクト設定を実行します。
　表示＞ウインドウを選択して、ビューボートの幅、高さを変更します。また画面解像度が変わった場合に自動的に画面の大きさとControlなどの各パーツのサイズ比率を保つように、ストレッチ/モードをcanvas_itemsに変更します。
　ポータブル/方向をPortraitにすると、スマホを縦に持った画面で実行できるようになります。
![スクリーンショット (722).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/90ac8c67-9bca-708c-ff2f-5f782518960c.png)
また後でタッチ操作を実装するときにWindowsPC上でデバッグできるようにしたいので、入力デバイス＞ポインティングの「マウスでタッチ操作をエミュレート」にチェックをいれます。
![スクリーンショット (724).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/6c2de73d-7b39-1e2a-d2e9-93e3a6304a61.png)
　ついでに、Androidアプリ(apkファイル）エクスポートのための設定もしておきます。
　右上のAdvanced SettingsをONにして、レンダリング＞テクスチャのVRAM圧縮/「Import ETC2 ASTC」をオンにします。設定後は保存して再起動ボタンを押下します。
![スクリーンショット (725).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/2c1d7e44-9a30-7866-d50e-194d198f6878.png)

# 新規シーンを作成する
　カメラとライト、床とボールをひとつ追加します。ライトは真上から床を照らします。
　StaticBody3Dの床の上にRigidBody3Dのボールを置きます。RigidBody3Dにすることでボールの動きはGodotの物理エンジンにお任せです。

| ノード名:ノードクラス名 |インスペクタ変更値|
|:-|:-|
|World : Node3D||
|　┝━Camera3D|Position : (0m, 12m, 12m)  Rotation : (-60°, 0°, 0°)
|　┝━DirectionalLight3D|Position : (-10m, 0m, 0m)  Rotation : (-90°, 0°, 0°) shadow:オン|
|　┝━Floor : StaticBody3D|Position : (0m, -0.5m, 0m)|
|　┃　　┝━MeshInstance3D　|新規BoxMesh3D Size x:10m, y:1m, z:20m|
|　┃　　┗━CollisionShape3D|新規BoxShape3D Size x:10m, y:1m, z:20m|
|　┗━MyBall : RigidBody3D|Position : (0m, 4m, 0m)|
|　 　　┝━MeshInstance3D　|新規SphereMesh3D |
|　 　　┗━CollisionShape3D|新規SphereShape3D |

シーンはこのようになります。
![スクリーンショット (727).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/bdcaf15a-54f4-e0a1-8105-ebd4155c3762.png)
実行します。ボールが上から落ちてきて床の上で止まります。
![スクリーンショット (726).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/ac565ace-f0eb-52ce-e91d-bb25867315f1.png)

# 床を傾けるスクリプトを実装
　スクリプトを実装して床を傾けるようにします。
　シーンのルートノートWorldを右クリックして、スクリプトをアタッチします。
　world.gdという名称になるのでそのまま保存します。

```GDScript
extends Node3D

func _physics_process(delta):
	var v3_gyro_rad = Input.get_gyroscope()
	$Floor.rotate_y(  v3_gyro_rad.z * delta)
	$Floor.rotate_z( -v3_gyro_rad.y * delta)
	$Floor.rotate_x(  v3_gyro_rad.x * delta)
```
　実行してもwindowsアプリでは何もおきません。
　Android用のapkファイルをエクスポートして、スマホで実行しましょう。
　スマホを傾けるとジャイロセンサー値によって床が傾きますが、おもったよりも自由な方向を向いてしまいます。

　下図はスマホを上から見たときの画面と回転軸です。
　やりたいこととしては、下図の左のようにスマホを左右に傾けたときは床も左右に傾いてほしい。
　中央の図のように奥手前に傾けたときも床は奥手前に傾いてほしい。
　しかし右の図のスマホを自動車のハンドルのように回した時には、回ってほしくないのです。
![スクリーンショット (730).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/f141f4cb-b92b-145d-31cb-7f5f4ae30f84.png)

　ではスクリプトを下記のようにして、rotate_yを実行しないようにすればよいと思うのですが、スマホを前後左右に傾けて戻してを繰り替えすと、床がY軸周りを回転しているように見えます。
```GDScript
extends Node3D

func _physics_process(delta):
	var v3_gyro_rad = Input.get_gyroscope()
	$Floor.rotate_z( -v3_gyro_rad.y * delta)
	$Floor.rotate_x(  v3_gyro_rad.x * delta)
```
　たぶん下記のオイラー角の問題点なのでしょうか？
>オイラー角の問題点
>https://docs.godotengine.org/ja/stable/tutorials/3d/using_transforms.html#axis-order

# 床を左右と手前奥方向にだけ傾くようにする
　Worldシーンの子にNode3DのX軸回転用の子とZ軸回転用の孫を作ってその下にFloorをひ孫としてぶら下げるようにしましょう。
　シーン内のWorldを右クリックして、子ノードを追加からNode3Dを追加します。「x」に名称を変更します。
　ｘを右クリックして、子ノードを追加からNode3Dを追加します。「z」に名称を変更します。
　そして、Floorをzにドラッグアンドドロップしましょう。
![スクリーンショット (731).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/f1b70ebf-e356-badd-e5e8-f13a0974651a.png)

　res://world.gdを下記のように修正します。
```GDScript
extends Node3D

func _physics_process(delta):
	var v3_gyro_rad = Input.get_gyroscope()
	$x/z.rotate_z( -v3_gyro_rad.y * delta)
	$x.rotate_x(  v3_gyro_rad.x * delta)
```
　これでなぜうまくいくのかは正直理解していないのですが、結果を見ると期待通り床が左右と奥行方向のみ傾くようになります。

# Windowsアプリでデバッグできるようにする
　デバッグ用にマウスのクリック＆ドラッグ操作で床の傾きを変更できるようにしましょう。　
　スマホで実行した場合は、タッチ操作／スマホ傾けのどちらの操作でも床が傾きます。

:::note
 2023/5/5 現在の床の傾きをtransform.basis.get_euler()で取得するように変更しました
:::

```GDScript
extends Node3D

@export var m_d_speed = 0.1		# 大きくすると床が傾きやすくなる

var m_touch_pos = Vector2.ZERO		# タッチした時の位置
var m_drag_pos = Vector2.ZERO		# 現在のタッチ位置・ドラッグ位置
var m_f_is_screen_touch = false		# スクリーンにタッチ（ドラッグ含む）している場合true

func _input(event):
	if event is InputEventScreenTouch:
		if event.is_pressed():
			# タッチしたところを初期位置として記憶する
			m_touch_pos  = event.position
			m_drag_pos = event.position
			m_f_is_screen_touch = true
		else:
			m_f_is_screen_touch = false
	elif event is InputEventScreenDrag:
		m_drag_pos = event.position
		m_f_is_screen_touch = true

func _physics_process(delta):
	# ジャイロセンサーによる床傾け
	var v3_gyro_rad = Input.get_gyroscope()
	$x/z.rotate_z( -v3_gyro_rad.y * delta)
	$x.rotate_x(  v3_gyro_rad.x * delta)

	# タッチ操作による床傾け
	if m_f_is_screen_touch:
		# タッチのX軸方向の操作で左右に傾けたいので、Z軸を回転する
		var diff_x_px = m_drag_pos.x - m_touch_pos.x	# スクリーン上のタッチ位置とドラッグ中の位置の差をピクセル量で計算
		var target_z_deg = -(diff_x_px * m_d_speed)		# 符号反転する
		var v3_z_rot : Vector3 = $x/z.transform.basis.get_euler()
		$x/z.rotate_z( deg_to_rad(target_z_deg) - v3_z_rot.z)
		
		# タッチのY軸方向の操作で手前・奥に傾けたいので、x軸を回転する
		var diff_y_px = m_drag_pos.y - m_touch_pos.y
		var target_x_deg = diff_y_px * m_d_speed
		var v3_x_rot : Vector3 =  $x.transform.basis.get_euler()
		$x.rotate_x( deg_to_rad(target_x_deg) - v3_x_rot.x )

```

# ボールの動きを調整する
　ボールの動きが止まりにくくて操作しにくいので調整します。

　シーン内の「MyBall」を選択した状態で、インスペクタのRigidBody3D/LinearのDampを「２」に変更します。
 「damps the body's movement」とあるので、動きを鈍らせる、減衰させる、ような効果があるようです。

>float linear_damp
>https://docs.godotengine.org/ja/latest/classes/class_rigidbody.html#class-rigidbody-property-linear-damp

　また加速が弱いので、インスペクタのRigidBody/GravityScaleを「２」にしました。
![スクリーンショット (732).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/bec819e1-1b1a-e4ba-94ee-e943fd55ec81.png)

:::note
　私の感覚で適当に調整をしています。
　またプロジェクト全体に適用する場合はプロジェクト設定の物理/3Dの方を修正します。
:::

![スクリーンショット (733).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/9b8cfb48-afdb-1901-28f4-6a6e16ae8188.png)

# ボールが弾むようにする
　実行直後4mの高さから落ちますが、床についたときにピタッと止まるのが不自然なので、少し跳ね返るようにします。

　シーン内の「MyBall」を選択した状態で、インスペクタのRigidBody3DのPhysics Material Overrideの右の＜空＞をクリックして、新規PhysicsMaterialを選択します。
![スクリーンショット (734).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/de46e59e-7faf-526f-a5ef-2b80eeb7b9bb.png)
　設定されたPhysicsMaterialをクリックして詳細設定を表示して、Bounceを0.5にすると、ボールが跳ねるようになります。
![スクリーンショット (735).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/fa4e79e8-f5f1-9109-324c-d93331e36282.png)

　DampやGravityScaleの設定値とも相互に影響していると思います。

# github

https://github.com/footinglow/Godot4/tree/main/03_study/GyroscopeMoveFloor


以上