Godot キャラコン 14 「スムーズな方向転換」
「1.Quat
で取って来て、2.slerp()
で補間して、3.Transform()
でセットする。」 この一連の流れが出来るようになったので、早速、キャラコンのスクリプトに入れ込んでいこう。忘れないうちにやっちゃわないと、天才過ぎる俺でもやり方忘れちゃうからな。
(御覧のように、0°をまたぐ方向転換もこなしてくれるようになる。さすが俺。)
スクリプト変更
[1]
ボディー用の変数宣言部分。
L31
slerp()
させる時のスタート値をいれる。Quat
型で入れることになる。
L32
slerp()
させる時のゴール値をいれる。こっちも後にQuat
型で。
L33
slerp()
させる時のtの値。float
型。- "0.1"は10%のこと。
- 注意点としては、物理プロセス内で
slerp()
させてるので、プロジェクトセッティングでdeltaを変更したら、スラープスピードは変わる。(これは加速・減速の時と同じ理屈) - もう、"C = A.slerp(B, t)"の式と内容は暗記だ。
[2]
れでぃー関数部分。
L44
- 「おばさん」ボディーの
Quat
を取得しておく。 - 取得しておかないと、最初のスタート値が空っぽなのでエラーになる。
[3]
ボディーの方向セット部分、ばっさり。
L75
slerp()
のゴール値を作る。Quat
のとってき方の2番目の方法。つまり、「軸と角度」でとってくるやりかたを採用。- 軸はy軸なので、"Vector3(0, 1, 0)"で指定。
- 角度はキャラコンの11・13で説明した
atan2()
を使って取って来ている。 - ついでに
atan2(x, z)
で角度が出るのも暗記だ。
L76
slerp()
させる。 "C = A.slerp(B, t)"- Aの値は現在「おばさん」が向いている方向(というか回転)。(スタート値)
- Bの値は、カメラの向きとキー入力で決まった、最終的に向いてほしい方向(回転)。(ゴール値)
- tの割合だけ近づけたものをCにセット。(これが次のスタート値)
- 結局Cは次の
slerp()
のAになるから、徐々にゴール値に近づいていく事になる。 - まー、考え方は、
linear_interpolate()
と同じ。(キャラコンの07) linear_interpolate()
はVector3
型の補間。slerp()
はQuat
型の補間。それぞれ専用のものがあるという事だ。
L77
Transform()
でQuat
値をセットするが、"my_body.transform"とローカルトランスフォームの変数を使用する事。- "my_body.transform": ローカル。
ローカルでセットすると、親である「KinematicBody」にくっついて動き、回転だけ表現してくれる。 - "my_body.global_transform": グローバル。
前回の練習ではこっちを使ったけど、間違いみたいだな。上手い事使うにはローカル(上の)にするのが正解のようだ。 Transform()
でうまく回転だけ表現したい場合は、親ノードを準備してあるかどうかが胆のようだ。動き部分は親に担当してもらうため。
※===
どうもグローバル("my_body.global_transform")にセットすると、「おばさん」が動かなくなってしまう。
おそらく、Transform()
はなんでもかんでも、(トランスフォーム情報である位置・回転・拡大縮小)全部まるっとひっくるめてセットしちゃうんだろうな、きっと。
だから、変更してない部分は恐らくデフォ値(位置:"(0, 0, 0)"、拡大縮小:"(1, 1, 1)")が常に入って来て動かない、となるのだと思う。注意。
上にも書いたが、今回の「おばさん」のように自由に移動したい場合は、移動を担当する親ノードにくっ付けて、ローカルトランスフォームで処理すること。
===※
結果と次回
結局、コレ、「1.Quat
で取って来て、2.slerp()
で補間して、3.Transform()
でセットする。」が出来ればイイよね。
で、[シーン再生]で一番上のGIFのように、0°をまたいだスムーズ補間が出来てれば成功。ま、できるけどな。
次は、アニメーションかな。アイドルとウォークアニメ。「おばさん」が歩けたらいいけど、出来そうな気がしないぜ。
daeって複数ファイルのアニメーションを取ってこれるんだっけ? 出来なかったら、3Dスペースシューティングゲームに変更だ! 「おばさん」はアニメしない宇宙船モデルになる・・・・・・。
「俺的スカイリム2」は遥か先だ。
こぴぺ
extends KinematicBody # マウス var my_mou_sen = 0.003 # マウスセンシ (mouse sensitivity) var my_mou_rel = Vector2(0, 0) # マウスが動いた差分 (mouse relative) # ヘッド (カメラピボット) var my_head # ヘッドノード入れる用 var my_head_ang = 0.0 # ヘッドのうなづき角度(ラジアン) (head angle) var my_head_ang_max = PI / 3.0 # ヘッドのうなづき限界角度(60°) (head angle MAX) # カメラ var my_ray = [0, 1, 2, 3] # RayCast_L, _R, _U, _Dノード入れる用 var my_ray_dis = [5.0, 5.0, 5.0, 5.0] # RayCast_@がヒットした時その場所までの距離 (distance) var my_ray_dis_min = 5.0 # 距離の最小値を入れる用 var my_cam # カメラノード入れる用 var my_cam_dis = 5.0 # ヘッドとカメラまでの標準距離 (camera distance) # プレイヤ var my_dir = Vector3(0, 0, 0) # キー入力での移動方向 (direction) var my_vel = Vector3(0, 0, 0) # 目標移動地点 (velocity) var my_spd = 10.0 # 移動速度 秒速10m (run speed) var my_acl = 0.03 # 加速率 (acceleration) var my_dcl = 0.08 # 減速率 (deceleration) var my_gra = -10.0 # 重力 (gravity) var my_yyy = 0.0 # 現在のy方向の大きさ保管用 # ボディ var my_body # インポートdaeボディーのノード用 # C = A.slerp(B, t) var my_body_quat_s # A (start値) でありslerp()の結果のC var my_body_quat_g # B (goal値) var my_body_quat_t = 0.1 # t (acceleration値) func _ready(): my_head = get_node("Spatial_Head") my_ray[0] = get_node("Spatial_Head/RayCast_L") my_ray[1] = get_node("Spatial_Head/RayCast_R") my_ray[2] = get_node("Spatial_Head/RayCast_U") my_ray[3] = get_node("Spatial_Head/RayCast_D") my_cam = get_node("Spatial_Head/Camera_Main") my_body = get_node("Dae_Player") my_body_quat_s = Quat(my_body.global_transform.basis) func _input(event): if event is InputEventMouseMotion: my_mou_rel += event.relative func _physics_process(delta): # プレイヤ動かす # キー入力により移動方向をセットする my_dir = Vector3(0, 0, 0) if Input.is_action_pressed("my_forward"): my_dir = my_dir - my_head.global_transform.basis.z if Input.is_action_pressed("my_backward"): my_dir = my_dir + my_head.global_transform.basis.z if Input.is_action_pressed("my_right"): my_dir = my_dir + my_head.global_transform.basis.x if Input.is_action_pressed("my_left"): my_dir = my_dir - my_head.global_transform.basis.x my_dir.y = 0.0 my_dir = my_dir.normalized() # 加速・減速 if my_dir == Vector3(0, 0, 0): my_vel = my_vel.linear_interpolate(Vector3(0, 0, 0), my_dcl) else: my_vel = my_vel.linear_interpolate(my_dir * my_spd, my_acl) # 重力 my_vel.y = my_yyy + my_gra * delta # 移動とy保持 my_yyy = move_and_slide(my_vel).y #ボディーの方向セット my_body_quat_g = Quat(Vector3(0, 1, 0), atan2(my_vel.x, my_vel.z)) my_body_quat_s = my_body_quat_s.slerp(my_body_quat_g, my_body_quat_t) my_body.transform = Transform(my_body_quat_s) # ヘッド動かす # 横回転 my_head.global_rotate(Vector3(0, 1, 0), -my_mou_rel.x * my_mou_sen) # 縦回転 my_head_ang = my_head_ang - my_mou_rel.y * my_mou_sen my_head_ang = clamp(my_head_ang, -my_head_ang_max, my_head_ang_max) my_head.rotation.x = my_head_ang # マウス移動がない時に勝手に動かない様、0に my_mou_rel = Vector2(0, 0) # 障害物に当たってたらカメラを前に、当たってなければ標準距離に # 当たり判定と距離取得 my_ray_hit(0) # 左レイ判定 my_ray_hit(1) # 右レイ判定 my_ray_hit(2) # 上レイ判定 my_ray_hit(3) # 下レイ判定 # 最小値取得 my_ray_dis_min = my_ray_dis[0] for i in [1, 2, 3]: my_ray_dis_min = min(my_ray_dis_min, my_ray_dis[i]) # カメラ位置セット my_cam.translation = Vector3(0, 0, my_ray_dis_min) func my_ray_hit(i): if my_ray[i].is_colliding(): my_ray_dis[i] = my_ray[i].global_transform.origin.distance_to(my_ray[i].get_collision_point()) else: my_ray_dis[i] = my_cam_dis