@mpgd/cli 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/templates/phaser-game/README.md +33 -0
- package/templates/phaser-game/src/i18n/messages.ts +3 -0
- package/templates/phaser-game/src/main.ts +44 -0
- package/templates/phaser-game/src/runtime/gameContext.ts +6 -1
- package/templates/phaser-game/src/scenes/LobbyScene.ts +18 -2
- package/templates/phaser-game/src/styles.css +25 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -51,16 +51,16 @@
|
|
|
51
51
|
"typescript": "7.0.1-rc",
|
|
52
52
|
"@mpgd/adapter-ait": "0.3.3",
|
|
53
53
|
"@mpgd/adapter-browser": "0.3.2",
|
|
54
|
-
"@mpgd/adapter-devvit": "0.3.3",
|
|
55
|
-
"@mpgd/analytics": "0.3.2",
|
|
56
54
|
"@mpgd/adapter-capacitor": "0.3.3",
|
|
55
|
+
"@mpgd/analytics": "0.3.2",
|
|
56
|
+
"@mpgd/adapter-devvit": "0.3.3",
|
|
57
57
|
"@mpgd/bridge": "0.4.0",
|
|
58
|
-
"@mpgd/game-services": "0.3.3",
|
|
59
58
|
"@mpgd/catalog": "0.3.2",
|
|
60
|
-
"@mpgd/i18n": "0.3.2",
|
|
61
59
|
"@mpgd/phaser-assets": "0.4.0",
|
|
62
|
-
"@mpgd/
|
|
63
|
-
"@mpgd/
|
|
60
|
+
"@mpgd/platform": "0.3.2",
|
|
61
|
+
"@mpgd/target-config": "0.3.4",
|
|
62
|
+
"@mpgd/i18n": "0.3.3",
|
|
63
|
+
"@mpgd/game-services": "0.3.3"
|
|
64
64
|
},
|
|
65
65
|
"main": "./dist/index.js",
|
|
66
66
|
"types": "./dist/index.d.ts",
|
|
@@ -71,6 +71,39 @@ emitted URLs into the manifest with `?url` imports, or keep static
|
|
|
71
71
|
starter keeps Vite `base: './'` so generated asset URLs stay portable across web
|
|
72
72
|
preview, Apps in Toss, Devvit, Android, and iOS bundles.
|
|
73
73
|
|
|
74
|
+
## Viewport And Controls
|
|
75
|
+
|
|
76
|
+
The starter computes an initial viewport plan with `@mpgd/target-config`.
|
|
77
|
+
Measure the game container first, then fall back to `visualViewport` or
|
|
78
|
+
`window.innerWidth`:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const measured = measureGameViewport();
|
|
82
|
+
const viewport = resolveTargetViewportPlan({
|
|
83
|
+
width: measured.width,
|
|
84
|
+
height: measured.height,
|
|
85
|
+
source: measured.source,
|
|
86
|
+
runtime: runtime.config.runtime,
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use this as a starting point for game-specific layout. The returned
|
|
91
|
+
recommendation is not a hard target rule; override it when your playfield or
|
|
92
|
+
input model needs a different UI arrangement.
|
|
93
|
+
|
|
94
|
+
- `compact` is `<= 599px`: phone and narrow Devvit/card layouts. Prefer bottom
|
|
95
|
+
controls and drawers.
|
|
96
|
+
- `medium` is `600px` through `899px`: larger phones, small tablets, and
|
|
97
|
+
moderate embeds. Keep the play surface centered and only add side controls
|
|
98
|
+
when touch targets remain comfortable.
|
|
99
|
+
- `expanded` is `>= 900px`: desktop-like layouts. Side controls and panels are
|
|
100
|
+
acceptable.
|
|
101
|
+
|
|
102
|
+
Orientation is measured from the container: height greater than width is
|
|
103
|
+
`portrait`; width greater than or equal to height is `landscape`. Devvit should
|
|
104
|
+
be treated as an embedded webview, so do not assume desktop Reddit always means
|
|
105
|
+
an expanded game surface.
|
|
106
|
+
|
|
74
107
|
## Reddit Devvit
|
|
75
108
|
|
|
76
109
|
This starter owns its Devvit app root in `apps/target-devvit`. That directory
|
|
@@ -3,6 +3,7 @@ import type { Locale } from '@mpgd/i18n';
|
|
|
3
3
|
export type GameMessageKey =
|
|
4
4
|
| 'title'
|
|
5
5
|
| 'target'
|
|
6
|
+
| 'viewport'
|
|
6
7
|
| 'backend'
|
|
7
8
|
| 'features'
|
|
8
9
|
| 'featuresNone'
|
|
@@ -19,6 +20,7 @@ const messages = {
|
|
|
19
20
|
en: {
|
|
20
21
|
title: '__GAME_TITLE__',
|
|
21
22
|
target: 'Target: {target}',
|
|
23
|
+
viewport: 'Viewport: {sizeClass} {orientation} - controls {controls}',
|
|
22
24
|
backend: 'Game Services: {mode}',
|
|
23
25
|
features: 'Features: {features}',
|
|
24
26
|
featuresNone: 'none',
|
|
@@ -34,6 +36,7 @@ const messages = {
|
|
|
34
36
|
ko: {
|
|
35
37
|
title: '__GAME_TITLE__',
|
|
36
38
|
target: '타깃: {target}',
|
|
39
|
+
viewport: '뷰포트: {sizeClass} {orientation} - 컨트롤 {controls}',
|
|
37
40
|
backend: 'Game Services: {mode}',
|
|
38
41
|
features: '기능: {features}',
|
|
39
42
|
featuresNone: '없음',
|
|
@@ -2,6 +2,7 @@ import './styles.css';
|
|
|
2
2
|
|
|
3
3
|
import { createAnalyticsReporter, createBufferedAnalyticsSink } from '@mpgd/analytics';
|
|
4
4
|
import { resolveMpgdLocale, type Locale } from '@mpgd/i18n';
|
|
5
|
+
import { resolveTargetViewportPlan } from '@mpgd/target-config';
|
|
5
6
|
|
|
6
7
|
import { t } from './i18n/messages';
|
|
7
8
|
import { createClientId } from './runtime/id';
|
|
@@ -24,6 +25,10 @@ async function bootstrap(): Promise<void> {
|
|
|
24
25
|
const runtimeConfig = detectRuntime();
|
|
25
26
|
const platform = await installPlatform(runtimeConfig);
|
|
26
27
|
const runtime = await platform.getTargetRuntime();
|
|
28
|
+
const viewport = resolveTargetViewportPlan({
|
|
29
|
+
...measureGameViewport(),
|
|
30
|
+
runtime: runtime.config.runtime,
|
|
31
|
+
});
|
|
27
32
|
const player =
|
|
28
33
|
(await platform.identity.getPlayer()) ?? {
|
|
29
34
|
playerId: 'local-player',
|
|
@@ -54,6 +59,7 @@ async function bootstrap(): Promise<void> {
|
|
|
54
59
|
context: {
|
|
55
60
|
platform,
|
|
56
61
|
runtime,
|
|
62
|
+
viewport,
|
|
57
63
|
player,
|
|
58
64
|
locale,
|
|
59
65
|
gameServices,
|
|
@@ -81,3 +87,41 @@ function renderBootstrapError(error: unknown, locale: Locale): void {
|
|
|
81
87
|
panel.textContent = `${t(locale, 'bootError')}: ${message}`;
|
|
82
88
|
root.append(panel);
|
|
83
89
|
}
|
|
90
|
+
|
|
91
|
+
function measureGameViewport(): {
|
|
92
|
+
readonly width: number;
|
|
93
|
+
readonly height: number;
|
|
94
|
+
readonly source: 'container' | 'visual-viewport' | 'window';
|
|
95
|
+
} {
|
|
96
|
+
const container = document.querySelector<HTMLElement>('#game');
|
|
97
|
+
const rect = container?.getBoundingClientRect();
|
|
98
|
+
|
|
99
|
+
if (rect !== undefined && rect.width > 0 && rect.height > 0) {
|
|
100
|
+
return {
|
|
101
|
+
width: rect.width,
|
|
102
|
+
height: rect.height,
|
|
103
|
+
source: 'container',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const visualViewport = window.visualViewport;
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
visualViewport !== undefined &&
|
|
111
|
+
visualViewport !== null &&
|
|
112
|
+
visualViewport.width > 0 &&
|
|
113
|
+
visualViewport.height > 0
|
|
114
|
+
) {
|
|
115
|
+
return {
|
|
116
|
+
width: visualViewport.width,
|
|
117
|
+
height: visualViewport.height,
|
|
118
|
+
source: 'visual-viewport',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
width: window.innerWidth,
|
|
124
|
+
height: window.innerHeight,
|
|
125
|
+
source: 'window',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { AnalyticsReporter, BufferedAnalyticsSink } from '@mpgd/analytics';
|
|
2
2
|
import type { Locale } from '@mpgd/i18n';
|
|
3
3
|
import type { PlayerIdentity } from '@mpgd/platform';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
TargetConfiguredGateway,
|
|
6
|
+
TargetRuntimeSnapshot,
|
|
7
|
+
TargetViewportPlan,
|
|
8
|
+
} from '@mpgd/target-config';
|
|
5
9
|
|
|
6
10
|
import type { StarterGameServices } from '../platform/gameServices';
|
|
7
11
|
|
|
@@ -10,6 +14,7 @@ export const starterContextKey = 'starterContext';
|
|
|
10
14
|
export interface StarterContext {
|
|
11
15
|
readonly platform: TargetConfiguredGateway;
|
|
12
16
|
readonly runtime: TargetRuntimeSnapshot;
|
|
17
|
+
readonly viewport: TargetViewportPlan;
|
|
13
18
|
readonly player: PlayerIdentity;
|
|
14
19
|
readonly locale: Locale;
|
|
15
20
|
readonly gameServices: StarterGameServices;
|
|
@@ -45,14 +45,30 @@ export class LobbyScene extends Phaser.Scene {
|
|
|
45
45
|
})
|
|
46
46
|
.setOrigin(0.5);
|
|
47
47
|
this.add
|
|
48
|
-
.text(
|
|
48
|
+
.text(
|
|
49
|
+
480,
|
|
50
|
+
258,
|
|
51
|
+
t(locale, 'viewport', {
|
|
52
|
+
sizeClass: context.viewport.layout.sizeClass,
|
|
53
|
+
orientation: context.viewport.layout.orientation,
|
|
54
|
+
controls: context.viewport.recommendation.primaryControls,
|
|
55
|
+
}),
|
|
56
|
+
{
|
|
57
|
+
color: '#d6dee8',
|
|
58
|
+
fontFamily: 'Arial, sans-serif',
|
|
59
|
+
fontSize: '18px',
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
.setOrigin(0.5);
|
|
63
|
+
this.add
|
|
64
|
+
.text(480, 300, t(locale, 'backend', { mode: context.gameServices.mode }), {
|
|
49
65
|
color: context.gameServices.mode === 'disabled' ? '#f59e0b' : '#2dd4bf',
|
|
50
66
|
fontFamily: 'Arial, sans-serif',
|
|
51
67
|
fontSize: '18px',
|
|
52
68
|
})
|
|
53
69
|
.setOrigin(0.5);
|
|
54
70
|
this.add
|
|
55
|
-
.text(480,
|
|
71
|
+
.text(480, 342, t(locale, 'tapToStart'), {
|
|
56
72
|
color: '#ffffff',
|
|
57
73
|
fontFamily: 'Arial, sans-serif',
|
|
58
74
|
fontSize: '22px',
|
|
@@ -1,20 +1,43 @@
|
|
|
1
1
|
html,
|
|
2
|
-
body
|
|
3
|
-
#game {
|
|
2
|
+
body {
|
|
4
3
|
width: 100%;
|
|
5
4
|
height: 100%;
|
|
6
5
|
margin: 0;
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
body {
|
|
9
|
+
display: grid;
|
|
10
|
+
place-items: center;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
min-width: 0;
|
|
13
|
+
min-height: 100dvh;
|
|
10
14
|
overflow: hidden;
|
|
15
|
+
padding:
|
|
16
|
+
env(safe-area-inset-top)
|
|
17
|
+
env(safe-area-inset-right)
|
|
18
|
+
env(safe-area-inset-bottom)
|
|
19
|
+
env(safe-area-inset-left);
|
|
11
20
|
background: #07111f;
|
|
12
21
|
color: #ffffff;
|
|
13
22
|
font-family: Arial, sans-serif;
|
|
14
23
|
}
|
|
15
24
|
|
|
25
|
+
#game {
|
|
26
|
+
--mpgd-game-max-height: calc(
|
|
27
|
+
100dvh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
width: min(100%, 960px, calc(var(--mpgd-game-max-height) * 16 / 9));
|
|
32
|
+
aspect-ratio: 16 / 9;
|
|
33
|
+
min-width: 0;
|
|
34
|
+
min-height: 0;
|
|
35
|
+
touch-action: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
16
38
|
canvas {
|
|
17
39
|
display: block;
|
|
40
|
+
touch-action: none;
|
|
18
41
|
}
|
|
19
42
|
|
|
20
43
|
.boot-error {
|