俺に解るように説明する "Godot Engine 3.x" 入門+

ゲームエンジン Godot Engine に関すること。入門とか使い方とかチュートリアルとか、あれとかこれとか。日本語解説。

Godot キャラコン 14 「スムーズな方向転換」

「1.Quatで取って来て、2.slerp()で補間して、3.Transform()でセットする。」 この一連の流れが出来るようになったので、早速、キャラコンのスクリプトに入れ込んでいこう。忘れないうちにやっちゃわないと、天才過ぎるでもやり方忘れちゃうからな。

f:id:ore2wakaru:20180422202221g:plain

(御覧のように、0°をまたぐ方向転換もこなしてくれるようになる。さすが。)


スクリプト変更

[1]

ボディー用の変数宣言部分。

f:id:ore2wakaru:20180422202923p:plain

L31

  • slerp()させる時のスタート値をいれる。Quat型で入れることになる。

L32

  • slerp()させる時のゴール値をいれる。こっちも後にQuat型で。

L33

  • slerp()させる時のtの値。float型。
  • "0.1"は10%のこと。
  • 注意点としては、物理プロセス内でslerp()させてるので、プロジェクトセッティングでdeltaを変更したら、スラープスピードは変わる。(これは加速・減速の時と同じ理屈)
  • もう、"C = A.slerp(B, t)"の式と内容は暗記だ。

[2]

れでぃー関数部分。

f:id:ore2wakaru:20180422203947p:plain

L44

  • 「おばさん」ボディーのQuatを取得しておく。
  • 取得しておかないと、最初のスタート値が空っぽなのでエラーになる。

[3]

ボディーの方向セット部分、ばっさり。

f:id:ore2wakaru:20180422204449p:plain

L75

  • slerp()のゴール値を作る。
  • Quatのとってき方の2番目の方法。つまり、「角度」でとってくるやりかたを採用。
  • はy軸なので、"Vector3(0, 1, 0)"で指定。
  • 角度キャラコンの1113で説明した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