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

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

Godot キャラコン 21 「おばさん、やっとこアイドル~ウォークする」

長かった。こんなしょぼい事でも1ヶ月半掛かるのかよ。YouTube動画なら30分だぜ、まったく、どうなってんだ。ただ、ゲームエンジンが無かったら、一生無理だったろうな。

f:id:ore2wakaru:20180512101220g:plain

んー、アニメがしょぼい。


「おばさん」ボディーの入れ替え

「おばさん」ボディを BodyF01 から BodyF02 に入れ替える。アニメの入ってるやつにする。

[1]

f:id:ore2wakaru:20180512083038p:plain

そんで、ファイルシステムパネルの Body フォルダの中にインポートした dae とマテリアルも全部消しとく。

[2]

f:id:ore2wakaru:20180512084417p:plain

  1. 消したら BodyF02 をインポートして。[New Inherited] で新シーンを作成して、追加。
    今後もアニメを追加したものに入れ替える時の手間を省くため、名前は変えずに "Scene Root" のまま。
  2. AnimationTreePlayer」ノード追加。
    ブレンドアニメをさせるには、インスペクタの設定と、tree の作成を行う必要がある。

[3]

まずはインスペクタ。

f:id:ore2wakaru:20180512090417p:plain

[Master Player][Base Path][Active]、この辺の設定がおかしいとアニメしないから注意。

[4]

次に tree。

f:id:ore2wakaru:20180512091202p:plain


スクリプト

変更したとこだけ。

[1]

変数追加。

f:id:ore2wakaru:20180512093243p:plain

[2]

_ready()関数内で変数に代入。onready varしないで、こっちで揃えた。

f:id:ore2wakaru:20180512093441p:plain

[3]

ブレンド値入れる。場所は方向転換の下。

f:id:ore2wakaru:20180512093813p:plain

  • length()関数はベクトルの長さ(大きさ)をとってくるもの。
  • "my_vel" は加速したり、減速したりさせてるので、"my_spd" で割って最大スピードを出してる。
  • これで、"0~1" のブレンド値を得ようって事。(YouTudeの人、なかなかやるな。)
  • ついでに、カッコ内の "blend2"ノード名。ノードタイプではないから注意。

ただ、落下してる時はすごいスピードになるので、どうなるんだろかな? "1" 越えるんじゃね?

"my_vel.y"の情報は抜いて、

my_blend2 = sqrt(my_vel.x * my_vel.x + my_vel.z * my_vel.z) / my_spd
  • sqrt()関数は平方根を出すの内蔵関数

で、ブレンド値をとった方がいいかもな。坂道とかにも。


※====

床の縁から落として見たら、アニメが破綻するな。"1" を越えた値を入れるとアカンのやな。じゃ、min()関数を使って、

    my_dae_anime_tree.blend2_node_set_amount("blend2", min(my_blend2, 1))

か?

====※


これで歩く「おばさん」出来ちゃった

だけど、やっぱり、歩き始めは常に右足からいってもらいたいんだよな~。その方が自然だろ。今回のこれは、常にループしてるアニメをブレンドしてるから、テキトウなところから開始しちゃうんだよな。どうにかなんねーかな。

それから、あれだ、方向転換の時が。もうちょいイイ見栄えを望むと、ここにも専用のアニメが必要だな。


※====

人間の歩くスピードは、1分で80m~90mらしいので、"my_spd" は変更した。あと、カメラの距離も。このへんは適当に。あと、どっか変えたかもしれないけど、そのへんも適当に。

====※


コード

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 = 3.0                # 距離の最小値を入れる用
var my_cam                              # カメラノード入れる用
var my_cam_dis = 3.0                    # ヘッドとカメラまでの標準距離 (camera distance)

# プレイヤ
var my_dir = Vector3(0, 0, 0)   # キー入力での移動方向 (direction)
var my_vel = Vector3(0, 0, 0)   # 目標移動地点 (velocity)
var my_spd = 1.5                # 移動速度 秒速5m (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値)

# アニメ
var my_dae_anime                # AnimationPlayerノード用
var my_dae_anime_tree           # AnimationTreePlayerノード用
var my_blend2 = 0.0             # Blend値

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")
    # daeボディ
    my_body = get_node("Scene Root")
    my_body_quat_s = Quat(my_body.global_transform.basis)
    # daeアニメ
    my_dae_anime = get_node("Scene Root/AnimationPlayer")
    my_dae_anime_tree = get_node("AnimationTreePlayer")
    my_dae_anime.get_animation("idle01").loop = true
    my_dae_anime.get_animation("walk01").loop = true
    
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_blend2 = my_vel.length() / my_spd
    my_dae_anime_tree.blend2_node_set_amount("blend2", my_blend2)

    # ヘッド動かす
    # 横回転
    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