Compartilhar via


Windowsストア アプリ 作り方解説 Line Attack編 第3回 ~宝石をちゃんと止める~

マイクロソフトの田中達彦です。
本連載では、Windowsストアアプリとして作成したパズルゲームである、Line Attackのプログラムを解説します。
Line Attack : https://apps.microsoft.com/webpdp/app/f11e327c-6228-4c8f-8245-ea57d65e0f09

[注意事項]
- この連載で提供するプロジェクトファイルは、サンプルとして提供しています。
- 毎回の記事で提供するプロジェクトファイルは、その時点でのソースコードです。最終バージョンのソースコードと異なる場合があります。

[今回のプロジェクトファイル]
今回公開するプロジェクトは、宝石を上下左右に動かしたあと、マス目に合うように止める部分を実装しています。
今回のプロジェクトを実行すると、以下のように動かした宝石がマス目に合わせて止まるようになっています。

[現在表示されている宝石の種類の情報の追加]
宝石を表示させるときに、PieceフィールドというImageコントロールを使用しています。
第2回目の記事までのPieceフィールドには、宝石の画像データしか入れていませんでした。
ImageコントロールにはTagというプロパティがあります。
このTagプロパティは、開発者が任意の情報を入れておくことができます。
今回はこのTagプロパティを活用し、その中にどの宝石が入っているかの情報を入れておきます。
SetPanelメソッドに、以下のコードを追加しています。
このコードが、Tagプロパティに宝石の情報を入れている部分です。

Piece[x + MaxColumn * 1, y + MaxColumn * 0].Tag = StageData[stage, x, y];
Piece[x + MaxColumn * 0, y + MaxColumn * 1].Tag = StageData[stage, x, y];
Piece[x + MaxColumn * 1, y + MaxColumn * 1].Tag = StageData[stage, x, y];
Piece[x + MaxColumn * 2, y + MaxColumn * 1].Tag = StageData[stage, x, y];
Piece[x + MaxColumn * 1, y + MaxColumn * 2].Tag = StageData[stage, x, y];

Tagプロパティに入れた宝石の情報は、点滅している部分に宝石が乗っているかどうかの判定などに使用します。
現在表示している宝石の種類を保持しておくフィールドを別途作成する方法もあります。
今回は新たなフィールドを作らず、Tagプロパティで代用しました。

[指を離したときの処理]
マウスボタンや画面から指を離したときの処理を、前回のプロジェクトでは入れていませんでした。
今回のプロジェクトでは、前回コードを実装していなかったPointerReleasedイベントハンドラーに指を離したときの処理を以下のように追加します。

private void GamePage_PointerReleased(object sender, PointerRoutedEventArgs e)
{
    Point p = e.GetCurrentPoint(MainPanel).Position;
    int releasedPieceX, releasedPieceY;
    int delta = 0;
    int i;
    Image[] tmpImage = new Image[MaxColumn];
    ImageSource[] tmpImageSource = new ImageSource[MaxColumn];
    int[] tmpPiece = new int[MaxColumn];

    for(i = 0; i < MaxColumn; i++)
        tmpImage[i] = new Image();

    if (p.X > MainPanel.ActualWidth / 3 && p.X < MainPanel.ActualWidth / 3 * 2 &&
        p.Y > MainPanel.ActualHeight / 3 && p.Y < MainPanel.ActualHeight / 3 * 2)
    {
        releasedPieceX = (int)((p.X - MainPanel.ActualWidth / 3) / (MainPanel.ActualWidth / (MaxColumn * 3)));
        releasedPieceY = (int)((p.Y - MainPanel.ActualHeight / 3) / (MainPanel.ActualHeight / (MaxColumn * 3)));

        //testText.Text = p.X.ToString() + " / " + p.Y.ToString() + " (" + releasedPieceX.ToString() + " / " + releasedPieceY.ToString() + ") " + MainPanel.ActualWidth.ToString();

        if (releasedPieceX < 0)
            releasedPieceX = 0;
        if (releasedPieceX >= MaxColumn)
            releasedPieceX = MaxColumn - 1;
        if (releasedPieceY < 0)
            releasedPieceY = 0;
        if (releasedPieceY >= MaxColumn)
            releasedPieceY = MaxColumn - 1;

        CompositeTransform ct = new CompositeTransform();
        ct.TranslateX = 0;
        ct.TranslateY = 0;

        if (MoveDirection == 1)
        {
            delta = releasedPieceX - PressedPieceX;

            for(i = 0; i < MaxColumn; i++)
            {
                tmpPiece[i] = (int)Piece[MaxColumn + i - delta, PressedPieceY + MaxColumn].Tag;
            }

            for(i = 0; i < MaxColumn; i++)
            {
                Piece[i + MaxColumn * 0, PressedPieceY + MaxColumn * 1].Source = PCBitmapImage[tmpPiece[i]];
                Piece[i + MaxColumn * 1, PressedPieceY + MaxColumn * 0].Source = PCBitmapImage[tmpPiece[i]];
                Piece[i + MaxColumn * 1, PressedPieceY + MaxColumn * 1].Source = PCBitmapImage[tmpPiece[i]];
                Piece[i + MaxColumn * 1, PressedPieceY + MaxColumn * 2].Source = PCBitmapImage[tmpPiece[i]];
                Piece[i + MaxColumn * 2, PressedPieceY + MaxColumn * 1].Source = PCBitmapImage[tmpPiece[i]];
                Piece[i + MaxColumn * 0, PressedPieceY + MaxColumn * 1].Tag = tmpPiece[i];
                Piece[i + MaxColumn * 1, PressedPieceY + MaxColumn * 0].Tag = tmpPiece[i];
                Piece[i + MaxColumn * 1, PressedPieceY + MaxColumn * 1].Tag = tmpPiece[i];
                Piece[i + MaxColumn * 1, PressedPieceY + MaxColumn * 2].Tag = tmpPiece[i];
                Piece[i + MaxColumn * 2, PressedPieceY + MaxColumn * 1].Tag = tmpPiece[i];
            }

            for (i = 0; i < MaxColumn * 3; i++)
            {
                Piece[i, PressedPieceY + MaxColumn].RenderTransform = ct;
            }
        }
        else if (MoveDirection == 2)
        {
            delta = releasedPieceY - PressedPieceY;

            for(i = 0; i < MaxColumn; i++)
            {
                tmpPiece[i] = (int)Piece[PressedPieceX + MaxColumn, MaxColumn + i - delta].Tag;
            }

            for(i = 0; i < MaxColumn; i++)
            {
                Piece[PressedPieceX + MaxColumn * 1, i + MaxColumn * 0].Source = PCBitmapImage[tmpPiece[i]];
                Piece[PressedPieceX + MaxColumn * 0, i + MaxColumn * 1].Source = PCBitmapImage[tmpPiece[i]];
                Piece[PressedPieceX + MaxColumn * 1, i + MaxColumn * 1].Source = PCBitmapImage[tmpPiece[i]];
                Piece[PressedPieceX + MaxColumn * 2, i + MaxColumn * 1].Source = PCBitmapImage[tmpPiece[i]];
                Piece[PressedPieceX + MaxColumn * 1, i + MaxColumn * 2].Source = PCBitmapImage[tmpPiece[i]];
                Piece[PressedPieceX + MaxColumn * 1, i + MaxColumn * 0].Tag = tmpPiece[i];
                Piece[PressedPieceX + MaxColumn * 0, i + MaxColumn * 1].Tag = tmpPiece[i];
                Piece[PressedPieceX + MaxColumn * 1, i + MaxColumn * 1].Tag = tmpPiece[i];
                Piece[PressedPieceX + MaxColumn * 2, i + MaxColumn * 1].Tag = tmpPiece[i];
                Piece[PressedPieceX + MaxColumn * 1, i + MaxColumn * 2].Tag = tmpPiece[i];
            }

            for (i = 0; i < MaxColumn * 3; i++)
            {
                Piece[PressedPieceX + MaxColumn, i].RenderTransform = ct;
            }
        }

        //testText.Text = delta.ToString();

    }

    PressedPoint.X = -1;
    MoveDirection = 0;
    MoveCount = 0;

}

黄色くマーカーした部分では、指を離したマス目がどこかを計算しています。
マス目は以下のように0から5までの数で示され、横方向、縦方向のマス目がそれぞれreleasedPieceXとreleasedPieceYに入ります。

水色でマーカーした部分では、宝石の移動に伴う、MainPanelの18x18のマス目に入っている情報を更新しています。
まずは、水平方向に移動したか、垂直方向で移動したかで処理を分けます。
水平方向の移動の場合は、横に何マス動いたかを計算し、deltaというフィールドに代入しています。

マウスや指をつかって宝石を動かしたとき、RenderTransformプロパティを使用して表示位置を変えていました。
この方法では、見た目として宝石が動いているように見せることができますが、内部的な情報としては宝石が動いたことにはなっていません。
単に、画像の表示位置をずらしているだけだからです。

そこで、tmpPieceという配列を使い、移動距離をもとにそのマス目に入るべき宝石の番号をいったんtmpPieceに入れておきます。
次に、Piece配列のSourceプロパティに表示すべき宝石の画像データを入れ、Tagプロパティには宝石の番号を入れます。
この処理により、Piece配列に移動後に表示すべき情報が入ります。
目に見えている6x6のマスだけでなく、その上下左右に6ずつ離れたマスにも同じ情報を入れておきます。

最後に、ずらして表示させていたときのズレをもとに戻します。
この処理は、RenderTransformの移動量にゼロをセットし、それを動かしていた列または行のPiece配列に代入するだけです。

[メイン部分の画面外にポインターが出たとき]
今回公開するプロジェクトを含め途中のプロジェクトまでは、メイン部分の画面外にマウスカーソルや指などのポインターが出てしまったときは、まったく宝石を動かさない仕様として実装しています。
そのコードはPointerMovedイベントハンドラーに、以下のように実装しています。

if (p.X < MainPanel.ActualWidth / 3 || p.X >= MainPanel.ActualWidth / 3 * 2 ||
    p.Y < MainPanel.ActualHeight / 3 || p.Y >= MainPanel.ActualHeight / 3 * 2)
{
    ct.TranslateX = 0;
    ct.TranslateY = 0;

    for (i = 0; i < MaxColumn * 3; i++)
    {
        Piece[i, PressedPieceY + MaxColumn].RenderTransform = ct;
        Piece[PressedPieceX + MaxColumn, i].RenderTransform = ct;
    }

    PressedPoint.X = -1;
    MoveDirection = 0;
    MoveCount = 0;

}

最初のif文の部分でメイン画面からはみ出しているかどうかを検知しています。
もしはみ出していたときは、RenderTransformに移動量ゼロを設定することで、ずらしていた表示をもとに戻しています。
最終的には、画面外にポインターが出たときは、その段階で表示されている状態に従って宝石の位置を決めるように変更しています。

[その他の注意事項]
- 公開しているソースコードに、コメントをほとんど入れていません。最後には、コメントを追加したプロジェクトを用意する予定です。
コメントを入れていない理由は、プログラムを組んでいるときにコメントを入れるという作業が思考の妨げになってしまうからです。
今回はスピード命でプログラムを組んだため、コメントを省き一気に開発しました。
メンテナンスや可読性の容易さを考えればコメントは入れるべきですので、時間が取れたときにコメントを追加します。

[前後の記事]
第2回 宝石を動かす
第4回 点滅セルの設定と合否判定

マイクロソフト
田中達彦

Pazzle_idea1_201301171633_move_complete.zip