AIRやFLASHなどを表示するのに不便なので、ブログから自作サイトに移転します。

N2-Works移転先

今後はこちらで更新を行っていきます。


前回のエントリ『画像ファイルから動的にBitmapクラスを作成 』でも扱ったBitmapについての新たなメモ。

ノベルゲームエンジンの画像転送処理について、どうするかを考えて色々試してみた結果を記載しておきます。


ゲーム制作で画像を描画する場合、ほとんどの人がダブルスクリーン手法をとっていると思います。

DirectXなどはそれと意識せずとも、内部ではダブルスクリーンの処理が行われているはずです。


ダブルスクリーンとは、実際に表示される画面をセカンダリスクリーン、メモリ内で保持している画像をプライマリスクリーンとして使用する手法です。APIを使用して直接描画を行うのは全てプライマリスクリーンに対してだけで、セカンダリスクリーンはプライマリスクリーンの画像をコピーすることによって描画を行います。


なぜこのような、まわりくどいやり方をするのでしょうか。それはセカンダリスクリーンに直接描画を行ってしまうと描画過程を全て表示してしまうからです。


例えば人物を表示する際、人レイヤーと服レイヤーを分けていたとします。人を表示するには人レイヤーに人物画像を描画した後、服を重ねて描画しますがセカンダリスクリーンに直接描画してしまうと人物画像を描画した時点で表示されてしまいますね。


つまり表示したい画像になるまではメモリ上のプライマリスクリーンに全て画像を描画し、プライマリスクリーンの画像が完成した後セカンダリスクリーンに描画してやれば内部処理を外に表示せずに済むのです。


ただAirのレンダリングエンジンがどの程度の速度で動作するのか試して、その速度によっては色々とやり方を考えないといけないと思っていました。


それで最初に試したのは前回のエントリの最後に書いた、BitmapDataクラスのメソッドGetPixels()を使用する方法です。



// プライマリスクリーンを作成
var primary_screen:Bitmap = new Bitmap(
new BitmapData({ステージ幅}, {ステージ高さ}));
			
// セカンダリスクリーンを作成し、画像表示ステージに展開
var secondary_screen:Bitmap = new Bitmap(
new BitmapData({ステージ幅}, {ステージ高さ}, true, 0xff000000));

// セカンダリスクリーンはステージに配置
addChildAt(this.secondary_screen, 0);

// 外部画像ファイルの情報を取得(Loaderで取得したもの)
var img:Bitmap = {Loaderで取得したBitmapインスタンス};

// 取得したBitmapから画像の情報だけを取得(ARGB)
var src_bytes:ByteArray = img.bitmapData.getPixels(new Rectangle(0, 0, img.width, img.height));

// ステージの大きさ分のRectangleを作成
var screen_rect:Rectangle = new Rectangle(0, 0, {ステージ幅}, {ステージ高さ});

// プライマリスクリーンに指定画像データをブレンドする
var dst_bytes:ByteArray = primary_screen.bitmapData.getPixels(screen_rect);
var src_pos:uint;
var dst_pos:uint;
for (var y:uint = 0; y < img.height; y++) {
    for (var x:uint = 0; x < img.width; x++) {
        // アルファが0の場合はブレンドしない(透過処理)
         if (src_bytes[(y * img.width * 4) + x * 4] == 0x00) {
	    continue;
        }

	// pixelの位置を計算(4はARGBの4byteだから)
	src_pos = (y * img.width * 4) + x * 4;
	dst_pos = (((y + {画像の表示位置Y}) * {ステージ幅} * 4) + (x + 画像の表示位置X) * 4);

	// アルファ値
	dst_bytes[dst_pos] = src_bytes[src_pos];
	// Red
	dst_bytes[dst_pos + 1] = src_bytes[src_pos + 1];
	// Green
	dst_bytes[dst_pos + 2] = src_bytes[src_pos + 2];
	// Blue
	dst_bytes[dst_pos + 3] = src_bytes[src_pos + 3];
    }
}

// プライマリスクリーンに転送済みのByteArrayを設定
dst_bytes.position = 0;
primary_screen.bitmapData.setPixels(screen_rect, dst_bytes);


これでロードした画像からプライマリスクリーンへの画像転送は終了です。セカンダリスクリーンは同じようにプライマリスクリーンから画像データを取得してセカンダリスクリーンに設定すればOKです。


ですが、この方法だと1byteずつ処理するループ部分で時間がかかってしまうようです。

そこでループ回数を減らすために1ラインずつ処理する方法をとろうとしたのですが、今度は透過処理で問題が出てきてしまいました。


しかたないので提供されているAPIを試してみたところ、BitmapDataクラスのCopyPixelsメソッドを使用した方が明らかに速いのです。


上記のように直接、ByteArrayを操作する方が処理が速いと思っていたのですが、どうも納得がいかないところです。

直接操作することも細かいエフェクトをかける時に必要なので、上記のコードも無駄にはならないのでしょうが…


AIRやFLASHなどを表示するのに不便なので、ブログから自作サイトに移転します。

N2-Works移転先

今後はこちらで更新を行っていきます。


ノベルゲームエンジンをAirで製作中ですが、C/C++などで画像データを動的に取得する方法と違う部分が多いのでメモ。


ゲームで画像を扱う場合、静的にプログラム本体にリソースを定義するか、実行中に外部ファイルから動的に画像データを取得するという方法があります。


当然、静的に定義する場合はビルドした実行ファイルに画像データも全て挿入されるため、非常にサイズが大きくなってしまいます。さらに画像を入れ替える度にビルドしなおす必要が生じます。


以上の理由で普通は外部ファイルに画像データを置くのが一般的です。ここでは両方の方法を載せておきます。


また、FlashPlayerに埋め込めるファイル形式は「JPEG」、「GIF」、「PNG」、「SVG」、「SWF」、「TTF」、「MP3」です。


Airで画像を扱うクラスはBitmapクラスなのですが、「BMP」は扱えないようです。画像ファイルの総称としてBitmapという名称を使用しているだけでしょう。


保持している画像バイナリデータの並びを見てもARGBの32bit=1pixelとなっていますので、Bitmap形式のフォーマットとは明らかに異なります。


では実装内容を見てみましょう。


まずは画像データの静的な定義の方法です。以下のようなコードになります。


[Embed(source='{ファイルパス}')]
var Image:Class;

var img:Bitmap = new Image();
addChild(img);


ファイルパスに画像ファイルを置く必要がありますが、ビルド後は実行ファイルに挿入されていますので、置く必要がなくなります。


次に画像データを外部ファイルから動的に取得する方法です。こちらはByteArrayでデータを取得できるようにします。


複数の画像ファイルを1つのファイルに連結したものを読み込み、そのデータをバイナリアクセスした方が都合が良いからです。


以下がそのコードになります。Airの仕様から少し回りくどいコードに見えますね。



// バイナリデータ格納
var byte:ByteArray = new ByteArray();

// 画像データ取得(flash.filesystem.*使用)
var file:File = new File({ファイルパス});
var if_stream:FileStream = new FileStream();
if_stream.open(file, FileMode.READ);
if_stream.readBytes(byte, 0, if_stream.bytesAvailable);
if_stream.close();

// ByteArrayのままではBitmapに変換できないのでLoaderクラスを使用
var loader:Loader = new Loader();

// イベントリスナ設定(Loaderのロードが完了時)
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, LoaderCompleteHandler);

// バイナリデータをロード
loader.loadBytes(byte);

// イベントリスナ
private function LoaderCompleteHandler(evt:Event) : void
{
    // ロードを完了したローダーを取得
    var loader:Loader = evt.target.loader;

    // Bitmapクラスに型変換
    var img:Bitmap = Bitmap(loader.content);
    addChild(bmp);
}


Bitmapクラスに変換したら、Bitmap.BitmapData.GetPixelsで画像バイナリを取得して処理すれば色々とエフェクトをかけられると思います。


AIRやFLASHなどを表示するのに不便なので、ブログから自作サイトに移転します。

N2-Works移転先

今後はこちらで更新を行っていきます。


FLVPlayer制作の最終回はSoundクラスについて書きます。


FLVPlayerの実装にあたり関係する機能はボリュームの調節機能だと思います。


SoundクラスにはL/Rのスピーカーの比重を設定できる機能もありますが、一般的なFLVPlayerにはこの機能がありませんでしたので、実装を見送りました。


実際に使用するコードは以下のようになります。


// ボリューム調節クラス(SoundTransform)を作成
var sound:SoundTransform = new SoundTransform({Number});

// 作成済みのNetStreamクラスのsoundTransformプロパティに挿入
stream.soundTransform = sound;


簡単なコードですが、注意すべき点が2点あります。


1つはSoundTransformクラスのコンストラクタの引数は0.0~1.0の小数でないといけないという点です。1~100という整数ではありませんのでご注意下さい。ただ代入する時に小数であればよいので以下のようにすれば整数でボリューム値を保持していても問題ありません。


// Numberでボリューム変数を定義
var volume:Number;

// ボリュームスライダー(vol_slider)の幅とボリュームスイッチ(vol_switch)の位置から割合を求める
volume = Math.floor((vol_switch.x / vol_slider.width) * 100);

// volumeには0~100(%)の値が入っているので100で割った値を代入
var sound:SoundTransform = new SoundTransform(volume / 100);


大多数の方がスライダーでボリュームを調節するでしょうから上記のようなコードにしてみました。volumeを整数で保持しておけば、stage上にボリューム値を表示する時など便利ですね。volumeがNumberになっているのは代入の式(volume / 100)の結果を小数にするためです。(volume / 100.0)とかにしてもいけるのでしょうか、試してないので分かりませんが。


さて、もう1つの注意すべき事は、NetStreamのsoundTransformのvolumeプロパティを直接変更することはできないということです。下記のコードを見てください。


// コンストラクタからvolumeを設定
var sound:SoundTransform = new SoundTransform({Number});

// 一度インスタンスを生成してからvolumeプロパティに値を設定
var sound:SoundTransform = new SoundTransform();
sound.volume = {Number};


2つのコードは同じことを行っています。コンストラクタから値を入れてもインスタンス生成後にvolumeを設定しても結局は同じなのです。


ではNetStream.SoundTransformプロパティにインスタンスを代入した後も、volumeプロパティの値を変更できるのでしょうか?


それは残念ながらできません。ボリュームを変更する時は必ず一度SoundTransformクラスのインスタンスを作成しvolumeプロパティを変更する値に設定してNetStream.SoundTransformプロパティに代入しないといけないのです。


つまり下記のようなコードではボリュームの変更に失敗します。


// NetStream.SoundTransformプロパティを直接変更するのはNG!
stream.SoundTransform.volume = {Number}



以上でSoundクラスについての説明を終わります。詳しく知りたい方はAS3.0のリファレンスマニュアルのSoundクラスの項 を参照して下さい。

とりあえず今回でFLVPlayerについての解説は終えます。ソースコードが欲しい方はコメントか何かで連絡を下さい。


というわけで終了です。