<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Codigrowa Docs</title><link>https://codigrowa.batako.net/</link><description>Recent content on Codigrowa Docs</description><generator>Hugo</generator><language>ja-jp</language><atom:link href="https://codigrowa.batako.net/index.xml" rel="self" type="application/rss+xml"/><item><title>Entity 実装ガイド</title><link>https://codigrowa.batako.net/docs/guides/new-entity/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://codigrowa.batako.net/docs/guides/new-entity/</guid><description>&lt;p>このガイドでは新しい Entity を &lt;code>engine/entities/&lt;/code> に追加し、Game へ組み込むまでの流れを説明します。&lt;/p>
&lt;h2 id="1-クラス定義">1. クラス定義&lt;a class="anchor" href="#1-%e3%82%af%e3%83%a9%e3%82%b9%e5%ae%9a%e7%be%a9">#&lt;/a>&lt;/h2>
&lt;ol>
&lt;li>&lt;code>engine/entities/MyEntity.ts&lt;/code> を作成。&lt;/li>
&lt;li>&lt;code>Entity&lt;/code> を継承し、&lt;code>MyEntitySnapshot&lt;/code> 型を定義。&lt;/li>
&lt;li>コンストラクタで &lt;code>tickRate&lt;/code> や初期座標を渡す。&lt;/li>
&lt;li>&lt;code>_process(elapsed)&lt;/code> に 1 tick 分のロジックを書く。&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">import&lt;/span> { &lt;span style="color:#a6e22e">Entity&lt;/span>, &lt;span style="color:#a6e22e">EntitySnapshot&lt;/span> } &lt;span style="color:#66d9ef">from&lt;/span> &lt;span style="color:#e6db74">&amp;#39;./Entity&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">type&lt;/span> &lt;span style="color:#a6e22e">MyEntitySnapshot&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">EntitySnapshot&lt;/span> &lt;span style="color:#f92672">&amp;amp;&lt;/span> { &lt;span style="color:#a6e22e">hp&lt;/span>: &lt;span style="color:#66d9ef">number&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">export&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">MyEntity&lt;/span> &lt;span style="color:#66d9ef">extends&lt;/span> &lt;span style="color:#a6e22e">Entity&lt;/span>&amp;lt;&lt;span style="color:#f92672">MyEntitySnapshot&lt;/span>&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#a6e22e">hp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">protected&lt;/span> &lt;span style="color:#a6e22e">_process() {&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 例: 徐々に減衰
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">hp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> Math.&lt;span style="color:#a6e22e">max&lt;/span>(&lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">hp&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">getSnapshot() {&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> { &lt;span style="color:#a6e22e">x&lt;/span>, &lt;span style="color:#a6e22e">y&lt;/span> } &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">getPosition&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> { &lt;span style="color:#a6e22e">id&lt;/span>: &lt;span style="color:#66d9ef">this.id&lt;/span>, &lt;span style="color:#a6e22e">x&lt;/span>, &lt;span style="color:#a6e22e">y&lt;/span>, &lt;span style="color:#a6e22e">hp&lt;/span>: &lt;span style="color:#66d9ef">this.hp&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="2-world-へ登録">2. World へ登録&lt;a class="anchor" href="#2-world-%e3%81%b8%e7%99%bb%e9%8c%b2">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">entity&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#a6e22e">MyEntity&lt;/span>({ &lt;span style="color:#a6e22e">tickRate&lt;/span>: &lt;span style="color:#66d9ef">1&lt;/span> &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#a6e22e">x&lt;/span>: &lt;span style="color:#66d9ef">50&lt;/span>, &lt;span style="color:#a6e22e">y&lt;/span>: &lt;span style="color:#66d9ef">120&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">game&lt;/span>.&lt;span style="color:#a6e22e">addEntity&lt;/span>(&lt;span style="color:#a6e22e">entity&lt;/span>);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>Game.addEntity&lt;/code> が World 経由で &lt;code>_ready()&lt;/code> を一度だけ呼びます。&lt;/li>
&lt;li>画面遷移やアンマウント時は &lt;code>game.removeEntity(entity.id)&lt;/code> を必ず実行。&lt;/li>
&lt;/ul>
&lt;h2 id="3-描画スナップショットの更新">3. 描画スナップショットの更新&lt;a class="anchor" href="#3-%e6%8f%8f%e7%94%bb%e3%82%b9%e3%83%8a%e3%83%83%e3%83%97%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88%e3%81%ae%e6%9b%b4%e6%96%b0">#&lt;/a>&lt;/h2>
&lt;p>Skia や React 側が必要とする最小情報だけを &lt;code>getSnapshot()&lt;/code> に詰めます。大きなオブジェクトを渡すと render 毎にシリアライズコストが掛かるため、描画に不要なロジック用フィールドは含めないでください。&lt;/p></description></item><item><title>GameCanvas / Renderer</title><link>https://codigrowa.batako.net/docs/rendering/game-canvas/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://codigrowa.batako.net/docs/rendering/game-canvas/</guid><description>&lt;p>&lt;code>components/game-canvas.tsx&lt;/code> は Game からのスナップショットを &lt;code>@shopify/react-native-skia&lt;/code> で描画するデモ実装です。エンジンの状態を UI に届ける際のベストプラクティスをまとめます。&lt;/p>
&lt;h2 id="初期化フロー">初期化フロー&lt;a class="anchor" href="#%e5%88%9d%e6%9c%9f%e5%8c%96%e3%83%95%e3%83%ad%e3%83%bc">#&lt;/a>&lt;/h2>
&lt;ol>
&lt;li>&lt;code>startGame({ reset: true })&lt;/code> を &lt;code>useEffect&lt;/code> で 1 度だけ呼び、GameLoop を開始。&lt;/li>
&lt;li>&lt;code>LinearMover&lt;/code> のような Entity をインスタンス化して &lt;code>game.addEntity()&lt;/code> で登録。&lt;/li>
&lt;li>クリーンアップ時に &lt;code>game.removeEntity(entity.id)&lt;/code> を忘れない。&lt;/li>
&lt;/ol>
&lt;h2 id="レンダリング購読">レンダリング購読&lt;a class="anchor" href="#%e3%83%ac%e3%83%b3%e3%83%80%e3%83%aa%e3%83%b3%e3%82%b0%e8%b3%bc%e8%aa%ad">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">useEffect&lt;/span>(() &lt;span style="color:#f92672">=&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">unsubscribe&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">game&lt;/span>.&lt;span style="color:#a6e22e">onRender&lt;/span>((&lt;span style="color:#a6e22e">payload&lt;/span>) &lt;span style="color:#f92672">=&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">renderPayloadRef&lt;/span>.&lt;span style="color:#a6e22e">current&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">payload&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">setRenderPayload&lt;/span>(&lt;span style="color:#a6e22e">payload&lt;/span>); &lt;span style="color:#75715e">// HUD 用に tick / elapsed / entityCount だけ更新
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#a6e22e">unsubscribe&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}, [&lt;span style="color:#a6e22e">game&lt;/span>, &lt;span style="color:#a6e22e">setRenderPayload&lt;/span>]);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>GameCanvas&lt;/code> は &lt;code>renderPayloadRef&lt;/code> に最新スナップショットを保持しつつ、Zustand には HUD で必要な &lt;code>tick/elapsed/entityCount&lt;/code> だけを流します。Skia の描画ノードは React ではなく「Entity ID → SharedValue ハンドル」へ直接書き込むため、Game の内部構造を UI から mutate する必要がありません。&lt;/p>
&lt;h2 id="scheduler-を使った-hud">Scheduler を使った HUD&lt;a class="anchor" href="#scheduler-%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%9f-hud">#&lt;/a>&lt;/h2>
&lt;p>&lt;code>GameCanvas&lt;/code> では &lt;code>game.schedule()&lt;/code> と &lt;code>game.wait()&lt;/code> を使い、定期ログと遅延ログを生成しています。&lt;code>setFunctionLogs&lt;/code> で最新 4 件だけ表示する仕組みは、Scheduler の動作確認に最適です。&lt;/p></description></item><item><title>アーキテクチャ概観</title><link>https://codigrowa.batako.net/docs/runtime/architecture/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://codigrowa.batako.net/docs/runtime/architecture/</guid><description>&lt;p>Codigrowa のゲームエンジンは &lt;strong>GameLoop → Scheduler → World → Entity → Renderer&lt;/strong> という鎖で構成されています。Expo（React Native）上では &lt;code>requestAnimationFrame&lt;/code> を起点に時間が進み、React 側のコンポーネントは &lt;code>Game.onRender()&lt;/code> から受け取るスナップショットだけで描画を完結させます。&lt;/p>
&lt;pre tabindex="0">&lt;code>useGameLoop (engine/hooks/useGameLoop.ts)
 │
 ▼
┌──────────────┐
│ Game │ ← ランタイム中枢
│ • Scheduler │ ← 関数単位 tick
│ • World │ ← Entity 単位 tick
└──────────────┘
 │
 ▼
Canvas Renderer (components/game-canvas.tsx)&lt;/code>&lt;/pre>&lt;h3 id="データフロー">データフロー&lt;a class="anchor" href="#%e3%83%87%e3%83%bc%e3%82%bf%e3%83%95%e3%83%ad%e3%83%bc">#&lt;/a>&lt;/h3>
&lt;ol>
&lt;li>&lt;code>useGameLoop&lt;/code> が &lt;code>requestAnimationFrame&lt;/code> から &lt;code>dt&lt;/code>（秒）を算出し &lt;code>Game.update(dt)&lt;/code> を連続呼び出し。&lt;/li>
&lt;li>&lt;code>Game.update&lt;/code> はまず &lt;code>Scheduler.update(dt)&lt;/code> を処理し、待機中のコールバックを実行。&lt;/li>
&lt;li>続いて &lt;code>World.process(dt)&lt;/code> が全 &lt;code>Entity&lt;/code> の &lt;code>process()&lt;/code> を tick に合わせて回す。&lt;/li>
&lt;li>&lt;code>Game.notifyRender()&lt;/code> が World からのスナップショットをまとめ、&lt;code>onRender&lt;/code> 購読者（HUD や Skia Canvas）へ配布。&lt;/li>
&lt;/ol>
&lt;p>描画層は &lt;code>GameRenderPayload&lt;/code> を受け取るだけで済むため、World/Entity の内部状態を UI から直接 mutate しません。これにより、レンダリングとロジックの責務分離とデバッグ容易性を両立しています。&lt;/p></description></item><item><title>Game クラス</title><link>https://codigrowa.batako.net/docs/runtime/game/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://codigrowa.batako.net/docs/runtime/game/</guid><description>&lt;p>&lt;code>engine/Game.ts&lt;/code> はランタイムの司令塔です。World と Scheduler を束ね、1 tick ごとに状態更新と描画用スナップショット生成を行います。&lt;/p>
&lt;h2 id="ライフサイクル-api">ライフサイクル API&lt;a class="anchor" href="#%e3%83%a9%e3%82%a4%e3%83%95%e3%82%b5%e3%82%a4%e3%82%af%e3%83%ab-api">#&lt;/a>&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>メソッド&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>start({ reset?: boolean })&lt;/code>&lt;/td>
 &lt;td>GameLoop 起動。&lt;code>reset&lt;/code> で &lt;code>elapsed&lt;/code> / &lt;code>tick&lt;/code> を初期化し即スナップショット配布。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>pause()&lt;/code> / &lt;code>resume()&lt;/code>&lt;/td>
 &lt;td>&lt;code>update()&lt;/code> の実行を一時停止／再開。Zustand ストアから UI でトグル。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>stop()&lt;/code>&lt;/td>
 &lt;td>ループ停止 + Scheduler タスク破棄。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>update(dt: number)&lt;/code>&lt;/td>
 &lt;td>&lt;code>Scheduler.update(dt)&lt;/code> → &lt;code>World.process(dt)&lt;/code> → &lt;code>notifyRender()&lt;/code> の順で実行。&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;code>useGameLoop&lt;/code> から渡される &lt;code>dt&lt;/code>（秒）だけを頼りに Game は決定的に進行します。React 側で &lt;code>Game&lt;/code> や &lt;code>World&lt;/code> を直接触る場合は、状態破壊を避けるためなるべく API 経由（&lt;code>addEntity&lt;/code>, &lt;code>wait&lt;/code> など）で操作してください。&lt;/p>
&lt;h2 id="world--scheduler-依存差し替え">World / Scheduler 依存差し替え&lt;a class="anchor" href="#world--scheduler-%e4%be%9d%e5%ad%98%e5%b7%ae%e3%81%97%e6%9b%bf%e3%81%88">#&lt;/a>&lt;/h2>
&lt;p>コンストラクタでは &lt;code>new Game({ world, scheduler })&lt;/code> としてテスト用のモックを注入できます。既存インスタンスを入れ替える場合は &lt;code>setWorld()&lt;/code> / &lt;code>setScheduler()&lt;/code> を利用します。&lt;/p></description></item><item><title>デバッグ &amp; 運用</title><link>https://codigrowa.batako.net/docs/guides/debugging/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://codigrowa.batako.net/docs/guides/debugging/</guid><description>&lt;p>エンジンを拡張する際に役立つ検証フローとチェックリストです。&lt;/p>
&lt;h2 id="1-ログと-hud">1. ログと HUD&lt;a class="anchor" href="#1-%e3%83%ad%e3%82%b0%e3%81%a8-hud">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;code>Game.update&lt;/code> は &lt;code>__DEV__&lt;/code> 時に &lt;code>dt&lt;/code> をログ出力します。大量に出る場合は &lt;code>expo start --no-dev&lt;/code> で実機挙動を確認。&lt;/li>
&lt;li>&lt;code>Game.onRender&lt;/code> から受け取る &lt;code>GameRenderPayload&lt;/code> を HUD 表示し、&lt;code>elapsed&lt;/code> / &lt;code>tick&lt;/code> / &lt;code>entities.length&lt;/code> をリアルタイムに監視。&lt;code>components/game-canvas.tsx&lt;/code> の &lt;code>functionLogs&lt;/code> 実装を流用できます。&lt;/li>
&lt;/ul>
&lt;h2 id="2-テスト駆動のすすめ">2. テスト駆動のすすめ&lt;a class="anchor" href="#2-%e3%83%86%e3%82%b9%e3%83%88%e9%a7%86%e5%8b%95%e3%81%ae%e3%81%99%e3%81%99%e3%82%81">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;code>World&lt;/code> と &lt;code>Entity&lt;/code> は純粋な TypeScript クラスなので Jest で単体テスト可能。&lt;/li>
&lt;li>&lt;code>Scheduler&lt;/code> は &lt;code>update(dt)&lt;/code> を段階的に呼び、&lt;code>task.callback&lt;/code> が発火するかで検証できます。&lt;code>getTaskCount()&lt;/code> を監視すればリーク検知が容易です。&lt;/li>
&lt;/ul>
&lt;h2 id="3-典型的なトラブルシュート">3. 典型的なトラブルシュート&lt;a class="anchor" href="#3-%e5%85%b8%e5%9e%8b%e7%9a%84%e3%81%aa%e3%83%88%e3%83%a9%e3%83%96%e3%83%ab%e3%82%b7%e3%83%a5%e3%83%bc%e3%83%88">#&lt;/a>&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>症状&lt;/th>
 &lt;th>対処&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Entity が停止する&lt;/td>
 &lt;td>&lt;code>tickRate&lt;/code> が大きすぎて &lt;code>_process&lt;/code> が呼ばれていない可能性。&lt;code>__DEV__&lt;/code> ログで &lt;code>dt&lt;/code> を確認。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Renderer に反映されない&lt;/td>
 &lt;td>&lt;code>getSnapshot()&lt;/code> で必要なフィールドを返しているか確認。&lt;code>GameCanvas&lt;/code> 側で型ガードを追加。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Scheduler タスクが残り続ける&lt;/td>
 &lt;td>&lt;code>game.cancelTask(id)&lt;/code> を &lt;code>useEffect&lt;/code> のクリーンアップで呼ぶ。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>複数画面で Game を共有したい&lt;/td>
 &lt;td>&lt;code>useGameStore&lt;/code> の &lt;code>game&lt;/code> インスタンスをシングルトンとして取り回し、各画面で &lt;code>onRender&lt;/code> を個別登録。&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="4-将来の拡張ポイント">4. 将来の拡張ポイント&lt;a class="anchor" href="#4-%e5%b0%86%e6%9d%a5%e3%81%ae%e6%8b%a1%e5%bc%b5%e3%83%9d%e3%82%a4%e3%83%b3%e3%83%88">#&lt;/a>&lt;/h2>
&lt;p>&lt;code>docs/game_engine_develop_plan.md&lt;/code> に記載されている Step #4 以降（タップ入力、Signal、Sprite 切替など）は現行アーキテクチャを前提としています。API 変更時は互換性を保つため、Hugo ドキュメント内の該当章も更新してください。&lt;/p></description></item><item><title>World &amp; Entity</title><link>https://codigrowa.batako.net/docs/runtime/world-entity/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://codigrowa.batako.net/docs/runtime/world-entity/</guid><description>&lt;p>&lt;code>engine/World.ts&lt;/code> と &lt;code>engine/entities/Entity.ts&lt;/code> はエンジンの心臓部です。World が Entity を束ね、Entity は tickRate ごとに &lt;code>_process()&lt;/code> を実行します。&lt;/p>
&lt;h2 id="world-の責務">World の責務&lt;a class="anchor" href="#world-%e3%81%ae%e8%b2%ac%e5%8b%99">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;code>addEntity(entity)&lt;/code> / &lt;code>removeEntity(id)&lt;/code> / &lt;code>clear()&lt;/code> で登録を管理。&lt;/li>
&lt;li>&lt;code>process(dt)&lt;/code> で全 Entity の &lt;code>process(dt)&lt;/code> を呼び出し。&lt;/li>
&lt;li>&lt;code>getSnapshots()&lt;/code> が描画層へ渡す &lt;code>EntitySnapshot[]&lt;/code> を生成。&lt;/li>
&lt;/ul>
&lt;p>World は順序保証のため &lt;code>Map&lt;/code> を使っています。Entity 追加時に &lt;code>_onEnterWorld()&lt;/code> が走り、&lt;code>_ready()&lt;/code> が 1 度だけ実行されます。除去時は &lt;code>_onExitWorld()&lt;/code> で &lt;code>_ready()&lt;/code> の再実行を許可します。&lt;/p>
&lt;h2 id="entity-抽象クラス">Entity 抽象クラス&lt;a class="anchor" href="#entity-%e6%8a%bd%e8%b1%a1%e3%82%af%e3%83%a9%e3%82%b9">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">abstract&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Entity&lt;/span>&amp;lt;&lt;span style="color:#f92672">TSnapshot&lt;/span> &lt;span style="color:#a6e22e">extends&lt;/span> &lt;span style="color:#a6e22e">EntitySnapshot&lt;/span>&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">tickRate&lt;/span>: &lt;span style="color:#66d9ef">number&lt;/span>; &lt;span style="color:#75715e">// デフォルト 1/60 秒
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#a6e22e">tickTimer&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">// 経過時間累積
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">process&lt;/span>(&lt;span style="color:#a6e22e">dt&lt;/span>: &lt;span style="color:#66d9ef">number&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">tickTimer&lt;/span> &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#a6e22e">dt&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (&lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">tickTimer&lt;/span> &lt;span style="color:#f92672">&amp;gt;=&lt;/span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">tickRate&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">tickTimer&lt;/span> &lt;span style="color:#f92672">-=&lt;/span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">tickRate&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">_process&lt;/span>(&lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">tickRate&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">protected&lt;/span> &lt;span style="color:#66d9ef">abstract&lt;/span> &lt;span style="color:#a6e22e">_process&lt;/span>(&lt;span style="color:#a6e22e">elapsed&lt;/span>: &lt;span style="color:#66d9ef">number&lt;/span>)&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">abstract&lt;/span> &lt;span style="color:#a6e22e">getSnapshot&lt;/span>()&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#a6e22e">TSnapshot&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="tickrate-と決定性">tickRate と決定性&lt;a class="anchor" href="#tickrate-%e3%81%a8%e6%b1%ba%e5%ae%9a%e6%80%a7">#&lt;/a>&lt;/h3>
&lt;p>&lt;code>tickRate&lt;/code> は Entity ごとの更新周期です。World は &lt;code>dt&lt;/code> をそのまま流すのではなく、&lt;code>tickTimer&lt;/code> に蓄積した上で tickRate を超えた回数だけ &lt;code>_process()&lt;/code> を実行します。これにより、RAF のフレームレートが揺らいでもロジックは固定周期で進行します。&lt;/p></description></item><item><title>Scheduler</title><link>https://codigrowa.batako.net/docs/runtime/scheduler/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://codigrowa.batako.net/docs/runtime/scheduler/</guid><description>&lt;p>&lt;code>engine/Scheduler.ts&lt;/code> は時間ベースの関数実行を担当します。GameLoop の &lt;code>dt&lt;/code> を受け取り、内部に積んだタスクの &lt;code>remaining&lt;/code> 時間を減算し、0 を下回ったタイミングで実行します。&lt;/p>
&lt;h2 id="タスクモデル">タスクモデル&lt;a class="anchor" href="#%e3%82%bf%e3%82%b9%e3%82%af%e3%83%a2%e3%83%87%e3%83%ab">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">type&lt;/span> &lt;span style="color:#a6e22e">SchedulerTask&lt;/span> &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">id&lt;/span>: &lt;span style="color:#66d9ef">string&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">callback&lt;/span>&lt;span style="color:#f92672">:&lt;/span> () &lt;span style="color:#f92672">=&amp;gt;&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">remaining&lt;/span>: &lt;span style="color:#66d9ef">number&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">interval&lt;/span>: &lt;span style="color:#66d9ef">number&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">repeat&lt;/span>: &lt;span style="color:#66d9ef">boolean&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>wait(delay, callback)&lt;/code> は一度だけ実行するタスクを登録。&lt;/li>
&lt;li>&lt;code>schedule(callback, interval, { repeat })&lt;/code> はデフォルトで繰り返し（&lt;code>repeat: true&lt;/code>）。&lt;/li>
&lt;li>&lt;code>cancel(id)&lt;/code> / &lt;code>clear()&lt;/code> で後始末。&lt;/li>
&lt;/ul>
&lt;p>&lt;code>Game.update&lt;/code> が Scheduler を World より先に呼び出すため、タスク内で Entity の状態を変更しても同じ tick 内で描画に反映されます。&lt;/p>
&lt;h2 id="使い所">使い所&lt;a class="anchor" href="#%e4%bd%bf%e3%81%84%e6%89%80">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>HUD へ定期的にログを出す（&lt;code>components/game-canvas.tsx&lt;/code> 内参照）。&lt;/li>
&lt;li>Cutscene のように「数秒待ってから次アクション」を表現する。&lt;/li>
&lt;li>Entity 内から &lt;code>this.game.wait()&lt;/code> を呼びたい場合は依存注入で Game インスタンスを受け取り、&lt;code>_ready()&lt;/code> でタスク登録・ &lt;code>_onExitWorld()&lt;/code> で &lt;code>cancelTask()&lt;/code> する。&lt;/li>
&lt;/ul>
&lt;h2 id="エラーハンドリング">エラーハンドリング&lt;a class="anchor" href="#%e3%82%a8%e3%83%a9%e3%83%bc%e3%83%8f%e3%83%b3%e3%83%89%e3%83%aa%e3%83%b3%e3%82%b0">#&lt;/a>&lt;/h2>
&lt;p>タスク実行中に例外が起こっても Scheduler 自身は落ちません。&lt;code>console.warn()&lt;/code> を出した上で、単発タスクは削除、繰り返しタスクは次のインターバルへ進みます。長時間走り続けるサービスを意識し、repeat タスクでは必ず &lt;code>cancel()&lt;/code> のタイミングを決めておきましょう。&lt;/p>
&lt;h2 id="パフォーマンスのコツ">パフォーマンスのコツ&lt;a class="anchor" href="#%e3%83%91%e3%83%95%e3%82%a9%e3%83%bc%e3%83%9e%e3%83%b3%e3%82%b9%e3%81%ae%e3%82%b3%e3%83%84">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>タスク数は &lt;code>Map&lt;/code> で管理しているため数百件程度までなら問題なし。大量登録が必要なら &lt;code>getTaskCount()&lt;/code> を監視し、デバッグ HUD から過剰登録を検知する仕組みを追加すると安全です。&lt;/li>
&lt;li>&lt;code>update(dt)&lt;/code> はタスクが無い場合 O(1) で即 return するため、必要なタイミングでこまめに &lt;code>cancel()&lt;/code> しておくと無駄なループを避けられます。&lt;/li>
&lt;/ul></description></item><item><title>GameLoop と状態共有</title><link>https://codigrowa.batako.net/docs/runtime/game-loop/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://codigrowa.batako.net/docs/runtime/game-loop/</guid><description>&lt;p>React 側では &lt;code>engine/hooks/useGameLoop.ts&lt;/code> と &lt;code>engine/stores/useGameStore.ts&lt;/code> がゲームエンジンを UI に接続します。&lt;/p>
&lt;h2 id="usegameloop-フック">useGameLoop フック&lt;a class="anchor" href="#usegameloop-%e3%83%95%e3%83%83%e3%82%af">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">useGameLoop&lt;/span>((&lt;span style="color:#a6e22e">dt&lt;/span>) &lt;span style="color:#f92672">=&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">game&lt;/span>.&lt;span style="color:#a6e22e">update&lt;/span>(&lt;span style="color:#a6e22e">dt&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}, &lt;span style="color:#a6e22e">isRunning&lt;/span> &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#a6e22e">game&lt;/span>.&lt;span style="color:#a6e22e">isRunning&lt;/span>);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>requestAnimationFrame&lt;/code> の &lt;code>time&lt;/code> から &lt;code>dt&lt;/code>（秒）を算出。&lt;/li>
&lt;li>&lt;code>isRunning&lt;/code> が &lt;code>false&lt;/code> のときは RAF を解除し、&lt;code>lastTimeRef&lt;/code> をリセット。&lt;/li>
&lt;li>ループ中に最新のコールバック参照を使うため &lt;code>useRef&lt;/code> で callback を保持。&lt;/li>
&lt;/ul>
&lt;p>必要に応じて UI 側から任意の &lt;code>Game&lt;/code> インスタンスを渡せます。Expo DevTools のホットリロードでも問題なく再初期化されるよう、&lt;code>useEffect&lt;/code> のクリーンアップで &lt;code>cancelAnimationFrame&lt;/code> を確実に呼んでいます。&lt;/p>
&lt;h2 id="zustand-ストア">Zustand ストア&lt;a class="anchor" href="#zustand-%e3%82%b9%e3%83%88%e3%82%a2">#&lt;/a>&lt;/h2>
&lt;p>&lt;code>engine/stores/useGameStore.ts&lt;/code> は &lt;code>Game&lt;/code> を 1 つだけ生成し、UI からは &lt;code>start/pause/resume/stop&lt;/code> をディスパッチできるようにしています。&lt;/p>
&lt;ul>
&lt;li>&lt;code>setRenderPayload(payload)&lt;/code> が &lt;code>Game.onRender&lt;/code> から届いたデータを &lt;code>tick/elapsed/entities&lt;/code> に反映。&lt;/li>
&lt;li>&lt;code>toggleRunning()&lt;/code> で UI トグルスイッチと同期。&lt;/li>
&lt;li>ストアはクライアントサイドのみで使うため、サーバーレンダリングの心配は不要。&lt;/li>
&lt;/ul>
&lt;p>&lt;code>GameCanvas&lt;/code> 以外の画面（HUD、デバッグパネル、外部ツール）でも同じストアを購読すれば、最新の World スナップショットを共有できます。&lt;/p></description></item></channel></rss>