ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
Godot_v4.0-rc1_win64.exe.zipを使用しています。
目的
敵も攻撃するようにしました
ベースプロジェクト
下記で作成したプロジェクトをベースに機能追加をします。
【Godot 4.0】スマホ3Dゲームを作るための勉強 その10 敵を増やしました。またBullet vs Enemyの処理を実装しました。
https://qiita.com/FootInGlow/items/195236f04bba83cad129
github(Godotのプロジェクトマネージャーからインポートして利用できます)
https://qiita.com/FootInGlow/items/195236f04bba83cad129
bulletシーンを複製してEnemyBulletシーンを作成します。
bulletシーンを複製しましょう。
方法1:bullet.tscnを開いて、シーンメニューから「名前を付けてシーンを保存...」
方法2:ファイルシステム上でbullet.tscnを右クリックから「複製」する
複製して「enemybullet.tscn」に名称変更します。
EnemyBulletシーンを変更する
enemybullet.tscnを開いて、手前方向にまっすぐ進む、画面外に出たら消えるというシンプルなものに修正します。
-
ルートノードのBulletをEnemyBulletに名称変更する
-
EnemyBulletのCollision Layer/Maskのチェックをいったんはずします。
後できちんと設定します。
-
EnemyBulletを右クリックして、「スクリプトをアタッチする」
Enemybullet.gdという名前でスクリプトを作成します。 -
スクリプトを編集する
下記のように編集します。
extends CharacterBody3D
@export var m_v_dir = Vector3.BACK
@export var m_d_speed_mps = 1.0
func _physics_process(delta):
velocity = m_v_dir.normalized() * m_d_speed_mps
move_and_slide()
func _on_visible_on_screen_notifier_3d_screen_exited():
queue_free()
-
@export var m_v_dir = Vector3.BACK
Player方向に向かうようにしたいので、FORWARDではなくBACK定数を使用します。
Enemyから発射する
EnemyからEnemyBulletを発射します。
6秒おきに3発づつ発射するようにします。3発のEnemyBulletは0.5秒間隔で発射たいと思います。
Timerノードを2つ追加します
enemy.tscnを開いて、Timerを2つ追加します。名称を「Timer_fire」と「Timer_short」に変更しましょう。
Timer_fire
発射動作間隔としてWait Timeを6sに設定します。また、Autostartをオンにしましょう。
Timer_short
Wait Timeを0.5sにします。Timer_shortはGDScriptからタイマーを開始して、3回timeoutしたら(3回発射したら)停止するように実装しますので、Autostartは初期値のままオフです。
timeoutシグナルをEnemyに接続します
Timer_fireを選択して、インスペクタの横のノードタブのシグナルを開いて、timeoutシグナルを接続します。
接続先はEnemyです。
Timer_shortについても同様にtimeoutシグナルをEnemyに接続します
GDScriptが下記のようになります。
_on_timer_fire_timeoutメソッドと、_on_timer_short_timeoutメソッドが追加されました。
extends CharacterBody3D
@export var m_attack_power_ps = 1.0
@export var m_hp = 10
func _physics_process(delta):
if m_hp < 0.0:
queue_free()
func _on_bullet_sensor_body_entered(body):
body.discover_enemy(transform.origin)
func _on_timer_fire_timeout():
pass # Replace with function body.
func _on_timer_short_timeout():
pass # Replace with function body.
発射ポイントを追加します。
Enemyノードに、Marker3Dを追加します。
Node3D/Transform/position/Zを0.7にします。
GDScriptにPackedSceneを@exportして、enemybullet.tscnを登録します。
enemy.gdにメンバ変数を追加します。
@export var m_scn_enemybullet : PackedScene
enemy.tscnを開いて、Enemyノードを選択します。
ファイルシステムのenemybullet.tscnをインスペクタのEnemy.gd/M Scn EnemyBulletの<empty>の上にドラッグアンドドロップします。
登録されました。
enemy.gdを修正して、発射する
extends CharacterBody3D
@export var m_attack_power_ps = 1.0
@export var m_hp = 10
@export var m_scn_enemybullet : PackedScene
@export var m_i_fire_num = 3
var m_i_remain_fire_cnt = 0
func _physics_process(delta):
if m_hp < 0.0:
queue_free()
func _on_bullet_sensor_body_entered(body):
body.discover_enemy(transform.origin)
func _on_timer_fire_timeout():
# 発射する段数を設定
m_i_remain_fire_cnt = m_i_fire_num
# タイマースタート
$Timer_short.start()
func _on_timer_short_timeout():
# EnemyBulletを発射する
var scn_enemybullet = m_scn_enemybullet.instantiate()
scn_enemybullet.transform.origin = $Marker3D.global_transform.origin
add_sibling(scn_enemybullet)
# 発射残数を減算して、0になったらタイマーを停止する
m_i_remain_fire_cnt -= 1
if m_i_remain_fire_cnt <= 0:
$Timer_short.stop()
メンバ変数の追加
-
@export var m_i_fire_num = 3
一度に発射する弾数をパラメータして定義 - var m_i_remain_fire_cnt = 0
発射残数をカウントするためのメンバ変数
func _on_timer_fire_timeout():Timer_fireのタイムアウト処理
初期値6秒周期の発射開始のタイミングです。
- m_i_remain_fire_cnt = m_i_fire_num
発射する弾数を初期化する - $Timer_short.start()
発射用ショートタイマーを開始する
func _on_timer_short_timeout():Time_shortのタイムアウト処理
0.5間隔で3発発射するためのタイムアウト処理です。3発発射したらタイマーを停止します。
- var scn_enemybullet = m_scn_enemybullet.instantiate()
enemybullet.tscnシーンのインスタンスを生成します。 - scn_enemybullet.transform.origin = $Marker3D.global_transform.origin
位置を設定します。$Marker3DはEnemyの子ノードのためグローバル座標系の位置を取得します - add_sibling(scn_enemybullet)
Enemyと同じ階層にインスタンスを追加します。 - m_i_remain_fire_cnt -= 1
弾数を1減算します。 - if m_i_remain_fire_cnt <= 0:
0(以下)になった場合、予定弾数を発射し終わったということです。 - $Timer_short.stop()
ショートタイマーを停止します。
実行しましょう。
BulletとEnemyBulletが相殺するようにします。
BulletでEnemyBulletを相殺するようにします。
EnemyBulletのCollision Layer/Maskを設定しましょう。
enemybullet.tscnを開いて、EnemyBulletノードを選択します。
インスペクタのCharacterBody3D/CollisionのLayerの3:Enemyをチェックします。
CollisionのMaskは1~4まですべてチェックします。
EnemyBulletにEnemyGroup設定しましょう。
Bulletは「EnemyGroup」を持つノードを攻撃しますので、EnemyBulletにもグループを追加します。
EnemyBulletノードを選択した状態で、ノード/グループを選択します。
EnemyGroupを入力して、追加ボタンを押下します。
追加されました。
EnemyBulletノードの横にも四角の中に●があるアイコンが追加されました。
Bulletは衝突したノードのうちEnemyBulletをグループに持つノードとバトルをします。
enemybullet.gdを開いて攻撃力とHPを設定します。
extends CharacterBody3D
@export var m_attack_power_ps = 0.5
@export var m_hp = 0.5
@export var m_v_dir = Vector3.BACK
@export var m_d_speed_mps = 1.0
func _physics_process(delta):
velocity = m_v_dir.normalized() * m_d_speed_mps
move_and_slide()
if m_hp < 0.0:
queue_free()
func _on_visible_on_screen_notifier_3d_screen_exited():
queue_free()
攻撃力とHPを定義
少し弱めに定義しました。
func _physics_process(delta):でHPがなくなったら削除する
HPが0以下になった場合、削除します。
- if m_hp < 0.0:
- queue_free()
実行しましょう。
BulletとEnemyBulletはぶつかったらお互い避けてしまいますね...
Bulletはバトル中はバトル相手に向かうように修正する
Playerを守りたいので、Bulletはバトル相手を追いかけるようにします。
bullet.gdを開いて修正しましょう。
修正内容としては下記になります。
-
半径3mの円に入ったときに検知して通知されたEnemyの位置の保存先はm_v3_sensing_posに変更する
-
m_v3_enemy_posは敵目標位置に変更します。
優先順位を決めて目標位置を設定します。
1番目:現在戦闘中のEnemyもしくはEnemyBullet
2番目:半径3mの円に入ったときに検知して通知されたEnemyの位置
3番目:なし(前進)
下記が修正後のスクリプトになります。
extends CharacterBody3D
@export var m_v_dir = Vector3.FORWARD
@export var m_d_speed_mps = 2.0
@export var m_attack_power_ps = 1.0
@export var m_hp = 1
var m_v3_sensing_pos = null
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()
# バトル判定
var battle_pos = null
for index in range(get_slide_collision_count()):
var collision = get_slide_collision(index)
if (collision.get_collider() == null):
continue
if collision.get_collider().is_in_group("EnemyGroup"):
var enemy = collision.get_collider()
# バトル中に相手の位置を保持
battle_pos = enemy.transform.origin
# Bulletの攻撃
enemy.m_hp -= m_attack_power_ps * delta
# Enemyの攻撃
m_hp -= enemy.m_attack_power_ps * delta
# HPが0になったら消える
if m_hp < 0:
queue_free()
# バトル中の相手、センシングした敵の順に目標位置を設定する
if battle_pos:
m_v3_enemy_pos = battle_pos
elif m_v3_sensing_pos:
if (m_v3_sensing_pos - transform.origin).length() < 0.1:
m_v3_sensing_pos = null
m_v3_enemy_pos = m_v3_sensing_pos
else:
m_v3_enemy_pos = null
func discover_enemy(enemy_pos):
m_v3_sensing_pos = enemy_pos
func _on_visible_on_screen_notifier_3d_screen_exited():
queue_free()
スクリプトが長くなってきたので部分ごとに変更点を説明します。
メンバ変数の追加
m_v3_sensing_posを追加しました。
var m_v3_sensing_pos = null
var m_v3_enemy_pos = null
Enemyから通知された位置をクリアする処理を削除
Enemyから通知された位置をクリアする処理がelse:の上にありましたが、削除しました。(後の方に移動しました)
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()
バトル中の相手の位置を保存
battle_posを追加して、バトル中の相手の位置を保存します。
# バトル判定
var battle_pos = null
for index in range(get_slide_collision_count()):
var collision = get_slide_collision(index)
if (collision.get_collider() == null):
continue
if collision.get_collider().is_in_group("EnemyGroup"):
var enemy = collision.get_collider()
# バトル中に相手の位置を保持
battle_pos = enemy.transform.origin
目標位置を選択
目標位置を選択してm_v3_enemy_posに設定する処理を追加しました。
バトル相手がいる場合はバトル相手の位置をm_v3_enemy_posに保存します。
次に、m_v3_sensing_posが設定されている場合は、半径3mの円で検知した敵の位置を保存します。
ただし、円で発見した敵がいなくなった場合にm_v3_sensing_posを初期化してとどまらないようにします。
それ以外は。nullを設定して、ただ前進するのみです。
# バトル中の相手、センシングした敵の順に目標位置を設定する
if battle_pos:
m_v3_enemy_pos = battle_pos
elif m_v3_sensing_pos:
if (m_v3_sensing_pos - transform.origin).length() < 0.1:
m_v3_sensing_pos = null
m_v3_enemy_pos = m_v3_sensing_pos
else:
m_v3_enemy_pos = null
保存する変数を変更
半径3mの円で発見した敵の位置を保存するメンバを変更しました。
func discover_enemy(enemy_pos):
m_v3_sensing_pos = enemy_pos
実行しましょう。
先ほどより撃ち落としやすくなったと思います。
以上です。