Edited at

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


PowerApps Game Tech No5


通信対戦の仕組み


1.ゲームルームの作成及び参加

image.png

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

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

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


2.ゲームの進行

image.png

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

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

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

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

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


作り方


Azure SQL Server

SQL Serverの作成方法は割愛します。

作り方はこちらのようなサイトをご覧くださいませ

日商エレクトロニクスさんのブログ


テーブル構造

image.png


DDL文


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

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


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


新しく部屋を作成するボタン

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


OnSelect

Navigate(CreateRoom,ScreenTransition.Fade)



部屋に参加するボタン

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


OnSelect

Navigate(JoinRoom,ScreenTransition.Fade)



2.CreateRoom

こんな画面になります。

image.png

各パラメータを操作する部分はこちらの記事を参考に作成してください。

PowerAppsでバトルゲームを作ろう!(その1:パラメータ設定画面)


画面遷移ボタン

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


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


参加する部屋の選択制御

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


Items

Filter('[dbo].[PowerApps_NetBattle_Test_tbl]',Turn="Pendding").room_id



画面遷移ボタン

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


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


ステータス表示部

HP部分はこんな風になっています。


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)を設定しています。

今回の処理は検証のために複雑なダメージ計算式などは除外、簡略化しています。


DisplayMode

If(

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


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"
}
)


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秒毎に繰り返す設定をしています。

タイマーがスタートしたときに以下の処理が実行されるように宣言します。


OnTimerStart

Refresh('[dbo].[PowerApps_NetBattle_Test_tbl]')



データソースについて

今回異なるテナントでも通信対戦ができるようにとデータソースとしてSQL Serverを使用しました。一番格安なBasicプランでも、サクサク動作するようです。

使用者が増えたりした場合は順次Standardなどに切り替えればよろしいかと思います。


まとめ

やってる内容については実はあまり大したことをしていません。

仕組みも理解できれば案外単純なのか!とおもったり・・・。

実際はトランザクション処理などいろいろ大変だとは思いますが。使い方が個人利用レベルであればこの程度でも十分でしょう。

これをもとにバーコードバトラーを改造しようかなと考えています。