コルネの進捗や備忘録が記されたなにか

進捗や成果物や備忘録てきななにかを雑に更新していきます。

【Power Apps】糸通しゲームを作成してみる


スポンサードリンク

はじめに

みなさん"糸通し"というアプリをご存じでしょうか?

play.google.com

元祖糸通し

元祖糸通し

  • Spicysoft Corp.
  • ゲーム
  • 無料
apps.apple.com

今回は上記のようなアプリをPower Apps で作成してみましたので作成方法を纏めてみようと思います!

糸通しゲームを作成する

糸を描画する

糸のアニメーションはSVG で描画させたいと思います。

糸の情報は trajectory というコレクションに格納したいと思います。

trajectory には1ms単位のx座標、y座標、ボタンが押されたか?の情報を保持させようと思います。

n - 1 レコード目とn レコード目、n + 1レコード目の点をそれぞれ繋ぐように線を引くことで糸を作成しています。

以下のようにコレクションの初期化、糸の描画のための記載をしましょう。

PlayScreen.OnVisible

ClearCollect(trajectory, {x:0, y:PlayScreen.Height / 2, press:false});

Image.Image

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'>
        <path d='" &
        Concat(
            ForAll(
                Sequence(CountRows(trajectory)),
                If(
                    Value = 1,
                    "M " & First(trajectory).x & " " & First(trajectory).y,
                    If(Last(FirstN(trajectory, Value)).press = Last(FirstN(trajectory, Value + 1)).press,
                        " L" & Last(FirstN(trajectory, Value)).x & " " & Last(FirstN(trajectory, Value)).y,
                        " S" & Last(FirstN(trajectory, Value)).x & " " & 
                               Last(FirstN(trajectory, Value)).y & " " &
                               Last(FirstN(trajectory, Value + 1)).x & " " & 
                               Last(FirstN(trajectory, Value + 1)).y
                    )
                )
            ),
            Value  
        )
        & "
    </svg>"
)

初期位置はx座標は0、y座標は画面の中心にくるように設定しています。

心の糸の描画箇所ですが、先ほども記載したように点と点を繋ぐようにして描画させています。

ただ点と点を繋いだだけでは線のUp/Down の切り替えタイミングが滑らかな線にならず切替点が尖ったものになってしまいますので、切替点は丸く、滑らかになるようにしたいと思います。
それを実現しているのが、

" S" & Last(FirstN(trajectory, Value)).x & " " & 
       Last(FirstN(trajectory, Value)).y & " " &
       Last(FirstN(trajectory, Value + 1)).x & " " & 
       Last(FirstN(trajectory, Value + 1)).y

です。

Up/Down が切り替わるのは、Nレコード目とN + 1レコード目のPress が異なっていた場合です。
それを、 Last(FirstN(trajectory, Value)).press = Last(FirstN(trajectory, Value + 1)).press を条件にして上記を実現させています。

trajectory にレコードを追加する

trajectory の情報によって糸は作成されます。

このコレクションのレコードは1ms単位で更新していきたいと思います。

なので、Duration:1のタイマーを追加し、以下のように設定します。

Timer.OnTimerStart

Collect(
    trajectory, 
    {
        x:If(Last(trajectory).x >= PlayScreen.Width / 2, Last(trajectory).x, Last(trajectory).x + PlayScreen.Width / 100), 
        y:Last(trajectory).y + If(Button1.Pressed, -1, 1) * PlayScreen.Height / 300,
        press:Button1.Pressed
    }
);

また、Up/Down を切り替えるためのボタンも作成しておいてください。

糸が動いているようにみせるために、xは時間経過で最後のレコードのx座標にプラスした値を設定していきます。
ただし、ずっとx座標の値をプラスし続けていると、画面からはみでてしまいます。
なので画面中央に来たタイミングでx座標の値のプラスは行わないようにしています。

y座標は最後の値にボタンが押されていないときはプラスし、ボタンが押されているときはマイナスします。

このままでは画面中央まで糸が到達した後、線が想定通り引かれません。

よってタイマー終了時、糸が画面中央まできていた場合以下のようにしてx座標の値を更新したいと思います

Timer.OnTimerEnd

If(
    CountRows(trajectory) >= 50, 
    RemoveIf(trajectory, x = First(trajectory).x, y = First(trajectory).y);
    UpdateIf(trajectory, true, {x:x - PlayScreen.Width / 100})
);

上記のようにすることにより、糸の点の情報が左方向にずれていくため、相対的に糸が右に進んでいるように見えるというわけです。

また、画面外にずれた情報は不要ですので削除しちゃいましょう。

これで糸のアニメーションを作成することができました!

リングを作成する

糸が通過するためのリングを作成したいと思います。

リングの情報は ring に持ちたいと思います。

このコレクションにはリングの座標と、リングの大きさを保持しています。

PlayScreen.OnVisible

ClearCollect(ring, {cx:PlayScreen.Width + 15, cy:PlayScreen.Height / 2, ry:50});

このコレクション情報をもとにリングを描画したいと思います。

Image.Image

' stroke='black' fill='transparent' stroke-width='2' />" & 
        Concat(
            ForAll(
                Sequence(CountRows(ring)),
                "<ellipse 
                    cx='" & Last(FirstN(ring, Value)).cx & "' 
                    cy='" & Last(FirstN(ring, Value)).cy & "' 
                    rx='15' 
                    ry='" & Last(FirstN(ring, Value)).ry & "' 
                stroke='red' fill='none' stroke-width='2' />"
            ),
            Value
        )

これで赤い楕円のリングを描画することができます。

続いて、糸の情報と同じように1msごとにコレクションの情報を更新したいと思います。

Timer.OnTimerStart

If(
    Last(ring).cx < PlayScreen.Width * 2 / 3,
    Collect(
        ring,
        {
            cx:PlayScreen.Width,
            cy:
                With(
                    {val:Last(ring).cy + Rand() * PlayScreen.Height * 2 / 9 - PlayScreen.Height * 2 / 18},
                    If(val > PlayScreen.Height - 150, 
                           PlayScreen.Height - 150,
                       val < 150,
                           150,
                       val
                    )
                ),
            ry:50 / (Rand() * 1 / 2 + 1)
        }
    )
)

Timer1.OnTimerEnd

UpdateIf(ring, true, {cx:cx - PlayScreen.Width / 100});
RemoveIf(ring, cx < -15);

リングは1msごとに作成する必要はないので、If 関数で作成する条件を制御しています。
今回、条件はリングが画面の2 / 3まできたら新規で追加するようにしています。

追加されたリングは1msごとに左にずれるようx座標を更新し、画面外に出た情報は削除するようにしています。

これでリング部分の実装が完成です!

糸がリングを通過したか判定を行う

糸がリングを通過したか判定を行うのには、トグルを利用します。

"切り替え"を追加して、以下のように設定します。

Toggle.Default

CountRows(
    Filter(
        ring,
        cx - 15 <= Last(trajectory).x,
        cx + 15 >= Last(trajectory).x,
        cy - ry > Last(trajectory).y ||
        cy + ry < Last(trajectory).y
    )
) <> 0

トグルが true になると、糸がリングを通過していないのでゲーム終了となります。

上記条件を簡単に解説します。

まず、判断を行うのは糸の先端ですので Last(trajectory) で糸の先端部分の座標を取得しています。
cx - 15 としているのは、リング横幅を15で固定しているためです。

x座標はリング内にいたときにいたときをフィルタリングしたのに対し、y座標はリング外にいたときでフィルタリングする必要があります。

y座標がリング外にいるというのは以下2つの場合が存在します。

  • リング上部より上にいるとき
  • リング下部より下にいるとき

書き出してみると当たり前じゃん。という感じですが、「どういったときにやりたいことを満たせる条件になるか」を考えてみると実装が少し楽になったりします。

1つ目の条件を満たしているのは、 cy - ry > Last(trajectory).y
2つ目の条件を満たしているのが、 cy + ry < Last(trajectory).y
です。

これでPower Apps での糸通しゲームの完成です!

おわりに

ご覧のとおり結構単純な実装で糸通しゲームが作成できました!

この技術を応用すれば横スクロールゲームも作成できそうですね。


スポンサードリンク