はじめてのGodot 第5回: 隕石をランダムに降らせよう — タイマーとインスタンス化


前回作った隕石は、まだ1個しか存在しません。今回はこれを毎秒量産してランダムに降らせます。前回「別シーンにした」理由が、今日わかります。

今回のゴール

ランダムな位置と速さで隕石が次々に降ってくる様子

0.5秒ごとに、ランダムな位置から、ランダムな速さの隕石が降ってくる。

Timerノードを置く

「一定間隔で何かをする」にはTimerノードを使います。

  1. main.tscn を開く(ファイルシステムからダブルクリック)
  2. Main の子に Timer ノードを追加し、名前を SpawnTimer に変更
  3. インスペクタで設定:
    • Wait Time: 0.5(0.5秒間隔)
    • Autostart: オン(ゲーム開始と同時に動き出す)

Timerは0.5秒ごとに timeout というシグナルを発します。前回はエディタでシグナルを接続しましたが、今回はコードから接続してみます。どちらも同じことができるので、両方知っておくと便利です。

メインシーンにスクリプトを書く

Main にスクリプトをアタッチします(main.gd)。

extends Node2D

const METEOR_SCENE = preload("res://meteor.tscn")

func _ready():
	$SpawnTimer.timeout.connect(_on_spawn_timer_timeout)

func _on_spawn_timer_timeout():
	var meteor = METEOR_SCENE.instantiate()
	meteor.position = Vector2(randf_range(20, 460), -50)
	meteor.speed = randf_range(200, 500)
	add_child(meteor)

実行(F5 / MacはCmd+B)してみてください。隕石の雨が降ってきたら成功です。 まだ当たり判定はないので、すり抜けて大丈夫。

何をしたのか、1行ずつ

const METEOR_SCENE = preload("res://meteor.tscn")

前回作った隕石シーンのファイルを読み込んで、METEOR_SCENE という名前を付けています。constvar の仲間ですが「後から変わらない入れ物」です。res:// はプロジェクトフォルダを指すGodot独自の書き方です。

func _ready():$SpawnTimer.timeout.connect(...)

_ready()_process() と同じ特別な関数で、ノードが登場した瞬間に1回だけ呼ばれます。初期設定を書く場所です。

$SpawnTimer は「子ノードのSpawnTimerを取ってくる」書き方。その timeout シグナルに _on_spawn_timer_timeout 関数を接続(connect)しています。「タイマーが鳴るたびに、この関数を呼んでね」という予約です。前回エディタでダブルクリックしてやったことと、まったく同じことをコードでやっています。

instantiate()add_child() — 部品の量産

	var meteor = METEOR_SCENE.instantiate()

instantiate() は、シーンから実体(インスタンス)をひとつ作る命令です。設計図からコピーを1個作るイメージです。

ただし、作っただけではまだゲーム内に存在しません。最後の add_child(meteor)シーンツリーに追加して、初めて画面に現れて動き出します。「作る」と「置く」は別の操作 — ここが今回いちばんのつまずきどころです。

randf_range(最小, 最大) — 乱数

randf_range(20, 460) は20〜460のランダムな数を返します。x座標に使えば出現位置が、speed に使えば落下速度がバラつきます。y座標の -50画面の上端より外です(yマイナス=上、でしたね)。画面外から生まれて、降りてくる演出になります。

meteor.speed と書けるのは、前回 meteor.gdvar speed = 300.0 を定義したからです。外から変数を上書きして、個体差を付けています

数字で遊ぼう

ゲームの難易度はこの数字たちで決まります。変えて実行してみてください。

  • Wait Time 0.50.1: 弾幕地獄
  • speed randf_range(200, 500)(600, 1000): 高速隕石
  • x座標の範囲を狭める: 安全地帯ができてしまう(バグっぽい挙動も体験)

つまずきポイント

  • Node not found: "SpawnTimer": Timerノードの名前を SpawnTimer に変え忘れていませんか? 追加したばかりだと名前が Timer のままで、コードの $SpawnTimer が見つけられずこのエラーになります。シーンドックでノード名をダブルクリックして SpawnTimer に直してください
  • 何も降ってこない(その1): SpawnTimerの Autostart がオフのままではないですか? インスペクタで確認してください
  • 何も降ってこない(その2): add_child(meteor) を書き忘れると、隕石は「作られたのに置かれていない」状態になります。エラーも出ないので気づきにくい、定番のミスです
  • Invalid assignment of property or key 'speed': meteor.gd の変数名と一致しているか確認してください(前回 speed と定義しました)

今回学んだこと

  • Timer ノード + timeout シグナルで「一定間隔の処理」を作る
  • シグナルはエディタでもコード(connect)でも接続できる
  • _ready() はノード登場時に1回だけ呼ばれる初期設定の場所
  • instantiate() で部品の実体を作り、add_child() で置く。2つで1セット
  • randf_range() の乱数でゲームに変化を付ける

次回予告

隕石は降ってきますが、今はすり抜けるだけ。第6回でいよいよ当たり判定を実装し、「ゲームオーバー」を作ります。if 文の初登場回です。

第6回: 当たったらゲームオーバー — 当たり判定とif文