※本記事にはアフィリエイト広告(Udemy)を含みます。
これは何の記事か
STATION OPSは、①~⑧の全8回で企画からWeb公開まで一通り完成しました。この番外編では、公開後に追加で行ったビジュアル面のブラッシュアップを紹介します。
今回追加した機能(背景の段階的変化、上長の評価システムなど)は、企画・実装を同じ場で進めず、先に別の場で仕様を整理してから実装に着手しています。作業の種類(企画を練る作業と、手を動かして実装する作業)を分けておくと、それぞれに集中しやすくなります。
NovelAIで背景素材を作る
「無人・パステル調」が伝わらなかった試行錯誤
背景素材はNovelAIで生成しましたが、一発で狙い通りの絵は出ませんでした。最初に「無人の駅の車両基地、パステルカラー、no character」といった指示を出したところ、次のような結果が続きました。
- 精巧な女性キャラクターが中心に大きく写った絵が出る(トーンも暗め)
- ネガティブプロンプトを強めても、今度は隅に小さな人影が残った絵が出る
- トーンも紫がかった暗めの配色に寄ってしまう
「no character」を指示に含めても人物が出てきてしまう、というのはよくあるつまずきポイントです。ネガティブプロンプト側を{{}}で強調して人物関連の単語を並べる、といった対策も試しましたが、改善はしたものの完全には安定しませんでした。
「2D platformer background art」という切り口が効いた
最終的に効果があったのは、「masterpiece, best quality, very aesthetic, 2D platformer background art, side-scrolling」という、ゲームの背景素材であることを明示する切り口でした。「背景だけを描く」という文脈が伝わることで、人物が出にくくなったと考えられます。この指示に、パステルカラーの色指定を組み合わせたところ、人物なし・狙い通りのトーンの絵が得られました。
Godotに組み込む
生成された画像はゲームの想定アスペクト比(16:9)と一致しないことが多いため、Pythonの画像処理ライブラリ(Pillow)で中央を基準にクロップし、リサイズしてから使用しています。
from PIL import Image
img = Image.open("background_raw.png").convert("RGB")
W, H = img.size
target_ratio = 1280 / 720
target_h = int(W / target_ratio)
if target_h < H:
top = (H - target_h) // 2
img = img.crop((0, top, W, top + target_h))
img = img.resize((1280, 720), Image.LANCZOS)
img.save("base_background.png")
Godot側では、この画像をTextureRectに設定し、stretch_modeを「Keep Aspect Covered」にすることで、画面サイズが変わってもアスペクト比を保ったまま画面いっぱいに表示できます。
背景が「進化」して見えるようにする
固定座標の装飾が動的レイアウトとズレて破綻した失敗
最初の設計では、背景画像に「窓」の透過部分と装飾線を焼き込み、その裏に星のレイヤーを重ねて「窓の外に星が見える」演出にしようとしました。しかし、これはうまくいきませんでした。UIのカードやボタンはVBoxContainerなどで動的に配置されるため、実行時の実際の位置は環境によって変わります。一方、背景画像側の「窓」は固定座標で描いてしまっていたため、両者の位置が噛み合わず、カードの隙間から窓や装飾線が中途半端に見切れてしまいました。
修正方針:画面端の帯だけに要素を配置する
この失敗を踏まえ、設計をやり直しました。UI側はMarginContainerで画面端に一定の余白を確保しているため、その余白の範囲(画面端から一定距離の帯)だけに星や光の演出を配置すれば、UIのレイアウトが多少変わってもぶつからないと考えたためです。
BORDER = 60 # 外周のこの幅の中だけに星を配置する
def in_border_band(x, y):
return x < BORDER or x > W - BORDER or y < BORDER or y > H - BORDER
星を生成するときにこの判定を使い、条件を満たす座標にだけ星を配置するようにしました。窓の穴あき表現もやめ、背景画像自体は単純なグラデーションのみにしています。
「暗→明」のオーバーレイを追加する
背景画像1枚だけでは、進行に応じた変化が伝わりにくいという指摘を受けました。そこで、画面全体を覆う暗めの色のColorRectを重ね、段階が進むほどそのアルファ値(不透明度)を下げていく処理を追加しました。
func _apply_background_for_stage(stage: int, animate: bool) -> void:
var dim_alpha: float = (1.0 - stage / 5.0) * 0.6
if animate:
var tween := create_tween()
tween.tween_property(dim_overlay, "color:a", dim_alpha, 1.5)
else:
dim_overlay.color.a = dim_alpha
段階0では画面全体がやや沈んだトーンになり、段階5に向けて徐々に明るく鮮やかになっていきます。create_tween()で滑らかに変化させることで、切り替わりが唐突にならないようにしています。
評価ランク(E1CS)を実装する
設備の段階に応じて、E・D・C・B・Sというランクを表示するようにしました。
func get_rank() -> String:
match upgrade_stage:
0, 1:
return "E"
2:
return "D"
3:
return "C"
4:
return "B"
5:
return "S"
_:
return "E"
ランクが実際に切り替わったタイミングだけ、ECHOがコメントするようにしています。段階が上がるたびに毎回セリフを出すのではなく、ランクという意味のある区切りが変わったときだけ反応させることで、演出にメリハリを持たせています。
UIの視認性を改善する
PanelContainerとPanelは別物だった、という落とし穴
カードの背景を白くはっきりさせようとテーマ(Theme)でPanel/styles/panelを設定したのですが、実際にはまったく反映されていませんでした。原因は、UIで使っていたノードがPanelではなくPanelContainerだったことです。名前が似ているためどちらも同じ設定で効くように見えますが、Godotのテーマは「ノードの種類ごと」にスタイルを参照するため、PanelContainerにはPanelContainer/styles/panelという別のキーで指定する必要がありました。
Panel/styles/panel = SubResource("StyleBoxFlat_card")
PanelContainer/styles/panel = SubResource("StyleBoxFlat_card")
両方に同じスタイルを指定しておくことで、どちらのノードを使っても正しく反映されるようになりました。似た名前のノードタイプがある場合は、実際にどちらを使っているか確認したうえでテーマを設定する必要がある、という教訓です。
アイコンを追加する
資源(電力・酸素・部品)と各アップグレードに、それぞれ対応するアイコンを追加しました。フラットなシルエットの図形(稲妻・しずく・歯車・ソーラーパネルなど)を単純な描画処理で組み合わせて作成しています。
2×2グリッドレイアウトへの変更
アップグレード一覧は当初、縦に1列で並べる設計でしたが、画面の縦方向が窮屈になったため、GridContainerを使って2×2の格子状に並べ替えました。
[node name="UpgradeGrid" type="GridContainer" parent="..."]
columns = 2
columnsプロパティに列数を指定するだけで、あとは子ノードを追加した順に自動で格子状に配置されます。
次回予告
番外編②では、タイトル画面の追加、「最初からやり直す」リセット機能、そしてすべての設備を均等に育てると見られる隠しエンディングを紹介します。
Godot学習におすすめのオンライン講座
テーマ(Theme)まわりの仕様は、慣れるまで戸惑いやすい部分です。体系的に学んでおくと、今回のような落とし穴にもハマりにくくなります。


コメント