LoginSignup
2
0

Power Appsで簡単なRPGを実現してみよう!- Part2

Posted at

キャラクターに動きをつけよう!

鉄は熱いうちに打たないとやる気をなくしそう。
ということで連続でQiitaに前回の続きを書いてみます。

前回はコチラ。
是非見てくださいね♪

何だかんだ見ていただいたりすることは、とても嬉しいです!

今回は、キャラクターの動きを表現します。

image.png

キャラクターの動作について

今回の作成において、メインキャラクターの画像は下記の通り用意しております。

  • characterの画像
Imageの名前 画像の種別 メモ
front 静止画 前向き
back 静止画 後ろ向き
right 静止画 右向き
left 静止画 左向き
frontM GIF画像 前向き
backM GIF画像 後ろ向き
rightM GIF画像 右向き
leftM GIF画像 左向き

合計8件 ※命名ルールは読者の価値観で決めてください

動いているときは、GIF画像
動いていないときは、静止画

上記のような定義で決めていきます。

「動いている」という条件を定義する

さて、何をもって動いている状態と定義するか。

Button コントロールPressedという値で定義します。

Pressed – コントロールが押されている間は true 、それ以外の場合は false。`
learnより

ゲームボーイのボタン押しっぱなしのような状態を、まさにキャラクターが動いていると定義します。

課題になるのはいつ評価するか

Power Appsの処理は、Button コントロールを押す(OnSelect)といったイベントがもとになります。
しかしながら押しっぱなしで、仮にGIF画像にデータを変更する、ということはできても、
継続的に動き続けるという表現は難しくなります。

これを可能にするコントロールをがTimer コントロールです。
下記のブログが大変参考になります。

とてもPower Appsを中心に凄いアウトプットを沢山出されていらっしゃる方です。

二番煎じになりますが、荒っぽく書くと
メッチャ短いスパンでタイマー開始OnTimerStartイベントを発火させて、高速で状態を評価し、動きを実現しているという方法です。

全く思いつかなかった方法なので、目から鱗でした👀💦

本当に参考になるので、是非上👆のブログを読んでいただきたいです。

Timer コントロールの設定

言葉だけだと、全く意味がわからないかもしれないので、プロパティを列挙します。

  • Timerコントロール
プロパティ
Duration 期間 400
Repeat 繰り返し true
AutoStart 自動開始 true
OnTimerStart タイマーの開始時 関数を記載

Durationの部分は、1000 = 1秒と該当するので、
400 = 0.4秒です。

0.4秒繰り返し続けて、開始時に関数が発火します。
この時の状態に応じて、関数が適用されるので、動きが達成されているわけです。

本当に良く思いつくなと圧巻でした💦

関数の部分

先にOnTimerStartの関数を書きます。
長いです。

OnTimerStart

// ボタンコントロールのいずれかが真 trueの場合
Switch(true,
    // 上ボタンを押している場合
    ButtonUp.Pressed,
        // _meという画像用の変数をGIF画像にアップデート、移動する方向を数値で決める
        UpdateContext({_me:backM,Direction:-1});
        // 動いた場合の値を先に求める
        UpdateContext({_next:_index + Direction});
        // メインキャラクターが動けない、またはギャラリーコントロールの総数より多いレコード数の場合
        // 移動はできないとする
        If(Or(_next in disableArea, _next > CountRows(maps)),
            UpdateContext({_next:_index});
        );
        // 移動後と移動前の値が異なるとき
        If(_next <> _index,
            // 移動前のマスの画像を無色透明の画像にする
            Patch(moves,
                First(Filter(moves,index=_index))
                ,
                {obj:_null}
                );
            // 移動後のマスにキャラクターのGIF画像にする
            Patch(moves,
                First(Filter(moves,index=_next))
                ,
                {obj:_me}
            );
            // 移動前を表す変数を移動後の値に変更する
            UpdateContext({_index:_next});
        ),
    // 上記のコメント同様 左バージョン
    ButtonLeft.Pressed,
        UpdateContext({_me:leftM,Direction:-8});
        UpdateContext({_next:_index + Direction});
        If(Or(_next in disableArea, _next > CountRows(maps)),
            UpdateContext({_next:_index});
        );
        If(_next <> _index,
            Patch(moves,
                First(Filter(moves,index=_index))
                ,
                {obj:_null}
                );
            Patch(moves,
                First(Filter(moves,index=_next))
                ,
                {obj:_me}
            );
            UpdateContext({_index:_next});
        ),
    // 上記のコメント同様 右バージョン
    ButtonRight.Pressed,
        UpdateContext({_me:rightM,Direction:8});
        UpdateContext({_next:_index + Direction});
        If(Or(_next in disableArea, _next > CountRows(maps)),
            UpdateContext({_next:_index});
        );
        If(_next <> _index,
            Patch(moves,
                First(Filter(moves,index=_index))
                ,
                {obj:_null}
                );
            Patch(moves,
                First(Filter(moves,index=_next))
                ,
                {obj:_me}
            );
            UpdateContext({_index:_next});
        ),
    // 上記のコメント同様 下バージョン
    ButtonBack.Pressed,
        UpdateContext({_me:frontM,Direction:1});
        UpdateContext({_next:_index + Direction});
        If(Or(_next in disableArea, _next > CountRows(maps)),
            UpdateContext({_next:_index});
        );
        If(_next <> _index,
            Patch(moves,
                First(Filter(moves,index=_index))
                ,
                {obj:_null}
                );
            Patch(moves,
                First(Filter(moves,index=_next))
                ,
                {obj:_me}
            );
            UpdateContext({_index:_next});
        )
);

コメントに書きましたが整理すると

  1. ボタンを押している場合
  2. _meという画像用の変数をGIF画像にアップデート、移動する方向を数値で決める、
    そして動いた場合の値を先に求める
  3. メインキャラクターが動けない、またはギャラリーコントロールの総数より
    多いレコード数の場合、移動はできないとする
  4. 移動後と移動前の値が異なるとき、
    • 移動前のマスの画像を無色透明の画像にする
    • 移動後のマスにキャラクターのGIF画像にする
  5. 移動前を表す変数を移動後の値に変更する

といったコトを行っています。
合わせて変数を解説すると以下の通りです。

変数 メモ
_me Image メインキャラクターの画像
_obj Image メインキャラクター以外のマスの画像
_index 数値 メインキャラクターの位置
_next 数値 動いた場合に位置する場所
move コレクション 画像とインデックスを制御
disableArea 配列 移動できない数値の配列

大事な部分であるPatch関数

Patch関数は、コレクションの更新のために利用しています。

UpdateContext関数だけでは、ギャラリー内の画像に変化は起きません。

都度Patch関数を使うことでギャラリーによる、表現を達成しているという論理になります。

image.png

静止画に戻す

それぞれのボタンのOnSelectには、下記の様な関数を書いています。

上ボタンのOnSelect
// 画像を静止画にする
UpdateContext({_me:back});
// ギャラリーを更新する
Patch(moves,
    First(Filter(moves,index=_index))
    ,
    {obj:_me}
);

これで動きのStopが表現できます。

OnSelectOnTimerStartの評価のタイミングは非同期です。
別々のタイミングで評価され、関数が発火します。

こういった関数をそれぞれのButton コントロールに配備することで実現しているわけですね。

高速でイベントを起こすってなんじゃそりゃ!? と書きながら改めて驚愕ですが、
これが実現できるPower Apps最高です!

改めて動画を紹介

キャラクターはそれぞれ、マス目に合わせて配備しています。
動けない部分はdisableAreaに配置したりと、結構力業になってます・・・が、
実現できることが何よりうれしい!

ということで、Power Apps RPG紹介 を締めくくりたいと思います!

それでは皆様!良いPower Lifeを!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0