lobster-farmer-cli 0.1.0
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/LICENSE +21 -0
- package/README.md +64 -0
- package/README.zh-CN.md +54 -0
- package/bin/lobster-farmer.cjs +400 -0
- package/dist/game.js +272 -0
- package/dist/index.js +55 -0
- package/package.json +48 -0
- package/public/game-assets/background/underwater-tileable.png +0 -0
- package/public/game-assets/lobster/spr_lobster_searching_bubble_strip10.png +0 -0
- package/public/game-assets/lobster/spr_lobster_walk_strip6.png +0 -0
- package/public/game.js +440 -0
- package/public/index.html +14 -0
- package/public/styles.css +32 -0
package/public/game.js
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
const { AnimatedSprite, Application, Assets, Container, Graphics, Rectangle, Texture, TilingSprite } = PIXI;
|
|
2
|
+
|
|
3
|
+
const BG_IMAGE = "/game-assets/background/underwater-tileable.png";
|
|
4
|
+
const WALK_STRIP = "/game-assets/lobster/spr_lobster_walk_strip6.png";
|
|
5
|
+
const BUBBLE_STRIP = "/game-assets/lobster/spr_lobster_searching_bubble_strip10.png";
|
|
6
|
+
|
|
7
|
+
const BASE_RED = 0xff3b30;
|
|
8
|
+
|
|
9
|
+
function clamp(value, min, max) {
|
|
10
|
+
return Math.max(min, Math.min(max, value));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function snap(value, grid = 2) {
|
|
14
|
+
return Math.round(value / grid) * grid;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function splitStrip(texture, frames, frameWidth, frameHeight) {
|
|
18
|
+
const result = [];
|
|
19
|
+
for (let index = 0; index < frames; index += 1) {
|
|
20
|
+
result.push(new Texture(texture.baseTexture, new Rectangle(index * frameWidth, 0, frameWidth, frameHeight)));
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function loadAssets() {
|
|
26
|
+
const [bgTexture, walkTexture, bubbleTexture] = await Promise.all([
|
|
27
|
+
Assets.load(BG_IMAGE),
|
|
28
|
+
Assets.load(WALK_STRIP),
|
|
29
|
+
Assets.load(BUBBLE_STRIP)
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
bgTexture,
|
|
34
|
+
walkFrames: splitStrip(walkTexture, 6, 60, 29),
|
|
35
|
+
bubbleFrames: splitStrip(bubbleTexture, 10, 16, 16)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createBubble(width, height) {
|
|
40
|
+
const size = 2 + Math.floor(Math.random() * 3) * 2;
|
|
41
|
+
const node = new Graphics();
|
|
42
|
+
node.beginFill(0x9ad9ff, 0.72);
|
|
43
|
+
node.drawRect(-size / 2, -size / 2, size, size);
|
|
44
|
+
node.endFill();
|
|
45
|
+
node.x = snap(Math.random() * width);
|
|
46
|
+
node.y = snap(Math.random() * height);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
node,
|
|
50
|
+
speed: 0.7 + Math.random() * 1.8,
|
|
51
|
+
phase: Math.random() * Math.PI * 2,
|
|
52
|
+
drift: 0.14 + Math.random() * 0.34
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resetBubble(bubble, width, height) {
|
|
57
|
+
bubble.node.x = snap(Math.random() * width);
|
|
58
|
+
bubble.node.y = snap(height + 10 + Math.random() * 60);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function drawBackground(scene) {
|
|
62
|
+
const texWidth = scene.assets.bgTexture.width;
|
|
63
|
+
const texHeight = scene.assets.bgTexture.height;
|
|
64
|
+
const scale = scene.height / texHeight;
|
|
65
|
+
const tileWidth = texWidth * scale;
|
|
66
|
+
const offsetX = snap((scene.width - tileWidth) * 0.5);
|
|
67
|
+
|
|
68
|
+
scene.bgSprite.width = scene.width;
|
|
69
|
+
scene.bgSprite.height = scene.height;
|
|
70
|
+
scene.bgSprite.tileScale.set(scale, scale);
|
|
71
|
+
scene.bgSprite.tilePosition.x = offsetX;
|
|
72
|
+
scene.bgSprite.tilePosition.y = 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function createModelLobster(scene, model) {
|
|
76
|
+
const container = new Container();
|
|
77
|
+
|
|
78
|
+
const sprite = new AnimatedSprite(scene.assets.walkFrames);
|
|
79
|
+
sprite.anchor.set(0.5, 0.5);
|
|
80
|
+
sprite.animationSpeed = 0.11 + Math.random() * 0.05;
|
|
81
|
+
sprite.roundPixels = true;
|
|
82
|
+
sprite.tint = BASE_RED;
|
|
83
|
+
sprite.play();
|
|
84
|
+
|
|
85
|
+
const bubble = new AnimatedSprite(scene.assets.bubbleFrames);
|
|
86
|
+
bubble.anchor.set(0.5, 0.5);
|
|
87
|
+
bubble.animationSpeed = 0.18;
|
|
88
|
+
bubble.scale.set(1.6, 1.6);
|
|
89
|
+
bubble.position.set(0, -24);
|
|
90
|
+
bubble.alpha = 0.5;
|
|
91
|
+
bubble.roundPixels = true;
|
|
92
|
+
bubble.play();
|
|
93
|
+
|
|
94
|
+
container.addChild(sprite);
|
|
95
|
+
container.addChild(bubble);
|
|
96
|
+
|
|
97
|
+
scene.modelLayer.addChild(container);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
model,
|
|
101
|
+
container,
|
|
102
|
+
sprite,
|
|
103
|
+
bubble,
|
|
104
|
+
baseX: scene.width * 0.5,
|
|
105
|
+
baseY: scene.height * 0.5,
|
|
106
|
+
driftX: 18 + Math.random() * 22,
|
|
107
|
+
driftY: 8 + Math.random() * 16,
|
|
108
|
+
phase: Math.random() * Math.PI * 2,
|
|
109
|
+
scale: 1.8,
|
|
110
|
+
direction: Math.random() > 0.5 ? 1 : -1
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function destroyModelLobster(entity) {
|
|
115
|
+
entity.container.destroy({ children: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function syncModelLobsters(scene, lobsterState) {
|
|
119
|
+
const nextMap = lobsterState?.lobsters ?? {};
|
|
120
|
+
const nextModels = new Set(Object.keys(nextMap));
|
|
121
|
+
|
|
122
|
+
for (const [model, entity] of Object.entries(scene.modelLobsters)) {
|
|
123
|
+
if (!nextModels.has(model)) {
|
|
124
|
+
destroyModelLobster(entity);
|
|
125
|
+
delete scene.modelLobsters[model];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const model of nextModels) {
|
|
130
|
+
if (!scene.modelLobsters[model]) {
|
|
131
|
+
scene.modelLobsters[model] = createModelLobster(scene, model);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const entries = Object.entries(nextMap).sort((a, b) => b[1].tokens - a[1].tokens);
|
|
136
|
+
const count = entries.length;
|
|
137
|
+
const cols = Math.max(1, Math.ceil(Math.sqrt(count)));
|
|
138
|
+
const rows = Math.max(1, Math.ceil(count / cols));
|
|
139
|
+
|
|
140
|
+
const swimWidth = Math.max(220, scene.swimBounds.right - scene.swimBounds.left);
|
|
141
|
+
const swimHeight = Math.max(180, scene.swimBounds.bottom - scene.swimBounds.top);
|
|
142
|
+
const cellW = swimWidth / cols;
|
|
143
|
+
const cellH = swimHeight / rows;
|
|
144
|
+
|
|
145
|
+
entries.forEach(([model, lobster], index) => {
|
|
146
|
+
const entity = scene.modelLobsters[model];
|
|
147
|
+
const col = index % cols;
|
|
148
|
+
const row = Math.floor(index / cols);
|
|
149
|
+
|
|
150
|
+
entity.baseX = snap(scene.swimBounds.left + col * cellW + cellW * 0.5 + (col % 2 === 0 ? -10 : 10));
|
|
151
|
+
entity.baseY = snap(scene.swimBounds.top + row * cellH + cellH * 0.5 + (row % 2 === 0 ? -6 : 8));
|
|
152
|
+
|
|
153
|
+
const targetScale = clamp(typeof lobster.size === "number" ? lobster.size : 1.7, 1.7, 4.8);
|
|
154
|
+
entity.scale = targetScale;
|
|
155
|
+
|
|
156
|
+
entity.sprite.tint = BASE_RED;
|
|
157
|
+
entity.sprite.scale.set(entity.direction > 0 ? targetScale : -targetScale, targetScale);
|
|
158
|
+
|
|
159
|
+
entity.container.zIndex = lobster.tokens;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
scene.modelLayer.sortableChildren = true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function spawnFood(scene, tokens, model) {
|
|
166
|
+
const amount = clamp(Math.round(Math.log2(tokens + 2) * 4), 4, 24);
|
|
167
|
+
const target = scene.modelLobsters[model];
|
|
168
|
+
const targetX = target ? target.container.x : scene.width * 0.5;
|
|
169
|
+
const targetY = target ? target.container.y : scene.height * 0.5;
|
|
170
|
+
|
|
171
|
+
for (let index = 0; index < amount; index += 1) {
|
|
172
|
+
const size = 4 + Math.floor(Math.random() * 3) * 2;
|
|
173
|
+
const node = new Graphics();
|
|
174
|
+
const color = index % 2 === 0 ? 0x77d5ff : 0x53f2cf;
|
|
175
|
+
|
|
176
|
+
node.beginFill(color, 0.96);
|
|
177
|
+
node.drawRect(-size / 2, -size / 2, size, size);
|
|
178
|
+
node.endFill();
|
|
179
|
+
node.x = snap(40 + Math.random() * (scene.width - 80));
|
|
180
|
+
node.y = snap(-30 - Math.random() * 120);
|
|
181
|
+
|
|
182
|
+
scene.foodLayer.addChild(node);
|
|
183
|
+
|
|
184
|
+
scene.foods.push({
|
|
185
|
+
node,
|
|
186
|
+
vx: (Math.random() - 0.5) * 2,
|
|
187
|
+
vy: 1.2 + Math.random() * 1.8,
|
|
188
|
+
life: 0,
|
|
189
|
+
targetX: targetX + (Math.random() - 0.5) * 24,
|
|
190
|
+
targetY: targetY + (Math.random() - 0.5) * 16
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function layout(scene, width, height) {
|
|
196
|
+
scene.width = width;
|
|
197
|
+
scene.height = height;
|
|
198
|
+
drawBackground(scene);
|
|
199
|
+
|
|
200
|
+
scene.swimBounds.left = 26;
|
|
201
|
+
scene.swimBounds.right = width - 26;
|
|
202
|
+
scene.swimBounds.top = 34;
|
|
203
|
+
scene.swimBounds.bottom = height - 120;
|
|
204
|
+
|
|
205
|
+
syncModelLobsters(scene, scene.stateRef());
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function updateScene(scene, deltaTime) {
|
|
209
|
+
const delta = deltaTime;
|
|
210
|
+
scene.elapsed += delta;
|
|
211
|
+
scene.bgSprite.tilePosition.x -= 0.12 * delta;
|
|
212
|
+
|
|
213
|
+
for (const entity of Object.values(scene.modelLobsters)) {
|
|
214
|
+
entity.container.x = snap(entity.baseX + Math.sin(scene.elapsed * 0.035 + entity.phase) * entity.driftX);
|
|
215
|
+
entity.container.y = snap(entity.baseY + Math.sin(scene.elapsed * 0.05 + entity.phase * 1.3) * entity.driftY);
|
|
216
|
+
entity.bubble.alpha = 0.42 + Math.sin(scene.elapsed * 0.07 + entity.phase) * 0.2;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const bubble of scene.bubbles) {
|
|
220
|
+
bubble.node.y -= bubble.speed * delta;
|
|
221
|
+
bubble.node.x += Math.sin(scene.elapsed * bubble.drift + bubble.phase) * 0.45 * delta;
|
|
222
|
+
|
|
223
|
+
if (bubble.node.y < -18) {
|
|
224
|
+
resetBubble(bubble, scene.width, scene.height);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (let index = scene.foods.length - 1; index >= 0; index -= 1) {
|
|
229
|
+
const orb = scene.foods[index];
|
|
230
|
+
orb.life += delta;
|
|
231
|
+
|
|
232
|
+
const dx = orb.targetX - orb.node.x;
|
|
233
|
+
const dy = orb.targetY - orb.node.y;
|
|
234
|
+
|
|
235
|
+
orb.vx += dx * 0.0032 * delta;
|
|
236
|
+
orb.vy += dy * 0.0032 * delta;
|
|
237
|
+
orb.vx *= 0.965;
|
|
238
|
+
orb.vy *= 0.965;
|
|
239
|
+
|
|
240
|
+
orb.node.x = snap(orb.node.x + orb.vx * delta * 2.2);
|
|
241
|
+
orb.node.y = snap(orb.node.y + orb.vy * delta * 2.2);
|
|
242
|
+
orb.node.alpha = clamp(1 - orb.life / 100, 0.2, 1);
|
|
243
|
+
|
|
244
|
+
if ((dx * dx + dy * dy < 180 || orb.life > 110) && orb.node.parent) {
|
|
245
|
+
orb.node.parent.removeChild(orb.node);
|
|
246
|
+
orb.node.destroy();
|
|
247
|
+
scene.foods.splice(index, 1);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function apiRequest(path, options) {
|
|
253
|
+
const response = await fetch(path, {
|
|
254
|
+
headers: {
|
|
255
|
+
"Content-Type": "application/json"
|
|
256
|
+
},
|
|
257
|
+
...options
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
const payload = await response.json().catch(() => ({}));
|
|
262
|
+
throw new Error(payload.error ?? `请求失败: ${response.status}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return response.json();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const root = document.getElementById("game");
|
|
269
|
+
if (!root) {
|
|
270
|
+
throw new Error("缺少 #game 容器");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const host = document.createElement("div");
|
|
274
|
+
host.className = "game-canvas-host";
|
|
275
|
+
root.appendChild(host);
|
|
276
|
+
|
|
277
|
+
const gameState = {
|
|
278
|
+
lobster: null,
|
|
279
|
+
lastTokensByModel: {}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
let sceneRef = null;
|
|
283
|
+
let resizeObserverRef = null;
|
|
284
|
+
let pollTimer = null;
|
|
285
|
+
|
|
286
|
+
function syncView() {
|
|
287
|
+
if (!sceneRef) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
syncModelLobsters(sceneRef, gameState.lobster);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function applyServerState(nextState) {
|
|
294
|
+
const previousTokens = gameState.lastTokensByModel;
|
|
295
|
+
const nextTokens = {};
|
|
296
|
+
|
|
297
|
+
if (sceneRef) {
|
|
298
|
+
for (const [model, lobster] of Object.entries(nextState.lobsters ?? {})) {
|
|
299
|
+
const prev = previousTokens[model] ?? 0;
|
|
300
|
+
if (lobster.tokens > prev) {
|
|
301
|
+
spawnFood(sceneRef, lobster.tokens - prev, model);
|
|
302
|
+
}
|
|
303
|
+
nextTokens[model] = lobster.tokens;
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
for (const [model, lobster] of Object.entries(nextState.lobsters ?? {})) {
|
|
307
|
+
nextTokens[model] = lobster.tokens;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
gameState.lobster = nextState;
|
|
312
|
+
gameState.lastTokensByModel = nextTokens;
|
|
313
|
+
syncView();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function pullState() {
|
|
317
|
+
try {
|
|
318
|
+
const payload = await apiRequest("/api/state");
|
|
319
|
+
applyServerState(payload.state);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error("同步状态失败", error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function cleanupScene() {
|
|
326
|
+
if (pollTimer) {
|
|
327
|
+
clearInterval(pollTimer);
|
|
328
|
+
pollTimer = null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (resizeObserverRef) {
|
|
332
|
+
resizeObserverRef.disconnect();
|
|
333
|
+
resizeObserverRef = null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!sceneRef) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
sceneRef.foods.forEach((orb) => {
|
|
341
|
+
orb.node.destroy();
|
|
342
|
+
});
|
|
343
|
+
sceneRef.bubbles.forEach((bubble) => {
|
|
344
|
+
bubble.node.destroy();
|
|
345
|
+
});
|
|
346
|
+
Object.values(sceneRef.modelLobsters).forEach((entity) => {
|
|
347
|
+
destroyModelLobster(entity);
|
|
348
|
+
});
|
|
349
|
+
sceneRef.app.destroy(true, { children: true, texture: true, baseTexture: true });
|
|
350
|
+
sceneRef = null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function initGame() {
|
|
354
|
+
const assets = await loadAssets();
|
|
355
|
+
|
|
356
|
+
const app = new Application({
|
|
357
|
+
antialias: false,
|
|
358
|
+
autoDensity: true,
|
|
359
|
+
resizeTo: host,
|
|
360
|
+
backgroundAlpha: 0
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const rootLayer = new Container();
|
|
364
|
+
const bgLayer = new Container();
|
|
365
|
+
const bgSprite = new TilingSprite(assets.bgTexture, host.clientWidth, host.clientHeight);
|
|
366
|
+
bgLayer.addChild(bgSprite);
|
|
367
|
+
|
|
368
|
+
const bubbleLayer = new Container();
|
|
369
|
+
const modelLayer = new Container();
|
|
370
|
+
const foodLayer = new Container();
|
|
371
|
+
|
|
372
|
+
rootLayer.addChild(bgLayer);
|
|
373
|
+
rootLayer.addChild(bubbleLayer);
|
|
374
|
+
rootLayer.addChild(modelLayer);
|
|
375
|
+
rootLayer.addChild(foodLayer);
|
|
376
|
+
|
|
377
|
+
app.stage.addChild(rootLayer);
|
|
378
|
+
host.appendChild(app.view);
|
|
379
|
+
|
|
380
|
+
const scene = {
|
|
381
|
+
app,
|
|
382
|
+
assets,
|
|
383
|
+
bgSprite,
|
|
384
|
+
bubbleLayer,
|
|
385
|
+
modelLayer,
|
|
386
|
+
foodLayer,
|
|
387
|
+
bubbles: [],
|
|
388
|
+
foods: [],
|
|
389
|
+
modelLobsters: {},
|
|
390
|
+
elapsed: 0,
|
|
391
|
+
width: host.clientWidth,
|
|
392
|
+
height: host.clientHeight,
|
|
393
|
+
swimBounds: {
|
|
394
|
+
left: 26,
|
|
395
|
+
right: host.clientWidth - 26,
|
|
396
|
+
top: 34,
|
|
397
|
+
bottom: host.clientHeight - 120
|
|
398
|
+
},
|
|
399
|
+
stateRef: () => gameState.lobster
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
for (let index = 0; index < 46; index += 1) {
|
|
403
|
+
const bubble = createBubble(scene.width, scene.height);
|
|
404
|
+
scene.bubbles.push(bubble);
|
|
405
|
+
scene.bubbleLayer.addChild(bubble.node);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
layout(scene, scene.width, scene.height);
|
|
409
|
+
sceneRef = scene;
|
|
410
|
+
|
|
411
|
+
resizeObserverRef = new ResizeObserver((entries) => {
|
|
412
|
+
const entry = entries[0];
|
|
413
|
+
if (!entry || !sceneRef) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const width = Math.max(360, Math.round(entry.contentRect.width));
|
|
418
|
+
const height = Math.max(500, Math.round(entry.contentRect.height));
|
|
419
|
+
layout(sceneRef, width, height);
|
|
420
|
+
});
|
|
421
|
+
resizeObserverRef.observe(host);
|
|
422
|
+
|
|
423
|
+
app.ticker.add((deltaTime) => {
|
|
424
|
+
if (!sceneRef) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
updateScene(sceneRef, deltaTime);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
await pullState();
|
|
431
|
+
pollTimer = setInterval(() => {
|
|
432
|
+
void pullState();
|
|
433
|
+
}, 2000);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
window.addEventListener("beforeunload", cleanupScene);
|
|
437
|
+
|
|
438
|
+
void initGame().catch((error) => {
|
|
439
|
+
console.error("游戏初始化失败", error);
|
|
440
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Lobster Farmer</title>
|
|
7
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.4.3/dist/pixi.min.js"></script>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="game"></div>
|
|
12
|
+
<script src="/game.js"></script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@import url("https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap");
|
|
2
|
+
|
|
3
|
+
html,
|
|
4
|
+
body,
|
|
5
|
+
#game {
|
|
6
|
+
margin: 0;
|
|
7
|
+
width: 100%;
|
|
8
|
+
height: 100%;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
background: #020f1a;
|
|
14
|
+
font-family: "Press Start 2P", monospace;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* {
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.game-canvas-host {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.game-canvas-host canvas {
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: 100%;
|
|
29
|
+
display: block;
|
|
30
|
+
image-rendering: pixelated;
|
|
31
|
+
image-rendering: crisp-edges;
|
|
32
|
+
}
|