知識ゼロからはじめるローコーディング開発

Power Platformでのアプリ開発について、いろいろと書きたいと思います。

Power Apps で疑似3Dシューティングゲームの地面を作成する①


JPPGBゲームコンテスト#1で発表をさせて頂きました疑似3Dシューティングゲーム「Power Guardians」。Power Apps の機能だけで作成させて頂いたこのゲームについて、たくさんの嬉しいコメントを頂き、コンテスト当日は最優秀賞まで頂くことができました。ご参加を頂いた皆様には本当に感謝の思いでいっぱいです。

コメントの中には「どのようにして作ったのか?」という質問もたくさんございまして、会場では出来る限りお話をさせて頂いたのですが、なかなかお伝えしきれないところもありましたので、あらためてブログの中で書かせて頂こうと思った次第です。少しでもご期待に沿うことができれば幸いです。

今回は地面のアニメーションについてお話をさせて頂こうと思いますが、疑似3Dについて少しだけ復習をさせてください。疑似3Dというのは、3次元グラフィックスの処理技術を使わずに、平面画像のみで奥行や立体感表現することです。基本平面画像しか使いませんので、最近の3Dゲームのように対象を回転させて見ることは出来ませんし視点を変えて、あらゆる角度から見るということもできません。


このような制限の中、どのようにして立体感や奥行を表現するのかというと、表示する画像を場面によって大きくしたり、小さくしたり、または移動する速さを変えることで、プレイヤーに3Dらしさを感じさせます。仕組みとしては、プレイヤー機、敵機、背景画像をそれぞれ別々に作成しておき、敵機の大きさをプレイヤーから遠い場合は小さく、近い場合は大きくなるように表示します。アニメーションさせる際には、遠い場合は遅く、近い場合は速く移動するように設定をします。

さて、疑似3Dの基本的な仕組みがわかったところで地面のアニメーションについて見ていきましょう。実際にゲームをプレイすると、プレイヤーが奥に向かって進んでいるように見えるのですが、よく見ると地面が手前に向かって流れてきているためだというのがわかります。
このアニメーションはSVGというもの使っていて、画像コントロールの中にセットをするのですが少し複雑な設定をしています。遠近感を感じさせるためには、まず、地面に等間隔に引いた線がプレイヤーの視点から、どのように見えるかを考えてみなくてはなりません。

地面に等間隔に引いた線をプレイヤー視点で見ると、奥の線の間隔が狭く、手前が広くなっているはずです。この線の間隔には、とある法則がありまして、一番手前の線の間隔を1としたとき、2番目以降の線の間隔は1/3、1/6、1/10…と細くなっていきます。この分母の値は初項1、公差1の等差数列の和で求めることができます。なので、線の順番をnとすると計算式はn(n+1)/2となります。

この計算式の答えをSVGにセットすれば奥行を感じさせる地面のアニメーションができあがるはずです。実際のゲームの中では、計算式からもとめた値を基に、奥から次第に高さが広くなるような長方形を14本セットしています。アニメーションは、各長方形の高さ分上から下に移動するアニメーションを無限に繰り返すことで、自然なアニメーションが表示されるはずです。

線が14本もあるので、変数やループ処理等を加えたいところですが、全てSVG側で制御した方がスムーズなアニメーションになりましたので、設定は大変ですが、長方形は1本ずつ設定しています。
テストで使用した設定を下記に書かせて頂きましたので、試してみたい方は、画像コントロールのImageプロパティに張り付けてみてください。

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg width='"& Self.Width & "' height='" & Self.Height & "' viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'>
        <rect x='0' y='0' width='1136' height= '1' fill='#6666CC66'></rect>
        <rect x='0' y='2' width='1136' height= '1' fill='#6666CC66'>
		<animate attributeName='y' from='0' to='2' dur='0.15s' repeatCount='indefinite' /></rect>
        <rect x='0' y='4' width='1136' height= '3' fill='#6666CC66'>
		<animate attributeName='y' from='2' to='4' dur='0.15s' repeatCount='indefinite' /></rect>
        <rect x='0' y='8.3' width='1136' height= '3.5' fill='#6666CC66'>
		<animate attributeName='y' from='8.3' to='9' dur='0.15s' repeatCount='indefinite' /></rect>
        <rect x='0' y='9' width='1136' height= '4.55' fill='#6666CC66'>
		<animate attributeName='y' from='9' to='19' dur='0.15s' repeatCount='indefinite' /></rect>
        <rect x='0' y='19' width='1136' height= '5' fill='#6666CC66'>
		<animate attributeName='y' from='19' to='30' dur='0.15s' repeatCount='indefinite' /></rect>
        <rect x='0' y='30' width='1136' height= '5.5' fill='#6666CC66'>
		<animate attributeName='y' from='30' to='42.7' dur='0.15s' repeatCount='indefinite' />
		<animate attributeName='height' from='5.5' to='6.25' dur='0.15s' repeatCount='indefinite' /></rect>
        <rect x='0' y='42.7' width='1136' height='6.25' fill='#6666CC66'>
		<animate attributeName='y' from='42.7' to='56.9' dur='0.15s' repeatCount='indefinite'/>
		<animate attributeName='height' from='6.25' to='7.14' dur='0.15s'  repeatCount='indefinite' /></rect>
        <rect x='0' y='56.9' width='1136' height='7.14' fill='#6666CC66'>
		<animate attributeName='y' from='56.9' to='73.6' dur='0.15s' repeatCount='indefinite'/>
		<animate attributeName='height' from='7.14' to='8.3' dur='0.15s'  repeatCount='indefinite' /></rect>
        <rect x='0' y='73.6' width='1136' height='8.3' fill='#6666CC66' >
		<animate attributeName='y' from='73.6' to='93.6' dur='0.15s' repeatCount='indefinite'/>
		<animate attributeName='height' from='8.3' to='10' dur='0.15s'  repeatCount='indefinite' /></rect>
        <rect x='0' y='93.6' width='1136' height='10' fill='#6666CC66'  >
		<animate attributeName='y' from='93.6' to='118.6' dur='0.15s' repeatCount='indefinite'/>
		<animate attributeName='height' from='10' to='12.5' dur='0.15s'  repeatCount='indefinite' /></rect>
        <rect x='0' y='118.6' width='1136' height='12.5' fill='#6666CC66'>
		<animate attributeName='y' from='118.6' to='151.9' dur='0.15s' repeatCount='indefinite' />
		<animate attributeName='height' from='2.5' to='16.6' dur='0.15s'  repeatCount='indefinite' /></rect>
        <rect x='0' y='151.9' width='1136' height='16.6' fill='#6666CC66'>
		<animate attributeName='y' from='151.9' to='201.9' dur='0.15s' repeatCount='indefinite'/>
		<animate attributeName='height' from='16.6' to='25' dur='0.15s' repeatCount='indefinite' /></rect>
        <rect x='0' y='201.9' width='1136' height= '25' fill='#6666CC66'>
		<animate attributeName='y' from='201.9' to='251.9' dur='0.15s' repeatCount='indefinite' />
		<animate attributeName='height' from='25' to='30' dur='0.15s' repeatCount='indefinite' /></rect>
    </svg>"
)

下記画像のように、奥から流れてくるような動きになれば成功です。

テストのSVGでは高さなどが細かく設定してありますが、前述の式の値だけでは微妙なズレを感じていましたので、細かく調整をさせて頂いています。もし違和感を感じるところがありましたら、height や to などの値を変更してみてください。

今回の解説はここまでです。
次回はプレイヤー機に合わせて動く、放射状の地面の模様について解説をさせて頂きたいと思います。