Godot放置ゲーム番外編②:タイトル画面・リセット機能・隠しエンディング

初心者向け実践開発

※本記事にはアフィリエイト広告(Udemy)を含みます。

前回のおさらい

番外編①では、NovelAIで生成した背景素材の組み込みと、その過程で起きた失敗の修正、評価ランクの実装、UIの視認性改善について紹介しました。今回は、ゲームの導入部分と、隠し要素の実装について紹介します。

「何の説明もなく始まる」問題に気づく

背景やUIが整い、一通り遊べる状態になったところで、あらためて最初から動かしてみると、ある課題に気づきました。ゲームを開いた瞬間、いきなりECHOの短い一言だけで始まり、「自分はこの世界で何者なのか」「何のためにこの作業をしているのか」がまったく伝わらない状態だったのです。

作り手として何度も触っていると、こうした「初見の分かりにくさ」には案外気づきにくいものです。この気づきをきっかけに、導入部分を作り直すことにしました。

導入の形式としては、次の3つを検討しました。

  1. タイトル画面(ロゴと「スタート」ボタンを表示し、そこから本編に入る)
  2. 初回起動時の説明モーダル(ゲーム画面はすぐ見えるが、上に説明のポップアップが重なる)
  3. 両方

今回は、タイトル画面を用意したうえで、本編開始直後のECHOのセリフを状況説明に対応させる形を選びました。

タイトル画面を追加する

新しいシーンとして独立させる理由

タイトル画面は、ゲーム本編(Main.tscn)とは別のTitleScreen.tscnという独立したシーンとして作成しました。同じシーンの中に両方を詰め込むこともできますが、役割が明確に異なる画面は別シーンに分けておいた方が、後から見返したときに分かりやすくなります。

「スタート」ボタンでシーン遷移する

extends Control

@onready var start_button: Button = $CenterContainer/VBoxContainer/StartButton


func _ready() -> void:
    start_button.pressed.connect(_on_start_pressed)


func _on_start_pressed() -> void:
    get_tree().change_scene_to_file("res://scenes/Main.tscn")

get_tree().change_scene_to_file()は、現在のシーンを丸ごと別のシーンに切り替える関数です。ボタンが押されたら、タイトル画面からゲーム本編へと画面全体を切り替えます。

project.godotのメインシーンを差し替える

これまで、ゲーム起動時に最初に開かれるシーンはMain.tscnに設定していましたが、これをTitleScreen.tscnに変更します。

  1. 「プロジェクト」→「プロジェクト設定」を開く
  2. 「Application」→「Run」にある「Main Scene」の項目をTitleScreen.tscnに変更する
  • なぜ:これで、ゲームを起動すると必ずタイトル画面から始まるようになります

プレイヤーの立場をセリフで明確にする

タイトル画面を用意しただけでは、「プレイヤーが何者なのか」までは伝わりません。そこで、ゲーム開始直後のECHOの最初のセリフに、プレイヤーとECHOの関係性を書き加えました。

const STAGE_INTRO_LINES: Dictionary = {
    0: "ECHO: ようこそ、技術者さん。……ここは記録にも残っていない、小さな中継ステーション。今は動力が落ちてて、照明も換気も何もかも止まってる。あなたは今回の復旧作業を任された技術者、私はECHOっていう管理ドローン、あなたの補助役だよ。まずはあなたの手で、電力を作るところから始めよう。下のボタンを押して、発電してみて。",
}

「プレイヤー=復旧作業を任された技術者」「ECHO=管理ドローン」という関係性を一言添えるだけで、状況の見え方が大きく変わります。設定を詳しく作り込む必要はなく、遊ぶ人が迷わない程度の情報を最初に渡しておくことが重要だと感じました。

「最初からやり直す」リセット機能を追加する

なぜ確認ダイアログが必要か

リセットは、それまでの進行状況をすべて消してしまう操作です。誤って押してしまった場合に取り返しがつかないため、実行前に必ず確認を挟む必要があります。

Godot標準のConfirmationDialogを使わなかった理由

最初はGodot標準のConfirmationDialogノードを使いました。しかし、実際に動かしてみると、ダイアログのタイトルバー部分だけが独自のテーマが反映されず、Godotデフォルトの暗い配色のまま表示されてしまいました。ConfirmationDialogはウィンドウ(Window)の一種で、タイトルバー部分は通常のControlノードとは異なる仕組みで描画されているため、Themeの設定だけでは統一した見た目にできなかったのです。

独自の確認オーバーレイを自作する

そこで、標準のダイアログは使わず、半透明の全画面ColorRectと、中央に配置した自作のPanelContainerを組み合わせて、確認画面を自作しました。

ResetOverlay (ColorRect, 半透明)
└ ResetCenter (CenterContainer)
  └ ResetConfirmPanel (PanelContainer)
    └ (説明文とボタン2つ)

表示・非表示はvisibleプロパティを切り替えるだけなので、ポップアップ特有の仕組みを使わずに済み、他のUIと完全に同じテーマ・同じ見た目に統一できました。

リセット処理で表示が更新されない不具合とその修正

リセット機能を実装した直後、「電力は0に戻るが、他の資源やアップグレードの表示は元のまま残ってしまう」という不具合が発生しました。原因は、表示更新処理が「条件を満たしたら表示する」という片方向の書き方になっており、「条件を満たさなくなったら非表示に戻す」処理が抜けていたことでした。

# 修正前(else分岐がない)
if GameManager.upgrade_stage >= 2:
    oxygen_row.visible = true
    oxygen_label.text = "酸素: %s" % _format_number(GameManager.oxygen)

# 修正後
if GameManager.upgrade_stage >= 2:
    oxygen_row.visible = true
    oxygen_label.text = "酸素: %s" % _format_number(GameManager.oxygen)
else:
    oxygen_row.visible = false
    oxygen_label.text = "酸素: 0"

条件分岐を書くときは、「条件を満たすとき」だけでなく「満たさなくなったとき」の処理もセットで考える必要がある、という基本に立ち返って修正しました。

隠しエンディングを実装する

「すべての設備を均等に育てる」という条件にした理由

効率だけを考えるなら、コストパフォーマンスの良い設備に資源を集中させるのが最適解になりがちです。今回は、あえてそれとは違う遊び方(4種類の設備をまんべんなく育てる)をした人へのご褒美として、隠しエンディングを用意しました。

発動判定と演出

const HIDDEN_ENDING_THRESHOLD: int = 20
var hidden_ending_triggered: bool = false


func _check_hidden_ending() -> void:
    if hidden_ending_triggered:
        return
    if power_panel_count >= HIDDEN_ENDING_THRESHOLD \
            and oxygen_generator_count >= HIDDEN_ENDING_THRESHOLD \
            and parts_factory_count >= HIDDEN_ENDING_THRESHOLD \
            and central_control_count >= HIDDEN_ENDING_THRESHOLD:
        hidden_ending_triggered = true
        hidden_ending_unlocked.emit()

発動すると、画面全体を覆う金色のオーバーレイと、光の粒子(CPUParticles2D)の演出が加わり、ECHOが特別なセリフを話します。hidden_ending_triggeredのフラグをセーブデータにも含めることで、一度見た演出はセーブデータを引き継ぐ限り再現される仕組みにしています。

Web書き出し後に見つかった不具合

黒い帯ができる問題とストレッチ設定

実際にWeb書き出しをしてブラウザで確認したところ、ウィンドウサイズによっては画面の周囲に黒い帯ができてしまいました。原因は、プロジェクトの表示設定でストレッチ(引き伸ばし)モードが設定されていなかったことです。

[display]
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"

project.godotにこの設定を追加することで、ウィンドウサイズに合わせてゲーム画面が正しく拡大縮小されるようになりました。

UIの縦幅が収まりきらない問題

もう1つ、環境によっては画面下部の「発電する」ボタンが見切れてしまう不具合もありました。原因は単純で、各要素の余白や間隔を積み上げた結果、UI全体の縦方向の合計サイズが、実際の画面の高さを超えてしまっていたことです。外周の余白やカード内のパディングを詰め、ボタンの高さも調整することで、一般的な画面サイズに収まるようにしました。

おわりに

本編8回に加えて、この番外編2回を通じて、企画時点では想定していなかった課題(導入の分かりにくさ、Web書き出し特有の不具合など)にも向き合うことになりました。実際に手を動かし、動くものを触りながらでないと気づけない問題が多くある、というのが今回の一番の実感です。

STATION OPSでの学びは、今後予定しているGodotの電子書籍化にもつなげていきたいと考えています。

Godot学習におすすめのオンライン講座

実装しながら見つかる不具合への対処力は、基礎知識があるほど身につけやすくなります。体系的な学習にはこちらもおすすめです。

Godotのコースを見る

参考リンク


コメント

タイトルとURLをコピーしました