必要条件

この記事に必要な予備知識

これまでにFlasCC関連のドキュメントを読み、少なくとも数回はサンプルをコンパイルした経験があることを前提にしています。

 

OpenGLまたはOpenGL ESの詳しい知識があることが望ましいでしょう。これらのAPIのいずれかを使用したC/C++コードベースで、Flash Playerに移植したいものを用意します。

 

その他必要な製品(サードパーティ/ラボ/オープンソース)

ユーザーレベル

中級

原文 作成日: 2012/12/3
Compiling OpenGL games with the Flash C++ Compiler (FlasCC)

この記事では、Flash C++ Compiler(FlasCC)を使って、コードベースやビルドシステムを最小限変更するだけで、オープンソースのOpenGLゲームNeverballをFlashランタイムに移植する方法を紹介します。FlasCCは、新しいアドビのコンパイル技術で、C/C++コードをFlash Playerで実行可能なSWFファイル形式にコンパイルします。

Flash Player 11ではStage3D APIが導入され、プログラム制御可能なシェーダーを活用したGPUアクセラレーションによる3Dグラフィックが実現しました。このAPIは、最新のモバイルデバイスやデスクトップPCでスムーズに実行できるように開発されました。Stage3D APIとOpenGL ES APIは意図的に似せて作られています。アドビは、変換やエミュレーションにお金を費やさなくても、Stage3D APIが最新のハードウェア機能を直接活用できるようにしたいと考えました。

OpenGL ESは、OpenGLの仕様を縮小したバージョンで、最新のプログラム制御可能なGPUでは対応していない旧式の機能は組み込まれていません。アドビは、OpenGLがFlashに対応できるように、「GLS3D (OpenGL for Stage3D)」と呼ばれるオープンソースプロジェクトを始動させました。これにより、Stage3D API上にOpenGL APIのサブセットを実装することで、変更作業をせずにOpenGLゲームをFlashランタイムに移植することができます。

この記事は、これまでにFlasCC関連のドキュメントを読み、少なくとも数回はサンプルをコンパイルした経験があるユーザーを対象に進めていきます。FlasCC SDKには、Mac OSおよびWindowsでのインストール方法および使用方法を説明したドキュメントが含まれています。また、コンパイラーのさまざまな機能を紹介したサンプルや、FlasCCを使って実際のコードベース(Box2D、Quake 1、Lua、Bullet Physics Libraryなど)をコンパイルしたサンプルも同梱されています。

OpenGL、OpenGL ES、Stage3D

初めに、現在利用できる各GPU APIを比較しながら、いかに簡単にOpenGLコードベースとOpenGL ESコードベースをFlashランタイムに移植できるか説明しましょう。表1は、3つのAPIで使用できるさまざまな機能を大まかにまとめたものです。OpenGLの機能の大半はハードウェアに依存しており、一部の機能はOpenGL拡張機能を通じて使用します。そのため、ここに挙げた機能はすべて、OpenGLの適合性を求めて使用したり、アクセラレーションしたりすることを厳密に要求しているわけではありません。Stage3Dの機能セットは規模が小さくなりますが、特定のデバイスですべて使用できる、または、使用できないことが決まっている点が異なります。いずれにしても、この表では、OpenGLまたはOpenGL ESのコードベースを、Flash PlayerおよびStage3Dベースラインプロファイルを使用したStage3Dに移植する際に発生する可能性がある機能上の相違点を紹介します。

表1 Stage3Dとの機能比較

機能

OpenGL

OpenGL ES

Stage3D

シェーダー言語         

なし/GLSL(ソース)   

GLSL(ソース)       

AGAL(バイトコード)

イミディエイトモード(直接モード)         

×

×

プリミティブタイプ

POINTS、LINES、LINE_STRIP、LINE_LOOP、TRIANGLES、TRIANGLE_STRIP、TRIANGLE_FAN、QUADS、POLYGON

POINTS、LINES, LINE_STRIP、LINE_LOOP、TRIANGLES、TRIANGLE_STRIP、TRIANGLE_FAN

TRIANGLES

プリミティブタイプ

ハードウェア依存、non-power-of-two (NPOT)

ハードウェア依存、non-power-of-two (NPOT)

2048px POT

テクスチャフォーマット      

多数

多数

BGRA 32bit、(および、ハードウェア依存:DXT1/5、PVRTC、ETC1)

Flash Player 11.4は、シェーダーの長さやテクスチャの参照に関する制限を減らし、Stage3Dコンテクストの構成方法に制限を設けた新しい制限されたプロファイルをサポートしています。これにより、Stage3Dは、ローパワーのGPUが搭載されたシステムでも使用することができます。制限されたプロファイルについては、Context3DProfileドキュメントのBASELINE_CONSTRAINED定数をご確認ください。

GLS3D:Flash用OpenGLラッパー

GLS3D(OpenGL for Stage3D)は、FlasCCの相互運用サポートを利用して、Stage3D API上にOpenGL APIを実装するOpenGL APIのサブセットを実装するオープンソースプロジェクトです。大まかに言うと、OpenGLコードベースと、アプリケーションに連携できる静的ライブラリに含める標準的なOpenGLヘッダーのセットを提供します。アプリケーションがOpenGL APIを呼び出すと、GLS3DはStage3D APIを起動し同じ機能を実現します。
OpenGLとStage3Dの機能が似ている場合には、簡単に変換されます。ここでは、glClearの実装についてサンプルを紹介しましょう。

// LibGL.cpp extern void glClear (GLbitfield mask) { // ActionScriptに呼び出すFlasCCのインラインasmサポート inline_as3("import GLS3D.GLAPI;\n"\ "GLAPI.instance.glClear(%0);\n" : : "r"(mask)); } // LibGL.as public function glClear(mask:uint):void { contextClearMask = 0 if (mask & GL_COLOR_BUFFER_BIT) contextClearMask |= Context3DClearMask.COLOR if (mask & GL_STENCIL_BUFFER_BIT) contextClearMask |= Context3DClearMask.STENCIL if (mask & GL_DEPTH_BUFFER_BIT) contextClearMask |= Context3DClearMask.DEPTH // コンテキストは現在のStage3Dコンテキストオブジェクト context.clear(contextClearR, contextClearG, contextClearB, contextClearA, contextClearDepth, contextClearStencil, contextClearMask) }

この特定のAPIの場合、変換は簡単です。Stage3D APIに等しいものが、さまざまなclearの値を指定した引数をOpenGLよりもたくさん取得しますが、これは内部の状態によって異なります。OpenGLでクリア可能なバッファーにはStage3Dに同等のものがあるため、GLの値から同等のStage3Dへビットフィールドされた値を変換するだけで済みます。
OpenGLの他の部分にはもっと複雑な処理が必要になります。例えば、GL_QUADSのサポートには、まず 頂点バッファーとインデックスバッファーを同等のGL_TRIANGLESに変換する必要がありますが、Stage3D はこれをネイティブに処理できます。GLS3Dの詳しい内容はこのチュートリアルでは取り上げませんが、OpenGLアプリケーションをサポートできるように改善したり、追加機能を実装したりする必要が出てくる場合もあります。そのため、GLS3Dがいかに機能するか大まかに知っておくことが大切です。

Neverballソースの取得

FlasCCを使えるように開発マシンを設定しているという前提の上で、最初にすることはNeverballのダウンロードです。このチュートリアルはNeverballバージョン1.5.4を使用して執筆しています。このバージョンは、こちらからダウンロードできます。

多くの複雑なソフトウェアプロジェクトと同様、Neverballは自立型ではなく、SDL、SDL_ttf、SDL_image、SDL_mixer、freetype、jpeg、png、zlibといった他のライブラリに依存しています。これらのライブラリの多くは自立型であり、FlasCCでコンパイルでき、追加作業は必要ありません。ただし、SDL自体は、グラフィック、音声、入力処理を抽象化するためのプラットフォーム抽象レイヤーです。このため、SDLには、サポート対象の各プラットフォーム(Mac、Windows、Linuxなど)用のプラットフォーム固有コードがあります。

SDLアプリケーションを簡単にFlashランタイムに移植できるように、FlasCC SDKでは、FlashプラットフォームレイヤーのあるSDLを提供しています。これは、入力処理およびバックバッファーの表示を説明したFlasCC SDKのQuake 1サンプルで使用されています。このチュートリアルでは、このSDLを使用してNeverballをコンパイルします。

他の付属ファイルもすべてコンパイルする必要がありますが、時間を節約するために、事前にコンパイルしたものをalcextra GitHub repositoryに用意しておきました。Gitを使用して、または、GitHubサイトからZIPファイルでこのリポジトリをダウンロードしたら、関連するヘッダーと静的ライブラリがinstallディレクトリにすべて揃っているか確認してください。

Neverballのコンパイル

コードベースをコンパイルする前に、ゲームのランループをわずかに変更し、マウスやキーボードからの入力、ファイルシステムへのアクセス、表示処理に対応するためのFlash固有のコードをいくつか実装する必要があります。FlasCCで移植する場合、一般的に以下の手順に従って進めます。

  1. ランループを中断させる
  2. PreLoaderクラスを実装する
  3. Consoleクラスを実装する
  4. ZIPファイルをFlasCC VFSに追加する
  5. Stage3Dコンテキストを初期化する
  6. GLS3Dインスタンスを作成する
  7. 標準的なFlashの右クリックメニューを無効にする
  8. キーボード入力処理を実装する
  9. マウス入力処理を実装する
  10. Mainを呼び出す
  11. ランループを呼び出す
  12. SWFとしてコンパイルする

ランループを中断させる

多くのゲームと同じように、Neverballは入力イベントをポーリングしながら、すべてのゲームロジックを動作させるmain()関数の中でランループを使用します。Flashランタイムは少し違う形で動作します。ランタイムが表示を更新できるように定期的にユーザーコードを返すことが求められます。つまり、通常はランループ内で実行されるコードをenterFrameイベントで実行できるように、Neverballに1つだけ小さな変更を行う必要があります。これによって、メインのFlash Playerスレッドをブロックすることなく、ランループとして同じ最終結果を出すことができます。

// ランループを使ったオリジナルコード int main() { // ゲームステートの初期化 while(true) { // ゲームロジックの実行 } } // ランループを中断させた修正コード int main() { // ゲームステートの初期化 AS3_GoAsync(); } void executeGameLogic() { // ゲームロジックの実行 }

FlasCCでコンパイルすると、修正バージョンは通常通りゲームを初期化しますが、main関数は特殊な方法でAS3_GoAsync()によって中断されるため、静的デストラクタは実行されません。

neverball-1.5.4/ball/main.cファイルの448行目に、whileループを確認できると思います。これが中断させるメインのランループです。このループの前に、AS3_GoAsync()マクロ をifdef内に挿入します。これによって、FlasCCコンパイラーを使った時だけコンパイルされます。そして、whileループのコンテンツをmainLoopTick()という名前の新しい関数にコピーしますが、これは、すべてのenterFrameで呼び出されます。このチュートリアルのサンプルファイルは、main.cを変更したコピー(ball_main.c)なので、変更前の内容と比較することができます。

PreLoaderクラスを実装する

コンパイルされたNeverball SWFファイルは1.5MB程度になり、ZIPファイルに圧縮されたデータファイルは、選択したNeverballレベルの数にもよりますが、10~50MBになります。SWFファイルやデータファイルをダウンロードする際にその進捗を表示しないのは優れたユーザー体験ではないため、ダウンロードの進捗を示すプログレスバーを表示するプリローダーを使用します。

これは一般的に必要であるため、FlasCCは、デフォルトですべてのSWFにシンプルなプリローダーを挿入します。このプリローダーは、メインのSWFを読み込む際の進捗イベントを取得するprogressEventハンドラーを登録するために、loaderInfoオブジェクトを使用します。このコードは一番最初にSWFに挿入されるため、SWFを数キロバイト読み込んだ時点でプログレスバーが表示されます。

デフォルトのPreLoaderクラスを実装するためのコードは、sdk/usr/share/DefaultPreLoader.asで確認できます。これを手がかりとして使用して、SWFの実際のコンテンツを実行する前に並行して3つのZIPファイルをダウンロードするように、PreLoaderクラスを変更しました。大きなデータファイルを複数の小さなファイルに分割すると、ダウンロードをスピードアップできます。また、ファイルがWebブラウザのキャッシュに保存されるようになります。ブラウザによって制限事項が異なりますが、通常10MB以下であれば、ファイルはキャッシュに保存されたままになります。

SWFでは、プリローダーがSWFの最初のフレームになり、FlasCCでコンパイルしたコードの残りが2番目のフレームになります。そのため、onPreloaderCompleteイベントハンドラーで、gotoAndStop(2)の呼び出しを確認できます。これは、Flash Playerに2番目のフレームに含まれているコードを実行するように指示するものです。SWFの読み込みが完了する前にこれが呼び出されると、画面上は何もアップデートされず、進捗を示すビジュアルが表示されません。

2番目のフレームに移動した後に、ダウンロードしたZIPファイルを渡すようにFlasCCアプリケーションで指定した関数を呼び出すことができます。Consoleクラスの実装に関する後述のセクションでは、これらのZIPファイルをFlasCCの仮想ファイルシステム(VFS)に追加する方法を紹介します。ZIPファイルを提供したら、Consoleクラスのインスタンスを作成して、FlasCCアプリケーションコードの実行を開始します。

このチュートリアルのサンプルファイルには、このセクションで説明したPreLoaderクラスを実装したPreLoader.asが入っています。このファイルと、SDKのデフォルトの実装を比較して、どのように変更されているかを確認してみてください。

Consoleクラスを実装する

すべてのFlash SWFにはルートスプライトがあります。これはflash.display.Spriteから生じるクラスで、SWFが実行されるとただちに、ステージ上に作成および配置されます。これはSWFのエントリーポイントであり、FlasCC SWFも例外ではありません。FlasCCはデフォルトでルートスプライトの実装(Consoleクラス)を埋め込みますが、これは単純にC/C++コードのmain()を呼び出し、TextFieldで画面へのprintf出力を指示しています。

このデフォルトのConsoleクラスの実装は、FlasCC SDKのsdk/usr/share/Console.asで確認できます。これは出発点として良い足がかりとなりますが、多くの場合、このファイルをコピーして、アプリケーションに合わせて修正する必要があります。

以下のタスクを完了するように、デフォルトのConsole実装をNeverballに合わせて変更する必要があります。

  • NeverballデータのZIPファイルをFlashCCの仮想ファイルシステムに追加する
  • GPUアクセラレーション機能を使ってレンダリングするStage3Dコンテキストを初期化する
  • GLS3Dを初期化する
  • 標準的なFlash Playerの右クリックメニューを無効にする(オプション)
  • マウスおよびキーボードからの入力を処理し、libVGLへ渡す
  • enterFrameイベントで、中断したランループmainLoopTick関数を呼び出す

ZIPファイルをFlasCC VFSに追加する

FlasCCは、さまざまなサブディレクトリにデータを読み込み/書き込みできるようにするため、VFSへ追加できるバッキングストアを作成するシンプルなActionScript APIを提供します。最も基本となるのはInMemoryBackingStoreです。これはByteArrayとして各ファイルをメモリに保存するだけで、SWFの実行中のデータは保存しません。Flash Local Shared Object(LSO)内にファイルを保存するバッキングストアを実装するやり方は、FlasCCアプリケーションで永続的なデータストレージを実現する1つの方法ですが、サーバーへアップロードしデータを保存するバッキングストアを実装することもできます。

このチュートリアルでは、バッキングストアが変更ファイルを保存するのではなく、SWFが最初に実行された際にダウンロードされる複数のZIPファイルのコンテンツとともに追加されるメモリ内バッキングストアを使用します。

Console.asでは、ZipBackingStoreという名前の小さなクラスを実装しました。これは、InMemoryBackingStoreクラスを拡張して、 addZipという新しいメソッドを追加します。 このメソッドはByteArrayを取得し、ZIPファイルとしてコンテンツを扱います。このZIPファイルは、com.adobe.flascc.vfs.zipパッケージの中にFlasCCの一部として同梱されているZIP解凍ライブラリを使って解凍されます。以下のように、ZIPファイルのコンテンツを抽出して、InMemoryBackingStoreオブジェクトに正しいファイルとディレクトリを簡単に与えることができます。

public function addZip(data:ByteArray) { var zip = new ZipFile(data) for (var i = 0; i < zip.entries.length; i++) { var e = zip.entries[i] if (e.isDirectory()) { addDirectory("/root/data/"+e.name) } else { addFile("/root/data/"+e.name, zip.getInput(e)) } } }

また、addVFSZipというグローバル関数も実装しています。これは、ZIPファイルがダウンロードされると、プリローダーで呼び出され、各ZIPファイルは、FlasCC VFSに後から追加し、アプリケーションで使用できる単一のZipBackingStoreオブジェクトに追加されます。

Stage3Dコンテキストを初期化する

Stage3Dコンテキストの初期化は非常に簡単です。 stage3Dsベクターの最初のStage3Dオブジェクトにイベントリスナーを追加して、 requestContext3Dを呼び出します。すると、CONTEXT3D_CREATEイベントが送出されるか、または、コンテキストが作成できない場合はERRORイベントが送出されます。

var s3d:Stage3D = stage.stage3Ds[0]; s3d.addEventListener(Event.CONTEXT3D_CREATE, context_created); s3d.requestContext3D(Context3DRenderMode.AUTO)

CONTEXT3D_CREATEイベントを受け取った時は、このコンテキストを持つStage3DオブジェクトからContext3Dオブジェクトにアクセスできます。Context3Dオブジェクトは、すべてのGPUリソースを作成し、3Dオブジェクトを描画できるインターフェイスを提供します。

フォールバックオプションとして、Stage3Dは、GPUアクセラレーションが無効になっていたり、何らかの理由で使用できなかったりする場合に、Softwareモードで実行することができます。シンプルなコンテンツの場合はこうした方法を取ることができますが、Neverballの場合は、Softwareモードは遅すぎるため、以下のようにして、アプリケーションをSoftwareモードで実行しないようにすることができます。

var ctx3d:Context3D = s3d.context3D ctx3d.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2, true /*enableDepthAndStencil*/ ) trace("Stage3D context: " + ctx3d.driverInfo); if(ctx3d.driverInfo.indexOf("Software") != -1) { trace("Software mode unsupported..."); return; }

セキュリティ上およびプライバシー上の理由から、リリースバージョンのFlash Playerで実行する場合には、driverInfoに「DirectX」「OpenGL」「Software」のいずれかを使用します。デバッガバージョンのFlash Playerで実行する場合は、ドライバーの正確なバージョンに関する情報を提供します。ソフトウェアフォールバックを使用する際には、driverInfoにそれを使用する理由も含むことができます。 詳しくは driverInfoドキュメントを参照してください。

GLS3Dインスタンスを作成する

GLS3Dには、非常にシンプルなAS3インターフェイスがあります。Context3Dオブジェクトを取得したら、以下のようにGLAPIオブジェクトを作成します。

GLAPI.init(ctx3d, null, stage); GLAPI.instance.context.clear(0.0, 0.0, 0.0); GLAPI.instance.context.present();

GLAPIインスタンスは全体的なOpenGLステートを管理し、Context3Dインスタンスの実行のためにOpenGL Cラッパーによって使用されます。 enterFrameハンドラーでは、GLAPI.instance.context.present()を呼び出して、現在のフレームをレンダリングさせる必要があります。

標準的なFlash Playerの右クリックメニューを無効にする

長い間、Webページのエレメントを右クリックすると、標準的なFlash Playerコンテキストメニューが表示され、それがFlashで作られたコンテンツかどうか確認することができました。しかし、ゲームの場合、多くのゲーム開発者はマウスの右ボタンを使ってアクションを起こさせたいため、この機能は不都合でした。

そこでFlash Player 11.2では、RIGHT_CLICKイベントまたはRIGHT_MOUSE_DOWNイベントのいずれかのイベントハンドラーを登録することで、このメニューを無効にする機能を導入しました。Neverball用のコンソールは、SWFが古いバージョンのFlash Playerで実行された場合無効になるように実装しました。このテクニックは、最新のFlash Playerの機能をサポートするゲームを提供しながら、古いバージョンのFlash Playerで実行された場合に別のビヘイビアーにフォールバックできるため、非常に便利な手法です。

try { stage.addEventListener(MouseEvent.RIGHT_CLICK, rightClick); // 成功 } catch(e:*) { // 失敗。まだ右クリックメニューが表示される } private function rightClick(e:MouseEvent):void { // 右クリックメニューが表示されない }

キーボードの入力処理


FlasCCに同梱されているlibSDLには、キーボード、マウス、グラフィックを処理するlibVGLに依存するバックエンドが含まれています。libVGLは低レベルのライブラリで、Cファイル記述子から読み込み、受け取ったバイトをキーコードに変換することで入力を処理します。FlasCCで提供されているものは、実験的に使用するのには適しています。しかし、libSDLベースでFlashゲームを提供したい場合は、カスタムの入力処理を実装する必要があります。

入力処理を適切に行うためには、まずStageにスプライトを追加します。これによって、入力イベントを受け取ることができる表示リスト内に少なくとも1つのオブジェクトが存在することになります。

var inputContainer:Sprite = new Sprite() addChild(inputContainer)

次に、「up」および「down」のキーイベントのイベントハンドラーを登録します。

stage.addEventListener(KeyboardEvent.KEY_DOWN, bufferKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, bufferKeyUp);

libSDLイベントループが実行されると、必要な数のキーボードイベントを取り出すことができるように、キーイベントを受け取ると、それらをByteArrayにバッファリングします。

public function bufferKeyDown(ke:KeyboardEvent) { if(Keyboard.capsLock || ke.keyCode >= 127) return; keybytes.writeByte(int(ke.keyCode & 0x7F)); } public function bufferKeyUp(ke:KeyboardEvent) { if(Keyboard.capsLock || ke.keyCode > 128) return; keybytes.writeByte(int(ke.keyCode | 0x80)); }

現在の入力処理構造における唯一の制限事項は、libVGLがシングルバイト文字コードしか使用しないことです。このため、Flash Playerのキーコードを直接渡すことができません。代わりに、使用できるキーを制限する必要があります。実際のゲームでは、Flash Playerのキーコードを直接使用したり、キーイベントのキューイングに別のメカニズムを使用したりできるように、ダブルバイトに対応させたほうがいいケースも出てくるでしょう。

NeverballのランループがenterFrameイベント中に実行されると、libVGLはファイル記述子0を使用してシングルバイトの読み取り動作を実行しようとします。 コードはVFS用のコンソールとしてConsoleクラスを割り当てたため(CModule.vfs.console = this)、stdinstdoutstderrの各ファイル記述子に対する読み取りおよび書き込みはすべて、このConsoleインスタンスで処理されます。このため、シングルバイトの読み取り動作の呼び出しは、readを実装して処理する必要があります。

public function read(fd:int, bufPtr:int, nbyte:int, errnoPtr:int):int { if(fd == 0 && nbyte == 1) { keybytes.position = kp++ if(keybytes.bytesAvailable) { CModule.write8(bufPtr, keybytes.readUnsignedByte()) } else { keybytes.position = 0 kp = 0 } } return 0 }

マウスの入力処理

libVGL/libSDLを使ったマウス入力処理は、すべてのフレームの現在のポジションとボタンステートを更新することで実行されます。 libVGLは、vgl_cur_mxvgl_cur_myvgl_cur_buttonsといった整数型のグローバル変数を指定します。ActionScriptから値を設定する場合は、CModule.getPublicSymbol()関数を使用して、変数のアドレスを参照して、 CModule.write32()を使ってそのアドレスに値を書き込む必要があります。以下は、現在のlibVGLにマウスのX座標を設定する方法を示しています。

// libVGLでのマウス入力処理の例 var mouseX:int = ... // mouseEventからマウスのX座標を取得する var vgl_mx:int = CModule.getPublicSymbol("vgl_cur_mx") CModule.write32(vgl_mx, mouseX);

Mainを呼び出す

すべてのC/C++プログラムと同様、main()関数がエントリーポイントになります。main()を実行しても、それが中断されたままにするには(C++の静的な初期化が解除されることなく進行できるようにするため)、Cコードのmain関数内にAS3_GoAsync()の呼び出しを挿入します。

CModule.vfs.console = this CModule.vfs.addBackingStore(zfs, null) CModule.startAsync(this, new <String>["/root/neverball.swf"])

まず、このConsoleインスタンスを使い、CModule.vfs.consoleにthisを割り当てることによって、stdinstdoutstderrに対する読み込み/書き込みを処理することを指定します。次に、ファイルとともにすでに追加されているZIPバッキングストアをVFSに追加し、libcファイルIOがすべてのNeverballデータファイルの読み込み/書き込みを処理できるようにします。最後に、実行ファイル(exe)の名前(Neverballは、ファイルシステムのどこにデータファイルがあるか判断するために使用する)を指定するため、「argv[0]」 の有効な値を渡しながらstartAsync()を呼び出します。

startAsync()が返されたら、main()関数が問題なく実行され、AS3_GoAsync()によって中断されていることを確認できます。これで、FlasCCコードは有効な状態になり、他の関数を呼び出したり、C/C++コード内のパブリック変数を利用したりできます。

ランループを呼び出す

main()関数は実行が完了していますが、ランループを中断しているため、何も起きていません。ゲームにそのゲームロジックを実行させるためには、フレームごとにmainLooptick()関数を呼び出し、ゲームロジックとレンダリングが実行されるようにします。

var mainloopTickPtr:int = CModule.getPublicSymbol("mainLoopTick") CModule.callI(mainloopTickPtr, emptyVec);

上のコードの1行目では、Cの名前を使って関数のアドレスを参照しています(C++関数では、オブジェクトファイルにnmを実行した時のマングルされた名前になります)。2行目では、無効の値または整数の値を返す関数であることを示すcallI()を使って関数を呼び出しています(64ビットの浮動小数点値を返す場合はcallNを使用します)。AS3 APIに関する詳細は、FlasCCドキュメントでご確認ください。

NeverballをSWFとしてコンパイルする


この段階ですべての要素が揃ったので、ActionScriptサポートクラスと合わせてCコードをコンパイルすることができます。時間の節約のために、このチュートリアルのサンプルファイルに入っているMakefileを使用します。Makefileとほかの補足ファイルを一緒に使いながら、NeverballをSWFにコンパイルすることができます。

  1. まず、Neverballのソースtarballのコンテンツを「Neverball-1.5.4」というディレクトリに取り出します。コマンドラインから、以下のようにtarを使用します。
tar -zxvf neverball-1.5.4.tar.gz
  1. 次に、自分でmain.cを変更してランループを中断させるか、または、このチュートリアルに入っている変更済みのファイルをコピーします。
cp ball_main.c neverball-1.5.4/ball/main.c
  1. Makefileを呼び出すには、以下のように、FlasCC SDK、GLS3Dリポジトリ、alcextraリポジトリの場所を指定する必要があります。
make FLASCC=/path/to/flascc/sdk ALCEXTRA=/path/to/alcextra GLS3D=/path/to/GLS3D

最初の実行時は、10分程度かかることがあります。

以下は、Makefileで実行する手順です。

  1. 最終的なSWFファイルとデータZIPファイルを保存するビルドディレクトリを作る。
  2. FlasCC SDKに同梱されているActionScriptコンパイラーを使って、Consoleクラスをコンパイルする。
  3. FlasCC SDKに同梱されているActionScriptコンパイラーを使って、PreLoaderクラスをコンパイルする。
  4. Neverballソースディレクトリのsolsビルドターゲットを呼び出し、Neverballレベルのデータをすべて生成する。
  5. dataディレクトリのすべてのファイルをZIPファイルに圧縮する。このファイルには、コンパイルされたNeverballレベルのデータが入っており、1つのレベルをローディングすると、Neverballが実行時にデータを読み込む。
  6. Neverballソースディレクトリのneverball.swfビルドターゲットを呼び出し、すべてのCコードをSWFにコンパイルし、連携させる。

メインのNeverball makefileでは、 FlasCC SDKのbinディレクトリが最初に来るようにPATH 環境変数を上書きします。こうすることで、スクリプトがgccや他の低レベルツール(arnm)を呼び出した場合に、FlasCC SDKのバージョンが最初に発見され、使用されるようになります。

Makefileの他の変数がいくつか無効になるため、依存関係にあるすべてのライブラリのヘッダーおよびアーカイブはFlasCC SDKまたはalcextraリポジトリから使用されます。

LDFLAGS変数はFlasCC固有のオプションで上書きされます。これらのオプションによって、GCCが最終的な出力ファイルとしてSWFを生成し、コンソールの実装やGLS3Dのサポートファイルなどさまざまなファイルと連動させることができます。使用するプリローダーも、LDFLAGS変数に渡されるGCCコマンドとして指定します。

FlasCC固有のGCCオプションの詳細については、FlasCCリファレンスガイドのGCCセクションをご確認ください。また、gcc –-target-helpを実行すると、簡単な説明を見ることができます。

SWFのデバッグ

FlashCCでは、GDBデバッガを使いながら、FlasCCでコンパイルしたC/C++コードをデバッグできるようになっています。どのようにこれが機能するのか理解するため、Neverballレンダラーの一部を実行して確認してみます。

まずは、Neverballが最適化されない状態でシンボルをデバッグしながらコンパイルするようにMakefileを変更する必要があります。使用している最適化フラッグを「-O4」から「-O0 –g」に切り替えます。MakefileにNeverballを作成するための新しいルールは以下のとおりです(CFLAGS変数に対する変更に注目してください)。

cd neverball-1.5.4 && PATH=$(FLASCC)/usr/bin:$(ALCEXTRA)/usr/bin:$(PATH) make \ DATADIR=data \ LDFLAGS="-L$(ALCEXTRA)/install/usr/lib/ $(FLASCC)/usr/lib/AlcVFSZip.abc -swf-preloader=$(BUILD)/neverball/PreLoader.swf -swf-version=17 -symbol-abc=$(BUILD)/neverball/Console.abc -jvmopt=-Xmx4G -emit-swf -swf-size=800x600 " \ CFLAGS="-O0 -g" \ CC="gcc" \ SDL_CPPFLAGS="$(shell $(FLASCC)/usr/bin/sdl-config --cflags) -I$(ALCEXTRA)/install/usr/include -I$(ALCEXTRA)/install/usr/include/SDL -I$(GLS3D)/install/usr/include" \ PNG_CPPFLAGS="$(shell $(FLASCC)/usr/bin/libpng-config --cflags)" \ SDL_LIBS="$(shell $(FLASCC)/usr/bin/sdl-config --libs)" \ PNG_LIBS="$(shell $(FLASCC)/usr/bin/libpng-config --libs) -lz" \ OGL_LIBS="$(GLS3D)/install/usr/lib/libGL.abc -L$(GLS3D)/install/usr/lib/ -lGL -lz -lfreetype -lvorbis -logg -lz" \ EXT=".swf" \ DEBUG=1 ENABLE_NLS=0 \ neverball.swf \ -j8

デバッグ情報とともにSWFを作成した後、GDBでそれを起動することができます。まず、SWFを実行するためにどのFlash Player実行ファイルを使用するかGDBに指示する環境変数を設定します。

export FLASCC_GDB_RUNTIME=”/path/to/FlashPlayer”

次に、デバッグしたいSWFを渡しながら、GDBを起動します。

/path/to/flascc/sdk/usr/bin/gdb ./build/neverball/neverball.swf GNU gdb (GDB) 7.3 Copyright (C) 2011 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "--host=x86_64-apple-darwin10 --target=avm2-elf". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... (gdb)

main関数にブレークポイントを設定して、SWFを実行することができます。

(gdb) b main No symbol table is loaded. Use the "file" command. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (main) pending. (gdb) r Starting program: ./build/neverball/neverball_debug.swf 0xdddddddd in ?? () Breakpoint 1, 0xf0028ae5 in main (argc=1, argv=0x200fd0) at main.c:384 384 SDL_Joystick *joy = NULL; (gdb)

Neverballはデバッガで実行しているので、コードのどの部分でも検証することができます。ランループを中断するために作成した関数にブレークポイントを設定してスタートします(mainLoopTick)。

(mainLoopTick): (gdb) b mainLoopTick Breakpoint 2 at 0xf0028cba: file main.c, line 523. (gdb) c Continuing. Breakpoint 2, 0xf0028cbb in mainLoopTick () at main.c:523 523 loop(); (gdb)

cコマンドを数回続けると、Flash Playerは数フレームをレンダリングし、Flash Player画面にこれが反映されます。

デバッガがランループ開始時に停止すると同時に、Neverballがベクターを標準化するために使用するコードに別のブレークポイントを設けることができます。これは、neverball-1.5.4/share/vec3.cファイルにあるv_nrmという名前の関数に設定します。

btコマンドを使用して、この関数が呼び出された時点でCコールスタックを確認することができます。

(gdb) b v_nrm Breakpoint 4 at 0xf005bca8 (gdb) c Continuing. Breakpoint 4, 0xf005bca9 in v_nrm () from remote:290.elf (gdb) bt #0 0xf005bca9 in v_nrm () from remote:290.elf #1 0xf00173c1 in game_set_fly (k=7.70714155e-44, fp=0xc8) at game_server.c:987 #2 0xf00256bc in title_timer (id=18, dt=0.0111111114) at st_title.c:219 #3 0xf000e2ce in st_timer (dt=0.0111111114) at state.c:92 #4 0xf0028cf5 in mainLoopTick () at main.c:545 #5 0x00000000 in ?? () (gdb)

Cコールスタックは、シンボル情報のないスタックフレームで終わります: #5 0x00000000 in ?? ()。これは、この時点で上記のスタックフレームがCコードではなく、ActionScriptコードであることを示しています。ActionScriptフレームを含むすべてのbacktraceを確認する場合は、FlasCC SDKに同梱されているGDBがサポートしている特殊なas3bt関数を使う必要があります。

(gdb) as3bt (*)global/C_Run::F_v_nrm()[/var/folders/hm/ty_1kgz51ds69dgbm_rl7lzc0000gn/T/cckXQgoe.lto.1.as:51] global/C_Run::F_game_set_fly()[/tmp/llvm_mkdtemp_cxGZor/alctmp-KjePUT.as:5160] global/C_Run_ball_2F_st_title_2E_o_3A_5F17B85D_2D_D761_2D_4032_2D_9214_2D_D6D354CB2D9C::F_title_timer()[/tmp/llvm_mkdtemp_5IAAke/alctmp-5w3gQB.as:907] global/C_Run::F_st_timer()[/tmp/llvm_mkdtemp_57bznU/alctmp-mAlwog.as:445] global/C_Run::F_mainLoopTick()[/tmp/llvm_mkdtemp_z4xDDh/alctmp-5GTYqI.as:2436] com.adobe.flascc::CModule$/callI() com.adobe.flascc::Console/enterFrame() (gdb)

C関数はすべてマングルされた名前を持ち、コンパイラーが生成した非常に長い名前でパッケージされるため、これを読み取ることは簡単ではありません。しかし、Console.asで指定されたenterFrameハンドラーが、パラメーターとしてmainLoopTickと一緒に、CModule.callIを呼び出しているコールスタックの下に確認することができます。FlasCCバージョンのGDBで使用できるActionScriptおよびFlasCC固有のコマンドについて詳細は、FlasCCリファレンスガイドのGDBセクションをご確認ください。

FlasCCに同梱されているGDBは、ネイティブの開発プラットフォームで使用する通常のGDBの拡張版であり、同じコマンドだけでなく、FlasCC固有のコマンドもいくつかサポートしています。これにより、スタックフレームやEvaluate関数などを切り替えることができます。

(gdb) f 1 #1 0xf00173c1 in game_set_fly (k=7.70714155e-44, fp=0xc8) at game_server.c:987 987 v_nrm(view_e[0], view_e[0]); (gdb) p view_e $1 = {{4.43174553, 0, -3.24232864}, {0, 1, 0}, {3.24232864, -0, 4.43174553}} (gdb) p *fp $2 = {ac = 0, mc = 0, vc = 0, ec = 0, sc = 0, tc = 0, gc = 0, lc = 0, nc = 0, pc = 0, bc = 0, hc = 0, zc = 0, jc = 0, xc = 0, rc = 0, uc = 0, wc = 0, dc = 0, ic = 0, av = 0x0, mv = 0x0, vv = 0x0, ev = 0x0, sv = 0x0, tv = 0x0, gv = 0x0, lv = 0x0, nv = 0x0, pv = 0x0, bv = 0x0, hv = 0x0, zv = 0x0, jv = 0x0, xv = 0x0, rv = 0x0, uv = 0x0, wv = 0x0, dv = 0x0, iv = 0x0} (gdb)

次のステップ

このチュートリアルでは、FlasCCとGLS3Dのおかげで、既存のOpenGLベースのアプリを簡単にFlash Playerに移植できることを説明しました。コードを少しだけ追加し、既存のコードベースをわずかに変更するだけでいいことがお分かりいただけたでしょうか。FlasCCはC言語およびC++言語を全面的にサポートしているだけでなく、SWIGなどの時間節約に役立つツールやGDBベースのデバッガもサポートしているため、アクティブなコードベースにFlash Playerビルドターゲットを簡単かつ目立たないように保持することができます。

Neverballには、Consoleクラス、Preloaderクラス、VFSが適用されたので、Flash Playerでゲームを再生することができます。FlasCCに同梱されているlibSDLは、現在音声を完全にサポートしていませんが、CコードとConsoleクラスをわずかに変更するだけで、簡単に音声を再生することができます。音声のサポートに必要な変更については、my GitHub repositoryのコードをご確認ください。

SWFがFlash Playerスタンドアロンで問題なく動作したら、Webページには簡単に埋め込むことができます。SWFを埋め込むには、SWFObject JavaScriptライブラリを使用するのが一番簡単な方法です。この方法では、ユーザーが対象外のFlash Playerを使用しているなどの問題を処理することができます。詳細は、SWFObjectのコードとドキュメントをご確認ください。

基本的な移植が完了したら、高度なFlash Playerの機能を活用しながら改善することができます。FlasCCアプリケーションは、Flash API全体を利用できるため、フルスクリーンサポート、ローカル共有オブジェクト、RTMFP、ハードウェアアクセラレーションビデオなどの機能を活用したい場合に制約を受けることがありません。Flash Playerの各バージョンに導入された新機能のリストは、Flash PlayerとAdobe AIRの機能リストをご確認ください。