0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Godot 4.0】スマホ3Dゲームを作るための勉強 その11 敵も攻撃するようにしました

Last updated at Posted at 2023-03-02

ゲームエンジン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.tscn - S11_enemy_fire - Godot Engine 2023_03_02 11_31_39.png

  • 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」に変更しましょう。
enemy.tscn - S11_enemy_fire - Godot Engine 2023_03_02 13_13_56.png

Timer_fire
発射動作間隔としてWait Timeを6sに設定します。また、Autostartをオンにしましょう。
enemy.tscn - S11_enemy_fire - Godot Engine 2023_03_02 13_14_06.png

Timer_short
 Wait Timeを0.5sにします。Timer_shortはGDScriptからタイマーを開始して、3回timeoutしたら(3回発射したら)停止するように実装しますので、Autostartは初期値のままオフです。
enemy.tscn - S11_enemy_fire - Godot Engine 2023_03_02 13_14_08.png

timeoutシグナルをEnemyに接続します

 Timer_fireを選択して、インスペクタの横のノードタブのシグナルを開いて、timeoutシグナルを接続します。
スクリーンショット (52).png
 接続先はEnemyです。
スクリーンショット (53).png

 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にします。
 (_) enemy.tscn - S11_enemy_fire - Godot Engine 2023_03_02 13_27_21.png

GDScriptにPackedSceneを@exportして、enemybullet.tscnを登録します。

enemy.gdにメンバ変数を追加します。

@export var m_scn_enemybullet : PackedScene

 enemy.tscnを開いて、Enemyノードを選択します。
 ファイルシステムのenemybullet.tscnをインスペクタのEnemy.gd/M Scn EnemyBulletの<empty>の上にドラッグアンドドロップします。
スクリーンショット (54).png
 登録されました。
スクリーンショット (55).png

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()
     ショートタイマーを停止します。
     
     実行しましょう。
     Screenshot 2023_03_02 13_51_53.png

BulletとEnemyBulletが相殺するようにします。

 BulletでEnemyBulletを相殺するようにします。

EnemyBulletのCollision Layer/Maskを設定しましょう。

 enemybullet.tscnを開いて、EnemyBulletノードを選択します。
 インスペクタのCharacterBody3D/CollisionのLayerの3:Enemyをチェックします。
CollisionのMaskは1~4まですべてチェックします。
スクリーンショット (57).png

EnemyBulletにEnemyGroup設定しましょう。

 Bulletは「EnemyGroup」を持つノードを攻撃しますので、EnemyBulletにもグループを追加します。

 EnemyBulletノードを選択した状態で、ノード/グループを選択します。
 EnemyGroupを入力して、追加ボタンを押下します。
enemy.tscn - S11_enemy_fire_002 - Godot Engine 2023_03_02 14_00_25.png
 追加されました。
enemy.tscn - S11_enemy_fire_002 - Godot Engine 2023_03_02 14_00_28.png
 EnemyBulletノードの横にも四角の中に●があるアイコンが追加されました。
enemy.tscn - S11_enemy_fire_002 - Godot Engine 2023_03_02 14_00_29.png

 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

 実行しましょう。
 先ほどより撃ち落としやすくなったと思います。

以上です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?