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

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

Godot キャラコン 07 「加速・減速 と 重力」...根本的に間違ってるかも

移動に関しては、「キーを押すといきなりトップスピード、離すと瞬時にストップ」という仕様になっている。これを、やや滑らかな加速・減速の仕組みを持ったものに修正する。あと、重力も追加する。で、結論的には一応ソレっぽくは動くんだけど、イマイチあってるか間違ってるか微妙なところ・・・。何かモヤっとした部分が・・・。ま、俺の理解できた範囲で書いておく。
f:id:ore2wakaru:20180406195123p:plain


加速・減速

キーを押した瞬間にいきなりトップスピードで動き出すのではなく、徐々に加速して行きトップスピードに近づくようにする。また、キーを離した時もピタッとすぐに止まるのではなく、徐々にスピードが遅くなり停止するようにする。
f:id:ore2wakaru:20180406165029p:plain

L14

  • 加速・減速機能を追加するには、"my_dir"の他にもう一つVector3型の変数が必要なので、追加する。この"my_vel"で移動位置を指定することになる。
  • "my_dir"は完全にキー入力の方向を入れるだけのものにし、move_and_slide()関数では"my_vel"を使用する。

L16~17

  • 加速率と減速率を指定する変数を追加する。
  • 例えば加速の"0.03"とは、現状スピードとトップスピードまでの速度差を3%ずつ縮めて速度を増していくことを表す。
  • 減速は停止に向かって速度を減じていく割合。値を加速のものより大きく設定("0.08")したことで、止まる時の方がシュッと早く止まる。
  • だが、いろいろ勝手に変えてイイ感じの数値にすればいい。
  • ただし加速・減速処理は_physics_process(delta)内で行うので、デフォでは1秒間に60回行うものとして考えておく事。もし[Project Settings]_physics_process(delta)のアクセスタイミングを変えたら、ここの値も変える必要がある。

L18~19

  • 重力用。後で。

f:id:ore2wakaru:20180406165011p:plain

L44

  • "my_dir"は移動キーの入力があれば何らかの数値が入るが、押されてなければ初めにリセットしてあるので(L32)、そのまま"Vector3(0, 0, 0)"が入る。
  • これを利用し、移動キーの入力が無ければ減速処理(L45)に、入力があれば加速処理(L47)に行くようにしている。

多分、本当はキーが押されているかいないかで判断するのではなく、"my_dir""my_vel"内積で分けるんだと思うんだけど、俺はこうしちゃった。これやりたい人は、Vector3クラスのdot()関数を参照。多分、

if my_dir.dot(my_vel) > 0:

とかやって、加速処理と減速処理を分けるんだと思う、多分。(これだと俺のと処理の順番がひっくり返る感じかな?) 違うかも。。。

L45

  • 減速処理。
  • linear_interpolate()Vector3クラスで定義されている関数。Vector3型の変数に、後ろから掛けて使う。働きは、ABの間でtで決めた割合だけ進んだCの値を返すということをする。AからBへ値をちょっとづつ近づけたい時に使う。下図。
    f:id:ore2wakaru:20180406174053p:plain
  • ちなみにfloat型なら、lerp()関数(内蔵関数)でいけるが、Vector3型の時は、linear_interpolate()を使う必要がある。
  • 減速では、現状速度(1秒後の目標移動位置)の"my_vel""(0, 0, 0)"へ向けて、8%ずつ近づけて行き、その結果を"my_vel"に代入し、これを新しい現状速度としている。
    f:id:ore2wakaru:20180417134917p:plain

L47

  • 加速処理。
  • 同様に、"my_vel"はトップスピードである"my_dir * my_spd"(トップスピードで行くと1秒後に到達する位置)に向けて、3%ずつ差を詰めていく。
    f:id:ore2wakaru:20180406183809p:plain
  • 理論的には、いつまで経ってもトップスピードにはならないが、ごく近い数値までは到達する。減速の場合も本当は"0"にはならないハズなんだが、四捨五入かなんかで"0"になるようだ。

f:id:ore2wakaru:20180406184429p:plain

L49~51

  • 重力用。後で。

以下変化なし。
f:id:ore2wakaru:20180406184527p:plain


重力

キネマティックボディーは外部からの力を感じない、そんなボディー。重力も外部の力のひとつであるよって、重力を感じて落下することはない。

だから、キャラクタコントローラにキネマティックボディーを選択した場合は、面倒くさいが自力でスクリプトする必要がある。

L18

  • 重力加速度を設定。別に9.8とかにこだわらなくてイイ。15とか20でも全然イイ。ちょっと大きめの方がゲームではソレっぽいと思う。

L19

  • "my_vel"では、y情報をクリアしてしまっているので、重力用に保持する為の変数が必要。

L49

  • 移動時に保持してあったy情報"my_yyy"に重力加速度を加え、"my_vel.y"に戻す。deltaを掛け忘れない事。
  • これで、重力を感じた雰囲気をmove_and_slide()で出せるようになる。

L51

  • ビックリしたけど、move_and_slide()はリターンタイプがVector3型! ただ「KinematicBody」を動かすだけのvoid型ではないんだなー! ビックリ。
    f:id:ore2wakaru:20180406194032p:plain
  • で、動かした分の情報が返される。それから、これ、代入してるだけに見えるけど、値を返すだけではないから注意。移動もするからね。
  • 今回保持しておきたい情報は、yの動きなので、".y"を掛けて、これだけ"my_yyy"に保存している。
  • こんなめんどくさい事をしないと、無限に重力加速度がガシガシ足されることになってしまう。平行移動してる時はy方向の動きは"0"になるから一旦クリアできるということ。


確認

では、適当に床やスロープを作って確認だ。ソレっぽく動いたかな?

※ーーー

なんか床のコリジョン[New PlaneShape]にすると動かないんだけど、俺だけ? 厚みがないとダメなんかな床。

結局、床や坂道は玉転がしの床みたいにキューブで作ったけど。

ーーー※ 

ではまた。


コピペ用:

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_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方向の大きさ保管用

func _ready():
    my_head = get_node("Spatial_Head")

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
    
    my_yyy = move_and_slide(my_vel).y
        
    # ヘッド動かす
    # 横回転
    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)