![見出し画像](https://assets.st-note.com/production/uploads/images/126072737/rectangle_large_type_2_b0612df6388c680dadbb6489b8e55653.png?width=1200)
【GB Studio】GBVMを理解する_日本語版_要約ドキュメント(ver3.1.0用)
はじめに
https://gbstudiocentral.com/tips/understanding-gbvm/
⚠️このノートは、上記公式サイトを翻訳しできるだけ要約したものです。個人の勉強用にまとめたものにすぎないこと、また翻訳により齟齬や説明、解釈が異なる場合があります。ご注意ください。
GBVMとは
GBVMは「Game Boy Virtual Machine」の略で、バイトコードを解釈し内部関数を呼び出すソフトウェアです。
確かに、すべてをC言語やアセンブリで書くことは理論上可能ですが、GBVMにはそれらにはない多くの利点があります。
実行の制御が可能で、スクリプトの実行を自由に停止・再開できます。
安全性が高く、ネイティブコードに比べエラーやバグを生じにくい環境です。
複数のスクリプトを同時に(マルチスレッドで)実行できる機能があります。
GB Studioが開発者でないユーザーに向けたプラットフォームであることを考慮すると、これらの機能は特に重要です。
欠点としては、仮想環境でのスクリプト実行はパフォーマンスに影響を与える可能性があります。
古典的なゲームボーイは3.5 MHzで動作する8ビットSM83 CPUを搭載し、性能は1 MIPS未満です。
単純な「NOP」命令が4サイクル、「CALL」命令が6サイクルを必要とします。
数が限られた8ビットCPUレジスタのため、Cコードの最適化が困難になる場合があります。
効率的な機能実行とパフォーマンスのバランスが重要で、その意味でスクリプトの仮想化は有効な手段です。
GBVMスタジオコアとは
GBVMを理解するためには、GB Studioのコアとなる部分の理解が必要です。
GB Studioは、C言語のコンパイラ、アセンブラ、ライブラリで構成されたクロス開発キットであるGBDK-2020を使用します。
GBDK-2020はWindowsおよびMacでGame Boyゲーム開発を可能にします。
ゲームの基礎となる「シーン」、「アクター」、「発射物」などのコンポーネントの構築にGBDK-2020が使用されます。
これにはアクター間やシーンとの衝突定義、ゲームの基本的な動作部分が含まれます。
さらに音楽とサウンドのドライバ、入力、UIレンダリングの管理もこのレベルで行われます。
コードが複雑になりがちで、変更するとゲームのパフォーマンスに不意の影響を及ぼすことがあります。
この技術に触れる場合、注意深く行動しどのような結果を期待しているのかを明確に理解する必要があります。
▼C言語のコンパイラ
▼アセンブラ
▼ライブラリ
▼クロス開発キット
▼GBDK-2020
コードの次の層は「ゲームの状態」で構成されています。
これらもGBDK-2020を使用しており、トップダウン、プラットフォーム、ロゴなど、様々なシーンのルールを定義します。
例えば、プラットフォームゲームの重力のルールやロゴシーンのハードウェア操作がここで定義されます。
経験豊富なユーザーは、ゲームの状態を追加や変更が可能です。
既存のゲーム状態の変更は、そのゲームタイプのみに影響し、エラーなしで適切に実施されます。
GB Studioの将来版では、ユーザー定義のゲーム状態が導入される予定です。
より高位レベルでは「GBVM」を用いたユーザー定義スクリプトによってゲームがカスタマイズされます。
「GBVM アセンブリ」で記述されたスクリプトは、コア機能と連動し、サウンド再生やゲームの状態を操作、あるいは低レベルアセンブリ言語で書かれたコードの呼び出しが可能です。
![](https://assets.st-note.com/img/1703923343593-XLjDZVW4aM.png)
コアの3つの部分をコンパイルすると機能するROMが出来上がります。
しかし、ユーザーコンテンツや指示がなければ、基本レイヤーだけでは実際には機能するゲームは生まれません。
これは、コアが実際のゲームというよりライブラリのように機能しているためです。
ユーザーのアセットがゲームを独自のものへと変化させる基盤となります。
背景、レベルデザイン、スプライト、音楽、スクリプトなどのアセットが該当します。
これらのアセットはネイティブなゲームボーイコードではなく、データとして存在し、コアによって解釈され、動くゲームが作成されます。
どのように機能するか
GBVMはバイトコードを解釈して動作する、ゲームボーイ用の仮想マシン(VM)です。
この仮想マシン内には現在実行中のスクリプトを指し示すプログラムカウンタ(PC)と、スクリプトの命令を実行する関数が存在します。
プログラムカウンタが指し示す機能バイトはGBVM命令を識別し、これによって命令テーブルを探索します。
命令テーブルにはGBVM命令を実現するネイティブ関数へのポインターと、関数に渡すパラメーターのバイトサイズがCPUスタック上に記述されています。
スクリプトデータからのバイトはCPUスタックにコピーされ、その後にGBVM命令に従うネイティブ関数が実行されます。
ネイティブ関数によって実行される操作は、以下のようなものです:
VMの状態を変更する。(例:VM_JUMP)
ゲームボーイのハードウェアに影響を与える。(例:VM_SOUND_PLAY)
エンジンの状態を操作する。(例:VM_ACTOR_MOVE_TO)
ゲーム自体の状態や規定値を設定する。(例:VM_SET_CONST_INT16で特定の内部状態値を設定)
これらは特にプラットフォームモードでのプレイヤーに重要な機能であり、GBVMを通じて多様なゲーム内動作や状態管理が可能になります。
GBStudioアセンブリについて
ネイティブ関数の呼び出しが完了すると、プログラムカウンタ(PC)はスクリプトデータ内で次に実行すべき命令コードを指し示すように更新されます。
VMステッピング関数が完了すると、メインコアサイクルはユーザースクリプトの次の命令を実行するかどうか決定するまで、他のタスクに移ることができます。
更に詳しく言うと次のようになります:
実際のVMステッピング関数は非常に複雑です。これは、メモリ上でROMバンクを適切に管理し、複数のVMコンテキストを同時に扱い(これにより複数のスクリプトを同時実行可能にしています)、そしてシステム全体のパフォーマンスとのバランスを取る実行のクォンタ(制限された実行時間)を管理するからです。
ROMバンクの管理は、プログラムやデータを格納するためのメモリ空間の有効利用を意味します。
複数のVMコンテキストを持つことにより、同じ物理ハードウェア上で複数のスレッドやプロセスを実行することが可能になり、これによりマルチタスク処理が実現します。
実行のクォンタを管理することで、一つのスクリプトが全てのリソースを独占することなく、複数のスクリプトがお互いに影響を与え合うことなく効率的に実行されることを保証します。
GBVMのこのような設計は、限られたリソースを持つゲームボーイのようなハードウェア上でも、複雑な動作を可能にし、開発者がより豊かなゲーム体験を提供できるようにしています。
GBVM(Game Boy Virtual Machine)は特定のバイトコードを解釈する仕組みを有しており、以下のプロセスがその基本です。
バイトコードの中間表現は「GBStudio アセンブリ」や「GBVM スクリプト」と呼ばれる、人間が判読可能な形式で提示されます。
GB Studioを使用する開発者は、ビジュアルスクリプトやイベントをこの形式のテキストとして作成し、手動で加工することができます。
作成されたGBStudio アセンブリは、GB Studioによってバイナリスクリプトデータへとコンパイルされ、実際にGBVMで動作するプログラムに変換されます。
GB Studio内の「ゲーム」メニューの「詳細設定」から「プロジェクトデータの取り出し」を選択することで、開発しているゲームに対して生成されたGBStudio アセンブリファイルを参照することができます。
生成されるファイルは「script_s0a0_interact.s」のように命名され、作成されるスクリプトが含まれています。
要するに、GBVMとはゲームボーイのゲーム開発において、開発者がより直感的な方法でゲームロジックを組み立てられるようにするための仕組みであり、GB Studioはその作成工程をサポートしているツールだということです。開発者はこのツールを用いて、ゲーム制作のために必要なスクリプトをより扱いやすい形式で書き、その後GBVMが理解できるバイトコードにコンパイルすることができます。
SDAS GBについて
SDAS(sdasgb)は、ゲームボーイ用のアセンブラであり、以下の要点で説明できます。
SDASはアセンブリ言語で書かれたソースコードをゲームボーイのネイティブ命令セットであるSM83の機械語コードへと変換(コンパイル)します。
本来ならば、このアセンブラはネイティブSM83プログラムを作成するために使用されますが、現在のケースではネイティブコードを生成する目的には利用しません。
代わりに、GBVM命令ごとに対応するマクロを含むマクロ定義ファイルが使用されます。これらのマクロは直接的なコードではなく、バイトコードや命令用データを出力します。
▼マクロ定義ファイル
実際の流れ:
GBVM用のスクリプトや命令を書く際には、先に述べたマクロ定義ファイルを参照し、必要なマクロを使用します。
SDASを使用して、これらのマクロを含むGBVMスクリプトをアセンブルします。
アセンブルされた結果は、実行可能なコードではなく、GBVMが解読し実行するためのデータとして出力されます。
このようにSDASはGBVMの実行環境に合わせて利用され、GBVMが理解できる形式で命令セットを生成するための一環となっています。このアプローチは、ソフトウェアの開発者がGBVMにおいてより効率的にコーディングを行うためのサポート体制を提供します。
サンプルコード
以下にVM_SET_PRINT_DIR命令の要約を提示します:
命令の機能:UIにおけるテキスト表示の方向を設定
パラメーター:8ビットの数値で表示方向を指定
OP_VM_SET_PRINT_DIR = 0x4C
.UI_PRINT_LEFTTORIGHT = 0
.UI_PRINT_RIGHTTOLEFT = 1
.macro VM_SET_PRINT_DIR DIRECTION
.db OP_VM_SET_PRINT_DIR, #<DIRECTION
.endm
命令の定義:
OP_VM_SET_PRINT_DIR = 0x4C: VM_SET_PRINT_DIR命令のオペコード(操作コード)
.UI_PRINT_LEFTTORIGHT = 0: 表示方向を左から右へ設定するパラメータ
.UI_PRINT_RIGHTTOLEFT = 1: 表示方向を右から左へ設定するパラメータ
マクロの使用方法:
.macro VM_SET_PRINT_DIR DIRECTION : マクロ定義の開始
.db OP_VM_SET_PRINT_DIR, #<DIRECTION : 表示方向パラメータと合わせてバイトデータをデータバンクに格納
.endm : マクロ定義の終了
▼GBVMスクリプトで記述する場合の例
VM_SET_PRINT_DIR .UI_PRINT_RIGHTTOLEFT
VM_SET_PRINT_DIR .UI_PRINT_RIGHTTOLEFT : スクリプトによるマクロの使用例で、テキスト表示方向を右から左に設定
バイナリ変換結果:
上記はバイナリデータ 0x4C, 0x01 として出力されます。
クリアな中間表現:
このマクロを使用することで、人間が読める形式で明確な中間表珣を保ちつつ、最終的なバイトコードも正確に生成することができます。
このように、GBVMスクリプトを用いることで、読みやすい形式でプログラムを記述できると同時に、必要なバイナリデータを正確に生成することができるため、人間の開発者にとっての可読性と機械の処理効率のバランスを取ることができます。
SDASが使われている理由
SDASが他のアセンブラではなく選ばれる理由についてですが、以下の点が挙げられます。
SDASは、ゲームボーイのネイティブなコードを生成できる強力なツールであり、信頼性と効率性が認められています。
このアセンブラはGBDK-2020という、非常に人気のある開発キットの一部分です。これにより、既存のエコシステムとシームレスに統合が可能です。
総合的な機能性を提供し、マクロの処理やラベルアドレスの計算はもとより、外部アセットやコードとのリンクといった複雑なタスクも行うことができます。
独自の構文やルールを用いて専用のアセンブリ言語を構築する可能性もあります。
ただし、アセンブリ言語は特有の挑戦を伴います:
一貫したシステムを構築することは、簡単ではありません。アセンブリ言語はその性質上、何かと注意が必要です。
ネイティブコードとGBDK-2020ライブラリの統合も複雑化する可能性があります。
新しいカスタムコンパイラをサポートするためには、追加の開発リソースが必要です。
GB Studioの開発にも多大なリソースが投じられることになります。
アセンブリ言語の詳細な構文は習得が難しく、例えば式の中にスペースが含まれている場合には、スペースを含む式を/^ … /で囲む必要があったり、即値を示すために#を付加するなどの特殊な記法が必要です。しかし、これらの課題はSDASの詳細なマニュアルで解決策が提供されており、予想されるメリットはこのような欠点を遥かに上回ると判断されています。
SDASの使用は利便性と整合性を優先し、複雑なタスクを簡素化し、GBVMやGBDK-2020のような既存のフレームワークとの親和性も考慮されているのです。
▼SDASのマニュアル
仮想マシンのアーキテクチャ
仮想マシンの概念を要約すると、以下のような構成要素を考えることができます。
仮想CPU: VMの状態を司るプログラムカウンタ(PCポインタ)を含む
プログラムメモリ: スクリプトのバイトコードが格納される場所
RAM: スクリプト変数やスタックが配置される場所
プログラムメモリとRAMは分離しており、スクリプトからは直接バイトコードにアクセスできない
この設計はハーバードアーキテクチャの特徴と似ている
これらの要素によって構成される仮想マシンは、架空のコンピューターとして機能し、その独自のプログラミング環境内でスクリプトやプログラムを実行します。ハーバードアーキテクチャに類似していることから、データとプログラムの物理的な分離が特徴で、これによりセキュリティや効率が向上します。
▼スタック
▼ハーバードアーキテクチャ
仮想マシンのRAM
VM RAM(Virtual Machine RAM)は、ゲームボーイ向けの仮想マシンで特有のメモリ構造を持っており、以下のような特徴を有しています。
VM RAMのメモリセルは16ビットのワード単位で構成されており、一般的な8ビットのバイト単位ではない点が「通常の」コンピュータのRAMと異なります。
vm.cファイル内のscript_memory配列としてVM RAMは定義されており、特定のレイアウトを取っています。
script_memory配列の先頭には「ヒープ」領域があり、デフォルトで768ワード(VM_HEAP_SIZE)のサイズが割り当てられています。このエリアはGB Studioでグローバルスクリプト変数のためのメモリスペースとして使用されます。
ヒープ領域の後には、各VMコンテキストのスタックが配置され、1つのコンテキストスタックサイズはデフォルトで64ワード(VM_CONTEXT_STACK_SIZE)です。
コンテキストの総数は16あり、これにより16個のスクリプトを並行して実行することが可能になります。ゲームボーイはWRAMが8キロバイトしかないという制約の中でこれを実現しており、そのためVM RAMは合計で3584バイトのメモリを使用します。
VM RAM内の特定の16ビット値へのアクセスは、script_memory配列のインデックスを介して行われます。
インデックス「5」の値にアクセスするには、script_memory[5]のように呼び出します。
変数を直感的に管理するために、インデックス値5にVAR_MY_VARIABLEのような名称を定義することもできます。すると、script_memory[VAR_MY_VARIABLE]と記述してアクセスでき、非常に読みやすくなります。
代入においても、script_memory[VAR_MY_VARIABLE] = 100のようにしてVM RAMの特定の位置に値を設定できます。
GB Studioでスクリプトを書く際は、メモリインデックスを直接気にすることなく、VAR_MY_VARIABLEのような具体的な変数名でVM RAM内の値を扱うことができます。これにより、エンジンの実装の詳細を知らずに、直感的かつ効率的にプログラミングを行うことが可能です。
▼配列(array)
▼コンテキスト
▼ポインタ
生成されたGBVMスクリプトは下記。
VM_SET_CONST VAR_MY_VARIABLE, 100
VMRAMがどのように構成されているか理解するのに役立つ図解はこちら⇩
![](https://assets.st-note.com/img/1703924995760-wniIabGkEn.png?width=1200)
コンテキストスタック
コンテキストスタックポインター(SP)とその動作についての説明は以下のようになります。
SPは、スクリプトデータを格納するscript_memory配列内で利用されるポインターで、ネイティブのCPUのスタックポインタと同様の役割を果たします。
スタックがどの方向に成長するかが実際のCPU、例えばゲームボーイのSM83との違いです。GBVMではスタックはscript_memoryと同じ配列を使用し、それに格納される値は16ビットであるため、ネイティブCPUのポインタをVMのスタックで扱うことができます。
VM_CALL命令が呼ばれる際には現在のプログラムカウンタ(PC)がスタックにプッシュされ、新たに呼び出されるサブルーチンのアドレスにPCが更新されます。そのサブルーチンの実行が終わるとVM_RET命令により、スタックからPCをポップし元の実行準備に戻ります。
スタックは関数の引数を渡すためにも使用されますが、ここでのアクセス方法は実際のCPUシステムのスタックと少し異なります。GBVMでは、負のインデックス値はSPからのオフセットとして機能し、スタック内の特定の場所へのアクセスを可能にします。
VMの命令は、ポインタが負の値を持つ場合、それが示すコンテキストスタック上のパラメータ(context.SP + A)にアクセス。正の値を持つ場合は、script_memory配列の先頭からのポインタ(script_memory + A)となり、これによってグローバル変数にアクセスします。
定義.ARG0、.ARG1などは関数呼び出し時にスタックから特定の引数を取得するために頻繁に使用され、これらは負のインデックスとしてスタック内の所定の値にアクセスする際のショートカットとして機能します。
この説明はGBVMのスタックの扱い方と、関数呼び出しや引数の受け渡しのための仕組みを表しており、GBVM上で効率的なプログラミングを行う上で中心的な役割を担うものです。
▼スタック
例:
VM_PUSH_CONST 0
VM_RTC_GET .ARG0, .RTC_HOURS
ここで解説するコードの挙動は、GBVM(Game Boy Virtual Machine)のスタック操作に関連しています。
指示通り、このコードはまず値「ゼロ」をスタックの一番上にプッシュします。スタックとは一時的にデータを保持する領域のことです。
続いての命令により、VM_RTC_GETを使用する際にスタックトップの値を参照することが示されます。「.ARG0」は実際には「-1」と同等で、これはスタックの最上位を指す特別な指示です。
VM_RTC_GET命令はカートリッジのRTC(リアルタイムクロック)から「時間」値を取得し、スタックに先にプッシュされた「ゼロ」の上にその値を書き込みます。
このプロセスは一般的なCPUスタックの用途と非常に似ていますが、VMコンテキストスタックの厳重な管理が必要です。
必要なのはスタックのバランスをとることです。つまり、プッシュされたデータの数は、スタックから取り除かれるデータの数(VM_POPを用いて)と一致していなければなりません。
もしこれが不均衡になると、サブルーチンを終了するVM_RET命令で無効なデータを読み込むことになり、結果としてプログラムクラッシュを引き起こす可能性があります。
注意点としては、VMコンテキストスタックが64エントリという制限があるため、スタックオーバーフローに注意する必要があります。オーバーフローが発生すると、不正なスタック操作をしている次のVM命令に影響を与える可能性があります。
さらに、パフォーマンスの制限のために、実行時にオーバーフローを検出する機能が存在しないという点も心に留めておくべきです。ゲームボーイは非常にリソースが限られているシステムのため、プログラミングする際にはその限界を理解し、慎重にコードを記述する必要があります。
VMアーキテクチャの概要
この図はGBVMアーキテクチャの概要を示しています。
![](https://assets.st-note.com/img/1703925191124-JbMSAA980t.png?width=1200)
このセクションでは、GBVM(Game Boy Virtual Machine)のメモリ構造と運用について解説しています。
VM RAM配列には、「ヒープ」と呼ばれるダイナミックに割り当てられるメモリ領域と、複数の「コンテキストスタック」の領域が含まれています。
また、VMコンテキストは、PC(プログラムカウンタ)とSP(スタックポインタ)を持っています。
ROMバンクはユーザースクリプトのバイトコードを格納しており、あるときはバンク番号を例として挙げることで、どこに何が格納されているかを説明します。
おわりに
このような詳細な情報は、GB Studioコミュニティ全体にとって一見すると複雑に見えるかもしれませんが、仕組みを理解すれば、システムの各部分がどう連携して機能しているのかを把握するのに役立ちます。
もしGBVMの使用を検討されているならば、こうした情報は最終的に重要な知識となるでしょう。そのため、初めは全体像を把握するのが難しいかもしれませんが、後でこの記事に戻ってコードや構文との関連性を掘り下げることを推奨します。
この記事が参加している募集
この記事が気に入ったらサポートをしてみませんか?