ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
Godot_v4.0-rc1_win64.exe.zipを使用しています。
目的
発射できるようになったので、一定距離に敵を見つけたら向かうようにしたい。
ベースプロジェクト
下記で作成したプロジェクトをベースに機能追加をします。
【Godot 4.0】スマホ3Dゲームを作るための勉強 その8 発射する
https://qiita.com/FootInGlow/items/dd0f63bf7a9cd92801b7
敵をつくる
CharacterBody3Dで敵を作成します。
global.tscnを開いて、シーンのglobalを右クリックして子ノードを追加します。
CharacterBody3Dを追加して、Enemyに名称を変更します。
- Enemy
Node3/Transform/yに0.5mを設定します。 - MeshInstance3Dに円柱(シリンダー)型を設定します。
インスペクタ内のMeshInstance3D/Meshを新規CylinderMeshに設定します。 - CollisionShape3Dに衝突形状を設定
MeshInstance3Dと同じ形にします。
インスペクタ内のCollisionShape3D/Shapeに新規CylinderShape3Dを設定 - DirectionalLight3D
敵が暗いので、Node3D/Transform/rotation/xを-10°に設定します。
実行します。
発射した弾丸が敵にぶつかるとよけていきます。
お互いCharacterBody3Dで衝突形状を設定してmove_and_slide()で動かしているので当然ですが、ある程度近づくと敵に向かうようにしたいです。
衝突をCollision Layer/Maskでコントロールする
今はディフォルトの設定をしているので、すべてのCharacterBody3D/StaticBosy3D(RigidBody3D)が衝突し、Area3Dがあれば衝突を検知する状態ですが、ノードをグループ分けして適切に衝突制御、検知をするようにしたいと思います。
Collision Layerを定義する
衝突・検知の観点でノードのグループを定義します。
プロジェクトメニューのプロジェクト設定でLayer Names/3D物理を開きます。
Layer1~4までLayer名を入力します。
Layer名 | 対象 |
---|---|
FloorAndObstacles | 床(と先々追加する障害物) |
Player | プレイヤー |
Enemy | 敵 |
Bullet | プレイヤーが発射したもの |
Player、StaticBody3D(床)、Enemy、BulletにCollision LayerとMaskを設定する
衝突・検知対象のノード、Player、StaticBody3D(床)、Enemy、BulletにLayerとして種別を設定します。
自身が属するLayer(グループ)を設定します。
Maskには自身が衝突/検出する対象を設定します。
Layerごとに衝突・検出したいものをまとめました。
Layerに設定するもの | Maskに設定するもの(衝突・検出したいもの) |
---|---|
FloorAndObstacles | なし(検出されますが、自ら衝突/検出したいということはない) |
Player | FloorAndObstacles(そのうち左右に壁を作って移動制限をしたい) |
Enemy | なし(今のところ移動予定はないので) |
Bullet | FloorAndObstacles、Enemy、Bullet※に衝突してほしい。検出はしない ※Bullet同士も重ならないようにしたい |
今のところPlayerは誰からも衝突、検出されませんが一応設定してあります。
シーンで対象のノードを選択して、インスペクタで設定します。
LayerもMaskも対象の番号をチェックしますが、右側の「...」をクリックすると先ほどプロジェクト設定で設定した名称が表示されるので、こちらの方が設定しやすいと思います。
-
StaticBody3D(床)
インスペクタのCollisionObject3D/Collisionで設定します。
FloorAndObstaclesのLayerは1番です。Maskは未選択。
-
Player
インスペクタのCharacterBody3D/Collisionで設定します。
PlayerはLayerは2です。Maskは1:FloorAndObstaclesを選択しています。
-
Enemy
インスペクタのCharacterBody3D/Collisionで設定します。
EnemyはLayer3番です。Maskは未選択。
-
Bullet
インスペクタのCharacterBody3D/Collisionで設定します。
BulletはLayer4番です。
Bulletは1:FloorAndObstacles、3:Enemy、4:Bulletと衝突します。
ある程度近づいたら敵に向かうようにしたい
Enemyに円を設定する
Bulletを賢くして、ある程度近づいたらEnemyに向かうようにします。
Area3Dをセンサーとして使用します。
global.tscnのEnemyを右クリックして、Area3Dを追加します。
名称を「BulletSensor」に変更します。
BulletSensorを右クリックしてCollisionShape3Dを追加します。
Enemyの半径3m以内に近づいたらBulletを検出したいので、CollisionShape3Dに3mの球を設定します。
BulletSensorの子ノードのCollisionShape3Dを選択します。
インスペクタ内のCollisionShape3D/Shapeに新規SphereShape3Dを設定。
SphereShape3Dをクリックして、Raduisを3.0mにします。
3D表示の左上を「上面図」を選択すると、Emnemyのいる位置の周囲に円が表示されています。
またデバッグメニューの「コリジョン形状を表示」にチェックを入れてから実行しても設定した円が確認できます。
Enemyの円でBulletを検出する
EnemyがBulletを検出できるようにします。
BulletSensorを選択してCollision LayerとMaskを設定します。
BulletSensorは誰からも衝突/検出される必要がないので、Layerは未選択です。
Bulletを検出したいので、Maskは4を選択します。
シグナルを接続するためにEnemyノードにスクリプトが必要です。
Enemyを右クリックしてスクリプトをアタッチします。
extendsの1行のみ残してください。
extends CharacterBody3D
あらためてBulletSensorを選択して、(インスペクタの横の)ノードタブのシグナルを選択します。
PhysicsBody3Dノードを検出したいので、body_entered(body:Node3D)を右クリックして接続します。
CharacterBody3DやRigidBody3Dなど検出したい場合は、body_xxx シグナルです。
Area3Dを検出したい場合は、area_xxx シグナルを使用するようです。
BulletSensorがBulletを検知すると、Enemyのスクリプトの_on_bullet_sensor_body_enteredが呼ばれるようになりました。print分を追加して、実行して確認します。
extends CharacterBody3D
func _on_bullet_sensor_body_entered(body):
print(body)
実行後、シーンのリモートをクリックしてPlayerなどが見えるようにします。
Bulletを発射すると、1発目はBulletという名前でシーンインスタンスが追加されます。2発目以降は、@Bullet@2、@Bullet@3...となります。
BulletがBulletSensorの円の中にはいると、コンソールにノード名がprintされます。
_on_bullet_sensor_body_enteredシグナルで通知されている「body」のはBulletノードインスタンス本体なので、Bulletのメンバーにアクセスしたり、メソッドを呼ぶことができます。
Enemyの位置をBulletに通知する
Enemyのスクリプトをこのように変更して、Bulletのdiscover_enemyメソッドを呼びEnemyの位置を通知します。
extends CharacterBody3D
func _on_bullet_sensor_body_entered(body):
body.discover_enemy(transform.origin)
bullet.gdスクリプトを開いて、discover_enemyメソッドを実装しましょう。
Enemyの位置を保存するメンバ変数を宣言して、discover_enemyメソッドが呼ばれたときに保存するようにします。
var m_v3_enemy_pos = null
func discover_enemy(enemy_pos):
m_v3_enemy_pos = enemy_pos
つづいて、Enemyを見つけるまでは今まで通り直進、Enemnyを見つけた場合はEnemyに向かうように修正します。
extends CharacterBody3D
@export var m_v_dir = Vector3.FORWARD
@export var m_d_speed_mps = 2.0
var m_v3_enemy_pos = null
func _physics_process(delta):
if m_v3_enemy_pos:
m_v_dir = m_v3_enemy_pos - transform.origin
else:
m_v_dir = Vector3.FORWARD
velocity = m_v_dir.normalized() * m_d_speed_mps
move_and_slide()
func discover_enemy(enemy_pos):
m_v3_enemy_pos = enemy_pos
func _on_visible_on_screen_notifier_3d_screen_exited():
queue_free()
メンバ変数初期値のVector3(0, 0, -1)をVector3.FORWARDに修正
-
@export var m_v_dir = Vector3.FORWARD
Vector3に定義されている定数に変更しました
値としてはVector3(0, 0, -1)なので同じです。
func _physics_process(delta):の修正
進む方向を示すm_v_dir(Vector3)に設定する方向はEnemyを見つけた場合はEmenyの方向を設定するようにしました
- if m_v3_enemy_pos:
m_v3_enemy_posが設定されている場合 - m_v_dir = m_v3_enemy_pos - transform.origin
目標位置から現在位置を引くと、現在位置から目標位置へ向かう方向になります。 - else:
m_v3_enemy_posが設定されていない場合 - m_v_dir = Vector3.FORWARD
今まで通り前進(Z軸マイナス方向)します
実行します。
BulletがEnemyに近づくと、Enemyに向きを変えて、最終的にくっつくようになりました。
以上