C#で2048ゲームのクローンを作る[その6]

今回は、前回に作ったのセルのアニメーション処理を追加します。


アニメーションをさせるということは、各セルに対して定期的に再描画のUpdate処理をコールしてあげる必要があります。このためUpdate処理を実装します。

最初に、Updateメソッドだけを持つインターフェースを作ります。

public interface ITaskBehaviour {
    void Update();
}



作ったインターフェースをCellScriptクラスに適用します。Updateメソッドは、一旦デバッグメッセージの出力として実装しておきます。

class CellScript : ITaskBehaviour {
    public void Update() {
        // Step1:動作確認
        System.Diagnostics.Debug.Print( "Updateが呼ばれました:value={0}, location=[{1},{2}]",
                                        lblCell.Text,
                                        lblCell.Location.X, lblCell.Location.Y );
    }
}




次に、呼び元側となる画面のForm1クラスを修正します。

    public partial class Form1 : Form {
        List<ITaskBehaviour> taskList = new List<ITaskBehaviour>();
 
        //*********************************************************************
        /// <summary> 画面表示時のハンドラ
        /// </summary>
        //*********************************************************************
        private void Form1_Load( object sender, EventArgs e ) {
            ...
 
            timGameTimer.Interval = 100;
            timGameTimer.Enabled = true;
 
            // 動作確認用のセルを生成する
            taskList.Add( new CellScript( this.pnlBase,   64, 0, 0 ) );
            taskList.Add( new CellScript( this.pnlBase,  256, 1, 0 ) );
            taskList.Add( new CellScript( this.pnlBase, 1024, 2, 0 ) );
            taskList.Add( new CellScript( this.pnlBase,65535, 3, 0 ) );
 
            taskList.Add( new CellScript( this.pnlBase,    8, 0, 1 ) );
            taskList.Add( new CellScript( this.pnlBase,    4, 1, 1 ) );
 
            taskList.Add( new CellScript( this.pnlBase,    2, 3, 2 ) );
 
            ...
        }
 
 
        private void timGameTimer_Tick(object sender, EventArgs e) {
            // すべてのタスク(セル)に定期的に処理を行わせる
            foreach (ITaskBehaviour task in taskList) {
                task.Update();
            }
        }
    }
}



先ほど定義したインターフェースを持つオブジェクト(セル)達を管理するtaskListメンバ変数として定義します。画面表示の初期化処理であるForm1_Load()でListにセルたちを入れ込んでいます。

画面にtimeGameTimerというTimerコントロールをおいた上で、timGameTimer_Tick()イベントをタイマー実行させます。timGameTimer_Tickが行っていることは、先ほど登録したセルたちのUpdate()メソッドを順にコールするだけです。呼び出し周期は、”timGameTimer.Interval = 100;”としているため100mSec毎となり、毎秒10フレームで表示(10FPS)されます。
ここでInterval=20など小さくすればより滑らかにアニメーション動作させることができます(ただし頻繁に書き換えるので負荷は上がります)。

ここまで書いて一旦実行させると…

出力ウィンドウにUpdateメソッドのデバッグログが出力され続けます。
(出力ウィンドウが無い時は、Alt+2キーで表示できます
これで、Update()の定期的な呼び出しができるようになりました。


次に、CellScriptのアニメーション処理を実装します。
セルの状態ととしては、静止状態(IDLE)とスライドのアニメーション中状態(SLIDE)があるので、状態の一覧であるSTATE列挙体と現在の状態を持つcurStateを定義します。

class CellScript {
 
    enum STATE {
        IDLE,
        SLIDE
    }
 
    STATE curState = STATE.IDLE;



コンストラクタでの初期化時にcurStateをIDLEに初期化します。
また、setPositionで移動先が指定されると、最終移動先をdestLocationに、現在位置とのずれをslideOffsetに入れます。
スライド直後はdestLocation + slideOffsetが現在位置になりますが、アニメーションで徐々にdestLocationの位置に移動させていきます。

SLIDE_FRAMEでは、アニメーションにかかる時間(フレーム数)を指定しています。たとえば、10FPSでSLIDE_FRAME=50だったら、5秒かけて移動のアニメーションが走ることになります。

    const int SLIDE_FRAME = 50;
 
    int slidingFrame = 0;
    Point slideOffset  = new Point(0,0);
    Point destLocation = new Point(0,0);
 
    //*********************************************************************
    /// <summary> コンストラクタ
    /// </summary>
    /// <param name="value"></param>
    /// <param name="x"></param>
    /// <param name="y"></param>
    //*********************************************************************
    public CellScript( Panel pnlBoard, int cellValue, int x, int y ) {
        this.pnlBoard = pnlBoard;
        _createLabel();
        initPosition( x, y );
        setValue( cellValue );
 
        curState = STATE.IDLE;
    }
 
    //*********************************************************************
    /// <summary> セルの初期位置を指定する
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    //*********************************************************************
    public void initPosition(int x, int y) {
        if (lblCell == null) {
            return;
        }
 
        lblCell.Location = new Point( x*CELL_SIZE + (x+1)*CELL_PADDING,
                                      y*CELL_SIZE + (y+1)*CELL_PADDING );
    }
 
    //*********************************************************************
    /// <summary> セルを移動させる
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    //*********************************************************************
    public void setPosition(int x, int y) {
        if (lblCell == null) {
            return;
        }
 
        // 移動先を求める
        destLocation = new Point( x*CELL_SIZE + (x+1)*CELL_PADDING,
                                  y*CELL_SIZE + (y+1)*CELL_PADDING );
 
        // 現在位置と、移動先の差(移動距離)を求める
        slideOffset = new Point( lblCell.Location.X - destLocation.X,
                                 lblCell.Location.Y - destLocation.Y );
 
        // 状態を移動中にする
        curState = STATE.SLIDE;
        slidingFrame = 0;
    }




次はUpdate()メソッドです。こちらは、現在の状態(curState)に応じて処理を変えます。
IDLEの時は何もしません。
SLIDEの時、SLIDE状態になってからのは経過フレーム(slidingFrame)に応じて、徐々にセルをdestLocationへ近づけていきます。そして経過フレームがSLIDE_FRAMEに達したらIDLEに戻します。

    public void Update() {
 
        switch (curState) {
            case STATE.IDLE:
                // do nothing
                break;
            case STATE.SLIDE:
                slidingFrame += 1;
 
                int x = destLocation.X + (int)( slideOffset.X * ( (SLIDE_FRAME-slidingFrame) / (float)SLIDE_FRAME ) );
                int y = destLocation.Y + (int)( slideOffset.Y * ( (SLIDE_FRAME-slidingFrame) / (float)SLIDE_FRAME ) );
                lblCell.Location = new Point( x, y );
 
                if (slidingFrame >= SLIDE_FRAME) {
                    slidingFrame = 0;
                    curState = STATE.IDLE;
                }
                break;
        }
    }


これを実行すると、セルのアニメーションが行えます。

最後にForm1にボタンを貼り付け、クリックしたら特定のセルの位置を変え(=STATEをSLIDE_FRAMEに変え)れば、アニメーションが行われます。

        private void button1_Click(object sender, EventArgs e) {
            cellScript.setPosition( 3, 3 );
        }



セルをたくさん用意し、以下のようなコードを書けば大量のセルたちを一斉にスライドさせることも簡単です。

    class Form1 {
        ...
 
        private void button1_Click(object sender, EventArgs e)
        {
            // 移動先のセル(本当はBoardManagerが計算すべきものだが、動作確認のために手入力している)
            List<Tuple<int,int>> destPos = new List<Tuple<int,int>>();
            destPos.Add( new Tuple<int,int>(0,2) );
            destPos.Add( new Tuple<int,int>(1,2) );
            destPos.Add( new Tuple<int,int>(2,3) );
            destPos.Add( new Tuple<int,int>(3,2) );
            destPos.Add( new Tuple<int,int>(0,3) );
            destPos.Add( new Tuple<int,int>(1,3) );
            destPos.Add( new Tuple<int,int>(3,3) );
 
            // 登録されているすべてのセルを、上に書いた場所へ移動させる
            int offset = 0;
            foreach (ITaskBehaviour task in taskList) {
                CellScript cellScript = task as CellScript;
                int x = destPos[offset].Item1;
                int y = destPos[offset].Item2;
 
                cellScript.setPosition( x, y );
                offset++;
 
            }
        }
 
        ...

関連記事

コメントを残す

メールアドレスが公開されることはありません。