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

# キャラクターに動きをつけよう！
鉄は熱いうちに打たないとやる気をなくしそう。
ということで連続でQiitaに前回の続きを書いてみます。

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

https://qiita.com/DEmodoriGatsuO/items/4f1c4b024b691bca83a6

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/ae6a1092-3c6c-b703-5ba5-2b00fc4a9e75.png)

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

- characterの画像

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

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

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

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

## 「動いている」という条件を定義する
さて、何をもって`動いている状態`と定義するか。

https://learn.microsoft.com/ja-jp/power-apps/maker/canvas-apps/controls/control-button

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

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

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

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

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

https://zezeze.hateblo.jp/entry/2020/03/21/113333

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

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

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

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

## Timer コントロールの設定
言葉だけだと、全く意味がわからないかもしれないので、`プロパティ`を列挙します。

- **Timerコントロール**

|プロパティ|訳|値
|---|---|---
| Duration | 期間 | 400
| Repeat | 繰り返し | true
| AutoStart | 自動開始 | true 
| OnTimerStart | タイマーの開始時 | 関数を記載

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

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

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

## 関数の部分
先に`OnTimerStart`の関数を書きます。
長いです。

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

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

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

### 大事な部分である`Patch関数`
Patch関数は、コレクションの更新のために利用しています。

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-patch

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/a4dfff76-a7e9-9043-4eb4-08d633a911db.png)

## 静止画に戻す
それぞれのボタンの`OnSelect`には、下記の様な関数を書いています。
```plaintext:上ボタンのOnSelect
// 画像を静止画にする
UpdateContext({_me:back});
// ギャラリーを更新する
Patch(moves,
    First(Filter(moves,index=_index))
    ,
    {obj:_me}
);
```

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

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

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

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

# 改めて動画を紹介
<iframe width="560" height="315" src="https://www.youtube.com/embed/7iP9BDtRbII" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

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

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

**それでは皆様！良いPower Lifeを！**