結論: 敵の生成時に種類を渡し、種類ごとにHP・移動速度・色を設定します。通常型と高速型で役割を分けると、敵の数を増やすだけでも、プレイヤーが対応を考えるゲームになります。
このシリーズでは、Brotatoのようなアリーナ型ゲームで使われる仕組みを、Godotで一つずつ小さく再現しています。前回は、ウェーブが進むほど敵のHPと移動速度を上げる仕組みを追加しました。
敵が全員同じ能力だと、ウェーブが進んでも対応方法は変わりません。今回は通常型と高速型を用意し、敵の種類によって追いかけ方と倒しやすさを変えます。高速型はHPを少し低くする代わりに、移動速度を高くします。
- 対象:前回記事のウェーブ・難易度調整プロジェクトを動かせる人
- 使用バージョン:Godot 4.7 系(4.7.x)
- 完成状態:ウェーブ3以降に、一定確率で高速型の敵が出現する
1. 2種類の役割を決める
最初に、敵の特徴を決めます。
| 種類 | 色 | HP | 移動速度 | 役割 |
|---|---|---|---|---|
| 通常型 | 赤 | 標準 | 標準 | 基本となる敵 |
| 高速型 | 黄色 | 少し低い | 高い | プレイヤーへ早く近づく敵 |
高速型のHPを低くするのは、速い代わりに倒しやすくするためです。すべての能力を高くすると、通常型を置き換えるだけになり、種類を追加した効果が分かりにくくなります。
2. Enemyの基準値を用意する
前回の記事で、敵の基準値とウェーブごとの増加量を定義しました。enemy.gd では、次の定数を使います。
const BASE_SPEED := 100.0
const BASE_HP := 3
const HP_GROWTH_PER_WAVE := 1
const SPEED_GROWTH_PER_WAVE := 8.0
const FAST_SPEED_RATE := 1.6
const FAST_HP_BONUS := -1
const EXPERIENCE_VALUE := 1
const KIND_NORMAL := "normal"
const KIND_FAST := "fast"
KIND_NORMAL と KIND_FAST は、敵の種類を表す文字列です。文字列を直接何度も書かず、定数にまとめておくと、後から名前を変更しやすくなります。
FAST_SPEED_RATE は通常の速度に掛ける倍率です。1.6なら、通常型の1.6倍の速度になります。FAST_HP_BONUS は高速型のHPへ加える補正で、今回は1減らします。
3. 種類ごとの現在値を持たせる
敵が生成されたあとに使う値を、変数として定義します。
var hp := BASE_HP
var speed := BASE_SPEED
var kind := KIND_NORMAL
kind には、現在の敵が通常型か高速型かを保存します。hp と speed は生成時のウェーブと種類によって更新されます。
4. 種類とウェーブから能力を計算する
setup() の引数に、現在のウェーブと敵の種類を追加します。
func setup(current_wave: int, enemy_kind: String) -> void:
var wave_index := maxi(current_wave - 1, 0)
var normal_hp := BASE_HP + wave_index * HP_GROWTH_PER_WAVE
var normal_speed := BASE_SPEED + wave_index * SPEED_GROWTH_PER_WAVE
kind = enemy_kind
hp = normal_hp
speed = normal_speed
if kind == KIND_FAST:
hp = maxi(1, normal_hp + FAST_HP_BONUS)
speed = normal_speed * FAST_SPEED_RATE
まず通常型の能力を計算し、そのあと高速型だけ補正します。通常型の計算を共通にしておくと、ウェーブの成長ルールを種類ごとに重複して書かずに済みます。
maxi(1, ...) によって、高速型のHPが1未満になることを防ぎます。敵が一発で消える設定にしたい場合は別ですが、今回は最低1HPを残します。
5. 種類ごとに見た目を変える
同じ画像を使っても、色を変えるだけで種類を見分けやすくできます。enemy.gd でスプライトを取得します。
@onready var sprite: Sprite2D = $Sprite2D
setup() の最後で、高速型の色を変更します。
if kind == KIND_FAST:
hp = maxi(1, normal_hp + FAST_HP_BONUS)
speed = normal_speed * FAST_SPEED_RATE
sprite.modulate = Color(1.0, 0.75, 0.2, 1.0)
通常型はシーンの色をそのまま使い、高速型には黄色系の色を掛けます。元の画像に色が付いていると、見た目は黄色にならない場合があります。確実に色分けするには白い画像を使うか、実際の表示を確認して色を調整してください。色は能力値の代わりではありませんが、プレイ中に敵の役割を判断する手がかりになります。
6. Mainで出現する種類を選ぶ
main.gd に、Enemyへ渡す種類の定数を追加します。
const ENEMY_KIND_NORMAL := "normal"
const ENEMY_KIND_FAST := "fast"
const FAST_ENEMY_CHANCE := 0.35
高速型を選ぶ関数を作ります。ウェーブ1と2では通常型だけを出し、ウェーブ3から高速型を混ぜます。
func _choose_enemy_kind() -> String:
if wave < 3:
return ENEMY_KIND_NORMAL
if randf() < FAST_ENEMY_CHANCE:
return ENEMY_KIND_FAST
return ENEMY_KIND_NORMAL
FAST_ENEMY_CHANCE は0.35なので、ウェーブ3以降の約35パーセントが高速型になります。乱数を使うため、毎回同じ順番で出現するわけではありません。
7. 生成時に種類を渡す
敵の生成処理で、選んだ種類を setup() へ渡します。
func _on_spawn_timer_timeout() -> void:
var enemies := get_tree().get_nodes_in_group("enemies")
if enemies.size() >= MAX_ENEMY_COUNT:
return
var enemy: Node2D = ENEMY_SCENE.instantiate()
add_child(enemy)
enemy.call("setup", wave, _choose_enemy_kind())
var angle := randf() * TAU
enemy.global_position = player.global_position + Vector2.from_angle(angle) * 700.0
_choose_enemy_kind() の戻り値を、そのまま setup() の2番目の引数へ渡しています。生成された敵は、自分の種類と現在のウェーブからHP・速度・色を決めます。
敵数上限の判定は、種類を選ぶ前に残しています。高速型を追加しても、敵が無制限に増える問題はそのまま防げます。
8. Enemyスクリプト全体の形
ここまでの変更を反映した enemy.gd の主要部分は次のとおりです。
extends CharacterBody2D
const BASE_SPEED := 100.0
const BASE_HP := 3
const HP_GROWTH_PER_WAVE := 1
const SPEED_GROWTH_PER_WAVE := 8.0
const FAST_SPEED_RATE := 1.6
const FAST_HP_BONUS := -1
const EXPERIENCE_VALUE := 1
const KIND_NORMAL := "normal"
const KIND_FAST := "fast"
var hp := BASE_HP
var speed := BASE_SPEED
var kind := KIND_NORMAL
@onready var sprite: Sprite2D = $Sprite2D
func _ready() -> void:
add_to_group("enemies")
func setup(current_wave: int, enemy_kind: String) -> void:
var wave_index := maxi(current_wave - 1, 0)
var normal_hp := BASE_HP + wave_index * HP_GROWTH_PER_WAVE
var normal_speed := BASE_SPEED + wave_index * SPEED_GROWTH_PER_WAVE
kind = enemy_kind
hp = normal_hp
speed = normal_speed
if kind == KIND_FAST:
hp = maxi(1, normal_hp + FAST_HP_BONUS)
speed = normal_speed * FAST_SPEED_RATE
sprite.modulate = Color(1.0, 0.75, 0.2, 1.0)
func take_damage(amount: int) -> void:
hp -= amount
if hp <= 0:
var player: Node = get_tree().get_first_node_in_group("player")
if player != null and player.has_method("gain_experience"):
player.call("gain_experience", EXPERIENCE_VALUE)
queue_free()
func _physics_process(_delta: float) -> void:
var player: Node2D = get_tree().get_first_node_in_group("player")
if player == null:
return
velocity = (player.global_position - global_position).normalized() * speed
move_and_slide()
9. よくあるミス
高速型が出現しない
ウェーブが3未満では通常型だけを返す処理になっています。すぐ確認したい場合は、テスト中だけ if wave < 3 の条件を外すか、開始ウェーブを一時的に3へ変更します。
高速型の色が変わらない
setup() が add_child() の前に呼ばれていないか確認します。今回の順番では、シーンを追加して @onready の参照を準備したあとに setup() を呼びます。
すべての敵が高速型になる
FAST_ENEMY_CHANCE が1になっていないか確認します。0.35なら高速型が出る確率は約35パーセントです。乱数の結果によって、短時間では偏って見えることがあります。
速い敵が倒しにくすぎる
FAST_SPEED_RATE を1.6から1.4へ下げるか、FAST_HP_BONUS を-2へ変更します。速度とHPを同時に上げると、通常型との差が大きくなります。
setup の引数エラーが出る
enemy.gd の関数が2つの引数を受け取る形になっているか確認します。Main 側だけを変更すると、古い1引数の関数へ2つの値を渡すことになります。
10. 実行して確認する
前回記事のプロジェクトを複製して、enemy.gd と main.gd を変更します。次の順番で確認します。
- Godot 4.7でプロジェクトを開く
- F6またはF5でゲームを実行する
- ウェーブ1と2で赤い通常型だけが出現することを確認する
- ウェーブ3以降で通常型と色の異なる高速型が出現することを確認する
- 高速型が通常型より速くプレイヤーへ近づくことを確認する
- 同じウェーブの高速型のHPが通常型より1低いことを確認する。必要な弾数は弾のダメージによって同じになる場合もある
- 敵数上限が働き、画面が敵で埋まり続けないことを確認する
- レベルアップで一時停止し、選択後も敵の種類が出現し続けることを確認する
乱数を使っているため、1回のプレイで高速型が出ない場合があります。確認時はウェーブ3以降まで進め、複数回の生成を観察してください。
11. まとめ
今回は、敵の生成時に種類を渡し、通常型と高速型を追加しました。
- 種類ごとの役割を先に決める
setup()でウェーブと種類から能力を計算する- 高速型は速度を上げ、HPを少し下げる
- 色を変えてプレイ中に種類を見分ける
- ウェーブ3以降だけ高速型を混ぜる
- 敵数上限と経験値報酬はそのまま維持する
敵を種類ごとに分けると、同じアリーナでも状況に応じた判断が生まれます。次は、敵の種類ごとに経験値や攻撃方法を変え、近距離型と遠距離型の役割を作れます。
参考:Godot 4とGDScriptの入門書
Godot 4とGDScriptの基本から2Dゲーム開発を学びたい場合は、今回のようなシーン構成やスクリプトの分割を体系的に確認できる書籍も参考になります。
※本記事にはアフィリエイトリンクが含まれています。
Godot 4 & GDScriptではじめる 2Dゲーム開発レシピ(Amazonで詳細を見る)
※本リンクはアフィリエイトリンクです。
統合版プロジェクトとの対応
この記事で扱う機能は、企画全体をまとめたauto_attack_final_verified.zipにも組み込んでいます。この記事のコードは段階的に追加するための説明なので、途中の記事と統合版全体のファイル構成は一致しない場合があります。
統合版では高速型はウェーブ2から出現し、setupの引数順は種類、ウェーブです。経験値は敵撃破時に直接加算せず、ジェムを取得して加算します。この記事のコードは途中段階の例です。統合版には後続記事の機能も含まれます。記事の機能を確認するときは該当する操作を先に試し、最後にGodot 4.7でF5起動、入力、セーブ・ロード、再起動後の復元を確認してください。
