enhance(drop-and-fusion): add new mode, some tweaks

This commit is contained in:
syuilo
2024-01-12 14:48:44 +09:00
parent be57ff4985
commit d2063df78d
18 changed files with 319 additions and 89 deletions

View File

@@ -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<Matter.Engine>) {
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);
}