【ハンズオン】そうだ、Power Apps でブロック崩しを作ろう。
かなり前にブロック崩しを Power Apps で作ってみたんですが、それの作成方法をまとめてみようと思います。(今度コミュニティの方でハンズオンでも出来たらなーとかも思ってたり。)
アプリはゲーム的ですが、楽しみながらPower Apps アプリ作成に役に立つTIPSが学べると思いますので、是非トライしてみてください。
どんなアプリを作るの?
こんな感じのアプリを作ります。
何の変哲もないブロック崩しですね。
ざっくり作成の流れ
- ボールを動かす
- ボールが壁に反射するようにする
- ボールをバーで跳ね返せるようにする
- ブロックを設置する
- ブロックに当たった時の処理を実装する
以下、作成手順です。
ボールを動かす
まずはボールを設置して、動くようにしてみましょう。
変数初期化用のボタンを設置
ボタンコントロールを追加し、各プロパティを以下の通りに設定しましょう。
コントロール名 |
ButtonInitBall |
Text |
InitBall |
OnSelect |
UpdateContext( { locBallPoint: { X: App.Width/2, Y: App.Height/2 }, locAcceleration:{ X: 30, Y: -30 } } ) |
OnePoint locBallPoint:ボールのXY座標の情報を保持しておくための変数です。のちの手順でボール用コントロールのXYプロパティに入れ込んでいきます。
locAcceleration:加速度情報の変数です。壁やブロックに当たって反射する際には、-1をかけて反転させています。描画更新のたびに locBallPoint に加算して、ボールを動いているように見せています。 |
ボタンの配置は適当でいいです。私はこんな感じにしました。
ボール用の円アイコンを設置
続いてボールを設置します。
円アイコンコントロールを追加し、各プロパティを以下の通りに設定しましょう。
コントロール名 |
Ball |
X |
locBallPoint.X-(Ball.Width/2) |
Y |
locBallPoint.Y-(Ball.Height/2) |
Width |
20 |
Height |
20 |
こんな感じになっていれば、OKです。
描画更新用のタイマーを設置
さらに、ボールを動かすためにlocBallPoint変数の更新が必要です。なので、短いタイマーを使って描画を更新させようと思います。
タイマーコントロールを追加し、各プロパティを以下の通りに設定しましょう。
コントロール名 |
Timer |
Duration |
20 |
Repeat |
true |
AutoStart |
true |
Visible |
false |
OnTimerStart |
//加速度に従いボールが移動する UpdateContext( { locBallPoint:{ X:locBallPoint.X+locAcceleration.X, Y:locBallPoint.Y+locAcceleration.Y } } ); |
これでボールが動くようになりました!ただ、ボールが突き抜けてどこかへ行ってしまいます。
※タイマーを動かすには再生モードにします。編集モードではタイマーが動きませんので注意!
OnePoint ここではボールの位置情報を持っているlocBallPointに加速度情報のlocAccelerationを加算しています。もっと早くしたい場合や遅くしたい場合は、locAccelerationを初期化している箇所の数値をいじってみましょう。 ちなみに、処理速度を上回るほどDurationを短くしても早くはならないようです。 |
再生モードにして、ボールが動くのを確認出来たら次に進みましょう!
ボールが壁に反射するようにする
天井と左右の壁に当たったら跳ね返す
続いて、ボールが突き抜けていかないように、天井や壁で跳ね返るようにしてみましょう。
先ほど設定した Timer コントロールの OnTimerStart に下記の処理を追加しましょう。
OnTimerStart |
//壁当たり判定 天井にあたったら跳ね返る If(locBallPoint.Y<0,UpdateContext({locAcceleration:{X:locAcceleration.X, Y:locAcceleration.Y*-1}})); //壁当たり判定 左の壁にあたったら跳ね返る If(locBallPoint.X<0,UpdateContext({locAcceleration:{X:locAcceleration.X*-1, Y:locAcceleration.Y}})); //壁当たり判定 右の壁にあたったら跳ね返る If(locBallPoint.X>App.Width,UpdateContext({locAcceleration:{X:locAcceleration.X*-1, Y:locAcceleration.Y}})); |
これでボールが跳ね返ってくれるようになりました。ただし、画面下に行ってしまうと相変わらずどこかへ行ってしまいます。このままではなすすべがありません。
ボールが壁と天井に反射するのを確認出来たら、次に進みます。
ボールをバーで跳ね返せるようにする
バーと連動させる用のスライダーを設置する
続いて、バーを使ってボールを跳ね返せるようにしましょう。まずは後の手順でバーと連動させるためのスライダーを準備します。
スライダーコントロールを追加し、各プロパティを以下の通りに設定しましょう。
コントロール名 |
SliderForHitBar |
X |
0 |
Y |
500 |
Width |
App.Width |
Height |
200 |
HandleSize |
200 |
RailThickness |
0 |
ShowValue |
False |
ValueFill |
RGBA(0, 0, 0, 0) |
HandleFill |
RGBA(0, 0, 0, 0) |
BorderStyle |
BorderStyle.None |
Max |
App.Width |
Default |
App.Width/2 |
OnePoint Widthには、横幅いっぱいにするためApp.Widthを指定しています。 HandleSizeはつかみやすくするため、200を指定しています。 見えないようにしたいので、5つの項目を設定しています。のちの手順で何をしているかわかりにくいので、後で理解してから設定してもいいかもしれません。 Maxはのちの手順でValueをバーと連動させるため、App.Widthにしています。 |
上にも書いていますが、非表示にする項目は後で設定してもかまいません。
設定出来たら、次に進みましょう。(スケスケなのでスクショは割愛します。)
跳ね返すバー用の四角形アイコンを設置する
続いて、実際に跳ね返すバーを追加します。先ほどのスライドと連動させていきます。
四角形アイコンコントロールを追加し、各プロパティを以下の通りに設定しましょう。
OnePoint Xプロパティがキーです。ここで先ほどのスライダーの値と連動させています。 スライダーを動かせば横の位置が連動して動くのが確認できるはずです。 |
バーにかぶっているとスライダーがつかめないので、スライダーを前面に持ってくるようにしましょう。ツリービューから設定できます。
これでバーを動かすことができるようになりましたね。ただ、これだけではまだボールを跳ね返すことはできません。
バーで跳ね返す処理を追加する
続いて、ボールを跳ね返せるようにしていきましょう。
ボールを跳ね返すようにするには、Timer.OnTimerStartに下記の処理を追加しましょう。
コントロール名 |
Timer |
OnTimerStart |
//バーの当たり判定 If( And( // ボールのY座標がヒットバーの内側かつ locBallPoint.Y+(Ball.Height/2)>HitBar.Y && locBallPoint.Y < HitBar.Y+HitBar.Height, // ボールのX座標がヒットバーの内側のとき locBallPoint.X>HitBar.X&&locBallPoint.X<HitBar.X+HitBar.Width ), // Y加速度を反転してボールを上方へ跳ね返す UpdateContext({locAcceleration:{X:locAcceleration.X, Y:locAcceleration.Y*-1}}) ); |
※既存の処理に追加します。既にあった数式を消さないように注意してください。
OnePoint 内容としては、ボールがバーにかぶったら(当たったら)、Y方向の変数に-1をかけて跳ね返すようにしています。 |
これでバーでボールを跳ね返せるようになります。やっとゲームっぽくなってきた気がしますね。
あとはブロックを設置して、ボールに当たったらブロックが消える処理を実装していけば完成です。
ブロックを設置する
ブロックの情報を準備する用のボタンを設置する
ブロックを設置していきますが、ブロックの情報はコレクションに保持しておこうと思います。
ということで、準備としてコレクションを初期化するためのボタンを作っていきます。
ボタンコントロールを追加し、各プロパティを以下の通りに設定しましょう。
コントロール名 |
ButtonInitBlocks |
Text |
InitBlocks |
OnSelect |
Clear(colBlocks); ForAll( Sequence(6,0,1) As Y, ForAll( Sequence(10,0,1) As X, Collect(colBlocks, { XIndex:X.Value, YIndex:Y.Value, Hit:false }) ) ); |
OnePoint Sequence()で生成したテーブルにAsで名前を付けています。こうすると、改装になっていても参照できますし、明示的に指定できるのでわかりやすくなりますので個人的におすすめです。 |
これで、次の手順で使用するコレクションの準備ができました。
作成出来たら、一度ボタンを押してコレクションを生成しておいてくださいね。
ブロック用ギャラリーを設置する
続けて、表示用のギャラリーとその中に四角アイコンを設置していきましょう。
空の垂直ギャラリーコントロールを追加し、各プロパティを以下の通りに設定しましょう。
コントロール名 |
GalleryBlocks |
Items |
colBlocks |
X |
0 |
Y |
0 |
Width |
App.Width |
Height |
300 |
WrapCount |
10 |
TemplateSize |
44 |
OnePoint ギャラリーは空にしておいてください。 垂直方向のギャラリーを追加することに注意。 Itemsには先ほど作成したコレクションを指定します。ボタンを一度押してコレクションが生成されていることを確認しておきましょう。 |
この時点ではまだ何も出てなくてOKです。気にせず次に進みます。
ギャラリーに四角形アイコンを追加
ギャラリーのセルにコントロールを追加してブロックを表せるようにします。
四角形アイコンコントロールを追加し、各プロパティを以下の通りに設定しましょう。
コントロール名 |
Block |
X |
0 |
Y |
0 |
Width |
Parent.TemplateWidth |
Height |
Parent.TemplateHeight |
Visible |
!ThisItem.Hit |
OnePoint Visibleに!ThisItem.Hitを指定して、コレクションの情報と連動させています。[ ! ]が付くことに注意。 |
これでブロックを配置することができましたね。
ブロックに当たった時の処理を実装する
ボールに当たったらボールが消えるようにする
続いて、ボールに当たったらブロックが消えるようにしてみましょう。
既存のTimerコントロールのプロパティに追記していきます。
コントロール名 |
Timer |
OnTimerStart |
// ブロック当たり判定処理 ブロック // 未HitならHit処理をする With( { _BallIndexX:RoundDown( locBallPoint.X/(App.Width/10),0), _BallIndexY:RoundDown( locBallPoint.Y/(GalleryBlocks.TemplateHeight+(GalleryBlocks.TemplatePadding*2)),0) }, With( { // ボールがある位置のセル情報を取得 _TargetBlock:LookUp(colBlocks,XIndex = _BallIndexX && YIndex = _BallIndexY) }, If( _TargetBlock.Hit = false, // ブロックのHit処理 Patch(colBlocks,_TargetBlock,{Hit:true}); ); ); ); |
OnePoint _BallIndexXYはボールの位置がインデックスで表すとどこにあるか?を取得しています。これを利用して、コレクションから該当する場所のブロック情報を取得し、更新しています。 Patch()でHitフィールドを更新していますが、これはブロックのVisibleと連動しているので、更新することでブロックが非表示になるわけです。 |
これで、ブロック崩しの貫通モードみたいになったかと思います。再生モードで試してみてください。
確認出来たら、先に進みます。
ブロックに当たったら跳ね返るようにする
仕上げに、ブロックに当たったら跳ね返るするようにしていきましょう。
先ほどの Timer.OnTimerStart に実装した部分を以下のように書き換えて、跳ね返るようにしていきます。
コントロール名 |
Timer |
OnTimerStart |
// ブロック当たり判定処理 ブロック // 未HitならHit処理をする With( { _BallIndexX:RoundDown( locBallPoint.X/(App.Width/10),0), _BallIndexY:RoundDown( locBallPoint.Y/(GalleryBlocks.TemplateHeight+(GalleryBlocks.TemplatePadding*2)),0) }, With( { // ボールがある位置のセル情報を取得 _TargetBlock:LookUp(colBlocks,XIndex = _BallIndexX && YIndex = _BallIndexY) }, If( _TargetBlock.Hit = false, // ブロックのHit処理 Patch(colBlocks,_TargetBlock,{Hit:true}); // 反射の処理 横から来てたらX方向に跳ね返す。 If(locLastIndex.X <> _BallIndexX,UpdateContext({locAcceleration:{X:locAcceleration.X*-1, Y:locAcceleration.Y}})); // 反射の処理 上下から来てたらY方向に跳ね返す。 If(locLastIndex.Y <> _BallIndexY,UpdateContext({locAcceleration:{X:locAcceleration.X, Y:locAcceleration.Y*-1}})); ); //今回の値を次回の比較のために保持しておく。 UpdateContext({locLastIndex:{X:_BallIndexX,Y:_BallIndexY}}); ); ); |
OnePoint ボールが横から来たらX方向に跳ね返す。上下から来たらY方向に跳ね返すようになっています。インデックス単位での判定なので、多少判定がガバガバなのはご愛敬。 また、どこから来たか判定したかったので、前の情報を保持するようにしています。 |
上記のように書き換えると、ボールに当たったブロックは消えつつ、ボールが跳ね返るようになるはずです。
完成!
以上でブロック崩し完成です!お疲れさまでした!
さらに余裕がある方は、点数加算のギミックを追加したり、残機を実装してゲームオーバーになるようにしたり、ブロックの背景に面白い画像などを置いてみても面白いかもしれません!
以上、ブロック崩しの作り方でした!
皆様が楽しく感じながら、ついでに少しでも学びにつながってましたら幸いです!
この記事はMicrosoft Power Appsのカレンダー | Advent Calendar 2022 - に参加しています。