※本記事にはアフィリエイトリンクが含まれています。
Amazonのアソシエイトとして、ゲームのタネは適格販売により収入を得ています。
▶ この回の完成形を、この場で遊ぶ
ブラウザ版『雨まで10秒』と同じ核(パイプ回転+水路の到達)を、Godot 4.7 向けに1本にまとめたミニゲームです。10秒以内に水路をつなぎ、雨が降ったときに植物へ水が届くとクリアです。
記事中の GDScript・ダウンロード ZIP と同じルールです。はじめる(または画面クリック、R)で開始。パイプをクリックで回転。
Godot の完成プロジェクト
上で遊んでいるものと同じゲームです。ZIP を解凍し、Godot 4.7(執筆時点の安定版は 4.7.2)で project.godot を開いて F5。
ame_made_10byo.zip をダウンロード
はじめに
「パイプは回った。でも、水が本当につながっているかは、どう判定するの?」——そんな疑問向けです。
この記事では、Godot 4.7 でブラウザ企画の『雨まで10秒』をエンジン向けに移植し、1本の完成ゲームにします。雨が降るまでの10秒間に庭の水路をつなぎ、植物に水を届けます。時間切れで水が流れ、入口から植物まで届いていればクリア、途切れていれば失敗です。R(またはボタン)で何度でもやり直せます。
今週身につけること:接続データの回転と、入口からの到達判定。 見た目の回転と内部の接続マスクを同じ rotation から作ります。隣マスは、共有辺の両側が開いているときだけつながります。幅優先探索(BFS)で入口から広げ、植物に届いたかを見ます。制限時間とクリア/失敗は、それがゲームになるための枠です。
ブラウザ版の連載(企画→接続→完成)で遊びの核はすでに検証済みです。この回は、そこで約束したエンジン移植の選択肢・その1(Godot)です。TypeScript の第④回ではありません。
完成形と、やらないこと
- 作るもの: 5×5 盤面。I / L / T パイプをクリックで90度回転。入口と植物は固定。10秒後に水が流れ、BFS で到達判定。クリア/失敗とやり直し。Godot ZIP と、同じルールのイン記事プレイ。
- やらないこと: 岩ステージ7本、成長段階の花、TypeScript 書き直し、Unity 併記、音、ランキング。
- 持ち帰る1アイデア: 「見た目の角度」と「つながっているか」を別々に持たない。同じ回転値からマスクを作り、両側オープンの辺だけを辿る。


準備(Godot 4.7)
- Godot 公式ダウンロードから 4.7(安定版 4.7.2)を入れます。
- 上の ame_made_10byo.zip を解凍し、
project.godotを開きます。自分で組む場合は 2D の新規プロジェクトでも構いません。 - メインシーンは
main.tscn(ルートはNode2D)です。
初心者がハマりやすい点: 盤面の塗りつぶしを CanvasLayer の全面 ColorRect に置くと、2D のパイプが HUD の下に隠れて見えなくなります。第1回の的あてでも起きた失敗です。盤は Node2D(この回では BoardDraw)側に置き、HUD は上部バーとラベルだけを CanvasLayer にします。

盤面データと接続マスク
各マスは種類(I / L / T / 入口 / 植物 / 岩)と rot(0〜3)を持ちます。基準の開口ビット(北=1, 東=2, 南=4, 西=8)を、時計回りに rot 回だけ回したものが「今の開口」です。描画も同じマスクを線として引くので、見た目と判定がずれません。
スクリプト1:pipe_board.gd(接続とBFS)
ファイル名:pipe_board.gd。シーンに直接付けず、class_name PipeBoard として他スクリプトから呼び出します。
extends RefCounted
class_name PipeBoard
## パイプ盤面の接続マスクと入口からの到達判定(BFS)。
## 見た目の回転と内部マスクは同じ rotation から作る。
# 方向ビット: N=1 E=2 S=4 W=8
const N := 1
const E := 2
const S := 4
const W := 8
const OPP := {N: S, E: W, S: N, W: E}
const DELTA := {N: Vector2i(0, -1), E: Vector2i(1, 0), S: Vector2i(0, 1), W: Vector2i(-1, 0)}
# shape 基準(rotation=0)の開口。I=縦、L=下+右、T=左+下+右
const BASE := {
"I": N | S,
"L": S | E,
"T": W | S | E,
"IN": S, # 入口:下へ出す(固定)
"PLANT": N # 植物:上から受ける(固定)
}
static func rotate_mask(mask: int, rot: int) -> int:
var r := ((rot % 4) + 4) % 4
var out := 0
for _i in r:
# 時計回り: N->E->S->W->N
var n := 0
if mask & N: n |= E
if mask & E: n |= S
if mask & S: n |= W
if mask & W: n |= N
mask = n
return mask
static func openings(kind: String, rot: int) -> int:
var base: int = BASE.get(kind, 0)
if kind == "IN" or kind == "PLANT":
return base # 固定マスは回さない
return rotate_mask(base, rot)
## cells: Array of Dictionary {kind, rot, fixed}
## size: Vector2i 入口・植物の座標を返す
static func find_special(cells: Array, size: Vector2i) -> Dictionary:
var inlet := Vector2i(-1, -1)
var plant := Vector2i(-1, -1)
for y in size.y:
for x in size.x:
var c: Dictionary = cells[y * size.x + x]
if c.kind == "IN":
inlet = Vector2i(x, y)
elif c.kind == "PLANT":
plant = Vector2i(x, y)
return {"inlet": inlet, "plant": plant}
## 両側が開いている辺だけ通る BFS。到達セルの座標配列を返す。
static func reach_from_inlet(cells: Array, size: Vector2i) -> Array[Vector2i]:
var special := find_special(cells, size)
var start: Vector2i = special.inlet
var reached: Array[Vector2i] = []
if start.x < 0:
return reached
var visited := {}
var q: Array[Vector2i] = [start]
visited[start] = true
while not q.is_empty():
var cur: Vector2i = q.pop_front()
reached.append(cur)
var ci: int = cur.y * size.x + cur.x
var open_a: int = openings(cells[ci].kind, int(cells[ci].rot))
for dir in [N, E, S, W]:
if (open_a & dir) == 0:
continue
var nxt: Vector2i = cur + DELTA[dir]
if nxt.x < 0 or nxt.y < 0 or nxt.x >= size.x or nxt.y >= size.y:
continue
if visited.has(nxt):
continue
var ni: int = nxt.y * size.x + nxt.x
var open_b: int = openings(cells[ni].kind, int(cells[ni].rot))
# 隣マスは反対方向も開いているときだけつながる
if (open_b & OPP[dir]) == 0:
continue
visited[nxt] = true
q.append(nxt)
return reached
static func water_reaches_plant(cells: Array, size: Vector2i) -> bool:
var special := find_special(cells, size)
var plant: Vector2i = special.plant
if plant.x < 0:
return false
for p in reach_from_inlet(cells, size):
if p == plant:
return true
return false
別ゲームに持ち替えるときの変更点: BASE に十字パイプを足す、入口を複数にする、ゴールを「スイッチ」に変える、など。BFS の枠組みはそのまま使えます。
なぜ両側オープンか:片側だけ開いていても水路はつながっていない、というルールをデータで表現するためです。隣マスの反対方向ビットも見て初めて辺を通します。

クリックで回転する
入力は Godot 4.7 の InputEventMouseButton を _unhandled_input で受け取ります(公式の InputEvent 解説どおり、GUI が先に処理したあとにゲーム側へ届きます)。左ボタンが押された座標からマスを求め、rot = (rot + 1) % 4 とします。
スクリプト2:クリック回転(main.gd の抜粋)
ファイル名:main.gd。ルートの Node2D(Main)にアタッチします。
extends Node2D
## クリックでパイプを1段階回転(抜粋)。main.gd に含めています。
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
_try_rotate_at(event.position)
func _try_rotate_at(pos: Vector2) -> void:
var local := pos - BOARD_ORIGIN
var gx := int(floor(local.x / CELL))
var gy := int(floor(local.y / CELL))
if gx < 0 or gy < 0 or gx >= COLS or gy >= ROWS:
return
var i := gy * COLS + gx
if cells[i].fixed:
return
cells[i].rot = (int(cells[i].rot) + 1) % 4
board_draw.queue_redraw()
別ゲームに持ち替えるときの変更点: マスではなくカードを回す、右クリックで逆回転、など。座標→インデックスの変換だけ差し替えれば足ります。
雨が降ったら到達判定
制限時間は _process で減らします(短いカウントなので Timer ノードでもよいですが、この完成 ZIP は毎フレーム減算です)。0 になったら PipeBoard.reach_from_inlet を呼び、植物マスが含まれていればクリアです。
スクリプト3:雨の判定(main.gd の抜粋)
func _rain_and_judge() -> void:
var size := Vector2i(COLS, ROWS)
var reached: Array = PipeBoard.reach_from_inlet(cells, size)
watered.clear()
for p in reached:
watered[p] = true
board_draw.queue_redraw()
var ok := PipeBoard.water_reaches_plant(cells, size)
if ok:
state = "clear"
# HUD / オーバーレイ更新…
else:
state = "fail"
# 失敗表示…
別ゲームに持ち替えるときの変更点: 時間切れではなく「決定ボタン」で流す、複数植物のうち過半数、など。判定関数の戻り値の使い方だけ変えれば応用できます。

エディタでの組み立て手順
- 新規プロジェクト(または ZIP)で
main.tscnを開きます。 - ルート
Main(Node2D)にmain.gdを付けます。 - 子に
BoardBg(Polygon2D・背景)、BoardDraw(Node2D +board_draw.gd)を置きます。ここがプレイフィールドです。 - 子に
UI(CanvasLayer)を作り、上部だけColorRect、Label、Button、結果用 Overlay を置きます。盤全体を覆う ColorRect は作らないでください。 pipe_board.gdをプロジェクトに置き、F5 で Play します。
Play で確認すること
- パイプが表示され、クリックで90度ずつ回る。
- 入口・植物・岩は回らない。
- 10秒で水が流れ、つながっていればクリア、切れていれば失敗。
- R または「もう一度」で再開できる。
- (学習の確認)途中のマスをわざとずらすと、水が途中で止まる。マスクと BFS が効いている証拠です。
できたこと
- Godot 4.7 で『雨まで10秒』のミニ版を1本完成させた。
- イン記事プレイ・記事の脚本・ZIP が同じルールになった。
身についたこと
- 接続マスクを rotation から作る考え方。
- 共有辺の両側オープン判定。
- 入口からの BFS 到達判定。
- プレイフィールドを CanvasLayer 全面で隠さない配置。
使い回しメモ
- スニペット:
pipe_board.gdのopeningsとreach_from_inlet。 - アイデア: 見た目と接続を同じデータから作る。
- 次の用途: 配線パズル、配管の漏水チェック、ダンジョンの扉が開く条件(スイッチが電源に届いているか)。
関連商品(PR)
パイプをクリックして回す回なので、Godot の入門書と、クリック操作がしやすいマウスを挙げます。
- ゆるっとはじめる Godot Engine ゲームプログラミング(杉山悠真) Amazonで見る
※本リンクはアフィリエイトリンクです。 - Logicool G304 ワイヤレスゲーミングマウス(クリック操作の練習にも扱いやすい定番) Amazonで見る
※本リンクはアフィリエイトリンクです。
参考
- Godot Engine 公式ダウンロード(本記事執筆時の安定版 4.7.2)
- InputEventMouseButton — Godot 4.7 クラスリファレンス
- Using InputEvent — Godot 4.7 ドキュメント
- Timer — Godot 4.7 クラスリファレンス
- Array — Godot 4.7 クラスリファレンス(BFS のキューに使用)
次に足してよい一言
ステージを2枚にする、クリアまでのクリック数を表示する、入口を左右どちらからも選べるようにする——どれか1つで十分「自分のアレンジ」になります。
前回まで: 第1回 Godot 20秒的あて / 第2回 Unity/Godot 比較
ブラウザ版の企画メモ(参考・TypeScript 第④ではありません):
①企画 /
②接続 /
③完成


コメント