シーン:メインゲーム

ここではメインのゲーム画面の制御方法について説明しています。

ゲーム画面では実際にユーザーがゲームをプレイするための画面の制御と、オプションのスクロールスピードの調整を行います。オプションはSTARTボタンのON/OFFにより、ゲームの入力かオプション変更かが自動的に処理されます。なお、オプションの表示と非表示が切り替わる際には特定のコールバックが呼び出されるので、これを監視することで制御します。

曲の解析(WAV、BGAのロード含む)と、実際にゲームを開始させるのはスクリプト側から指示を出す必要があります。曲選択画面で決定された曲情報はシステムが保持していますが、この画面が開始された直後はまだ何もロードされていません。ロードにはある程度時間がかかるため、画面が長時間真っ暗状態となるのを防ぐために、ロード中はタイトル名などを表示するなどしてユーザーを飽きさせないような演出を行います。
外部スクリプトファイル
システムスキンのサンプルでは、メインゲーム画面の処理は_06_game.luaファイルにまとめられています。script.luaからこのファイルをinclude()関数を使って取り込むことで、最終的にscript.luaに定義されたものとして読み込まれます。
スクリプトによる制御方法
メインゲーム画面に遷移するとOnStartMainGame()が呼び出されるので、ここでメインゲームで使用するリソースをロードしたり変数の初期化などを行います。

次にメインゲーム中は毎フレームOnRunGame()が呼ばれるので、ここで演算と画面描画などを行います。

ゲーム画面直後の開始演出が完了したらhdxStartLoading()を呼び出して、BMSの解析とWAV、BGAなどのロードを開始させます。なお、ロード処理は別スレッドにて行われるため、その間もOnRunGame()は毎フレーム呼び出されます。BMSの解析やWAV、BGAのロード進行状態はOnGameLoading()にて通知されるので、これを利用してタイトル名の表示やロード中の%ゲージなどを制御することが出来ます。

タイトル用の画像が用意されている場合は、hdxDrawTitle()を呼び出すことで任意の位置に表示することが出来ます。タイトル画像はBMS内に#STAGEFILE#BACKBMPが存在する場合で、さらに正しくロード出来た場合に利用可能です。タイトル用の画像が存在しない場合や自前でタイトル名を表示したい場合は、タイトル名からフォントテクスチャなどを生成してそれを表示します。
タイトル名などの曲情報はhdxGetGameData()を呼び出すことで取得出来ますが、この関数はOnGameLoading()LOADEVENT_BMS_SUCCESS(BMS解析完了)イベント以降でなければ正しい情報を返しません。

OnGameLoading()LOADEVENT_COMPLETEイベントが通知されるとゲームの準備が完了したことになります。これ以降に任意のタイミングでhdxStartGame()を呼び出すことで実際にゲームが開始されます。

レーンを表示するには任意のタイミングでhdxSysDrawLane()を呼び出します。例えば背景を表示した後にレーンを描画したい場合、まず自前の画像を表示させ、そのあとでこの関数を呼び出してレーンを表示します。ちなみにその後さらに自前の画像などを表示することで、レーンの上にオーバーラップさせることが可能です。これを利用して判定文字などを描画することが出来ます。

hdxSysDrawLane()を呼び出すと、その中でレーン背景、オブジェ、レーンカバーなどの要素が描画されるごとにOnDrawLane()が呼び出されます。例えば白数字や緑数字はレーンカバーの上に描画する必要がありますが、このコールバック内でレーンカバー描画後を判断して、その上に白数字や緑数字を表示するといったことが可能です。コールバックは要素ごとに画面がクリッピングされた状態で呼び出されるため、そのまま白数字や緑数字を表示するだけで、はみ出さずに描画が可能です。

白数字や緑数字を描画する場合はレーンカバーの現在の座標が必要となりますが、これはhdxGetCoverInfo()にて取得することが出来ます。また、レーンカバーが設定されているかはhdxGetGameInfo()にて取得することが出来るので、まずはこれを利用してレーンカバーの状態を把握し、必要ならばレーンの座標を取得して指定の位置に数値を表示します。

ゲーム中に表示されるBGA(動画含む)は、hdxDrawBga()を呼び出すことで任意の位置とサイズで表示出来ます。この関数は1フレーム内で何度も呼び出すことが出来るため、ダブルプレイ時の4画面表示なども可能です。もしこの関数から0が返った場合、BGAがまだ指定されていない状態となり、その場合は何も描画されません(黒画像なども表示されません)。これを利用して、0の場合は独自の画像を表示しておくといった事も可能です。

ゲーム中にオブジェの判定が行われるとOnGameJadge()が呼び出されます。ここでレーンの上に判定結果を表示するためのカウンタ制御などを行います。なお、通常のオブジェは判定結果を一定時間で消去しますが、ロング中はロングが終了するまでずっと表示した状態にしなければなりません。このため、このコールバックに連続表示が必要かが引数に渡されるので、これを利用して連続か一定時間で消去するか制御してください。
※ロング終了時(成功や失敗は関係なく)は必ず一定時間消去が通知されるので、曲終了時は判定文字は必ず消えている状態となるはずです

全てのオブジェ判定が終わった時にフルコンボだった場合、OnFullCombo()が呼び出されます。ここでフルコンボアニメーションを開始させるトリガーとして使用出来ます。

ゲームが終了するとOnEndGame()が呼び出されます。これ以降にhdxEndScene()を呼び出すことでゲームシーンを終了させることが出来ます。なお、フルコンボ時はフルコンボアニメーションが終わっていない場合があるため、必要ならばフルコンボアニメーションの終了を待ってからhdxEndScene()を呼び出すようにしてください。



この画面上の任意のタイミングでSTARTボタンが押されると、OnStartGameOption()が呼び出されてスクロールスピード変更状態となります。その後、STARTボタンが離されるとOnEndGameOption()が呼び出されてゲームプレイ状態に戻ります。

スクロールスピードの調整には曲選択画面と共通のコールバックOnChangeScrollSpeed()が呼び出されます。このコールバックの引数には、ボタンによるデジタル値か、外部入力プラグイン使用時はスクラッチを回した分のアナログ値が入る(予定)ので、これを利用して最終的なスクロールスピードを計算して返します。曲選択なのかメインゲーム中なのかは引数に渡されるので、シーンごとに異なる計算式を使用するといったことも可能です。スクロールスピード値が規定値以外だった場合、曲選択時と同様にシステム側で補正されますので、最終的なスクロールスピードはhdxGetGameInfo()にて取得してください。
サンプルコード
このサンプルでは以下の処理を行っています。

ゲーム部分の処理

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ゲーム画面
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MNST_START          = 0;                // ゲームフレームアニメ
MNST_LOADING        = 1;                // ローディング中
MNST_TITLE          = 2;                // タイトル表示
MNST_WAIT           = 3;                // スタート待ち(タイトルも表示)
MNST_GAME           = 4;                // ゲーム中
MNST_END            = 5;                // 終了状態(フルコンボアニメがあれば終了まで待機)
MNST_FINISH         = 6;                // フェードアウト

MN_BASEPOS_X        = 1280 / 2;         // 画面中心位置
MN_TITLEPOS_Y       = 220;              // タイトル名の中心位置
MN_TITLEWIDTH       = 640;              // タイトル幅
MN_TITLEHEIGHT      = 80;               // タイトル高さ
MN_GENREPOS_Y       = 160;              // ジャンル名の中心位置
MN_GENREWIDTH       = 240;              // ジャンル幅
MN_GENREHEIGHT      = 20;               // ジャンル高さ
MN_ARTISTPOS_Y      = 420;              // アーティストの中心位置
MN_ARTISTWIDTH      = 200;              // アーティスト幅
MN_ARTISTHEIGHT     = 32;               // アーティスト高さ

// #STAGEFILE(4:3)
MN_STAGEFILE_WIDTH  = 640;              // 表示幅
MN_STAGEFILE_HEIGHT = 480;              // 表示高さ

// #BACKBMP(正方形)
MN_BACKBMP_WIDTH    = 480;              // 表示幅
MN_BACKBMP_HEIGHT   = 480;              // 表示高さ

// 切り抜きID
mn_pid_gauge0       = 0;
mn_pid_gauge1       = 1;
mn_pid_lv_err       = 2;
mn_pid_lv_num0      = 3;
mn_pid_lv_num1      = 4;
mn_pid_lv_num2      = 5;
mn_pid_num_g        = 6;
mn_pid_num_g2       = 7;
mn_pid_num_mill     = 8;
mn_pid_num_normal   = 9;
mn_pid_num_small    = 10;
mn_pid_num_w        = 11;
mn_pid_num_wide     = 12;
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;
mn_pid_str_level0   = 18;
mn_pid_str_level1   = 19;
mn_pid_str_level2   = 20;
mn_pid_str_level3   = 21;

mn_pid_GREAT1       = 30;
mn_pid_GREAT2       = 31;
mn_pid_GREAT3       = 32;
mn_pid_GREAT4       = 33;
mn_pid_GOOD         = 34;
mn_pid_POOR         = 35;
mn_pid_BAD          = 36;
mn_pid_NUM_WHITE    = 37;
mn_pid_NUM_BLUE     = 38;
mn_pid_NUM_RED      = 39;
mn_pid_NUM_YELLOW   = 40;

g_dwMainState       = MNST_START;       // メインゲームステート
g_bLoadComplete     = FALSE;            // ロードが完了したか
g_iLoadPercent      = 0;                // ゲームローディングパーセント値
g_iTitleFadeCount   = 0;                // タイトル名のフェードイン(0~30/60fpsで+1)
g_dwWaitTitleTime   = 0;                // タイトル名を最低限表示させる時間
g_bGameOver         = FALSE;            // ゲームオーバーしたか
g_iJadgeMode        = { -1,-1 };        // 判定表示状態(-1=非表示、0=連続表示中、1以上=ワンショット時の点滅終了時間)
g_iJadgeCount       = { 0,0 };          // 判定点滅カウンタ
g_iJadgeType        = { 0,0 };          // 判定タイプ(JADGE_PKGREAT~JADGE_POOR)
g_iJadgeCombo       = { 0,0 };          // GOOD以上の場合のコンボ数

// スクロールスピード変更用
MN_SCRPOS_X1    = { 306,974,640 };      // 隠れ位置
MN_SCRPOS_Y1    = { 343,343,-480 };
MN_SCRPOS_X2    = { 370,910,640 };      // オープン位置
MN_SCRPOS_Y2    = { 343,343,180 };
MN_SCRPOS_FRAME = 8;                    // STARTボタン押下時のスクロールスピードのアニメフレーム数
g_bScrPos       = FALSE;                // スクロールスピードアニメが開かれているか
g_iScrPosFrame  = 0;                    // 現在のスクロールスピードアニメのフレーム状態

///////////////////////////////////////////////////////////////////////
// ゲームが開始されると呼び出される。
///////////////////////////////////////////////////////////////////////
function OnStartMainGame()
    // 初期化
    g_dwMainState       = MNST_START;
    g_bLoadComplete     = FALSE;
    g_iLoadPercent      = 0;
    g_iTitleFadeCount   = 0;
    g_dwWaitTitleTime   = 0;
    g_bGameOver         = FALSE;
    g_iJadgeMode        = { -1,-1 };
    g_iJadgeCount       = { 0,0 };
    g_iJadgeType        = { 0,0 };
    g_iJadgeCombo       = { 0,0 };
    g_bScrPos           = FALSE;
    g_iScrPosFrame      = 0;


    // 画像
    hdxLoadImage( 0,"mn_object.tga" );
    hdxLoadImage( 1,"mn_jadge.tga" );
    // 切り抜き
    hdxSetPutRange( mn_pid_gauge0       ,0, 472,24,6,24,0,0         );          // gauge0
    hdxSetPutRange( mn_pid_gauge1       ,0, 504,0,6,24,0,0          );          // gauge1
    hdxSetPutRange( mn_pid_lv_err       ,0, 464,0,40,18,0,0         );          // lv_err
    hdxSetPutRange( mn_pid_lv_num0      ,0, 216,88,240,18,0,0       );          // lv_num0
    hdxSetPutRange( mn_pid_lv_num1      ,0, 216,112,240,18,0,0      );          // lv_num1
    hdxSetPutRange( mn_pid_lv_num2      ,0, 0,136,240,18,0,0        );          // lv_num2
    hdxSetPutRange( mn_pid_num_g        ,0, 0,88,216,20,0,0         );          // num_g
    hdxSetPutRange( mn_pid_num_g2       ,0, 360,24,72,8,0,0         );          // num_g2
    hdxSetPutRange( mn_pid_num_mill     ,0, 432,24,14,7,0,0         );          // num_mill
    hdxSetPutRange( mn_pid_num_normal   ,0, 0,32,300,26,0,0         );          // num_normal
    hdxSetPutRange( mn_pid_num_small    ,0, 304,32,168,14,0,0       );          // num_small
    hdxSetPutRange( mn_pid_num_w        ,0, 0,112,216,20,0,0        );          // num_w
    hdxSetPutRange( mn_pid_num_wide     ,0, 0,0,360,26,0,0          );          // num_wide
    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
    hdxSetPutRange( mn_pid_str_level0   ,0, 360,0,100,18,0,0        );          // str_level0
    hdxSetPutRange( mn_pid_str_level1   ,0, 312,48,100,18,0,0       );          // str_level1
    hdxSetPutRange( mn_pid_str_level2   ,0, 240,136,100,18,0,0      );          // str_level2
    hdxSetPutRange( mn_pid_str_level3   ,0, 344,136,100,18,0,0      );          // str_level3

    hdxSetPutRange( mn_pid_GREAT1       ,1, 0,0,160,54,0,0          );          // GREAT1
    hdxSetPutRange( mn_pid_GREAT2       ,1, 160,0,160,54,0,0        );          // GREAT2
    hdxSetPutRange( mn_pid_GREAT3       ,1, 320,0,160,54,0,0        );          // GREAT3
    hdxSetPutRange( mn_pid_GREAT4       ,1, 0,54,160,54,0,0         );          // GREAT3
    hdxSetPutRange( mn_pid_GOOD         ,1, 160,54,160,54,0,0       );          // GOOD
    hdxSetPutRange( mn_pid_POOR         ,1, 320,54,160,54,0,0       );          // POOR
    hdxSetPutRange( mn_pid_BAD          ,1, 0,108,160,54,0,0        );          // BAD
    hdxSetPutRange( mn_pid_NUM_WHITE    ,1, 0,162,360,54,0,0        );          // NUM_WHITE
    hdxSetPutRange( mn_pid_NUM_BLUE     ,1, 0,216,360,54,0,0        );          // NUM_BLUE
    hdxSetPutRange( mn_pid_NUM_RED      ,1, 0,270,360,54,0,0        );          // NUM_RED
    hdxSetPutRange( mn_pid_NUM_YELLOW   ,1, 0,324,360,54,0,0        );          // NUM_YELLOW

    // ロード
    local game_info = hdxGetGameInfo();

    if( game_info["pmode"]==PLAYMODE_DOUBLE ) then
        // ダブル
        hdxLoadAnime( 0,"mn_frm_double.hda",0 );
        hdxCtrlAnime( 0,CTRLMODE_AUTO_END,0,0,95 );     // レーンありで停止
    else
        // シングル
        hdxLoadAnime( 0,"mn_frm_single.hda",0 );
        hdxCtrlAnime( 0,CTRLMODE_AUTO_END,0,0,60 );     // レーンありで停止
    end

    hdxLoadAnime( 1,"mn_fullcombo.hda",0 );             // フルコンボアニメ
    hdxCtrlAnime( 1,CTRLMODE_MANUAL,-1,0,-1 );          // 停止&非表示
    
    hdxLoadAnime( 2,"mn_finish.hda",0 );                // フェードアウトアニメ
    hdxCtrlAnime( 2,CTRLMODE_MANUAL,-1,0,-1 );          // 停止&非表示

    hdxLoadAnime( 3,"mn_speed.hda",0 );                 // フェードアウトアニメ
    hdxCtrlAnime( 3,CTRLMODE_MANUAL,-1,0,-1 );          // 停止&非表示

    hdxLoadAnime( 4,"mn_time.hda",0 );                  // タイムランプ
    hdxCtrlAnime( 4,CTRLMODE_AUTO_LOOP,0,0,-1 );        // ループ再生

    // パネル内のゲームモードアニメ
    if( game_info["demo"]==TRUE ) then
        hdxLoadAnime( 10,"mn_panel_demo.hda",0 );
    elseif( game_info["gmode"]==GAMEMODE_BEGINNER ) then
        hdxLoadAnime( 10,"mn_panel_beginner.hda",0 );
    elseif( game_info["gmode"]==GAMEMODE_STANDARD ) then
        hdxLoadAnime( 10,"mn_panel_standard.hda",0 );
    elseif( game_info["gmode"]==GAMEMODE_EXPERT ) then
        hdxLoadAnime( 10,"mn_panel_expert.hda",0 );
    elseif( game_info["gmode"]==GAMEMODE_CLASS ) then
        hdxLoadAnime( 10,"mn_panel_class.hda",0 );
    elseif( game_info["gmode"]==GAMEMODE_FREE ) then
        hdxLoadAnime( 10,"mn_panel_free.hda",0 );
    end
    hdxCtrlAnime( 10,CTRLMODE_AUTO_LOOP,0,0,-1 );   // ループ

    // ステージ番号アニメ
    hdxLoadAnime( 11,"mn_stage_num.hda",0 );
    hdxCtrlAnime( 11,CTRLMODE_AUTO_LOOP,0,0,-1 );   // ループ

    // レベルアニメ
    hdxLoadAnime( 12,"mn_level_num.hda",0 );
    hdxCtrlAnime( 12,CTRLMODE_AUTO_LOOP,0,0,-1 );   // ループ
end


///////////////////////////////////////////////////////////////////////
// ゲーム中に毎フレーム呼び出されるので、ここで演算と描画処理を行う。
///////////////////////////////////////////////////////////////////////
function OnRunGame( upd_frm )
    local game_data     = hdxGetGameData();
    local game_info     = hdxGetGameInfo();
    local cover_info    = hdxGetCoverInfo();
    local conf          = hdxGetConfig();

    ///////////////////////////////////
    // 処理
    ///////////////////////////////////
    if( g_dwMainState==MNST_START ) then
        // フレーム表示中
        if( hdxIsEndAnime(0)==TRUE ) then
            // フレームが全て表示されたらBMSロードを開始
            hdxStartLoading();
            g_dwMainState = MNST_LOADING;
        end
    elseif( g_dwMainState==MNST_LOADING ) then
        // ローディング中
    elseif( g_dwMainState==MNST_TITLE ) then
        // 曲名表示かスタート待ちなら
        if( upd_frm==TRUE ) then
            // 60fpsでフェードイン
            g_iTitleFadeCount = g_iTitleFadeCount + 1;
            if( g_iTitleFadeCount>30 ) then
                g_iTitleFadeCount = 30;
            end
        end
    elseif( g_dwMainState==MNST_WAIT ) then
        if( upd_frm==TRUE ) then
            // 60fpsでフェードイン
            g_iTitleFadeCount = g_iTitleFadeCount + 1;
            if( g_iTitleFadeCount>30 ) then
                g_iTitleFadeCount = 30;
            end
        end
        if( hdxGetTime()>=g_dwWaitTitleTime ) then
            // 最低3秒間表示し終わっていたらゲーム開始
            hdxStartGame();
            g_dwMainState = MNST_GAME;
        end
    elseif( g_dwMainState==MNST_GAME ) then
        // ゲーム中
    elseif( g_dwMainState==MNST_END ) then
        // 終了待ち
        if( hdxIsEndAnime(1)==TRUE ) then
            // フルコンボアニメ終了ならゲーム終了
            // ※フルコンボ以外ではすでに終了済みとなっているので即座に通る
            if( g_bGameOver==TRUE ) then
                hdxCtrlAnime( 2,CTRLMODE_AUTO_END,60,0,180 );       // ゲームオーバーなら遅いフェード
            else
                hdxCtrlAnime( 2,CTRLMODE_AUTO_END,0,0,30 );         // 通常の終了なら早いフェード
            end
            g_dwMainState = MNST_FINISH;
        end
    elseif( g_dwMainState==MNST_FINISH ) then
        // フェードアウト待ち
        if( hdxIsEndAnime(2)==TRUE ) then
            trace( "ゲーム処理終了" );
            hdxNextScene();
            return;
        end
    end
    
    // 判定カウンタ
    if( upd_frm==TRUE ) then
        // 60fpsで制御
        for i=1,2 do
            if( g_iJadgeMode[i]>=0 ) then
                // 表示状態なら
                if( g_iJadgeMode[i]!=0 && hdxGetTime()>=g_iJadgeMode[i] ) then
                    // ワンショットでタイムアウトなら非表示にする
                    g_iJadgeMode[i] = -1;
                    break;
                end
                // 表示中ならアニメカウンタを加算(0~7でループ)
                g_iJadgeCount[i] = (g_iJadgeCount[i] + 1) % 8;
            end
        end
    end

    // スクロールアニメフレームの制御
    if( g_bScrPos==TRUE ) then
        // STARTボタン押下中なら開く
        g_iScrPosFrame = g_iScrPosFrame + 1;
        if( g_iScrPosFrame>MN_SCRPOS_FRAME ) then
            g_iScrPosFrame = MN_SCRPOS_FRAME;
        end
    else
        // 押されていないなら閉じる
        g_iScrPosFrame = g_iScrPosFrame - 1;
        if( g_iScrPosFrame<0 ) then
            g_iScrPosFrame = 0;
        end
    end




    ///////////////////////////////////
    // 描画
    ///////////////////////////////////

    // BGAの表示
    hdxSetFilterMode( FILTER_LINEAR );                  // バイリニア
    if( game_info["pmode"]==PLAYMODE_DOUBLE ) then
        // ダブル時
        hdxDrawBga(   30, 37, 236, 272, 1.0 );
        hdxDrawBga(   30,320, 236, 272, 1.0 );
        hdxDrawBga( 1014, 37, 236, 272, 1.0 );
        hdxDrawBga( 1014,320, 236, 272, 1.0 );
    else
        // シングル時
        hdxDrawBga( 333, 0, 614, 614, 1.0 );
    end
    hdxSetFilterMode( FILTER_POINT );

    // スクロールスピードパネルとメインフレーム、イコライザの表示
    if( game_info["pmode"]==0 ) then
        // シングル1P
        _mnDrawEqualizer( 0,139,551 );                                      // イコライザー
        _mnDrawScrSpeed( 0,game_info["opt_speed"] );                        // シングル時はメインフレームより奥に表示
        hdxDrawAnime( 0, 0, 0, 1.0, 1.0 );                                  // メインフレーム
    elseif( game_info["pmode"]==1 ) then
        // シングル2P
        _mnDrawEqualizer( 0,983,551 );                                      // イコライザー
        _mnDrawScrSpeed( 1,game_info["opt_speed"] );                        // シングル時はメインフレームより奥に表示
        hdxDrawAnime( 0, 0, 0, 1.0, 1.0 );                                  // メインフレーム
    else
        // ダブル
        _mnDrawEqualizer( 1,304,575 );                                      // 左イコライザー
        _mnDrawEqualizer( 1,880,575 );                                      // 右イコライザー
        hdxDrawAnime( 0, 0, 0, 1.0, 1.0 );                                  // メインフレーム
        _mnDrawScrSpeed( game_info["pmode"],game_info["opt_speed"] );       // ダブル時はメインフレームより前に表示
    end

    // タイムライト
    if( g_dwMainState>=MNST_LOADING ) then
        local _mul = 1.0;                               // 一番下で初期化
        if( g_dwMainState<MNST_GAME ) then
            // ローディングの進行度
            _mul = 1.0 - g_iLoadPercent / 100;          // 下から上へ移動する
        elseif( g_dwMainState>=MNST_GAME ) then
            _mul = game_data["game_progress"];          // 上から下へ移動する
        end
        _mnDrawTimeLight( game_info["pmode"],_mul );
    end

    // タイトルステート以降ならシステムレーン描画
    if( g_dwMainState>=MNST_TITLE ) then
        // レーン表示
        hdxSysDrawLane();

        // 判定結果(中心座標)
        local RESULT_POS_X = {
            { 188,1092 },               // シングル時の1Pと2Pの位置
            { 456,824 }                 // ダブル時の1Pと2Pの位置
        };

        // 左右のレーンを同時に処理
        for i=1,2 do
            local base_x;
            // 中心原点を取得
            if( game_info["pmode"]==PLAYMODE_DOUBLE ) then
                base_x = RESULT_POS_X[2][i];                    // ダブル時(配列は1から)
            else
                base_x = RESULT_POS_X[1][i];                    // シングル時(配列は1から)
            end

            if( g_iJadgeMode[i]!=-1 ) then
                // コンボの桁数
                local combo = g_iJadgeCombo[i];                 // 現在のレーンに表示するコンボ数
                local keta = 1;                                 // 桁(0も1桁とする)
                // 10で割って1の位で止める
                while combo>=10 do
                    keta = keta + 1;                            // 10以上なら桁加算
                    combo = math.floor( combo / 10 );
                end

                // 判定文字の位置と点滅状態の算出
                local jadge_x   = 0;                                // 判定文字の左上表示X座標
                local jadge_y   = 354 - cover_info["lift_offset"];  // 判定文字の左上表示Y座標(LIFTがあれば上昇)
                local jadge_id  = -1;                               // 判定文字画像ID(-1=非表示)
                local combo_id  = -1;                               // コンボ数画像ID(-1=非表示)
                if( g_iJadgeType[i]==JADGE_PKGREAT ) then
                    jadge_x     = base_x - math.floor((160 + keta * 30) / 2);               // コンボの桁分を考慮して左上位置を算出
                    jadge_id    = mn_pid_GREAT1 + math.floor(g_iJadgeCount[i] / 2);
                    combo_id    = mn_pid_NUM_WHITE + math.floor(g_iJadgeCount[i] / 2);
                elseif( g_iJadgeType[i]==JADGE_GREAT ) then
                    if( ((g_iJadgeCount[i]/2)%2)==1 ) then
                        // 点滅させる
                        jadge_x     = base_x - math.floor((160 + keta * 30) / 2);           // コンボの桁分を考慮して左上位置を算出
                        jadge_id    = mn_pid_GREAT4;
                        combo_id    = mn_pid_NUM_YELLOW;
                    end
                elseif( g_iJadgeType[i]==JADGE_GOOD ) then
                    if( ((g_iJadgeCount[i]/2)%2)==1 ) then
                        // 点滅させる
                        jadge_x     = base_x - math.floor((160 + keta * 30) / 2);           // コンボの桁分を考慮して左上位置を算出
                        jadge_id    = mn_pid_GOOD;
                        combo_id    = mn_pid_NUM_YELLOW;
                    end
                elseif( g_iJadgeType[i]==JADGE_BAD ) then
                    if( ((g_iJadgeCount[i]/2)%2)==1 ) then
                        // 点滅させる
                        jadge_x     = base_x - 160 / 2;
                        jadge_id    = mn_pid_BAD;
                    end
                elseif( g_iJadgeType[i]==JADGE_EMPTY_POOR || g_iJadgeType[i]==JADGE_POOR ) then
                    if( ((g_iJadgeCount[i]/2)%2)==1 ) then
                        // 点滅させる
                        jadge_x     = base_x - 160 / 2;
                        jadge_id    = mn_pid_POOR;
                    end
                end

                // 判定文字
                if( jadge_id>=0 ) then
                    // 画像が指定されている場合のみ表示(PKGREAT以外は点滅する)
                    hdxPut( jadge_id,jadge_x,jadge_y );
                end
                // コンボ数字
                if( combo_id>=0 ) then
                    // 画像が指定されている場合のみ表示(BADやPOOR時は表示されない)
                    hdxPutInt( combo_id, jadge_x+160, jadge_y, g_iJadgeCombo[i], 1.0, ALIGNTYPE_LEFTTOP );
                end
            end
        end
    end

    // フレーム内のゲージや数値類
    if( g_dwMainState>=MNST_LOADING ) then
        local mode;
        if( game_info["opt_difficult"]==OPTDIFFICULT_HARD || game_info["gmode"]==GAMEMODE_EXPERT || game_info["gmode"]==GAMEMODE_CLASS ) then
            mode = mn_pid_gauge1;           // 赤ゲージ
        else
            mode = mn_pid_gauge0;           // 通常ゲージ
        end

        // ゲージ
        GAUGE_POS_X = { 49,833,441 };                               // 左上X座標(配列は1から)
        _DrawGauge( mode,GAUGE_POS_X[game_info["pmode"]+1],572,game_data["game_gauge_meter"] );

        // ゲージ値
        local GAUGE2_POS_X = { 417,901,670 };                               // 左上X座標(配列は1から)
        local GAUGE2_POS_Y = { 558,558,528 };                               // 左上Y座標(配列は1から)
        hdxPutInt( mn_pid_num_normal, GAUGE2_POS_X[game_info["pmode"]+1], GAUGE2_POS_Y[game_info["pmode"]+1], game_data["game_gauge"], 1.0, ALIGNTYPE_RIGHTBOTTOM );

        // BPM
        local BPM_POS_X = { 679,679,682 };                              // 左上X座標(配列は1から)
        hdxPutInt( mn_pid_num_wide, BPM_POS_X[game_info["pmode"]+1], 696, game_data["game_bpm"], 1.0, ALIGNTYPE_RIGHTBOTTOM );

        // スコア
        local SCORE_POS_X = { 290,1237,290 };                               // 右下X座標(配列は1から)
        local SCORE_POS_Y = { 646,646,661 };                                // 右下Y座標(配列は1から)
        hdxPutInt( mn_pid_num_wide, SCORE_POS_X[game_info["pmode"]+1], SCORE_POS_Y[game_info["pmode"]+1], game_data["game_score"], 1.0, ALIGNTYPE_RIGHTBOTTOM );

        // MAXコンボ
        local COMBO_ID  = { mn_pid_num_small,mn_pid_num_small,mn_pid_num_wide };
        local COMBO_POS_X = { 269,1216,1232 };
        local COMBO_POS_Y = { 666,666,661 };
        hdxPutInt( COMBO_ID[game_info["pmode"]+1], COMBO_POS_X[game_info["pmode"]+1], COMBO_POS_Y[game_info["pmode"]+1], game_data["game_maxcombo"], 1.0, ALIGNTYPE_RIGHTBOTTOM );
    end

    // ゲームモードアニメ
    hdxDrawAnime( 10, 0, 0, 1.0, 1.0 );

    // ステージ番号アニメ
    hdxDrawAnime2( 11, 0, 0, 1.0, 1.0, "_mnStageNum" );

    // レベルアニメ
    hdxDrawAnime2( 12, 0, 0, 1.0, 1.0, "_mnLevelNum" );

    // タイトル名
    if( g_dwMainState==MNST_TITLE || g_dwMainState==MNST_WAIT ) then
        // タイトルかロード中ステートなら表示
        hdxSetBlendMode( BLEND_ADD );                   // 加算合成
        hdxSetFilterMode( FILTER_LINEAR );              // バイリニア
        local alpha = g_iTitleFadeCount / 30.0;

        // STARTを押している場合は半透明に(スクロール速度変更フレームが見えるように)
        inp = hdxSysGetInput();                         // キー状態を取得
        if( inp["is_press_start"]==TRUE ) then
            alpha = alpha * 0.3;
        end

        // ゲームパラメータからタイトル名のタイプを取得
        if( game_data["type"]==0 ) then
            // 文字列によるタイトル名表示なら

            // タイトル名
            local sz = hdxGetFontSize( 0 );                 // タイトル名フォントのサイズ
            if( sz["width"]<=MN_TITLEWIDTH ) then
                hdxDrawFontEx( 0,MN_BASEPOS_X-sz["width"]/2,MN_TITLEPOS_Y-MN_TITLEHEIGHT/2,sz["width"],MN_TITLEHEIGHT,alpha );              // 指定のサイズ以下なら等倍表示
            else
                hdxDrawFontEx( 0,MN_BASEPOS_X-MN_TITLEWIDTH/2,MN_TITLEPOS_Y-MN_TITLEHEIGHT/2,MN_TITLEWIDTH,MN_TITLEHEIGHT,alpha );          // はみ出す場合はスケーリング表示
            end

            // ジャンル名
            sz = hdxGetFontSize( 1 );                   // ジャンル名フォントのサイズ
            if( sz["width"]<=MN_GENREWIDTH ) then
                hdxDrawFontEx( 1,MN_BASEPOS_X-sz["width"]/2,MN_GENREPOS_Y-MN_GENREHEIGHT/2,sz["width"],MN_GENREHEIGHT,alpha );              // 指定のサイズ以下なら等倍表示
            else
                hdxDrawFontEx( 1,MN_BASEPOS_X-MN_GENREWIDTH/2,MN_GENREPOS_Y-MN_GENREHEIGHT/2,MN_GENREWIDTH,MN_GENREHEIGHT,alpha );          // はみ出す場合はスケーリング表示
            end

            // アーティスト名
            sz = hdxGetFontSize( 2 );                   // アーティスト名フォントのサイズ
            if( sz["width"]<=MN_ARTISTWIDTH ) then
                hdxDrawFontEx( 2,MN_BASEPOS_X-sz["width"]/2,MN_ARTISTPOS_Y-MN_ARTISTHEIGHT/2,sz["width"],MN_ARTISTHEIGHT,alpha );           // 指定のサイズ以下なら等倍表示
            else
                hdxDrawFontEx( 2,MN_BASEPOS_X-MN_ARTISTWIDTH/2,MN_ARTISTPOS_Y-MN_ARTISTHEIGHT/2,MN_ARTISTWIDTH,MN_ARTISTHEIGHT,alpha );     // はみ出す場合はスケーリング表示
            end
        elseif( game_data["type"]==1 ) then
            // #STAGEFILEなら(4:3)
            hdxDrawTitle( MN_BASEPOS_X-MN_STAGEFILE_WIDTH/2,300-MN_STAGEFILE_HEIGHT/2,MN_STAGEFILE_WIDTH,MN_STAGEFILE_HEIGHT,alpha );
        else
            // #BACKBMPなら(1:1)
            hdxDrawTitle( MN_BASEPOS_X-MN_BACKBMP_WIDTH/2,300-MN_STAGEFILE_HEIGHT/2,MN_BACKBMP_WIDTH,MN_BACKBMP_HEIGHT,alpha );
        end
        hdxSetFilterMode( FILTER_POINT );
    end

    // フェードアウト
    if( g_dwMainState==MNST_FINISH ) then
        hdxDrawAnime( 2, 640, 360, 1.0, 1.0 );
    end
    
    
    
end


// ゲージ表示サブルーチン
//   mode  : ゲージの種類(0=blue&red、1=red)
//   x,y   : ゲージ原点の左上座標
//   meter : ゲージの個数(0~50)
function _DrawGauge( mode,x,y,meter )

    // メーターが0なら何も表示しない
    if( meter<=0 ) then
        return;
    end

    // ゲージ上限から下にランダムで微振動させる
    local len = meter - 1 - hdxGetRand() % 3;
    if( len<0 ) then
        len = 0;
    end

    if( mode==0 ) then
        // 80パー(39個)未満は青
        local i = 0;
        while i<len do
            if( i>=39 ) then
                break;                  // 青ゲージ終了なら抜ける
            end
            hdxPut( 0,x+i*8,y );
            i = i + 1;
        end

        // それ以降は赤(あれば)
        while i<len do
            hdxPut( 1,x+i*8,y );
            i = i + 1;
        end

        // 現在のゲージ上限のランプは常に表示
        if( meter>=40 ) then
            hdxPut( 1,x+(meter-1)*8,y );
        else
            hdxPut( 0,x+(meter-1)*8,y );
        end
    else
        // すべて赤
        for i=0,len-1 do
            hdxPut( 1,x+i*8,y );
        end
        hdxPut( 1,x+(meter-1)*8,y );
    end
end


// ステージ番号アニメ表示コールバック
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

// レベルアニメ表示コールバック
function _mnLevelNum( layer,x,y,frm_x,frm_y,frm_sx,frm_sy,frm_alpha,frm_rot )
    local game_data = hdxGetGameData();

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

    if( game_data["level"]>=1 && game_data["level"]<=12 ) then
        // レベルが範囲内ならレベル別に使用する画像IDを特定
        local id1,id2;
        if( game_data["level"]>=1 && game_data["level"]<=5 ) then
            id1 = mn_pid_str_level0;                // 水色
            id2 = mn_pid_lv_num0;                   // 数字
        elseif( game_data["level"]>=6 && game_data["level"]<=9 ) then
            id1 = mn_pid_str_level1;                // オレンジ
            id2 = mn_pid_lv_num1;                   // 数字
        elseif( game_data["level"]>=10 && game_data["level"]<=12 ) then
            id1 = mn_pid_str_level2;                // 赤
            id2 = mn_pid_lv_num2;                   // 数字
        end

        // LEVEL文字100px、数値1文字あたり20px
        if( game_data["level"]>=10 ) then
            // 2桁
            hdxPut( id1, _x-70, _y-8 );             // LEVEL
            hdxPutInt( id2, _x+30, _y-8, game_data["level"], 1.0, ALIGNTYPE_LEFTTOP );
        else
            // 1桁
            hdxPut( id1, _x-60, _y-8 );             // LEVEL
            hdxPutInt( id2, _x+40, _y-8, game_data["level"], 1.0, ALIGNTYPE_LEFTTOP );
        end

    else
        // レベル??(??画像は40px)
        hdxPut( mn_pid_str_level3, _x-70, _y-8 );   // LEVEL
        hdxPut( mn_pid_lv_err, _x+30, _y-8 );       // ??
    end

    return TRUE;
end

// スクロールスピード変更アニメの表示
function _mnDrawScrSpeed( pmode,spd )
    // 1以上なら表示する
    if( g_iScrPosFrame>0 ) then
        // 表示するフレーム番号を算出
        for i=0,10 do
            if( spd<=FIXED_SCROLLSPEED[i+1] ) then
                hdxCtrlAnime( 3,CTRLMODE_MANUAL,i,0,-1 );
                break;
            end
        end

        local pm = pmode + 1;
        local _x = g_iScrPosFrame * (MN_SCRPOS_X2[pm] - MN_SCRPOS_X1[pm]) / MN_SCRPOS_FRAME + MN_SCRPOS_X1[pm];
        local _y = g_iScrPosFrame * (MN_SCRPOS_Y2[pm] - MN_SCRPOS_Y1[pm]) / MN_SCRPOS_FRAME + MN_SCRPOS_Y1[pm];
        hdxDrawAnime( 3, _x, _y, 1.0, 1.0 );
    end

end

// イコライザの表示(SOUND EQUALIZER APIがENABLEでなければ全て0が入る)
function _mnDrawEqualizer( mode,x,y )
    local eq = hdxGetSoundEqualizer();
    local h;
    
    if( mode==0 ) then
        // single
        for i=0,15 do
            hdxFillRect( i*10+x, y, 8, 14, 0xFF22091A );
            h = math.floor( eq["eq_"..(i+1)] * 14.0 / 255.0 );
            hdxFillRect( i*10+x,y+(14 - h), 8, h, 0xFFFF7ED3 );
        end
    else
        // double
        for i=0,15 do
            hdxFillRect( i*6+x, y, 5, 30, 0xFF22091A );
            h = math.floor( eq["eq_"..(i+1)] * 30.0 / 255.0 );
            hdxFillRect( i*6+x, y+(30 - h), 5, h, 0xFFFF7ED3 );
        end
    end
end

// タイムライト描画
TIMELIGHT_POS_Y = { 26,475 };
function _mnDrawTimeLight( pmode,mul )

    local _y = (TIMELIGHT_POS_Y[2] - TIMELIGHT_POS_Y[1]) * mul + TIMELIGHT_POS_Y[1];

    if( pmode==0 ) then
        hdxDrawAnime( 4, 32, _y, 1.0, 1.0 );
    elseif( pmode==1 ) then
        hdxDrawAnime( 4, 1248, _y, 1.0, 1.0 );
    else
        hdxDrawAnime( 4, 294, _y, 1.0, 1.0 );
        hdxDrawAnime( 4, 986, _y, 1.0, 1.0 );
    end
end


///////////////////////////////////////////////////////////////////////
// BMSの解析状態が変化するごとに呼び出される。
///////////////////////////////////////////////////////////////////////
function OnGameLoading( event,per )
//    trace( "■OnGameLoading : ["..event.."] "..per.."%" );
    g_iLoadPercent  = per;

    if( event==LOADEVENT_BMS_ANALYZE ) then

    elseif( event==LOADEVENT_BMS_SUCCESS ) then
        // 解析が完了したならタイトル名の表示が可能
        local game_data = hdxGetGameData();
        trace( "■タイトルタイプ : "..game_data["type"] );
        if( game_data["type"]==0 ) then
            // タイトル画像無しならフォントを構築
            hdxCreateFont( 0,"メイリオ",64,game_data["title"],0xFFFFFFFF,0xFFFFA5FF,2,0xFF000000 );
            hdxCreateFont( 1,"メイリオ",48,game_data["genre"],0xFFFFFFFF,0xFFFFFFFF,1,0xFF000000 );
            hdxCreateFont( 2,"メイリオ",48,game_data["artist"],0xFFFFFFFF,0xFFFFFFFF,1,0xFF000000 );
        end
        // レーン無しフレームにしてシステムレーンを表示する
        local game_info = hdxGetGameInfo();
        if( game_info["pmode"]==PLAYMODE_DOUBLE ) then
            hdxCtrlAnime( 0,CTRLMODE_MANUAL,100,100,-1 );
        else
            hdxCtrlAnime( 0,CTRLMODE_MANUAL,65,65,-1 );
        end
        // タイトルは最低3秒表示
        g_dwWaitTitleTime   = hdxGetTime() + 3000;
        g_dwMainState       = MNST_TITLE;
    elseif( event==LOADEVENT_BMS_ANALYZE ) then

    elseif( event==LOADEVENT_SOUND_LOADING ) then

    elseif( event==LOADEVENT_IMAGE_LOADING ) then

    elseif( event==LOADEVENT_COMPLETE ) then
        // ゲーム開始待ちへ
        g_dwMainState = MNST_WAIT;
    end

end

///////////////////////////////////////////////////////////////////////
// ゲームプレイ時に判定が行われると呼び出される。
///////////////////////////////////////////////////////////////////////
function OnGameJadge( side,draw,jadge,combo,timing )
//    trace( "OnGameJadge : "..side.." "..draw.." "..jadge.." "..combo.." "..timing );

    // 最新の判定タイプを記録
    g_iJadgeType[side+1]    = jadge;
    g_iJadgeCombo[side+1]   = combo;

    if( draw==0 ) then
        // ワンショット表示
        g_iJadgeMode[side+1] = hdxGetTime() + 1000;     // 1秒で消す
    elseif( draw==1 ) then
        // 連続表示開始
        g_iJadgeMode[side+1] = 0;
    end
end

///////////////////////////////////////////////////////////////////////
// レーン描画状態が変更されるごとに呼び出される。
// ※ビューポートは設定済み
///////////////////////////////////////////////////////////////////////
function OnDrawLane( state,side,x,y,w,h )
    if( state==DRAWSTATE_OBJECT ) then
        // オブジェの描画後でカバーより手前に表示
        local game_info     = hdxGetGameInfo();
        local cover_info    = hdxGetCoverInfo();

        // フルコンボアニメ(LIFTがあればその分上昇)
        if( game_info["pmode"]==PLAYMODE_SINGLE_1P || game_info["pmode"]==PLAYMODE_SINGLE_2P ) then
            // シングル
            local FULLCOMBO_X = { 42,948 };
            hdxSetViewport( FULLCOMBO_X[side+1], 0 - cover_info["lift_offset"], 290, 474 );
            hdxDrawAnime( 1, FULLCOMBO_X[side+1], 0 - cover_info["lift_offset"], 1.0, 1.0 );
        else
            // ダブル
            local FULLCOMBO_X = { 304,686 };
            hdxSetViewport( FULLCOMBO_X[side+1], 0 - cover_info["lift_offset"], 290, 474 );
            hdxDrawAnime( 1, FULLCOMBO_X[side+1], 0 - cover_info["lift_offset"], 1.0, 1.0 );
        end
    elseif( state==DRAWSTATE_COVER ) then
        // レーンカバーの上に緑数値と白数字を表示
        local game_info     = hdxGetGameInfo();
        local cover_info    = hdxGetCoverInfo();

        if( g_bScrPos==TRUE ) then
            // スクロールスピードパネル表示時のみ
            if( game_info["opt_cover"]==OPTCOVER_NO ) then
                // カバー無しならスクロールスピードパネルの上部に表示
                hdxSetViewport( 0,0,0,0 );              // ビューポートをクリアしてレーン以外に描画できるようにする
                local COVERNO_POS_X = { 370,910,640 };
                local COVERNO_POS_Y = { 176,176,12 };
                hdxPutInt( mn_pid_num_g, COVERNO_POS_X[game_info["pmode"]+1], COVERNO_POS_Y[game_info["pmode"]+1], cover_info["num_green"], 1.0, ALIGNTYPE_CENTER );
                hdxPutInt( mn_pid_num_g2, COVERNO_POS_X[game_info["pmode"]+1]+8, COVERNO_POS_Y[game_info["pmode"]+1]+11+8, cover_info["num_green_ms"], 1.0, ALIGNTYPE_RIGHTBOTTOM );
                hdxPut( mn_pid_num_mill, COVERNO_POS_X[game_info["pmode"]+1]+8+1, COVERNO_POS_Y[game_info["pmode"]+1]+11+1 );
            else
                // カバーありなら
                local id = side + 1;
                if( game_info["opt_cover"]==OPTCOVER_SUDDEN || game_info["opt_cover"]==OPTCOVER_HIDSUD || game_info["opt_cover"]==OPTCOVER_LIFTSUD ) then
                    // 白数字
                    hdxPutInt( mn_pid_num_w, cover_info["cover_top_x"..id]+86, cover_info["cover_top_y"..id]+482-18, cover_info["cover_top_num_white"], 1.0, ALIGNTYPE_CENTER );
                    // 緑数字
                    hdxPutInt( mn_pid_num_g, cover_info["cover_top_x"..id]+204, cover_info["cover_top_y"..id]+482-18, cover_info["num_green"], 1.0, ALIGNTYPE_CENTER );
                    hdxPutInt( mn_pid_num_g2, cover_info["cover_top_x"..id]+204+8, cover_info["cover_top_y"..id]+482-18+9+8, cover_info["num_green_ms"], 1.0, ALIGNTYPE_RIGHTBOTTOM );
                    hdxPut( mn_pid_num_mill, cover_info["cover_top_x"..id]+204+8+1, cover_info["cover_top_y"..id]+482-18+9+1 );
                end
                if( game_info["opt_cover"]==OPTCOVER_HIDDEN || game_info["opt_cover"]==OPTCOVER_HIDSUD || game_info["opt_cover"]==OPTCOVER_LIFT || game_info["opt_cover"]==OPTCOVER_LIFTSUD ) then
                    // 白数字
                    hdxPutInt( mn_pid_num_w, cover_info["cover_under_x"..id]+86, cover_info["cover_under_y"..id]+19, cover_info["cover_under_num_white"], 1.0, ALIGNTYPE_CENTER );
                    // 緑数字
                    hdxPutInt( mn_pid_num_g, cover_info["cover_under_x"..id]+204, cover_info["cover_under_y"..id]+19, cover_info["num_green"], 1.0, ALIGNTYPE_CENTER );
                    hdxPutInt( mn_pid_num_g2, cover_info["cover_under_x"..id]+204+8, cover_info["cover_under_y"..id]+19+9+8, cover_info["num_green_ms"], 1.0, ALIGNTYPE_RIGHTBOTTOM );
                    hdxPut( mn_pid_num_mill, cover_info["cover_under_x"..id]+204+8+1, cover_info["cover_under_y"..id]+19+9+1 );
                end
            end
        end
    end
end

///////////////////////////////////////////////////////////////////////
// フルコンボ達成時に呼び出される。
///////////////////////////////////////////////////////////////////////
function OnFullCombo()
    trace( "■フルコンボ" );
    hdxCtrlAnime( 1,CTRLMODE_AUTO_END,0,0,-1 );
end


///////////////////////////////////////////////////////////////////////
// ゲームが終了すると呼び出される。
///////////////////////////////////////////////////////////////////////
function OnEndGame( retire )
    trace( "■ゲーム終了 : "..retire );
    g_bGameOver     = retire;
    g_dwMainState   = MNST_END;
end

///////////////////////////////////////////////////////////////////////
// ゲーム中にStartボタンが押されてゲームオプションが変更可能になると呼び出される。
///////////////////////////////////////////////////////////////////////
function OnStartGameOption()
    g_bScrPos   = TRUE;
end

///////////////////////////////////////////////////////////////////////
// ゲーム中にStartボタンが離されてゲームオプションが変更不可になると呼び出される。
///////////////////////////////////////////////////////////////////////
function OnEndGameOption()
    g_bScrPos   = FALSE;
end

スクロールスピード処理

※以下のコードは曲選択画面(_05_songselect.lua)に定義したものです
// スクロールスピードの固定倍率
FIXED_SCROLLSPEED = { 1.00, 1.50, 2.00, 2.25, 2.50, 2.75, 3.00, 3.25, 3.50, 3.75, 4.00 };

function OnChangeScrollSpeed( scene,cur_spd,digital,analog )
//    trace( "digital="..digital );

    local ret;
    if( digital==0 ) then
        // アナログ値ならそのまま加算
        ret = cur_spd + analog;
        if( ret<0.5 ) then 
            ret = 0.5;
        end
        if( ret>10 ) then
            ret = 10;
        end
    elseif( digital>0 ) then
        // デジタルの上昇なら
        for i=1,11 do
            if( FIXED_SCROLLSPEED[i]>cur_spd ) then
                // 現在の値より大きい倍率なら確定
                return FIXED_SCROLLSPEED[i];
            end
        end
        // すでに最大値なら
        if( scene==0 ) then
            // 曲選択中の場合は回り込んで再び1倍から
            return FIXED_SCROLLSPEED[1];
        end
        ret = cur_spd;
    else
        // デジタルの下降なら
        for i=11,1,-1 do
            if( FIXED_SCROLLSPEED[i]<cur_spd ) then
                // 現在の値より小さい倍率なら確定
                return FIXED_SCROLLSPEED[i];
            end
        end
        // すでに最小値なら何もしない
        ret = cur_spd;
    end

    return ret;
end