ファミコン風2Dアクションゲームのステージを、自作エディタで作る話

水彩画イメージ C#

個人で進めているファンプロジェクトで、ステージマップを視覚的に構築するための 自作エディタ「StageSmith Editor」を作りました。

先日も少しだけイントロ的に記事を出しましたが、このエディタはOSS(オープンソースソフトウェア)で、GitHubで公開中です(^^)/

GitHub – unlimitedloop-admin/2dgame-mapeditor: Open-source editor application built to support the development of my own action games.
Open-source editor application built to support the development of my own action games. – unlimitedloop-admin/2dgame-map…

今回はその紹介と、 開発を通して改めて実感した設計の話を書いてみます。


どんなエディタを作ったのか?

StageSmith Editor(愛称:ステージ職人)は、Windows環境で動く2Dアクションゲーム用の ステージマップエディタです。

ファミコン版ロックマン風のステージ構造を想定して作っています。

もともとは、ステージのマップデータをソースコード上に直接配列として書いていたのですが、

  • マップを1マス直すだけでもビルド・実行して確認が必要
  • マップ設計とコード記述が分離していないので、デザインに集中しづらい

という問題が積み重なり、「だったら専用のツールを作ってしまおう」と思い立ったのがきっかけです。

主な機能

  • タイルパレットからのマップ配置・編集
  • ページ(部屋)単位でのステージ構造管理
  • ページ同士の接続を視覚的に編集するノードエディタ
  • タグ・ブックマーク・マーカーによる編集補助
  • タイル検索/置換、メタタイル(プリセットタイル)機能
  • ゲーム側で読み込めるバイナリ・定義ファイルの出力

技術構成

項目内容
言語C#
フレームワーク.NET 8 / Windows Forms
アーキテクチャClean Architecture(4層構成)
UI補助DockPanel Suite

Windows Forms+DockPanel Suiteという、少し懐かしい構成ですが、 「ドッキング可能な補助ウィンドウを好きなだけ並べられる」という開発ツールらしいUIには 今でも十分実用的だと感じています。


エディタの機能紹介

数ある機能の中から、特に「作っていて楽しかった/設計で苦労した」3つを紹介します。

メタタイル定義機能

同じタイルの組み合わせ(地面のブロックや足場など)を、何度も1マスずつ配置するのは 地味に手間がかかるので、複数タイルの組み合わせを手軽に利用できれば作成効率が上がりますよね。メタタイル機能は、複数タイルをまとめて1つのプリセットとして 登録し、まとめて配置できるようにする機能です。

内部的には、こんな単純なデータ構造を持っています。

{
  "id": 1,
  "name": "GroundBlock",
  "width": 2,
  "height": 2,
  "tiles": [
    [1, 2],
    [3, 4]
  ]
}

タイルIDの集合体でしかないので実装としては軽量ですが、「範囲選択 → メタタイル化 → 別の場所にクリック一発で再配置」という一連の流れができるようになると、 体感の作業効率はかなり変わります。

ページノードエディタ機能

ステージは「ページ(部屋)」の集まりで構成されていて、部屋同士がどう繋がっているかを 管理する必要があります。これをテキストやテーブルで管理すると位置関係が把握しづらいため、 専用のノードエディタを別ウィンドウとして用意しました。

  • 設定済みのページを緑、未接続を赤、無効化を灰色など色分け表示
  • 既存ページの周囲に「ここに部屋を作れます」という候補位置を自動表示
  • 右クリックメニューから、新規ページの作成と接続を同時に実行

ツールボックスを持たず、マウス操作だけで完結することにこだわりました。 左ドラッグでビュー移動、クリックで選択、という基本操作だけで、迷わず部屋を 繋げていけるようにしています。

バイナリデータ出力 〜 ゲームアプリへの取り込みの流れ

最後に、作ったステージをゲーム側で実際に使えるようにする部分です。 StageSmith Editorでは、編集データと実行データを明確に分離しています。

編集
 ↓
.ssestage(エディタの正規データ)
 ↓
Export BIN
 ↓
.bin(ゲーム実行用バイナリ)+ .def(ロジック定義データ)
 ↓
ゲームアプリ側で Import

.bin は1ページあたり256byteの固定長データで、先頭16byteに部屋番号や隣接情報を 詰め込んだ「ページヘッダ」を持たせています。ファミコン世代のROMのヘッダ構造を イメージしながら設計したので、地味にお気に入りのフォーマットです。

ステージタイル構造をそのままバイト単位基準で出力したバイナリファイル、敵オブジェクトなどを座標単位で記録したjsonファイルにも対応

.bin はいつでも .def から再生成できる「派生データ」という位置づけにしているので、 仮にフォーマットを拡張したくなっても、正規データである .def さえ壊さなければ 安心して作り直せます。

ちなみにここで出力した.bin ファイルは私が並行で開発を進めているロックマンのファンゲームへそのままインクルードしています。

エディタでステージを作り、出力された軽量のバイナリファイルを用いてゲームを動かす・・・

その仕組みは任天堂さんやセガさんなど、大手ゲームメーカーの開発部でも実際に用いられている事でもあるのです。


SOLID原則とKISSの重要性について

ここからは少し技術寄りの話です。今回のエディタ開発を通して、 「原則は知っているつもりでも、実際に手を動かすと初めて腹落ちする」ことがたくさんありました。

「読み取り専用モード」で学んだこと

わかりやすい例が、読み取り専用(Read-Only)モードの実装です。

最初は「編集操作のエントリーポイントそれぞれにガード処理を入れる」という 素朴なやり方で実装していました。しかし機能が増えるたびに、

  • 「あ、このメニューにもガードを入れ忘れてた」
  • 「ショートカットキー経由だとガードをすり抜ける」

というモグラ叩きが発生してしまいました。

最終的には、既存の RefreshXxxMenuState() / UpdateEditorAvailability() という 「UI状態を更新する」仕組みに乗せる形に変更しました。個別の処理にガードを仕込むのではなく、 そもそも無効化されたUIからは操作が発生しないという状態にしたことで、 抜け漏れの心配自体がなくなりました。

これはSOLID原則で言うところの「単一責任」に近い話だと思っています。 「編集可否の判定」という責任を、個々のハンドラではなく、UI状態管理の仕組みに 一箇所に集約したことで、見通しがよくなりました。

KISSと「3ファイル目」ルール

もう1つ、開発中に自分の中で決めていたルールが「同じような処理が3つ目のファイルに 出てきたら、そこでリファクタリングを検討する」というものです。

たとえば、複数のコントロールでタイルセット画像をクローンして保持する処理が 似たような形で重複してきたタイミングで、SafeTilesetHolder という共通クラスに 切り出しました。

  • 1〜2ファイルの重複はまだ許容範囲
  • 3ファイル目が出てきたら、それは「パターン」であって「偶然の一致」ではない

という肌感覚のラインを持っておくと、「とにかく早めに共通化する」でも 「一切共通化しない」でもない、ちょうどいいバランスを取りやすいと感じています。

KISS(Keep It Simple, Stupid)は「シンプルに保て」という言葉だけを見ると 抽象的ですが、実践してみると「シンプルさ」とは機能を削ることではなく、 責任の置き場所を迷わせないことなんだな、と今回の開発で実感しました。


おわりに

個人開発でここまでのツールを作るのは正直かなり手間がかかりましたが、 「マップをその場で見ながら試行錯誤できる」という体験は、それに見合う価値があったと感じています。

エディタの詳しい使い方やファイル仕様については、別途ヘルプサイトも公開しているので、 気になる方はこちらもどうぞ。

👉 StageSmith Editor ヘルプ

今後もステージ制作を進めながら、機能追加や記事の続編も書いていく予定です。

最後まで読んでいただきありがとうございました(^-^)/

コメント

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