テクニック:ロケーターアニメーション

ロケーターアニメーションとは、アニメーションツール上で動きを作成しつつその位置への描画を自前で行うことで、アニメーションの中身をリアルタイムに変更するためのテクニックです。

アニメーションツール上で動きを作成したい場合、まずは動きの原点が分かるように十字が描かれた画像を用意し、これを動かすことで動きを定義しますが、ここではこの十字型の画像をロケーターと言います。

このテクニックは、実はシステムスキンにて既にいくつかの箇所で使用されており、例えばプレイモード選択時のシングルかダブルの選択フレームや、ゲーム中のステージやレベル表示、結果画面での今回のランク表示など、アニメーションは固定でその中身だけが変更となる部分で多用しています。

これにより、スクリプトで複雑な動きをわざわざ作成する必要も無く、ロケーターの位置を基準に描画処理だけを実装しておけば、あとはデザイナーが思うように動きを作成するだけで済むため、プログラマーとデザイナーが動きをいちいち相談して決めるという手間が無くなります。

ちなみにこのロケーター部分にさらに別なアニメーションを再生させることで、Flashのような階層表示を行うことも可能です。
ステージ番号を表示してみる
ここでは参考として、システムスキンで使用されているステージ番号の表示方法について解説しています。

まず、アニメーションメーカーV2を使って下のような十字画像を追加登録します。
※特に十字である必要はありませんが、作るのが面倒な場合はこれをこのままダウンロードして使うことも可能です


そしてさらにこの画像の中心(十字の交わっている部分)を原点とします。
(取り込んだ画像のリストをダブルクリックで設定が可能)



なお、アニメーションの位置を正しく合わせるために、ここではゲームの背景画像も一緒にロードしておき一番下のレイヤーに置いておきます。

※作業しづらい場合は背景を半透明にしておくと良いかもしれません

そしてステージ番号のエリアの上にマスクレイヤーを置き、その下のレイヤーに実際に十字アニメーションを作成します。

※ここでは背景の左上(0,0)を原点としてステージ番号領域を指定していますが、この領域の中心などを原点として作成し、スクリプトの方で表示位置を指定するといった方法も可能です

アニメーションは大体以下のような感じにします。



最後にhda形式でデータをエクスポートしますが、書き出す際には背景画像が含まれないようにレイヤー表示をOFFにしてから書き出してください。
これでマスクとロケーターアニメーションのみが含まれたhdaファイルの作成が終了です。



なお、ここで重要なのはロケーターがどのレイヤーに位置しているかとなります。描画を行う場合、通常はレイヤーの一番下から描画が行われますが、コールバック付きの描画関数を使用すると、レイヤーの描画ごとに今回描画されるレイヤー番号が渡されるため、これを見て必要ならば自分で描画を行うことで、そのレイヤー内を自由に描画することが出来るわけです。ここではロケーターは一番下のレイヤーとなる(背景レイヤーを消去したため書き出し時に下に詰められる)ので、描画の際にはレイヤー0かをチェックすることになります。
※レイヤーの表示時にマスクがあればそれが適用された状態で描画されます



次に、このロケーター部分に表示されるステージ番号用の画像などを作成します。ゲームの仕様では1~3までは専用の画像、4以降は「数値+TH」の組み合わせで表示するので、ここでは以下のような画像を用意しておきます。



これらの画像を最終的には1枚のテクスチャにまとめる必要がありますが、手動で1枚にするのが面倒な場合は、別途HPで公開されているテクスチャメーカーを使用することで、一瞬でまとめることが出来ます。なお、ここではシステムスキンを例として説明しているため、以下の説明ではこれ以外の画像も含まれたテクスチャとなっています。



最後にこれをスクリプトで制御してみます。

まずはOnStartMainGame()関数内でステージ用画像のロード、切抜きと、アニメーションのロードを行います。なお、ここではステージ番号の含まれる画像をmn_object.tga、ステージ番号アニメーションはmn_stage_num.hdaとして作成しているものとし、また切り抜きIDをグローバル変数として定義しておき、画像の切り抜きや表示にはこの変数を使います。この方法ではあとでIDが変わったとしても、変数の値だけを更新することでプログラム側の変更は必要ないため、修正時間を短縮出来るメリットがあります。
// 切り抜きID
    :
mn_pid_stg_1st          = 13;
mn_pid_stg_2nd          = 14;
mn_pid_stg_3rd          = 15;
mn_pid_stg_num          = 16;
mn_pid_stg_th           = 17;
    :

///////////////////////////////////////////////////////////////////////
// ゲームが開始されると呼び出される。
///////////////////////////////////////////////////////////////////////
function OnStartMainGame()

        :
    // 画像
    hdxLoadImage( 0,"mn_object.tga" );
        :

    hdxSetPutRange( mn_pid_stg_1st      ,0, 312,72,70,16,0,0        );          // stg_1st
    hdxSetPutRange( mn_pid_stg_2nd      ,0, 416,64,78,16,0,0        );          // stg_2nd
    hdxSetPutRange( mn_pid_stg_3rd      ,0, 416,48,78,16,0,0        );          // stg_3rd
    hdxSetPutRange( mn_pid_stg_num      ,0, 0,64,312,20,0,0         );          // stg_num
    hdxSetPutRange( mn_pid_stg_th       ,0, 456,80,52,20,0,0        );          // stg_th// ステージ番号アニメ
    hdxLoadAnime( 11,"mn_stage_num.hda",0 );
    hdxCtrlAnime( 11,CTRLMODE_AUTO_LOOP,0,0,-1 );   // ループ
        :
end

次に実際の表示を行いますが、ここではコールバック対応のアニメーション描画関数を使用します。また、描画時にはコールバック関数として_mnStageNum()を呼び出させます。関数名は自由に付けることが出来るため、たくさんコールバックを作って呼び出し先を変えたり、アニメーションファイルごとにコールバックを分けるといったことも可能です。
function OnRunGame( upd_frm )
        :
    // ステージ番号アニメ
    hdxDrawAnime2( 11, 0, 0, 1.0, 1.0, "_mnStageNum" );
        :
end

コールバック関数は通常の関数のようにfunctionを使って実装します。ただしコールバック関数の引数は決まっているため、必ず以下のように引数を定義しておく必要があります。
// ステージ番号アニメ表示コールバック
function _mnStageNum( layer,x,y,frm_x,frm_y,frm_sx,frm_sy,frm_alpha,frm_rot )

    local game_info = hdxGetGameInfo();

    // 原点(中心)
    local _x = x + frm_x;
    local _y = y + frm_y;

    if( game_info["stage"]==1 ) then
        // 1st
        hdxPut( mn_pid_stg_1st, _x-39, _y-8 );              // 左位置の座標を算出して表示
    elseif( game_info["stage"]==2 ) then
        // 2nd
        hdxPut( mn_pid_stg_2nd, _x-39, _y-8 );              // 左位置の座標を算出して表示
    elseif( game_info["stage"]==3 ) then
        // 3rd
        hdxPut( mn_pid_stg_3rd, _x-39, _y-8 );              // 左位置の座標を算出して表示
    else
        // 4~99
        // ※数値1文字の幅は26px、thの幅は52px
        if( game_info["stage"]<10 ) then
            // 1桁(26+52=78px)
            hdxPutInt( mn_pid_stg_num, _x-39, _y-8, game_info["stage"], 1.0, ALIGNTYPE_LEFTTOP );
            hdxPut( mn_pid_stg_th, _x-13, _y-8 );
        else
            // 2桁(26*2+52=104px)
            hdxPutInt( mn_pid_stg_num, _x-52, _y-8, game_info["stage"], 1.0, ALIGNTYPE_LEFTTOP );
            hdxPut( mn_pid_stg_th, _x-0, _y-8 );
        end
    end

    return TRUE;
end

このコールバック関数はhdxDrawAnime2()が呼び出されると、内部でレイヤーの描画が行われるごとに呼び出されます。この関数の戻り値にFALSEを返すことで通常のアニメ表示が行われますが、自前で描画を行った場合はTRUEを返すことで、通常の描画はスキップされるようになります。これによりロケーターのレイヤー番号を把握して、その時に自分で描画処理を行うようにすれば、そのレイヤーの画像を任意に書き換えるといったことが出来るというわけです。
(自分で描画をしつつFALSEを返すことで、通常アニメの描画も行うといったことも出来ますが、仕様上は通常のアニメは必ず自前描画のあとに描画されてしまいます)

さて、引数のlayerというのが上記で説明したロケーターのレイヤー番号で、ここでは0でなければ元のアニメ内画像を描画するようにFALSEを返しています。なお、レイヤー番号をチェックするということは、1つのアニメに複数のロケーターを仕込むといったことも出来るということです。

このコールバックについて解説すると、まずは最初にhdxGetGameInfo()にて現在のステージ番号を取得します。

次に最終的な画面上でのロケーターの原点座標を求めています。これは引数のx、yfrm_x、frm_yから求めることが出来ます。x、yhdxDrawAnime2()に渡されたx、y座標そのままの値で、frm_x、frm_yはアニメーション処理により計算されたローカルのx、y座標が入っています。これを足し算することでアニメーション込みでの画面上での座標が計算出来ます。なお、この座標はアニメーションツール上での画像の原点(ここでは十字画像の中心)を指しているため、実際に画像を表示する場合はこれを考慮して表示します。
※それ以外に引数としてスケール値やアルファ、回転情報が含まれますが、これらの情報を使って描画を行う場合は自分で計算しなければなりません

最後に現在のステージ番号を確認して1~3なら専用の画像、それ以降なら「数値+TH」の画像を表示しています。なお、4~9THと10TH~で処理が分かれていますが、これは1桁と2桁で中心位置が変わるからで、このあたりは実装次第でどうにでも出来ると思います。
※現在charatbeatHDXでは内部的な最大ステージ数は99に設定されており、それ以上のステージをプレイしても99以上にはなりません

これらのソースはシステムスキンのソースコードに含まれるので必要ならばそちらを参考にしてください。
※システムスキンには実はif( layer==0 ) then~endの部分は記述されていません。これはマスクレイヤーはコールバックの対象ではないので、この場合は常にレイヤー0の描画しか来ないためです。ここでは分かりやすくするため、特定のレイヤーかをチェックする部分を敢えて実装しています。