見出し画像

自作ゲーム機の開発進捗

現在妄想中の自作ゲーム機開発の進捗を記します。


当初想定 & 現在の課題

当初はvgssdk-picoをベースにしたRaspberryPi Zero(ベアメタル)用の新しいSDK(vgssdk-zero)を開発する気マンマンだったのですが、ベアメタル環境での開発が思っていた以上に大変だと実感しているところです。

しかし、幸いなことにRaspberryPiであればCircleというOSSを使えば比較的簡単にベアメタル開発ができます。

そこで、Circleの試食を兼ねてmicro-msx2p(私が開発した組み込み用MSXエミュレータ)をRaspberryPi Zeroのベアメタル環境に対応してみました。

Circleを使う上での懸念点

技術的な面での懸念についてはほぼ無い感触です。

強いて言えば、ググれば簡単に答えが出てくることが少ないので、英語で書かれた公式ドキュメントを熟読する必要があり少し面倒な程度(つまり無問題)です。

ただし、ライセンス面で懸念事項があります。

CircleのOSSライセンスはGPL(GPLv3)です。

GPLライセンスのOSSを使用したソフトウェアには、ユーザからの要求に応じてソースコードを開示する義務が生じます。(つまり、OSSにする必要があります)

VGS-ZeroのSDKは最初からMITライセンスのOSSとして開発する予定だったので全然問題無いのですが、その場合、そのSDKを使って開発したゲームにもOSS化の義務が生じてしまう問題があります。

そこで、SDKとゲームのバイナリを分離することで、ゲーム側にはOSS化の義務が生じないようにするスキームを検討しました。

バイナリ分離スキームの検討

例えばファミコンの場合、ファミコンのエミュレータとゲームROMのバイナリは分離されています。

そして、仮にファミコンのエミュレータがGPLのOSSだったとしても、そこで動作するファミコン用のゲームROMにはOSS化の義務は生じないので、それと同じスキームにすれば今回の要件(ゲームのOSS化回避)を満たすことができます。

私はVGSとは別軸でFCS80(Fairy Computer System 80)という架空のゲーム機のエミュレータ(OSS)も開発しています。

以前、MSX、ゲームギア、ファミコンなどのエミュレータを開発した時、ゲーム開発者目線で「このハード仕様だとゲームを作るのキツくない?」と感じる箇所が幾つかありました。(もちろん、ハード制約上の理由で仕方なく使い難かった部分もあったとは思われますが…)

そこで、不満点を解消した新しい架空の8bitレトロゲーム機を創ってみたくなり開発したのがFCS80です。

いわゆる、僕が作った最強の8bitゲーム機ですね。

そして、FCS80を使えばゲームのバイナリ分離が簡単に実現できそうです。(VGS-ZeroではなくFCS80-Zeroになってしまいますが)

FCS80の概要

FCS80は、CPUにZ80A、音源チップにAY-3-8910(PSG)+Sound Creative Chip(SCC)という既製のICチップを採用しつつ、バンクコントローラとVideo Display Processor(VDP)は独自仕様です。

CPUから見える64KBのメモリ(0x0000〜0xFFFF)を下図のようなメモリマップにしています。

FCS80のメモリマップ

ROMバンクはバンク番号(0〜255)を設定することで、最大2MB(8KB×256バンク)のROMファイル中の任意バンクへ切り替えることができます。

つまり、MSXのASCII8形式のメガロムとだいたい同じ仕様です。

VDPはキャラクタパターン方式のBG(背景)とFG(前景)を表示することができ、BGとFGをそれぞれ独立した形でハードウェアスクロール(1ピクセル単位のスムーススクロール)をすることができます。

更に最大256個のスプライトを表示でき、水平上限は無制限です。(※スプライトはBGの前面 & FGの背面に表示されます)

キャラクタパターンは8x8ピクセルの4bitカラー(16色)&最大256個です。(1キャラクタ32bytesなので全部で8KB)

それぞれのキャラクタパターンに16セットのパレットの中から任意のひとつを割り当てることができる仕様なので、最大同時発色数は256色(16×16)です。(※FGとスプライトは色番号0が透明色なので最大240色)

VRAMサイズはMSX1と同じ16KBです。

16KBにした理由は、CPUからメモリマップしたかった為です。

レトロゲーム機のVDP(ファミコンならPPU)側のRAM(VRAM)へCPUからアクセスするには、アドレスポートを2回連続でOUTしてアクセス先のアドレスを設定後、データポートへのIN/OUTでアクセスする…という野暮ったいステップを踏む必要があります。

更に、アドレスポート設定中に割り込みが発生すると、アドレス設定に不整合が生じてバグる可能性があるため、アドレスポート設定処理はDI〜EIで囲む必要があります。(より厳密に言えば、割り込み先でVRAMアドレスが変わる可能性もあるのでVRAMアクセスが全て完了するまで割り込み禁止すべきかと思われます)

これが最大の不満点です。

という訳で、VRAM(16KB)を全てCPUから見えるようにメモリマップしてみました。

これにより、LD系の命令で簡単にVRAMのread/writeが実現でき、プログラムがかなりシンプルになります。

VRAMのCPUメモリマップのハード的な実現可否についてはよく分かりませんが、PC-88のVRAMはCPUからダイレクトにアクセスできていたので、恐らく当時の技術でメモリマップのハード的な実現は可能だったと考えられます。

FCS80のRPi0ベアメタル対応

Circleを使えば簡単でした。

RaspberryPi Zero(無印、W、WH)に対応したものとRaspberryPi Zero 2Wに対応したものを作成済みです。

上記のimageディレクトリ以下のファイル群をFATフォーマットのSDカードに格納するだけでFCS80のゲームが起動できます。

Zero1とZero2のどちらも問題無い性能(60fps)で動作します。

PC(Linux or macOS)で動作するSDL2版のエミュレータも提供しているので、「SDL2版でデバッグ」→「完成したらラズパイ0で動かす」といった形で効率的にゲーム開発が出来ると思われます。

C言語対応(予定)

FCS80では、通常はマシン語(Z80アセンブリ言語)でゲームを開発する必要があり、その点がネックかもしれません。

以下、FCS80でHello, World!を表示しつつ、ハードウェアスクロール機能を用いてジョイパッドでそれを上下左右にスクロールする公式サンプルプログラムのコードです。(コチラを参照)

org $0000
.main
    ; 割り込み関連の初期化
    IM 1
    DI

    ; VBLANKを待機
    call wait_vblank

    ; パレットを初期化
    ld bc, 8
    ld hl, palette0_data
    ld de, $9400
    ldir

    ; Bank 1 を Character Pattern Table ($A000) に転送 (DMA)
    ld a, $01
    out ($c0), a

    ; 画面中央付近 (10,12) に "HELLO,WORLD!" を描画
    ld bc, 12
    ld hl, hello_text
    ld de, 394 + $8000 ; 394 = 12 * 32 + 10
    ldir

    ; メインループ
mainloop:
    ; VBLANKを待機
    call wait_vblank

    ; ジョイパッド(1P)の入力を読み取る
    ld a, $0E
    out ($A0), a
    in a, ($A2)
    ld b, a

    ; 左カーソルが押されているかチェック(押されている場合は左スクロール)
    ld hl, $9602
    and %00100000
    jp nz, mainloop_check_right
    inc (hl)
    jmp mainloop_check_up
mainloop_check_right:
    ; 右カーソルが押されているかチェック(押されている場合は右スクロール)
    ld a, b
    and %00010000
    jp nz, mainloop_check_up
    dec (hl)
mainloop_check_up:
    ; 上カーソルが押されているかチェック(押されている場合は上スクロール)
    ld hl, $9603
    ld a, b
    and %10000000
    jp nz, mainloop_check_down
    inc (hl)
    jmp mainloop_check_end
mainloop_check_down:
    ; 下カーソルが押されているかチェック(押されている場合は下スクロール)
    ld a, b
    and %01000000
    jp nz, mainloop_check_end
    dec (hl)
mainloop_check_end:
    jmp mainloop

; VBLANKになるまで待機
.wait_vblank
    ld hl, $9607
wait_vblank_loop:
    ld a, (hl)
    and $80
    jp z, wait_vblank_loop
    ret

palette0_data: defw %0000000000000000, %0001110011100111, %0110001100011000, %0111111111111111
hello_text: defb "HELLO,WORLD!"

慣れればこれでもゲームが作れると思いますが、高級言語のぬるま湯にどっぷり浸かった身にはなかなか堪えますね😂

FCS80は、16KBという(8bit時代にしては)そこそこ広大なRAMを自由に使うことができるので、C言語でも割りと問題無くゲームが作れる程度のスペックだと思われます。

そこで、C言語用のライブラリを準備して、可能な限りC言語だけでもゲーム開発できるようにしようと思っているところです。

C言語対応ができたら、ちゃんと遊べるゲームをFCS80で幾つか創ってみようと思っているところです。

この記事が気に入ったらサポートをしてみませんか?