2020/11/15(日) [n年前の日記]
#1 [godot] Godot Engineでアタリ属性を設定
Godot Engine 3.2.3 x64 を使って3D表示の簡単なシューティングゲームっぽいものを作る。
今回は各オブジェクトでアタリ判定をするためにアタリ属性を設定してみる。
今回は各オブジェクトでアタリ判定をするためにアタリ属性を設定してみる。
◎ レイヤーとマスクの名前を付ける。 :
Godot Engine は各種アタリ判定(衝突判定)をする際に、レイヤー(Layer)とマスク(Mask)というものを利用して、衝突するものとしないものの関係を設定することができる。
まずは分かりやすくするために、各レイヤーにレイヤー名を設定しておく。
レイヤー名の設定はプロジェクト設定で行える。「プロジェクト」 → 「プロジェクト設定」を選択。
「Layer Names」 → 「3D Physics」を選択。右側に「Layer 1」「Layer 2」と一覧が並んでる。ここでレイヤー名を設定できる。
Layer 1 から順々に、必要になりそうなレイヤー名を入力していく。今回は以下の4つが必要になる。
ついでに、3D Render にもレイヤー名をつけておく。将来的に使うかどうかわからないけど…。
まずは分かりやすくするために、各レイヤーにレイヤー名を設定しておく。
レイヤー名の設定はプロジェクト設定で行える。「プロジェクト」 → 「プロジェクト設定」を選択。
「Layer Names」 → 「3D Physics」を選択。右側に「Layer 1」「Layer 2」と一覧が並んでる。ここでレイヤー名を設定できる。
Layer 1 から順々に、必要になりそうなレイヤー名を入力していく。今回は以下の4つが必要になる。
- player (プレイヤー)
- playerbullets (プレイヤーの弾)
- enemy (敵)
- enemybullets (敵弾)
ついでに、3D Render にもレイヤー名をつけておく。将来的に使うかどうかわからないけど…。
◎ 各シーンのレイヤーとマスクを設定。 :
各シーンのレイヤーとマスクを設定していく。
まずはプレイヤーキャラ。res://assets/Player.tscn を開く。Collision の欄に「Layer」と「Mask」があるので、クリックして有効無効を切り替える。
プレイヤーの弾を設定する。res://assets/PlayerBullet.tscn を開く。
敵を設定する。res://assets/EnemyZako.tscn を開く。
敵弾を設定する。res://assets/EnemyBullet.tscn を開く。
まずはプレイヤーキャラ。res://assets/Player.tscn を開く。Collision の欄に「Layer」と「Mask」があるので、クリックして有効無効を切り替える。
- プレイヤーキャラなので、Layer は一番左の player を有効化。
- Mask でアタリ判定(衝突判定)をする対象を選ぶ。プレイヤーキャラは、「自分には当たらない」「自分の弾にも当たらない」「敵とは当たる」「敵弾にも当たる」ので、左から3番目の enemy と、左から4番目の enemybullets を有効化。
プレイヤーの弾を設定する。res://assets/PlayerBullet.tscn を開く。
- プレイヤーの弾なので、Layer は左から2番目の playerbullets を有効化。
- Mask は、「プレイヤーには当たらない」「自分(達)にも当たらない」「敵には当たる」「敵弾とは当たらない」ので、左から3番目の enemy だけを有効化。
敵を設定する。res://assets/EnemyZako.tscn を開く。
- 敵なので、Layer は左から3番目の enemy を有効化。
- Mask は、「プレイヤーには当たる」「プレイヤーの弾にも当たる」「自分には当たらない」「自分の弾にも当たらない」ので、一番左の player と、左から2番目の playerbullets を有効化。
敵弾を設定する。res://assets/EnemyBullet.tscn を開く。
- 敵弾なので、Layer は左から4番目の enemybullets を有効化。
- Mask は、「プレイヤーには当たる」「プレイヤーの弾には当たらない」「敵には当たらない」「自分(達)にも当たらない」ので、一番左の player だけを有効化。
◎ グループ名を指定。 :
後々、何かと何かが当たった(衝突した)際に、一体何に当たったのかを調べる必要が出てくるかもしれない。その判別をするために、各シーンに「グループ名」をつけておくことにする。
例えば、プレイヤーと、プレイヤーの弾に異なるグループ名をつけておけば、敵が何かに当たった際にグループ名を調べて、それがプレイヤーなのか、プレイヤーの弾なのかを判別することができたりする。
プレイヤーキャラのシーン、Player.tscn を開いて、ノードタブをクリック → グループをクリック。「player」と入力して「追加」。これで、プレイヤーに「player」というグループ名をつけられる。
プレイヤーの弾、PlayerBullet.tscn を開いて、「playerbullets」というグループ名を設定。
敵のシーン、EnemyZako.tscn を開いて、「enemys」というグループ名を設定。
敵弾シーン、EnemyBullet.tscn を開いて、「enemybullets」というグループ名を設定。
これで、アタリ属性を設定できた。
次回はアタリ判定処理を作る。
例えば、プレイヤーと、プレイヤーの弾に異なるグループ名をつけておけば、敵が何かに当たった際にグループ名を調べて、それがプレイヤーなのか、プレイヤーの弾なのかを判別することができたりする。
プレイヤーキャラのシーン、Player.tscn を開いて、ノードタブをクリック → グループをクリック。「player」と入力して「追加」。これで、プレイヤーに「player」というグループ名をつけられる。
プレイヤーの弾、PlayerBullet.tscn を開いて、「playerbullets」というグループ名を設定。
敵のシーン、EnemyZako.tscn を開いて、「enemys」というグループ名を設定。
敵弾シーン、EnemyBullet.tscn を開いて、「enemybullets」というグループ名を設定。
これで、アタリ属性を設定できた。
次回はアタリ判定処理を作る。
[ ツッコむ ]
#2 [godot] Godot Engineでアタリ判定処理を書く
Godot Engine 3.2.3 x64 を使って3D表示の簡単なシューティングゲームっぽいものを作る。
今回は各オブジェクトのアタリ判定処理を作っていく。
今回は各オブジェクトのアタリ判定処理を作っていく。
◎ プレイヤーの弾にアタリ判定処理を追加。 :
プレイヤーの弾にアタリ判定処理を追加する。
プレイヤーの弾は、敵(EnemyZako)とだけ衝突するように Layer と Mask を使って設定してあるけれど、敵は Areaノードをルートノードとして作ってあるので…。
プレイヤーの弾と敵が衝突した際、プレイヤーの弾は「Areaとぶつかったよ」と、area_entered(area:Area) というシグナルを発行してくれる。そのシグナルにメソッドを接続してやれば、プレイヤーの弾と敵が当たった時にそのメソッドが呼ばれるので、そのメソッドの中に敵と当たった時の処理を書いておけばいい。
プレイヤーの弾のシーン、res://assets/PlayerBullet.tscn を開いて、シグナルにメソッドを接続する。ノードタブをクリック → シグナルをクリック。「area_entered(area:Area)」を選択して右クリックして「接続」。
PlayerBullet を選択して「接続」。
スクリプトファイル PlayerBullet.gd の最後に、_on_PlayerBullet_area_entered(area) というメソッドが追記されたので、敵と当たった時の処理を書く。
記述内容は以下になる。
_PlayerBullet.gd
プレイヤーの弾は、敵(EnemyZako)とだけ衝突するように Layer と Mask を使って設定してあるけれど、敵は Areaノードをルートノードとして作ってあるので…。
プレイヤーの弾と敵が衝突した際、プレイヤーの弾は「Areaとぶつかったよ」と、area_entered(area:Area) というシグナルを発行してくれる。そのシグナルにメソッドを接続してやれば、プレイヤーの弾と敵が当たった時にそのメソッドが呼ばれるので、そのメソッドの中に敵と当たった時の処理を書いておけばいい。
プレイヤーの弾のシーン、res://assets/PlayerBullet.tscn を開いて、シグナルにメソッドを接続する。ノードタブをクリック → シグナルをクリック。「area_entered(area:Area)」を選択して右クリックして「接続」。
PlayerBullet を選択して「接続」。
スクリプトファイル PlayerBullet.gd の最後に、_on_PlayerBullet_area_entered(area) というメソッドが追記されたので、敵と当たった時の処理を書く。
記述内容は以下になる。
_PlayerBullet.gd
extends Area var velocity = Vector3() var speed = 90 func _ready(): velocity = Vector3(0, 0, -1) #func _process(delta): # pass func _physics_process(delta): translate(velocity * speed * delta) func _on_Timer_timeout(): queue_free() func _on_PlayerBullet_area_entered(area): if is_queued_for_deletion(): return print("PlayerBullet Hit!") queue_free()
- とりあえず仮の処理として、print() を呼んで当たったことを示すメッセージを出力してみる。
- プレイヤーの弾が敵に当たったら、その場でプレイヤーの弾は消滅してほしいので、自分を消去する queue_free() を呼んでおく。
- is_queued_for_deletion() は、自分に消去処理が要求されているかを調べることができる。もし自分が消去予定になっていたら、何も処理をせずにメソッドを抜けるようにしてある。
◎ 動作確認してみる。 :
Mainシーンを開いてF6キーを押して動作確認してみる。
プレイヤーの弾と敵が当たった時に「PlayerBullet Hit!」というメッセージが出力ウインドウに出力された。ちゃんと当たってることが判別できた。
画面内でも、プレイヤーの弾が敵に当たった時に、プレイヤーの弾がその場で消滅している様子が見て取れる。
動作確認できたので、メッセージを出力する print() は、頭に「# 」を追加してコメントアウトしておく。
プレイヤーの弾と敵が当たった時に「PlayerBullet Hit!」というメッセージが出力ウインドウに出力された。ちゃんと当たってることが判別できた。
画面内でも、プレイヤーの弾が敵に当たった時に、プレイヤーの弾がその場で消滅している様子が見て取れる。
動作確認できたので、メッセージを出力する print() は、頭に「# 」を追加してコメントアウトしておく。
# print("PlayerBullet Hit!")
◎ 敵弾にアタリ判定処理を追加。 :
敵弾シーン EnemyBullet.tscn にもアタリ判定処理を追加していく。
敵弾は Layer と Mask を使ってプレイヤーとだけ当たるように設定してある。そしてプレイヤーキャラは KinematicBody をルートノードにして作ってあるので…。
敵弾 EnemyBullet (Area) は、プレイヤー Player (KinematicBody) と当たった際に、body_entered(body: Node) というシグナルを発行する。(area_entered()ではなくて、body_entered()。相手が Area ではなくて KinematicBody なので。)
このシグナルにメソッドを接続してやれば、敵弾がプレイヤーと当たった時にそのメソッドが呼ばれるので、そのメソッドの中にプレイヤーと当たった時の処理を書いておけばいい。
敵弾のシーン、res://assets/EnemyBullet.tscn を開いて、シグナルにメソッドを接続する。ノードタブをクリック → シグナルをクリック。「body_entered(body: Node) を選択して右クリックして「接続」。EnemyBullet を選択して「接続」。
スクリプトファイル EnemyBullet.gd の最後に、_on_EnemyBullet_body_entered(body) というメソッドが追加された。このメソッドにプレイヤーと当たった時の処理を書く。
内容は以下。
_EnemyBullet.gd
PlayerBullet とほとんど同じ処理になっている。
動作確認。Mainシーンを開いてF6キーを押す。敵弾がプレイヤーと当たるたびに、出力ウインドウにメッセージが表示された。ちゃんとアタリ判定ができている。
動作確認できたので、print() をコメントアウト。
敵弾は Layer と Mask を使ってプレイヤーとだけ当たるように設定してある。そしてプレイヤーキャラは KinematicBody をルートノードにして作ってあるので…。
敵弾 EnemyBullet (Area) は、プレイヤー Player (KinematicBody) と当たった際に、body_entered(body: Node) というシグナルを発行する。(area_entered()ではなくて、body_entered()。相手が Area ではなくて KinematicBody なので。)
このシグナルにメソッドを接続してやれば、敵弾がプレイヤーと当たった時にそのメソッドが呼ばれるので、そのメソッドの中にプレイヤーと当たった時の処理を書いておけばいい。
敵弾のシーン、res://assets/EnemyBullet.tscn を開いて、シグナルにメソッドを接続する。ノードタブをクリック → シグナルをクリック。「body_entered(body: Node) を選択して右クリックして「接続」。EnemyBullet を選択して「接続」。
スクリプトファイル EnemyBullet.gd の最後に、_on_EnemyBullet_body_entered(body) というメソッドが追加された。このメソッドにプレイヤーと当たった時の処理を書く。
内容は以下。
_EnemyBullet.gd
extends Area var velocity = Vector3() var direction = 0 var speed = 3 func _ready(): pass # Replace with function body. #func _process(delta): # pass func _physics_process(delta): translate(velocity * speed * 60 * delta) func set_dir_and_speed(angle, spd): direction = angle speed = spd var x = cos(deg2rad(angle)) var z = sin(deg2rad(angle)) velocity = Vector3(x, 0, z) func _on_KillTimer_timeout(): queue_free() func _on_EnemyBullet_body_entered(body): if is_queued_for_deletion(): return print("EnemyBullet Hit!") queue_free()
PlayerBullet とほとんど同じ処理になっている。
動作確認。Mainシーンを開いてF6キーを押す。敵弾がプレイヤーと当たるたびに、出力ウインドウにメッセージが表示された。ちゃんとアタリ判定ができている。
動作確認できたので、print() をコメントアウト。
# print("EnemyBullet Hit!")
◎ 敵にアタリ判定処理を追加。 :
敵にアタリ判定処理を追加していく。
敵は、Layer と Mask を使って、プレイヤー、及び、プレイヤーの弾と当たるように設定されている。そして、プレイヤーは KinematicBody、プレイヤーの弾は Area をルートノードにして作ってあるので…。
敵のシーン、res://assets/EnemyZako.tscn を開いて、area_entered() シグナルと、body_entered() シグナルにメソッドを接続してやる。ノードタブをクリック → シグナルをクリック。それぞれのシグナルを右クリックして「接続」。EnemyZako を選択して「接続」。
スクリプトファイル EnemyZako.gd に、_on_EnemyZako_area_entered(area) と、_on_EnemyZako_body_entered(body) が追記された。
それぞれのメソッドに処理を書く。
内容は以下。
_EnemyZako.gd
動作確認してみる。Mainシーンを開いてF6キーを押す。
プレイヤーを操作して敵と当ててみたり、プレイヤーの弾を敵に当ててみたりする。出力ウインドウに、当たってる旨を伝えるメッセージが出力された。
これで一応、アタリ判定はできた。
さて、プレイヤーに敵弾が当たったらプレイヤーのHP(体力?)を減らしたり、敵にプレイヤーの弾が当たったら敵のHPを減らしたりしたい。次回は各HPを画面に表示するために、HUD(Head-Up Display)シーンを作成する。
敵は、Layer と Mask を使って、プレイヤー、及び、プレイヤーの弾と当たるように設定されている。そして、プレイヤーは KinematicBody、プレイヤーの弾は Area をルートノードにして作ってあるので…。
- body_entered(body: Node) シグナルにメソッドを接続すれば、そのメソッドにプレイヤーと当たった時の処理を書ける。
- area_entered(area: Area) シグナルにメソッドを接続すれば、そのメソッドにプレイヤーの弾と当たった時の処理を書ける。
敵のシーン、res://assets/EnemyZako.tscn を開いて、area_entered() シグナルと、body_entered() シグナルにメソッドを接続してやる。ノードタブをクリック → シグナルをクリック。それぞれのシグナルを右クリックして「接続」。EnemyZako を選択して「接続」。
スクリプトファイル EnemyZako.gd に、_on_EnemyZako_area_entered(area) と、_on_EnemyZako_body_entered(body) が追記された。
それぞれのメソッドに処理を書く。
内容は以下。
_EnemyZako.gd
extends Area export (PackedScene) var enemybullet var base_pos = Vector3() var angle = 0 export var move_w = 22 export var move_h = 12 var bullets func _ready(): base_pos = translation angle = 0 bullets = get_tree().root.get_node("Main/EnemyBullets") #func _process(delta): # pass func _physics_process(delta): move(delta) func move(delta): angle += delta translation.x = base_pos.x + move_w * cos(deg2rad(angle * 90)) translation.z = base_pos.z + move_h * sin(deg2rad(angle * 70)) func _on_ShotTimer_timeout(): var angle_add = 12 var angle = 90 - angle_add * 3 var spd = 0.2 for i in range(7): _shot(translation, angle, spd) _shot(translation, angle, spd * 0.8) angle += angle_add func _shot(pos, angle, spd): var bullet = enemybullet.instance() bullet.translation = pos bullet.set_dir_and_speed(angle, spd) bullets.add_child(bullet) func _on_EnemyZako_area_entered(area): if is_queued_for_deletion(): return print("playerbullet hit the enemy") func _on_EnemyZako_body_entered(body): if is_queued_for_deletion(): return print("player hit the enemy")
動作確認してみる。Mainシーンを開いてF6キーを押す。
プレイヤーを操作して敵と当ててみたり、プレイヤーの弾を敵に当ててみたりする。出力ウインドウに、当たってる旨を伝えるメッセージが出力された。
これで一応、アタリ判定はできた。
さて、プレイヤーに敵弾が当たったらプレイヤーのHP(体力?)を減らしたり、敵にプレイヤーの弾が当たったら敵のHPを減らしたりしたい。次回は各HPを画面に表示するために、HUD(Head-Up Display)シーンを作成する。
[ ツッコむ ]
以上、1 日分です。