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

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

【Power Apps】カスタム関数で当たり判定を作成する


スポンサードリンク

はじめに

この記事ではPower Apps のカスタム関数を利用して、当たり判定関数の作成方法について解説します。

今回取り扱う当たり判定は、

  • 四角 × 四角
  • 円 × 円
  • 四角 × 円

の3パターンです。

当たり判定を作成する

Power Apps のカスタム関数ってなに?という方は、【Power Apps】カスタム関数が作成できるようになりました! を先にご覧ください。

四角 × 四角

まずは一番単純な四角 × 四角の当たり判定を作成します。

この当たり判定の式は、【GPPB Virtual Bootcamp 2021 Sapporo】30分で作成!?解説しながら作る横スクアクションゲームで解説した式を利用します。

今回は上記ブログの流用で、再度の解説は省きます。

当たり判定は以下のようにPlayer とStage を定義したとき

f:id:koruneko:20210214163618p:plain

以下のような式であらわすことができます。

((PlayerX > StageX && PlayerX < StageX + StageWidth) || (StageX > PlayerX && StageX < PlayerX + PlayerWidth)) && ((PlayerY > StageY && PlayerY < StageY + StageHeight) || (StageY > PlayerY && StageY < PlayerY + PlayerHeight))

まず2つの図形が"重なっている"というのは、下図のようにX成分でみたときもY成分でみたときも重なっている。
というのが条件になります。

f:id:koruneko:20210214164617p:plain

上の式でいいますと、
この式が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の変形

円と円の当たり判定にはピタゴラスの定理を活用します。

パラメータの値を図にして表すと以下のようになります。

f:id:koruneko:20210306200901p:plain

このとき、2つの円の距離は三平方の定理によって求めることが可能ですね。

f:id:koruneko:20210306201529p:plain

 c^{2} = a^{2} + b^{2}

このとき、
 a = x_1 - x_2
 b = y_1 - y_2
 x_1 などは x_1 に読み替えてください。
であるので2つの円の距離は、
 \sqrt{(x_1 - x_2)^{2} + (y_1 - y_2)^{2}}
となります。

この2つの円の距離と、2つの円の半径の和を比較して、この2つの円の距離が2つの円の半径の和以下であれば2つの円が接していると判断することができます。
 \sqrt{(x_1 - x_2)^{2} + (y_1 - y_2)^{2}}  \leqq r_1 + r_2

このとき、より式の記載を簡単にするために左辺の平方根を右辺を乗算することで簡略化させましょう。
 (x_1 - x_2)^{2} + (y_1 - y_2)^{2}  \leqq (r_1 + r_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)

なおこのとき、
 A = 矩形上下領域
 B = 矩形左右領域
 C = 左上の円
 D = 右上の円
 E = 左下の円
 F = 右下の円
です。

実際に利用してみる

では、作成したカスタム関数を実際に利用してみましょう!

まずは、先ほど作成したカスタム関数を画面内に追加します。

続いて以下のようなコレクションを作成して、ステージの情報を作成します。

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 に設定しておきます。

設定ができたら、スライダーの値がオブジェクトの XY に設定されるようにしましょう。

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 のカスタム関数"とかやりたいですね。


スポンサードリンク