ゲーム制作勉強中!あこがれだったプログラマーに今からなろう!

昔、あこがれていたプログラマー。今からでも勉強してみようと思い立ち、チャレンジ開始! 勉強メモや、悪戦苦闘な日々の記録です。

Unity経験者のGodotの遊び方:Body系ノードの移動方法と衝突検知方法について


Unityで作成したColorChangePanicをGodotへ移植する作業がだいぶ進んで、簡単なテストができるレベルまでになったのですが
youtu.be
敵増殖時の生成が、Unityは元の敵に吸い付くように生成されているのに対して、Godotではちょっと反発して生成されているのが気になりました。

Object(GodotではNode)の移動手段も、衝突判定方法も、UnityGodotでは異なっているのですが
まだ習い始めということもあって、今はよくわからないままに、弾も敵もRigidBody2DというNodeルートノードにして作成していました。

どういう理屈で生成の挙動がUnityGodotで変わってしまっているのか判りませんが、いい機会なのでルートノードになりやすい、いくつかのNodeについて、それぞれの移動手段と、衝突判定方法について纏めておこうと思います。


Unityの場合はGameObjectを移動させる方法として、

transform.positionを直接変更する。
transform.translateで移動量を指定して、移動させる。
Rigidbodyコンポーネントをアタッチして、力を加えて移動させる。

などがあり、①と②の方法であれば、どのGameObjectでも使うことができます。
衝突が発生した場合は、イベント関数のOnCollisionEnterなどが呼び出されます。
そのため、Unityでは比較的容易にObjectを移動させて、衝突判定をとることができます。

ところがGodotの場合は、①のpositionを直接変更する移動方法であれば、どのNodeでもできる様ですが、
②と③の様な手段となると、そうもいかない様で、移動させるメソッド・衝突を検知する方法ともNodeによって異なるみたいです。

zenn.dev
こちらのサイト様でかなり詳しく説明されていましたので、実際にNodeスクリプトを作成して実験してみようと思います。

RigidBody2Dノード

まずはUnityで馴染みが深いRigidBodyです。

このノードUnityの時と同じ様に、add_forceで力を加えたり、velocityを直接操作したりするという移動方法のほか、move_and_collide()メソッドでも移動させる事ができます。

衝突判定については、RigidBodyシグナルBody_enteredが使えるので、シグナルから衝突判定することがきます。
スクリプトで衝突判定をとる事もできますが、この場合、移動方法によって判定方法が変わる様です。

まずmove_and_collide()メソッドを使う場合です。

extends RigidBody2D

var speed :int = 160
var coll_item
var Obj_A
var Obj_B
var message_label

func _ready():
    Obj_A=get_node("../ObjA").get_class()
    $"../ObjA/CollisionShape2D/ColorRect/Label".text="Obj-A \n"+str(Obj_A)
    Obj_B=get_node("../ObjB").get_class()
    $"../ObjB/CollisionShape2D/ColorRect/Label".text="Obj-B \n"+str(Obj_B)
    message_label=$"../Label"

func _physics_process(delta):
    coll_item=move_and_collide(Vector2(1,0)*speed*delta)#右にspeed*delta移動する。

    if coll_item!=null:
        message_label.text="Obj-Aがぶつかったのは、"
        message_label.text += str(coll_item.get_collider().get_class()) #衝突した相手の型を表示
        message_label.text += "の" + str(coll_item.get_collider().name) #衝突した相手の名前を表示

move_and_・・・というのは、Body系ノードを移動させるメソッドで、
move_and_collide() は移動中に他のcolliderにぶつかった場合、動きを止めて「衝突情報」を返してくれます。
そして、この移動方法は、衝突した相手に物理的な運動効果を与えない移動になる様です。

この「衝突情報」から.get_colliderで相手のcolliderを取得し、更にget_collider.get_classノードの型を、get_collider.nameで名前を確認する事ができます。
youtu.be

次に、velocityを直接操作する場合です。
add_forceの場合は、加えられた力を物理計算した結果として、velocityで移動していると思いますので、そちらは省略します。)

func _physics_process(delta):
    linear_velocity=Vector2(1,0)*speed
    
    coll_item=get_colliding_bodies()

    if coll_item!=null:
        for body in coll_item:
            message_label.text="Obj-Aがぶつかったのは、"
            message_label.text += str(body.get_class()) #衝突した相手の型を表示
            message_label.text += "の" + str(body.name) #衝突した相手の名前を表示

velocityの移動の場合は、move_and_collide()の様に、移動中colliderにぶつかると止まるというわけではないため、同じフレーム内で複数の衝突が発生する可能性があります。
get_colliding_bodies()を使用すると、そのフレームで衝突があった場合、相手のcollider全てが配列変数に取得されます。

ちょっと分かりにくかったのですが、move_and_collide()での取得は「衝突情報」でしたが、get_colliding_bodies()では相手の「collider」が取得されるため、型や名前を取得する場合には、.get_colliderが不要でしたので、注意が必要です。

そしてvelocityでの移動は、衝突した相手に物理的な運動効果を与える移動になる様です。
youtu.be
相手がRigidBodyの場合、この様に相手を移動させてしまいます。
ただ、相手がCharacterBodyStaticBodyの場合は、物理挙動をしているノードでは無いからなのかvelocityでの移動でも、相手を移動させてしまうことはない様です。


CharacterBody2Dノード

次は、CharacterBodyです。
CharacterBodyは、RigidBodyで使用できたmove_and_collide()メソッドの他、move_and_slide()メソッドも使用できるようになります。

move_and_collide()は移動中にcolliderにぶつかると、そこで動きを止めますが
move_and_slide()は移動中にcolliderにぶつかっても、動きを止めるのではなく、あらかじめ指定しておいた角度以下なら障害物に沿ってスライドするなど、まさにゲームキャラクターの移動に使うことを前提にしたような便利なメソッドです。
そして、CharacterBody2Dノードは、move_and_collide()move_and_slide()の2つのメソッドを移動に使用することができます。

衝突の検知については、移動手段のバリエーションに反してか、シグナルBody_enteredが用意されていないため、シグナルで衝突を検知することができない様です。


移動の実験ですがmove_and_collideの使い方はRigidBodyと同じ様でしたので、move_and_slideを実験してみます。

extends CharacterBody2D # ← ここが変わっている事に注意。

var speed :int = 160
var coll_item
var Obj_A
var Obj_B
var message_label

func _ready():
    Obj_A=get_node("../ObjA").get_class()
    $"../ObjA/CollisionShape2D/ColorRect/Label".text="Obj-A \n"+str(Obj_A)
    Obj_B=get_node("../ObjB").get_class()
    $"../ObjB/CollisionShape2D/ColorRect/Label".text="Obj-B \n"+str(Obj_B)
    message_label=$"../Label"

func _physics_process(delta):
    if coll_item==null:
        #ぶつかるまでは右に毎フレームspeedピクセルの移動を定義
        velocity=Vector2(1,0)*speed
    else:
        #ぶつかったら左に毎フレームspeedピクセルの移動を定義
        velocity=Vector2(-1,0)*speed  
    
    move_and_slide()#velocityの定義にしたがって移動。
    
    if get_slide_collision_count()!=0: #このフレームでの衝突数を確認
        for i in range(get_slide_collision_count()): #衝突数でforを回す
            coll_item = get_slide_collision(i) #i番目の衝突情報を格納
            
            #衝突情報coll_itemからget_collider()で衝突したNodeの情報を取得して、
            #get_class()でNodeの型を取得
            message_label.text="Obj-Aがぶつかったのは、"
            message_label.text += str(coll_item.get_collider().get_class()) #衝突した相手の型を表示
            message_label.text += "の" + str(coll_item.get_collider().name) #衝突した相手の名前を表示

move_and_slide()colliderにぶつかっても移動を止めるわけではないため、velocityの移動と同じように、同じフレーム内に複数の衝突が発生する可能性があり、「衝突情報」は配列変数として格納されます。
しかし、velocityとは異なり、取得されるのは「collider」ではなく、「衝突情報」なのでcolliderを取得するためにget_colliderが必要になります。

そして、move_and_slide()の移動はvelocityに従った移動のように見えますが、衝突した相手に力を及ぼす事がない移動になる様です。

youtu.be

Area2Dノード

最後は、Area2Dです。
StaticBodyというものもあるのですが、これは地面のような動かないObjectを作ることを想定しているようで、その使用用途からあまり移動させる事はないのでは?と思いましたので省略します。
一応、StaticBodymove_and_collide()メソッドを使うことができます。
そして、シグナルBody_enteredが用意されておらず、この辺りはRigidBodyと同じイメージなのかもしれません。

最後のArea2Dは、今までのNodeと異なり、move_and_・・・メソッドやvelocityのような移動手段は用意されていないようで、移動させたい場合はpositionを直接操作する様です。

移動手段が限られていますが、衝突判定としてはシグナルbody_enteredだけではなく、area_enteredというArea同士の衝突を検知する、ちょっと変わったシグナルが用意されています。

公式ドキュメントを読むと、Areaは例えば、取得できるコインのようなObjectを作成したりするのに向いている様です。
UnityでいうところのOnTriggerEnterの様なものなのかもしれません。
あとは、今回のテーマとは変わりますが、エリア内に特殊は効果を設定したい場合などにも利用できるみたいです。

youtu.be


参考サイト
zenn.dev
zenn.dev