From d2063df78d94964a6637713d19a1b257e46892f6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Jan 2024 14:48:44 +0900 Subject: enhance(drop-and-fusion): add new mode, some tweaks --- .../frontend/src/scripts/drop-and-fusion-engine.ts | 85 ++++++++++++++-------- 1 file changed, 56 insertions(+), 29 deletions(-) (limited to 'packages/frontend/src/scripts') diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 41af9cb7a4..ad02c2832b 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -10,13 +10,15 @@ import seedrandom from 'seedrandom'; export type Mono = { id: string; level: number; - size: number; + sizeX: number; + sizeY: number; shape: 'circle' | 'rectangle'; score: number; dropCandidate: boolean; sfxPitch: number; img: string; - imgSize: number; + imgSizeX: number; + imgSizeY: number; spriteScale: number; }; @@ -59,6 +61,7 @@ export class DropAndFusionGame extends EventEmitter<{ private overflowCollider: Matter.Body; private isGameOver = false; private monoDefinitions: Mono[] = []; + private hasComboBonus = true; private rng: () => number; private logs: Log[] = []; private replaying = false; @@ -66,7 +69,9 @@ export class DropAndFusionGame extends EventEmitter<{ /** * フィールドに出ていて、かつ合体の対象となるアイテム */ - private activeBodyIds: Matter.Body['id'][] = []; + private fusionReadyBodyIds: Matter.Body['id'][] = []; + + private gameOverReadyBodyIds: Matter.Body['id'][] = []; /** * fusion予約アイテムのペア @@ -74,8 +79,6 @@ export class DropAndFusionGame extends EventEmitter<{ */ private fusionReservedPairs: { bodyA: Matter.Body; bodyB: Matter.Body }[] = []; - private latestDroppedBodyId: Matter.Body['id'] | null = null; - private latestDroppedAt = 0; private latestFusionedAt = 0; // frame private stock: { id: string; mono: Mono }[] = []; @@ -101,11 +104,17 @@ export class DropAndFusionGame extends EventEmitter<{ public replayPlaybackRate = 1; - constructor(env: { monoDefinitions: Mono[]; seed: string; replaying?: boolean }) { + constructor(env: { + monoDefinitions: Mono[]; + seed: string; + hasComboBonus: boolean; + replaying?: boolean; + }) { super(); this.replaying = !!env.replaying; this.monoDefinitions = env.monoDefinitions; + this.hasComboBonus = env.hasComboBonus; this.rng = seedrandom(env.seed); this.tick = this.tick.bind(this); @@ -147,6 +156,7 @@ export class DropAndFusionGame extends EventEmitter<{ //#endregion this.overflowCollider = Matter.Bodies.rectangle(this.GAME_WIDTH / 2, 0, this.GAME_WIDTH, 200, { + label: '_overflow_', isStatic: true, isSensor: true, render: { @@ -165,7 +175,7 @@ export class DropAndFusionGame extends EventEmitter<{ const options: Matter.IBodyDefinition = { label: mono.id, //density: 0.0005, - density: mono.size / 1000, + density: ((mono.sizeX + mono.sizeY) / 2) / 1000, restitution: 0.2, frictionAir: 0.01, friction: 0.7, @@ -175,16 +185,16 @@ export class DropAndFusionGame extends EventEmitter<{ render: { sprite: { texture: mono.img, - xScale: (mono.size / mono.imgSize) * mono.spriteScale, - yScale: (mono.size / mono.imgSize) * mono.spriteScale, + xScale: (mono.sizeX / mono.imgSizeX) * mono.spriteScale, + yScale: (mono.sizeY / mono.imgSizeY) * mono.spriteScale, }, }, }; if (mono.shape === 'circle') { - return Matter.Bodies.circle(x, y, mono.size / 2, options); + return Matter.Bodies.circle(x, y, mono.sizeX / 2, options); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (mono.shape === 'rectangle') { - return Matter.Bodies.rectangle(x, y, mono.size, mono.size, options); + return Matter.Bodies.rectangle(x, y, mono.sizeX, mono.sizeY, options); } else { throw new Error('unrecognized shape'); } @@ -202,8 +212,9 @@ export class DropAndFusionGame extends EventEmitter<{ const newX = (bodyA.position.x + bodyB.position.x) / 2; const newY = (bodyA.position.y + bodyB.position.y) / 2; + this.fusionReadyBodyIds = this.fusionReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); + this.gameOverReadyBodyIds = this.gameOverReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); Matter.Composite.remove(this.engine.world, [bodyA, bodyB]); - this.activeBodyIds = this.activeBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!; const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1); @@ -216,11 +227,11 @@ export class DropAndFusionGame extends EventEmitter<{ this.tickCallbackQueue.push({ frame: this.frame + this.msToFrame(100), callback: () => { - this.activeBodyIds.push(body.id); + this.fusionReadyBodyIds.push(body.id); }, }); - const comboBonus = 1 + ((this.combo - 1) / 5); + const comboBonus = this.hasComboBonus ? 1 + ((this.combo - 1) / 5) : 1; const additionalScore = Math.round(currentMono.score * comboBonus); this.score += additionalScore; @@ -245,14 +256,6 @@ export class DropAndFusionGame extends EventEmitter<{ for (const pairs of event.pairs) { const { bodyA, bodyB } = pairs; - if (bodyA.id === this.overflowCollider.id || bodyB.id === this.overflowCollider.id) { - if (bodyA.id === this.latestDroppedBodyId || bodyB.id === this.latestDroppedBodyId) { - continue; - } - this.gameOver(); - break; - } - const shouldFusion = (bodyA.label === bodyB.label) && !this.fusionReservedPairs.some(x => x.bodyA.id === bodyA.id || @@ -261,7 +264,7 @@ export class DropAndFusionGame extends EventEmitter<{ x.bodyB.id === bodyB.id); if (shouldFusion) { - if (this.activeBodyIds.includes(bodyA.id) && this.activeBodyIds.includes(bodyB.id)) { + if (this.fusionReadyBodyIds.includes(bodyA.id) && this.fusionReadyBodyIds.includes(bodyB.id)) { this.fusion(bodyA, bodyB); } else { this.fusionReservedPairs.push({ bodyA, bodyB }); @@ -274,12 +277,19 @@ export class DropAndFusionGame extends EventEmitter<{ }); } } else { + if (bodyA.label === '_overflow_' || bodyB.label === '_overflow_') continue; + + if (bodyA.label !== '_wall_' && bodyB.label !== '_wall_') { + if (!this.gameOverReadyBodyIds.includes(bodyA.id)) this.gameOverReadyBodyIds.push(bodyA.id); + if (!this.gameOverReadyBodyIds.includes(bodyB.id)) this.gameOverReadyBodyIds.push(bodyB.id); + } + const energy = pairs.collision.depth; if (energy > minCollisionEnergyForSound) { const volume = (Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4; const panV = - pairs.bodyA.label === '_wall_' ? bodyB.position.x - this.PLAYAREA_MARGIN : - pairs.bodyB.label === '_wall_' ? bodyA.position.x - this.PLAYAREA_MARGIN : + bodyA.label === '_wall_' ? bodyB.position.x - this.PLAYAREA_MARGIN : + bodyB.label === '_wall_' ? bodyA.position.x - this.PLAYAREA_MARGIN : ((bodyA.position.x + bodyB.position.x) / 2) - this.PLAYAREA_MARGIN; const panW = this.GAME_WIDTH - this.PLAYAREA_MARGIN - this.PLAYAREA_MARGIN; const pan = ((panV / panW) - 0.5) * 2; @@ -290,6 +300,21 @@ export class DropAndFusionGame extends EventEmitter<{ } } + private onCollisionActive(event: Matter.IEventCollision) { + for (const pairs of event.pairs) { + const { bodyA, bodyB } = pairs; + + // ハコからあふれたかどうかの判定 + if (bodyA.id === this.overflowCollider.id || bodyB.id === this.overflowCollider.id) { + if (this.gameOverReadyBodyIds.includes(bodyA.id) || this.gameOverReadyBodyIds.includes(bodyB.id)) { + this.gameOver(); + break; + } + continue; + } + } + } + public surrender() { this.logs.push({ frame: this.frame, @@ -314,6 +339,7 @@ export class DropAndFusionGame extends EventEmitter<{ this.emit('changeStock', this.stock); Matter.Events.on(this.engine, 'collisionStart', this.onCollision.bind(this)); + Matter.Events.on(this.engine, 'collisionActive', this.onCollisionActive.bind(this)); } public getLogs() { @@ -360,17 +386,18 @@ export class DropAndFusionGame extends EventEmitter<{ this.emit('changeStock', this.stock); const inputX = Math.round(_x); - const x = Math.min(this.GAME_WIDTH - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), inputX)); - const body = this.createBody(head.mono, x, 50 + head.mono.size / 2); + const x = Math.min(this.GAME_WIDTH - this.PLAYAREA_MARGIN - (head.mono.sizeX / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.sizeX / 2), inputX)); + const body = this.createBody(head.mono, x, 50 + head.mono.sizeY / 2); this.logs.push({ frame: this.frame, operation: 'drop', x: inputX, }); Matter.Composite.add(this.engine.world, body); - this.activeBodyIds.push(body.id); - this.latestDroppedBodyId = body.id; + + this.fusionReadyBodyIds.push(body.id); this.latestDroppedAt = Date.now(); + this.emit('dropped', x); this.emit('monoAdded', head.mono); } -- cgit v1.2.3-freya From a5ea7c976ba13dd4ad0aeddc5fe34378324ea8bb Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Jan 2024 15:28:41 +0900 Subject: chore(drop-and-fusion): bump version --- packages/frontend/src/scripts/drop-and-fusion-engine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/frontend/src/scripts') diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index ad02c2832b..8c5892e381 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -47,7 +47,7 @@ export class DropAndFusionGame extends EventEmitter<{ }> { private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる private COMBO_INTERVAL = 60; // frame - public readonly GAME_VERSION = 1; + public readonly GAME_VERSION = 2; public readonly GAME_WIDTH = 450; public readonly GAME_HEIGHT = 600; public readonly DROP_INTERVAL = 500; -- cgit v1.2.3-freya From c33f56e3ed18bd63da1f5001be8db55d34334f80 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Jan 2024 11:43:13 +0900 Subject: refactor(drop-and-fusion): レンダリングや効果音に関する関心をエンジンから分離 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/drop-and-fusion.game.vue | 410 +++++++-------------- .../frontend/src/scripts/drop-and-fusion-engine.ts | 337 ++++++++++++++--- 2 files changed, 425 insertions(+), 322 deletions(-) (limited to 'packages/frontend/src/scripts') diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index 19ee029ea7..b316a79569 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -144,7 +144,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue index 3f8fef6632..b21960a490 100644 --- a/packages/frontend/src/components/MkMediaBanner.vue +++ b/packages/frontend/src/components/MkMediaBanner.vue @@ -5,20 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only