はじめに
超ミニ4X(シヴィライゼーション風のターン制経済シミュレーション)ブラウザゲーム「小さな帝国」の開発連載、第7回です。
- サービスURL:https://satou20250828.github.io/micro-empire/
- リポジトリ:https://github.com/Satou20250828/micro-empire
| 回 | 内容 |
|---|---|
| #1 | 企画・課題発見 |
| #2 | 要件定義 |
| #3 | 設計(技術選定・状態設計・アーキテクチャ) |
| #4 | 開発①ゲームロジック実装編 |
| #5 | 開発②UI・画面実装編 |
| #6 | 開発③テスト・CI/CD構築編 |
| #7(本記事) | 開発中に遭遇した技術的問題 |
| #8 | 完成・振り返り |
今回は、実装・運用の過程で実際につまずいた技術的な問題と、その解決策をまとめます。
問題1:全体再描画のたびに、開いたばかりの<select>が閉じてしまう
#3で書いた通り、状態が変わるたびにapp.innerHTMLをまるごと作り直す設計にしています。この設計は難易度選択の<select>要素と相性が悪く、選択肢を開くクリックがそのままclickイベントの処理に到達すると、直後のrender()で<select>自体が作り直され、開いたばかりのドロップダウンが即座に閉じてしまう問題が発生しました。
// main.js
app.addEventListener('click', (event) => {
if (event.target.closest('#reset-game')) {
window.location.reload()
return
}
// ネイティブ<select>を開くクリックがここまで到達すると、下の再描画で
// select要素自体が作り直され、開いたばかりのドロップダウンが即座に閉じてしまう
if (event.target.closest('#difficulty-select')) {
return
}
// ...
})
対処は単純で、<select>要素へのクリックをclickハンドラの早い段階で無視し、render()を呼ばないようにしただけです。値の変更自体は別途changeイベントで拾っており、render()を呼ぶタイミングを選択完了後(=change発火時)に限定することで解決しました。「状態が変わったら常に全体を描き直す」というシンプルな設計は見通しが良い一方、ネイティブのUIコンポーネントが持つ内部状態(ドロップダウンの開閉など)とは競合しうる、という制約に気づいた実例です。
問題2:GIFが動かない原因を、思い込みで決めつけなかった話
READMEに埋め込んだゲームプレイGIFが、実際にはアニメーションせず静止画のように表示される問題がありました(Issue #42)。
最初はGitHub側の表示制限やキャッシュを疑いましたが、生成したGIFファイル自体をバイナリレベルで解析(Graphic Control Extensionの個数・delay time・NETSCAPE2.0ループ拡張の有無)したところ、フレーム数もループ設定も正常で、ファイルフォーマット自体には異常が見当たりませんでした。実際にローカルの簡易HTTPサーバー経由でブラウザに表示させて確認したところ、撮り直した新しいGIFは問題なくアニメーションしました。
原因の完全な特定には至っていませんが、「ファイル構造は正しいのに動かない」というケースに直面した際、思い込みで原因を決めつけず、実機で見た目を確認するまで完了と判断しないことの重要性を再認識しました。
問題3:Mermaidの太い矢印は、パイプ記法でラベルを付けられない
READMEの画面遷移図をMermaidで書く際、太い矢印(==>)に対してパイプ記法でラベルを付けようとしたところ、図全体が描画されない問題に遭遇しました(Issue #46)。
' 動かなかった書き方
Main ==>|勝利条件達成| Victory
' 正しい書き方
Main == 勝利条件達成 ==> Victory
Mermaidでは矢印の種類ごとにラベルの書き方が決まっており、パイプ記法(-->|ラベル|)が使えるのは通常の矢印のみで、太い矢印は== ラベル ==>という別記法が必要でした。パースエラー時にエラー箇所が分かりにくかったため、ノード定義とリンク定義を別ブロックに分けて書くようにし、以降は問題箇所を特定しやすくしています。
問題4:サブグラフのdirection指定が無視されるケース
同じくREADMEの画面遷移図で、モーダル3つを縦に並べるためにsubgraph内でdirection TBを指定しようとしましたが、意図通りに反映されませんでした。
Mermaidには「サブグラフ内のノードが外部ノードとリンクしている場合、direction指定は無視される」という既知の制限があります。今回はメイン画面からモーダル3つ全てに線が伸びておりこのケースに該当していたため、directionは指定せず自動レイアウトに委ねることにしました。結果として、指定なしでも意図通り縦に並んだため、この制限自体が実害にはなりませんでしたが、「指定したのに効かない」という挙動の原因を、ライブラリの既知の制限として切り分けられたのは収穫でした。
問題5:Git Flow運用ではCloses #46が効かないことがある
PRの説明文にCloses #46と書いても、そのPRがdevelopブランチへマージされる場合はIssueが自動クローズされませんでした。GitHubの自動クローズ機能は、リポジトリのデフォルトブランチ(このプロジェクトではmain)へのマージ時にしか働かない仕様のためです。Git Flowではfeature/*はdevelopへマージするのが基本のため、実装のPRをマージした段階では毎回手動でIssueをクローズする運用にしています。GitHub Flow(mainに直接マージ)を前提にした機能なので、Git Flow運用ではこの前提が崩れる、という噛み合わせの問題でした。
問題6:CIの「deprecated」警告の直接原因は、Node.jsではなくActions自体だった
CIのログに「Node.js 20 is deprecated」という警告が出た際、まずNode.jsのバージョンを疑いました。調べてみると、Node.js 20は2026年4月に既にEOL(サポート終了)済みでしたが、警告の直接の原因はactions/checkout@v4やactions/setup-node@v4といった、CIで使っているGitHub Actions自体が古いバージョンのままだったことでした。Node.jsのバージョンを上げるだけでなく、actions/checkout・actions/setup-node・actions/configure-pages・actions/upload-pages-artifact・actions/deploy-pagesをすべて最新版へ更新することで、警告が解消しました。
次回予告
次回(#8)は完成・振り返り編です。連載を通しての開発全体を振り返り、1年前の自分と比べて何が変わったかを書きます。