Skip to content

Commit e345f7e

Browse files
authored
Fallableの実装2 (#8)
* fallableを追加 * highlight周りのtick関数を名前変更&移動 * Revert "highlight周りのtick関数を名前変更&移動" This reverts commit 9f8c188. * grid.tick()とfallableの落下処理を実装 * pasteやredo時にdyをリセット * switchWithObjectを削除し、movableとfallableにswitchIdを追加 * tick()とctrl+Z時の動作をfallable+スイッチに対応 * fallableの速度が保存されていなかった * 同じidの複数のスイッチを設置可能にする * inverseSwitchingBlock実装 * 画面外に落下するfallableの挙動を実装 * inventoryIsInfiniteをステージ定義で設定可能にする * ステージ実装、バグ修正 * world3ではcopyを許可 * fix * fix * fix * fix
1 parent 6b77f5d commit e345f7e

File tree

12 files changed

+221
-82
lines changed

12 files changed

+221
-82
lines changed

routes/+page.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@
1515
<p>
1616
<a href="/game?stage=3-1">Stage 3-1</a>
1717
<a href="/game?stage=3-2">Stage 3-2</a>
18+
<a href="/game?stage=3-3">Stage 3-3</a>
19+
<a href="/game?stage=3-4">Stage 3-4</a>
1820
</p>

src/ability.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,7 @@ import { createSnapshot } from "./history.ts";
55
import * as History from "./history.ts";
66
import type { AbilityInit, Context, Coords, MovableObject } from "./public-types.ts";
77

8-
export function init(cx: Context, options?: AbilityInit) {
9-
cx.state.update((prev) => ({
10-
...prev,
11-
usage: options?.enabled ?? {
12-
copy: Number.POSITIVE_INFINITY,
13-
paste: Number.POSITIVE_INFINITY,
14-
cut: Number.POSITIVE_INFINITY,
15-
},
16-
inventoryIsInfinite: options?.inventoryIsInfinite ?? false,
17-
}));
18-
8+
export function init(cx: Context) {
199
console.log("ability init");
2010
document.addEventListener("copy", (e) => {
2111
const { onGround } = cx.dynamic.player;
@@ -160,12 +150,13 @@ export function placeMovableObject(cx: Context, x: number, y: number, object: Mo
160150
console.error("[placeMovableObject] cannot place object");
161151
return;
162152
}
153+
const newObjectId = Math.random().toString();
163154
for (const rel of object.relativePositions) {
164155
const positionX = x + rel.x;
165156
const positionY = y + rel.y;
166157
grid.setBlock(cx, positionX, positionY, {
167158
block: object.block,
168-
objectId: object.objectId,
159+
objectId: newObjectId,
169160
switchId: undefined,
170161
});
171162
}

src/constants.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ export enum Block {
2121
fallable = "fallable",
2222
switch = "switch",
2323
switchBase = "switch-base",
24-
switchingBlockOFF = "switching-block-off",
25-
switchingBlockON = "switching-block-on",
24+
switchingBlockOFF = "switching-block-off", // 初期状態で出現している
25+
switchingBlockON = "switching-block-on", // スイッチを押すと隠れる
26+
inverseSwitchingBlockOFF = "inverse-switching-block-off", // 初期状態で隠れている
27+
inverseSwitchingBlockON = "inverse-switching-block-on", // スイッチを押すと出現する
2628
switchPressed = "switch-pressed",
2729
spike = "spike",
2830
goal = "goal",
@@ -46,6 +48,7 @@ export const BlockDefinitionMap = new Map<string, Block | null>([
4648
["s", Block.switch],
4749
["S", Block.switchBase],
4850
["w", Block.switchingBlockOFF],
51+
["W", Block.inverseSwitchingBlockOFF],
4952
["^", Block.spike],
5053
["g", Block.goal],
5154
]);

src/grid.ts

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@ export type GridCell =
4040
}
4141
| {
4242
// switches / triggerable blocks
43-
block: Block.switch | Block.switchingBlockOFF | Block.switchingBlockON | Block.switchPressed;
43+
block:
44+
| Block.switch
45+
| Block.switchingBlockOFF
46+
| Block.switchingBlockON
47+
| Block.inverseSwitchingBlockOFF
48+
| Block.inverseSwitchingBlockON
49+
| Block.switchPressed;
4450
switchId?: string; // optional でいいの?
4551
objectId?: unknown;
4652
};
@@ -49,6 +55,7 @@ export class Grid {
4955
__vsom: VirtualSOM;
5056
marginY: number; // windowの上端とy=0上端の距離(px)
5157
oobSprites: Sprite[]; // グリッド定義の外を埋めるやつ
58+
oobFallableSprites: { sprite: Sprite | null; vy: number }[]; // グリッド定義外に落ちたfallable
5259
constructor(
5360
cx: {
5461
_stage_container: Container;
@@ -61,6 +68,7 @@ export class Grid {
6168
) {
6269
const stage = cx._stage_container;
6370
this.oobSprites = [];
71+
this.oobFallableSprites = [];
6472
this.marginY = (height - cellSize * stageDefinition.stage.length) / 2;
6573
const vsprites: VirtualSOM = [];
6674

@@ -104,6 +112,7 @@ export class Grid {
104112
});
105113
break;
106114
}
115+
case Block.inverseSwitchingBlockOFF:
107116
case Block.switchingBlockOFF: {
108117
const switchId = (
109118
get(cx.state).cells[y][x] as {
@@ -126,6 +135,7 @@ export class Grid {
126135
});
127136
break;
128137
}
138+
case Block.inverseSwitchingBlockON:
129139
case Block.switchingBlockON:
130140
case Block.switchPressed:
131141
throw new Error(`[Grid.constructor]: block is not supported: ${dblock}`);
@@ -226,6 +236,15 @@ export class Grid {
226236
}
227237
}
228238
this.initOOBSprites(cx, cellSize);
239+
this.clearFallableSprites(cx);
240+
}
241+
// 画面外のfallableはステージ内のfallableのコピーでありhistoryの対象ではないので、
242+
// undoなどでステージ全体に変更を加えるときに使う
243+
clearFallableSprites(cx: { _stage_container: Container }) {
244+
for (const sprite of this.oobFallableSprites) {
245+
if (sprite.sprite) cx._stage_container.removeChild(sprite.sprite);
246+
}
247+
this.oobFallableSprites = [];
229248
}
230249
getBlock(cx: Context, x: number, y: number): Block | null {
231250
return get(cx.state).cells[y]?.[x]?.block ?? null;
@@ -307,7 +326,7 @@ export class Grid {
307326
vprev.block = Block.switchPressed;
308327
vprev.dy = 0;
309328
vprev.vy = 0;
310-
} else if (vprev?.block === Block.switchPressed) {
329+
} else if (vprev?.block === Block.switchPressed && cNewCell.block === Block.switch) {
311330
// switchがプレイヤーに押されているのが戻るとき
312331
assert(
313332
cprev.block === Block.switchPressed || cprev.block === Block.switch,
@@ -328,7 +347,7 @@ export class Grid {
328347
vprev.vy = 0;
329348
}
330349
// switch上にオブジェクトを置くとき
331-
else if (vprev?.block === Block.switch) {
350+
else if (vprev?.block === Block.switch || vprev?.block === Block.switchPressed) {
332351
if (cNewCell.block !== Block.movable && cNewCell.block !== Block.fallable) {
333352
console.warn("No block other than movable cannot be placed on the switch");
334353
console.log("cell.block", cNewCell.block);
@@ -338,7 +357,10 @@ export class Grid {
338357
stage.addChild(movableSprite);
339358
assert(cNewCell.objectId !== undefined, "movable block must have objectId");
340359
assert(
341-
(cprev.block === Block.switch || cprev.block === Block.movable || cprev.block === Block.fallable) &&
360+
(cprev.block === Block.switch ||
361+
cprev.block === Block.switchPressed ||
362+
cprev.block === Block.movable ||
363+
cprev.block === Block.fallable) &&
342364
cprev.switchId !== undefined,
343365
"block is not switch",
344366
);
@@ -354,6 +376,7 @@ export class Grid {
354376
get(cx.state).switches.filter((s) => {
355377
if (s.x === x && s.y === y) {
356378
s.pressedByBlock = true;
379+
s.pressedByPlayer = false;
357380
}
358381
return s;
359382
});
@@ -391,50 +414,60 @@ export class Grid {
391414
});
392415
}
393416
// switchingBlockOFFがONに切り替わるとき
394-
else if (vprev?.block === Block.switchingBlockOFF) {
395-
if (cNewCell.block !== Block.switchingBlockON) {
417+
else if (vprev?.block === Block.switchingBlockOFF || vprev?.block === Block.inverseSwitchingBlockOFF) {
418+
if (cNewCell.block !== Block.switchingBlockON && cNewCell.block !== Block.inverseSwitchingBlockON) {
396419
console.warn("No block other than switchingBlockON cannot replace the switchingBlockOFF");
397420
return;
398421
}
399422
assert(
400-
cprev.block === Block.switchingBlockOFF || cprev.block === Block.switchingBlockON,
423+
cprev.block === Block.switchingBlockOFF ||
424+
cprev.block === Block.switchingBlockON ||
425+
cprev.block === Block.inverseSwitchingBlockOFF ||
426+
cprev.block === Block.inverseSwitchingBlockON,
401427
"block is not switchingBlock",
402428
);
429+
const inversed = vprev.block === Block.inverseSwitchingBlockOFF;
430+
const switchONBlock = inversed ? Block.inverseSwitchingBlockON : Block.switchingBlockON;
403431
const switchId = cprev.switchId;
404432
if (!switchId) throw new Error("switchId is undefined");
405-
const blockSprite = createSprite(blockSize, Block.switchingBlockON, x, y, marginY, switchColor(switchId));
433+
const blockSprite = createSprite(blockSize, switchONBlock, x, y, marginY, switchColor(switchId));
406434
stage.addChild(blockSprite);
407435
cells[y][x] = {
408-
block: Block.switchingBlockON,
436+
block: switchONBlock,
409437
switchId,
410438
objectId: undefined,
411439
};
412440
vprev.sprite = blockSprite;
413-
vprev.block = Block.switchingBlockON;
441+
vprev.block = switchONBlock;
414442
vprev.dy = 0;
415443
vprev.vy = 0;
416444
}
417445
// switchingBlockONがOFFに切り替わるとき
418-
else if (vprev?.block === Block.switchingBlockON) {
419-
if (cNewCell.block !== Block.switchingBlockOFF) {
446+
else if (vprev?.block === Block.switchingBlockON || vprev?.block === Block.inverseSwitchingBlockON) {
447+
if (cNewCell.block !== Block.switchingBlockOFF && cNewCell.block !== Block.inverseSwitchingBlockOFF) {
420448
console.warn("No block other than switchingBlockOFF cannot replace the switchingBlockON");
421449
return;
422450
}
423451
assert(
424-
cprev.block === Block.switchingBlockOFF || cprev.block === Block.switchingBlockON,
452+
cprev.block === Block.switchingBlockOFF ||
453+
cprev.block === Block.switchingBlockON ||
454+
cprev.block === Block.inverseSwitchingBlockOFF ||
455+
cprev.block === Block.inverseSwitchingBlockON,
425456
"block is not switchingBlock",
426457
);
458+
const inversed = vprev.block === Block.inverseSwitchingBlockON;
459+
const switchOFFBlock = inversed ? Block.inverseSwitchingBlockOFF : Block.switchingBlockOFF;
427460
const switchId = cprev.switchId;
428461
if (!switchId) throw new Error("switchId is undefined");
429-
const blockSprite = createSprite(blockSize, Block.switchingBlockOFF, x, y, marginY, switchColor(switchId));
462+
const blockSprite = createSprite(blockSize, switchOFFBlock, x, y, marginY, switchColor(switchId));
430463
stage.addChild(blockSprite);
431464
cells[y][x] = {
432-
block: Block.switchingBlockOFF,
465+
block: switchOFFBlock,
433466
switchId,
434467
objectId: undefined,
435468
};
436469
vprev.sprite = blockSprite;
437-
vprev.block = Block.switchingBlockOFF;
470+
vprev.block = switchOFFBlock;
438471
vprev.dy = 0;
439472
vprev.vy = 0;
440473
} else {
@@ -507,6 +540,19 @@ export class Grid {
507540
tick(cx: Context, ticker: Ticker) {
508541
const { blockSize, gridX, gridY, marginY } = get(cx.config);
509542
const cells = get(cx.state).cells;
543+
544+
for (const s of this.oobFallableSprites) {
545+
if (s.sprite) {
546+
s.sprite.y += s.vy * ticker.deltaTime;
547+
s.vy += consts.gravity * blockSize * ticker.deltaTime;
548+
if (s.sprite.y > gridY * blockSize + marginY * 2) {
549+
// 画面外に出たら消す
550+
s.sprite.parent?.removeChild(s.sprite);
551+
s.sprite = null;
552+
}
553+
}
554+
}
555+
510556
for (let y = gridY - 1; y >= 0; y--) {
511557
for (let x = 0; x < gridX; x++) {
512558
const vcell = this.__vsom[y][x];
@@ -522,14 +568,20 @@ export class Grid {
522568
}
523569

524570
let swapDiff = 0;
571+
let goOOB = false;
525572
while (swapDiff * blockSize <= vcell.dy) {
526573
// 下にブロックがあるなどの要因で止まる
527574
if (!isAvail(cells, x, y + swapDiff + 1)) break;
528575
vcell.dy -= blockSize;
529576
swapDiff++;
577+
if (y + swapDiff >= cells.length) {
578+
goOOB = true;
579+
break;
580+
}
530581
}
582+
531583
// これ以上下に行けない
532-
if (!isAvail(cells, x, y + swapDiff)) {
584+
if (!goOOB && !isAvail(cells, x, y + swapDiff)) {
533585
// 着地 (dy はたいてい 0 未満なので、別で判定が必要)
534586
if (vcell.dy >= 0) {
535587
vcell.dy = 0;
@@ -538,7 +590,18 @@ export class Grid {
538590
}
539591

540592
vcell.sprite.y = y * blockSize + marginY + vcell.dy;
541-
if (swapDiff > 0) {
593+
594+
if (goOOB) {
595+
// vcell.sprite はsetBlockで消えてしまうので、あたらしく作る
596+
const oobSprite = createSprite(blockSize, Block.fallable, x, y, marginY);
597+
oobSprite.y = (y + swapDiff) * blockSize + marginY + vcell.dy;
598+
cx._stage_container.addChild(oobSprite);
599+
this.oobFallableSprites.push({
600+
sprite: oobSprite,
601+
vy: vcell.vy,
602+
});
603+
this.setBlock(cx, x, y, { block: null });
604+
} else if (swapDiff > 0) {
542605
this.setBlock(cx, x, y, { block: null });
543606
this.setBlock(cx, x, y + swapDiff, ccell);
544607
const vSwapCell = this.__vsom[y + swapDiff][x];
@@ -561,8 +624,7 @@ function isAvail(cells: GridCell[][], x: number, y: number) {
561624
switch (true) {
562625
case y >= cells.length:
563626
// 床が抜けている
564-
// どうする?
565-
return false;
627+
return true;
566628
case cell.block === null || cell.block === Block.switch:
567629
// 下にブロックがない
568630
return true;
@@ -609,6 +671,8 @@ export function createCellsFromStageDefinition(stageDefinition: StageDefinition)
609671
}
610672
// switches
611673
case Block.switch:
674+
case Block.inverseSwitchingBlockON:
675+
case Block.inverseSwitchingBlockOFF:
612676
case Block.switchingBlockON:
613677
case Block.switchingBlockOFF: {
614678
const group = stageDefinition.switchGroups.find((b) => b.x === x && b.y === y);
@@ -681,13 +745,15 @@ function createSprite(
681745
updateSprite(switchBaseSprite, blockSize, x, y, marginY, 0);
682746
return switchBaseSprite;
683747
}
748+
case Block.inverseSwitchingBlockON:
684749
case Block.switchingBlockOFF: {
685750
const sprite = new Sprite(rockTexture);
686751
if (switchColor) sprite.tint = switchColor;
687752
else sprite.tint = 0xffa500;
688753
updateSprite(sprite, blockSize, x, y, marginY, 0);
689754
return sprite;
690755
}
756+
case Block.inverseSwitchingBlockOFF:
691757
case Block.switchingBlockON: {
692758
const sprite = new Sprite(rockTexture);
693759
if (switchColor) sprite.tint = switchColor;

src/history.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ function restore(cx: Context, ss: StateSnapshot) {
105105
cx.dynamic.player.y = ss.playerY;
106106
cx.dynamic.player.facing = ss.playerFacing;
107107
cx.grid.diffAndUpdateTo(cx, ss.game.cells);
108+
cx.grid.clearFallableSprites(cx);
108109
printCells(ss.game.cells, "restore");
109110
}
110111
function stash(cx: Context) {

src/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ export async function setup(
7373
};
7474
const initialGameState = {
7575
inventory: null,
76-
inventoryIsInfinite: false,
76+
inventoryIsInfinite: !!stageDefinition.inventoryIsInfinite,
7777
usage: stageDefinition.usage ?? {
7878
copy: 0,
79-
cut: Infinity,
80-
paste: Infinity,
79+
cut: Number.POSITIVE_INFINITY,
80+
paste: Number.POSITIVE_INFINITY,
8181
},
8282
cells: createCellsFromStageDefinition(stageDefinition),
8383
paused: false,

0 commit comments

Comments
 (0)