#PowerApps GameDev#5 通信対戦を実装してみた

# PowerApps Game Tech No5
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">通信対戦を実装した。<br><br>I built a game with a communication competition function using <a href="https://twitter.com/hashtag/PowerApps?src=hash&amp;ref_src=twsrc%5Etfw">#PowerApps</a> !!<a href="https://twitter.com/hashtag/PowerAppsGame?src=hash&amp;ref_src=twsrc%5Etfw">#PowerAppsGame</a><a href="https://twitter.com/hashtag/PowerPlatform?src=hash&amp;ref_src=twsrc%5Etfw">#PowerPlatform</a><a href="https://twitter.com/hashtag/SQLServer?src=hash&amp;ref_src=twsrc%5Etfw">#SQLServer</a><a href="https://twitter.com/hashtag/Azure?src=hash&amp;ref_src=twsrc%5Etfw">#Azure</a><a href="https://twitter.com/hashtag/jpaug?src=hash&amp;ref_src=twsrc%5Etfw">#jpaug</a> <a href="https://t.co/QvUGlWGt30">pic.twitter.com/QvUGlWGt30</a></p>&mdash; りなたむ || RYOTA NAKAMURA (@R_t_A_n_M) <a href="https://twitter.com/R_t_A_n_M/status/1128342990265536513?ref_src=twsrc%5Etfw">May 14, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 通信対戦の仕組み
## 1.ゲームルームの作成及び参加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269159/2d77193c-bb78-d472-f3e3-e7e34b90af7f.png)


① 1P側がゲームルームを作成すると、Azure上のSQL Serverに生成したroom idと1Pの情報、ターン情報としてPendingとしてInsertする

② 2P側はターン情報がPendingとなっているroom id一覧を取得する

③ 選択したroom idのレコードに2Pの情報及びターン情報を1PTurnとしてUpdateする

## 2.ゲームの進行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269159/3167a062-6ce6-45d8-7ecd-681c45e45d4e.png)

① 1秒間隔で、参加中のroom idのレコードを参照する

② ターン情報が1PTurnとなっている場合にのみ攻撃ボタンが有効化される

③ 攻撃ボタンを押すと、1Pのステータスと2Pのステータス情報をもとに、ダメージ値を計算し、2PのHPを減算しUpdateする

④ ターン情報が2PTurnとなっている場合にのみ攻撃ボタンが有効化される

⑤ 攻撃ボタンを押すと、1Pのステータスと2Pのステータス情報をもとに、ダメージ値を計算し、1PのHPを減算しUpdateする

# 作り方
## Azure SQL Server
SQL Serverの作成方法は割愛します。
作り方はこちらのようなサイトをご覧くださいませ
[日商エレクトロニクスさんのブログ](https://cloud.nissho-ele.co.jp/blog/azure-sqldb/)

### テーブル構造
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269159/b9420c8b-352a-c9d6-d8b2-5557f9ea7434.png)

### DDL文

``` sql:PowerApps_NetBattle_Test_tbl.sql
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[PowerApps_NetBattle_Test_tbl](
	[room_id] [char](50) NOT NULL,
	[1PMAXHP] [int] NOT NULL,
	[1PCurrentHP] [int] NOT NULL,
	[1PPwr] [int] NOT NULL,
	[1PDfs] [int] NOT NULL,
	[1PSpd] [int] NOT NULL,
	[1PLcy] [int] NOT NULL,
	[1PMAXMP] [int] NOT NULL,
	[1PCurrentMP] [int] NOT NULL,
	[2PMAXHP] [int] NULL,
	[2PCurrentHP] [int] NULL,
	[2PPwr] [int] NULL,
	[2PDfs] [int] NULL,
	[2PSpd] [int] NULL,
	[2PLcy] [int] NULL,
	[2PMAXMP] [int] NULL,
	[2PCurrentMP] [int] NULL,
	[Turn] [char](10) NULL,
 CONSTRAINT [PK_PowerApps_NetBattle_Test_tbl] PRIMARY KEY CLUSTERED 
(
	[room_id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

```

## PowerApps
### 0.App
アプリ起動時に実行される処理を定義します。

```Bash:OnStart
//global_valiable
Set(Dist_Point,20);

//1P_global_valiable
Set(Pre_1p_Max_HP,20);
Set(Pre_1p_Current_HP,Pre_1p_Max_HP);
Set(Pre_1p_Pwr,1);
Set(Pre_1p_Dfs,1);
Set(Pre_1p_Spd,1);
Set(Pre_1p_Lcy,1);
Set(Pre_1p_Max_MP,3);
Set(Pre_1p_Current_MP,3);
Set(Pre_1p_Dist_Point,Dist_Point);
Set(Pre_Room_ID,GUID());

//2P_global_valiable
Set(Pre_2p_Max_HP,Pre_1p_Max_HP);
Set(Pre_2p_Current_HP,Pre_2p_Max_HP);
Set(Pre_2p_Pwr,0);
Set(Pre_2p_Dfs,0);
Set(Pre_2p_Spd,0);
Set(Pre_2p_Lcy,0);
Set(Pre_2p_Max_MP,Pre_1p_Max_MP);
Set(Pre_2p_Current_MP,Pre_2p_Max_MP);
Set(Pre_2p_Dist_Point,Dist_Point);
```

### 1.SelectScreen
ここではボタンを二つ作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269159/0c4a0902-90fb-f267-6705-35e9c32d86e7.png)

#### 新しく部屋を作成するボタン
ボタンを押すと以下の処理が実行されるように宣言します。

```bash:OnSelect
Navigate(CreateRoom,ScreenTransition.Fade)
```

#### 部屋に参加するボタン
ボタンを押すと以下の処理が実行されるように宣言します。

```bash:OnSelect
Navigate(JoinRoom,ScreenTransition.Fade)
```

### 2.CreateRoom
こんな画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269159/60f9f2e5-e9eb-1605-b8c6-916f38d078e0.png)

各パラメータを操作する部分はこちらの記事を参考に作成してください。
[PowerAppsでバトルゲームを作ろう！（その１：パラメータ設定画面）](https://qiita.com/rnakamuramartiny/items/bffe2aa96e7989949c98)

#### 画面遷移ボタン
ボタンを押すと以下の処理が実行されるように宣言します。

```bash:OnSelect
Patch(
    '[dbo].[PowerApps_NetBattle_Test_tbl]',
    Defaults('[dbo].[PowerApps_NetBattle_Test_tbl]'),
    {
        room_id: Pre_Room_ID,
        '1PMAXHP': Pre_1p_Max_HP,
        '1PMAXMP': Pre_1p_Max_MP,
        '1PCurrentHP': Pre_1p_Max_HP,
        '1PCurrentMP': Pre_1p_Max_MP,
        '1PPwr': Pre_1p_Pwr,
        '1PDfs': Pre_1p_Dfs,
        '1PSpd': Pre_1p_Spd,
        '1PLcy': Pre_1p_Lcy,
        Turn: "Pendding"
    }
);
Set(
    CurrentRoomID,
    Pre_Room_ID
);
Navigate(
    BattleView,
    ScreenTransition.Fade
)
```

### 3.JoinRoom
こんな画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269159/dfe86135-83a7-729e-7a8e-67789a0b9167.png)

#### 参加する部屋の選択制御
部屋を選択するドロップダウンボックスはこのように宣言します。

```bash:Items
Filter('[dbo].[PowerApps_NetBattle_Test_tbl]',Turn="Pendding").room_id
```

#### 画面遷移ボタン
ボタンを押すと以下の処理が実行されるように宣言します。

```bash:OnSelect
Set(
    CurrentRoomID,
    Dropdown1.SelectedText.Value
);
Patch(
    '[dbo].[PowerApps_NetBattle_Test_tbl]',
    First(
        Filter(
            '[dbo].[PowerApps_NetBattle_Test_tbl]',
            room_id = Dropdown1.SelectedText.Value
        )
    ),
    {
        '2PMAXHP': Pre_2p_Max_HP,
        '2PMAXMP': Pre_2p_Max_MP,
        '2PCurrentHP': Pre_2p_Max_HP,
        '2PCurrentMP': Pre_2p_Max_MP,
        '2PPwr': Pre_2p_Pwr,
        '2PDfs': Pre_2p_Dfs,
        '2PSpd': Pre_2p_Spd,
        '2PLcy': Pre_2p_Lcy,
        Turn: "1PTurn"
    }
);
Navigate(
    BattleView,
    ScreenTransition.Fade
)
```

### 4. BattleView
こんな画面になります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269159/2694e101-7c47-2b98-54a7-a1025a6663fb.png)

#### ステータス表示部
HP部分はこんな風になっています。

```bash:Text
Concatenate(
    "HP：",
    Text(
        First(
            Filter(
                '[dbo].[PowerApps_NetBattle_Test_tbl]',
                room_id = CurrentRoomID
            )
        ).'1PCurrentHP'
    ),
    "/",
    Text(
        First(
            Filter(
                '[dbo].[PowerApps_NetBattle_Test_tbl]',
                room_id = CurrentRoomID
            )
        ).'1PMAXHP'
    )
)
```

#### 攻撃ボタン
攻撃ボタン（1PTurnと2PTurn）は、ボタン操作を制御するプロパティ（DisplayMode）と押したときに処理が実行されるプロパティ（OnSelect）を設定しています。
今回の処理は検証のために複雑なダメージ計算式などは除外、簡略化しています。

```bash:DisplayMode
If(
    First(
        Filter(
            '[dbo].[PowerApps_NetBattle_Test_tbl]',
            room_id = CurrentRoomID
        )
    ).Turn = "1PTurn",
    DisplayMode.Edit,
    Disabled
)
```

```bash:OnSelect(1PTurn)
Set(
    Pre_2p_Current_HP,
    First(
        Filter(
            '[dbo].[PowerApps_NetBattle_Test_tbl]',
            room_id = CurrentRoomID
        )
    ).'2PCurrentHP' - 1
);
Patch(
    '[dbo].[PowerApps_NetBattle_Test_tbl]',
    First(
        Filter(
            '[dbo].[PowerApps_NetBattle_Test_tbl]',
            room_id = CurrentRoomID
        )
    ),
    {
        '2PCurrentHP': Pre_2p_Current_HP,
        Turn: "2PTurn"
    }
)
```
```bash:OnSelect(2PTurn)
Set(
    Pre_1p_Current_HP,
    First(
        Filter(
            '[dbo].[PowerApps_NetBattle_Test_tbl]',
            room_id = CurrentRoomID
        )
    ).'1PCurrentHP' - 1
);
Patch(
    '[dbo].[PowerApps_NetBattle_Test_tbl]',
    First(
        Filter(
            '[dbo].[PowerApps_NetBattle_Test_tbl]',
            room_id = CurrentRoomID
        )
    ),
    {
        '1PCurrentHP': Pre_1p_Current_HP,
        Turn: "1PTurn"
    }
)
```

#### SQL Serverの情報取得タイマー
現在のレコードの内容を取得するタイマーです。
1秒毎に繰り返す設定をしています。
タイマーがスタートしたときに以下の処理が実行されるように宣言します。

```bash:OnTimerStart
Refresh('[dbo].[PowerApps_NetBattle_Test_tbl]')
```

# データソースについて
今回異なるテナントでも通信対戦ができるようにとデータソースとしてSQL Serverを使用しました。一番格安なBasicプランでも、サクサク動作するようです。
使用者が増えたりした場合は順次Standardなどに切り替えればよろしいかと思います。

# まとめ
やってる内容については実はあまり大したことをしていません。
仕組みも理解できれば案外単純なのか！とおもったり・・・。
実際はトランザクション処理などいろいろ大変だとは思いますが。使い方が個人利用レベルであればこの程度でも十分でしょう。
これをもとにバーコードバトラーを改造しようかなと考えています。