Aqualeadプログラミングマニュアル


目次

1. 更新関連
1. アップデータ
1.1. 生成と破棄
1.2. アップデート処理
1.3. 開始と停止
1.4. ファイバ
1.5. イベント
1.6. 他のオブジェクトの参照
1.7. Update処理の切り替えの注意点
2. メッセージ
2.1. 基本
2.2. 遅延メッセージ
2.3. クイックメッセージ
2.4. 受信イベント
2.5. オプション
2.6. スリープ
3. シーン
3.1. 生成と破棄
3.2. デフォルトシーン
3.3. 生成済みのインスタンス
3.4. ファミリービット
3.5. 描画ノードの列挙
2. 描画関連
1. 描画ノード
1.1. 生成と破棄
1.2. 座標管理
1.3. 表示コントロール
1.4. 親子関係
1.5. グループ
2. スクリーン
2.1. 基本的な機能
2.2. プライオリティ
2.3. その他の機能
3. カメラ
3.1. 基本的な使い方
4. パーティクル
4.1. 基本的な使い方
5. プリミティブ
5.1. 基本的な使い方
6. 3Dプリミティブ
6.1. 基本的な使い方
6.2. 頂点の書き換え
6.3. 簡易形状設定
7. テキスト
7.1. 基本的な使い方
7.2. アイコン
7.3. 色
3. モーション
1. モーション基本
1.1. 基本的な使い方
1.2. モーションに対する操作
1.3. モーションの更新タイミング
1.4. マーカ
1.5. モーション補間
1.6. ブレンド
1.7. ノートトラック
2. 動的モーション
2.1. 基本的な使い方
2.2. 複数モーションの登録
2.3. モーションの変換
3. アセンブル処理
3.1. 基本的な使い方
4. コントローラ
1. 共通
1.1. 概要
1.2. 基本的な使い方
1.3. スリープ
1.4. プライオリティ
1.5. タグ
1.6. ユーザーコントローラ
2. ゲーム系コントローラ
2.1. Fsmコントローラ
2.2. モーションマネージコントローラ
5. リソース関連
1. イメージ
1.1. 基本的な使い方
1.2. ダイレクトアクセス
2. テクスチャ
2.1. 基本的な使い方
2.2. 親子関係
2.3. 境界情報
2.4. ロック
3. メッシュ
3.1. メッシュの構成
3.2. メッシュ共通部
3.3. メッシュブロック
4. 動的メッシュ
4.1. 基本的な使い方
4.2. 動的メッシュ共通部
4.3. 動的メッシュブロック
4.4. 頂点情報
5. フォント
5.1. フォントの種類
5.2. 文字変換テーブル
5.3. アイコン
6. リソース
6.1. 基本的な使い方
6.2. リソースに登録出来るデータ
6.3. ロードした各種リソースの使用
7. サウンド
7.1. サウンドで使用するクラス
7.2. 単純な使い方
7.3. サウンドのロード
7.4. アーカイブからの一括ロード
7.5. ボイスクラス
7.6. サウンドプレイヤー
6. メニュー
1. ウィジェット基本
1.1. ステート
1.2. コールバック
1.3. モーション
1.4. フォーカスの移動
1.5. ロック
1.6. オーナー
2. コンテナウィジェット
3. ボタンウィジェット
4. リストボックスウィジェット
5. セルリストウィジェット
6. カーソル
7. スキン
8. スクロールバー
7. システム関連
1. メモリ
2. プロパティ
2.1. 使用可能なクラス
2.2. プロパティクラス
2.3. 取得方法
2.4. プロパティの持つ情報
2.5. 値の読み書き
2.6. ユーザープロパティ
3. タスク
3.1. 基本的な使い方
3.2. タスクでのシーン
3.3. シンボルによるタスク選択
3.4. リソースの管理
3.5. 各種データの自動解放
4. スレッド
5. スクリプト
5.1. 生成
5.2. 関数の登録
5.3. 変数
5.4. ロードと実行
5.5. クラスの使用
5.6. ファイバ
5.7. コントローラ
5.8. ファイバコントローラ
5.9. イベントコントローラ
8. 入力関連
1. パッド
1.1. 基本的な使い方
9. 各種ユーティリティ
1. コンテナ
1.1. 生成と破棄
1.2. 値の登録と取得
1.3. 使用出来る型
1.4. その他のアクセス関数
2. テーブル
2.1. フィールド
2.2. レコード
2.3. データの読み書き
2.4. テーブル参照
2.5. 共有
2.6. 保存
2.7. 読み込み
2.8. 直参照
10. ファイル関連
1. ストリーム
1.1. ファイルのオープンとクローズ
1.2. Readによる読み込み
1.3. データポインタの取得
2. アーカイブ
2.1. 基本的な使い方
11. コリジョン関連
1. コリジョン
1.1. 基本的な使い方
1.2. コリジョンマネージャ
1.3. コリジョンの各種設定
1.4. コリジョンマネージャを使わない判定

第1章 更新関連

1. アップデータ

アップデータ(ALUpdater)は、Aqualeadの基礎となる重要なクラスの一つです。

提供する機能は、毎フレームのアップデート処理、各種イベント処理、メッセージ処理、およびハンドル処理です。

1.1. 生成と破棄

生成にはCreate関数を使います。引数はありません。

        ALUpdater * obj = ALUpdater::Create();

破棄するときは、Destroy関数を使用します。

        obj->Destroy();

注意点として、アップデータは破棄を呼び出しても、すぐにメモリ上から削除されません。 具体的には、最低1フレーム以上経過してから破棄します。 この間は、obj->IsDestroy()関数を呼ぶとTrueが返ります。 この特性により、他のアップデータから破棄されたことを検知することが出来ます。 詳しくは後述します。

ALUpdaterを継承したクラスを使う場合、デストラクタの呼び出しが遅れる点に注意してください。 破棄を行うタイミングに制限はありません。 後述するアップデート処理や、各種コールバック内で自身を破棄することが出来ます。 ただし、その場合は速やかにイベントからリターンしてください。 破棄を実行したアップデータに対し、書き込みを行うとアサートで停止します。 ただし、状態が変化しないconst関数を使用した値の取得ならば、メモリ上から破棄するまでは有効です。

1.2. アップデート処理

アップデート処理を登録するためには、コールバック関数を登録する方法と、ALUpdaterを継承する方法があります。 どちらを使っても構いませんが、いくつかの処理を切り替えたい場合などはコールバック関数式が便利です。 コールバック関数を登録するには、SetUpdateFunc関数を使用します。

	void update_func( ALUpdater * self )
	{
		~~
	}

	ALUpdater * obj = ALUpdater::Create();
	obj->SetUpdateFunc( update_func );
        

アップデートコールバックの引数は、自分自身のアップデータのみを持ちます。 返り値はありません。 継承して使用する場合は、DoUpdate関数をオーバーライドします。

	virtual void DoUpdate();
        

DoUpdateには引数はありません。

	obj->UseUpdate();
        

これによりDoUpdateが毎フレームごとに呼びだされます。 コールバック関数を使用する際は、関数を登録したときに自動的にUseUpdate()を実行するため、明示的に呼び出す必要はありません。 なお、DoUpdateをオーバーライドし、UseUpdateを実行していても、アップデートコールバック関数を登録している場合は、 コールバック関数を優先し、DoUpdateは呼び出しません。

どちらの場合でも、毎フレームごとに一回アップデート処理を呼び出します。 後述するファイバを使用することにより、処理を中断することも可能です。 アップデートの順番はプライオリティで指定します。

プライオリティは1~254の固定小数値で指定します。 固定小数クラスは自動的に使用されるため、通常は整数、もしくは実数で指定します。 少数の精度は8ビットなので、1/256=0.00390625以下の幅を設定しても同じものとして認識します。

プライオリティは通常は整数を使い、どうしても微調整したいときのみ実数を使用することを推奨します。 少数以下の値を使うと、アップデータの登録・破棄時に位置をサーチする必要があるため、わずかな速度のペナルティがあります。 なお、デフォルト値はALPRIO_DEFAULTで128です。 シンボルとして、ALPRIO_MIN = 1、ALPRIO_MAX = 254を定義しています。

1.3. 開始と停止

アップデータは、アップデートコールバック関数を登録するか、UseUpdateを実行すると動作を開始します。 停止する方法は、アップデータ自身に2つ、アップデータの管理クラスであるシーンクラスに2つあります。 ここではアップデータ自身の停止方法を説明します。 1つ目は、SleepとAwakeです。 Sleepは引数ありと引数なしがあり、引数なしの場合はスリープを解除するまで、ありの場合は指定したフレーム数スリープします。

	obj->Sleep();		//スリープを解除するまでアップデートを停止
	obj->Sleep(10);		//10フレームアップデートを停止
        

なお、数値指定でスリープした場合もAwake関数で強制的に起こすことが出来ます。 スリープ状態はIsSleep()で取得出来ます。

	if( obj->IsSleep() ){
	//このオブジェクトはスリープ中
	};
        

このIsSleepはフレーム指定の有無に関係なくスリープ状態を返しますが、他の要因によって停止しているかどうかは調べることが出来ません。 IsTotalSleep関数を使うことにより、要因関係なしに更新を行っているか、停止しているかを調べることが出来ます。

	if( obj->IsTotalSleep() ){
	//このオブジェクトの更新は止まっている
	};
        

もう一つの方法はSetEnable関数を使用する方法です。 SetEnable( false )を実行しても更新は停止します。 再開するにはSetEnable( true )を実行します。

	obj->SetEnable( false );	//更新を停止
	obj->SetEnable( true );		//Enableによる更新停止を解除
        

Enable状態を取得するにはIsEnable関数を使用します。

	if( obj->IsEnable() ){
	//このオブジェクトの更新は止まっている
	};
        

実際に更新を行うためには、Sleep,Enableを含めた全ての要素で更新可能になっている必要があります。 どれか一つでも更新停止を指示していれば更新は停止します。

アップデータでは、フレーム指定がないことを除けばSleepとSetEnableとの効果は変わりません。 しかし、アップデータを継承した描画ノードではSetEnable( false )を実行すると、更新停止と同時に描画も停止します。 アップデータの使い方としては、処理中の細かな制御にはSleep関数を使い、 しばらく使用しなくなるなどの大きな制御では、SetEnable関数を使うということを推奨します。

1.4. ファイバ

ファイバは、アップデート処理を任意のタイミングで中断または、再開する仕組みです。 マイクロスレッドと呼ぶこともあります。 明示的に生成することも可能ですが、アップデータは自動でファイバを生成、破棄する仕組みを持っているため、 存在を意識せずにいつでも処理を中断することが出来ます。 具体的にはこのように使用します。

	void update_func( ALUpdater * self )
	{
		for( Uint32 i=0; i<10; i++ ){
			ALPrintf("%d\n",i);
			ALYield();
		};
	}
        

ALYield関数が処理を中断する関数で、自動的にファイバを生成し使用します。 ALYieldの行をコメントにしてこのアップデート処理を実行すると、1フレームの間に一気に0~9を表示します。 ALYieldを有効にして実行すると、1フレームに1つずつ数値を表示するため、10フレームで処理が終わります。

このように、ALYieldを使うと、アップデートの途中の好きなタイミングで、アップデート処理を中断し、 他の処理に制御を渡すことが出来ます。 ただし、自動ファイバ処理はアップデートコールバック内か、DoUpdateをオーバーライドした関数内でのみ使用可能です。 それ以外のタイミングでは、ALYieldを使用した自動ファイバ処理は使用出来ません。 一般的にはアップデート処理は、1フレームで完了するように書く必要があります。 例えば5フレームごとにABCと順に表示するという処理を普通に作ると、状態変数とカウンタをどこかに保存し、それを用いて処理することになります。

	void update_func( ALUpdater * self )
	{
		static int mode = 0;
		static int cnt = 5;
		cnt--;
		if( cnt == 0 ){
			cnt = 5;
			switch( mode ){
			case 0:	ALPrintf("A"); break;
			case 1:	ALPrintf("B"); break;
			case 2:	ALPrintf("C"); break;
			};
			mode++;
			if( mode == 3 ){
				self->Destroy();
			};
		};
	}
        

ファイバを使った場合は、以下のようにとても簡単に記述出来ます。

	void update_func( ALUpdater * self )
	{
		ALPrintf("A");
		ALYield(5);
		ALPrintf("B");
		ALYield(5);
		ALPrintf("C");
		self->Destroy();
	}
        

ALYieldはこのように数値をパラメータとして渡すことも出来、その場合は単純にALYieldを続けて指定回数実行したものと等価になります。 ファイバは必要がなくなると自動的に破棄するため、通常は1フレームで処理が完了するような作りにしておきます。 ある特定のタイミングでのみ数フレーム待ちたいといった場合、その時だけALYieldを使うということが出来ます。

さらに、ALYieldはSleepと組み合わせて使うことが出来ます。 ALYieldは原則的に次のフレームで処理を再開するため、先ほどのように回数指定をしても内部的には毎回処理を再開します。 一方、Sleepは完全に呼び出しが停止するため、ALYieldで複数フレーム待つよりは、Sleepを併用した方が負荷が減ります。 先ほどの例の場合、このように記述することも出来ます。

	void update_func( ALUpdater * self )
	{
		ALPrintf("A");
		self->Sleep(5);
		ALYield();
		ALPrintf("B");
		self->Sleep(5);
		ALYield();
		ALPrintf("C");
		self->Destroy();
	}
        

この場合、Sleepは単に次からのアップデートをスリープさせる命令で、ALYieldを実行するまで処理が中断しないことに注意してください。 そのため、ALYieldとSleepの順序で実行すると意味が変わります。

なお、ALYield単体における一定時間スリープと、Sleepとの組み合わせによるスリープでは外部から中断した時は動作が違うことに注意してください。

ALYield単体では後述するCancelYieldで中断出来ますが、その場合は、その直後ではなく関数の頭から再開します。

Sleepとの組み合わせの場合はAwakeで中断が出来、その場合は中断した次の行から再開します。

ALYieldで中断する際は、スタックをファイバごとのワークにコピーし、再開するときにワークからスタックにコピーします。 そのため、使用しているスタックが大きい場合、中断と再開の処理コストは上がります。 スタックを使っていなくても、通常のアップデート処理よりは若干処理コストがかかります。 通常はほとんど問題にならないコストですが、大量のアップデート処理を実行する場合は使用を避けたほうが高速に動作します。

また、スタックをコピーするという特性のため、割り込み処理ルーチンやコールバック関数などにスタック内の変数のポインタを渡すと誤動作します。 これは処理を中断した瞬間に、スタック上の変数におけるポインタの先が無効になるためです。 この点だけがファイバを使用する場合の注意点です。 ファイバで中断しているアップデータを破棄することは、問題なく行えます。

1.5. イベント

アップデータのイベントは、前述のアップデート処理のほか、 Destroyイベント、Removeイベント、Messageイベントの合計4種類あります。 Destoryイベントとは、アップデータでDestroy関数を呼び出した直後に実行するイベントです。 Removeイベントとは、Destroyを実行した数フレーム後、デストラクタを呼ぶ直前で呼ばれるイベントです。 Messageイベントは後述します。 それぞれアップデートイベントと同様に、

	void destroy_func( ALUpdater * self )
	{
		~~
	}

	void remove_func( ALUpdater * self )
	{
		~~
	}

	obj->SetDestroyFunc( destroy_func );
	obj->SetRemoveFunc( remove_func );
        

のようにして使用します。 DestroyイベントはDestroyを実行した直後に呼ばれ、その時点ではIsDestroyはFalseが返ります。 RemoveイベントではIsDestroyではTrueが返るので、そのオブジェクトに対する書き込みはアサートで停止します。

通常Set~~Funcで登録するイベントは一つだけ使用出来、もう一度コールバック関数を設定すると前の関数を上書きします。 しかし、DestroyとRemoveに関しては複数イベントを登録することが出来ます。 その場合はAddDestroyFunc、AddRemoveFunc、DeleteDestroyFunc、DeleteRemoveFuncを使用します。 このAdd~~Funcで登録したイベントは、Set系とは違い、上書きしないで追加します。 Set系の登録個所とは独立しているので、Add~~FuncでSet~~Funcで設定したコールバックを上書きすることはありません。

Add~~Func系の関数でコールバックを登録する場合、Set~~Func系の関数とは違い、 ALPropSetオブジェクトか、voidポインタを登録することが可能です。 ALPropSetオブジェクトを登録した場合、コールバック関数の引数はアップデータではなく、そのALPropSetが第一引数になります。 voidポインタを登録した場合引数が2つになり、アップデータのほか、そのvoidポインタを渡します。

	void destroy_func( ALPropSet * self )
	{
		~~
	}

	void remove_func( ALUpdater * self, void * data )
	{
		~~
	}
	ALPropSet *ps = ALPropSet::Create();
	obj->AddDestroyFunc( destroy_func, ps );
	void * data = NULL;
	obj->AddRemoveFunc( remove_func, data );
        

登録したイベントを削除する場合には、登録した時とまったく同じ引数が必要です。 引数が完全に一致する場合は、Add~~Funcで登録するときもスキップします。 コールバック関数が一致していても、追加パラメータが違っていれば別のイベントとして登録します。 Set系とAdd系の使い分けの方針は、Set系は自分自身が使うために、 Add系は他のアップデータに追加でイベントを登録したい場合に使います。

1.6. 他のオブジェクトの参照

アップデータが他のアップデータを参照する場合、そのアップデータが削除されていないか監視する必要があります。 監視する方法は全部で4つあります。

IsDestroyをポーリングで監視する

一番簡単な方法は、Updateイベント内で、参照しているアップデータのIsDestroyをチェックする方法です。 前述のように、Destroyを実行してから、メモリ上から削除されるまで最低1フレームの時間があるため、 毎フレームチェックする限り、必ずそのオブジェクトがメモリ上から削除される前に検知することが可能です。

この方法の注意点は、毎フレームチェックする必要があるため若干のコストがかかる点と、 何らかの理由で自分自身がスリープすると、IsDestroyを見落とすことがある点です。 後者の問題があるため、IsDestroy監視を行う場合は、自分だけがスリープすることがないように作る必要があります。

参照カウントを使い、IsDestroyを調べる

アップデータは参照カウントを持っているため、 参照しているアップデータの参照カウントを増やすことにより、そのアップデータがメモリ上から削除されることを防ぐことが可能です。

この時、参照カウントを増やすにはAddRefを使用し、減らすにはReleaseを使用します。 参照カウントを増やしたアップデータをDestroyした場合、Destroyイベントが発生し、IsDestroyがTrueになったまま、メモリ上に残り続けます。 Releaseを実行し、参照カウントが0になった後にメモリ上から削除します。

参照するオブジェクトをたまにしか参照しない場合、AddRefで参照カウントを増やしておき、 参照するときのみIsDestroyチェックを行うと、1.の毎フレームチェックより負荷は下がります。 この場合の注意点としては、AddRefしたオブジェクトは責任を持ってReleaseをする必要があります。 Releaseをしなかった場合、そのアップデータはリークします。

Destroyイベントを追加し、削除を監視する

参照するアップデータにDestroyイベントを追加し、そのアップデータが削除されたことを検知します。 この方式の場合は、IsDestroyをチェックする必要はありません。 注意点は、自分を先に削除した場合でもイベントは発生するため、その際に登録したイベントを削除するか、 イベントが発生しても問題ないように作っておく必要があります。

ハンドルを使い、アップデータが削除されたか調べる

ハンドルとは、間接的にオブジェクトを参照するための、ポインタの代わりに使う32ビットの数値です。 ALHandleという別名を定義しています。 削除されたアップデータをハンドルを使って参照するとNULLが返るため、 使用時にNULLをチェックするだけで参照先オブジェクトが削除されていることを調べられます。

ハンドルを取得するためには、アップデータのCreateHandleか、FindCreateHandle関数を使用します。 CreateHandleでは、その度に新規のハンドルを生成します。 FindCreateHandleでは、既に生成済みのハンドルがあればそれを返し、なければ新規のハンドルを生成します。 極端に多くない限りは同じアップデータを指すハンドルがあっても問題はないので、 通常はCreateHandleを使い、同一アップデータに対し複数箇所から多くのハンドルを使用する必要がある場合はFindCreateHandleを使用します。

FindCreateHandleは生成済みハンドルを検索する必要があるため、CreateHandleより若干生成コストがかかります。 生成したハンドルからアップデータを取得するには、Dereference関数を使用します。

	ALUpdater *obj = ALUpdater::Create();
	ALHandle h = obj->CreateHandle();
	ALUpdater *obj2 = ALUpdater::Dereference( h );
        

Dereferenceを実行した際、対象のアップデータがDestroyされていた場合NULLが返ります。 そのため、Dereferenceを実行した際は必ずNULLをチェックする必要があります。 生成したハンドルは破棄する必要はありません。

1.7. Update処理の切り替えの注意点

Update処理に設定したコールバック関数は、どのようなタイミングでもSetUpdateFuncを利用して切り替えることが可能ですが、 SleepやYieldを併用した場合に注意する必要があります。

Update関数の切り替えは、現在設定中のUpdate関数を最後まで実行した時に行います。 これがSleepやYieldを使わず、毎回1フレームで処理が完了する場合は次のフレームから処理が切り替わりますが、 SleepやYieldを使い処理が1フレームで終わらない場合は、それが完了するまでUpdate関数の切り替えを行いません。

そのため、すぐにUpdate関数を切り替える際はこのSleepやYieldの状態を中断する必要があります。

なお、これを逆に利用して次のUpdate関数の変更予約として使う事は問題ありません。

Sleepしている場合はAwakeで起こすことが出来、Yieldを実行している場合はCancelYield関数でYield処理をキャンセルすることが出来ます。

Sleepの場合はUpdate関数を最後まで実行してから停止するため特に問題は発生しませんが、 CancelYieldを使った場合は、関数内のYieldで止まっている箇所で処理を中断し、以降は実行しません。

そのため、Yieldをはさんで何らかの終了処理を行っている場合は、その終了処理を飛ばしてしまいます。

この問題に対する簡単な対象法は存在しないので、アプリケーション側で工夫して使う必要があります。

フレーム指定のYieldを使って、その後の処理をスキップせずにYieldを抜けたい場合は、SleepとYieldの組み合わせで実現可能です。 Sleepで指定時間スリープした後にALYieldを実行し、Awakeをすればすぐにその次の行から動作を再開します。

2. メッセージ

メッセージクラス(ALMessage)とは、アップデータに対し、IDとパラメータを指定してイベントを起こすクラスです。 他のイベントとは違い、イベントが発生するタイミングはそのオブジェクトのアップデートタイミング直前になります。

2.1. 基本

メッセージは31ビットのメッセージIDと、ポインタと同じサイズの数値を2つ送ることが出来ます。 メッセージIDの最上位ビットがたっている場合は、システム内部のメッセージとして使用するので、使用禁止です。 それ以外は、アプリケーションごとに定義して使用します。

  ALUpdater * target = ALUpdater::Create();
  obj->PostMessage( target, 100, 10, NULL );   //メッセージID100,パラメータ10,0のメッセージを送る
      

メッセージは後述のQuickメッセージを使わない限り、対象アップデータのアップデートタイミングの直前で処理されるという特徴があります。 このため、時間がかかりALYieldなどを使用する処理を行いたいが、自分のアップデートをとめたくない場合、 処理を行うタイミングを、送信元アップデータにたよらず揃えたい場合などに有効です。

2.2. 遅延メッセージ

また、すぐにメッセージを送るほか、指定フレーム後に送る遅延メッセージを使用可能です。 自分自身に遅延メッセージを送ることにより、 カウンタを用意しなくても少し後から処理をするということが可能になります。

  obj->PostDelayMessage( obj, 100, 60, 10, NULL ); //メッセージID100,パラメータ10,0のメッセージを自分自身に60フレーム後に送る
      

2.3. クイックメッセージ

Quickメッセージは他のメッセージ処理と違い、送信するとその関数内で相手のメッセージ受信イベントを起こし、 メッセージの処理が完了するとその関数から戻ります。 これのみ、メッセージ処理のタイミングが違うため、使用するときは注意してください。

  ALUpdater * target = ALUpdater::Create();
  obj->PostQuickMessage( target, 100, 10, NULL );  //メッセージID100,パラメータ10,0のメッセージをすぐに送る
  //この時点で、targetのメッセージハンドラの処理は完了している
      

2.4. 受信イベント

メッセージの受信はメッセージ受信イベントを使用します。 このイベントはメッセージを受信した場合、メッセージ一つに対し一回起動されます。 複数のメッセージがある場合は続けて呼ばれることもあります。

他のイベントと違い、Quickメッセージ以外のメッセージは必ずアップデートイベントの直前に呼ばれます。 また、アップデートと同様にALYieldも使用可能ですが、その場合後に続くメッセージやアップデートも一緒に止まりますので注意が必要です。 メッセージ受信イベントは、引数として自分自身のアップデータと、メッセージクラスを渡します。 この中でメッセージIDを調べ、適切な処理を実行してください。

  void message_func( ALUpdater *self, ALMessage * mes )
  {
      switch( mes->GetMessageID() ){
      case ~~
      case ~~
      };
  };
      

メッセージクラスはこのイベント終了時に破棄されるため、このイベント内で解放したりポインタを保持してはいけません。 メッセージからは送信時のメッセージID、2つのパラメータの他、送信元アップデータ、送信時のアップデートフレームを取得することが出来ます。 メッセージは、FindMessageで自分に届いているメッセージの中からIDで検索することも出来ます。 この時、不要なメッセージであればRemoveMessageでそのメッセージを削除することも出来ます。

  ALMessage * mes = obj->FindMessage( 100 );   //メッセージIDが100のメッセージを検索
  if( mes != NULL ){
      obj->RemoveMessage( mes );       //メッセージがあった場合、そのメッセージを削除
  };
      

2.5. オプション

メッセージを送る場合は、オプションを指定することも出来、現在3種のオプションがあります。

  ALMESSAGE_OPTION_OVERWRITE = 1,     //同一メッセージIDを上書きする
  ALMESSAGE_OPTION_PARAM1_DELETE = 2, //Param1をポインタとしてDeleteする
  ALMESSAGE_OPTION_PARAM2_DELETE = 4  //Param2をポインタとしてDeleteする
      

それぞれ、orを使用して同時に使用することが出来ます。

  char * buf = ALNew char[100];
  obj->PostMessage( target, 100, 10, ( size_t )buf, ALMESSAGE_OPTION_PARAM2_DELETE );
  //メッセージID100、パラメータ10、bufのメッセージを送り、完了後にbufを削除する
      

ALMESSAGE_OPTION_OVERWRITEと遅延メッセージを併用することにより、 メニューなどでワンテンポ遅れて画像などを更新する場合などは、項目を変更したときにそのメッセージを送るだけで処理が出来ます。 ALMESSAGE_OPTION_PARAM1_DELETEとALMESSAGE_OPTION_PARAM2_DELETEは単にvoidポインタとしてメモリを解放するので、 デストラクタを設定しているようなオブジェクトでも、デストラクタは呼び出されない点に注意してください。

2.6. スリープ

メッセージは送信先のオブジェクトが単体のSleep状態でも届きます。

その為、メッセージが届くまでSleepしておき、メッセージによってAwakeし、処理が終わるとSleepするという使い方が出来ます。

ただし、そのオブジェクトの所属するシーンがスリープしていたり、ファミリービットによりスリープしていた場合は、メッセージの送信は停止します。

いっせいにオブジェクトをスリープする場合は、処理自体を停止するという意味合いがあるからです。

シーンやファミリービットでメッセージが送信を停止している場合、送信されたメッセージはキューにたまります。

スリープ状態を解除したときにいっせいに届くようになるため、必要であれば送信先のスリープ状態をチェックするか、 OVERWRITEオプションを使用して、メッセージが大量にたまらないようにして下さい。

3. シーン

シーンクラス(ALScene)とは各種アップデータやそのサブクラスを管理するクラスで、アップデートをまとめて止めたり、 所属しているアップデータをまとめて解放したりするクラスです。

アップデータは必ずどれかのシーンクラスのインスタンスに所属し、シーンクラスを破棄する時一緒に破棄します。

3.1. 生成と破棄

シーンを生成するには、Create関数を使用します。引数はありません。

ALScene * sce = ALScene::Create();

なお、今後このように生成したシーンを区別のためユーザーシーンと呼びます。

後述するALTaskを使用すると、内部で自動的にユーザーシーンを作ります。

破棄はRelease関数を使用します。

sce->Release();

シーンを破棄すると、所属しているアップデータも一斉に破棄します。 アップデータに参照カウントが残っていても強制的に削除するので注意してください。

また、破棄対象が後述するデフォルトシーンの場合はアサートになりますので、あらかじめ変更しておく必要があります。

3.2. デフォルトシーン

シーンにはデフォルトという概念があり、新規に生成するアップデータはそのデフォルトシーンに所属します。

アップデータ生成後に所属シーンを再設定することも可能ですが、通常は別シーンを使いたい場合デフォルトシーンを切り替えます。

取得と設定は、GetDefault関数と、SetDefault関数を使用します。

ALScene *sce  = ALScene::GetDefault();
ALScene::SetDefault( sce );

一時的にデフォルトシーンを切り替える場合、シーンスタックに一時的にデフォルトシーンを保存することが出来ます。 シーンスタックはあまり深くは無いので、あくまでも一時的に切り替えるときに使用してください。

スタックへのPushとPopはPushDefault関数と、PopDefault関数を使用します。

ALScene::PushDefault();
ALScene::SetDefault( sce );
~~
ALScene::PopDefault();

なお、シーンをPushする時はたいていその直後にSetDefaultを使うので、PushDefaultとSetDefaultをセットにしたPushSetDefaultという関数もあります。

ALScene::PushSetDefault( sce );    //ALScene::PushDefault();ALScene::SetDefault( sce );と同等

また、SetDefaultやPushDefault実行時に、デフォルトに設定したり、スタックにプッシュしたシーンは参照カウントを増やし、 デフォルトから外れたりポップしたシーンは参照カウントを減らす処理を行います。 そのため、SetDefault実行後、 あらかじめそのシーンに対しReleaseを実行すると、デフォルトシーンを変更した時やスタックから除去した時にそのシーンを自動削除することが出来ます。

ALScene *sce = ALScene::Create();
ALScene::PushSetDefault( sce );
sce->Release();  //スタックとデフォルトに存在するので、ここでは削除されない
~~
ALScene::PopDefault();  //スタックとデフォルトから除去されたため、ここで先ほど生成したシーンが破棄される。

3.3. 生成済みのインスタンス

シーンクラスのインスタンスは、起動時に自動的に2つ作ります。それらはシステムシーンと、メインシーンと呼びます。

システムシーンは内部で使用し、アプリケーション終了まで破棄せず、一切スリープをしないシーンです。 ユーザーが生成したシーン破棄時に、一緒に破棄しては困るアップデータやシステム内部で使用するアップデータを登録します。 スリープしたり破棄するとアサートで停止します。

取得にはGetSystemSceneを使用します。

ALScene *syssce = ALScene::GetSystemScene();

メインシーンは最初から生成しているというだけで、ユーザーが生成したシーンと特に区別はありません。 初期状態で、明示的にシーンを生成しない限りこのシーンを使います。

メインシーンを取得する関数は特にありません。起動時のデフォルトシーンとして設定しています。 必要があれば別のユーザーシーンをデフォルトに設定し、メインシーンを破棄することも可能です。

3.4. ファミリービット

アップデータにはファミリービットという32ビットのフラグがあり、 そのアップデータが所属するシーンで、ファミリービットを使用したスリープや、アップデータの列挙などを行うことが出来ます。

このファミリービットを使用することにより 、例えばメニュー関連とキャラクターで別のファミリービットを割り当て、メニューオープン中はキャラクターの更新を止めるなどの処理が出来ます。

また、エフェクトや敵、弾などに別のファミリービットを割り当て、弾のみ列挙、敵のみ列挙なども行うことが出来ます。

ビット単位で設定出来るため、前述の例なら敵と弾両方に属するような設定も可能です。

アップデータは必ずどこかのファミリーに属する必要があるため、デフォルトでは最上位ビットのみが立っています。 ユーザーが設定する場合は、この最上位ビットは立てても立てなくてもかまいません。用途に応じて設定してください。

シーンでファミリービットを利用したスリープを行う場合、シーンにSetUpdateFamilyBits関数を実行し、アップデートを許可するファミリービットを設定します。 取得にはGetUpdateFamilyBits関数を使用します。

sce->SetUpdateFamilyBits( 0x1 | 0x4 );    //ビット0とビット2のファミリーのみアップデートを有効にする
sce->SetUpdateFamilyBits( sce->GetUpdateFamilyBits() | 0x10 );  //さらにビット4もアップデートを有効にする

なお、デフォルトでは0xffffffffになっており、すべて許可になっています。

アップデータにファミリービットを設定するには、生成後にSetFamilyBits関数を呼び出すか、 シーンクラスのデフォルト値を、SetDefaultFamilyBits関数を用いて変更します。

ALUpdater *obj1 = ALUpdater::Create();
obj1->SetUpdateFamilyBits( 4 );

ALScene::GetDefault()->SetDefaultFamilyBits( 4 );
ALUpdater *obj2 = ALUpdater::Create();

この例では、両方とも生成したアップデータにファミリービット4を設定します。

シーン自体のPush、Popと同様に、デフォルトのファミリービットの値もPush、Popが使用可能です。

sce->PushDefaultFamilyBits();
sce->SetDefaultFamilyBits( sce->GetDefaultFamilyBits() | 0x8 );
sce->PopDefaultFamilyBits();

sce->PushSetDefaultFamilyBits( sce->GetDefaultFamilyBits() | 0x8 );
sce->PopDefaultFamilyBits();

この例も、両方ともまったく同じ動作を行います。

3.5. 描画ノードの列挙

シーンクラスには、所属している描画ノードをファミリービットごとに列挙する機能があります。

その際、単一のファミリーだけではなく、複数のファミリーのorをとったリストを取得することが出来ます。 and条件を取ることは出来ないので、必要であればand条件用の別のファミリービットを定義して使用してください。

取得したリストはノード配列クラスで、今後該当するオブジェクトの追加、破棄がある度に自動更新されます。

登録するノードは、単独のノードか、グループマスタのノードのみです。 そのため、全ノードが必要であればその列挙した各ノードのグループの子も調べる必要があります。

生成にはCreateFamilyNodeArray関数を使用します。破棄は、生成したノード配列に対し、Release関数を実行します。

ALNodeArray *nar = sce->CreateFamilyNodeArray( 0x1 | 0x4 );
~~

nar->Release();

ノード配列の生成時にシーンに所属している描画ノードを全サーチし、該当ノードのみを列挙します。 複数のファミリー指定があっても重複して登録することはありません。

一度生成した後はノード配列の内容も更新するため、ノード配列の全ノードに対して処理を行うような場合は、ALYield()をはさんだり、 ノード数の増減が起こらないように使用してください。

なお、ノード配列を生成した後に追加したノードは必ず最後に追加し、ノードの削除があっても順番は維持します。

第2章 描画関連

1. 描画ノード

描画ノード(ALNode)とは、アップデータを継承し、表示に必要な機能を追加したクラスです。 ALNode自体は何も描画せず、実際の描画はこのALNodeを継承したクラスが行います。

提供する機能は、座標・姿勢管理、座標の親子関係、マテリアル関連、モーション関連、グループ処理、 アセンブル処理、コントローラ処理、コリジョン処理、および描画ノード用のイベント処理です。 これらのほとんどの機能はそれぞれ独立したクラスが処理しますが、 特殊なことを行わない限り、あまりそのことを意識する必要はありません。

1.1. 生成と破棄

生成にはCreateか、後述するアセンブル処理を使用します。

ALNode * node = ALNode::Create();

ただし、大半はALNode自体を直接使用せず、継承したクラスを生成して使用します。 破棄はアップデータと同じくDestroyを使用します。 Destroy周りの特性は全てアップデータと同一です。

1.2. 座標管理

描画ノードはそれぞれ、位置、回転、およびスケールを持ちます。 描画を行う場合、これに併せて中心位置、親子関係を考慮して描画します。 内部ではこの描画ノードが2D系か3D系かを区別し、それに応じて処理します。 位置はXYZの3軸の座標をALVector型で保持します。 設定、取得する場合はALVectorによる一括か、XYZの値を個別に取得設定することも出来ます。

	node->SetX( 1.2f );
	node->SetY( 2.3f );
	node->SetZ( 3.4f );
	ALNumber x = node->GetX();
	ALNumber y = node->GetY();
	ALNumber z = node->GetZ();
	node->SetPos( ALVector( 1.2f, 2.3f, 3.4f ) );
	node->SetPos( 1.2f, 2.3f, 3.4f );
	node->SetPos( 1.2f, 2.3f );
	ALVector v1;
	node->GetPos( &v1 );
	const ALVector &v2 = node->GetPos();

2D系の場合でもZ値の取得や設定は有効ですが描画時には無視します。 回転は、2D系の場合は単一のALAngle型の回転角を、3D系の場合オイラー角、クォータニオン、およびマトリクス指定の3種があります。

	node->SetRot( ALANGLE_45 );
	ALAngle rot = node->GetRot();

	node->SetEuler( ALANGLE_90, ALANGLE_15, ALANGLE_0 );
	node->SetEuler( ALAngleVector( ALANGLE_90, ALANGLE_15, ALANGLE_0 ) );
	const ALAngleVector & euler = node->GetEuler();

	node->SetQuat( ALQuat( 0, 0, 0, 1 ) );
	const ALQuat & quat1 = node->GetQuat();
	node->GetQuat( &quat2 );

	node->SetLocalMatrix( mat1 );
	const ALMatrix & mat2 = node->GetLocalMatrix();
	node->GetLocalMatrix( &mat4 );

座標の保持クラスは、内部では描画ノードとは独立しており、2D系、3D系の切り替え、 親子関係の設定、回転方式の設定などにより適切なクラスが選ばれ、切り替えて使用されます。

通常意識する必要はありませんが、回転方式は設定した方式に合わせて切り替えるため、 同一のノードに対し、オイラー角とクォータニオンなどを混在して使用すると負荷がかかります。

この座標の保持クラスの切り替えは値の設定時のみ行うため、 回転の取得に関しては方式が違っていても座標保持クラスの変換は行いません。 設定した回転と形式が違っていても、適切に変換を行い値を取得することが出来ます。 スケールは2D系ならXYのALVector2が、3D系ならALVector型を使用します。

1.3. 表示コントロール

描画ノードは生成してもそのままでは表示しません。 表示するためには、Showを実行するか、SetDispでTrueを設定する必要があります。 表示を消すためには、Hideを実行するか、SetDispでFalseを設定します。 それぞれShowはSetDisp( true )とまったく同じもので、HideはSetDisp( false )とまったく同じものになります。

	node->Show();
	node->SetDisp( false );
	node->Hide();

描画ノードではこのSetDispでの表示コントロールの他、3つの表示コントロール方法があります。 アップデータの更新設定と同じく、全ての表示フラグがオンになると実際に表示を行います。

生成時では、SetDispの設定以外はすべて表示設定になっています。 2つ目の表示コントロール方法は、アップデータで使用しているSetEnableです。 SetEnableでFalseを設定すると、表示がオフになると同時に更新も停止します。

後2つは表示の管理クラスであるスクリーンクラス(ALScreen)が行います。 SetDispとSetEnableの設定は、後述する親子関係により親の設定を引き継ぐことも可能です。

1.4. 親子関係

座標計算を行うときは、親子関係を付けることが可能です。 設定した親子関係は座標計算のほか、描画状態を親から継承することが出来ます。 親子関係の設定にはSetParent、取得にはGetParentを使用します。

	node->SetParent( parentnode );
	ALNode * p = node->GetParent();

描画ノードの親子関係は親を取得するほか、子を取得することも可能です。 ただし、親と違い子は複数存在出来るため、全ての子を取得するためにはまず代表となる子を取得して、次にその兄弟を取得するという手順を取ります。

	ALNode *child = node->GetChild();
	while( child != NULL ){
		~~
		child = child->GetSibling();
	};

親子関係と表示コントロールの連動を設定する場合には、SetParentDispLinkとSetParentEnableLinkを使用します。 Trueに設定すると、親の設定が子に引き継がれます。

この時、親に設定した表示コントロールの内容を子に上書きするわけではなく、子自身の状態と親の状態両方を参照して、両方とも表示可能の場合に表示します。 デフォルトではどちらもTrueです。 なお、その状態の取得にはIsParentDispLinkとIsParentEnableLinkを使用します。

	node->SetParentDispLink( true );
	node->SetParentEnableLink( false );
	bool dl = node->IsParentDispLink();
	bool el = node->IsParentEnableLink();

親子設定があった場合、親子関係を考慮した座標計算はアップデート処理の後、描画の前のタイミングでまとめて行います。 その際、値を設定していたとしても、使用していない描画ノードの座標計算は行いません。

親子関係を計算したワールドマトリクスやワールド座標を取得するには、 CalcWorldMatrix、GetWorldMatrix、CalcWorldPos、GetWorldPosの関数の何れかを使用します。

ワールドマトリクスや座標を取得するときには、このようにCalc系とGet系があります。 Calc系はその時点で階層を親までたどり、全て計算をして値を求めます。 Get系は前フレームで計算済みの値を取得します。ただし、未計算の場合はCalcと同様にその場で計算を行います。

Calc系は負荷はかかりますが正確な値を取得したい場合に使用します。 1フレーム前の値で構わなければ、Get系の方が負荷は軽くなります。

1.5. グループ

描画ノードはグループ機能を使い、擬似的に複数のノードを1つとして扱うことが出来ます。 グループは必ず管理する描画ノードがあり、それをグループマスタと呼びます。 グループマスタを削除するとグループにおけるメンバのノードも削除します。 また、表示関連以外のパラメータ設定はグループ全体に設定します。 グループマスタを生成するためには、ALGroupNodeのCreate関数を使用します。

	ALGroupNode *gnode = ALGroupNode::Create();

その描画ノードがグループかどうかはIsGroup関数で調べることが出来ます。

	bool b = gnode->IsGroup();

グループノードに描画ノードを追加するには、AddNode関数を使用します。

	gnode->AddNode( node );

ただし、描画ノードは参照カウント制御を行うため、AddNode時に参照カウントが増えます。 そのままではグループマスタを削除しても参照カウントが残り、描画ノードは削除されません。 従って、通常はAddNodeではなく、AddReleaseNodeを使用します。

	gnode->AddReleaseNode( node );

これは、AddNodeを実行した後、その描画ノードにReleaseを実行した場合と同じになります。 これにより、グループマスタ削除時に登録した描画ノードも自動的に削除します。

グループマスタには動作を変更するフラグが3つあります。 1つ目は、GroupAutoRemoveフラグです。SetGroupAutoRemove、IsGroupAutoRemoveで設定を取得します。 このフラグを設定することにより、グループに追加したノードのDestoryが発生した場合、グループから自動でそのノードをはずします。 これは、Destroy実行時に行います。 デフォルトではFalseです。

	gnode->SetGroupAutoRemove( true );
	bool fl = gnode->IsGroupAutoRemove();

2つ目は、GroupSuicideフラグです。SetGroupSuicide、IsGroupSuicideで設定を取得します。 このフラグを設定すると、GroupAutoRemoveと同じ動作をします。 さらにその結果、グループにおけるメンバの描画ノードがなくなると、自分自身をDestroyします。

このフラグを設定している間は、GroupAutoRemoveを設定した動作を含むため、 GroupAutoRemoveフラグを設定しても動作は変化しません。 デフォルトではFalseです。

	gnode->SetGroupSuicide( true );
	bool fl = gnode->IsGroupSuicide();

3つ目は、GroupOwnerフラグです。SetGroupOwner、IsGroupOwnerで設定を取得します。 このフラグが立っていると、グループマスタを削除するときに、メンバの描画ノードをDestroyし、 立っていない場合は、グループマスタ削除時にはメンバの描画ノードをReleaseします。

どちらの場合も、グループのメンバが共有していないため、AddReleaseNodeなどで参照カウントの残りが1の場合は動作は変わりません。 グループのメンバが共有していて、Destroyすると問題が出る場合のみこのフラグをFalseに設定します。 デフォルトではTrueです。

	gnode->SetGroupOwner( true );
	bool fl = gnode->IsGroupOwner();

2. スクリーン

スクリーンはAqualeadにおける、全ての描画の対象となるクラスで、 デフォルトでは1つだけ生成され、それが実際の画面と対応します。 カメラはスクリーンに対し設定するため、カメラを複数使用する場合は、特定ノードだけ描画エリアを制限して描画したい場合や、 特定ノードを描画後にZバッファをクリアしたい場合などに、新たにスクリーンを生成(追加)して使用します。

2.1. 基本的な機能

スクリーンはシステム初期化時に1つ生成(デフォルトスクリーン)し、それはGetDefault関数で取得出来ます。 各種描画ノードは、生成時にこのデフォルトスクリーンに所属するように生成します。 なお、生成後に変更することも可能です。

SetDefault関数でデフォルトを変更することが出来ます。 一時的なデフォルトスクリーン変更用にスクリーンスタックが存在します。 これはあくまでも一時的にデフォルトを変更するためのもので、 長時間変更する場合はスタックを使用せずに、自前でスクリーンインスタンスのポインタを保持することをお勧めします。

PushDefault、PopDefaultの他、Push後にSetをするPushSetDefault関数があります。 スクリーンはスクリーン単位で表示のOnOffを行ったり、ファミリービットを使用し、特定のノードのみを非表示にすることなどが出来ます。

2.2. プライオリティ

スクリーンにはプライオリティがあり、プライオリティ順に描画します。 描画はスクリーン単位で行うため、一つのスクリーンにおける内部の描画ノードを全て描画した後、 次のスクリーンの描画ノードを全て描画するという手順で描画します。

スクリーンの描画ノードにおけるプライオリティの間に別スクリーンの描画を行いたい場合は、 スクリーンに親子関係をつけ、別スクリーンの描画に割り込ませることが出来ます。 その時は、スクリーン生成時に親となるスクリーンを指定して生成します。

そうすると、通常スクリーンのプライオリティはスクリーン同士の優先順位を決めるのに使いますが、 親を決めるとその親となるスクリーン内部のプライオリティとなり、他の描画ノードのプライオリティと同じように扱います。

2.3. その他の機能

スクリーンは描画時に矩形範囲を指定し、その範囲内でのみ描画するように指定することが出来ます。 SetViewRectで指定します。

そのほか、描画開始時にフレームバッファをクリアするか、その時の色はどうするか、Zバッファをクリアするかの指定が出来ます。 最初に生成されたスクリーンはフレームバッファ、Zバッファ共にクリアする設定にしますが、 その後に生成するスクリーンはどちらもクリアしない設定です。

3. カメラ

3.1. 基本的な使い方

カメラは引数なしのCreate関数で生成します。 生成したカメラはスクリーンに設定して使用します。 1スクリーン内で複数カメラを使用することは出来ないので、必要であれば複数のスクリーンを使用します。

カメラの向きは回転角度を使う方法と、ターゲットを利用する方法の2種類があります。 ターゲットがない場合デフォルトではZマイナス方向を向いているので、ノードの回転を使用してカメラの向きを変更します。 ターゲットがある場合は、SetTargetでターゲットとなるノードを指定します。

また、透視射影と平行射影の両方が使用出来、透視射影ではSetPerspective関数を、平行射影ではSetOrtho関数を使用します。 透視射影の場合は視野角を使用するSetPerspectiveFovと、縦横サイズを指定するSetPerspectiveの2種類があります。

4. パーティクル

パーティクルは、指定したテクスチャをビルボードとして大量に表示するものです。 2D系、3D系両方で使うことが出来ます。 パーティクルのエミッタは独立クラスになっているため、独自形状のエミッタを生成することも可能です。 同様に外部からの影響を与えるアフェクタも同様に独自クラスになっているため、追加して使用することが可能です。

4.1. 基本的な使い方

パーティクルは設定項目が多いため、プログラムで設定をするよりあらかじめデータを作ってアセンブルすることをお勧めしますが、 ここでは直接生成する場合を説明します。 まずはそのままCreateで生成します。 生成時に各種パラメータはある程度の値はあらかじめ設定しているので、必要なものを設定してください。

次に、Startを実行することによりパーティクルの生成を開始します。 その際、SetLengthで長さが指定してあれば、その時間経過後に生成が停止します。 Lengthが0の場合は自動的には停止しないので、Stopで停止する必要があります。

生成が停止しても、放出した粒子は動き続けます。全ての粒子が消えたかどうかを判断するにはIsComplete関数を使います。 これがTrueになると、放出が停止して、粒子も全部消滅したことになります。 SetAutoDestroyをtrueにしておくと、このタイミングで自動的にDestroyされます。

5. プリミティブ

プリミティブ(ALPrim)とは、描画ノードの一種で主に2Dで自由な形状を描画するときに使用します。 3Dで使用する場合は、3Dプリミティブ(ALPrim3D)を主に使います。 ALPrimでは点、ライン、矩形、および三角形が使用出来、それぞれ頂点ごとにUV座標や色を設定出来ます。

5.1. 基本的な使い方

まず、Create関数によりインスタンスを生成します。 プリミティブに各種形状を追加するには、まずBeginAdd関数を呼び出し、追加可能な状態にします。 その後、AddLineやAddFillRectなどの関数を必要なだけ追加します。 個数や種別混在などの制限はありません。

形状を追加する際、座標は必須ですが色、UV座標はそれぞれ白、(0,0)がデフォルト値となります。 登録がおわったらEndAddを実行し、登録作業を完了させます。 完了後、BeginAddを再び追加し、さらに追加することも可能です。

クリアするためにはClear関数を呼びます。 テクスチャを使用する場合は、形状を登録する前にSetTextureやLoadTextureなどでテクスチャを設定する必要があります。 形状追加後に新規にテクスチャを設定することは出来ません。 ただし、すでに設定済みのテクスチャを切り替えることは可能です。 また、テクスチャは1枚のみ使用可能です。2枚以上使用したい場合は複数のALPrimを生成する必要があります。

6. 3Dプリミティブ

3Dプリミティブはライン、三角形の組み合わせで3Dの形状を定義して表示するものです。 比較的簡易的な処理になっているため、複数マテリアルや、ボーンなどは使用出来ません。

6.1. 基本的な使い方

3Dプリミティブは、2Dのプリミティブと違い使用する種別(ラインリスト、トライアングルストリップなど)を指定した後は、 頂点をひとつずつ登録して形状を定義します。 各頂点には、座標、法線、UV座標、および色の指定が出来ます。UV座標のみ4つまで指定が可能です。

6.2. 頂点の書き換え

3Dプリミティブは2Dのプリミティブと違い、一度設定した頂点の一部を書き換えることが出来ます。 書き換えは座標や法線など種別ごとに行い、どこからどこの範囲の頂点を書き換えるか指定し、 そのデータをロックしてポインタを取得します。 書き換え後にアンロックをすることでデータが書き換わります。

6.3. 簡易形状設定

3Dプリミティブは、単純な形状を設定するユーティリティ関数があります。 現在、XYZの3軸をあらわすライン、ラインによる球、ラインによる立方体、ポリゴンによる立方体、平面のグリッドの設定が出来ます。

7. テキスト

テキストとは後述するフォントと併用し、文字列を描画するクラスです。 単純な文字列のほか、printf形式の形式文字列が使用可能です。 文字幅や改行幅の指定が可能なほか、アイコンを埋め込んだり、縁取り、影付けなどが使えます。

7.1. 基本的な使い方

テキストは必ずフォントを使用します。 生成時にフォントを指定することも出来ますが、フォントにはデフォルトフォントという概念があるため、 フォント指定を省略した場合はデフォルトフォントが使われます。

その後は、PutStringで文字列を設定したり、Printf関数で形式文字列を指定したりします。 Printfで\nを指定したり、AddReturn関数で改行して複数行の描画も出来ます。 どこに描画するかというカーソル位置があり、それはLocate関数で自由に指定が可能です。 この位置はテキストクラス自体からのオフセットとなります。

7.2. アイコン

テキストにはアイコンを埋め込むことが可能です。 アイコンはノードであれば何でも良いのですが、一般的にはスプライトを使用します。 使用するには文字コード+アイコンに使用するノードを指定します。 このノードはクローンして使用するため、複数箇所でも問題なく使用が可能です。 モーションを定義しておけば、アニメーションする文字なども定義出来ます。

7.3. 色

テキストはテキスト全体の色と、文字単位の色が設定出来ます。 テキスト全体の色はノード自体が持っている色指定を使い、文字単位の色と掛け算して使用します。

文字単位の色は単一色の他、上の色、下の色を指定して上下のグラデーションとして使用出来ます。 テキスト全体の色は文字表示後の変更が可能ですが、文字の色は文字を追加するときに設定し、その後は変更不可です。 文字単位の色を変更したい場合は、一度文字をクリアしてから再度追加します。 また、テキスト全体に影と縁取りの指定が出来ます。 これらは描画時にオフセットをずらして重ねがきで実現しています。 それぞれ、色と、どれだけずらすかを指定します。

第3章 モーション

1. モーション基本

モーションは一般的にはモデルやスプライトのアニメーションを行うシステムですが、 Aqualeadではプロパティで扱える値は全てモーションの対象となるため、応用範囲は非常に広くなっています。

プロパティ単位で補間種別の設定が出来ます。現在は補間なし、線形、およびベジエの3種が使用出来ます。 モーションはノードに対して処理を行いますが、ノードがグループに属している場合そのグループ全体を一括してモーションすることが出来ます。

モーションはツールによって生成した.amtというモーションファイルをロードすることにより使用します。 ただし、後述するダイナミックモーションを使うことにより、プログラム中でモーションを生成することも出来ます。

モーションデータは、その中に複数のモーションを含めることが出来ます。その場合はロードせずに切り替えて使用出来ます。 モーションは通常ノード1つに対して1つを使用しますが、複数のモーションとブレンド率を設定することによりモーションのブレンディングを行うことが出来ます。

モーションを別のモーションに切り替える際は、 そのまま切り替える他に今の最終値と次のモーションを線形補完するモーション補完、ブレンド率を変更しつつ切り替えるブレンド補間が使用出来ます。 モーションにはマーカという印をつけることが出来、モーションの一部をループしたり、フレーム値を直指定せずに特定フレームにジャンプしたり出来ます。

1.1. 基本的な使い方

モーションはノードに対し、LoadMotionかChangeMotionを実行することにより設定します。 ChangeMotionはロード済みモーションの中で切り替えるもののため、一度LoadMotionで複数モーションを含んだamtをロードした後しか使用出来ません。

対象のノードがグループノードの場合、グループに属するノードに一括でモーションを設定します。 このとき、ノードとモーションの結びつけは、ノードのIDを使用して行います。 その為、ノードのIDを適切に設定していなければ、モーションをロードしません。

これを利用し、特定ノードのみモーションをロードしたくない場合、一時的にノードIDを変更するとそのノードだけスキップします。 なお、例外として単独ノードの場合はノードIDの設定関係無しにモーションを設定します。

グループに対しモーションをロードした場合、モーションの再生停止、速度変更など、モーションに対する操作は全てグループ単位で行い、 グループ内のノードのみを独立して制御することは出来ません。

1.2. モーションに対する操作

ノードに設定したモーションは、ノードのPlayMotionとStopMotion関数により再生と停止が可能です。 これらの操作は、ノードからモーションのインスタンスを取得し、PlayやStopを実行する場合と同等です。

以降、LoadMotionやChangeMotionなどのモーション切り替え関数を除き、ノードの~~Motion~~というMotionという名前を含む関数は、 ノードからGetMotionによりALMotionのインスタンスを取得し、そのインスタンスに対し、ノードのMotionという部分を抜いた関数を実行する場合と同等です。 特に必要がなければ、ノードに存在する~~Motion~~という関数をそのまま使用してください。

モーションの長さはGetMotionLengthで、現在のフレームを取得する場合はGetMotionFrameを使用します。 モーションのフレームは固定小数で管理します。整数や実数との変換は自動で行うため、通常は意識する必要はありません。

フレームの設定はJumpMotionFrameとSeekMotionFrameの2つがあります。 これは後述するノートトラックなどのイベントを使用するときに差が出ます。

JumpMotionFrameでは、一気に指定したフレームに移動しますが、 SeekMotionFrameではその間にイベントを起こすデータがあった場合、それらのイベントを全て発生させます。 イベントを特に使用していない場合は、JumpMotionFrameを使用してください。

モーションはそれぞれループするかしないかの設定があり、IsLoopMotionで取得、SetLoopMotionで設定が可能です。 ループありなしでは最終フレームの扱いに違いがある点に注意してください。

例えば、モーションが0~10フレームある場合、ループありでは0~9を繰り返し10フレームには来ません。 ループがない場合は、0~10まで再生し10フレームを再生した時点で停止します。 モーションは再生速度の変更も可能です。 SetMotionSpeedで設定、GetMotionSpeedで取得です。 通常は1で、負の設定をする事により逆再生も可能です。

1.3. モーションの更新タイミング

通常は意識する必要はありませんが、モーションの更新タイミングを説明します。 通常のアップデートの後、全ノードのモーションが描画の前に適用されます。

処理の順番については、まずモーションごとに現フレームの値を各プロパティに設定します。その後、フレームが進みます。 その為アップデート処理で、モーションで設定するはずのプロパティの値を取得する場合、1フレームずれている点に注意してください。 これらはLoadMotionやChangeMotion後も同様で、この時点では各プロパティに値は設定しません。

ただし、ApplyMotionを実行することにより、その時点での値をプロパティに設定します。 この後、本来のモーション更新タイミングでは値の適用は行われず、フレームの更新のみを行います。 また、モーションの適用はフレームが更新したときのみ行うため、モーションが停止している場合は値の適用は行いません。

1.4. マーカ

マーカとはモーション内部に埋め込んだ目印で、プログラムからモーションのフレーム制御を行う場合に、フレームの値を直使用する必要がなくなります。 また、特殊なループ用のマーカがあり、これを利用するとモーションにおける一定のフレーム間をループすることが出来ます。

マーカにはIndexとIDという2種類の値を使用します。 IDは16ビットの数値で、マーカを埋めるときに使用します。1~0x7fffの範囲の値が使用可能です。 0はマーカがないという時のIDで、0x8000~以降はシステムが使用する特殊IDです。 Indexは0フレームから順に並べたときのインデックスで、最初のマーカが1、次が2となります。Index0は実際のマーカではなく、常に0フレームを指します。

現在のマーカIDやIndexを取得するには、GetCurrentMotionMarkerIDやGetCurrentMotionMarkerIndexで取得出来ます。 なお、取得するときは今のフレームのマーカか直前のマーカの値を取得します。 例えば、5フレームにマーカID10、10フレームにマーカID11があった場合、6フレームでのGetCurrentMotionMarkerIDは10となります。

指定したマーカのフレームに移動するには、JumpMotionMarkerID、SeekMotionMarkerID、JumpMotionMarkerIndex、SeekMotionMarkerIndexを使用します。 JumpとSeekの違いは、前述のJumpMotionFrameとSeekMotionFrameの違いと同一です。

マーカには指定した区画をループするループマーカがあります。 ループマーカは16種類あり、それぞれループスタートと、ループエンドがあります。 ループエンドマーカにフレームが移動した場合、そのフレームを再生せずに対応するループスタートマーカにフレームが移動します。

このループから脱出するには2種類の方法があります。 1つは、ループエンドマーカにすぐ移動する方法、もう1つは次のループエンドでループせずにそのまま進む方法です。 すぐ抜けるにはSkipMotionLoopを、次のループエンドでループせずに抜ける場合はPassMotionLoopを使用します。

1.5. モーション補間

モーション補間は、主に3Dモデルでモーションとモーションの切り替わりをスムーズにするものです。 このモーション補間はコントローラとして実装しており、現在線形補間を使用したものと、モーションブレンドを使用したものがあります。 デフォルトでは線形補間式を使います。

線形補間式は処理が軽いですが、補間完了までは次のモーションが開始しないため、すぐに切り替わらなければならないモーションには使えません。 ブレンド補間はすぐに次のモーションを開始するため遅延はありませんが、一時的に2つのモーションをブレンドするため負荷が高くなります。 状況に応じて使い分けてください。

使用するには、LoadMotionIP、もしくはChangeMotionIPを使用します。 通常のLoadMotionやChangeMotionのように名前かIDを指定し、その後に補間に要する時間を指定します。 この使い方ではデフォルトの補間を使いますが、補間時間の代わりに補間に使用する別のコントローラを指定することも出来ます。

デフォルトの補間は変更することも出来ます。 SetDefaultMotionIPControllerで使用したいコントローラを指定します。 現在用意しているのは、ALNormalMotionIPControllerとALBlendMotionIPControllerの2種類です。 なお、モーション補間は1つのモーションに対し同時に1つしか実行出来ないため、モーション補間中にモーション補間を行うと直前の補間をキャンセルします。

1.6. ブレンド

モーションブレンドは複数のモーションを同時に再生して、モーションを合成するものです。 歩きと走りをブレンドし中間状態などを作ることが出来ます。 今まではノードに対するモーションは1つという前提で説明をしてきましたが、必要であれば複数のモーションを1つのノードに割り当てることが出来ます。 上限はありません。

その際、ブレンド率を設定することが出来、ブレンド率に基づいてモーションを合成します。 通常ブレンド率は合計1になるように設定しますが、 例えば2つのモーションで別々のプロパティに対してモーションを設定する場合などは、どちらもブレンド率を1にします。

モーションブレンドを使用する場合は、各種モーション設定関数にブレンドインデックスを指定するか、     GetMotion( int bindex )でブレンドインデックスを指定し、モーションのインスタンスを取得し、そこに対し処理を行います。 特に指定がない場合のモーション操作は、ブレンドインデックスに0を指定したものとして扱います。

ブレンドを行う場合は1以降のブレンドインデックスを使用しますが、基本的には連番で使用します。 間を空けることも出来ますが、内部的にはその間にもダミーのモーションが生成されるため若干非効率になります。 基本的にブレンドインデックスが違えば完全に独立したモーションとなるので、各種モーションへの操作が干渉することはありません。 独立したタイミングでモーションを切り替えたり、再生、停止することも可能です。

ブレンド率の設定はモーションのインスタンスを取得後、SetBlendWeightで指定します。 ブレンドの使用が終わって、モーションをクリアする場合はそのブレンドインデックスを指定しClearMotionを実行します。

1.7. ノートトラック

ノートトラックとは、モーション専用のユーザーデータ格納領域です。 16ビットのID+可変データをフレームごとに複数個持たせることが出来ます。 このノートトラックは現在のフレームのノートトラックを取得するほか、 ノートトラックが発生したときにイベントを発生させることが出来ます。 イベントが発生するのはモーション適用タイミングです。

ノートトラックを取得する場合は、まずGetCurrentMotionNoteCount関数で今のフレームにノートトラックがあるか調べます。 1以上であればノートトラックがあり、その場合個数分ノートトラックがあるので、 GetCurrentMotionNoteIDでIDを、GetCurrentMotionNoteDataでそのデータを取得します。

モーション側で特にアライメント指定をしない限り、アライメントは16ビット単位になるので、 32ビットデータを扱うときは注意してください。 ALRead32Align16関数や、ALReadFloatAlign16関数を使うと16ビットアライメントで32ビットデータを読み込みます。

イベントを使用する場合は、SetNoteTrackFunc関数でコールバック関数を指定します。 その場合、コールバック関数は第1引数に対象となるノードを渡し、第2引数にIDとデータのアドレスを渡します。 先頭2バイトがIDで、以降がデータとなります。 1フレームに複数のノートトラックデータがあったり、SeekMotionFrameでフレーム移動した場合などは複数回のイベントが連続して発生します。

2. 動的モーション

モーションは前述のファイルからロードする固定モーションの他、プログラムで生成する動的モーションがあります。 動的モーションはメモリ、速度共に効率は悪いですが、生成が完了した後フリーズする事で通常のモーションと同じ形に変換が出来、    その状態なら通常のモーションと効率は変わりません。 また、生成したモーションをamtとしてセーブすることも可能です。

2.1. 基本的な使い方

まずは、ノードに対しBeginDynamicMotion関数を実行します。 実行したノードがグループマスタの場合は、グループメンバ全てに影響を与えます。 すでにモーションをロードしていた場合は、今あるモーションを動的モーションに変換します。

モーションをどの項目に設定するかは全てプロパティで指定します。 キーを追加するにはAddMotionKey関数を使用します。プロパティと、フレームを指定します。 値は現在ノードに設定してある値を使います。フレームを省略すると現在のフレームを使用します。 同様にキーを削除するにはRemoveMotionKeyを使用します。

一度設定したキーの値を変更するにはUpdateMotionKeyを使用してください。 指定したキーに補間の設定をするには、SetMotionIPTypeを使用して、補間の種別を指定します。これにはフレームの指定はありません。 ベジエ補間など、補間時に追加パラメータが必要なものはキーを設定後、続けてSetMotionIPDataで追加データを設定します。

2.2. 複数モーションの登録

通常のモーションと同様、動的モーション使用時も複数のモーションを設定し切り替えて使うことが出来ます。 その場合、まずBeginDynamicMotionで1つ目のモーションを設定します。

その後、そのモーションにSetMotionIDかSetMotionNameでIDか名前をつけます。 次にNewDynamicMotion関数を実行してから次のモーションを設定します。 その後もIDやNameを付け、さらに必要ならまたNewDynamicMotionを実行します。

最後のモーションに関しては、IDやNameをつけ終わった後は特に終了処理の様なものは必要ありません。 その後ではChangeMotionでこれらのモーションを切り替えて使用出来ます。

2.3. モーションの変換

生成したモーションをフリーズする事で、通常のモーションと同じパフォーマンスで使用することが可能になります。 FreezeDynamicMotion関数を呼び出すことでフリーズを行います。 以降は通常のモーションとまったく同じように使用出来ます。 再び編集する必要があれば、もう一度BeginDynamicMotionを呼び出して動的モーションに変換します。

また、動的モーション状態であればSaveMotion関数によりamtとしてファイルに保存することも出来ます。 モーションファイルをこのように生成することも可能です。

3. アセンブル処理

アセンブル処理とはファイルからノード定義ファイル(.aod)を読み取り、各種ノードの生成やプロパティ設定を行う処理です。 複数ノードで構成することが多い3Dモデルなどは、主にアセンブル処理を使って生成します。 アセンブル時に出来ることは、各種ノードの生成、プロパティの設定の他、コリジョンやコントローラの生成です。

3.1. 基本的な使い方

アセンブル処理の使い方は、単に生成した.aodファイルをAssemble関数に指定するだけです。 これによりノードを生成します。

生成したノードが1つならそのノードそのものが返りますが、複数ノードがある場合はグループノードを生成し、そのグループノードが返ります。 生成したノードは全て子のグループに所属し、また親設定がないものはこのグループマスタが親になります。

破棄時はこのグループマスタをDestroyするとグループのメンバも全て削除します。 そのため、ノード単位に処理をする必要がなければ、単一ノードが生成されたか、グループノードが生成されたかは特に意識する必要はありません。

また、Assemble関数では完全に新規のノードを生成しますが、グループマスタノードが自分のメンバとしてノードを生成するAssembleMemberという関数もあります。 これはグループノードに対し実行する関数で、そのグループノードのメンバとしてノードを生成します。 必ずグループノードつきで生成した場合などは、予めグループノードを生成しておき、AssembleMemberを使うという方法があります。 なお、AssembleMemberではノードの追加は行いますが、削除は行いません。すでに同一IDのノードがメンバに存在するならば、そのノードを使用します。

第4章 コントローラ

1. 共通

1.1. 概要

コントローラとは、コントローラクラス(ALController)を継承したクラスの総称で、描画ノードに動的に機能を追加するものです。

なお、Aqualeadでは一般的なゲーム入力デバイスはパッドと呼び、このコントローラとは関係が無いので注意してください。

1.2. 基本的な使い方

コントローラは、Create関数で生成し、描画ノードにAddController関数で登録します。解放は描画ノードが行うので、必要の無い限り解放する必要はありません。

ALNode *node = ALNode::Create();
node->AddController( ALAngleSpeedController::Create( 0, 1 ) );

この例で使用しているのは2D系で、角度と速度を指定して座標を更新するコントローラです。

この後、毎フレーム座標を自動的に更新します。

1.3. スリープ

コントローラは特殊なものを除き、描画ノードにAddControllerした時点で動作を開始します。 必要があれば、コントローラに対しSleepを実行することでコントローラの動作を一時停止させることが出来ます。

再開するにはAwakeを、スリープしているかはIsSleep関数でチェック出来ます。

ALAngleSpeedController *asc = ALAngleSpeedController::Create();
node->AddController( asc );
asc->Sleep();
~~
if( asc->IsSleep() ){
    asc->Awake();
};

なお、コントローラは所属する描画ノードがスリープしている場合は動作しません。 コントローラ自体のスリープフラグは独立しています。 コントローラは、所属している描画ノードがスリープしていない状態で、コントローラ自身もスリープしていない状態の時のみ動作します。

1.4. プライオリティ

コントローラにはアップデータと同様のプライオリティ設定が出来ます。 これも範囲はALPRIO_MIN=1~ALPRIO_MAX=254で、8ビット精度小数も利用出来ます。

設定するにはSetPrio関数、取得にはGetPrio関数を使用します。

ALController *cont = ALAngleSpeedController::Create( 0, 1 );

cont->SetPrio( 10 );

cont->SetPrio( cont->GetPrio()+1 );

ただし、コントローラの場合実行するタイミングが大きく4つあり、プライオリティの範囲によりその4つに振り分けます。

更新する順番に列挙すると、

・アップデータの更新前

 通常のアップデータの更新を行う前に実行します。

 範囲はALPRIO_MIN=1 以上でALCONTROLLER_PRIO_UPDATE_PRE_MIN=64未満です。

 その他、以下のシンボルを定義しています。

ALCONTROLLER_PRIO_PRE_HIGH = 16;
ALCONTROLLER_PRIO_PRE_MID = 32;
ALCONTROLLER_PRIO_PRE_LOW = 48; 

・所属するノードのアップデート直前

 所属するノードのアップデートの直前に実行します。 アップデート処理の一部のため、ALYieldを使っている場合、このプライオリティのコントローラの動作も止まる点に注意してください。

 範囲はALCONTROLLER_PRIO_UPDATE_PRE_MIN=64 以上でALCONTROLLER_PRIO_UPDATE_POST_MIN=128未満です。

 その他、以下のシンボルを定義しています。

ALCONTROLLER_PRIO_UPDATE_PRE_HIGH = 80;
ALCONTROLLER_PRIO_UPDATE_PRE_MID = 96;
ALCONTROLLER_PRIO_UPDATE_PRE_LOW = 112; 

・所属するノードのアップデート直後

 所属するノードのアップデートの直後に実行します。 アップデート処理の一部のため、ALYieldを使っている場合、このプライオリティにおけるコントローラの動作も止まる点に注意してください。

 範囲はALCONTROLLER_PRIO_UPDATE_POST_MIN=128 以上でALCONTROLLER_PRIO_POST_MIN=196未満です。

 その他、以下のシンボルを定義しています。

ALCONTROLLER_PRIO_UPDATE_POST_HIGH = 144;
ALCONTROLLER_PRIO_UPDATE_POST_MID = 160;
ALCONTROLLER_PRIO_UPDATE_POST_LOW = 176; 

・アップデータの更新後

 通常のアップデータの更新が全て終わった後に実行します。

 範囲はALCONTROLLER_PRIO_POST_MIN=196 以上でALPRIO_MAX=254未満です。

 その他、以下のシンボルを定義しています。

ALCONTROLLER_PRIO_POST_HIGH = 208;
ALCONTROLLER_PRIO_POST_MID = 224;
ALCONTROLLER_PRIO_POST_LOW = 240; 

なお、一部の独自設定をしているコントローラを除けば、デフォルトはALCONTROLLER_PRIO_POST_MID = 224です。

1.5. タグ

コントローラにはユーザーが自由に使用出来る32ビット数値のタグが存在します。

単なるユーザーでの識別の他、このタグのビットを種別として扱い、ノードに所属するコントローラをクリアすることも出来ます。

設定にはSetTag関数を、取得にはGetTag関数を使います。

指定したビットにおけるTagのコントローラをクリアするには、描画ノードのClearControllerByTagBit関数を使用します。

この関数は、指定したビットが1つでも立っているコントローラを全てクリアします。

ALController *cont1 = ALAngleSpeedController::Create( 0, 1 );
cont1->SetTag( 0x1 );
node->AddController( cont1 );
ALController *cont2 = ALAngleSpeedController::Create( 0, 1 );
cont2->SetTag( 0x4 );
node->AddController( cont2 );

node->ClearControllerByTagBit( 0x4 );  //cont2のみクリア

1.6. ユーザーコントローラ

コントローラはユーザが作って使用することも出来ます。 その場合は、コールバック関数を使用して作る方法と、継承して作る方法があります。 どちらもそれほど違いは無いので、使いやすい方法を使用してください。

コールバック関数を使用する場合は、ALFuncControllerを使用します。 引数に、アップデート関数を指定します。 単純に使うだけならそれで十分ですが、FindControllerなどで他のコントローラと区別したい場合などは、 IDとNameをつける必要があります。

その場合、アップデート関数の前に32ビットのコントローラID、文字列でのコントローラ名を指定してください。 コントローラIDは4文字の文字IDを推奨します。システムでは大文字、数値、アンダーバーの3種のみを使用するので、 小文字や記号を含めると、システムのコントローラIDと衝突する事はありません。

コールバック関数は、アップデート関数の他、初期化関数、終了関数も指定出来ます。 これらはオプションなので省略が可能です。 初期化関数は、AddControllerを実行し、対象ノードが確定した後に呼ばれます。 制御対象のノードは、GetOwnerNodeで取得出来ます。

継承して作る場合は、ALControllerをそのまま継承します。 アップデート関数はDoUpdate関数を、初期化関数はDoInit関数をオーバーライドします。 IDの設定は、GetControllerType関数を、Nameの指定はGetName関数をオーバーライドします。 終了関数は存在しませんので、デストラクタを使用してください。

2. ゲーム系コントローラ

2.1. Fsmコントローラ

Fsmコントローラ(ALFsmController)とは、複数の状態を持ち、状態ごとに設定したアップデートや初期化関数などを切り替えつつ動作するコントローラです。 ゲームなどで、キャラクターが複数の状態(停止、移動、攻撃など)を持つ場合、それらを登録し条件によってアップデート関数を切り替えることが出来ます。

2.1.1. 基本的な使い方

まず、Create関数でFsmコントローラを生成します。 その後、必要な数の状態を、32ビット識別IDとアップデート関数をセット関数で追加していきます。 アップデート関数の他、遷移チェック関数、初期化関数、終了関数、および状態ごとのユーザーデータも登録可能です。 その後、対象となるノードにAddControllerで登録し、ChangeState関数で登録した識別IDを指定すると動作を開始します。

enum{
	STATE_A,
	STATE_B
};

ALFsmController *fsm = ALFsmController::Create();
ALNode *node =ALNode::Create();
fsm->AddState( STATE_A, UpdateFunc );
fsm->AddState( STATE_B, UpdateFunc, CheckFunc, InitFunc, ExitFunc, (void *)10 );
node->AddController( fsm );
fsm->ChangeState( STATE_A );

ChangeStateの第2引数には、遷移チェック関数への引数を渡すことも出来ます。 また、CanChangeState関数を使用すると、その状態に遷移出来るかどうかを調べることが出来ます。 trueなら遷移可能、falseなら遷移不可です。

fsm->ChangeState( STATE_B, 100 ); //CheckFuncにこの100という値が渡される
bool changeok = fsm->ChangeState( STATE_B, 100 );

2.1.2. 各種コールバック関数

Fsmコントローラでは4種のコールバックを使用します。 型は、アップデート、初期化、終了関数では

typedef void (*ALFSM_STATE_EVENT_FUNC)( ALFsmState * state );

型の関数を、遷移チェック関数は

typedef bool (*ALFSM_STATE_CHECK_FUNC)( ALFsmState * state, Uint32 checkparam );

型の関数を使用します。

アップデート関数はメインの処理を行う関数です。      この関数は、Fsmコントローラ動作中は毎フレーム呼び出されます。 ここでメインの処理を行います。 引数のALFsmStateとは、AddStateで生成するステートごとのクラスです。 ここから、FsmControllerやAddStateの第6引数で指定出来るステートごとのユーザーデータを取得することが出来ます。

GetOwner関数でFsmControllerを、GetStateData関数でユーザーデータを取得出来ます。 ユーザーデータはSetStateData関数で設定することも出来ます。 対象ノードを取得したい場合は、FsmControllerのGetOwnerNode関数を使用します。

FsmコントローラのデフォルトプライオリティはALCONTROLLER_PRIO_UPDATE_PRE_MIDとなっており、対象ノードのアップデート直前に動作します。 ALYieldも使用可能です。 またノードのアップデート処理も登録し、両方とも使用することが可能ですが、ALYieldを使用するともう片方のアップデートも中断するので注意してください。

void UpdateFunc( ALFsmState * state )
{
    ALFsmController *cont = state->GetOwner();
    void *data = state->GetStateData();
    Uint32 stateid = cont->GetCurrentStateID();
    ALNode * node = cont->GetOwnerNode();
}

初期化関数は指定したステートに変更した後、初期化として呼ばれる関数です。 呼ぶタイミングはChangeStateを実行したときではなく、その後アップデート処理の直前になります。 そのため、アップデートが来る前に複数回ChangeStateを実行しても、最後に変更したもののみが呼ばれます。

また、同一ステートに変更した場合でもこの初期化関数は呼ばれます。 AddState時に省略したり、NULLを指定すると使用しません。

終了関数は元のステートの終了処理として、指定したステートの変更前に呼ばれる関数です。 呼ばれるタイミングは初期化関数と同様、ChangeStateを実行したときではなく、その後のアップデート処理直前になります。 その他の条件も初期化関数と同じです。 初期化、終了関数がどちらも登録している場合、呼び出し順は

  • 元ステートの終了関数

  • 新ステートの初期化関数

  • 新ステートのアップデート関数

となります。

遷移チェック関数は、ChangeStateを実行したときに、指定したステートに変更するかどうかをチェックする関数です。 他のコールバック関数と違い、ChangeState実行時に呼び出されます。 返り値がtrueなら変更許可、falseなら変更不許可になります。

第3引数のcheckparamはChangeState実行時に指定することが出来るパラメータです。 ChangeState関数の第2引数として渡すことが出来、それをそのまま渡します。 省略時は0になります。 もし、チェックの結果別のステートに変更したい場合はこのコールバック内でChangeStateを呼び出すことも可能です。 その場合、返り値はfalseに設定する必要があります。

2.1.3. ステートの取得

現在実行中のステートを取得するには、GetCurrentStateID関数もしくは、GetCurrentState関数を実行します。 これにより、現在のステートクラスか、識別IDが取得出来ます。 Fsmコントローラ生成直後など、まだChangeStateを実行前の場合、GetCurrentStateIDならALFSM_STATE_NONE、GetCurrentStateならNULLが返ります。

同様に、次に変更予定のステートはGetNextStateIDとGetNextStateで取得出来ます。 変更予定が無い場合は、ALFSM_STATE_NONEが返ります。 これを利用して、各種コールバック内でどのようにステートが変化しようとするかを取得することが出来ます。

アップデート関数内ではGetCurrentStateIDでは自分のステートの識別IDを、ChangeStateを実行しない限りGetNextStateIDはALFSM_STATE_NONEを返します。

初期化関数内ではまだステートが切り替わっていないため、GetCurrentStateIDでは直前のステートにおける識別IDを、 GetNextStateIDは自分のステートにおける識別IDを返します。

終了関数内のGetCurrentStateIDでは自分のステートにおける識別IDを、GetNextStateIDでは次のステートにおける識別IDを返します。

遷移チェック関数内のGetCurrentStateIDでは現在のステートにおける識別IDを、GetNextStateIDでは自分のステートにおける識別IDを返します。

2.1.4. グローバルコールバック

各ステートのコールバックのみではなく、Fsmコントローラ自体に登録し、常に使われるコールバックもあります。 それはステート変更時に呼び出されるグローバル変更コールバックと、ステートチェック時に常に呼び出されるグローバルチェックコールバックです。

グローバル変更コールバックは、登録するとステートの終了関数と初期化関数の間に呼び出されるようになります。 これによりステート変化時に常に行いたい処理を実行することが出来ます。 登録にはSetGlobalChangeFunc関数を使います。登録する関数の型は

typedef void (*ALFSM_EVENT_FUNC)( ALFsmController * cont );

です。 ステートコールバックとは引数が違うので注意してください。 GetCurrentStateでは現在のステートが、GetNextStateでは次のステートが取得出来ます。

グローバルチェックコールバックは、ステートごとのチェック関数の直後に呼ばれるコールバックです。 SetGlobalCheckFunc関数で登録します。関数の型は

typedef bool (*ALFSM_CHECK_FUNC)( ALFsmController * cont, Uint32 checkparam );

です。 第1引数の型が違う以外は、ステートのチェック関数とまったく同じ動作を行います。 ただし、ステートごとのチェック関数でfalseが返ってきた場合はグローバルのチェックコールバックは実行されません。

2.2. モーションマネージコントローラ

モーションマネージコントローラ(ALMotionManageController)とは、ゲームでのキャラクターなどのモーションを管理するコントローラで、 特定のモーションから特定のモーションへつなぐ場合はどうするとか、この場合はこのモーションとこのモーションをブレンドしたいとか、 特定条件の時だけ違うモーションを使いたい場合の処理を行います。 モーションが多く、補間を多用する場合に威力を発揮します。

2.2.1. モーション定義

モーションマネージコントローラでは モーションの組み合わせによる新規モーションを定義することが出来ます。 新規モーション定義には1~4個のモーションを定義出来、 2個以上の場合はブレンド率も設定します。

ブレンド率は設定したモーション個数の数か、それより1つ少ない数を設定します。 1つ少ない場合は、1.0からそれまでの合計を引いた値を使用します。 ブレンド率は実行中いくらでも変化させることが可能です。 単一モーションを指定した場合は、単にエイリアスとして使うことが出来ます。

モーションマネージコントローラに対し、モーション変更指示を行った場合はこのモーション定義を使い、 もし指定したIDにモーション定義がない場合は、元のモーションIDをそのまま使います。 そのため、必要なものだけモーション定義を設定して使用します。

ALNode *node = ALNode::Create();
node->LoadMotion( "test.amt" );		//モーションID1、2、3のみを定義しているとする
ALMotionManageController *mmc = ALMotionManageController::Create();
node->AddController( mmc );
mmc->AddMotionID( 11, 1 );	//新規モーションID11はモーションID1を呼び出す
mmc->AddMotionID( 12, 2, 3 );	//新規モーションID12はモーションID2と3をブレンドして呼び出す
mmc->SetBlendRate( 12, 0.2f, 0.8f );	//ブレンド率は0.2と0.8
mmc->AddMotionID( 13, 1, 3 );	//新規モーションID12はモーションID2と3をブレンドして呼び出す
mmc->SetBlendRate( 12, 0.4f );	//ブレンド率の一つは自動設定され、0.4と(1-0.4)の0.6が設定される

mmc->LoadMotion( 11 );		//モーション1をロードする
mmc->LoadMotion( 12 );		//モーション2と3をブレンドして呼ぶ
mmc->SetBlendRate( 12, 0.1f );	//ロード中のモーション12のブレンド率を0.1、0.9に変更する
mmc->LoadMotion( 3 );		//モーション定義がないので、モーション3をそのまま呼ぶ

この新規モーション定義は、主に歩きと走りをブレンドして新規モーションとして定義する時などに使います。 この時に使用するIDは、すでに存在するモーションIDとぶつからないように定義する必要があります。

ただし、あえてすでに存在するモーションIDを指定し、置き換えるような使い方も可能です。 ブレンド指定はこの新規モーションIDに対して行います。 固定のブレンド率のモーションを登録してもいいですし、 前述のような歩きと走りをブレンドする場合などは、アプリケーション側から毎フレーム更新することになります。

ブレンド率はモーションの数より1つ少なければ、自動的に1.0から残りの値を計算して設定します。 ブレンド率とモーション数が一致していれば、すべてのブレンド率を手動で設定することが可能です。

第5章 リソース関連

1. イメージ

イメージとは、Aqualead上で画像を扱うクラスです。 イメージは純粋にメモリ上だけに存在するクラスで、描画に使用する場合は後述するテクスチャに設定して使用します。

イメージはいくつものフォーマットがあり、ロード時のフォーマットにあわせて使用します。 ドット単位の読み書きの他、ラインごとの先頭アドレスを取得して書き換えることなどが出来ます。 イメージローダは、Aqualeadに最初から用意されていますが、後から追加して使用することが可能です。 現在ローダは、bmp、tga、Aqualead専用フォーマットであるaigの3種に対応しています。

1.1. 基本的な使い方

イメージはファイルからロードする他、フォーマットやサイズを指定して生成することが可能です。 また、別のイメージを元にフォーマットを変換して生成したり、 イメージにおける一部分の矩形を切り出すことが出来ます。 一部切り出しを行った場合は、メモリ上で共有されるためコピーは発生しません。

生成したイメージはGetPixelColorでピクセルの色を取得したり、SetPixelColorでピクセルの色を設定、 CopyRectで別イメージからのコピーが出来ます。

1.2. ダイレクトアクセス

イメージは先ほどのGetPixelColorなどの関数を使用すると、 イメージが実際どのようなフォーマットであるかを気にしなくてもアクセスが出来ますが、 その分非常に低速になります。 ですので、必要であればイメージのメモリを取得し、直接読み出したり書き換えたりが出来ます。 その場合、まずはGetFormatでイメージのフォーマットを取得します。

これにより、認識出来るフォーマットであれば、GetScanLineで横1ラインの先頭アドレスを取得し、そこを読み書きします。 また、GetRawDataで画像の先頭アドレスが取得出来、GetRawDataSizeで画像のみのサイズが取得出来ます。

2. テクスチャ

テクスチャはハードウェアで使用出来る形の画像イメージです。 各種ノードなどに設定して使用します。 画像イメージ自体はイメージクラスを使用しますが、 それに追加して内部に各種プラットフォーム用の管理データや、画像を区切って使用する境界情報を持ちます。

2.1. 基本的な使い方

テクスチャは一般的にはファイルからイメージファイルや、テクスチャファイルを読み取って使用します。 ノードにはLoadTexture関数があるので、通常はこの関数を使用します。 このとき、解放はノードの削除時に自動的に行います。

そのほか、テクスチャをCreate関数を利用し単体で生成してノードにSetTextureで設定することも出来ます。 この場合は、使用が終わった後にテクスチャをRelease関数で呼び出して削除する必要がありますが、 テクスチャは参照カウント管理しているため、ノードに設定したらすぐにReleaseを実行してもかまいません。

この場合ノードを削除すると、参照がなくなり、テクスチャを一緒に削除します。 ひとつのテクスチャを複数ノードに設定したい場合を除けば、LoadTextureを使うことをお勧めします。

2.2. 親子関係

テクスチャは、1つのテクスチャの中に複数のテクスチャを含めることが出来ます。 これを利用すると、小さなテクスチャをまとめ、1つの大きなテクスチャとして扱うことが出来ます。

この時もアプリケーション側が特殊なことをしなければ、そのテクスチャが単独か、親の一部かを気にする必要はありません。

ALImageMergeというツールを使うことにより、小さなテクスチャを隙間をつめてマージすることが出来ます。 このとき、それぞれのテクスチャには独立したIDやNameが付きますが、 これらを使用するためには親となるテクスチャがメモリ上に存在する必要があります。

2.3. 境界情報

テクスチャには前述の親子関係を設定出来るほか、さらにそのテクスチャの中身を小さく分けて使うことが出来ます。 これをテクスチャの境界情報と呼びます。 これは主にスプライトで使い、スプライトのSetPatternNoで何番目の境界情報を使うか指定します。 先ほどの親子関係とは違い、この境界情報はインデックスしか持たず、0からの連番で指定します。

また、テクスチャ内の座標、サイズの他、中心座標を持つことも可能です。 親子関係との違いは、親子関係は基本的に別種類のテクスチャをまとめたものであるということです。 境界情報で分割するものは、スプライトなどで使用する1種類のキャラのアニメーションパターンなどです。

親子関係でも、境界情報のどちらでもアニメーションすることが可能ですが、境界情報を使用するほうが高速になります。 この情報はテクスチャファイルに含めることも、プログラムで設定することも可能です。

2.4. ロック

テクスチャは通常イメージクラスを持ちますが、その画像を読み込んだり書き込んだりする場合はロックする必要があります。 ロック時には、読み込みロックか書き込みロックか、全範囲なのか一部のみなのかを指定します。

また、イメージのフォーマットもテクスチャで使用しているフォーマットのままなのか、 特定のフォーマットに変換してロックするのかを選ぶことが出来ます。 アクセスし終わった場合はアンロックをします。書き込みロックをした場合は、この時点でテクスチャに反映します。

3. メッシュ

メッシュは主にモデルに設定して使用する3Dデータです。 メッシュには頂点情報のほか、マテリアル情報、テクスチャへの参照情報、ボーン情報などを含みます。 内部の頂点データは、メッシュブロック(ALMeshBlock)という頂点グループ単位で管理します。

通常はモデルのLoadMesh関数で読み取り、主に頂点情報を取得する必要がある場合にこのクラスを使用します。 IDやNameが一致しているメッシュがロード済みの場合は、ファイルからは読み込まずメモリ上のメッシュと共有します。

変更は出来ず、その場合は動的メッシュ(ALDynamicMesh)を使用します。

メッシュでは、シェープという複数メッシュブロックを使ったモーフィングも使用出来ます。 描画時にどのシェープを使うかはモデル(ALModel)で指定しますが、 指定したシェープインデックスでどのメッシュブロックを使うかは、メッシュブロックインデックスで指定します。

3.1. メッシュの構成

メッシュは、メッシュ共通部とメッシュブロックに分かれます。 メッシュ共通部には、マテリアル情報、テクスチャ情報、ボーン情報などを記録し、 メッシュブロックにはそれ以外の頂点情報と、共通部のマテリアル、ボーンへのインデックスを記録します。

3.2. メッシュ共通部

メッシュ共通部にはマテリアル情報、テクスチャ情報、メッシュブロック構成情報、バウンディングスフィア情報を含みます。 テクスチャはマテリアルから参照して使用し、 マテリアルはメッシュブロックからインデックス参照で使用します。

3.2.1. シェープ

メッシュブロックは、シェープが存在しなければ単純に全てをそのまま表示します。 シェープが存在する場合は、インデックス参照で使用するメッシュブロックが決まります。

シェープ数はGetShapeCount()取得し、モーフィングを使用しないメッシュでは1になります。 2以上で、シェープ有りとなります。 メッシュブロックの総数は、GetBlockCount()で取得します。 これはシェープも含めた全てのブロック数です。 一度に描画するブロック数は、GetDrawBlockCount()で取得します。 シェープを使用しない場合は、これらは同じ数を返します。 シェープがある場合で、全ブロックがシェープ対応の場合、

GetBlockCount() = GetDrawBlockCount() * GetShapeCount()

が成り立ちます。 一部シェープに対応しないメッシュブロックを含む場合は、この式よりGetBlockCount()は少なくなります。

描画に使用するシェープはGetBlockIndex関数で描画ブロックインデックス、 シェープのインデックスの指定でブロックインデックスが求められるので、 そのインデックスをGetBlock関数に渡すと、そのシェープを求めることが出来ます。 GetIndexedBlock関数を使うとその処理を一気に行います。

なお、シェープを使用しない場合は、常にGetBlockIndexは描画ブロックインデックスと同じ値を返し、 GetBlockとGetIndexedBlockは同じブロックを返します。

3.2.2. バウンディングスフィア

Aqualeadでは、メッシュはコンバート時に計算したバウンディングスフィアを持ちます。 全頂点は、このバウンディングスフィア内に存在する事を保証します。 バウンディングスフィアはGetBoundingSphereで求める事が出来ます。 xyzが中心座標、wが半径です。 これらは、GetBoundingSphereCenter関数とGetBoundingSphereRadius関数を使用して別々に求める事も出来ます。

また、全体のバウンディングスフィアの他、ボーン単位のバウンディングスフィアも使用出来ます。 これはそのボーンを使用している全頂点を含む球です。 頂点のブレンドをしている場合も、0より大きいウェイトの頂点は全て含みます。 ボーンのインデックスを指定し、全体のバウンディングスフィアと同様に求める事が出来ます。 このバウンディングスフィアの原点は、指定したインデックスにおけるボーンの座標になります。

バウンディングスフィアはメッシュブロック単位でも存在し、 メッシュブロックにあるバウンディングスフィア取得関数を使用して取得します。

3.3. メッシュブロック

メッシュブロックはメッシュの内部で保持する頂点のグループクラスです。 メッシュから取得出来、単独で生成は出来ません。

メッシュブロックはマテリアルを1つしか持てないため、マテリアルを複数持つ場合はかならずメッシュブロックも複数になります。 また、メッシュブロック単位でクリッピングを行うため、広いメッシュを分割して使用する事もあります。 描画時は、メッシュ内では常にこのメッシュブロックの順番で描画を行います。

頂点情報では、位置、法線、色、UV、ボーンは原則インデックス参照です。 インデックスを使用しない場合でも、0から連番のインデックスを取得出来ます。 ボーンウェイトのみ、頂点情報に直接記録します。 また、法線、色、UVは1頂点あたり複数の値を持つ事も可能です。 ボーンとマテリアルはメッシュが保持し、メッシュブロックではその参照インデックスのみを保持します。

メッシュブロックの頂点は1つ以上のストリップで構成します。 トライアングルストリップは、このストリップ単位で描画します。 トライアングルリストの場合は、通常ストリップは1つとなります。

3.3.1. 各種情報取得

位置や法線などの情報を取得する場合は、ストリップインデックスとそのストリップ内の頂点インデックスを指定して取得します。 ストリップの数はGetStripCountで、ストリップ内の頂点の数はGetStripVertexCountで、 また全ストリップの頂点を合計した数を、GetStripVertexCountで取得することが出来ます。

また、インデックス参照のため位置や法線などの数は頂点数とは別になります。 それぞれ、GetPositionCount、GetNormalCount、GetUVCount、GetColorCountで取得が出来ます。 色やUVが頂点あたりに複数存在しても、この色やUVのリストは1本です。

このリストから直接値を取得する場合は、GetPosition、GetNormal、GetUV、GetColorを使用します。 これらの関数は頂点情報とは独立している点に注意してください。

ボーン情報のみは、メッシュ共通部に格納しているため、メッシュブロックにはその参照インデックスを記録します。 ブロック内におけるボーン数の取得は、GetBoneCountで、メッシュ共通部のボーンへのインデックスは、GetBoneIDIndexで、 実際にそのボーンのノードIDを取得するには、GetBoneIDを使用します。 ボーンのみ、共通部とブロックの2重インデックスになるので注意してください。

頂点から各種のインデックスを取得するには、GetPositionIndex、GetNormalIndex、GetUVIndex、GetColorIndex、GetBoneIndexの関数を取得します。 これらから取得したインデックスを、前述のGetPositionなどの関数に指定すると実際の値が取得出来ます。 インデックスを取得せずに、一気に値を取得するため GetIndexedPosition、GetIndexedNormal、GetIndexedUV、GetIndexedColor、GetIndexedBoneIDという関数も用意しています。

3.3.2. ストリップグループ

メッシュブロックには、ストリップグループという物を定義している事があります。 これは、背景のような大きなメッシュの一部をクリッピングするときなどに使用します。 ストリップグループは階層構造を持ち、それぞれに中心位置、半径を持ちます。

ストリップグループはグループインデックスで管理します。 グループ数はGetStripGroupCount関数で取得します。 存在しない場合は、0が返ります。 それぞれのグループにどのストリップを含むかは、GetStripGroupStart関数とGetStripGroupEnd関数で取得します。 Start <= n < End のnが使用するストリップのインデックスです。

各ストリップは2つ以上のストリップグループに所属する事はなく、必ずソートされています。 また、StartとEndが同じ値になる事もあります。その場合は、そのグループにはストリップはありません。

ストリップグループが階層構造をとる場合は、子のグループインデックスををGetStripGroupChildで取得する事が出来ます。 ここで取得した子は、子の先頭なのでGetStripGroupSibling関数でその兄弟を順次取得出来ます。 どちらも-1が返るとそれ以上の子はありません。 親子関係がある場合、親のバウンディングスフィアは全ての子の頂点を含みます。 なお、0番目のストリップグループが常に一番の親になります。

4. 動的メッシュ

動的メッシュは、編集可能なメッシュです。 メッシュを0から構築することが出来ます。 各種メッシュコンバータはこの動的メッシュを使用して作っており、 全形式のメッシュがこの動的メッシュで生成出来ます。

なお、各種のメッシュが生成出来る都合上扱いは少々難しくなっています。 簡易的な3Dモデルを表示する場合は、3Dプリミティブ(ALPrim3D)を使う事を推奨します。

ストリップ化、格子状の分割など各種メッシュの加工を行う事も出来ます。 実際の頂点編集は動的メッシュブロック(ALDynamicMeshBlock)を使用します。

シェープを作ることも可能ですが、現時点ではメッシュブロックインデックス指定のシェープは作れません。 単純に総ブロック数からシェープ数を割ったものが、1回の描画単位になります。

4.1. 基本的な使い方

動的メッシュはALDynamicMesh::Create関数で生成します。 動的メッシュも通常のメッシュと同様、共通部とメッシュブロック部に分かれます。 動的メッシュブロックは、動的メッシュのAddBlock()関数で生成して取得します。

生成した動的メッシュは、モデルに設定すればそのまま描画が可能です。 描画用のセットアップはモデルに設定した時に行うため、各種設定が全て終わった後にモデルに設定して下さい。

4.2. 動的メッシュ共通部

動的メッシュの共通部も、通常のメッシュと管理するデータは変わりません。 各種Get系関数の他に、Add系やSet系関数を追加しています。 Get系関数は通常のメッシュ関数を使うため、動的メッシュ自体には追加していません。

Add系関数で各種データを登録出来ます。 またFindAdd系の関数を使用するとデータが存在しない時のみ各種データを登録します。 その際、すでにデータがあればそのインデックスを、無ければ登録したデータのインデックスを返します。。 そのため、FindAdd系関数を使うと重複データを抑えコンパクトなメッシュを作る事が出来ます。

シェープを作る場合は、現時点では単純にSetShapeCountに2以上の数を設定します。 そうすると、全てのブロックはシェープ対応となります。 その際、ブロック数は必ずシェープ数の倍数に設定して下さい。

各種頂点情報のクリアや、メッシュ加工関数は、どれも所有する全てのブロックに対し同様の関数を発行します。 外側でブロック数分ループして、個別にメッシュブロックに設定しても結果は変わりません。

4.3. 動的メッシュブロック

動的メッシュブロックは、動的メッシュで使用するメッシュブロックです。 生成するときは、動的メッシュのAddBlock関数を使用します。

動的メッシュブロックでは、単なるメッシュブロックと違い位置法線などの情報の他、頂点自体もインデックス管理します。 頂点は動的メッシュ頂点(ALDynamicMeshVertex)という構造体で表し、ここに座標や法線などのインデックスを格納します。 新たな頂点を登録する場合は、この動的メッシュ頂点を生成し、そこに各種インデックスを指定し、 この動的メッシュブロックに登録するという形を取ります。

頂点を登録しても、頂点インデックスを登録しない限り頂点として使用しないことに注意してください。 たとえば、三角形2つを使った四角形を作る場合、頂点を4つ登録し、頂点インデックスを6つ登録します。

また、頂点がいくつのボーンを持つかの情報を設定することが出来ます。 この値はデフォルトで0(頂点がボーンを持たない)を設定しており、この他に現在は1、2を設定することが出来ます。

頂点には、接続した各ボーンから影響を受ける度合いを示すボーンウェイトをそれぞれ設定することが出来ます。 ただし、頂点が持つボーンの数が1の時はボーンウェイトを実質無視し1.0として扱い、 ボーンの数が2の時でも2つ目のボーンウェイトを(1.0-1つ目のボーンウェイト)として扱います。

4.4. 頂点情報

動的メッシュブロックへ登録する1頂点の情報は、専用のALDynamicMeshVertex構造体を使用します。 この構造体には、1頂点に関する様々なインデックスを格納することが出来ます。

ボーンウェイトは、頂点に接続したボーンから受ける影響の度合いを表した値です。 頂点に接続しているボーンの数は、動的メッシュブロックが管理します。 また、1頂点のボーンウェイトの総計は1.0になる必要があります。

5. フォント

フォントとは、テキストクラスで使用する文字描画のためのデータです。 文字の画像データと、文字コードと画像の対応表をまとめて管理します。 フォントコンバータを使い、フォントファイルを作りそれをテキストクラスで読み込んで使用します。

5.1. フォントの種類

フォントは大きく、文字を展開したイメージをテクスチャとしてそのまま利用するテクスチャフォント、 主に白黒2階調のデータを使い、必要な文字データだけを展開して使用する圧縮フォントの2種類があります。

圧縮フォントはメモリも最小限で、漢字などの大量の文字を管理することが出来ますが、 必要な文字をテクスチャに展開して描画するため速度は低速です。 ただし、キャッシュを行うため、一度表示した文字はワークがフルになるまで展開せずに使いまわします。 逆にテクスチャフォントはテクスチャをそのまま使うため高速に処理が出来ますが、 プラットフォームごとにテクスチャサイズの制限を受け、大量の文字は登録出来ません。 主に数値やアルファベットに使用します。

5.2. 文字変換テーブル

文字はすべてシフトJISで扱います。 そして、文字コードと実際のイメージとの対応表を元に、実際に表示する画像を決定します。 対応表は、数値用、数値+アルファベット用、8ビット文字コード用、第1水準漢字用、それにカスタムテーブルがあります。 使用する対応表を適切に選ぶことにより、使用するデータサイズを最小限にすることが出来ます。

フォントコンバータではテキストファイルの使用する文字だけを抜き出すことも出来るため、 漢字を使う場合はカスタムテーブルを使い、必要な文字だけを登録することが出来ます。

5.3. アイコン

フォントにもテキスト同様アイコンを埋め込むことが出来ます。 テキストにアイコンを埋め込むと、そのテキスト内でしか使用出来ませんが、 フォントに埋め込むと、このフォントを使用するテキストすべてで使用出来ます。 フォントとテキストの両方に同じコードのアイコンを定義すると、テキストに定義したものを優先します。

6. リソース

リソース(ALResource)とは、アーカイブやテクスチャなどゲームで使う各種データを一括管理するためのクラスです。 まとめて使用するデータをリソースに登録し、IDと共に登録しておくと、一括ロード、一括アンロードが行えます。 その際、裏読みにすることも可能です。 タスクと組み合わせることにより、タスク切り替え時の自動ロード、アンロード、先読みなどが可能です。

6.1. 基本的な使い方

リソースを管理するリソースマネージャ(ALResourceManager)はシステムに1つだけ存在し、 リソースマネージャのstatic関数を利用してリソースを登録、ロード、アンロードを行います。 登録するには、AddResource関数を使用します。 登録時に1以上のリソースIDと、必要なら初期化関数、終了関数を登録します。 AddResourceの返り値として、ALResourceDefineクラスのインスタンスが返ります。 登録したい各種データはこのインスタンスに登録していきます。 ALResourceDefineは破棄の必要は無く、使用しない場合はAddResource関数の返り値は無視してかまいません。 このALResourceDefineのインスタンスに必要なデータを登録していきます。 初期化が終わって、そのリソースを使用するときはLoadResource関数を、破棄するときはUnloadResource関数を呼びます。

enum{
	RESOURCE_PLAYER = 1,
};

ALResourceDefine rd = ALResourceManager::AddResource( RESOURCE_PLAYER );
rd.AddArchive("player.aar");
rd.AddTexture("player.atx");

ALResourceManager::LoadResource( RESOURCE_PLAYER );
~
ALResourceManager::UnloadResource( RESOURCE_PLAYER );

6.2. リソースに登録出来るデータ

リソースに登録出来るデータは、Texture、Archive、Stream、Font、Soundの5種類です。 それぞれIDか、Nameで登録します。 登録関数は、それぞれAddTexture、AddArchive、AddStream、AddFont、AddSoundです。 このほか、依存関係を登録出来ます。 これは、例えばあるリソースを使用する場合、別のリソースに含んでいるアーカイブを使用する場合などに、 別のリソースを依存として登録しておけば、必要なときにそのリソースもまとめて読み込みます。 依存関係はループしない限りはいくらでも深く出来ますので、基本的には各種データは1つのリソースのみに所属し、依存関係を設定した方が扱いやすくなります。 なお、すでにロード済みのリソースに依存していた場合は特にロードはせずそのまま使用します。 各種データのロード順は登録順ですが、まず最初に依存関係が解決してからデータのロードを行います。

enum{
	RESOURCE_PLAYER = 1,
	RESOURCE_PLAYER_EFFECT,
};
ALResourceDefine rd1 = ALResourceManager::AddResource( RESOURCE_PLAYER_EFFECT );
rd1.AddArchive("peffect.aar");

ALResourceDefine rd2 = ALResourceManager::AddResource( RESOURCE_PLAYER );
rd2.AddTexture( 0x1000 );
rd2.AddSound("player.wav");
rd2.AddStream("player.dat");
rd2.AddDepend( RESOURCE_PLAYER_EFFECT );

ALResourceManager::LoadResource( RESOURCE_PLAYER ); //依存があるので、RESOURCE_PLAYER_EFFECTも一緒にロードされる

6.3. ロードした各種リソースの使用

ALResourceがロードした各種リソースは、ALResourceから取り出さなくてもそのまま使用出来ます。 各種Create関数でIDや名前を指定して生成すると、それらを呼び出して使う事が出来ます。

必要があればFindTexture、FindArchiveなどでALResourceのインスタンスから直接取り出すことも可能です。 特に理由がなければ、Create関数に直接IDや名前を指定して使用して下さい。

7. サウンド

Aqualeadでは現在サウンド処理はwavの再生のみが行えます。 現在のところmidiやoggなどは未対応ですが、対応予定です。 wavは単発再生のほか、単純なループ、ループポイントを指定したループが使用出来ます。 ループポイントを使用する場合は、あらかじめ何らかのサウンド編集ツールでwavにループポイントを設定する必要があります。 現在必要最小限の機能しかありませんが、順次充実させていく予定です。

7.1. サウンドで使用するクラス

サウンド処理は3つのクラスを使用します。

wavデータなど、音自体のリソースを管理するALSound。

実際に音を再生するALSoundPlayer。

再生した音に対するアクセスを提供するALVoice。

7.2. 単純な使い方

もっとも単純なサウンド再生は、ALSoundPlay関数を使用する方法です。 ALSoundPlay関数にはIDか、ファイル名を指定します。 この関数で実行すると、サウンドをロードし、サウンドを再生、 再生したサウンドのALVoiceを返し、再生終了後に解放という動作を行います。 サウンドをロードしていない場合は、ロードも同時に発生します。 そのため大きなサイズのサウンドを再生すると、一瞬ロードでゲームが停止する可能性があります。 何度も使うサウンドや、大きいサウンドはあらかじめロードすることを推奨します。

7.3. サウンドのロード

サウンドをロードするには、ファイル名かIDを指定してALSound::Create関数を使用します。 ファイル名でロードをする場合、IDを同時に指定すると以降IDで呼び出すことも可能になります。 これは、ファイル名でCreateしたあと、SetIDでID指定したのと同じ動作です。

ロードしたサウンドは、そのままPlay関数で再生するか、ALSoundPlayerでファイル名やIDを指定して再生します。 前述の簡易再生でもすでに同名や同IDのサウンドがロード済みであればそれを使う為、ロードが発生することはありません。 解放する場合はRelease関数を呼びます。

なお、再生中の場合はReleaseを実行してもすぐには解放せず、再生完了後に解放を行うので注意してください。 これを利用して、1度しか使用しないサウンドの場合、再生直後にReleaseを呼び出せば再生終了後に自動解放を行います。 前述のALSoundPlay関数はこれを利用し、Createしたサウンドをそのまま再生し、直後にReleaseを呼び出しています。

7.4. アーカイブからの一括ロード

.aarファイルの準備機能を使うと、簡単にサウンドのロードが出来ます。 使用方法は簡単で、.sarにファイルを記述するとき、[Prepare]というセクションをつくり、そこにファイルを記述します。 こうするとアーカイブをロードしたときに指定したサウンドは、すべてロード済みとなります。

以降はアーカイブを破棄するまで、メモリ上のサウンドを使用するのでロードが発生しなくなります。 なお、この準備機能は現在サウンドとテクスチャのみの対応で、通常のファイルは[Keep]というセクションに記述します。

.sarファイルの記述例

[Prepare]
sound1.wav
image.bmp
[Keep]
obj1.aod

7.5. ボイスクラス

ALVoiceとは、再生したサウンドの制御用のクラスです。 再生を行うごとに新規のボイスを生成します。 再生したボイスの情報を取得したり、停止する必要がなければ保持する必要はありません。 再生終了と共に実体は自動的に消滅します。 ボイスのインスタンス自体は無効になってもアクセスは常に可能ですが、再生が終わったボイスのインスタンスへの設定は全て無視します。

ボイスのインスタンスが有功かどうかは、IsValid()関数で確認出来ます。 falseの場合はすでに再生が終わっているので、このボイスインスタンスは無効です。

IsPlay()を監視することにより、再生終了を調べることが出来ます。 IsStop()でサウンドの停止、SkipLoop()でループポイントのあるサウンドのループを脱出します。

7.6. サウンドプレイヤー

サウンドの再生は常にサウンドプレイヤー経由で行います。 サウンドプレイヤーはデフォルトの概念があり、システム起動時にデフォルトのサウンドプレイヤーを生成します。

サウンドをCreateして再生したときには、そのときのデフォルトプレイヤーがサウンドに設定され、 直接Playをしたときはそのプレイヤーを使います。 再生時点のデフォルトプレイヤーではなく、生成時のデフォルトプレイヤーを使用する点に注意してください。 なお、現時点ではサウンドプレイヤーを直接使用する必要はほとんどありません。 今後の拡張で機能を追加する予定です。

第6章 メニュー

1. ウィジェット基本

ウィジェットとは各種GUIを構築するための部品です。 GUIはアプリケーションごとに仕様や見た目が変わるため、Aqualeadではデバッグ用の単純なものしか見た目は用意していません。 各種の動作やアニメーションなどは、描画ノードとモーションの組み合わせで実現します。

ウィジェットにはスキンという概念があり、このスキンに見た目を登録しておけば、共通する見た目を実現することが出来ます。 スキンは必要な数だけ使うことが出来、また特殊なものはスキンを使わず直接見た目を設定出来ます。

各種ウィジェットには通常・フォーカス・親のフォーカス・決定・無効・オープン・クローズの7種の状態があり、 これらの組み合わせにそれぞれモーションを割り当てることにより、見た目の制御を行います。 また、各種ステート変化時にコールバックを発生させることが出来るため、それによりプログラムの制御が出来ます。 その際は、それぞれのステート変化に合わせ効果音を発生させることが出来ます。

ウィジェットは親子の概念があり、操作出来るものは自分とルートまでの親すべてにフォーカスがあるものになります。 親となるウィジェットのフォーカスがなくなれば、そこのすべての子ウィジェットが操作不可能になり、また親が無効になれば子も無効となります。

フォーカスはクリック、もしくはカーソルの上下左右で変更が出来ます。 それぞれのウィジェットに、上下左右を押した場合における移動先ウィジェットの登録が出来ます。 フォーカスがあるウィジェットにはカーソルを表示させることが出来ます。 カーソルの見た目、移動方法などもすべてカスタマイズ出来ます。

1.1. ステート

ステートは前述のように7種あります。これらは通常を除くとビットフィールド管理になっているため、複数の状態が同時に発生することがあります。 それぞれの説明を記述します。

通常(Normal)

これは以下の各種ステートのどれにも当てはまらない状態をあらわします。

フォーカス(Focus)

このウィジェットを選択していることを示します。ただし、後述する親のフォーカス状態になっていないと操作は無効です。 親のフォーカスを失ってもこのフォーカス状態を維持するため、再び親にフォーカスが来た場合は同じウィジェットが操作可能となります。

親のフォーカス(OwnerFocus)

自分の親にフォーカスがある状態です。このステートとフォーカスステートが有効になった時点でそのウィジェットは操作可能になります。

決定(Decision)

決定ボタンを押した時などの決定中になっている状態です。 通常一定時間の短いアニメーションを行った後、別のステートへ移行します。

無効(Invalid)

条件を満たしていないことなどで、そのウィジェットは使えないことを示すステートです。 暗い色や灰色などで、ユーザーに使えないということが見てわかるようにする必要があります。 この状態のウィジェットは決してフォーカスや決定状態にはなりません。

オープン(Open)

メニューを表示開始するときなどに使用する、アニメーション中の状態です。 そのアニメーションが終わるとオープンステートは終わります。 なお、そのウィジェットに子があり子がオープンステートの場合、すべての子のオープンステートが終わるまで親のオープンステートは終わりません。

クローズ(Close)

メニューを消すときなどに使用する、アニメーション中の状態です。 決定ステートが完了した後などにこのステートに移行し、メニューを消去します。 これもオープンと同様、すべての子がクローズ完了するまで親のクローズステートは終わりません。

1.2. コールバック

ウィジェットでは主に各種ステートが有効になった、無効になった時のコールバックを使用します。 SetAddFocusFuncなどSetAdd~Funcでステートが有効になったコールバックを設定し、 SetRemoveFocusFuncなどSetRemove~Funcでステートが無効になったコールバックを設定します。

また、コールバックだけではなくこのようなステート変化時にメッセージを送ることも出来ます。 AddWidgetMessageTargetでどのステート変化時にメッセージを送るかのマスクつきで、メッセージ送信を指定出来ます。 メッセージIDはALMESSAGE_ID_WIDGET_EVENT_ADD_STATEかALMESSAGE_ID_WIDGET_EVENT_REMOVE_STATEで、Param1にステートが入ります。

1.3. モーション

ステートが変わるときは、モーションの変更をすることが出来ます。 各ステートのモーションはモーションIDで指定します。 SetNormalMotionIDなどのSet~~MotionIDという関数を指定します。 指定出来るのはモーションだけなので、特定のモーションでのみ使うようなノードがある場合でも、 あらかじめすべてのノードを生成しておき、モーションでは表示フラグを変更して対応します。

1.4. フォーカスの移動

フォーカスの移動の設定は、パッドの入力でそのウィジェットにフォーカスが移動するかで指定します。 SetUpWidgetなどのSet~~Widget関数で、上下左右の入力があった場合の移動先を指定します。 移動先が無効だった場合は、さらにその先へ移動します。

上下左右以外のボタンで移動をしたい場合、AddPadMoveWidget関数で、ボタンと移動先を登録します。 このボタンは複数ボタンのorを取ったものを指定出来、その場合は同時に押したときに移動します。 2種類以上のボタンのどれでもを反応させたい場合は、AddPadMoveWidgetをその個数分実行してください。

また、ショートカットのように特定のボタンが特定のウィジェットと対応する場合は、AddShortCutKeyを使用します。 この関数で指定したボタンを押した場合、そのウィジェットにフォーカスが移り、フォーカスがある場合は決定します。

1.5. ロック

ウィジェットは、一時的に操作を受け付けたくないときはロックをすることが出来ます。 ロック時は入力関係がすべて無効になります。 ロックはウィジェットごとに個別に設定するローカルなロックと、ウィジェット全体に対して作用するグローバルなロックがあります。

これらのロックは、どちらもカウントの管理をしていないので、ロックを2回実行してアンロックを1回実行するとそこでロックが外れます。 グローバルロックのみは、フレーム数を指定してロックすることも出来ます。 グローバルロック時に引数としてフレーム数を指定すると、指定したフレーム数ロックした後、自動的にアンロックされます。 なお、決定状態になると自動的に2フレームのグローバルロックが設定され、多重に操作されることを防ぎます。

1.6. オーナー

ウィジェットは、単純な座標系の親子関係だけではなくウィジェット独自の親子関係があります。 ウィジェット独自の親子関係の親をオーナーと呼びます。 現在オーナーとなることが出来るのは、コンテナウィジェットとそれを継承するウィジェットだけです。 それ以外のウィジェットがオーナーになることは出来ません。 オーナーを指定するには、ウィジェット生成時にオーナーとなるウィジェットを指定します。

オーナーを指定せずに生成する事もできます。 その場合は、デフォルトオーナーに設定されているコンテナウィジェットがオーナーとして自動的に指定されます。 デフォルトオーナーを設定していない場合は、システムが予め用意しているコンテナウィジェットが設定されています。 このシステムが用意しているウィジェットについては後述します。 ALWidget::SetDefaultOwner(ALContainerWidget *wid)関数で、使用するデフォルトオーナーを指定する事もできます。 あらかじめデフォルトオーナーを指定しておけば、毎回明示的に指定する必要はありません。

システムで内部的に生成され保持しているウィジェットがあります。 そのウィジェットはシステムオーナーと呼ばれ、ALWidget::GetSystemOwner()関数で取得出来ます。

ウィジェットの描画は常にこのオーナーが行います。グループノードでUseDrawLinkを指定した動作と同じです。 そのため、描画プライオリティはローカルになる点に注意してください。

なお、内部的にはこのウィジェットのオーナーはノードのグループマスタとして動作します。 そのため、オーナーを解放する所属するウィジェットも同時に解放されます。

2. コンテナウィジェット

コンテナウィジェットとは、各種ウィジェットを配置するために使用するウィジェットで、特に操作は受け付けません。 リストのみの使用等の単純な場合を除き、このコンテナの上に各種ウィジェットを配置して使用します。 その際、コンテナ自体にスキン等を利用して枠等を描いてもかまいませんし、特に何も表示しなくてもかまいません。 ただし、コンテナ自体はShowで表示状態にしておかないと、その上の各種ウィジェットも表示されないので注意してください。

3. ボタンウィジェット

操作のメインとなるウィジェットです。 フォーカスを持ち、決定状態になることが出来ます。 通常はコンテナウィジェット上にこのボタンウィジェットを並べ、フォーカスの移動先等の設定を行って使用します。

4. リストボックスウィジェット

ひとつの項目が一行のテキストになるリスト用ウィジェットです。 各項目はテキスト一行しか表示出来ないため、それ以上のものを表示するには後述するセルリストを使用します。 各項目はテキストのほか、任意のポインタをひとつ持たせることが出来ます。

項目を表示する際は、どの項目を表示するかのマスクの設定やソートの指定が出来ます。 ウィジェットのサイズは項目に合わせて自動調整にすることや、固定にすることが出来ます。 固定にした場合は、スクロールバーを表示することが出来ます。

5. セルリストウィジェット

ひとつの項目にコンテナウィジェットを使用するリストです。 コンテナウィジェットの中に任意のウィジェットや各種ノードを含むことが出来るため、 リストボックスウィジェットでは表現出来ない複雑なリストを表現出来ます。

6. カーソル

カーソルとはフォーカスのあるウィジェットを指し示す為に使用するクラスです。 各種ステートやモーションを持ち、選択しているウィジェットのステートにあわせモーションを切り替えることが出来ます。 カーソルの移動方法はコールバック関数により処理されるため、自由にカスタマイズが可能です。

7. スキン

スキンとは、各種ウィジェットの見た目を変化させるために使用するクラスです。 ゲームごとに一定のパターンの枠を使う場合などは、スキンとして登録しておくとどれも同じ見た目に設定することが出来ます。 これも各種ステートやモーションをもつため、対象ウィジェットの状態に合わせて変化させることが可能です。 対象となるウィジェットは大きさが違うことがあるため、コールバック関数で大きさを調整することが出来ます。

8. スクロールバー

スクロールバーは、主にリスト系ウィジェットで使われ、現在のスクロール位置を表示したり、スクロールをさせたりするクラスです。 スクロールバーや、そのつまみの位置やサイズなどはシステムが自動的に求めるため、コールバック関数でその値を取得し適切に表示させる必要があります。

第7章 システム関連

1. メモリ

Aqualeadでは独自のメモリ管理システムを持っています。 このメモリ管理システムではメモリを256バイト以下とそれ以外にわけ、 小さなメモリは後ろから、大きなメモリは前から確保します。 小さなメモリは、大きなメモリとは違い2kバイト単位のブロックで確保し、その中にぴったりつめて確保します。

このため、ブロックの管理領域を除けば、小さなメモリを大量に確保しても余分なメモリは使わず、 また空き領域の検索を行う必要もないため非常に高速に確保、解放を行います。 同時に、ブロックのサイズは固定のため、隙間が開くこともありません。 ですので、Aqualeadではメモリ確保、解放に関しては速度、効率を気にせずに使用することが可能です。

メモリの確保には、ALMallocを使うか、オーバーライドしたnewを使用します。 この時、newの代わりにALNewを使用すると、確保したソースの位置をメモリブロックに記録し、 メモリダンプ時に参照が可能になります。

ただし、256バイト以下の小さなメモリには、コメント領域や管理領域がないのでその情報は記録しません。 解放にはALFreeかdeleteを使います。 ALMallocで確保した場合にはALFreeを、newやALNewで確保した場合はdeleteで解放してください。

2. プロパティ

プロパティ(ALProp)とはAqualeadの特徴の1つでもあるデータ駆動を実現するためのシステムで、 各種クラスのパラメータを文字列ベースで検索し、取得・設定するものです。 Javaや.Netのリフレクションに似ています。 モーションなどはこのプロパティを使用して実現しているため、大半の要素がモーション可能となっています。

2.1. 使用可能なクラス

プロパティを使用可能なクラスは、ALPropSetをスーパークラスとするクラスです。 アップデータや描画ノードなどが含まれます。 ALPropSetを自分で生成することにより、ユーザープロパティを作ることが出来ます。 既存の描画ノードなどにも、追加する形でユーザープロパティを追加出来ます。 追加したユーザープロパティは、システムで使用するプロパティとの違いは無いので、モーションなどから使用することが出来ます。

2.2. プロパティクラス

パラメータと1対1に対応するプロパティのクラスはALPropです。 これは、通常ポインタを使わずクラスのコピーを使用する点に注意してください。 内部には4バイト*2の8バイトでプロパティの実体となるクラスへのポインタと、 そのデータを保持するALPropSetへのポインタとの、2つの組み合わせになります。

このような仕組みのため、取得したプロパティは解放する必要はありません。

2.3. 取得方法

プロパティを取得するには、ALPropSetのサブクラスで、主にFindProp系の関数を使用します。 FindProp系の関数とは、FindPropByName、FindPropByCaption、FindPropの3種で、どれも文字列1つを引数に取ります。

各種プロパティはNameと呼ぶアルファベットの識別子とCaptionと呼ぶ日本語名称の2つを持ちます。 FindPropByNameではNameのみを、FindPropByCaptionではCaptionのみを、FindPropでは両方が検索対象になります。 通常、FindPropのみを使えば十分ですが、どちらか一方だけを使うのがわかっている場合は該当する関数を使った方が若干高速になります。

ALNode *nd = ALNode::Create();
ALProp ppr = nd->FindPropByName("Position");
ALProp spr = nd->FindPropByCaption("スケール");

FindProp系関数は必ずALPropを返しますが、該当するプロパティが見つからないこともあります。 その場合、取得したALPropは無効になります。 取得したALPropが有効かどうかはALPropのIsNull関数で調べることが出来ます。

ALProp dpr = nd->FindProp("Dummy");
if( dpr.IsNull() ){
//Dummyという名前のプロパティはないので、dprは無効
};

また、全てのプロパティを列挙したい場合などは、インデックスアクセスも可能です。 GetPropCount関数でそのALPropSetのプロパティの数を取得し、GetProp関数で取得が出来ます。

Uint32 pcnt = nd->GetPropCount();
for( Uint32 i=0; i<pcnt; i++ ){
	ALProp pr = nd->GetProp( i );
	~~
};

2.4. プロパティの持つ情報

プロパティは前述のようにNameとCaptionを持ちます。 プロパティからそのNameとCaptionを取得するには、それぞれGetName関数とGetCaption関数を使用します。

const char *n = pr.GetName();
const char *c = pr.GetCaption();

また、プロパティはそれぞれ型を持ち、それをGetType関数を用いて取得することも可能です。 型は、ALPROP_VALUE_TYPEとして定義しています。 代表的なものでは、 ALPROP_VALUE_TYPE_INT、ALPROP_VALUE_TYPE_FLOAT、ALPROP_VALUE_TYPE_BOOLなどがあります。

if( pr.GetType() == ALPROP_VALUE_TYPE_FLOAT ){
//このプロパティは実数型
};

その他、数値型プロパティ限定ですが、GetMin関数とGetMax関数を用いて最小値、最大値を求めることが出来ます。 ただし、物によっては定義しておらず、その場合はどちらも0を返します。 なお、このMinMaxは情報としては取得されますが、書き込み時にチェックなどはしません。 主にツールなどでの値チェックに使用します。

同様に、浮動小数点の場合には、GetPrecisionCount関数で少数以下の推奨桁数の取得も出来ます。 これも定義していない場合には0が返ります。 主にデバッグ出力やツール時に使用します。

2.5. 値の読み書き

プロパティの値を読み書きするには、GetAsIntやSetAsBoolなどのGetAs系関数とSetAs系関数を使用します。 取得や設定する場合は、型は厳密に一致している必要はありません。 変換不可能なものはAssertになりますが、変換可能なものはその型に変換して読み書きします。 使用出来る種別は、String、Int、Float、Bool、Vector、AngleVectorの6種類です。

pr.SetAsInt( 2 );
if( pr.GetAsBool() ){
	~~
};

また、これで対応出来ない型はポインタを利用して読み書きします。 読み込みにはGetValue関数とGetValueCopy関数があり、書き込みにはSetValue関数があります。 どちらもプロパティの内部データをそのまま使用するので、 予めプロパティの型をチェックして処理を行う必要があります。

取得にはGetValue関数とGetValueCopy関数がありますが、GetValue関数はデータへのポインタを返します。 このポインタにおける先のデータは一時的なデータですので、速やかにコピーする必要があります。 またこのポインタはconstポインタですので、代入先もconstポインタである必要があります。

GetValueCopyは予めワークを用意し、そこにデータを書き込みます。 このワークサイズはプロパティで使用しているサイズ分必要になります。 プロパティで使用しているデータのサイズは、GetDataSize関数で取得出来ます。

ALNode *nd = ALNode::Create();
ALProp apr = nd->FindPropByName("Alpha");
if( apr.GetType() == ALPROP_VALUE_TYPE_FLOAT ){
    //a1と*a2は同じ値が入る
    float a1 = *static_cast< const float * >( apr.GetValue() ); 
    float *a2 = new float[ apr.GetDataSize() / sizeof( float ) ];  //実際は、Float型なら4バイト固定なのでサイズを取得する必要は無い
    apr.GetValueCopy( a2 );
    float a3 = 0.5f;
    apr.SetValue( &a3 );
    delete[] a2;
};

2.6. ユーザープロパティ

プロパティはシステムで用意しているプロパティだけではなく、ユーザーアプリケーションで生成して使用することも出来ます。 ユーザープロパティは、ALPropSetやそのサブクラスで用意しているUserDataに直接アクセスするもの、 ALPropSetやそのサブクラスを継承して作ったユーザークラスのメンバ変数に直接アクセスするもの、 読み書き用のコールバック関数を用いてアクセスするものの3種類があります。

前者2つはoffsetofマクロを使用し、UserDataの先頭からのオフセットと、クラス先頭からのオフセットを使用してメモリを直接書き換えます。 そのため、非常に簡単にプロパティを追加出来るかわりに、値のチェックは出来ず、また必ずメモリ上の実体を持つ必要があります。 ただし、書き込み直後のコールバックだけは追加することは可能です。

2.6.1. UserDataプロパティ

UserDataを使用するプロパティを生成するには、ALPropSetのAddUserIntPropやAddUserFloatPropなどのAddUser~~Prop関数を使用します。 現在使用出来る型は、Int、Int16、Int8、Float、Bool、String、Vector、AngleVector、Angle、Color、Rectです。

数値型は全て符号付です。符号無しの数値型プロパティは存在しません。 それぞれ、メモリ上の型と一致している必要がありますが、 Stringのみ文字列ワークをそのまま持つValueStringと、文字列への参照を持つRefStringの2つに分かれます。

引数は第3引数までは共通で、Name、Caption、UserDataからのオフセットを指定します。これらは必須です。 以降は全てデフォルト値があるので省略可能です。 その後、数値型であれば最小値、最大値、実数型であればそれに追加して桁数を指定します。 Bool型はBit指定も可能です。なお、Boolは8ビット型とみなされるので、指定出来るBitは0~7のみです。 文字列の場合は、最大長さも指定出来ます。

終わり2つの引数には、書き込みが終わったあとのコールバック関数と、そのコールバック関数用のTagが指定出来ます。 Tagは32ビット整数で、コールバック引数の引数として渡します。 コールバック関数を共有したい場合の識別用に使用してください。返り値として生成したALPropが返ります。必要なければ保持する必要はありません。

なお、使用する場合は当然ながらUserDataが有効なポインタを保持している必要があります。

struct TestUserData
{
	ALVector	Vector;
	char		ValueString[16];
	char *		RefString;
	Sint32		Int32;
	Uint8		Bool;
};
void PropCallback( const ALProp & prop, Sint32 tag )
{
	ALPrintf("PropWrite %d\n", tag );
};

	TestUserData udata;
	ALPropSet *ps = ALPropSet::Create();
	ps->SetUserData( &udata );
	ALProp vpr = ps->AddUserVectorProp( "Vector","ベクタ型", offsetof( TestUserData, Vector ), 4 );
	ALProp vspr = ps->AddUserValueStringProp( "VString", "値型文字列", offsetof( TestUserData, ValueString ), sizeof( udata.ValueString ) );
	ALProp rspr = ps->AddUserRefStringProp( "RString", "参照文字列", offsetof( TestUserData, RefString ) );
	ps->AddUserIntProp( "Int", "数値型", offsetof( TestUserData, Int32 ), 0, 100, PropCallback, 5 );
	ps->AddUserBoolProp( "Bool", "ビット型", offsetof( TestUserData, Bool ), 2, PropCallback, 5 );

2.6.2. クラスプロパティ

ALPropSetやそのサブクラスを継承して独自のクラスを作り、そのメンバ変数をプロパティにする場合は、 ALPropSetのAddClassIntPropなどのAddClass~Prop関数を使用します。 使用出来る型や使い方などはUserDataプロパティと同一です。

class TestClass : public ALPropSet
{
public:
	ALVector	Vector;
	char		ValueString[16];
	char *		RefString;
	Sint32		Int32;
	Uint8		Bool;
};

void PropCallback( const ALProp & prop, Sint32 tag )
{
	ALPrintf("PropWrite %d\n", tag );
};

	TestClass *tc = new TestClass;
	ALProp vpr = tc->AddClassVectorProp( "Vector","ベクタ型", offsetof( TestClass, Vector ), 4 );
	ALProp vspr = tc->AddClassValueStringProp( "VString", "値型文字列", offsetof( TestClass, ValueString ), sizeof( tc->ValueString ) );
	ALProp rspr = tc->AddClassRefStringProp( "RString", "参照文字列", offsetof( TestClass, RefString ) );
	tc->AddClassIntProp( "Int", "数値型", offsetof( TestClass, Int32 ), 0, 100, PropCallback, 5 );
	tc->AddClassBoolProp( "Bool", "ビット型", offsetof( TestClass, Bool ), 2, PropCallback, 5 );

2.6.3. コールバックプロパティ

プロパティを定義する際、メモリ上に直接存在しない値を読み書きしたり、 書き込む前に厳密にチェックがしたい場合などは、コールバックプロパティを使用します。 これは、変数のアドレスではなく、読み書き用のコールバック関数を指定してそこで処理をするプロパティです。

3. タスク

タスク(ALTask)とは、ゲーム中の比較的大きな実行単位を管理するクラスで、 例えばタイトルタスク、バトルタスク、オプション画面タスクのようなものを作って使います。

このタスクにはALResourceを使ったリソースの自動ロードのほか、 そのタスク内で新規に生成したテクスチャやアーカイブなどの保持をまかせ、タスク終了時に自動開放させることが出来ます。 このタスクはタスクマネージャ(ALTaskManager)に登録し、タスク終了時のリターン値によって次にどのタスクを起動するかを選べます。 タスクマネージャは複数生成が可能なので、ゲーム中のデバッグメニューや、ポーズメニュー内のモード切り替えなどにも利用が出来ます。

3.1. 基本的な使い方

タスクはタスクマネージャとセットで使用します。 タスクマネージャはCreate関数で生成し、Destroy関数で解放します。 タスクの生成には、タスクマネージャのAdd関数を使用します。 通常、タスクごとに区別するためのIDと、メインとなる関数を渡します。 IDは1以上の数値を使用します。 デバッグメニューなど、簡易的に使用する場合はIDを使わないことも可能で、 その場合、メインとなる関数のポインタがIDとして使用します。

その後、タスクマネージャに対し、Start関数を呼び出し、そこで最初に起動するタスクのIDを渡すと動作を開始します。 Startを実行すると、各タスクはタスクマネージャのアップデート関数から呼び出されます。 タスクマネージャはアップデータを継承して作られているので、 実行タイミングはアップデートプライオリティの指定に依存します。

タスクのメイン関数では、リターン値にタスクIDを指定すると、次はそのタスクに切り替わります。 0を返すとその後次のタスクは呼び出さずにタスクマネージャは停止します。

static ALTASK_ID	MAIN_TASK_ID = 1;
static ALTASK_ID	TITLE_TASK_ID = 2;

ALTASK_ID GameMain( ALTask * task, size_t flags)
{
	~~
	return TITLE_TASK_ID;
}

ALTASK_ID TitleMain( ALTask * task, size_t flags)
{
	~~
	return MAIN_TASK_ID;
}

ALTaskManager *tm = ALTaskManager::Create();
tm->Add( MAIN_TASK_ID, GameMain );
tm->Add( TITLE_TASK_ID, TitleMain );
tm->Start( TITLE_TASK_ID );

3.2. タスクでのシーン

タスクのデフォルトでは内部で自動的にシーンを生成して、そのシーンをデフォルトにして起動します。 これにより、タスク内で生成したアップデータや描画ノードをタスク終了時に自動開放させることが出来ます。 使用しているシーンは、GetScene関数で取得が可能です。 なお、デフォルトシーンの切り替えはこのタスク関数実行時のみではなく、グローバルに変更するので注意してください。

ALTASK_ID GameMain( ALTask * task, size_t flags)
{
	ALScene *sce = task->GetScene();
	ALScene *defsce = ALScene::GetDefaultScene();	//自動的にデフォルトになるため、sceとdefsceは等しい

}	//この直後taskのシーンは開放される

このシーンの自動生成は使用しないことも可能です。 その場合は、そのタスクを起動する前にSetUseScene関数にfalseを指定します。 このとき、タスクのGetScene関数はNULLを返します。

タスクのコールバック起動後に設定してもすでに生成済みのシーンには影響はありません。 その時は、次回コールバック起動時から反映します。 この設定はIsUseScene関数で取得することも出来ます。

ALTaskManager *sm = ALTaskManager::Create();
ALTask *task = sm->Add( MAIN_TASK_ID, GameMain );
task->SetUseScene( false );		//これでこのタスクは自動生成シーンを使用しない

ALTASK_ID GameMain( ALTask * task, size_t flags)
{
	bool fl = task->IsUseScene();		//上でfalseに設定しているので、falseが返る
	task->SetUseScene( true );			//次回このGameMainが呼ばれたときは自動生成シーンを使用する
}

3.3. シンボルによるタスク選択

次に起動するタスクを選ぶには、関数リターン時にタスクIDを直に指定するだけではなく、 タスクごとにシンボルを定義し、それによりタスクを選ぶことが可能です。 例えば、NEXT=100、PREV=101などの値をユーザーが決め、 それぞれのタスクのNEXTとPREVで起動するタスクを決め、順番起動や戻りなどを実現出来ます。

実現するにはマネージャへの静的な変更と、コールバック関数による動的な変更があります。 静的な変更を登録するには、タスクマネージャのSetNextTask関数を使います。 引数は4つあり、今起動中のタスクID、その時のリターン値のタスクID、その場合次に起動するタスクID、そのときの起動パラメータです。 起動パラメータは省略可能で、省略時は0となります。

static ALTASK_ID	MAIN_TASK_ID = 1;
static ALTASK_ID	TITLE_TASK_ID = 2;
static ALTASK_ID	TASK_NEXT = 100;

ALTaskManager *tm = ALTaskManager::Create();
tm->Add( MAIN_TASK_ID, GameMain );
tm->Add( TITLE_TASK_ID, TitleMain );
//タイトルタスクでTASK_NEXTを返すと、メインタスクを起動パラメータ5で起動する設定
tm->SetNextTask( TITLE_TASK_ID, TASK_NEXT, MAIN_TASK_ID, 5 );

コールバック関数を利用する場合は、

typedef void (*ALTASK_MANAGER_CHANGE_FUNC)( ALTaskManager * taskmanager, ALTASK_ID resultid, ALTASK_ID *nextid, size_t *param );

形式のコールバック関数をタスクマネージャにSetChangeFunc関数で設定します。 このコールバック関数はタスクが終了するごとに呼ばれ、必要であれば次の起動タスクを切り替えたり、起動パラメータを設定します。

直前に起動していたタスクIDは、タスクマネージャのGetCurrentTaskIDで取得します。 そのタスク終了時のリターンタスクIDはresultidに格納されています。 nextidとparamは次の変更候補の値がすでに入っています。 この値は、設定があれば前述のSetNextTaskの結果も含みます。 変更したい場合、それぞれの値を書き換えます。必要がなければ何もせずにリターンします。

tm->SetChangeFunc( TaskChangeFunc );


void TaskChangeFunc( ALTaskManager * taskmanager, ALTASK_ID resultid, ALTASK_ID *nextid, size_t *param )
{
  //メインタスク終了時にTASK_NEXTを返した
  if( taskmanager->GetTaskID() == MAIN_TASK_ID && resultid == TASK_NEXT ){
    //次のタスクをタイトルタスクに変更
    *nextid = TITLE_TASK_ID;
    //起動パラメータはデフォルトの0のまま変更しない
  };
  //それ以外はそのまま
}

3.4. リソースの管理

タスクでは、使用するリソースをあらかじめ登録しておくと、タスク起動時に自動的にそのリソースをロードし、 タスク終了時にそのリソースをアンロードすることが出来ます。

あらかじめ登録するには、タスクを登録し起動する前にAddResource関数を実行します。 必要なリソースはいくつでも登録可能です。 あらかじめ登録しておく以外にも、タスクの処理が開始してから必要なリソースを追加ロードすることが可能です。 その場合は、タスク関数起動中にLoadResource関数でリソースをロードします。 追加ロードしたリソースもあらかじめ登録したリソースと同様自動アンロードします。

static ALRESOURCE_ID	MAIN_RESOURCE_ID1 = 10;
static ALRESOURCE_ID	MAIN_RESOURCE_ID2 = 11;

ALTaskManager *tm = ALTaskManager::Create();
ALTask *task = tm->Add( MAIN_TASK_ID, GameMain );
task->AddResource( MAIN_RESOURCE_ID1 );

ALTASK_ID GameMain( ALTask * task, size_t flags)
//この時点でMAIN_RESOURCE_ID1はロード完了済み
{
	task->LoadResource( MAIN_RESOURCE_ID2 );	//この場ですぐMAIN_RESOURCE_ID2をロードする
}
//ここでMAIN_RESOURCE_ID1、MAIN_RESOURCE_ID2が共に解放される

3.5. 各種データの自動解放

タスクではリソースを使った各種自動解放のほか、テクスチャやアーカイブなどの単体データでの自動解放も行うことが出来ます。 使用出来るデータはTexture、Archive、Stream、Font、Object、Nodeの6種類です。 これらのデータを生成し初期化が終わり、あとは解放するだけになった場合、Consign~~関数で解放をタスクに依頼することが出来ます。

ALTASK_ID GameMain( ALTask * task, size_t flags)
{
	ALTexture *tex = ALTexture::Create( 0x100 );
	task->ConsignTexture( tex );
	~~~
}
//ここでtex->Release()が実行される。

4. スレッド

Aqualeadではスレッドをサポートします。 提供する機能はシンプルで、開始と停止を除けば、同期関数呼び出し位しかありません。

Aqualeadではメモリ確保以外はスレッド非対応なため、Aqualeadの関数を呼び出す場合は同期関数呼び出しを使用します。 同期関数呼び出しは、指定した関数をメインスレッド内で実行します。 関数の登録自体はすぐに完了するため、返り値をとりたい場合などは自前で同期処理を入れる必要があります。 キューがあるため、複数関数が登録出来ます。

スレッドは実行関数を登録して実行します。 終了はTerminate関数を使用しますが、終了フラグを立てるのみとなるので、スレッド内部で自発的に終了する必要があります。

5. スクリプト

Aqualeadではスクリプト言語Squirrelをサポートします。 スクリプトのインターフェースはスクリプトクラスを基本とした抽象的なものとなります。 そのため、細かな制御は出来ない代わりに非常に簡単に使用することが出来ます。

5.1. 生成

生成にはCreate関数を使用します。 生成時の引数には親となるインスタンスと、スタックサイズを指定出来ます。 親のインスタンスを指定せずにALSquirrelを生成した場合、完全に独立した仮想マシンを生成します。 初期化に少し時間がかかり、メモリも消費する代わりに他のインスタンスと干渉することがありません。

親を指定して生成した場合、親のALSquirrelの仮想マシン中に、グローバルな環境を継承したまま独立した環境を作ります。 この場合、初期化時間やメモリ使用量はわずかになり、ある程度独立した環境を作ります。 特別な理由がない限りは、生成する仮想マシンはひとつにし、親を指定して生成することをお勧めします。

親を指定した場合、グローバル変数や関数などを設定するとその新たなインスタンスのグローバル環境に設定しますが、 読み込み時このインスタンスのグローバル環境に該当するものがない場合は、親のグローバル環境を続けて読みに行きます。 ですので、すべての環境で使用したい関数などは一番親となるALSquirrelのインスタンスに設定し、 特定の環境のみ必要な関数などは、そのインスタンスのみに設定することをお勧めします。

ALSquirrel *psq = ALSquirrel::Create();		//親となるインスタンス。仮想マシンを生成する。
ALSquirrel *csq = ALSquirrel::Create( psq );	//子となるインスタンス。仮想マシンを共有する。
psq->AddGlobalFunc( "TestFunc1", TestFunc1 );	//親にグローバル関数を登録。psq、csqどちらからも使用出来る。
csq->AddGlobalFunc( "TestFunc2", TestFunc2 );	//子にグローバル関数を登録。csqからのみ使用出来る。
csq->Release();			//子のみ解放。TestFunc2の登録は解除される。
psq->Release();			//親も解放。

子インスタンスを生成するときは、Defaultの指定を使うことが出来ます。 SetDefault関数で親インスタンスを設定し、引数を省略して以降ALSquirrelのインスタンスを生成した時に、そのDefaulutを自動的に使用します。

プログラムの頭で親インスタンスを生成し、それをDefaultとして設定しておくと、以降は自動的にそのインスタンスを親として使用するようになります。 SetDefaultをした時点で参照カウントが増えるので、直後にReleaseをしても問題ありません。 その場合、SetDefaultが変更したときに、そのインスタンスが解放されることになります。

ALSquirrel *psq = ALSquirrel::Create();		//親となるインスタンス。仮想マシンを生成する。
ALSquirrel::SetDefault( psq );		//親インスタンスをデフォルト設定
psq->Release();	//直接参照が必要なければ、Releaseしてもかまわない。
ALSquirrel *csq = ALSquirrel::Create();	//親インスタンスを指定しなくても子インスタンスになる。。
csq->Release();
ALSquirrel::SetDefault( NULL );		//ここで親インスタンスが解放される。

5.2. 関数の登録

関数は、グローバルな関数と、クラス関数を登録することが出来ます。 グローバル関数はそのインスタンス内で特に指定無しで使用出来る関数であり、 クラス関数はALPropSetのサブクラスをベースクラスとして使用する関数です。

それぞれ、ALSCRIPT_ARG_0やALSCRIPT_RESULT_ARG_0などで定義している型のコールバック関数を使用します。 各定義は以下のようになります。

typedef void ( *ALSCRIPT_ARG_0 )();
typedef void ( *ALSCRIPT_ARG_1 )( const ALVariant & arg0 );
typedef void ( *ALSCRIPT_ARG_2 )( const ALVariant & arg0, const ALVariant & arg1 );
typedef void ( *ALSCRIPT_ARG_3 )( const ALVariant & arg0, const ALVariant & arg1, const ALVariant & arg2 );
typedef void ( *ALSCRIPT_ARG_4 )( const ALVariant & arg0, const ALVariant & arg1, const ALVariant & arg2, const ALVariant & arg3 );
typedef void ( *ALSCRIPT_ARG_5 )( const ALVariant & arg0, const ALVariant & arg1, const ALVariant & arg2, const ALVariant & arg3, const ALVariant & arg4 );
typedef void ( *ALSCRIPT_ARG_ARRAY )( const ALVariantArray & args );

typedef void ( *ALSCRIPT_RESULT_ARG_0 )( ALVariantArray & result );
typedef void ( *ALSCRIPT_RESULT_ARG_1 )( ALVariantArray & result, const ALVariant & arg0 );
typedef void ( *ALSCRIPT_RESULT_ARG_2 )( ALVariantArray & result, const ALVariant & arg0, const ALVariant & arg1 );
typedef void ( *ALSCRIPT_RESULT_ARG_3 )( ALVariantArray & result, const ALVariant & arg0, const ALVariant & arg1, const ALVariant & arg2 );
typedef void ( *ALSCRIPT_RESULT_ARG_4 )( ALVariantArray & result, const ALVariant & arg0, const ALVariant & arg1, const ALVariant & arg2, const ALVariant & arg3 );
typedef void ( *ALSCRIPT_RESULT_ARG_5 )( ALVariantArray & result, const ALVariant & arg0, const ALVariant & arg1, const ALVariant & arg2, const ALVariant & arg3, const ALVariant & arg4 );
typedef void ( *ALSCRIPT_RESULT_ARG_ARRAY )( ALVariantArray & result, const ALVariantArray & args );

このように、大きく返り値あり、なしの場合、それから引数が0~5、もしくは可変の組み合わせで14種類存在します。 引数に使用しているALVariantとALVariantArrayはこの後で解説します。

グローバル関数とクラス関数では使用するコールバック自体は一緒ですが、 クラス関数の場合は第1引数にそのクラス自体を格納します。 引数が5個までならそのまま使用出来ますが、6個以上になる場合はALVariantArrayを使用します。 引数が5個以下でもALVariantArrayは使用可能です。

スクリプト側の呼び出し引数個数と厳密に一致する必要はなく、引数が足りない場合はargにNull値が入ります。 実際の登録は以下のように、スクリプトで使用する名前文字列と、関数を渡します。

static void Func1( const ALVariant & arg0 );
{
	~~
};

sq->AddGlobalFunc( "Func1", Func1 );

クラス関数の場合は対象となるクラスを第1引数に渡します。 クラスの識別は内部のプロパティ管理クラスで行うため、プロパティを一切登録していないクラスは親クラスと同一と判定します。

また、そのプロパティ管理クラスはCloneをしたり、SharePropsを行わないと共有しません。 独自のクラスにクラス関数を登録する場合は注意してください。 なお、ALNodeやALSpriteなどのクラスにそのままクラス関数を登録することも可能ですが、それはすべてのインスタンスで共有するので注意してください。

ALPropSet *ps = ALPropSet::Create();
ps->AddUserIntProp("Prop1",~~);		//この時点で、内部にプロパティ管理クラスが作られ、独立クラスと判定するようになります。
sq->AddClassFunc( ps, "ClassFunc1", ClassFunc1 );	//psでのみ使用出来る関数を登録
ALPropSet *ps2 = ps->Clone();	//ps2でもClassFunc1が使用出来るようになる。
ALPropSet *ps3 = ALPropSet::Create();
ps3->ShareProps( ps );		//これでps3でもClassFunc1が使用出来るようになる。
ALPropSet *ps4 = ALPropSet::Create();
ps4->AddUserIntProp("Prop1",~~);		//psと同じプロパティを持つが、内部では独立しているのでClassFunc1は使用出来ない。

ALSprite *sp = ALSprite::Create();
sq->AddClassFunc( sp, "ClassFunc2", ClassFunc2 );	//すべてのALSpriteでClassFunc2が使用出来るようになる。

5.3. 変数

スクリプトと値をやり取りする場合は、ALVariantというクラスを経由します。 これには、論理型、整数型、実数型、文字列型、ALPropSet型、配列型、辞書型の値を格納出来ます。 スクリプトクラスに値を渡す場合は、ALVariantクラスのコンストラクタを自動的に呼び出すので、 明示的にALVariantを呼び出すことなく値を直接記述出来ます。

sq->Call( "Func1", 1, 2.4f, "String" );	//Func1という関数を、整数1、実数2.4、文字列Stringの引数3つで呼び出す。
sq->Call( "Func1", ALVariant( 1 ), ALVariant( 2.4f ), ALVariant( "String" ) );	//上の行はこれの省略形

返り値などでは、IsBoolやIsFloat、もしくはGetType関数などで型を調べ、 適切なGetAsBoolやGetAsFloatなどの関数を呼び出す必要があります。 数値型と実数は相互変換可能ですが、それ以外は一致しない型で取り出すとAssertが発生します。 また、関数呼び出しなどで引数が足りない場合などは、IsNoneがtrueになります。

static void Func1( const ALVariant & arg0 );
{
	if( arg0.IsInt() ){
		ALPrintf("整数 %d\n", arg0.GetAsInt() );
	} else if( arg0.IsFloat() ){
		ALPrintf("実数 %f\n", arg0.GetAsFloat() );
	} else if( arg0.IsNone() ){
		ALPrintf("引数なし\n" );
	};
}

引数の数が6つ以上になったり、可変の場合はALVariantArrayクラスを使用します。 これはALVariantの配列で、GetCountで個数を取得し、GetでALVariantを取り出します。

static void Func2( const ALVariantArray & arg )
{
	for( Uint32 i=0; i<arg.GetCount(); i++ ){
		if( arg.Get(i).IsInt() ){
			ALPrintf("%d\n", arg.Get(i).GetAsInt() );
		};
	};
};

5.4. ロードと実行

スクリプトはファイルから読み込むか、文字列でスクリプトを指定して実行します。 ファイルから読み込むにはLoad関数を使用します。 スクリプト内に関数定義があれば、グローバル関数として登録します。

sq->Load( "script.nut" );	//script.nutというスクリプトをロードして実行

文字列でスクリプトを実行するにはExec関数を使用します。

ALVariantArray r;
r = sq->Exec( "return 1+2" );	//このスクリプトを直接実行

Exec関数では帰り値を取得することが可能です。 帰り値はALVariantArrayで取得します。帰り値がなければ個数は0です。

登録したグローバル関数や、スクリプト内で定義した関数を呼び出すにはCall関数を使用します。 第1引数は関数名で、以降は0~5個の引数、もしくはALVariantArrayを指定します。

返り値はALVariantArrayになっており、返り値があればこの個数が1となり、1つ目の要素に返り値が入ります。 スクリプト内で返り値に配列を指定した場合は、その配列が展開され複数の帰り値として取得出来ます。 もし、配列をそのまま取得したい場合は、2重の配列にする必要があります。

ALVariantArray r;
r = sq->Call( "Func1", 1, 2.4f, "String" );
for( Uint32 i=0; i<r.GetCount(); i++ ){
	if( r.Get(i).IsInt() ){
		ALPrintf("%d\n", r.Get(i).GetAsInt() );
	};
};

5.5. クラスの使用

前述したクラス関数などを使用する場合は、ALPropSetがベースとなります。 ALPropSetのサブクラスであるALUpdaterやALNodeなどは、すでにC++の関数と同名の関数が使用可能になっています。 現在のところ、ALPropSetのサブクラス以外では、Aqualeadとのやり取りにはクラスのやり取りは行えません。

また、スクリプト内ではAqualeadのプロパティはそのままの形で読み書きが可能です。 プロパティは、コントローラやコリジョンのプロパティもそのまま使用出来ます。

アプリケーションではクラス関数を登録しなくても、プロパティの追加だけでも独自の機能を追加することが可能です。 クラスを使用するには大きく2種類の方法があります。 1つは引数としてクラスを渡す方法、2つ目はBaseClassの設定です。 引数として渡せば、その変数はクラスのインスタンスとしてそのまま使用出来ます。

ALNode *nd = ALNode::Create();
nd->SetPos( 2, 3 );
sq->Exec("function Func3( node ){ return node.Position.x + node.Position.y }"); //関数の登録
ALVariantArray r;
r = sq->Call( "Func3", nd );
ALPrintf("%f\n", r.Get(0).GetAsFloat() ); //2+3で5が表示される。

BaseClassを指定すると、そのSquirrelのインスタンス内ではC++でのクラス内の関数のように、暗黙のthisにそのクラスが使用されるようになります。 つまり、そのBaseClassで指定したクラス変数やプロパティがそのまま使用出来ます。 なお、スクリプト内ではthisキーワードでそのBaseClassにアクセスすることも可能です。 BaseClassを指定している場合は、関数や変数はそのBaseClassのものが優先されますが、::をつけることでグローバルな変数や関数を優先させることも出来ます。

ALNode *nd = ALNode::Create();
sq->SetBaseClass( nd );
sq->Exec("function Func4(){ return Position.x + this.Position.y }");
ALVariantArray r;
r = sq->Call( "Func4" );
ALPrintf("%f\n", r.Get(0).GetAsFloat() ); //2+3で5が表示される。

このようにBaseClassを指定している場合に、その呼び出された関数内でC++で定義したクラス関数を呼び出すと、第1引数に指定したBaseClassが格納されます。

static void Func5( const ALVariant & arg0, const ALVariant & arg1 )
{
	ALPrintf("%f\n", static_cast< ALNode * >( arg0.GetAsPropSet() )->GetX()+arg1.GetAsFloat() );
};

ALNode *nd = ALNode::Create();
sq->AddClassFunc( nd, "Func5", Func5 );
sq->SetBaseClass( nd );
nd->SetX( 1 );
sq->Exec("Func5( 2 )");		//Func5ではarg0にndがarg1に変数2が渡されるので、PosXと2で3が表示される。

その他のクラスにおける機能として、スクリプト側のユーザー変数があります。 引数指定のクラス、BaseClassの指定両方でクラスのインスタンスに独自の変数を追加出来ます。 追加した変数はその引数やBaseClassが有効の間使用出来ますが、引数として使用する場合は少し工夫が必要になります。 あらかじめ、そのクラスに対しALVariantを生成し、その時にALSquirrelのインスタンスを指定します。 こうすることにより、このALVariantかそのインスタンスのスクリプト側の環境を保持します。 BaseClassの指定にはクラスそのままの他、そうやって指定したインスタンスも渡せます。

ALNode *nd = ALNode::Create();
ALVariant ndarg( nd, sq );	//ALSquirrelのインスタンスも一緒に指定してScriptArgを生成
sq->Exec("function Func6( arg ){ arg.NewVal <- 5 }");	//NewValというスクリプト側のユーザー変数を追加
sq->Call( "Func6", ndarg ); //引数にはndではなく、ndargを渡すことに注意
sq->SetBaseClass( ndarg ); //今度はBaseClassに指定
ALVariantArray r;
r=sq->Exec( "return NewVal" ); //先ほど作ったユーザー変数を取得

このインスタンスのユーザー変数は、ALSquirrelのインスタンスを一緒に指定したALVariantに保存されることを忘れないでください。 そうしない場合、CallやExecから抜けたときに破棄されます。 BaseClassに指定した場合は、そちらが保持するので切り替えない限りはそのユーザ変数は保持されます。

5.6. ファイバ

SquirrelでもAqualeadと同じようにファイバが使用可能です。 ファイバはCreateFiber関数で生成します。 ファイバはALSquirrelのサブクラスですので、ALSquirrelの機能はそのまま使用出来ます。 唯一の違いが、関数を中断させ、再開させることが出来る点です。 ファイバ内で実行した関数は、suspend関数を実行することにより呼び出し元に戻ります。 これはAqualeadのALYieldのようなものです。 その時、引数をひとつ指定することも出来、それが呼び出し側の帰り値になります。 省略や、配列を使用した複数指定も可能です。 再開するには、Resume関数を実行します。 この時は引数を省略するか、1つ指定することが出来ます。 指定した引数がsuspend関数の帰り値になります。

ALSquirrelFiber *sfb = sq->CreateFiber();
sfb->Exec("function Func7(){ local n = suspend( 1 ); suspend( n ) }");
ALVariantArray r;
r = sfb->Call( "Func7" ); //rにはsuspendの引数1が返る
r = sfb->Resume( 2 ); //スクリプトには2が渡されるので、nは2になる。
sfb->Resume(); //スクリプトは終了しているのでなにも行われない。

なお、ファイバが関数の実行を終了したかどうかはIsTerminated関数で調べることが可能です。

5.7. コントローラ

Squirrelを使用したコントローラは3種あります。 基本のALSquirrelController、ファイバを使用したALSquirrelFiberController、イベント制御用のALSquirrelEventControllerです。 まずALSquirrelControllerを説明します。 このコントローラは主にモーションと組み合わせて使用し、モーションの特定フレームでスクリプトを実行させることが出来ます。 sodに以下のように記述すると、ALSquirrelControllerが生成されます。

[1]
Type=Node
Controller=Squirrel

その後、smtに以下のように記述すると、指定したフレームでそのスクリプトが実行されます。

[1:0]
Squirrel.Script=FuncArg1()

[1:2]
Squirrel.Script=FuncArg2(1)
Squirrel.Script=FuncArg3(2,3)

この例のように、同一フレームに複数の記述があってもかまいません。 ;でつなぎ1行に記述しても結果は同じです。 このコントローラだけでなく、すべてのコントローラに共通ですが、このコントローラが登録されたノードがBaseClassとして設定されます。 ですので、呼び出したい関数はそのノードのクラス関数かグローバル関数として登録しておく必要があります。

5.8. ファイバコントローラ

これは前述における通常のコントローラとは違い、1つのスクリプトをファイバを利用して実行するコントローラです。 このファイバコントローラでスクリプトを起動した場合、suspendで値を指定すると、それはSleep時間とみなされます。 suspend(10)と指定すると、10フレーム待つという指示になります。 Resume時に引数を渡すことは出来ません。 SetNodeSuicide関数でtrueを指定すると、スクリプト終了と同時にそのノードをDestroyさせることが出来ます。

5.9. イベントコントローラ

イベントコントローラとは、AqualeadのALNode系の各種イベントをSquirrelで実行させるものです。 現在Widget系イベントのみが実装されています。 各種イベント+"Event"の名前の関数が定義されていた場合、そのイベント発生時にそのスクリプトが呼ばれます。 例えば、DestroyEventという関数を定義しておくと、対象ノードがDestoyされたときにその関数が呼ばれます。

第8章 入力関連

1. パッド

パッドは、一般的なデジタル入力デバイスからの入力を受け取ります。 デジタル入力のほか、アナログ入力があればそれも使用出来ます。 デジタル入力は入力値そのままのほか、押した瞬間、離した瞬間、リピート、押している時間を取得出来ます。 リピートに関しては、リピート開始までの時間、リピート間隔も指定出来ます。 また、パッド入力はファイルに記録し、後でそれを再生することにより入力を再現することが出来ます。

1.1. 基本的な使い方

パッドからの入力は、パッドのインスタンスを取得し、そのインスタンスから値を取得します。 インスタンスはすでに生成済みなので生成する必要はありません。 GetDevice関数でパッドのデバイスを取得します。

第9章 各種ユーティリティ

1. コンテナ

コンテナクラス(ALContainer)とは汎用の名前つきデータ格納クラスです。 数値や文字列、バイナリデータなどを名前つきの配列として格納することが出来ます。 格納したデータはバイナリ形式のほか、XML形式での入出力が可能です。 あくまでも、識別名+データ*nという形式ですので、構造体のようなものは作れませんが、 コンテナの中にコンテナを含めることは可能ですので、ツリー構造を持たせることは可能です。

1.1. 生成と破棄

生成には引数なしのCreate関数を使用します。 破棄にはRelease関数です。 ただし、内部で参照カウント管理されているので、 Releaseされていても他から参照がある場合は削除されません。

ALContainer *cont = ALContainer::Create();
~~
cont->Release();

1.2. 値の登録と取得

コンテナへの値の登録は、識別名と一緒に行います。各種型ごとに追加関数があり、使い方はほぼ一緒です。 同じ識別名へ続けて登録すると、その識別名に値が追加されます。

ALContainer *cont = ALContainer::Create();
cont->AddInt( "Val1", 10 );
cont->AddInt( "Val2", 20 );
cont->AddInt( "Val1", 11 );	//Val1には2つの値がある

このように、Add+型という関数を使用します。使用出来る型は後述します。 コンテナではVal1、Val2という識別名単位の事をItem、Val2の10、11という値をValueと呼びます。 通常はこのように追加していきますが、第3引数を追加してすでに登録した値を置き換えたり、間を空けて値を登録することが出来ます。 間を空けた場合はそれぞれ空の値が追加されます。 空の値とは、数値型なら0、ポインタ型ならNULL、文字列型なら空文字列です。

cont->AddInt( "Val3", 30 );
cont->AddInt( "Val3", 31 );
cont->AddInt( "Val3", 32, 1 );	//1番目の値を置き換える。これにより、(30,32)となる
cont->AddInt( "Val4", 43, 2 );  //0番目、1番目の値が追加されVal4は(0,0,43)となる

取得にはこの識別名をそのまま使う方法と、識別名からそのアイテムのインデックスを取得し、そのインデックスでアクセスする方法があります。 識別名を使う場合、該当するアイテムが無い場合は空の値が返ります。 識別名で値を取得するにはFind+型の関数を、インデックスで取得する場合はGet+型の関数を使用します。 アイテムのインデックス取得はFindItemIndex関数です。

ALContainer *cont = ALContainer::Create();
cont->AddInt( "Val1", 10 );
cont->AddInt( "Val2", 20 );
cont->AddInt( "Val1", 11 );

int v1 = cont->FindInt( "Val1", 1 );	//1番目の値、11が返る
int v2 = cont->FindInt( "Val2" );		//第2引数省略時は0番目の値、20が返る
int v3 = cont->FindInt( "Val3" );		//値が存在しないので、0が返る

int idx1 = cont->FindItemIndex( "Val2" );	//Val2は2番目の要素なので、1が返る
int v4 = cont->GetInt( idx )			//20が返る
int idx2 = cont->FindItemIndex( "Val3" );	//アイテムは存在しないので-1が返る

注意点として、Find系関数では存在しないアイテムでは空の値が返りますが、Get系関数で存在しないアイテムのインデックスを指定するとアサートとなります。

1.3. 使用出来る型

コンテナ型で使用出来る型は以下のとおりです。

  • 論理型

    FindBool、AddBool関数を使用します。

  • 32ビット符号付整数型

    FindInt、AddInt関数を使用します。

  • 32ビット単精度実数型

    FindFloat、AddFloat関数を使用します。

  • 文字列型

    FindString、AddString関数を使用します。

    なお、ShiftJISを使用しますが、XML入出力時はUTF-8に変換されます。

  • バイナリ型

    FindBinary、AddBinary関数を使用します。 登録時はデータへのポインタとサイズを渡します。要素ごとのサイズはそろえる必要はありません。

  • コンテナ型

    FindContainer、AddContainer関数を使用します。

    AddContainer関数で登録されたコンテナは参照カウントが増えるため、元コンテナはすぐにReleaseすることが可能です。

  • プロパティ型

    FindProp、FindSetProp、AddProp関数を使用します。

    これのみ若干特殊で、指定したプロパティにおける現在の値を取得、設定します。

    取得するときも該当するプロパティを予め準備しておく必要があります。

    FindPropではバイナリのポインタを取得しますが、FindSetPropではプロパティにそのまま値を設定します。

1.4. その他のアクセス関数

コンテナにはその他、アイテムが存在するか調べる関数、アイテムの型を調べる関数、アイテムや値の数を取得する関数、 指定したインデックスの識別名を取得する関数、識別名を変更する関数、およびアイテムや値を削除する関数があります。

アイテムの存在確認にはIsExists関数を使用します。 この関数は識別名のアイテムが存在するかどうかだけではなく、そのアイテムの指定したインデックスの値が存在するかを調べることも出来ます。 指定したインデックスの値が存在するかどうかの調査には、値の数を元に判定するため空の要素などがあってもそれは存在するものになります。

ALContainer *cont = ALContainer::Create();
cont->AddInt( "Val1", 10 );
cont->AddInt( "Val2", 20 );
cont->AddInt( "Val1", 11 );

cont->IsExists( "Val1" ); //Va1lは存在するのでtrue
cont->IsExists( "Val1", 3 ); //Va1lの要素は2つしかないのでfalse
cont->IsExists( cont->FindItemIndex( "Val2" ), 1 ); //実質cont->IsExists( "Val2",1 )と同じ

型を調べるにはGetType関数を使用します。識別名とインデックスの両方が使用出来ます。 返り値はALCONTAINER_TYPEです。そのアイテムが存在しない場合はALCONTAINER_TYPE_NONEになります。

ALContainer *cont = ALContainer::Create();
cont->AddInt( "Val1", 10 );

ALCONTAINER_TYPE ct = cont->GetType( "Val1" ); //ALCONTAINER_TYPE_INTが返ります

アイテムの数を取得するにはGetItemCount()関数を、アイテム内の値数を取得するにはGetValueCount関数を使用します。

ALContainer *cont = ALContainer::Create();
cont->AddInt( "Val1", 10 );
cont->AddInt( "Val2", 20 );
cont->AddInt( "Val1", 11 );

int cnt1 = cont->GetItemCount(); //Val1、Val2の2つで2が返ります。
int cnt2 = cont->GetValueCount( "Val1" ); //10、11の2つで2が返ります。

識別名取得はGetName関数を使用します。

ALContainer *cont = ALContainer::Create();
cont->AddInt( "Val1", 10 );
const char *n = cont->GetName( 0 ); //0番目のアイテムの識別子、Val1が返る

識別名変更はRename関数を使用します。変更元は識別子か、アイテムインデックスを使用します。 変更後の識別子がすでに存在したり、元のアイテムが見つからない場合は、falseを返します。

ALContainer *cont = ALContainer::Create();
cont->AddInt( "Val1", 10 );
cont->AddInt( "Val2", 20 );

cont->Rename( "Val1", "Val3" ); //Val1->Val3に変更
cont->Rename( 1, "Val4" ); //Val2->Val4に変更
cont->Rename( "Val3", "Val4" ); //Val4が存在するので失敗

アイテムや値の削除はRemove関数を使用します。識別名やアイテムインデックスのみの指定ではアイテムをすべて削除し、 値のインデックスがある場合はその値のみを削除します。 値を削除した場合、その後ろにも値がある場合は前に詰められます。 また、値を削除して空っぽになってもアイテムは空のまま残ります。

ALContainer *cont = ALContainer::Create();
cont->AddInt( "Val1", 10 );
cont->AddInt( "Val2", 20 );
cont->AddInt( "Val1", 11 );

cont->Remove( "Val1", 0 ); //10を削除するので、11のみが残る
cont->Remove( 1 ); //インデックス1のアイテムVal2を削除

2. テーブル

テーブルクラス(ALTable)とは、ゲーム中の各種データを格納する簡易データベースです。 データは構造体配列のような形で単一のメモリブロックとして保持します。 基本的なアクセスはプロパティ経由ですが、データの先頭アドレスを取得し構造体にキャストし直アクセスすることも可能です。 テーブルは現在の配列インデックスをレコードインデックスとして保持し、 テーブルにおけるデータの読み書きは、そのレコードインデックスで指定されたレコードに対して行います。 また、別のテーブルへの参照を保持することが出来、テーブル丸ごとの参照、テーブルにおける一部の値のみの参照を使うことが出来ます。

2.1. フィールド

テーブルでは値をプロパティとしてアクセスしますが、他のプロパティと区別するために テーブルにおける値のプロパティをフィールド(ALField)と呼びます。 フィールドクラスはALPropを継承しているため、普通のプロパティと同じように使用出来ます。 フィールドの定義は、プロパティとは違いAdd~~Fieldクラスを使用します。 現在使用可能な型は、32ビット整数、16ビット整数、8ビット整数、32ビット実数、 論理型、ビット論理型、埋め込み文字列、参照文字列、ALRect、ALVector、ALColor、ALAngle、ALAngleVector、および参照テーブルです。 フィールドを定義する場合はプロパティと同様にNameとCaptionを指定します。 また、必要であれば先頭からのオフセットも指定出来ます。 オフセットを省略した場合は自動的にオフセットが計算されます。

_Table = ALTable::Create();
_Table->AddIntField( "Int", "整数", 1, 11 );	//32ビット整数型を、min、max値と一緒に設定
_Table->AddRefStringField( "RefString", "参照文字列" );	//参照文字列型を設定
_Table->AddIntField( "Int2", "整数2", 1, 11, 16 );	//16バイト目のオフセットに32ビット整数を設定する

2.2. レコード

レコードとは、このテーブルの1構造体分のデータを指す用語です。 通常はテーブルのデータは複数レコードで構成されます。 レコードのサイズは、通常フィールド定義から自動的に計算されます。 テーブルは、カレントレコードという概念を持ち、レコードデータへのアクセスは常にこのカレントレコードが対象になります。 カレントレコードはSetRecordIndexで設定し、GetRecordIndexで取得します。 なお、初期状態でレコードがない場合はレコードインデックスは-1となります。 レコードを追加するには、AppendRecord関数を使用します。 その際、カレントレコードは自動的に追加したレコードになります。 レコード数はGetRecordCountで取得出来ます。

_Table = ALTable::Create();
_Table->AddIntField( "Int", "整数" );
_Table->AddIntField( "Int2", "整数2" );
_Table->AppendRecord();		//レコード数が1になり、カレントレコードはインデックス0になる。
_Table->AppendRecord();		//レコードをもう1つ追加
_Table->SetRecordIndex( 0 );	//最初に生成したレコードをカレントレコードに

2.3. データの読み書き

データの読み書きはカレントレコードに対し、フィールドを経由して行います。 フィールドはインデックスで取得するほか、名称で検索することが出来ます。 フィールドはプロパティでもあるので、フィールドを取得した後はSetAsIntやGetAsFloatなどの関数でアクセスが出来ます。 注意点として、フィールドはカレントレコードの情報は持たないため、 フィールドを取得した後カレントレコードを移動し、その後フィールドに対しアクセスするとフィールドを取得したレコードではなく、 現在のカレントレコードに対してアクセスを行います。 複数のレコードに対してアクセスする場合は、カレントレコードの状態に注意してください。 なお、設定していないデータは0となります。

_Table = ALTable::Create();
_Table->AddIntField( "Int", "整数" );
_Table->AddIntField( "Int2", "整数2" );
_Table->AppendRecord();		//レコードを1つ生成
_Table->GetField(0).SetAsInt( 1 );
_Table->FindField("Int2").SetAsInt( 2 );
_Table->AppendRecord();		//2つ目のレコードを生成
ALProp pr = GetField(0);
pr.SetAsInt( 3 );
_Table->SetRecordIndex( 0 );
int n = pr.GetAsInt();		//カレントレコードが変更されたため、3ではなく、1が返る

2.4. テーブル参照

テーブルでは、複数のテーブルをリンクさせて使用することが出来ます。 リンクの方法は、レコードインデックスをそのまま使用するか、特定のフィールドを利用します。 対象となるテーブルは元テーブルにルックアップテーブルとして登録し、インデックスでアクセスします。 その後、該当フィールドを取得しGetTable()でその参照先テーブルが取得出来ます。 このとき、取得したテーブルのカレントレコードは自動的に変更されます。 当然その後別の箇所で参照があった場合は、カレントレコードも移動するので注意してください。 テーブル参照フィールドを作る場合は、AddTableField関数を使用します。 識別名と表示名称に続き、参照テーブルインデックス、自テーブルのキーフィールドインデックス、および参照テーブルのキーフィールドインデックスを指定します。 この自テーブルのキーフィールドの値と、参照テーブルのキーフィールドの値が一致するものが対象テーブルになります。 見つからない時はGetTableでNULLが返ります。 キーフィールドインデックスはALFIELD_RECORD_INDEXを指定することが出来、フィールドの値の変わりにレコードインデックスを使用することが出来ます。 特に参照テーブルのキーはテーブル内を検索して見つけることになるため、レコードインデックスを使った方が高速になります。

ALTable *mtab = ALTable::Create();
mtab->AddTableField("Table1","テーブル1", 0 );	0番目のテーブルを、このテーブルのそれぞれレコードインデックスで結ぶ
mtab->AddTableField("Table2","テーブル2", 0, 1 );0番目のテーブルを、このテーブルの1番目のフィールドと対象のレコードインデックスで結ぶ
mtab->AddTableField("Table3","テーブル2", 1, 1, 2 );1番目のテーブルを、このテーブルの1番目のフィールドと対象のレコードの2番目のフィールドで結ぶ
mtab->AddIntField( "Int1_1", "整数1_1" );
mtab->AddIntField( "Int1_2", "整数1_2" );
ALTable *tb1 = ALTable::Create();
mtab->AddLookupTable( tb1 );	//tb1が0番目のルックアップテーブルになる
ALTable *tb2 = ALTable::Create();
tb2->AddIntField( "Int3_1", "整数3_1" );
tb2->AddIntField( "Int3_2", "整数3_2" );
tb2->AddIntField( "Int3_3", "整数3_2" );
mtab->AddLookupTable( tb2 );	//tb2が1番目のルックアップテーブルになる

この例では、mtabのTable1でGetTable()を使うとtb1が取得出来ます。 このとき、レコードインデックス同士で結び付けているので、mtabのレコードインデックスが2なら、tb1のレコードインデックスも2になります。 Table2もtb1が取得出来ますが、2番目のフィールドを使用する指定になっているため、 mtabのInt1_2の値をtb1のレコードインデックスとしてテーブルを呼び出します。 Int1_2に3という値が入っていれば、GetTable()で取得したテーブルはtb1のレコードインデックス3です。 Table3はtb2が取得出来、mtabの2番目のフィールドとtb2の3番目のフィールドを結びつける設定になっています。 そのため、mtabのInt1_2に入っている値と同じ値を、tb2のInt3_3から探し、見つかるとそのレコードがカレントレコードになります。 見つからない場合はGetTable()ではNULLが返ります。 なお、AddLookupTableでテーブルを登録すると内部で参照カウントが増えるので、 ルックアップのみでテーブルを使用する場合は元テーブルをReleaseしておけば、登録したテーブル破棄時に一緒に削除されます。

2.5. 共有

テーブルとテーブル定義は共有することが出来ます。 それぞれ、IDとNameをつけることが出来、同じIDやNameのテーブルやテーブル定義を使う場合は共有されます。 テーブル定義は完全に共有されますが、テーブルはデータのみが共有されます。 そのため、レコードインデックスなどは共有したテーブルでも独立して設定が可能です。 また、テーブルを共有した場合、ルックアップテーブルの情報も共有されますが、 これはコピーなためルックアップテーブルはテーブルごとに独立して設定することが可能です。 テーブルを共有するには、後述するファイルからロードを行うか、明示的にIDやNameをつけます。

ALTable *tb = ALTable::Create();
tb->AddIntField( "Int1_1", "整数1_1" );
tb->AppendRecord();
tb->GetField(0).SetAsInt( 1 );
tb->SetID( 1 );	//ID1で共有される

ALTable *tb1 = ALTable::Create(1);	//ID=1のテーブルを呼び出す
ALTable *tb = ALTable::Create();
tb->AddIntField( "Int1_1", "整数1_1" );
tb->SetDefineName( "Def1" );	//Def1という名前で共有される

ALTable *tb1 = ALTable::Create();
tb1->LoadDefine( "Def1" );	//Name=Def1という定義を呼び出す

2.6. 保存

テーブルのデータは、レコード情報、レコード定義ともに保存することが可能です。 これらは別々に保存することも、1ファイルに保存することも出来ます。 デフォルトの拡張子は、レコード情報はatb、レコード定義はardです。 テーブルのSave関数を呼び出すと保存が出来ます。 Save関数にはファイル名のほか、オプション情報も指定が出来ます。 オプション情報を指定すると、レコード定義を別ファイルにしたり、ルックアップテーブル情報を保存することが出来ます。 デフォルトではルックアップテーブル情報は保存されないので注意してください。 オプションにはALTableSaveOptionというクラスを使用します。 レコード定義を外部ファイルにするには、ここにSetDefineIDやSetDefineName関数を使いレコード定義のIDかNameを設定します。 また、SetDefineNone関数を使用することによりレコード定義を読まないようにすることも出来ます。 ルックアップテーブルを登録するには、AddLookupIDかAddLookupNameを使用して登録します。 レコード定義、ルックアップテーブルまとめて、IDのみで指定するか、Nameのみで使用するかの片方だけが使えます。 混在する事は出来ません。

ALTable *tb = ALTable::Create();
~~
ALTableSaveOption opt;
opt.SetDefineID( 1 );		//ID=1のレコード定義を読むように
opt.AddLookupTableID( 10 );	//ID=10のテーブルをルックアップテーブルとして読むように
opt.AddLookupTableID( 11 );	//ID=11のテーブルをルックアップテーブルとして読むように
tb->Save( "test.atb", &opt );
ALTable *tb = ALTable::Create();
~~
tb->SaveDefine( "test.ard" );	//レコード定義のみを保存する

2.7. 読み込み

すでにファイルにあるテーブルを読み込むにはCreate時にIDかNameを指定して生成します。 このとき、すでに同じテーブルがロード済みだったり、SetID等で共有化がされていればそれを優先して読み出します。 ファイルからロードしたテーブルは情報があればレコード定義も含まれているのでそのまま使用出来ますが、 その後LoadDefineで、レコード定義を別に読むことも可能です。 ルックアップテーブル情報を持たないテーブルで、ルックアップテーブルを使用する場合はロード後に明示的にAddLookupTableを使用してください。

ALTable *tb = ALTable::Create( "test.atb" );
ALTable *tb2 = ALTable::Create( "test.atb" );	//すでにロード済みがあるので共有される
tb2->LoadDefine( "test.ard" );	//レコード定義のみ追加読み込み

2.8. 直参照

テーブルのレコードデータはフィールド定義を使わず、直接読むことも可能です。 あらかじめフィールドの定義に合わせて構造体を定義しておき、 テーブルのGetRecordData関数でポインタを取得し、それをその構造体にキャストすることで読み出すことも出来ます。 このポインタはカレントレコードのポインタになります。 高速にアクセスする必要がある場合は、この方式を使用してください。なお、このように直参照する場合はレコード定義がなくても使用可能です。ただし、レコードサイズのみSetRecordSizeであらかじめ指定しておく必要があります。

第10章 ファイル関連

1. ストリーム

ストリーム(ALStream)とは、ファイルアクセスのためのクラスです。ファイルへのアクセスは、全てこのストリームを使用します。 CDやHDD等の物理デバイス直だけではなく、アーカイブに含まれているファイルもそのまま読み込むことが出来ます。 このストリームは、単にファイルをリードするだけでなく、ファイルを全てリードしその内容を保持することも出来ます。 その場合は、読み込んだデータの先頭アドレスを取得して使用します。 ロードは非同期で行うことも出来ます。その場合は完了をコールバックやメッセージで通知します。 また、メモリ上のデータをストリームとして使用するメモリストリーム(ALMemoryStream)、 ストリームの一部を別のストリームとして使用する、部分ストリーム(ALPartStream)等があります。

1.1. ファイルのオープンとクローズ

ストリームを使用してファイルをロードする場合、Create関数、またはCreateTry関数を使用します。 Create関数はファイルが見つからない場合はアサートで停止しますが、CreateTry関数はファイルが見つからない場合はNULLを返します。

ALStream *st = ALStream::Create("test.dat");	//test.datが無ければアサート
ALStream *tst = ALStream::CreateTry("test2.dat");  //test2.datが無ければNULLを返す

ストリームはオープンするときには物理デバイスより先にロード済みのアーカイブの中身をサーチします。 その為、もしアーカイブ上に同名ファイルがあった場合などは、物理デバイス上のファイルをオープンすることは出来ません。 アーカイブの中身関係無しに物理デバイス上のファイルをアクセスするために、CreateDirect関数が用意されています。 これは、アーカイブの中身は検索せずに物理デバイス上のファイルのみをオープンします。

ALStream *st = ALStream::CreateDirect("test.dat");	//物理デバイス上にtest.datが無ければアサート
ALStream *tst = ALStream::CreateDirectTry("test2.dat");  //物理デバイス上にtest2.datが無ければNULLを返す

使用が終わった場合はRelease関数を利用して解放します。 なお、内部で参照カウント管理されているため、別の箇所から参照がある場合などはその場では解放されません。

st->Release();

1.2. Readによる読み込み

ファイルからデータの読み込みには大きく二つの方法があります。 一つ目は一般的な方法で、Read関数を使用して渡したワークに読み込んだデータを書き込む方法です。 二つ目はGetTopAddress関数やGetCurrentAddress関数を使用して、ストリーム内部のワークにデータを読み込み、それを利用する方法です。 ここではRead関数を使った読み込みを説明します。 Read関数は書き込むデータのワークと、読み込むサイズを指定し、現在のファイルの位置から読み込みます。 返り値は実際に読み込んだサイズです。一切読み込めなければ0が返ります。 ファイルの位置は、Read関数を実行すると読み込みサイズ分だけ増加するので、続けて実行すると順次データを読み込みます。 ファイル位置はGetPositionで取得、SetPositionで設定することが出来ます。

ALStream *st = ALStream::Create("test.dat");
Uint32 p = st->GetPosition(); //0が返る
char buf[8];
Uint32 s = st->Read( buf, sizeof( buf ) ); //8バイト読み込み、8が返る
p = st->GetPosition(); //8が返る
st->SetPosition( 10 );
char buf2[8];
st->Read( buf, sizeof( buf ) ); //ファイルの10バイト目から8バイト読み込み。

1.3. データポインタの取得

二つ目の読み込み方法である、GetTopAddressやGetCurrentAddressを使った方法は、 ユーザーアプリ側でワークを準備する必要が無く、解放忘れがありません。 また、アーカイブ等からファイルを読み込む場合等では、すでにメモリ上に存在するデータを直接使えるため効率的です。 ただし、ファイルの一部しか使用しない場合は余分なものまで読み込んでしまうため非効率となります。 通常、ファイル全体を使用する場合は常にこちらの方式を使用することをお勧めします。 GetTopAddress関数は、省略可能なオフセット引数を一つ持ち、内部に読み込んだデータのポインタを返します。 このポインタで取得したメモリは読み込み専用で、書き込んではいけません。 オフセットの指定は、単に取得するアドレスにそのオフセットを足した値が返るだけなので、 オフセットに0を指定し、その後ポインタ演算をしても結果はまったく変わりません。 GetCurrentAddress関数は、アドレスの基準が現在のファイル位置が基準になるだけでそれ以外の動作はGetTopAddress関数と一緒です。 なお、最初にGetTopAddress関数やGetCurrentAddress関数を実行したときに、内部にメモリが確保されストリームの全データが読み込まれます。 その時内部のファイルハンドル等も開放され、以降のGetTopAdress等の関数やRead関数はすべてその確保済みのメモリが対象になります。 確保されたデータはストリーム破棄時に一緒に破棄します。

ALStream *st = ALStream::Create("test.dat");
void *b1 = st->GetTopAddress();	//先頭データのオフセットが返る。同時にファイル読み込みが発生する
void *b2 = st->GetTopAddress( 10 );	//10バイト目のデータのオフセットが返る。ここではもう読み込みは発生しない
st->SetPosition( 20 );
void *b3 = st->GetCurrentAddress();	//20バイト目のデータのオフセットが返る。
void *b4 = st->GetCurrentAddress( 30 );	//20+30の50バイト目のデータのオフセットが返る。

なお、予め内部にデータを読み込んでおきたい場合は、FetchAll()関数を使用します。 主に非同期読み込みを使用する場合に用いる関数ですから、それ以外で使う必要はありません。

ALStream *st = ALStream::Create("test.dat");
st->FtechAll(); //ファイルの内容を全部ロード
void *b1 = st->GetTopAddress();	//先頭データのオフセットが返る。ここではもう読み込みは発生しない

2. アーカイブ

アーカイブは複数のファイルをまとめる機能で、アーカイブをメモリ上にロードしておけば各種ファイルロード時に、 自動的にアーカイブの中のファイルも検索します。 アーカイブの中にあるファイルをオープンする場合、アーカイブ内のメモリ領域をそのまま使うため、 メモリのコピー等は行いません。 アーカイブ生成時のオプション指定により、アーカイブを一括でメモリ上に読み込む他、 管理領域だけをメモリ上に読み込み、必要なファイルの領域だけを必要なときに読み込む方式が使えます。

2.1. 基本的な使い方

アーカイブはツールを使って生成した後は基本的にメモリ上に存在するだけで自動的に使用されます。 その為、通常は生成した後は特に何もせず、使用が完了したときにReleaseするだけです。

第11章 コリジョン関連

1. コリジョン

コリジョンシステムでは、2D系、3D系両方でいくつかの形状の衝突判定をすることが出来ます。 現在2D系では点、円、矩形、3D系では球、ボックス、ライン、カプセル、メッシュ、ハイトフィールドが使用出来ます。 コリジョンはノードに所有され、ノードの位置、回転の影響を受けます。 コリジョンは32ビットのアトリビュートを持ち、そのアトリビュート単位でどれと衝突するかを指定します。

1.1. 基本的な使い方

まずコリジョンを生成し、ノードに登録します。 生成時は各種コリジョンのCreate関数を使用し、ノードのAddCollision関数で登録します。 破棄はノードの削除時に一緒に行われます。 その時にCreateの引数か、SetAttribute関数でアトリビュートを設定します。 アトリビュートは32ビット整数なので、32種類のグループに属することが出来ます。 通常は一つのビットのみを使用するようにしますが、複数ビットを使用することも出来ます。 その後、コリジョンマネージャにどのアトリビュートとアトリビュートがヒットするかを設定します。 この組み合わせが多ければ多いほど衝突判定に時間がかかるため、必要最小限の組み合わせになるように設定してください。 衝突判定はコリジョンマネージャが行い、衝突を検知するとノードのコールバックを呼び出します。 コールバックは2種類あり、SetHitFuncで設定する衝突ごとに発生するコールバックと、 SetHitAllFuncで設定するそのノードへの衝突を全て検出した後に発生するコールバックがあります。 衝突の優先度や順番を気にする必要がなければSetHitFuncを、 ヒットした中で一つを選ぶ必要がある場合などはSetHitAllFuncを使用します。 SetHitFuncで設定するコールバックではその後の検索をキャンセルさせることも出来るため、 種別を問わない場合は一つ検知した時点でキャンセルすると高速になります。 なお、コールバックはそのノードがグループに所属している場合はグループマスタのみで発生します。 個別のノードでのコールバックは設定しても無視されるため、グループを使う場合は注意して下さい。

1.2. コリジョンマネージャ

コリジョンの判定はコリジョンマネージャを使用します。 コリジョンマネージャのインスタンスは通常は一つを使用しますが、状況により複数使うことも出来ます。 デフォルトの概念があり、通常はそのデフォルトを使用します。 各コリジョンはどれかのコリジョンマネージャに所属し、 同一のコリジョンマネージャに所属しているコリジョン同士のみ衝突判定が行われます。 コリジョンマネージャに登録されたコリジョンは、通常のアップデートの後にまとめて衝突判定が行われます。 その時にノードの衝突コールバックが呼ばれます。 コリジョンがどのアトリビュート同士で衝突するかは、このコリジョンマネージャに対して行います。 SetTargetAttribute関数で、自分側のアトリビュートと相手側のアトリビュートを指定します。 どちらも複数ビットを指定可能です。 なお、衝突判定は自分側から相手側に対してチェックが行われます。 SetHitFuncで登録する単一衝突コールバックでは両コリジョンのコールバックが呼ばれますが、 SetHitAllFuncで登録される複数衝突コールバックは自分側のみコールバックが呼ばれます。 また、コリジョンマネージャではデバッグ用にコリジョンをワイヤーフレームで表示することが出来ます。 表示はアトリビュート単位で設定出来、色もアトリビュート単位で設定出来ます。

1.3. コリジョンの各種設定

コリジョンには半径やサイズ等のコリジョン固有の設定のほか、共通の設定があります。 まず、SetEnable関数で有効無効を設定することが出来ます。 攻撃判定等、一定時間のみ有効にしたい場合などはこのEnableで設定します。 SetDispLink関数により、ノードの描画状態とコリジョンの有効化をリンクさせることが出来ます。 これがtrueの場合、ノードが非表示になった場合コリジョンも無効になります。 このフラグと先ほどのEnableは独立しているので、SetDispLinkがtrueで表示状態でも、Enableがfalseならそのコリジョンは無効です。 また、コリジョンはSetPostion関数によりノードからオフセットをずらしてセットすることが出来ます。 そのほか、Kindというパラメータがあります。これはAqualeadのシステムは一切使用しないので、 Attributeより細かい単位でコリジョンの区別をする場合に使用します。 例えば、Attributeでは攻撃、防御等の区別をし、Kindではどの攻撃のコリジョンかというような区別に使います。 最後にGetGlobalIDで取得出来るグローバルIDがあります。 これは、起動時からインクリメントされるコリジョンごとに必ず違う値が返ります。 同じAttribute,Kindの場合、同じコリジョンにあたったのか、違うコリジョンにあたったのかを区別出来ます。 コリジョンのアドレスで比較しようとしても、タイミングが違えば別のコリジョンが同じアドレスになることがあるので注意してください。

1.4. コリジョンマネージャを使わない判定

衝突判定はコリジョンマネージャを使う方法の他、 コリジョン単位で明示的に判定を行うことも出来ます。 各ノードのアップデート処理内ですぐに衝突判定の結果が必要な場合に使用してください。 ただし、若干速度的には非効率になります。 チェックするには、自分側のコリジョンに対しCheckHit関数を呼びます。 引数は相手のコリジョンか、アトリビュートを指定します。 ヒットしていればtrueを返します。