はじめに
この記事ではPower Apps のカスタム関数を利用して、当たり判定関数の作成方法について解説します。
寝落ちしないようにキーノート聞きながら #PowerApps のカスタムコネクタで遊んでいましたが限界です。
— コルネ (@koruneko32767) March 2, 2021
寝ます。 pic.twitter.com/ikfKo6vPYh
今回取り扱う当たり判定は、
- 四角 × 四角
- 円 × 円
- 四角 × 円
の3パターンです。
当たり判定を作成する
Power Apps のカスタム関数ってなに?という方は、【Power Apps】カスタム関数が作成できるようになりました! を先にご覧ください。
四角 × 四角
まずは一番単純な四角 × 四角の当たり判定を作成します。
この当たり判定の式は、【GPPB Virtual Bootcamp 2021 Sapporo】30分で作成!?解説しながら作る横スクアクションゲームで解説した式を利用します。
今回は上記ブログの流用で、再度の解説は省きます。
当たり判定は以下のようにPlayer とStage を定義したとき
以下のような式であらわすことができます。
((PlayerX > StageX && PlayerX < StageX + StageWidth) || (StageX > PlayerX && StageX < PlayerX + PlayerWidth)) && ((PlayerY > StageY && PlayerY < StageY + StageHeight) || (StageY > PlayerY && StageY < PlayerY + PlayerHeight))
まず2つの図形が"重なっている"というのは、下図のようにX成分でみたときもY成分でみたときも重なっている。
というのが条件になります。
上の式でいいますと、
この式がX成分の重なり判定で
((PlayerX > StageX && PlayerX < StageX + StageWidth) || (StageX > PlayerX && StageX < PlayerX + PlayerWidth))
この式がY成分の重なり判定です。
((PlayerY > StageY && PlayerY < StageY + StageHeight) || (StageY > PlayerY && StageY < PlayerY + PlayerHeight))
これら2つの条件を満たしているとき、重なっていると判断できるので
&&
でそれぞれの式を繋げているわけです。
プロパティは以下のように設定します。
表示名
SquareSquare
プロパティの型
出力
データ型
ブール値
パラメーターには以下のようなものを作成します。
パラメータ名 | データ型 | 役割 |
---|---|---|
x_1 | 数値 | オブジェクトAのX座標 |
y_1 | 数値 | オブジェクトAのY座標 |
width_1 | 数値 | オブジェクトAの幅 |
height_1 | 数値 | オブジェクトAの高さ |
x_2 | 数値 | オブジェクトBのX座標 |
y_2 | 数値 | オブジェクトBのY座標 |
width_2 | 数値 | オブジェクトBの幅 |
height_2 | 数値 | オブジェクトBの高さ |
最後に四角 × 四角の当たり判定の式を設定します。
SquareSquare
((x_1 > x_2 && x_1 < x_2 + width_2) || (x_2 > x_1 && x_2 < x_1 + width_1)) && ((y_1 > y_2 && y_1 < y_2 + height_2) || (y_2 > y_1 && y_2 < y_1 + height_1))
これで四角形のオブジェクトA, B の情報を渡すことで、当たり判定を行うことができます。
当たっている場合は、 true
当たっていない場合は false
ですね。
円 × 円
プロパティは以下のように設定します。
表示名
CircleCircle
プロパティの型
出力
データ型
ブール値
パラメーターには以下のようなものを作成します。
パラメータ名 | データ型 | 役割 |
---|---|---|
x_1 | 数値 | オブジェクトAの中心のX座標 |
y_1 | 数値 | オブジェクトAの中心のY座標 |
r_1 | 数値 | オブジェクトAの半径 |
x_2 | 数値 | オブジェクトBの中心のX座標 |
y_2 | 数値 | オブジェクトBの中心のY座標 |
r_2 | 数値 | オブジェクトBの変形 |
円と円の当たり判定にはピタゴラスの定理を活用します。
パラメータの値を図にして表すと以下のようになります。
このとき、2つの円の距離は三平方の定理によって求めることが可能ですね。
このとき、
※ などは x_1 に読み替えてください。
であるので2つの円の距離は、
となります。
この2つの円の距離と、2つの円の半径の和を比較して、この2つの円の距離が2つの円の半径の和以下であれば2つの円が接していると判断することができます。
このとき、より式の記載を簡単にするために左辺の平方根を右辺を乗算することで簡略化させましょう。
上記をPower Apps で式にすると以下になります。
CircleCircle
Power(x_1 - x_2, 2) + Power(y_1 - y_2, 2) < Power(r_1 + r_2, 2)
これで円形のオブジェクトどうしの当たり判定を実装することができました!
四角 × 円
最後に四角形と円の当たり判定です。
これはちょっと複雑になってきます。
まずはカスタムプロパティの作成です。
以下のように設定します。
表示名
SquareCircle
プロパティの型
出力
データ型
ブール値
パラメーターには以下のようなものを作成します。
パラメータ名 | データ型 | 役割 |
---|---|---|
x_1 | 数値 | 四角形オブジェクトのX座標 |
y_1 | 数値 | 四角形オブジェクトのY座標 |
width | 数値 | 四角形オブジェクトの幅 |
height | 数値 | 四角形オブジェクトの高さ |
x_2 | 数値 | 円形オブジェクトの中心のX座標 |
y_2 | 数値 | 円形オブジェクトの中心のY座標 |
r | 数値 | 円形オブジェクトの変形 |
四角形と円の当たり判定の理論については以下サイトが大変わかりやすく解説をしてくれていました。
円と長方形の当たり判定
こちらのサイトに記載の式をPower Apps で記載すると以下のようになります。
SquareCircle
// 矩形上下領域 (x_2 > x_1) && (x_2 < x_1 + width) && (y_2 > y_1 - r) && (y_2 < y_1 + height + r) || // 矩形左右領域 (x_2 > x_1 - r) && (x_2 < x_1 + width + r) && (y_2 > y_1) && (y_2 < y_1 + height) || // 左上の円 Power(x_1 - x_2, 2) + Power(y_1 - y_2, 2) < Power(r, 2) || // 右上の円 Power((x_1 + width) - x_2, 2) + Power(y_1 - y_2, 2) < Power(r, 2) || // 右下の円 Power((x_1 + width) - x_2, 2) + Power((y_1 + height) - y_2, 2) < Power(r, 2) || // 左下の円 Power(x_1 - x_2, 2) + Power((y_1 + height) - y_2, 2) < Power(r, 2)
なおこのとき、
= 矩形上下領域
= 矩形左右領域
= 左上の円
= 右上の円
= 左下の円
= 右下の円
です。
実際に利用してみる
では、作成したカスタム関数を実際に利用してみましょう!
まずは、先ほど作成したカスタム関数を画面内に追加します。
続いて以下のようなコレクションを作成して、ステージの情報を作成します。
ClearCollect( stage, {x:0, y:0, width:0, height:0, Type:"Square"}, {x:0, y:Self.Height / 2, width:150, height:150, Type:"Square"}, {x:200, y:100, width:150, height:150, Type:"Square"}, {x:500, y:300, width:300, height:150, Type:"Square"}, {x:1000, y:70, width:150, height:400, Type:"Square"}, {x:50, y:100, r:50, Type:"Circle"}, {x:300, y:400, r:100, Type:"Circle"}, {x:600, y:650, r:70, Type:"Circle"}, {x:650, y:90, r:90, Type:"Circle"}, {x:1200, y:550, r:40, Type:"Circle"}, {x:0, y:0, r:0, Type:"Circle"} )
今回はこの情報をもとに、SVG でステージを描画したいと思います。
イメージを追加して以下のように設定します。
Image.Image
"data:image/svg+xml,"& EncodeUrl( "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'>" & Concat( ForAll( stage, Switch( Type, "Square", "<rect x='" & x &"' y='" & y &"' width='" & width &"' height='" & height &"' />", "Circle", "<circle cx='" & x &"' cy='" & y &"' r='" & r &"' />" ) ), Value ) & " </svg>" )
次にこのステージのオブジェクトとの当たり判定を行うオブジェクトを設置します。
今回
- 四角 × 四角
- 円 × 円
- 四角 × 円
の3パターンの当たり判定を作成しており、オブジェクトの種類は四角形と円の2種なので、"アイコン"より"四角形"と"円"の2つを追加してください。
これらは現在選択している値によって切り替えたいと思います。
"ラジオ"を追加して以下のように設定します。
Radio1.Items
["Square", "Circle"]
文字通り、このラジオにて選択されているオブジェクトを表示したいため、それぞれのオブジェクトの Visible
で以下のように設定します。
Rectangle1.Visible
Radio1.Selected.Value = "Square"
Circle1.Visible
Radio1.Selected.Value = "Circle"
また、円に関しては楕円ではなく、真円での当たり判定なので、高さと幅は同じ値となるようにしておきましょう。
Circle1.Height
Self.Width
※ 楕円の当たり判定の式は大分複雑な計算式となるので省いています。
実装が大変なのはもちろんですが、Power Apps で当たり判定のような頻繁に呼ばれるようなものでそのような複雑な計算式は処理性能の観点から避けるべきではないかなぁ。。。と個人的には考えています。
式に関しては興味のある方は是非調べてみてください。
次にこのオブジェクトたちを動かすためのスライダーを設定します。
今回は簡単のために、X座標用のスライダーとY座標用のスライダーの2つを作成しましょう。
X座標用のスライダーの最大値は Parent.Width
に、Y座標用のスライダーのスライダーの最大値は Parent.Height
に設定しておきます。
設定ができたら、スライダーの値がオブジェクトの X
、 Y
に設定されるようにしましょう。
X
X
Y
Parent.Height - Y
※ スライダーの名前をそれぞれXとYにしています。
これでスライダーの値を変えることでオブジェクトの座標も変わるようになったかと思います。
これでオブジェクトたちの用意はできたので、最後に当たり判定です。
当たり判定は、これまでの私のブログでも紹介してきたように、トグル(切り替え)を利用します。
トグルの Defaule
に以下のように設定します。
Toggle1
CountIf( ForAll( stage, If( Type = "Square" && Radio1.Selected.Value = "Square", CollisionUtils_1.SquareSquare( Rectangle1.X, Rectangle1.Y, Rectangle1.Width, Rectangle1.Height, x, y, width, height ), Type = "Circle" && Radio1.Selected.Value = "Square", CollisionUtils_1.SquareCircle( Rectangle1.X, Rectangle1.Y, Rectangle1.Width, Rectangle1.Height, x, y, r ), Type = "Circle" && Radio1.Selected.Value = "Circle", CollisionUtils_1.CircleCircle( Circle1.X + Circle1.Width / 2, Circle1.Y + Circle1.Height / 2, Circle1.Width / 2, x, y, r ), Type = "Square" && Radio1.Selected.Value = "Circle", CollisionUtils_1.SquareCircle( x, y, width, height, Circle1.X + Circle1.Width / 2, Circle1.Y + Circle1.Height / 2, Circle1.Width / 2 ) ) ), Value = true ) > 0
簡単に解説します。
オブジェクト間の当たり判定を行うのは、"コレクションに設定された値" と "オブジェクトの値" です。
コレクションの情報を取得するために ForAll 関数を利用しています。
このとき1レコードでも接している、つまり True
のものが1レコードでもあれば接していると判断できるため、CoutIf 関数の結果が0より多きければ、オブジェクトどうしが接していると判断することができます。
また、オブジェクトどうしの組み合わせで呼び出す関数が異なるので、If 関数でそれぞれ呼び出す関数をわけています。
この関数の呼び出し時1点注意すべき事項があります。
作成した関数では、円の中心座標を受け取ることを想定しています。
SVG で円を描画する際の、 circle
では円の中心座標と半径を指定して描画するのに対し、Power Apps のオブジェクトである Circle は円の左上上端の座標と直径の座標を指定して描画します。
これらの違いを認識する必要があります。
よって、Power Apps で描画されている円の中心のX座標を取得するには Circle1.X + Circle1.Width / 2
とし、円の中心のY座標を取得するには Circle1.Y + Circle1.Height / 2
とし、円の半径を取得するには Circle1.Width / 2
とする必要があります。
最後に当たったとされた場合に視認しやすいようにしましょう。
今回はこれをオブジェクトの色を変更することでわかるようにしたいと思います。
Fill
If( Toggle1.Value, Red, RGBA(56, 96, 178, 1) )
以上で完了です!
お疲れさまでした!!
おわりに
こういったよく使うような汎用的な式を関数化することによって、今後の開発を高速化することが可能になります。
次はダメージ計算とかを関数化してみて、"~ゲームを作りながら覚える~ Power Apps のカスタム関数"とかやりたいですね。