---
title: 251018JPPGB#2
tags: 
author: [みのる](https://www.docswell.com/user/meccha__eeyan)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/DEYZ98LP7M.jpg?width=480
description: 下記イベント登壇資料です https://jppgb.connpass.com/event/363360/
published: October 18, 25
canonical: https://www.docswell.com/s/meccha__eeyan/5G2YW8-2025-10-18-223304
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/DEYZ98LP7M.jpg)

がんばれハム子 製作秘話
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 2

![Page Image](https://bcdn.docswell.com/page/VJN3LQ4MJ8.jpg)

自己紹介
プライベート（2児の父）
POWER CLUB 部長
みのる (@meccha__eeyan) / X
Qiita PowerAppsタグ 年間4位
資格取得
⚫ 基本情報技術者試験 (2025年1月)
⚫ MCP PL900,100,300,500,200,600
⚫ APPLIED SKILLS
会社（製造業の品質保証部）
Power Platformの社内コミュニティ(約160名)運営
⚫ Power Apps
⚫ Power AppsとDataverse
⚫ Power Automate
⚫ Copilot Studio
非IT部門で初の環境管理者権限
Dataverseアプリリリース
Power Platformで初の外部伴走支援導入
部内DX推進チーム創設
2025年10月18日 JPPGB
POWER CLUB - connpass
ゲーム作成コンテスト #2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/YE994DQWE3.jpg)

作品紹介
• ハム子はおなかを空かしてます。
• でもねこがハム子をねらっています・・・
• つかまらずにたくさんエサを集めてね！
(のこり時間にも気をつけよう）
ハム子の操作はデバイスの傾きを利用しているため、スマホや
タブレット等加速度センサ搭載デバイス限定アプリです。
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 4

![Page Image](https://bcdn.docswell.com/page/GE89Q3GR7D.jpg)

プレイの様子
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 5

![Page Image](https://bcdn.docswell.com/page/LELWXVG2JR.jpg)

ハム子紹介
種類： ジャンガリアンハムスター
色： ブルーサファイア
性別： 雌
誕生日： 2023年11月29日（良い肉の日）
豆腐チップスが
大好き
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 6

![Page Image](https://bcdn.docswell.com/page/4JM9LGQ9EW.jpg)

着想背景
前回（24年6月22日）作品
• 前回は風の影響を考慮した弾道計算
• そういえば加速度センサの値とれたよな・・・
• おまけ用とかでよくある
バランス迷路おもちゃ
苦労ポイント③ 弾道計算
放物運動（初速と角度から計算） - 高精度計算サイト (casio.jp)
りょ！
• 我が家のペットのハム子のえさ集めにしよ
砲弾の弾着シミュレーション - 防衛省・自衛隊
無理！
風の影響はvxだけにして、風との速
度差に比例すると仮定しよ。
• それだけだと面白くないから敵用意
：風の速度
：風の抵抗係数
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 7

![Page Image](https://bcdn.docswell.com/page/PJRGKZ89E9.jpg)

加速度（Acceleration）信号の検知
Power Apps での Acceleration、App、Compass、Connection、および
Location 信号 - Power Platform | Microsoft Learn
+
+
-
+
-
X
0
0
0
0
9.81
-9.81
Y
0
0
9.81
-9.81
0
0
z
9.81
-9.81
0
0
0
0
Acceleration.X とか書くだけで加速度数値化 ⇒ 姿勢検知
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 8

![Page Image](https://bcdn.docswell.com/page/PEXXL983EX.jpg)

正負がややこしい・・・
加速度の正負
+
+
-
+
Power Appsの
座標の正負
x
+
+
-
y
ハム子を左にうごかしたい ⇒ スマホの右側を持ち上げる ⇒ 加速度＋値 ⇒ X座標-方向
ハム子を下にうごかしたい ⇒ スマホの上側を持ち上げる ⇒ 加速度＋値 ⇒ Y座標+方向
加速度の正負と座標の正負の対応がXとYでことなるのでややこしい・・・
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 9

![Page Image](https://bcdn.docswell.com/page/3EKWLPKNED.jpg)

ゲーム実装解説
THIS IS Low-Code！ Do more with less！
メインスクリーン： コード355行（自力コーディングは20%くらい）
変数設定(ボタン)
演算処理(タイマー)
グラフィック
(ハム子/猫)
グラフィック
(フィールド/アイテム)
2025年10月18日 JPPGB
Screens:
ScreenMain:
Properties:
Fill: =App.Theme.Colors.Lighter80
OnVisible: |=Select(ButtonParamReset);
Select(ButtonItemSet);
Reset(AudioGameMusic);
Set(_PlayMusic, false)
Children:
- ContainerGameField:
Control: GroupContainer@1.3.0
Variant: ManualLayout
Properties:
BorderColor: =RGBA(255, 191, 0, 1)
BorderStyle: =BorderStyle.None
BorderThickness: =7
DropShadow: =DropShadow.None
Height: =1040
RadiusBottomLeft: =0
RadiusBottomRight: =0
RadiusTopLeft: =0
RadiusTopRight: =0
Width: =Parent.Width
Children:
- ImageField:
Control: Image@2.2.3
Properties:
Height: =Parent.Height
Image: =Field_pixel_art_finer
ImagePosition: =ImagePosition.Fill
Transparency: =0.3
Width: =Parent.Width
- GalleryItemSet:
Control: Gallery@2.15.0
Variant:
BrowseLayout_Vertical_OneTextOneImageVariant_ver5.0
Properties:
BorderColor: =App.Theme.Colors.Lighter10
BorderStyle: =BorderStyle.None
Height: =Parent.Height
Items: =Sequence(Parent.Height/_ObjSize)
TemplateSize: =_ObjSize
Width: =Parent.Width
Children:
- Gallery2:
Control: Gallery@2.15.0
Variant:
BrowseLayout_Horizontal_TwoTextOneImageVariant_ver5.0
Properties:
Height: =Parent.TemplateHeight
Items: =Sequence(Parent.Width/_ObjSize)
TemplateSize: =_ObjSize
Width: =Parent.Width
Children:
- Image4:
Control: Image@2.2.3
Properties:
Height: =Parent.Height
Image:
=Switch(LookUp(ColItemSet,XGrid=ThisItem.Value &amp;&amp;
YGrid=Value(TextYGrid.Text)).RandVal,1,item01,2,item02,3,item03)
OnSelect: =Select(Parent)
PaddingBottom: =Self.PaddingTop
PaddingLeft: =Self.PaddingTop
PaddingRight: =Self.PaddingTop
PaddingTop: =_ObjSize*0.3
Visible:
=!IsBlank(LookUp(ColItemSet,XGrid=ThisItem.Value &amp;&amp;
YGrid=Value(TextYGrid.Text)))
Width: =Parent.TemplateWidth
- TextYGrid:
Control: Text@0.0.51
Properties:
Text: =ThisItem.Value
Visible: =false
- HtmlHam:
Control: HtmlViewer@2.1.0
Properties:
Font: =Font.&#039;Open Sans&#039;
- ButtonBack:
_y:_y+_vy*_dt,
_duration:60000
Height: =_ObjSize*Sqrt(2)
Control: Button@0.0.45
//座標&amp;向き計算(猫）
});
HtmlText: |Properties:
_catX:_catX+_catV*Cos(Radians(_catDirect))*_dt,
Set(_IteNum,20),
=$&quot;&lt;img
Appearance: =&#039;ButtonCanvas.Appearance&#039;.Primary
_catY:_catY-_catV*Sin(Radians(_catDirect))*_dt,
&quot;easy&quot;,
src={Switch(_HamMode,0,_ImgHamStop,1,_ImgHamWalk1,
FontSize: =30
_catDirect:Mod(Degrees(Atan2((_x-_catX)+1e-7,-(_yUpdateContext({
2,_ImgHamWalk2)}
Height: =70
_catY))), 360)
_catV:20,
style=&quot;&quot;
Icon: =&quot;ArrowExit&quot;
});
_duration:180000
width:{_ObjSize}px;
Layout: =&#039;ButtonCanvas.Layout&#039;.TextOnly
//向き計算（ハム子）
});
text-align:center;
OnSelect: |Set(_Rotate,Mod(Degrees(Atan2(_vx+1e-7,_vy))+90,360));
Set(_IteNum,10),
transform:rotate({_Rotate}deg);
=Navigate(ScreenStart,ScreenTransition.CoverRight);
//えさ捕食
&quot;oni&quot;,
margin-top:{(Self.Width-_ObjSize)/2}px;
Set(_HamMode,1);Select(ButtonStartReset)
UpdateContext({_hamX:_x+_ObjSize/2,_hamY:_y+_ObjSize/2})
UpdateContext({
margin-left:{(Self.Width-_ObjSize)/2}px;
Text: =&quot;もどる&quot;
;
_catV:200,
&quot;&quot;
Width: =137
RemoveIf(ColItemSet,XGrid*_ObjSize&gt;_hamX &amp;&amp; (XGrid_duration:60000
&gt;&quot;
X: =502
1)*_ObjSize&lt;_hamX &amp;&amp; YGrid*_ObjSize&gt;_hamY &amp;&amp; (YGrid});
PaddingBottom: =0
Y: =1053
1)*_ObjSize&lt;_hamY);
Set(_IteNum,40)
PaddingLeft: =0
- LabelTimer:
With({_num:CountRows(ColItemSet)},If(_num&lt;_remainItems,
);
PaddingRight: =0
Control: Label@2.5.1
UpdateContext({_remainItems:_num})));
Text: =&quot;ParamReset&quot;
PaddingTop: =0
Properties:
//猫接触
Y: =140
Width: =_ObjSize*Sqrt(2)
Color: =RGBA(224, 86, 6, 1)
If(
- ButtonItemSet:
X: =_x-(Self.Width-_ObjSize)/2
Font: =&quot;HGS創英角
体&quot;
Or(
Control: Button@0.0.45
Y: =_y-(Self.Height-_ObjSize)/2
Height: =96
_catX+_ObjSize&gt;_hamX &amp;&amp; _catX&lt;_hamX &amp;&amp;
Properties:
- HtmlCat:
Size: =35
_catY+_ObjSize&gt;_hamY &amp;&amp; _catY&lt;_hamY,
OnSelect: |
Control: HtmlViewer@2.1.0
Text: =$&quot;のこり{Text(_duration/1000-TimerMain.Value/1000,&quot;0&quot;)}
TimerMain.Value=TimerMain.Duration
=With(
Properties:
秒&quot;
),
{
Font: =Font.&#039;Open Sans&#039;
Width: =316
Navigate(ScreenGameOver);Select(ButtonStartReset),
Nx: Max(1, Int(ContainerGameField.Width / _ObjSize)),
Height: =_ObjSize*Sqrt(2)
Y: =1040
CountRows(ColItemSet)=0,
Ny: Max(1, Int(ContainerGameField.Height / _ObjSize)),
HtmlText: |- ContainerGameEngine:
Navigate(ScreenGameClear,ScreenTransition.Fade,{_mode:
Ntake: Min(_IteNum, Max(1,
=$&quot;&lt;img
Control: GroupContainer@1.3.0
_mode});Select(ButtonStartReset)
(Int(ContainerGameField.Width / _ObjSize) - 1) *
src={_ImageCatHand}
Variant: ManualLayout
)
(Int(ContainerGameField.Height / _ObjSize) - 1)))
style=&quot;&quot;
Properties:
Repeat: =true
},
Height:{_ObjSize}px;
Fill: =RGBA(255, 255, 255, 0.3)
Reset: =!_Start
UpdateContext({_remainItems:_IteNum});
text-align:center;
Height: =247
Start: =_Start
ClearCollect(
transform:rotate({-_catDirect+90}deg);
Visible: =CheckboxSetting.Value
Text: =&quot;TimerCalc&quot;
ColItemSet,
margin-top:{(Self.Width-_ObjSize)/2}px;
Width: =385
Width: =147
// 全セルを列挙 → シャッフル → 先頭Ntake件だけ
margin-left:{(Self.Width-_ObjSize)/2}px;
Y: =780
X: =231
AddColumns(
&quot;&quot;
Children:
Y: =81
FirstN(
&gt;&quot;
- TimerHam:
- TimerMain:
Shuffle(
PaddingBottom: =0
Control: Timer@2.1.0
Control: Timer@2.1.0
RemoveIf(
PaddingLeft: =0
Properties:
Properties:
AddColumns(
PaddingRight: =0
AutoPause: =false
AutoPause: =false
Sequence(Nx * Ny) As rec,
PaddingTop: =0
Duration: =3000/(Sqrt(_vx*_vx+_vy*_vy)/10+1)
Duration: =_duration
XGrid, 1 + RoundDown((rec.Value - 1) / Ny,0),
Width: =_ObjSize*Sqrt(2)
Height: =38
Height: =38
YGrid, 1 + Mod(rec.Value - 1, Ny)
X: =_catX-(Self.Width-_ObjSize)/2
OnTimerEnd:
OnTimerStart: =
),XGrid=1&amp;&amp;YGrid=1)
Y: =_catY-(Self.Height-_ObjSize)/2
=Switch(_HamMode,0,Set(_HamMode,1),1,Set(_HamMode,2),2,Set(_Ha
Reset: =!_Start
),
- CheckboxSetting:
mMode,1))
Start: =_Start
Ntake
Control: Classic/CheckBox@2.1.0
Repeat: =true
Text: =&quot;TimerMain&quot;
),
Properties:
Reset: =!_Start
Width: =146
// 画像選択用ランダム属性（1～3）
BorderStyle: =BorderStyle.None
Start: =_Start
X: =231
RandVal, 1 + Mod( RoundDown(Rand() * 1000000,0), 3 )
CheckboxSize: =35
Text: =&quot;TimerHam&quot;
Y: =119
)
Height: =45
Width: =146
- ButtonParamReset:
)
Size: =13
X: =231
Control: Button@0.0.45
)
Text: =&quot;設定&quot;
Y: =43
Properties:
Text: =&quot;えさ&quot;
Width: =124
- TimerCalc:
Height: =31
Y: =191
X: =-Self.Width
Control: Timer@2.1.0
OnSelect: |
- AudioGameMusic:
Y: =65
Properties:
=Set(_AccMul,50);
Control: Audio@2.3.1
- ButtonStartReset:
AutoPause: =false
Set(_Rotate,0);
Properties:
Control: Button@0.0.45
Duration: =10
Set(_HamMode,0);
Loop: =true
Properties:
Height: =38
UpdateContext({
Media: =&#039;がんばれハム子 プレイ中&#039;
FontSize: =30
OnTimerEnd: |_x:_InitX,
Reset: =!_PlayMusic
Height: =70
=With(
_y:_InitY,
Start: =_PlayMusic
OnSelect: |{_fieldW:ContainerGameField.Width,_fieldH:ContainerGam
_vx:0,
Width: =385
=If(
eField.Height},
_vy:0,
X: =-Self.Width
_HamMode=0,
//壁面接触時の処理（ハム子）
_dt:0,
//_HamModeが0の時⇒スタート処理
If(_x+_ObjSize&gt;_fieldW,UpdateContext({_vx:_t:0,
Set(_HamMode,1);
_vx*0.5,_x:_fieldW-_ObjSize-1}));
_catX:ContainerGameField.Width-_ObjSize-1,
Set(_Start,true);
If(_x&lt;0,UpdateContext({_vx:-_vx*0.5,_x:1}));
_catY:ContainerGameField.Height-_ObjSize-1,
Set(_PlayMusic, false);
If(_y+_ObjSize&gt;_fieldH,UpdateContext({_vy:_catDirect:135
Set(_PlayMusic, true),
_vy*0.5,_y:_fieldH-_ObjSize-1}));
});
//_HamModeが0以外の時⇒リセット処理
If(_y&lt;0,UpdateContext({_vy:-_vy*0.5,_y:1}));
Switch(
Select(ButtonParamReset);
);
_mode,
Set(_Start,false);
UpdateContext({_dt:(TimerMain.Value-_t)/1000});
&quot;normal&quot;,
Set(_HamMode,0);
If(Mod(TimerMain.Value,1000)&lt;20,UpdateContext({_debug:_
UpdateContext({
Select(ButtonItemSet);
dt}));
_catV:50,
Set(_PlayMusic, false);
UpdateContext({
_duration:120000
)
_t:TimerMain.Value,
});
Text: =If(_HamMode=0,&quot;スタート&quot;,&quot;リセット&quot;)
//速度、座標計算(ハム子）
Set(_IteNum,20),
Width: =171
_vx:_vx-Acceleration.X*_dt*_AccMul,
&quot;hard&quot;,
X: =316
_vy:_vy+Acceleration.Y*_dt*_AccMul,
UpdateContext({
Y: =1053
_x:_x+_vx*_dt,
_catV:80,
ゲーム作成コンテスト #2


# Page. 10

![Page Image](https://bcdn.docswell.com/page/L7313XZZJ5.jpg)

変数設定(ボタン) アイテム配置
ハム子が回収するエサをランダムに配置（ハム子初期位置左上を除く）
With(
{
Nx： 横方向の分割数
Nx: Max(1, Int(ContainerGameField.Width / _ObjSize)),
Ny： 縦方向の分割数
Ny: Max(1, Int(ContainerGameField.Height / _ObjSize)),
Ntake: Min(_IteNum, Max(1, (Int(ContainerGameField.Width / _ObjSize) - 1)
* (Int(ContainerGameField.Height / _ObjSize) - 1)))
Ntake： アイテム数
},
UpdateContext({_remainItems:_IteNum});
ClearCollect(
ColItemSet,
// 全セルを列挙 → シャッフル → 先頭Ntake件だけ
AddColumns(
FirstN(
Shuffle(
RemoveIf(
① Nx × Ny 行のテーブル用意
AddColumns(
Sequence(Nx * Ny) As rec,
XGrid, 1 + RoundDown((rec.Value - 1) / Ny,0),
YGrid, 1 + Mod(rec.Value - 1, Ny)
),XGrid=1&amp;&amp;YGrid=1)
② XGrid列に1～Nx、YGrid列に1～Nyの採番
),
③ ハム子初期位置（１，１）を除外
Ntake
④ シャッフルしたものから先頭Ntake件のみ抽出
),
// 画像選択用ランダム属性（1～3）
RandVal, 1 + Mod( RoundDown(Rand() * 1000000,0), 3 )
)
⑤ エサ画像バリエーション用の1～3ランダム数
)
)
ちょっと無駄あり・・・
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 11

![Page Image](https://bcdn.docswell.com/page/87DX42R4EG.jpg)

というわけでリファクタリング
ポイント
• RemoveIfの無駄削除
• Grid列採番の無駄削除
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 12

![Page Image](https://bcdn.docswell.com/page/VJPPM1WVJ8.jpg)

演算処理(タイマー)① ハム子のアニメ
HTMLテキストコントロールを使用
速度に応じて切り替え速度を変える
- TimerHam:
Duration: =3000/(Sqrt(_vx*_vx+_vy*_vy)/10+1)
OnTimerEnd: =Switch(_HamMode,
0,Set(_HamMode,1),
1,Set(_HamMode,2),
2,Set(_HamMode,1)
)
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 13

![Page Image](https://bcdn.docswell.com/page/2EV29Y8REQ.jpg)

演算処理(タイマー)② ハム子＆猫の移動計算
速度一定で方向決定 ⇒ 単調すぎてゲーム性
坂道を転がるボールのように、角度や時間に応じて速度が変わるように
リアルタイム計測用のメインタイマー（プレイ制限時間）
短時間ループでゲーム演算処理
10msec設定⇒理論上100fpsだが実測12fps程度
_t：前回ループ時のメインタイマー値
_AccMul：加速補正定数
ハム子はユーザー操作で移動、猫は演算ループごとにハム子へ軌道修正
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 14

![Page Image](https://bcdn.docswell.com/page/57GRZ956EL.jpg)

演算処理(タイマー)③ 接触判定（えさ捕食）
XGrid=1
XGrid=2
_ObjSize
• ハム子中心がColItemSetのXGrid,YGrid侵入 ⇒ 該当レコード消去
YGrid=1
(_hamX, _hamY)
※ すなわち捕食
• その後ColItemSetのレコード数を確認し_remainItemsに格納
YGrid=2
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 15

![Page Image](https://bcdn.docswell.com/page/4EQVLWZ2JP.jpg)

演算処理(タイマー)④ 接触判定（猫接触）
_ObjSize
• ハム子中心が猫のエリアに侵入 ⇒ ゲームオーバー
(_hamX, _hamY)
(_catX, _catY)
2025年10月18日 JPPGB
• またはメインタイマーが時間切れでもゲームオーバー
• ColItemSetのレコード数がゼロになったらゲームクリア
ゲーム作成コンテスト #2


# Page. 16

![Page Image](https://bcdn.docswell.com/page/KJ4MDK3PE1.jpg)

難易度調整
3要素で難易度調整
_catV： 猫の速度
_duration： 制限時間[msec]
＿IteNum： エサの個数
鬼Mode ⇒ 私と長女(小3)クリア済み
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 17

![Page Image](https://bcdn.docswell.com/page/LE18ZX1X7G.jpg)

あとがき
参加を決めてから約１週間、夜な夜な寝不足になりながら没頭しました
遊んでくれる人をどう楽しませるか？ ⇒ 業務アプリ製作でも大事！
こんなに少ないコード＆短期間で実装できるPower Apps最高！
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 18

![Page Image](https://bcdn.docswell.com/page/GEWZ9N8KE2.jpg)

これをCode Appsで
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 19

![Page Image](https://bcdn.docswell.com/page/47Z1938NJ3.jpg)

Code Apps
• 仕様書作って爆速でアプリ完成
• 仕様書そのものもCopilotと一緒に
• Androidデバイスでは加速度が取得できなかった
• 爆速だがやはり細かい指示しきれないので想定外の動作
• 表示や操作感のレビューと修正はまだ人間必須
• 制限はあるがキャンバスアプリの安心感◎
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


# Page. 20

![Page Image](https://bcdn.docswell.com/page/YJ6LKXP9EV.jpg)

現場からは以上です。
ご安全に。
2025年10月18日 JPPGB
ゲーム作成コンテスト #2


