日経ソフトウエア2025年11月号

日経ソフトウエア2025年11月号」にRoblox特集・後編の記事を書かせて頂きました。

 

スクリーンショットを150枚くらい撮ったけど、ほとんど使いませんでした。ボツ画像の一部です。新旧のソルジャーとゾンビを動かした様子です。古いソルジャーは棒立ちですが、新しいソルジャーは積極的に走り回ります。

 

古いモデルのゾンビで作った例です。新しいゾンビはリアルすぎて好きじゃないという人には良いかもしれません。

 

これもボツ画像。カメラアングルが肩越しなので、プレイヤーが銃を持っているのがわかりにくいと思って、複数のプレイヤーで実行してみました。

 

あと、RobloxのCEOが「インタラクティブ・フィジックス」を作ったという話はこの動画で知りました。

https://www.youtube.com/watch?v=VL6rYNmfrjM

 

本誌のコラムで書きましたが、昔、「ロボコンマガジン」という雑誌で「インタラクティブ・フィジックス」の記事を連載していたことがあったのですが、その証拠の画像がこちら。これは2003年のNo.32です。確か3年くらい連載していたはず。

 

C言語でスーファミ用のソフト開発(4)スプライトの表示

前回の続きです。

C言語でスーファミ用のソフト開発(3)BGの表示 - takuya matsubara blog

今回はスーパーファミコンでスプライトを表示します。

PVSnesLIbでのスプライト表示する方法は大きく分けて2つあります。「ダイナミックスプライト」というエンジンを使う方法と、それを使わない方法です。

 

公式ページにダイナミックスプライトを使ったサンプルがあります。

https://github.com/alekmaul/pvsneslib/tree/master/snes-examples/graphics/Sprites/

「DynamicEngineSprite」の実行画面です。64個のスプライトがアニメーションしながら同時に動いています。ピンとこないかもしれませんが、スーファミはCPUが遅いので、ここまでやって処理落ちしないのは凄いことなのです。ただし、Wikiのダイナミックスプライトのページがまだ未完成で使い方がよくわからないのが難点です。

ダイナミックスプライトを使わず、普通にスプライトを表示したいだけなら、簡単です。

・oamInitGfxSet関数を呼んで、VRAMにパターンとパレットを登録する。

・oamSet関数を呼んでスプライトを動かす。

・メインループでWaitForVBlank関数を毎回呼ぶ。

これだけです。この他、表示サイズを変更したい場合にはoamSetExを呼び出します。

 

スプライトでオリジナルの画像を動かす例を紹介します。

まず、スプライトの画像を用意します。512枚のパターン(タイル)を登録する場合、128×256ピクセルの画像が必要です。使える色は16色までです。自分の場合は16色のbmpファイルで保存しました。ファイル名が「resource_sp.bmp」の場合だと、makefileの書式は次のようになります。

resource_sp.pic: resource_sp.bmp
$(GFXCONV) -s 8 -o 16 -u 16 -p -t bmp -i $<

これでパターンとパレットのファイルが作成されます。前回のBGの時と違って、パレット番号を指定する必要がありません。

 

スプライトを画面内でバウンドさせて、fps(frame per second)を測定するというプログラムを作ってみました。タイマー(snes_vblank_count)で1秒を計測して、その間にメインループが何周したかでfpsを求めています。

上記の画面だと、スプライトを32個表示しつつ、60fpsを維持しています。満足な結果です。

 

スプライトには、SMALLとLARGEの2種類があります。

LARGEでも全く問題なく60fpsを維持できています。SMALLでもLARGEでもCPUの負担は変わらないようです。

 

スプライトを48個表示すると、30fpsに落ちてしまいました。処理落ちです。静止画だと伝わりませんが、スローモーションのようにスプライトの動きがノロノロです。VSyncまでに処理が終わらないので、次のVSyncまで待ってしまうので、速度が半減してしまいます。

スーファミにはCPUの速度を変更する機能があります。デフォルト設定では「SLOWROM」ですが、これを「FASTROM」に変更します。

 

FASTROMにするには、makefileの先頭に「FASTROM=1」と記述するだけです。これで動作周波数が2.68MHz → 3.58MHzになります。

、、、期待したほど速くなってくれません。2年前に発売されたメガドライブが7.67MHzってことを考えると、かなり遅いと思います。

 

FASTROMに変更した結果、30fps → 60fpsに上がりました。

 

スプライトが64個だと、FASTROMでも30fpsに処理落ちしてしまいます。

やはり、たくさん表示したい場合にはダイナミックスプライトを導入したほうが良いですね。

 

スプライトを128個表示してみました。20fpsにまで落ちました。

一応、対策方法はあって、oamSet関数を極力呼ばないようにすれば、処理落ちを軽減できます。2フレームに1回だけ動かすとか、細かい工夫が必要です。

 

エミュレータによる処理落ちの再現がどこまで正確なのかが不明です。そこで、ROMに焼いて、実機で動かしてみました。

 


www.youtube.com

動画にしてみました。実機でもスプライトが64個になった時点で処理落ちします。エミュレータによる処理落ちの再現はかなり正確であると実感しました。

 

コードはGitHubで公開しています。

github.com

 

続き

nicotakuya.hatenablog.com

C言語でスーファミ用のソフト開発(3)BGの表示

前回の続きです。

C言語でスーファミ用のソフト開発(2)Hello World - takuya matsubara blog

今回も引き続きスーパーファミコンでBGを表示します。

 

前回はhello_worldを改造して、オリジナルのフォントで表示しましたが、色を3色しか使っていません(透明色・白色・灰色)。せっかく16色のBG画面なのに、もったいないです。

 

そこで、16色のBG1を使わず、4色のBG3にテキストを表示することにします。

BG3を他よりも手前に表示するには、次のようにコードを書き換えます。

setMode(BG_MODE1, 0);

 ↓

setMode(BG_MODE1, BG3_MODE1_PRIORITY_HIGH);

 

makefile内の画像変換も16色→4色に変えます。

$(GFXCONV) -s 8 -o 16 -u 16 -p -e 0 -t bmp -i $<

$(GFXCONV) -s 8 -o 4 -u 4 -p -e 0 -t bmp -i $<

使い方については、ここに書いてあります。

https://github.com/alekmaul/pvsneslib/blob/master/tools/gfx4snes/readme.md

 

関数のBG番号を全部書き換えて、表示してみた結果がこちら。

パレットがおかしいです。

setPaletteColor関数を使って8,9,10番目のカラーを変更すると、うまく表示されましたが、バグなのか仕様なのかわかりません。console系関数は16色専用で、4色は非対応な気がします。

そこで、console系関数を使わず、直接VRAMに書き込むことにしました。

// BG3に文字表示
void bg3print(u8 x, u8 y, u8 *ptr)
{
    u16 vramaddr;
    u16 length=0;
    u8 *dst;
    dst = (u8 *)vramwork;
    // タイルマップに変換
    while(*ptr != 0){
        *dst++ = *ptr++ - 0x20;
        *dst++ = (1 << 5)+(PAL_TXT << 2);
           // priority bit + palette
        length += 2;
    }
    vramaddr = VRAM_TXTMAP+(32*y)+x;
    dmaCopyVram(vramwork, vramaddr, length);
}

たったこれだけのコードで済みます。一旦、タイル番号とパレットと反転情報をワークに格納して、それからdmaCopyVram関数でタイルマップに書き込んでいます。dmaCopyVramはVブランク中にDMA転送をやってくれるという便利な関数です。タイルマップの書き換えだけじゃなくて、パターンの書き換えにも使えます。

以上で、BG1が空いたので、何か表示してみます。

 

BG1に表示したい画像を用意します。

お絵描きソフトを使い、この画像を256×224ピクセルに縮小して、さらに16色に減色。16色のbmpファイルで保存します。

ファイル名を「resource_bg.bmp」とした場合、makefileの書式はこうなります。

resource_bg.pic: resource_bg.bmp
$(GFXCONV) -s 8 -o 16 -u 16 -e 1 -p -m -t bmp -i $<

この時点でパレット番号を決定している必要があります。この画像はパレット1に割り振ります。重複しないように、フォントはパレット0を使うことにします。

画像を変換すると、拡張子が「pic」「pal」「map」のファイルが生成されます。picがパターン、palがパレット、mapがタイルマップのバイナリファイルです。これらのバイナリデータをC言語ではグローバルな配列変数として扱います。詳しくは公式のWikiを参照してください。

https://github.com/alekmaul/pvsneslib/wiki/Backgrounds

 

画像の複雑さによって、パターンのデータ容量が変わってしまいます。そのまま変換すると、サイズが28KBになってしまいました。1枚だけなら表示可能ですが、複数枚を同時に表示したいので、もっと容量を節約することにします。

画像をアニメ調に塗りつぶすのが良い解決方法ですが、面倒なので、ここでは縮小するだけで済ませました。結果、7KBに落ちました。

 

まだVRAMに余裕があるので、BG2の画像も作ります。これも7KBくらいでした。こちらはパレット2を使うことにしました。

 

各データをVRAMに割り振ってみました。将来、スプライトを表示することを考えて、0x0000番地あたりを空けてあります。

 

出来上がったデモプログラムです。BG1とBG2がスクロールして、その上にBG3(テキスト)が表示されます。さすが、スーファミ。色彩が豊かです。

作ったコードはこちらです。

github.com

 

続き

nicotakuya.hatenablog.com

C言語でスーファミ用のソフト開発(2)Hello World

スーパーファミコン用開発環境「PVSnesLib」の話。前回の続きです。

C言語でスーファミ用のソフト開発(1)PVSnesLibの導入 - takuya matsubara blog

今回は「PVSnesLib」のサンプルプログラム「hello_world」について紹介します。普通、Hello Worldというと、一番簡単なプログラムですが、PVSnesLibの場合はそんなことはありません。

 

hello_worldの実行画面です。画面に「Hello World !」の文字が表示されます。

妙な色使いになっていますが、「色がいっぱい使えますよ」とアピールするためだと思います。

 

スーファミは画面モードが0~7まであります。hello_worldが使っているのは「モード1」です。BG1だけを表示していて、BG2と3は非表示にしています。

モード1は3枚のBG画面を重ねて表示できます。使える色が決まっていて、BG1~2が16色、BG3が4色です。BG番号は0から数えるので、BG1は「0番」です。

スーファミはBGのサイズを「32×32」「64×32」「32×64」「64×64」の中から選択可能です。hello_worldは32×32を使っています。

 

タイル番号やパレットや反転情報を記憶する領域のことを「タイルマップ」と呼びます。ファミコンとは違って、スーファミは1セル単位でパレットを変更可能です。

 

    // Initialize text console with our font
    consoleSetTextMapPtr(0x6800);
    consoleSetTextGfxPtr(0x3000);
    consoleSetTextOffset(0x0100);
    consoleInitText(0, 16 * 2, &tilfont, &palfont);

    // Init background
    bgSetGfxPtr(0, 0x2000);
    bgSetMapPtr(0, 0x6800, SC_32x32);

hello_worldで画面を初期化している部分がこちらです。意味不明な数値が並んでいます。console~で始まっているのは、テキスト関連の関数です。それとは別にbgSet~というBG関連の関数も呼んでいます。これらはVRAMを割り当てるための処理です。

PVSnesLibの場合、「タイル」の画像のことを「グラフィック」と呼んだりしています。自分の場合は「パターン」と呼んでいます。

 

hello_worldはこのようにVRAMを割り当てています。番地は0x0000~7FFFの範囲で自由に設定可能ですが、0x1000や0x0800のようにキリが良い必要があります。

オレンジ色の領域がフォントデータ(パターン)です。アルファベットや記号で96文字×32バイト=3072バイト使います。

ピンク色の領域がタイルマップです。32×32文字=1024ワード=2048バイト使います。

青色の領域がフォント以外のパターンです。256枚×32バイト=8KBも確保していますが、全く使っていません。

 

「Snes9X v1.51.ep10r2 - Geiger's Snes9x Debugger.」というエミュレータを使って、実行中のVRAMを表示してみました。VRAMは1アドレス=1ワードで記憶します。しかし、このエミュレータでは1アドレス=1バイトで扱っていて、アドレスが2倍で表示されます。0x6800番地が0xD000番地です。ややこしいです。

たとえば、座標(10,10)の文字は0x6800+(32×10)+10=0x694A番地に格納されます。エミュレータだと2倍になるので、0xD294~95番地です。ここに「Hello World」の「H」の文字が入っています。メモリ内で上位と下位がひっくり返っているので、1ワードに直すと「0x2128」になります。

本来、「H」のキャラクターコードは「0x48」ですが、フォントデータはスペース(0x20)から始まるので、0x20を引くと0x28になります。さらにオフセット値の0x100を足すとタイル番号は0x128になります。さらにBGの表示の優先度をオンにする設定として0x2000を足すと、「0x2128」です。これでエミュレータの値と一致します。

ここで言う「オフセット値」というのはフォントに加算するタイル番号のことです。先ほど256枚=8KBのパターンが未使用と書きましたが、それがオフセット値となります。

この8KBは使っていないので、削除することにします。次のように書き換えます。

    consoleSetTextGfxPtr(0x2000); // 0x3000→0x2000
    consoleSetTextOffset(0x0000); // 0x100→0

これでオフセット値が0になります。フォントデータを0x2000~2BFF番地に割り当てるので、0x3000~3BFF番地が空きます。

 

オリジナルのフォントを表示するには、リソースの画像を差し替えるだけです。hello_worldの場合、「pvsneslibfont.png」という画像です。

フォントの画像は1文字あたり8×8ピクセルです。キャラクターコード順に96文字、並べます。途中で折り返しても構いません。使える色は16色までです。

 

続き

nicotakuya.hatenablog.com

今から始める!生成AI活用プログラミング

お知らせが遅れてしまいましたが、「今から始める!生成AI活用プログラミング」というムックが10日に発売されました。このうち一部の記事(Robloxなど)を担当してます。