実践ガイド

1波の防衛を完成させる|Godotで学ぶ敵の出現と砲台の射程

1波の防衛の開始画面

※本記事にはアフィリエイトリンクが含まれています。

Amazonのアソシエイトとして、ゲームのタネは適格販売により収入を得ています。

▶ この回の完成形を、この場で遊ぶ

ブラウザ版と同じ核(道を進む敵の出現と、砲台の射程)を、Godot 4.7 向けに1本にまとめたミニゲームです。1波(10体)をすべて撃破すればクリア、1体でも基地に着くと失敗です。

1波の防衛 はじめるでスタート
このブラウザではキャンバスを表示できません。

記事中の GDScript・ダウンロード ZIP と同じルールです。はじめる(または画面クリック、R)で開始。プレイ中クリックで砲台を移動。

Godot の完成プロジェクト

上で遊んでいるものと同じゲームです。ZIP を解凍し、Godot 4.7(執筆時点の安定版は 4.7.2)で project.godot を開いて F5。

one_wave_defense.zip をダウンロード

はじめに

「敵は出た。砲台も置いた。でも、いつ撃てて、いつ撃てないの?」——そんな疑問向けです。

この記事では、Godot 4.7 で1波の防衛を1本の完成ゲームにします。敵は道のウェイポイントを進み、砲台は射程内の最も近い敵だけを自動で撃ちます。10体すべてを基地の手前で倒せばクリア、1体でも基地に着くと失敗です。R(またはボタン)で何度でもやり直せます。

今週身につけること:出現(スポーン)と射程(レンジ)。 Timer の timeout で敵シーンを instantiate して場に出します。砲台は Vector2.distance_to で距離を測り、射程以下のときだけ発射します。勝敗とやり直しは、それがゲームになるための枠です。

完成形と、やらないこと

  • 作るもの: 固定の1本道。敵10体が間隔を空けて出現。砲台1種(自動射撃・位置はクリックで移動)。クリア/失敗とやり直し。Godot ZIP と、同じルールのイン記事プレイ。
  • やらないこと: 複数ウェーブ、アップグレード、壁設置、タワー種類の切り替え、音、ランキング、Unity 併記。
  • 持ち帰る1アイデア: 「出すタイミング」と「届く距離」を分けて持つ。出現は Timer、攻撃可否は距離チェック。
1波の防衛の開始画面。道・砲台・はじめるボタン
完成形の開始画面(イン記事プレイ/ZIP と同じルール)。
出現点・射程円・射程内外の敵の図
今週の核:Timer での出現と distance_to による射程。

準備(Godot 4.7)

  1. Godot 公式ダウンロードから 4.7(安定版 4.7.2)を入れます。
  2. 上の one_wave_defense.zip を解凍し、project.godot を開きます。記事のコードは仕組みを説明する抜粋です。初めて試すときは ZIP の完成プロジェクトを使ってください。
  3. メインシーンは main.tscn(ルートは Node2D)です。

初心者がハマりやすい点: 盤面の塗りつぶしを CanvasLayer の全面 ColorRect に置くと、2D の敵や砲台が HUD の下に隠れて見えなくなります。第1回の的あてでも起きた失敗です。道とキャラは Node2D 側に置き、HUD は上部バーとラベルだけを CanvasLayer にします。

Main Node2D、FieldDraw、Turret、SpawnTimer、UI CanvasLayer のシーン構造
入力・シーン構造。プレイフィールドは Node2D、HUD は CanvasLayer(全面塗りは禁止)。

敵の出現(スポーン)

1波は全部で10体です。開始直後に1体出し、あとは Timer の wait_time(この完成版では 0.85 秒)ごとに1体ずつ出します。敵の見た目と動きは別シーン enemy.tscn にまとめ、PackedScene.instantiate() で複製します。公式ドキュメントの「Nodes and scene instances」でも、弾や敵を量産する定番手順として紹介されているやり方です。

スクリプト1:main.gd(Timer で出現)

以下は出現処理を説明する抜粋です。PATH の定義や開始処理などは含みません。完成版の main.gd は ZIP にあります。

extends Node2D
## Timer の timeout で敵シーンを1体ずつ出現させる(抜粋)。

@onready var spawn_timer: Timer = $SpawnTimer
@onready var enemies_root: Node2D = $Enemies
var enemy_scene: PackedScene
var spawned: int = 0
const WAVE_SIZE := 10

func _ready() -> void:
	enemy_scene = preload("res://enemy.tscn")
	spawn_timer.timeout.connect(_on_spawn_timeout)

func _start_wave() -> void:
	spawned = 0
	_spawn_one()          # 最初の1体はすぐ
	spawn_timer.start()   # 以降は wait_time ごと

func _on_spawn_timeout() -> void:
	if spawned >= WAVE_SIZE:
		spawn_timer.stop()
		return
	_spawn_one()

func _spawn_one() -> void:
	var inst: Node2D = enemy_scene.instantiate()
	enemies_root.add_child(inst)
	inst.setup(PATH)  # ウェイポイント配列を渡す
	spawned += 1

別ゲームに持ち替えるときの変更点: WAVE_SIZE と wait_time、enemy_scene の中身だけ変えれば、シューティングの弾幕やクイズの次の問題カードにも使えます。

プレイ中。敵が道を進み砲台が射程内を射撃
プレイ中。出現済と撃破数が上部に出ます。

砲台の射程(レンジ)

砲台は常に「一番近い敵」を狙いますが、距離が射程を超えていたら撃ちません。Godot 4.7 では Vector2.distance_to で2点間の距離を取れます。クールダウン中は撃てない、という時間側の制限も合わせて持たせます。

スクリプト2:turret.gd(射程判定)

ファイル名:turret.gd。以下は仕組みを示す抜粋です。完成ZIPには描画などを含む全文があります。

extends Node2D
## 射程内の最も近い敵へ自動攻撃する砲台。

@export var fire_range: float = 140.0
@export var fire_cooldown: float = 0.40
@export var damage: int = 1
var cooldown_left: float = 0.0

func _process(delta: float) -> void:
	if cooldown_left > 0.0:
		cooldown_left -= delta

func try_fire(enemies: Array) -> void:
	if cooldown_left > 0.0:
		return
	var nearest: Node2D = null
	var nearest_d := fire_range + 1.0
	for e in enemies:
		if e == null or not is_instance_valid(e):
			continue
		var d: float = global_position.distance_to(e.global_position)
		if d <= fire_range and d < nearest_d:
			nearest_d = d
			nearest = e
	if nearest == null:
		return
	nearest.take_damage(damage)
	cooldown_left = fire_cooldown

別ゲームに持ち替えるときの変更点: fire_range を広げると「遠くまで届く弓」、狭くすると「近接だけ」。敵ではなくアイテム回収の「拾える距離」にも同じ距離チェックが使えます。

道を進む敵

敵は配列で持ったウェイポイントを順に move_toward します。完成ZIPでは速度とdeltaから1フレームの移動量を計算し、目標点への到達を判定します。最後の点(基地)に着いたら敗北シグナルを出します。HP が0になったら撃破です。

スクリプト3:enemy.gd(道の移動)

ファイル名:enemy.gd。以下は道の移動を示す抜粋です。完成ZIPには位置調整や描画などを含む全文があります。

extends Node2D
## ウェイポイントを進む敵。

signal reached_base
signal died
@export var speed: float = 70.0
@export var max_hp: int = 3
var hp: int = 3
var path: Array[Vector2] = []
var waypoint_i: int = 0
var alive: bool = true

func setup(waypoints: Array[Vector2]) -> void:
	path = waypoints.duplicate()
	hp = max_hp
	waypoint_i = 0
	if path.size() > 0:
		global_position = path[0]

func take_damage(amount: int) -> void:
	if not alive:
		return
	hp -= amount
	if hp <= 0:
		alive = false
		died.emit()
		queue_free()

func _process(delta: float) -> void:
	if not alive or path.is_empty() or waypoint_i >= path.size():
		return
	var target: Vector2 = path[waypoint_i]
	global_position = global_position.move_toward(target, speed * delta)
	if global_position.distance_to(target) < 1.0:
		waypoint_i += 1
		if waypoint_i >= path.size():
			alive = false
			reached_base.emit()
			queue_free()

別ゲームに持ち替えるときの変更点: ウェイポイントを増やす、速度を敵タイプごとに変える、max_hp を上げる、など。道そのもののデータと移動ロジックは分けたままにします。

クリア画面。1波をすべて迎撃した表示
クリア例。10体すべてを基地の手前で倒しています。

エディタでの組み立て手順

  1. ZIP の main.tscn を開きます。以下は完成シーンの構成を確認するための説明です。記事中の抜粋だけでは、経路データ、開始処理、勝敗判定、HUD と描画の実装が不足するため、空のプロジェクトからは完成しません。必要なスクリプトと設定は ZIP で確認してください。
  2. ルート Main(Node2D)に main.gd を付けます。
  3. 子に BoardBg(Polygon2D・背景)、FieldDraw(Node2D + field_draw.gd)、Enemies(Node2D)、Turret(Node2D + turret.gd)、SpawnTimer(Timer)を置きます。ここがプレイフィールドです。
  4. 子に UI(CanvasLayer)を作り、上部だけ ColorRect、Label、Button、結果用 Overlay を置きます。フィールド全体を覆う ColorRect は作らないでください。
  5. enemy.tscn(Node2D + enemy.gd)を別シーンとして保存し、F5 で Play します。

Play で確認すること

  • はじめるまで敵が出ない(自動開始しない)。
  • 開始後、敵が道に沿って現れ、間隔を空けて続く。
  • 砲台の薄い円(射程)の中にいる敵への射撃を線で表示する。
  • 射程の外を通る位置に砲台を移すと、基地に着かれて失敗しやすい。
  • 10体撃破でクリア、R で再開できる。
  • (学習の確認)fire_range を一時的に 60 に下げ、射程内の敵にだけ攻撃するか確認する。

できたこと

  • Godot 4.7 で「1波の防衛」を1本完成させた。
  • イン記事プレイ・記事の脚本・ZIP が同じルールになった。

身についたこと

  • Timer と PackedScene.instantiate による出現。
  • Vector2.distance_to による射程判定。
  • プレイフィールドを CanvasLayer 全面で隠さない配置。

使い回しメモ

  • スニペット: main.gd の出現ループと turret.gd の距離チェック。
  • アイデア: 「出す」と「届く」を別の仕組みにする。
  • 次の用途: シューティングの弾の有効距離、クリックで拾えるアイテムの半径、会話が始まる距離。

関連商品(PR)

砲台を置いて距離を見る回なので、Godot の入門書と、クリック操作がしやすいマウスを挙げます。

  • ゆるっとはじめる Godot Engine ゲームプログラミング(杉山悠真) Amazonで見る

    ※本リンクはアフィリエイトリンクです。
  • Logicool G304 ワイヤレスゲーミングマウス(砲台の配置クリックとプレイテスト向け) Amazonで見る

    ※本リンクはアフィリエイトリンクです。

参考

次に足してよい一言

砲台を2基にする、敵の速度を2種類にする、射程を画面に数字で出す——どれか1つで十分「自分のアレンジ」になります。

前回: 第4回 1戦闘のターン制(Unity)

次回予告(キュー): Unity で1部屋の3D(アイテムを取って出口へ)。カメラとレイを学びます。

← 記事一覧・検索