このサイトは、只今WEB業界で活躍中のデザイナー、プログラマーの方々の情報を集めたweb統合情報サイトです。

Archives Details

【R-CHR】Rust + egui でファミコン用スプライトエディタを作った話 その1 概要とアーキテクチャ

Rust

2026.09.22

どもです。

ちょっと前の事になりますが、Rust でファミコン(NES)用のスプライト(CHR)エディタを作った話でもつらつらと記事を書いていこうかと思います。

名前は R-CHR。Rust で書いた CHR エディタなので、そのまんまの名前となります。

リポジトリはこちら。

ファミコンのグラフィックいじりといえば YY-CHR が定番中の定番でして、自分もずっとお世話になっていたのですが、「描画ツールがもう少し欲しい」「PNG をもっと雑に放り込みたい」「Mac でもネイティブに動いてほしい」といった欲が出てきまして。だったら自分で作ってしまえ、と。

ということで、今回から数回に分けて、この R-CHR の技術的な中身を順を追って解説していく形になります。ファミコンのグラフィック形式の話から、egui での UI 構築、描画アルゴリズム、PNG インポート、macOS ネイティブ対応、GitHub Actions でのリリースまで、一通り触れていく予定です。

連載の予定

  1. 概要とアーキテクチャ(この記事)
  2. NES の CHR フォーマット(2BPP)と iNES ヘッダ、パレット
  3. egui で組む 3 パネル UI とバンクビュー
  4. ドットエディタの基礎とアンドゥ設計
  5. 図形ツール(Bresenham・楕円・塗りつぶし・スタンプ)
  6. PNG インポートと 3 つのマッピング戦略
  7. macOS ネイティブメニュー・多言語対応・GitHub Actions でのリリース

R-CHR でできること

まずはどんなものかをざっくりと。

  • .nes(iNES 形式)/ .bin(生の CHR バイナリ)/ .zip(中の .nes を自動展開)を開いて編集・保存
  • CHR データ全体を 16 タイル幅の縦長テクスチャとして連続スクロール表示
  • 8 / 16 / 32 / 64 / 128px の 5 段階で編集単位(フォーカスサイズ)を切り替え
  • ペン・パターンペン・直線・矩形(枠 / 塗り / パターン)・楕円(枠 / 塗り)・塗りつぶし・スタンプの 10 種類の描画ツール
  • 1 ストローク = 1 アンドゥ単位の Undo(最大 100 ステップ)
  • タイルブロックのコピー & ペースト
  • インデックスカラー / フルカラー / 透過 PNG のインポート(3 種類のマッピング戦略)
  • YY-CHR 互換の .pal / .dat パレットファイル読み書き
  • macOS はネイティブメニューバー、日本語 / 英語の UI 切り替え

YY-CHR の基本機能は押さえつつ、描画周りと画像の取り込みを厚くした、という感じですね。

技術スタック

言語は Rust(edition 2024)、GUI は egui / eframe を採用しています。

Cargo.toml の依存はこんな感じです。

[package]
name = "R-CHR"
version = "0.1.0"
edition = "2024"

[dependencies]
eframe = "0.31"
egui = "0.31"
egui_extras = { version = "0.31", features = ["svg"] }
rfd = "0.15"
zip = { version = "2", default-features = false, features = ["deflate"] }
image = { version = "0.25", default-features = false, features = ["png", "bmp"] }
png = "0.17"

[target.'cfg(target_os = "macos")'.dependencies]
muda = "0.15"
objc2-app-kit    = { version = "0.2", default-features = false, features = ["NSAlert", "NSAppearance", "NSApplication"] }
objc2-foundation = { version = "0.2", default-features = false, features = ["NSThread"] }
クレート 用途
eframe / egui ネイティブウィンドウとイベントループ、即時モード GUI
egui_extras SVG アイコンの読み込み(svg feature)
rfd OS ネイティブのファイルダイアログ(開く / 保存)
zip ZIP 内の .nes を取り出す
image PNG / BMP を RGBA に展開する。アイコン読み込みにも使用
png PNG の PLTE / tRNS チャンクなど低レベル情報にアクセスする
muda macOS のネイティブ NSMenu 構築(macOS のみ)
objc2-app-kit / objc2-foundation NSAppearance(ダークモード)や NSAlert の呼び出し(macOS のみ)
なぜ egui なのか

Rust の GUI ライブラリはいくつか選択肢がありますが、今回は egui にしました。

理由はシンプルで、即時モード(immediate mode)GUI なので、ドットエディタのような「毎フレーム状態から絵を描き直す」タイプのアプリと相性がとても良いからです。ウィジェットツリーを保持して差分更新する、みたいなことを考えなくて良い。update() の中で「今の状態」をそのまま描けば、それが画面になります。

あとは単一バイナリで Windows / macOS / Linux に配れるという点も大きいですね。ここは後の回で GitHub Actions のビルドと合わせて触れます。

ちなみに、macOS のメニューバーだけは egui のメニューではなくネイティブ NSMenu を使っています。この辺りの「egui で足りないところをどう埋めたか」は最終回で。

全体アーキテクチャ

構造はこんな形になっています。

┌──────────────────────────────────────────────────────────┐
│  eframe(ネイティブウィンドウ・OpenGL / Metal)           │
│  ┌────────────────────────────────────────────────────┐  │
│  │  egui(即時モード GUI レンダリング)               │  │
│  │  ┌──────────────────────────────────────────────┐  │  │
│  │  │  editor::app::RChrApp(アプリ状態)          │  │  │
│  │  │    update() ─ 毎フレーム呼ばれる UI 構築     │  │  │
│  │  └──────────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────┘
         │                    │
    io モジュール          model モジュール
    ├── chr.rs              └── palette.rs
    ├── nes.rs
    └── png.rs

[macOS のみ]
native_menu.rs ──── muda(NSMenu)
                └── objc2-app-kit(NSAppearance)

モジュールは大きく 3 層に分かれています。

責務 依存先
model NES パレットのデータ表現(MasterPalette / DatPalette なし
io CHR のデコード / エンコード、iNES パース、PNG 変換 model
editor egui の UI と、アプリ状態 RChrApp io, model

依存の方向は常に editor → io → model の一方通行で、モデル層は egui にも他のモジュールにも依存しません。CHR の 2BPP 変換や PNG のマッピングといった「純粋なデータ処理」が UI から切り離されているので、この後の記事でもその順番で解説していけるという寸法です。

ディレクトリ構成
src/
  main.rs              — エントリポイント & ウィンドウ設定
  native_menu.rs       — macOS ネイティブメニュー(muda)
  editor/
    app.rs             — アプリ状態 & メインループ(eframe::App)
    bank_view.rs       — バンクビュー(CHR 全体表示 & タイル選択)
    clipboard.rs       — タイルのコピー & ペースト
    dot_editor.rs      — ドットエディタ & 10 種類の描画ツール
    file_ops.rs        — 開く / 保存 / 新規作成
    i18n.rs            — 日本語 / 英語の UI 文字列
    info_panel.rs      — 右パネル(パレット・描画色・情報表示)
    keyboard.rs        — キーボードショートカット
    png_import.rs      — PNG インポートダイアログ
    setup.rs           — フォント設定
    theme.rs           — 色・フォント・サイズ定数
    mac/
      menu_bar.rs      — egui メニューバー(macOS 以外)
      menu_events.rs   — macOS ネイティブメニューのイベント処理
  io/
    chr.rs             — CHR のデコード / エンコード / レンダリング
    nes.rs             — iNES パーサ / RomData 型
    png.rs             — PNG / BMP インポート(マッピング & CHR 書き込み)
  model/
    palette.rs         — NES パレット(MasterPalette / DatPalette)

assets/
  rchr.pal             — デフォルトのマスターパレット(YY-CHR 互換 .pal)
  rchr.dat             — デフォルトのパレットセット(YY-CHR 互換 .dat)
  rchr.bin             — 起動時に表示する CHR データ(R-CHR ロゴ)
  nes.pal              — NES 標準 64 色パレット(リセット用)
  icons/               — ツールバー用 SVG アイコン
  fonts/               — Noto Sans JP(日本語表示用)

editor/ 配下は「app.rs を薄いオーケストレーターに保ち、機能ごとに impl RChrApp ブロックを別ファイルへ分割する」という方針で書いています。Rust は同じ構造体の impl を複数ファイルに分けて書けるので、app.rs には構造体定義と update() の骨格だけを置き、バンクビューは bank_view.rs、ドットエディタは dot_editor.rs、といった具合に責務ごとに分けています。

最初は 1 ファイルにベタ書きだったのですが、描画ツールを増やしていくうちに 1,700 行を超えてきて、いかぁーん、となって分割した経緯があります。。

画面レイアウト

画面は左から順に 3 つのパネルで構成されています。

パネル 位置
バンクビュー 最低 410px。ウィンドウ幅に合わせて整数倍で拡大
ドットエディタ 中央 リサイズ可能。最低 180px
情報パネル 245px 固定

バンクビューで編集したいタイルをクリックすると、ドットエディタにそのブロックが拡大表示され、右の情報パネルでパレットや描画色を選ぶ、という流れです。この辺りの egui での組み方は第 3 回で。

ビルドと起動

Rust 1.80 以降があればビルドできます。依存ライブラリは Cargo が勝手に取ってきてくれます。

git clone https://github.com/retrodig/r-chr.git
cd r-chr
cargo run --release

起動すると assets/rchr.bin に埋め込んである R-CHR ロゴ入りの CHR データが表示されます。ウィンドウに .nes ファイルをドラッグ & ドロップすれば、そのまま中身の CHR-ROM が開きます。

ビルド済みのバイナリは GitHub の Releases に Windows / macOS / Linux 向けを置いてありますので、Rust 環境が無い方はそちらからどうぞ。

まとめ

  • R-CHR は Rust + egui で作った NES 用 CHR タイルエディタ
  • YY-CHR の基本機能に加えて、10 種類の描画ツール、PNG インポート、連続スクロール表示などを搭載
  • 構成は editor → io → model の 3 層で、データ処理は UI から分離
  • 機能ごとに impl RChrApp をファイル分割し、app.rs は薄く保つ

次回は、そもそもファミコンの絵がどういうバイト列で格納されているのか、という CHR の 2BPP フォーマットと iNES ヘッダ、パレットの話になります。ここが分かると、あとの描画ツールや PNG インポートの実装がすんなり読めるようになるので、まずはデータの話から。

ではではぁ。

またまたぁ。

Comment

Related Article

【R-CHR】Rust + egui でファミコン用スプライトエディタを作った話 その1 概要とアーキテクチャ

2026.09.22

ゲーム開発に必要な基本数学入門

2025.08.15

Godot 4 & Rust 始め方

2025.06.19

Rustでつくる ふっかつのじゅもん

2025.04.29

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その5

2025.04.14

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その4

2025.04.10

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その3

2025.04.08

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その2

2025.04.07

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その1

2025.04.05

keyring-rsで、Macのキーチェーンに登録する。

2025.04.04

CATEGORY LIST

LATEST NEWS

【R-CHR】Rust + egui でファミコン用スプライトエディタを作った話 その1 概要とアーキテクチャ

Rust

2026.09.22

M5Stack Stopwatchで、オリジナル LT(Lightning Talks)タイマーを作成した。

電子工作

2026.09.21

【Railway】GitHub Actions で定期的にサーバーをリスタートさせる

運用

2026.08.30

ラズパイが高騰しているのでストレス解消のためAliExpressで購入した電子パーツ達

電子工作

2026.08.02

【2026】【VMware Fusion】MacでWindowsを無料で扱う。

windows

2026.07.19

Raspberry Pi 5 でマインクラフトサーバーを立てる(Java版 × 統合版クロスプレイ対応)

RaspberryPi

2026.06.24

ラズパイが高い。

RaspberryPi

2026.05.26

【Claude Code】フル稼働。ToDo Appを様々なGUIフレームワーク用いて作らせる。

AI・Bot・algorithm

2026.05.24

Macで歩く「たのしいバイナリの歩き方」うさみみハリケーンの代わりに、Cheat Engine / Bit slicerを使用する

アセンブラ

2026.04.12

Macで歩く「たのしいバイナリの歩き方」

アセンブラ

2026.04.10

【Railway】ひたすら安く個人開発サービスを運用する計画

サーバー

2026.04.06

たびのきろく

イベント

2026.02.23

RANKING

Follow

SPONSOR

現在、掲載募集中です。



Links

About Us

WEBデザイナーの、WEBデザイナーによる、WEBデザイナーの為のサイト。「みんなで書こう!」と仲間を募ってみたが、結局書くのは自分だけとなってしまいました。日々のメモを綴っていきます。

Entry Profile

Graphical FrontEnd Engineer
- Daisuke Takayama

MAD CITY 北九州市で生まれ育つ。20代はバンド活動に明け暮れ、ふと「webデザイナーになりたい。」と思い、デジタルハリウッド福岡校入学。卒業後、数々の賞を受賞、web業界をざわつかせる。
現在、主に、ゲーム制作中心に港区六本木界隈で活動中。

FOLLOW US