EDGE1専用形式のデコーダーEDGDecoder

ドット絵業界ではとても有名なドット絵エディタEDGEというソフトウェアがあります。で、その専用保存形式である.edg形式のデコーダーを作りました。
Twitterの一部のお絵かきクラスタでpsdファイル(PhotoShopの保存形式)を公開する流れがあって、ドッター界隈でもレイヤー等をそのまま残したもので交流なりができればいいと思って実装しました。

実際に動いてるのはこちら

EDGE1とEDGE2があるのですが、ひとまずEDGE1形式のみです。EDGE1のほうは入出力のC++ソースが公開されているので、簡単でした。

EDGE2形式のほうは資料が無いので作れるかどうか微妙です。あと、現在有力なドット絵エディタの保存形式としてGraphicsGaleの.gal形式があるので、そっちもできれば対応させたいところです。

あとはビューアー側でもレイヤーを閲覧できるようにしたり、PHPのほうでサムネイル出力できるようにしたいですね。

はたして自分以外に必要な人がいるのか知らないですが、一応ソースも置いておきます。

PNGバイナリをデコードするPNGDecoder

タイトルの通り、PNGのデータをデコードしてBitmapDataとパレットなどの情報を取得するクラスを作りました。ソースはひとまず

に置いてあります。MITライセンス。

PNGの仕様を全部実装してあるわけではないので、バージョン 0.1としてあります。基本的な部分しか実装していないので、画像によっては正しく読み込めなかったり表示できなかったりするかもしれません。手持ちのPNGファイルは全部普通に読み込めたので、たぶん実用上はそれほど困らない範囲だと思います。ちなみにグレイスケール対応してなかったりします。だって使ったことないし。

PNGPaletteDecoderのほうは、画像本体のデコードはしませんが、パレット形式だけ読み込んでくれます。実用性を考えると、PNGDecoderよりもまだ需要がありそうです。ちなみに、PNGDecoderはPNGPaletteDecoderを継承させてます。

使い方・実用例

大体こんな感じ

// PNGのByteArrayをBitmapDataに変換する
private function getPNGBitmapData(bytes:ByteArray):BitmapData
{
  var decoder:PNGDecoder = new PNGDecoder();
  
  try{
    decoder.decode(bytes); // デコードする
  }catch(e:Error){
    trace('invalid PNG ByteArray');
  }

  // パレット形式のとき、1番最初のパレットに格納されている色をtraceする
  if(decoder.colorType = 3)
    trace(decoder.paletteTable[0] as uint)
  
  return decoder.image; // デコードされたBitmapDataを返す
}

まだテスト段階ですが、パレット形式のPNGを読み込んで特徴領域抽出と近似領域生成するやつPNG読み込みに使ってみました。表示にはLoaderではなくPNGDecoderを使っています。基本的にドット絵用なので使い道は限られていますが、そのうちこれを活用してなにかを作る予定です。速度重視していてあまり時間のかかる判定処理は組み込んでいないのですが、64〜256程度のサイズだとけっこううまくいきます。

作った背景

普通、画像のByteArrayからDisplayObjectもしくはBitmapDataに変換したいときは、Loader.loadBytes()を使えばよいです。むしろ中途半端な機能しかないPNGDecoderをわざわざ使って変換するよりよい方法です。なので、PNGPaletteDecoderはともかく、PNGDecoderはどちらかというと画像処理の勉強のために作った感じです。

PNGPaletteDecoderは前に作ったパレット変換用に作ったものを綺麗にしただけです。PNGDecoderを作ったきっかけは、PNGEncoderというPNGエンコードをするクラスのソースをみたら、IDATチャンク(PNGの画像データで、deflate/inflate圧縮がかかっている)の圧縮処理部分がByteArray.compress()で実装されていて、通常は面倒な圧縮・解凍処理もAS3なら簡単にできるということがわかったからです。解凍処理がByteArray.uncompress()だけで出来るわけで、それならDecoderも簡単に作れるんじゃね?と思ったのが運の尽きでした。

ちなみに、PNG以外の画像形式のデコーダーはいくつかあって、GIFDecoder(Loaderと違ってGifアニメも一応対応)やBMPDecoderなんてのがあります。

一般的なPNG画像を表示するのための最低限の実装

PNGのバイナリをBitmapData(要は、RGB+透明度の値が入った表)にすることがデコーダーの目的です。PNGの仕様書を読むとわかるとおり、色々と補助的な情報が入っているのですが、全部実装するのは面倒です。今回は一般的に使われているであろう範囲でのPNG画像を読み込んで表示できればよいとします。

細かいことは参考文献を読んでもらえばいいのですが、PNGはチャンクと言うデータブロックで構成されています。最低限読み込んだほうがいいチャンクは以下の通りです。

  • IHDR(画像サイズやタイプなどの基本情報)
  • PLTE(パレット表)
  • IDAT(画像データ本体)
  • bKGD(背景色)
  • tRNS(透過色)

IDAT以外は参考文献を読めばすぐに解釈できると思うのでここでは触れません。バイナリを数値に変換するだけです。

IDATのほうもByteArray.uncompress()でデータ解凍して、ビットの深さと透過・背景色に注意して色(パレット形式のときはパレット番号)に変換すれば基本的にはOKです。ただ、圧縮率を上げるために、画像の色データに対して、行単位でフィルターがかかっていることがあります。フィルター逆変換処理をしないと、フィルターのかかっている行の色がおかしいことになるので、フィルターの章も読んでちゃんと実装しておきましょう。

あと、今回のPNGDecoderでは実装していませんが、余裕があればcHRM(基本色度)、gAMA(ガンマ値)、sRGB(色空間)あたりもたまに埋め込まれてるのでちゃんと解釈してやるとよりベターだと思います。

今後の発展とか

Flex3から標準でPNGEncoderがあるのですが、実はαチャンネル付きPNGしか出力してくれません。なので、ファイルサイズは無駄にでかくなります。また、ドット絵などパレット形式で保存したいときには使えません。

ビット深度やアルファチャンネル・背景色の有無、パレット形式での保存など、もう少し細かい設定ができるPNGEncoderがあると、もしかすると便利なことがあるかもしれません。基本的にはPNGDecoderと逆のことをすればよいので、誰か作ってもいいと思います。圧縮性能を上げるためにはいろいろ工夫も必要ですが。

ハレディバイダの製作

いまさらですが、年末にハレディバイダ紅白Flash合戦2008闇鍋祭に出展してました。
属性切り替え弾消し弾幕STGです。

STGとしてのシステムは色々混じってますが、一番の元ネタはパロディウス菊一文字です。異論は認める。

Flashゲーとしてはパララライザでやっちゃったことと同じなので代わり映えしませんね。まあ一応ハレディバイダの製作について解説しておきます。

製作の目的

前回のパララライザはFlash弾幕STGで60FPSで動くものを作る、というのが目的でした。その後Flash Player 10が出たので、もっとゴリゴリ凝ったものを作ってみようと思ってできたのがハレディバイダです。

結果、製作に3ヶ月ぐらい(パララライザと同じぐらい)かかってるんですが、見た目全然変わってないどころか更に地味に…。不思議ですね。当初はドット絵背景付けたりとか、エフェクトを増やして豪華にしたりとか、ボスをゴリゴリ変形させようとか考えていたのですが、素材を作成するモチベーションがそこまで付いていかなかったのが残念です。

ちなみに、FP10になって処理は多少は安定するようになったものの、弾数が増えすぎたりレーザー何本も出そうとするとごっそり処理落ちします。ほとんど処理落ちしない程度に敵や弾幕を構成していますが、やっぱり処理速度の面でゲームの幅が制限された感があります。今回は前回ほど処理を軽くしようと思って頑張ってないので、まだまだ改善できそうな気はしますけどね。

カスタムフィルタでトランジション

ゲーム開始時のトランジションに回転ブラーっぽいものを掛けてみました。最初はもうちょっと複雑なエフェクトだったんですが、手続きを増やせば増やすほどすごい処理落ちするので、実用的な速度になるまで大分そぎ落としました。
でも自分でフィルタを簡単にいじれるのは楽しいですね。

リプレイのファイル保存

FP10になってファイル操作が簡単になったので、リプレイデータをローカルに保存できるようにしました。ByteArrayをそのまま保存できるので楽チンです。

ランダム生成ゲー

開発も終盤になったころ、時間に余裕があったので残りでチュートリアルとか簡単なスタッフロールも作れそうだなと思っていたのですが、ついついランダム生成ゲーについて思いを膨らませてしまって、急遽Extraモードとしてランダム生成ステージを作ってしまったのでした。だってチュートリアルとかスタッフロールとかよりも作っていて楽しいし。

いろいろなランダム生成ゲーを見れば、本当に完全ランダムだと面白くなるわけがないのがわかります。ランダム生成ゲーのポイントは大きく2つあって、ランダムながら戦略が生まれる程度のパターン性を内包することと、難易度の上昇は単調増加ではなく簡単な時と難しいときの波をつけるということです。

Extraは通常・Anotherの弾幕を流用して製作時間をできるだけ削減しつつ、上の2つのポイントを意識して作ってみました。このゲームのシステムだとうまい人なら延々と続けられるので、4,5分ぐらいからは本格的に圧殺にかかってくるようにもしてます。短時間で遊べる仕様。

誰が得するんだこのゲーム

いままでもそうでしたけど、スコア稼ぎすると楽しいゲームなんですが、1回やる分には地味だしなにやってんだかわからないし、弾幕STG慣れてないと難しすぎるし、STGやる人にしてみたらたいしたインパクトもないしで、本当に自分以内の誰が得するんだこれっていうゲームですよね。そもそも最初Easy一回だけやってAnotherとかExtraには全く手が出ない人の多いこと。(自分もプレイする側だったらそういうこともあるので非難するつもりは全くないです)

もっとわかりやすく直感的に楽しい!っておもえるようなゲームのほうがFlashゲームとしては求められているような気がします。

とはいえ、そもそも自分がゲーム作る目的で一番大きいのは、自分で遊んでて楽しいゲームを作ることなので、少なくとも今年のウチはニッチなゲームをまた出していくことになると思います。ニッチなゲームができてから、ついでに間口を少しでも広げられるような努力はしていきたいですけどね。

Flashな弾幕STGで60FPSを目指す

パララライザ

パララライザでどのぐらい動かせているのかというと、こんな感じ。

パララライザHardのクリアリプレイ(ネタバレ注意)

けっこうな弾量でてますよね。画面右上の数字がFPSです。PCの性能や弾の量によっても変わりますが、だいたい50〜60ぐらいは出せているかなと(ただし、IEだと遅い)。

ということで、Flash弾幕STG「パララライザ」の製作におけるポイントについてざっくり解説したいと思います。技術的にすごいことをやっているわけでもなく、これからAS3とかでゲームつくり始めたい人向けです。

弾幕モノに限らず、Flashゲームにおいてかなり困りごとなのが、FPSが安定しないのと、描画速度が遅いこと。FPSが安定しないのは、ゲームの中で精密性を要求しない(格ゲーならコマンドを簡易にする・弾幕ならイライラ棒な要素を減らす)ようにすればそんなに問題ではないのですが、描画が遅いのは本当に困ります。ガリガリ動くようなものを作ろうとおもったら、すぐに処理落ちしてくれます。なので、弾や敵を出すたびに富豪的にSpriteやBitmapなんかをフレーム毎にnewするなんてのは到底無理な話です。

幸いなことに、AS3で弾幕を高速に処理するというのは先人が開拓してくれていて、大変参考になりました。もちろん、弾幕でなくてもアクションゲームなり格闘ゲームなりに十分応用できます。

大きなポイントは、

  • 必要なBitmapDataは前もって生成しておき、処理速度の速いfillRectとcopyPixelで描画する
  • インスタンスはむやみにnewしないで使いまわす
  • 当たり判定は簡単に

といったところ。それぞれ解説します。

必要なBitmapDataは前もって生成しておく

基本的に、ゲーム中のグラフィックは全てBitmapDataを使います。ベクタにするとどうしても重くなるからです。私の場合、元々ドット絵しか作れないので好都合。

一番処理が重いのがBitmapDataの生成なのですが、一旦生成してしまえばいくらでも使いまわせます。なので、ステージの開始前などに必要なBitmapDataを一通り生成しておいて、それを元に描画します。(パララライザの場合、そんなに量もないのでステージごとではなくゲーム開始時に全部生成してる)

BitmapDataを元に描画する方法はいくつかあるのですが、その中でも一番高速なcopyPixelを使います。フレーム毎にfillRectで描画領域を塗りつぶし、弾や敵などのBitmapDataをcopyPixelしていきます。

これだけでもかなり処理速度が改善されるはずです。

ただ、copyPixelだけだと回転・拡大縮小などが出来ないので、回転角や拡大倍率ごとにまたBitmapDataを生成しておきます。大抵の場合、それほど細かく生成する必要はなくて、回転角なら15度とか30度とかごとに回転して生成するだけでも十分だったりします。どうしても細かい変化や調整が必要ならば、その部分のグラフィックだけSpriteにするなどでもなんとかなると思います。

また、描画する画面全体の大きさも処理速度に大きくかかわってくるので、必要最小限にしておきましょう。

インスタンスはむやみにnewしないで使いまわす

無駄にnewしないでインスタンスを使いまわすのは処理を軽くする基本ですね。BitmapDataを前もって生成しておくのと同じように、他のインスタンス生成もできるだけ使いまわします。

STGなんかだと敵やら弾やら、バンバンでてくるわりに消えるのもすぐです。出現するごとにnewして、必要なくなったら後はガーベージコレクタにまかせてしまうとかしていたら、大変なことになります。敵とか弾とかはどうせ似たようなモノなので、消えた後もインスタンスをブールしておいて、次の敵や弾の生成のときに使いまわしちゃいましょう。

まあ、前に作ったユキノネだとこれはやっていなかったので、弾が大量に出るシーンなどではけっこう処理落ちしたりしてましたね。ユキノネでは結局30FPSで妥協していました。

当たり判定は簡単に

ゲームにおいて、処理量が一番多く、不安定になりがちなのが当たり判定です。処理量を減らすために一つ一つの判定をできるだけ簡単にしましょう。FlashでありがちなhitTestはすごく重いので論外です。

当たり判定にもいろいろありますが、基本的には矩形判定が一番楽かつ処理も軽くていいと思います。あまり複雑にしすぎるとバグの元だし、2DSTGぐらいだとものすごく改善できるわけでもないので単純に(このあと少しだけ改善するのですが)。もちろん、当たり判定するごとに矩形を new Rectangle() するとかしてたらだめですよ?

STGで必要な当たり判定は、「自機と敵・敵弾」と「敵と自弾」があれば最低限です。あと、ゲームシステムによっては判定がもっと必要になりますね(地形判定・アイテム取得判定・弾消し判定・かすり判定などなど)

一番重要なのが「自機と敵・敵弾」で、この判定だけはしっかりやりましょう。被弾判定がおおざっぱすぎると、ストレスの元になります(某有名横シューの3作目とか)幸いなことに、1対多の判定なので、さほど処理量は多くなりません。矩形判定でやってもいいと思いますが、ここでは描画に使用したBitmapDataを利用する判定を考えます。

自機の当たり判定は1ピクセルと決めてしまって、その判定の部分に敵や敵弾が描画されているかどうかで判定するのです。

// buffer:BitmapData(敵・敵弾の描画領域), px,py:プレイヤーの当たり判定座標
if ((buffer.getPixel32(px, py) & 0xFF000000) != 0) {
	damaged(); // 被弾した!
}

といった感じ。大量の敵や敵弾との判定を一つ一つやる必要はなく、一発で判定できます。ただし、個々の敵・敵弾と判定しているわけではないので、ややおおざっぱな判定です。あと、敵弾ごとにダメージが違うとかいうことまでは判定できません。なので、「BitmapDataを利用して一気に判定 → 当たっていたらそれぞれ矩形判定する」などの方法があります。もちろん、BitmapData利用だと描画領域=当たり判定となるので、ゲームシステムによっては都合がわるいです。

ちなみに、パララライザの場合、被弾すると敵・敵弾に関わらずライフ1.0減少固定なので、BitmapDataでしか判定していません。「弾消し判定」もBitmapDataのみ。「敵と自弾」「アイテム判定」は個別に判定が必要なので、点と点との距離判定で行っています(矩形判定よりやや遅くおおざっぱですが、モノごとに当たり判定を設定する手間がほぼ無いので楽)。簡単に判定していてもまだ重い場合は、2フレームに1回しか判定しないとかするのもありだと思います。

ある程度厳密さや製作の手間は割り切ってしまって、それに合わせたゲーム内容にしてしまうというのも大切なことです。

もっと最適化

上の三つは処理に関わる一番大きいところで、これらだけでも一工夫すれば大抵は十分な速度が出せるのではと思います。

さらに、高速に処理を行うための細かいテクニックは他にも沢山あります。たいてい、AS3に限ったことではなく、他のほとんどの言語でも似たようなテクニックが通じます(むしろ逆)

あんまり最適化することを気にしすぎて製作が遅れたりするのも、わざわざFlashを作る意味があんまり無いので、最初は細かいことはほどほどでいいと思います。パララライザでもあんまりやってません。製作速度重視の構造が悪く汚い勢いで書いたコードになってます。いまはそれで十分だと思ってやってます。

まとめ

結局のところ、なんだかんだいってますが、古典的なゲーム製作テクニックそのままですね。

参考リンク

パララライザの製作にあたっても大変参考になりました。ありがとうございます。

弾幕STG パララライザ

パララライザ

Flash弾幕STGです。Flash Game Festival'08に出展させていただきました。


ユキノネでの反省点も踏まえつつ、今度は縦で弾幕STGを作りました。全3面+αで、1プレイ約10分です。

基本はただの弾幕STGですが、パラライザーショットで敵を弱らせて進むのが特徴です。ただ、使わなくても進めるようにはしています(使わないとけっこう厳しいけど)

処理速度をいろいろ改善して、Flash弾幕STGでもなんとか60FPS出せるように頑張りました。弾量的には、初期の弾幕モノと同じかそれ以上ぐらい撃ってます。今回出来たことと、新しい反省点とか今度まとめたいて書きたいと思います。

ユキノネ

弾消し横STGユキノネ」。


今回は「*50レス目のネタでFLASHを作れ Part44」の250番目のお題「雪の音」のためにがりがりつくってました。

敵をショットで凍らせて、氷で弾を消したり敵にダメージを与えたりする横STGです。魅せ弾が多いとはいえ、個人的には弾幕STGではないつもり。

前回同様、リプレイをスコアランキングに組み込んでます。

そんなにむずかしくないつもり(そもそも、作った人が東方HARDをやっとクリアできる程度の実力しかない)のですが、
やっぱりシステムや敵配置を把握している作った人にとっては簡単でも、普通の人がやるとそこそこの難易度っぽいです。稼がなければシールド貼りまくりで余裕だと思うんだけどなぁ。

あと、製作にあたってお題より先に何らかのテーマでSTG作りたいっていう思いがあったせいか、お題のほうはややおろそかになってる印象みたいです(雪をイメージしてるのは伝わるかと思うけど、雪の音かというとイマイチ)。ストーリー性とかも皆無だしね。そこは反省。

ソースを一応公開してますが、前回の勢いで作ったエンジンをだいぶ受け継いでるので設計が酷い感じ。でもおかげでゲームで必要な構造とかはつかめてきた気がします。3月以降になるかと思いますが、設計をきっちり作り直して、今度は縦で弾幕STGつくりたいなーと思ってます。

ネットでスコアランキングするならついでにリプレイもつければいいじゃない

紅白まんじゅうという簡単な避けゲーを作って、紅白Flash合戦2007の闇鍋祭に参加してきました(今更)

70秒ほど敵から逃げ続けるゲームです。真ん中の赤いところに敵を集めておしくらまんじゅう状態にしていると得点が上がります。

で、ついさっきスコアランキング+リプレイ表示機能をつけてみました。スコアランキングに投稿すると、リプレイも見られるようになります。

Normalモードのリプレイ

昨今のPCゲームにはリプレイできるものも珍しくないですが、Flashだとあまり見かけませんね。でも、動画サイトでゲームのプレイ動画を見る人がいる世の中ですから、需要はあるはず。

フレームごとに入力の整合性をとるのは少し面倒ですが、ようは「ゲームの設定」と「乱数の種」と「入力されたボタン」と「ボタンの入力時間」さえ受け渡しすればいいわけです。

ボタン入力処理をきちんと設計しておけば、リプレイ機能をつけるのもそんなに難しくないはず(私はちゃんと設計していなかったのでそこそこ苦労しましたが)

あと、スコアランキングに参加する人の中にはリプレイを見せたくない人もいると思うので、任意でリプレイの送信しないようにできるほうがよりベターでしょう。今回は勢いで作ったので細かいところまでやっていませんが。