実践ガイド

【Godot 4.7】Brotato風「武器の自動攻撃」を作ろう:一番近い敵を自動で撃つ仕組み

青いプレイヤーが赤い敵へ黄色い弾を自動で撃つ2Dゲーム画面

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

結論: 「タイマーで一定間隔ごとに、一番近い敵を探して、弾を撃つ」。この3つを組み合わせるだけで、Brotato のような自動攻撃になります。この記事では Godot 4.7(無料のゲームエンジン)と GDScript(Godot 専用のプログラミング言語)で、動くところまで作ります。元ネタの Brotato も Godot 製のゲームです。

  • 対象:Godot をインストール済みで、プロジェクトを新規作成できる人
  • 使用バージョン:Godot 4.7 系(4.7.x)

1. 完成イメージ

  • 矢印キーでアイコン画像のプレイヤーを動かせる
  • 色を変えた敵が周囲から湧いて、プレイヤーに向かってくる
  • 本記事の段階版では、何も操作しなくても一番近い敵に向かって弾が自動で飛び、当たった敵が消える

2. 仕組みの解説

  • 一定間隔の攻撃:Timer
  • 一番近い敵を探す:グループ
  • 弾を撃つ:instantiate() によるシーンの複製
  • 当たり判定:Area2D
  • ノード:Godot の部品の最小単位。
  • シーン:ノードを組み合わせて保存したもの。プレハブ(部品をテンプレート化したもの)に近い。
  • シグナル:「〜が起きた」という合図。Timer の時間切れなどで発生する。
  • インスペクタ:画面右側の、選んだノードの設定欄。
  • ファイルシステム:画面左下の、プロジェクト内のファイル一覧。

3. 準備:プロジェクトを作る

この記事は英語のメニュー名を使い、シーンとスクリプトをプロジェクト直下に保存します。レンダラーは初期設定のままで始めます。

  1. Godot を起動し、プロジェクト一覧の画面で新しいプロジェクトを作成するボタンを押します。英語表示では「Create」です。
  2. 開いたダイアログで、プロジェクト名を auto_attack などにし、保存先フォルダを選びます。
  3. レンダラー(描画方式)は初期設定のままにし、ダイアログ下部の「Create」ボタンを押します。
  4. エディタが開いたら、表示言語を English(英語) にします。メニューの Editor → Editor Settings を開き、Interface → Editor → Editor Language で English を選びます。日本語表示を使う場合は、同じ役割の項目を選んでください。
  5. 以降、シーンとスクリプトはすべて res:// 直下(フォルダを作らない場所)に保存します。

4. 弾(Bullet)を作る

4-1. シーンを作る

Area2D を使い、弾が敵に触れたことを検知します。

  1. 左上の「Scene」メニューから「New Scene」を選びます。
  2. 「Other Node」から Area2D を選んで追加します。
  3. ルートノード(シーンの一番上のノード)の名前を Bullet に変更します。

4-2. 見た目と当たり判定を付ける

初期アイコンを小さくし、当たり判定の形を設定します。

  1. Bullet を選んで子ノードに Sprite2D(画像を表示するノード)を追加します。
  2. Sprite2D の Texture 欄に、ファイルシステムの icon.svg をドラッグします。
  3. Sprite2D の Scale(拡大率)の X と Y の両方を 0.2 にします。
  4. Bullet に子ノード CollisionShape2D(当たり判定の形を持つノード)を追加します。
  5. インスペクタの Shape 欄で「New CircleShape2D」を選びます。半径は初期値のままで構いません。

4-3. スクリプトを書く

テンプレートのコードを、次のコードに置き換えます。

  1. Bullet を右クリックして「Attach Script」を選びます。
  2. 開いたダイアログのファイル名が bullet.gd になっていることを確認し、「Create」を押します。
  3. コードエディタ内をクリックし、Ctrl+A で全選択してから、次のコードを貼り付けます。
class_name Bullet
extends Area2D

const SPEED := 600.0

var direction := Vector2.ZERO


func _ready() -> void:
	get_tree().create_timer(3.0).timeout.connect(queue_free)


func _physics_process(delta: float) -> void:
	position += direction * SPEED * delta


func _on_body_entered(body: Node2D) -> void:
	if body.is_in_group("enemies"):
		body.queue_free()
		queue_free()

コードの読み方:

  • class_name Bullet:このスクリプトに Bullet という型名を付けます。プレイヤー側から direction を安全に書き込むためです。
  • const:変えない値(定数)です。SPEED は弾の速さです。
  • Vector2:縦横2つの数値の組です。位置や向きを表します。
  • _ready:シーンに入った直後に1回だけ呼ばれる関数です。ここでは「3秒後に自分を削除する」予約をしています。当たらなかった弾が飛び続けて増えるのを防ぐためです。
  • _physics_process:物理演算の更新ごと(初期設定で毎秒60回)に呼ばれる関数です。ここで弾を進めます。
  • delta:前回の更新からの経過秒数です。これを掛けると、更新間隔が変わっても移動速度がおおむね一定になります。
  • queue_free():ノードを安全に削除する命令です。

4-4. 当たり判定の合図をつなぐ

物体が重なったときに _on_body_entered が呼ばれるよう、シグナルを接続します。

  1. Bullet を選び、右側の「Node」タブ →「Signals」を開きます。
  2. body_entered をダブルクリックし、Bullet を選んで「Connect」を押します。
  3. Ctrl+S で保存します。ファイル名を bullet.tscn にして、保存先が res:// 直下であることを確認してから保存します。

5. 敵(Enemy)を作る

見下ろし視点に合わせて Floating モードを使い、敵の色を変えてプレイヤーと見分けます。

  1. 新規シーンで CharacterBody2D(コードで動かす、物理演算つきのノード)を追加し、名前を Enemy にします。
  2. インスペクタで Enemy の Motion Mode を「Floating」にします。
  3. 子に Sprite2D(icon.svg、Scale は X・Y とも 0.3)を追加します。
  4. Sprite2D を選び、インスペクタの CanvasItem → Visibility → Modulate(色を掛け合わせる設定)をクリックして、赤系の色を選びます。元の絵が青系なので、完全な赤にはならず、色味が変わります。
  5. 子に CollisionShape2D を追加し、Shape に「New CircleShape2D」を選びます。次に、インスペクタに表示された CircleShape2D の欄をクリックして展開し、半径(Radius)を 16 にします。
  6. Enemy を右クリックして「Attach Script」を選び、ファイル名が enemy.gd になっていることを確認して「Create」を押します。
  7. コードエディタ内で全選択してから、次のコードを貼り付けます。
extends CharacterBody2D

const SPEED := 100.0


func _ready() -> void:
	add_to_group("enemies")


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()
  • add_to_group("enemies"):弾とプレイヤーが「敵」を見つけるための名札です。
  • global_position:画面全体を基準にした位置です。
  • normalized():向きだけを残して長さを1にします。速度が一定になるためです。
  • velocity と move_and_slide():velocity に速さと向きを入れて move_and_slide() を呼ぶと、その方向に動きます。
  • get_tree():ゲーム全体のノードを扱うための入口です。ここから「グループ名で探す」ことができます。
  • _delta:使わない引数は、先頭に _ を付けると警告が出ません。
  1. Ctrl+S で、ファイル名を enemy.tscn にして保存します(保存先は res:// 直下)。

6. プレイヤーを作る(自動攻撃の本体)

Camera2D はプレイヤーを追いかけます。移動中はプレイヤーが中央に留まり、敵や背景が動いて見えます。ゲーム画面をクリックしてから矢印キーを押し、敵や目印の位置を確認してください。攻撃用 Timer は自動開始にします。

  1. 新規シーンで CharacterBody2D を追加し、名前を Player にします。Motion Mode は「Floating」にします(理由は §5 と同じです)。
  2. 子に Sprite2D(icon.svg、Scale は X・Y とも 0.4)を追加します。色は変えず、そのままで構いません。
  3. 子に CollisionShape2D を追加し、Shape に「New CircleShape2D」を選んで、展開して半径(Radius)を 20 にします。
  4. 子に Camera2D を追加します。
  5. 子に Timer を追加し、名前を AttackTimer にします。
  6. AttackTimer のインスペクタで、Wait Time を 0.8(0.8秒ごとに攻撃)にし、Autostart をオンにします。
  7. Player を右クリックして「Attach Script」を選び、ファイル名が player.gd になっていることを確認して「Create」を押します。コードエディタ内で全選択してから、次のコードを貼り付けます。
extends CharacterBody2D

const SPEED := 300.0
const BULLET_SCENE := preload("res://bullet.tscn")


func _ready() -> void:
	add_to_group("player")


func _physics_process(_delta: float) -> void:
	var dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	velocity = dir * SPEED
	move_and_slide()


func _on_attack_timer_timeout() -> void:
	var target := _find_nearest_enemy()
	if target == null:
		return
	var bullet: Bullet = BULLET_SCENE.instantiate()
	get_parent().add_child(bullet)
	bullet.global_position = global_position
	bullet.direction = (target.global_position - global_position).normalized()


func _find_nearest_enemy() -> Node2D:
	var nearest: Node2D = null
	var nearest_dist := INF
	for enemy in get_tree().get_nodes_in_group("enemies"):
		var d := global_position.distance_squared_to(enemy.global_position)
		if d < nearest_dist:
			nearest_dist = d
			nearest = enemy
	return nearest
  • preload:ゲーム開始時にファイルを読み込んでおく命令です。ここでは弾のシーンを読み込んでいます。
  • Input.get_vector(...):矢印キーの入力を上下左右の向きにまとめます(ui_left などは Godot に最初から入っている操作名です)。
  • instantiate():シーンから実物のノードを1つ複製して作ります。
  • distance_squared_to:距離の2乗を返します。大小比較だけなら平方根の計算が不要で軽いためです。
  • INF:無限大を表す値です。最初に見つけた敵が、必ず「今までで一番近い」になります。
  • add_child の後に global_position を設定します。位置の設定は、シーンに追加したあとに行う形にそろえておくためです。

時間切れのたびに _on_attack_timer_timeout を呼ぶよう、シグナルを接続します。

  1. 「Node」タブ →「Signals」で AttackTimer の timeout を Player に接続します。
  2. Ctrl+S で、ファイル名を player.tscn にして保存します(保存先は res:// 直下)。

7. メインシーンで組み立てる

メインシーンにプレイヤーと敵を生成する Timer を配置します。

  1. 新規シーンで Node2D を追加し、名前を Main にします。
  2. ファイルシステムの player.tscn をドラッグして Main の子にします。位置は初期値(0, 0)のままで構いません。
  3. Timer を追加し、名前を SpawnTimer にして、Wait Time を 1.0、Autostart をオンにします。
  4. Main を右クリックして「Attach Script」を選び、ファイル名が main.gd になっていることを確認して「Create」を押します。コードエディタ内で全選択してから、次のコードを貼り付けます。
extends Node2D

const ENEMY_SCENE := preload("res://enemy.tscn")

@onready var player: Node2D = $Player


func _on_spawn_timer_timeout() -> void:
	var enemy: Node2D = ENEMY_SCENE.instantiate()
	add_child(enemy)
	var angle := randf() * TAU
	enemy.global_position = player.global_position + Vector2.from_angle(angle) * 700.0
  • @onready:シーンの準備ができてから、$Player(子ノード Player)を変数に入れる指定です。
  • randf() * TAU:ランダムな角度です(TAU は円一周=約6.28)。プレイヤーから700ピクセル離れた円周上に敵が現れます。画面の大きさやカメラの設定によっては、画面内に現れる場合もあります。

このシーンを保存し、実行時にメインシーンとして選びます。

  1. SpawnTimer の timeout を Main に接続します。
  2. Ctrl+S で、ファイル名を main.tscn にして保存します(保存先は res:// 直下)。
  3. F5 キーで実行します。メインシーンが未設定という確認が出たら、現在のシーンを選びます。

8. 動かないときのチェック

  • 弾が出ない:AttackTimer の timeout が Player に接続されているか確認します。
  • 敵が動かない:Player が player グループに入っているか(_ready の add_to_group("player"))確認します。
  • 敵が消えない:bullet.tscn の body_entered が接続されているか確認します。
  • 弾が敵をすり抜ける:Bullet と Enemy の CollisionShape2D に Shape が設定されているか確認します。
  • 起動時にエラーが出る:シーンとスクリプトを res:// 直下に保存しているか確認します。

9. ハマった罠

Camera2D のせいで移動していないように見えた

Camera2D をプレイヤーの子に追加すると、プレイヤーは画面中央に追従します。背景や目印がない状態で矢印キーを押すと、プレイヤーの絵が画面内で動かないため、入力が効いていないように見えました。

ワールドの軸や敵の相対位置を確認すると、プレイヤーは実際には移動しています。検証用プロジェクトには、移動を確認しやすいよう横軸と縦軸を追加しました。

今回確認できた実例はこの1件です。未確認の失敗談を、数合わせで追加しないようにしました。

付属プロジェクトの検証結果(2026年10月17日)

検証時の auto_attack.zip を Godot 4.7 stable で確認しました。段階版ZIPは現在この記事からダウンロードできません。project.godot のメインシーン設定、シーンとスクリプトの読み込み、メインシーンの起動に問題はありませんでした。

ヘッドレス実行では、次の動作を確認しています。

  • AttackTimer が動き、一番近い敵へ弾を生成する
  • 弾が敵に当たると、敵が消える
  • 敵がプレイヤーへ近づく
  • SpawnTimer によって敵が生成される

なお、これはコードと実行経路の確認です。エディタ上のクリック操作、画面の色や配置、矢印キーを使った手動操作は、別途 Godot の画面で確認してください。

2026年10月18日追記:プレイヤーの移動を画面で確認しやすくするため、検証済みZIPにはワールドの横軸・縦軸を追加しました。Camera2D がプレイヤーを追従するため、矢印キーを押すとプレイヤーではなく軸や敵の相対位置が動いて見えます。

10. まとめと次の一歩

  • 自動攻撃は「Timer + 最も近い敵の検索 + 弾の生成」の組み合わせでできます。
  • 次は、AttackTimer の Wait Time を変えて攻撃速度を調整する、弾を複数同時に撃つ、といった拡張がおすすめです。
  • このシリーズでは、Brotatoのようなアリーナ型ゲームで使われる仕組みを、Godotで一つずつ小さく再現していきます。

11. さらに学ぶ

今回の内容をもう少し体系的に学びたい場合は、Godot 4 と GDScript による2Dゲーム開発を扱う書籍も参考になります。

Godot 4 & GDScriptではじめる 2Dゲーム開発レシピ(Amazonで詳細を見る)

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

統合版プロジェクトとの対応

この記事で扱う機能は、企画全体をまとめたauto_attack_final_verified.zipにも組み込んでいます。この記事のコードは段階的に追加するための説明なので、途中の記事と統合版全体のファイル構成は一致しない場合があります。

統合版には後続記事の機能も含まれます。記事の機能を確認するときは該当する操作を先に試し、最後にGodot 4.7でF5起動、入力、セーブ・ロード、再起動後の復元を確認してください。

← 記事一覧・検索