|
@@ -52,6 +52,13 @@ interface Obstacle {
|
|
|
height: number;
|
|
height: number;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+interface UiRect {
|
|
|
|
|
+ x: number;
|
|
|
|
|
+ y: number;
|
|
|
|
|
+ width: number;
|
|
|
|
|
+ height: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
interface Bullet {
|
|
interface Bullet {
|
|
|
x: number;
|
|
x: number;
|
|
|
y: number;
|
|
y: number;
|
|
@@ -63,6 +70,7 @@ interface Bullet {
|
|
|
pierce: boolean;
|
|
pierce: boolean;
|
|
|
kind: WeaponId | 'enemy';
|
|
kind: WeaponId | 'enemy';
|
|
|
homing?: boolean;
|
|
homing?: boolean;
|
|
|
|
|
+ hitEnemies?: Set<Enemy>;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const W = 1280;
|
|
const W = 1280;
|
|
@@ -77,11 +85,14 @@ const END_X = U(3320);
|
|
|
const ACTOR_RENDER_Y_OFFSET = 0;
|
|
const ACTOR_RENDER_Y_OFFSET = 0;
|
|
|
const VISUAL_LANE_Y = LANE_Y + ACTOR_RENDER_Y_OFFSET;
|
|
const VISUAL_LANE_Y = LANE_Y + ACTOR_RENDER_Y_OFFSET;
|
|
|
const ENEMY_BULLET_HIT_RADIUS = U(24);
|
|
const ENEMY_BULLET_HIT_RADIUS = U(24);
|
|
|
|
|
+const BOSS_MAX_HP = 300;
|
|
|
|
|
+const BOSS_PHASE_TWO_RATIO = 0.6;
|
|
|
|
|
+const BOSS_ENRAGE_RATIO = 0.2;
|
|
|
|
|
|
|
|
const WEAPONS = {
|
|
const WEAPONS = {
|
|
|
rifle: { name: 'RIFLE', cd: 0.11, damage: 4, speed: U(880) },
|
|
rifle: { name: 'RIFLE', cd: 0.11, damage: 4, speed: U(880) },
|
|
|
flame: { name: 'FLAME', cd: 0.055, damage: 1.4, speed: U(520) },
|
|
flame: { name: 'FLAME', cd: 0.055, damage: 1.4, speed: U(520) },
|
|
|
- rail: { name: 'RAIL', cd: 0.58, damage: 18, speed: U(1250) }
|
|
|
|
|
|
|
+ rail: { name: 'RAIL', cd: 0.58, damage: 5, speed: U(1250) }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
function enemyVisualProjectileOffset(type: EnemyType) {
|
|
function enemyVisualProjectileOffset(type: EnemyType) {
|
|
@@ -100,8 +111,10 @@ export class SteelAssaultGame extends Component {
|
|
|
private spriteLayer = new Node('RuntimeSprites');
|
|
private spriteLayer = new Node('RuntimeSprites');
|
|
|
private propLayer = new Node('RuntimeProps');
|
|
private propLayer = new Node('RuntimeProps');
|
|
|
private uiArtLayer = new Node('RuntimeUIArt');
|
|
private uiArtLayer = new Node('RuntimeUIArt');
|
|
|
|
|
+ private outcomeButtonLayer = new Node('RuntimeOutcomeButtons');
|
|
|
private hudNode = new Node('RuntimeHUD');
|
|
private hudNode = new Node('RuntimeHUD');
|
|
|
private graphics!: Graphics;
|
|
private graphics!: Graphics;
|
|
|
|
|
+ private outcomeButtonGraphics!: Graphics;
|
|
|
private hud!: Label;
|
|
private hud!: Label;
|
|
|
private hpHud!: Label;
|
|
private hpHud!: Label;
|
|
|
private scoreHud!: Label;
|
|
private scoreHud!: Label;
|
|
@@ -109,6 +122,8 @@ export class SteelAssaultGame extends Component {
|
|
|
private outcomeTitle!: Label;
|
|
private outcomeTitle!: Label;
|
|
|
private outcomeStats!: Label;
|
|
private outcomeStats!: Label;
|
|
|
private outcomeAction!: Label;
|
|
private outcomeAction!: Label;
|
|
|
|
|
+ private outcomeNextAction!: Label;
|
|
|
|
|
+ private outcomeRestartAction!: Label;
|
|
|
private keys = new Set<KeyCode>();
|
|
private keys = new Set<KeyCode>();
|
|
|
private weapon: WeaponId = 'rifle';
|
|
private weapon: WeaponId = 'rifle';
|
|
|
private player = { x: U(-360), y: LANE_Y, vx: 0, vy: 0, hp: 6, face: 1, grounded: true };
|
|
private player = { x: U(-360), y: LANE_Y, vx: 0, vy: 0, hp: 6, face: 1, grounded: true };
|
|
@@ -128,6 +143,7 @@ export class SteelAssaultGame extends Component {
|
|
|
private heroSprite: Sprite | null = null;
|
|
private heroSprite: Sprite | null = null;
|
|
|
private enemySprites = new Map<Enemy, Sprite>();
|
|
private enemySprites = new Map<Enemy, Sprite>();
|
|
|
private bulletSprites = new Map<Bullet, Sprite>();
|
|
private bulletSprites = new Map<Bullet, Sprite>();
|
|
|
|
|
+ private obstacleSprites = new Map<Obstacle, Sprite>();
|
|
|
private heroFrames: SpriteFrame[] = [];
|
|
private heroFrames: SpriteFrame[] = [];
|
|
|
private sporeFrames: SpriteFrame[] = [];
|
|
private sporeFrames: SpriteFrame[] = [];
|
|
|
private wingFrames: SpriteFrame[] = [];
|
|
private wingFrames: SpriteFrame[] = [];
|
|
@@ -182,10 +198,11 @@ export class SteelAssaultGame extends Component {
|
|
|
{ x: U(520), y: LANE_Y, width: U(118), height: U(58) },
|
|
{ x: U(520), y: LANE_Y, width: U(118), height: U(58) },
|
|
|
{ x: U(1120), y: LANE_Y, width: U(138), height: U(72) },
|
|
{ x: U(1120), y: LANE_Y, width: U(138), height: U(72) },
|
|
|
{ x: U(2220), y: LANE_Y, width: U(132), height: U(64) },
|
|
{ x: U(2220), y: LANE_Y, width: U(132), height: U(64) },
|
|
|
- { x: U(2650), y: LANE_Y, width: U(122), height: U(70) },
|
|
|
|
|
- { x: U(2860), y: LANE_Y, width: U(112), height: U(58) },
|
|
|
|
|
- { x: U(3025), y: LANE_Y, width: U(132), height: U(92) },
|
|
|
|
|
- { x: U(3160), y: LANE_Y, width: U(112), height: U(126) }
|
|
|
|
|
|
|
+ { x: U(2600), y: LANE_Y + U(8), width: U(132), height: U(58) },
|
|
|
|
|
+ { x: U(2760), y: LANE_Y + U(78), width: U(126), height: U(56) },
|
|
|
|
|
+ { x: U(2940), y: LANE_Y + U(48), width: U(132), height: U(58) },
|
|
|
|
|
+ { x: U(3100), y: LANE_Y + U(132), width: U(138), height: U(56) },
|
|
|
|
|
+ { x: U(3190), y: LANE_Y + U(82), width: U(132), height: U(72) }
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
onLoad() {
|
|
onLoad() {
|
|
@@ -283,6 +300,12 @@ export class SteelAssaultGame extends Component {
|
|
|
this.uiArtLayer.layer = Layers.Enum.UI_2D;
|
|
this.uiArtLayer.layer = Layers.Enum.UI_2D;
|
|
|
this.uiArtLayer.addComponent(UITransform).setContentSize(this.viewportWidth, H);
|
|
this.uiArtLayer.addComponent(UITransform).setContentSize(this.viewportWidth, H);
|
|
|
|
|
|
|
|
|
|
+ this.node.addChild(this.outcomeButtonLayer);
|
|
|
|
|
+ this.outcomeButtonLayer.layer = Layers.Enum.UI_2D;
|
|
|
|
|
+ this.outcomeButtonLayer.addComponent(UITransform).setContentSize(this.viewportWidth, H);
|
|
|
|
|
+ this.outcomeButtonGraphics = this.outcomeButtonLayer.addComponent(Graphics);
|
|
|
|
|
+ this.bindOutcomeRestartNode(this.outcomeButtonLayer);
|
|
|
|
|
+
|
|
|
this.node.addChild(this.hudNode);
|
|
this.node.addChild(this.hudNode);
|
|
|
this.hudNode.layer = Layers.Enum.UI_2D;
|
|
this.hudNode.layer = Layers.Enum.UI_2D;
|
|
|
this.hudNode.addComponent(UITransform).setContentSize(W, U(64));
|
|
this.hudNode.addComponent(UITransform).setContentSize(W, U(64));
|
|
@@ -354,6 +377,26 @@ export class SteelAssaultGame extends Component {
|
|
|
this.outcomeAction.lineHeight = 36;
|
|
this.outcomeAction.lineHeight = 36;
|
|
|
this.outcomeAction.color = new Color(14, 29, 36, 255);
|
|
this.outcomeAction.color = new Color(14, 29, 36, 255);
|
|
|
|
|
|
|
|
|
|
+ const outcomeNextNode = new Node('OutcomeNextAction');
|
|
|
|
|
+ outcomeNextNode.layer = Layers.Enum.UI_2D;
|
|
|
|
|
+ this.node.addChild(outcomeNextNode);
|
|
|
|
|
+ outcomeNextNode.addComponent(UITransform).setContentSize(U(220), U(64));
|
|
|
|
|
+ outcomeNextNode.setPosition(U(-126), U(-100), 30);
|
|
|
|
|
+ this.outcomeNextAction = outcomeNextNode.addComponent(Label);
|
|
|
|
|
+ this.outcomeNextAction.fontSize = 26;
|
|
|
|
|
+ this.outcomeNextAction.lineHeight = 32;
|
|
|
|
|
+ this.outcomeNextAction.color = new Color(23, 32, 38, 255);
|
|
|
|
|
+
|
|
|
|
|
+ const outcomeRestartNode = new Node('OutcomeRestartAction');
|
|
|
|
|
+ outcomeRestartNode.layer = Layers.Enum.UI_2D;
|
|
|
|
|
+ this.node.addChild(outcomeRestartNode);
|
|
|
|
|
+ outcomeRestartNode.addComponent(UITransform).setContentSize(U(220), U(64));
|
|
|
|
|
+ outcomeRestartNode.setPosition(U(126), U(-100), 30);
|
|
|
|
|
+ this.outcomeRestartAction = outcomeRestartNode.addComponent(Label);
|
|
|
|
|
+ this.outcomeRestartAction.fontSize = 26;
|
|
|
|
|
+ this.outcomeRestartAction.lineHeight = 32;
|
|
|
|
|
+ this.outcomeRestartAction.color = new Color(235, 252, 255, 255);
|
|
|
|
|
+
|
|
|
view.resizeWithBrowserSize(true);
|
|
view.resizeWithBrowserSize(true);
|
|
|
|
|
|
|
|
this.applyFullscreenLandscapeLayout();
|
|
this.applyFullscreenLandscapeLayout();
|
|
@@ -553,6 +596,7 @@ if (!this.backgroundSprite) return;
|
|
|
for (const name of ui) {
|
|
for (const name of ui) {
|
|
|
this.loadFrame(`sprites/imagegen/ui_scene/ui/${name}`, (frame) => this.uiFrames.set(name, frame));
|
|
this.loadFrame(`sprites/imagegen/ui_scene/ui/${name}`, (frame) => this.uiFrames.set(name, frame));
|
|
|
}
|
|
}
|
|
|
|
|
+ this.loadFrame('sprites/imagegen/frames/obstacle_block', (frame) => this.uiFrames.set('obstacle_block', frame));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private loadFrame(path: string, use: (frame: SpriteFrame) => void) {
|
|
private loadFrame(path: string, use: (frame: SpriteFrame) => void) {
|
|
@@ -661,10 +705,8 @@ if (!this.backgroundSprite) return;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private randomEnemyType(seed: number): Exclude<EnemyType, 'boss'> {
|
|
|
|
|
- const types: Array<Exclude<EnemyType, 'boss'>> = ['spore', 'wing', 'turret'];
|
|
|
|
|
- const value = Math.abs(Math.sin(seed * 12.9898 + this.score * 0.01 + this.player.x * 0.017));
|
|
|
|
|
- return types[Math.floor(value * types.length) % types.length];
|
|
|
|
|
|
|
+ private randomEnemyType(_seed: number): Exclude<EnemyType, 'boss'> {
|
|
|
|
|
+ return 'spore';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private updateObstacleHits() {
|
|
private updateObstacleHits() {
|
|
@@ -726,7 +768,8 @@ if (!this.backgroundSprite) return;
|
|
|
this.bossTime += dt;
|
|
this.bossTime += dt;
|
|
|
enemy.vx = Math.sin(this.bossTime * 0.7) * U(28);
|
|
enemy.vx = Math.sin(this.bossTime * 0.7) * U(28);
|
|
|
enemy.y = BOSS_Y + Math.sin(this.bossTime * 1.1) * U(10);
|
|
enemy.y = BOSS_Y + Math.sin(this.bossTime * 1.1) * U(10);
|
|
|
- const phase = enemy.hp > 115 ? 1 : enemy.hp > 55 ? 2 : 3;
|
|
|
|
|
|
|
+ const hpRatio = enemy.hp / enemy.maxHp;
|
|
|
|
|
+ const phase = hpRatio <= BOSS_ENRAGE_RATIO ? 3 : hpRatio <= BOSS_PHASE_TWO_RATIO ? 2 : 1;
|
|
|
if (enemy.cd <= 0 && this.canEnemyShoot(enemy, U(980))) {
|
|
if (enemy.cd <= 0 && this.canEnemyShoot(enemy, U(980))) {
|
|
|
const shotX = enemy.x - U(115);
|
|
const shotX = enemy.x - U(115);
|
|
|
const targetX = this.player.x;
|
|
const targetX = this.player.x;
|
|
@@ -734,8 +777,8 @@ if (!this.backgroundSprite) return;
|
|
|
this.enemyShotAt(shotX, enemy.y + this.enemyShotOffset(enemy) + U(46), targetX, targetY, phase >= 3 ? U(255) : U(220), 2.6);
|
|
this.enemyShotAt(shotX, enemy.y + this.enemyShotOffset(enemy) + U(46), targetX, targetY, phase >= 3 ? U(255) : U(220), 2.6);
|
|
|
if (phase >= 2) this.enemyShotAt(shotX, enemy.y + this.enemyShotOffset(enemy) - U(18), targetX, targetY + U(34), U(205), 2.45);
|
|
if (phase >= 2) this.enemyShotAt(shotX, enemy.y + this.enemyShotOffset(enemy) - U(18), targetX, targetY + U(34), U(205), 2.45);
|
|
|
if (phase >= 3) this.enemyShotAt(shotX, enemy.y + this.enemyShotOffset(enemy) + U(88), targetX, targetY - U(28), U(190), 2.35);
|
|
if (phase >= 3) this.enemyShotAt(shotX, enemy.y + this.enemyShotOffset(enemy) + U(88), targetX, targetY - U(28), U(190), 2.35);
|
|
|
- if (phase >= 3 && this.enemies.filter((e) => e.type === 'spore').length < 4) this.spawnEnemy('spore', enemy.x - U(140), 0);
|
|
|
|
|
- enemy.cd = phase === 1 ? 1.75 : phase === 2 ? 1.45 : 1.18;
|
|
|
|
|
|
|
+ if (phase >= 3 && this.enemies.filter((e) => e.type === 'spore').length < 5) this.spawnEnemy('spore', enemy.x - U(180), 0);
|
|
|
|
|
+ enemy.cd = phase === 1 ? 1.65 : phase === 2 ? 1.28 : 0.95;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
enemy.x += enemy.vx * dt;
|
|
enemy.x += enemy.vx * dt;
|
|
@@ -767,9 +810,15 @@ if (!this.backgroundSprite) return;
|
|
|
if (bullet.player) {
|
|
if (bullet.player) {
|
|
|
for (const enemy of this.enemies) {
|
|
for (const enemy of this.enemies) {
|
|
|
const radius = enemy.type === 'boss' ? U(155) : U(44);
|
|
const radius = enemy.type === 'boss' ? U(155) : U(44);
|
|
|
|
|
+ if (bullet.pierce && bullet.hitEnemies?.has(enemy)) continue;
|
|
|
if (enemy.hp > 0 && this.hit(bullet.x, bullet.y, enemy.x, enemy.y, radius)) {
|
|
if (enemy.hp > 0 && this.hit(bullet.x, bullet.y, enemy.x, enemy.y, radius)) {
|
|
|
enemy.hp -= bullet.damage;
|
|
enemy.hp -= bullet.damage;
|
|
|
- if (!bullet.pierce) bullet.life = 0;
|
|
|
|
|
|
|
+ if (bullet.pierce) {
|
|
|
|
|
+ if (!bullet.hitEnemies) bullet.hitEnemies = new Set<Enemy>();
|
|
|
|
|
+ bullet.hitEnemies.add(enemy);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ bullet.life = 0;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} else if (this.hit(bullet.x, bullet.y, this.player.x, this.player.y, ENEMY_BULLET_HIT_RADIUS)) {
|
|
} else if (this.hit(bullet.x, bullet.y, this.player.x, this.player.y, ENEMY_BULLET_HIT_RADIUS)) {
|
|
@@ -831,7 +880,7 @@ if (!this.backgroundSprite) return;
|
|
|
player: false,
|
|
player: false,
|
|
|
pierce: false,
|
|
pierce: false,
|
|
|
kind: 'enemy',
|
|
kind: 'enemy',
|
|
|
- homing: true
|
|
|
|
|
|
|
+ homing: false
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -849,7 +898,7 @@ if (!this.backgroundSprite) return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private spawnBoss() {
|
|
private spawnBoss() {
|
|
|
- const boss = { type: 'boss' as const, x: U(3290), y: BOSS_Y, hp: 180, maxHp: 180, vx: 0, cd: 0.5 };
|
|
|
|
|
|
|
+ const boss = { type: 'boss' as const, x: U(3290), y: BOSS_Y, hp: BOSS_MAX_HP, maxHp: BOSS_MAX_HP, vx: 0, cd: 0.5 };
|
|
|
this.boss = boss;
|
|
this.boss = boss;
|
|
|
this.enemies.push(boss);
|
|
this.enemies.push(boss);
|
|
|
this.playSfx('boss_alert', 0.95);
|
|
this.playSfx('boss_alert', 0.95);
|
|
@@ -896,17 +945,17 @@ if (!this.backgroundSprite) return;
|
|
|
}
|
|
}
|
|
|
const half = this.visibleWorldWidth() / 2;
|
|
const half = this.visibleWorldWidth() / 2;
|
|
|
const hpX = -half + 18;
|
|
const hpX = -half + 18;
|
|
|
- const scoreX = half - 246;
|
|
|
|
|
|
|
+ const scorePanel = this.scoreWeaponPanelRect();
|
|
|
g.fillColor = new Color(5, 12, 22, 205);
|
|
g.fillColor = new Color(5, 12, 22, 205);
|
|
|
g.fillRect(hpX, H / 2 - U(54), 232, 56);
|
|
g.fillRect(hpX, H / 2 - U(54), 232, 56);
|
|
|
- g.fillRect(scoreX, H / 2 - U(54), 228, 56);
|
|
|
|
|
|
|
+ g.fillRect(scorePanel.x, scorePanel.y, scorePanel.width, scorePanel.height);
|
|
|
g.fillColor = new Color(255, 207, 72, 230);
|
|
g.fillColor = new Color(255, 207, 72, 230);
|
|
|
g.fillRect(hpX, H / 2 - U(54), 4, 56);
|
|
g.fillRect(hpX, H / 2 - U(54), 4, 56);
|
|
|
- g.fillRect(scoreX, H / 2 - U(54), 4, 56);
|
|
|
|
|
|
|
+ g.fillRect(scorePanel.x, scorePanel.y, 4, scorePanel.height);
|
|
|
g.strokeColor = new Color(64, 238, 255, 190);
|
|
g.strokeColor = new Color(64, 238, 255, 190);
|
|
|
g.lineWidth = 2;
|
|
g.lineWidth = 2;
|
|
|
g.rect(hpX, H / 2 - U(54), 232, 56);
|
|
g.rect(hpX, H / 2 - U(54), 232, 56);
|
|
|
- g.rect(scoreX, H / 2 - U(54), 228, 56);
|
|
|
|
|
|
|
+ g.rect(scorePanel.x, scorePanel.y, scorePanel.width, scorePanel.height);
|
|
|
g.stroke();
|
|
g.stroke();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -969,7 +1018,16 @@ if (!this.backgroundSprite) return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private drawObstacleSolids(g: Graphics) {
|
|
private drawObstacleSolids(g: Graphics) {
|
|
|
- if (this.state !== 'playing') return;
|
|
|
|
|
|
|
+ if (this.state !== 'playing') {
|
|
|
|
|
+ this.hideObstacleSprites();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const frame = this.uiFrames.get('obstacle_block');
|
|
|
|
|
+ if (frame) {
|
|
|
|
|
+ this.syncObstacleSprites(frame);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.hideObstacleSprites();
|
|
|
for (const obstacle of this.obstacles) {
|
|
for (const obstacle of this.obstacles) {
|
|
|
const x = obstacle.x - this.cameraX;
|
|
const x = obstacle.x - this.cameraX;
|
|
|
const half = this.visibleWorldWidth() / 2;
|
|
const half = this.visibleWorldWidth() / 2;
|
|
@@ -999,31 +1057,51 @@ if (!this.backgroundSprite) return;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private syncObstacleSprites(frame: SpriteFrame) {
|
|
|
|
|
+ const half = this.visibleWorldWidth() / 2;
|
|
|
|
|
+ for (const obstacle of this.obstacles) {
|
|
|
|
|
+ let sprite = this.obstacleSprites.get(obstacle);
|
|
|
|
|
+ if (!sprite) {
|
|
|
|
|
+ sprite = this.createSprite('ObstacleBlock', this.propLayer);
|
|
|
|
|
+ sprite.sizeMode = Sprite.SizeMode.CUSTOM;
|
|
|
|
|
+ this.obstacleSprites.set(obstacle, sprite);
|
|
|
|
|
+ }
|
|
|
|
|
+ const x = obstacle.x - this.cameraX;
|
|
|
|
|
+ const visible = x >= -half - U(180) && x <= half + U(180);
|
|
|
|
|
+ sprite.spriteFrame = frame;
|
|
|
|
|
+ sprite.node.getComponent(UITransform)?.setContentSize(obstacle.width, obstacle.height);
|
|
|
|
|
+ sprite.node.setPosition(x, obstacle.y + obstacle.height / 2 + ACTOR_RENDER_Y_OFFSET, 2);
|
|
|
|
|
+ sprite.node.active = visible && this.state === 'playing';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private hideObstacleSprites() {
|
|
|
|
|
+ for (const sprite of this.obstacleSprites.values()) sprite.node.active = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private drawControlButtons(g: Graphics) {
|
|
private drawControlButtons(g: Graphics) {
|
|
|
if (this.state !== 'playing') return;
|
|
if (this.state !== 'playing') return;
|
|
|
- const x = this.visibleWorldWidth() / 2 - 228;
|
|
|
|
|
- const y = H / 2 - 144;
|
|
|
|
|
- const slotWidth = 60;
|
|
|
|
|
- const slotGap = 7;
|
|
|
|
|
- g.fillColor = new Color(8, 21, 29, 220);
|
|
|
|
|
- g.fillRect(x, y, 204, 72);
|
|
|
|
|
- g.strokeColor = new Color(64, 238, 255, 230);
|
|
|
|
|
- g.lineWidth = 3;
|
|
|
|
|
- g.rect(x, y, 204, 72);
|
|
|
|
|
- g.stroke();
|
|
|
|
|
|
|
+ const panel = this.scoreWeaponPanelRect();
|
|
|
|
|
+ const slotWidth = U(54);
|
|
|
|
|
+ const slotHeight = U(36);
|
|
|
|
|
+ const y = panel.y + U(18);
|
|
|
(Object.keys(WEAPONS) as WeaponId[]).forEach((id, index) => {
|
|
(Object.keys(WEAPONS) as WeaponId[]).forEach((id, index) => {
|
|
|
- const sx = x + 14 + index * (slotWidth + slotGap);
|
|
|
|
|
|
|
+ const sx = panel.x + U(68) + index * U(64) - slotWidth / 2;
|
|
|
const active = this.weapon === id;
|
|
const active = this.weapon === id;
|
|
|
g.fillColor = active ? this.weaponColor(id, 235) : new Color(17, 34, 42, 225);
|
|
g.fillColor = active ? this.weaponColor(id, 235) : new Color(17, 34, 42, 225);
|
|
|
- g.fillRect(sx, y + 14, slotWidth, 42);
|
|
|
|
|
|
|
+ g.fillRect(sx, y, slotWidth, slotHeight);
|
|
|
g.strokeColor = active ? new Color(255, 255, 255, 245) : new Color(64, 238, 255, 120);
|
|
g.strokeColor = active ? new Color(255, 255, 255, 245) : new Color(64, 238, 255, 120);
|
|
|
g.lineWidth = active ? 4 : 2;
|
|
g.lineWidth = active ? 4 : 2;
|
|
|
- g.rect(sx, y + 14, slotWidth, 42);
|
|
|
|
|
|
|
+ g.rect(sx, y, slotWidth, slotHeight);
|
|
|
g.stroke();
|
|
g.stroke();
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private drawActors(g: Graphics) {
|
|
private drawActors(g: Graphics) {
|
|
|
|
|
+ if (this.isOutcomeState()) {
|
|
|
|
|
+ this.hideActorSprites();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
if (this.spritesReady) {
|
|
if (this.spritesReady) {
|
|
|
this.drawSpriteActors(g);
|
|
this.drawSpriteActors(g);
|
|
|
return;
|
|
return;
|
|
@@ -1115,6 +1193,13 @@ if (!this.backgroundSprite) return;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private hideActorSprites() {
|
|
|
|
|
+ if (this.heroSprite) this.heroSprite.node.active = false;
|
|
|
|
|
+ if (this.heroWeaponSprite) this.heroWeaponSprite.node.active = false;
|
|
|
|
|
+ for (const sprite of this.enemySprites.values()) sprite.node.active = false;
|
|
|
|
|
+ for (const sprite of this.bulletSprites.values()) sprite.node.active = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private syncHeroSprite() {
|
|
private syncHeroSprite() {
|
|
|
if (!this.heroSprite) this.heroSprite = this.createSprite('HeroSprite');
|
|
if (!this.heroSprite) this.heroSprite = this.createSprite('HeroSprite');
|
|
|
const frame = this.getHeroFrame();
|
|
const frame = this.getHeroFrame();
|
|
@@ -1161,7 +1246,9 @@ if (!this.backgroundSprite) return;
|
|
|
const frame = this.getEnemyFrame(enemy);
|
|
const frame = this.getEnemyFrame(enemy);
|
|
|
if (frame) sprite.spriteFrame = frame;
|
|
if (frame) sprite.spriteFrame = frame;
|
|
|
sprite.node.setPosition(enemy.x - this.cameraX, this.enemyRenderY(enemy), 0);
|
|
sprite.node.setPosition(enemy.x - this.cameraX, this.enemyRenderY(enemy), 0);
|
|
|
- const face = enemy.x > this.player.x ? -1 : 1;
|
|
|
|
|
|
|
+ const face = enemy.type === 'spore'
|
|
|
|
|
+ ? (enemy.x > this.player.x ? 1 : -1)
|
|
|
|
|
+ : (enemy.x > this.player.x ? -1 : 1);
|
|
|
const scale = enemy.type === 'boss' ? US(1.06) : enemy.type === 'turret' ? US(0.44) : enemy.type === 'wing' ? US(0.41) : US(0.51);
|
|
const scale = enemy.type === 'boss' ? US(1.06) : enemy.type === 'turret' ? US(0.44) : enemy.type === 'wing' ? US(0.41) : US(0.51);
|
|
|
sprite.node.setScale(new Vec3(scale * face, scale, 1));
|
|
sprite.node.setScale(new Vec3(scale * face, scale, 1));
|
|
|
sprite.node.active = true;
|
|
sprite.node.active = true;
|
|
@@ -1209,6 +1296,7 @@ if (!this.backgroundSprite) return;
|
|
|
this.syncWeaponArt();
|
|
this.syncWeaponArt();
|
|
|
this.syncTouchControls();
|
|
this.syncTouchControls();
|
|
|
this.syncOutcomePanelArt();
|
|
this.syncOutcomePanelArt();
|
|
|
|
|
+ this.syncOutcomeButtons();
|
|
|
this.syncRetryArt();
|
|
this.syncRetryArt();
|
|
|
this.syncOutcomeText();
|
|
this.syncOutcomeText();
|
|
|
if (this.failOverlaySprite) this.failOverlaySprite.node.active = this.state === 'lose';
|
|
if (this.failOverlaySprite) this.failOverlaySprite.node.active = this.state === 'lose';
|
|
@@ -1216,12 +1304,23 @@ if (!this.backgroundSprite) return;
|
|
|
|
|
|
|
|
private syncResponsiveUiLayout() {
|
|
private syncResponsiveUiLayout() {
|
|
|
const half = this.visibleWorldWidth() / 2;
|
|
const half = this.visibleWorldWidth() / 2;
|
|
|
|
|
+ const scorePanel = this.scoreWeaponPanelRect();
|
|
|
this.hpHud.node.setPosition(-half + U(112), H / 2 - U(30), 8);
|
|
this.hpHud.node.setPosition(-half + U(112), H / 2 - U(30), 8);
|
|
|
- this.scoreHud.node.setPosition(half - U(210), H / 2 - U(38), 8);
|
|
|
|
|
- this.weaponHud.node.setPosition(half - U(210), H / 2 - U(70), 24);
|
|
|
|
|
|
|
+ this.scoreHud.node.setPosition(scorePanel.x + scorePanel.width / 2, scorePanel.y + scorePanel.height - U(34), 8);
|
|
|
|
|
+ this.weaponHud.node.setPosition(scorePanel.x + scorePanel.width / 2, scorePanel.y + U(70), 24);
|
|
|
if (this.hpPanelSprite) this.hpPanelSprite.node.setPosition(-half + U(112), H / 2 - U(30), 6);
|
|
if (this.hpPanelSprite) this.hpPanelSprite.node.setPosition(-half + U(112), H / 2 - U(30), 6);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private scoreWeaponPanelRect(): UiRect {
|
|
|
|
|
+ const half = this.visibleWorldWidth() / 2;
|
|
|
|
|
+ return {
|
|
|
|
|
+ x: half - U(430),
|
|
|
|
|
+ y: H / 2 - U(150),
|
|
|
|
|
+ width: U(306),
|
|
|
|
|
+ height: U(112)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private syncTouchControls() {
|
|
private syncTouchControls() {
|
|
|
const visible = this.state === 'playing';
|
|
const visible = this.state === 'playing';
|
|
|
this.touchLeftSprite = this.syncMobileButton(
|
|
this.touchLeftSprite = this.syncMobileButton(
|
|
@@ -1315,6 +1414,7 @@ if (!this.backgroundSprite) return;
|
|
|
if (!frame) return sprite;
|
|
if (!frame) return sprite;
|
|
|
if (!sprite) {
|
|
if (!sprite) {
|
|
|
sprite = this.createSprite(name, this.uiArtLayer);
|
|
sprite = this.createSprite(name, this.uiArtLayer);
|
|
|
|
|
+ sprite.sizeMode = Sprite.SizeMode.CUSTOM;
|
|
|
this.bindTouchControlNode(sprite.node, onPress, onRelease, repeatOnMove);
|
|
this.bindTouchControlNode(sprite.node, onPress, onRelease, repeatOnMove);
|
|
|
}
|
|
}
|
|
|
sprite.spriteFrame = frame;
|
|
sprite.spriteFrame = frame;
|
|
@@ -1364,8 +1464,7 @@ if (!this.backgroundSprite) return;
|
|
|
const restart = (event: EventTouch | EventMouse) => {
|
|
const restart = (event: EventTouch | EventMouse) => {
|
|
|
if (!this.isOutcomeState()) return;
|
|
if (!this.isOutcomeState()) return;
|
|
|
this.unlockAudio();
|
|
this.unlockAudio();
|
|
|
- this.reset();
|
|
|
|
|
- this.stopTouchEvent(event);
|
|
|
|
|
|
|
+ if (this.handleOutcomeAction(event)) this.stopTouchEvent(event);
|
|
|
};
|
|
};
|
|
|
node.on(Node.EventType.TOUCH_START, restart, this);
|
|
node.on(Node.EventType.TOUCH_START, restart, this);
|
|
|
node.on(Node.EventType.MOUSE_DOWN, restart, this);
|
|
node.on(Node.EventType.MOUSE_DOWN, restart, this);
|
|
@@ -1377,6 +1476,31 @@ if (!this.backgroundSprite) return;
|
|
|
stoppable.propagationStopped = true;
|
|
stoppable.propagationStopped = true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private handleOutcomeAction(event: EventTouch | EventMouse) {
|
|
|
|
|
+ const { x, y } = this.pointerToWorldPoint(event);
|
|
|
|
|
+ if (this.state === 'win') {
|
|
|
|
|
+ if (this.pointInRect(x, y, this.outcomeNextRect())) {
|
|
|
|
|
+ this.state = 'next';
|
|
|
|
|
+ this.clearMobileControls();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.pointInRect(x, y, this.outcomeRestartRect())) {
|
|
|
|
|
+ this.reset();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ((this.state === 'lose' || this.state === 'next') && this.pointInRect(x, y, this.outcomeSingleRestartRect())) {
|
|
|
|
|
+ this.reset();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private pointInRect(x: number, y: number, rect: UiRect) {
|
|
|
|
|
+ return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private setMobileDirectionFromTouchPoint(event: EventTouch | EventMouse) {
|
|
private setMobileDirectionFromTouchPoint(event: EventTouch | EventMouse) {
|
|
|
this.clearMobileMove();
|
|
this.clearMobileMove();
|
|
|
const action = this.resolveDirectionalButtonFromPoints(this.pointerWorldPointCandidates(event));
|
|
const action = this.resolveDirectionalButtonFromPoints(this.pointerWorldPointCandidates(event));
|
|
@@ -1415,6 +1539,7 @@ if (!this.backgroundSprite) return;
|
|
|
private syncWeaponArt() {
|
|
private syncWeaponArt() {
|
|
|
if (this.weaponSprite) this.weaponSprite.node.active = false;
|
|
if (this.weaponSprite) this.weaponSprite.node.active = false;
|
|
|
const ids = Object.keys(WEAPONS) as WeaponId[];
|
|
const ids = Object.keys(WEAPONS) as WeaponId[];
|
|
|
|
|
+ const panel = this.scoreWeaponPanelRect();
|
|
|
for (let index = 0; index < ids.length; index++) {
|
|
for (let index = 0; index < ids.length; index++) {
|
|
|
const id = ids[index];
|
|
const id = ids[index];
|
|
|
const frame = this.uiFrames.get(`weapon_${id}`);
|
|
const frame = this.uiFrames.get(`weapon_${id}`);
|
|
@@ -1426,7 +1551,7 @@ if (!this.backgroundSprite) return;
|
|
|
}
|
|
}
|
|
|
const active = id === this.weapon;
|
|
const active = id === this.weapon;
|
|
|
sprite.spriteFrame = frame;
|
|
sprite.spriteFrame = frame;
|
|
|
- sprite.node.setPosition(this.visibleWorldWidth() / 2 - U(184) + index * U(67), H / 2 - U(110) + (active ? 3 : 0), 20);
|
|
|
|
|
|
|
+ sprite.node.setPosition(panel.x + U(68) + index * U(64), panel.y + U(36) + (active ? 3 : 0), 20);
|
|
|
sprite.node.setScale(new Vec3(active ? US(0.146) : US(0.125), active ? US(0.146) : US(0.125), 1));
|
|
sprite.node.setScale(new Vec3(active ? US(0.146) : US(0.125), active ? US(0.146) : US(0.125), 1));
|
|
|
sprite.node.active = this.state === 'playing';
|
|
sprite.node.active = this.state === 'playing';
|
|
|
}
|
|
}
|
|
@@ -1470,7 +1595,7 @@ if (!this.backgroundSprite) return;
|
|
|
if (enemy.type === 'spore') return this.sporeFrames[Math.floor(this.elapsed * 7) % 3];
|
|
if (enemy.type === 'spore') return this.sporeFrames[Math.floor(this.elapsed * 7) % 3];
|
|
|
if (enemy.type === 'wing') return this.wingFrames[Math.floor(this.elapsed * 10) % 3];
|
|
if (enemy.type === 'wing') return this.wingFrames[Math.floor(this.elapsed * 10) % 3];
|
|
|
if (enemy.type === 'turret') return this.turretFrames[enemy.cd > 0.82 ? 2 : Math.floor(this.elapsed * 2) % 2];
|
|
if (enemy.type === 'turret') return this.turretFrames[enemy.cd > 0.82 ? 2 : Math.floor(this.elapsed * 2) % 2];
|
|
|
- if (enemy.hp < enemy.maxHp * 0.33) return this.bossFrames[3];
|
|
|
|
|
|
|
+ if (enemy.hp <= enemy.maxHp * BOSS_ENRAGE_RATIO) return this.bossFrames[3];
|
|
|
if (enemy.cd > 0.72) return this.bossFrames[2];
|
|
if (enemy.cd > 0.72) return this.bossFrames[2];
|
|
|
return this.bossFrames[Math.floor(this.elapsed * 2) % 2];
|
|
return this.bossFrames[Math.floor(this.elapsed * 2) % 2];
|
|
|
}
|
|
}
|
|
@@ -1529,33 +1654,89 @@ if (!this.backgroundSprite) return;
|
|
|
g.fillRect(-330, -158, 660, 6);
|
|
g.fillRect(-330, -158, 660, 6);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private syncOutcomeButtons() {
|
|
|
|
|
+ if (!this.outcomeButtonGraphics) return;
|
|
|
|
|
+ const g = this.outcomeButtonGraphics;
|
|
|
|
|
+ g.clear();
|
|
|
|
|
+ const visible = this.isOutcomeState();
|
|
|
|
|
+ this.outcomeButtonLayer.active = visible;
|
|
|
|
|
+ if (!visible) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (this.state === 'win') {
|
|
|
|
|
+ this.drawOutcomeButton(g, this.outcomeNextRect(), new Color(255, 211, 88, 238), new Color(255, 248, 190, 255), new Color(24, 136, 169, 210));
|
|
|
|
|
+ this.drawOutcomeButton(g, this.outcomeRestartRect(), new Color(17, 48, 66, 238), new Color(86, 230, 255, 235), new Color(255, 185, 82, 215));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const restartColor = this.state === 'lose'
|
|
|
|
|
+ ? new Color(116, 25, 31, 238)
|
|
|
|
|
+ : new Color(17, 48, 66, 238);
|
|
|
|
|
+ const accentColor = this.state === 'lose'
|
|
|
|
|
+ ? new Color(255, 86, 72, 235)
|
|
|
|
|
+ : new Color(255, 185, 82, 215);
|
|
|
|
|
+ this.drawOutcomeButton(g, this.outcomeSingleRestartRect(), restartColor, new Color(255, 226, 205, 235), accentColor);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private drawOutcomeButton(g: Graphics, rect: UiRect, fill: Color, stroke: Color, accent: Color) {
|
|
|
|
|
+ g.fillColor = fill;
|
|
|
|
|
+ g.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
|
|
|
+ g.fillColor = accent;
|
|
|
|
|
+ g.fillRect(rect.x, rect.y, U(7), rect.height);
|
|
|
|
|
+ g.fillRect(rect.x + rect.width - U(7), rect.y, U(7), rect.height);
|
|
|
|
|
+ g.strokeColor = stroke;
|
|
|
|
|
+ g.lineWidth = 3;
|
|
|
|
|
+ g.rect(rect.x, rect.y, rect.width, rect.height);
|
|
|
|
|
+ g.stroke();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private outcomeNextRect(): UiRect {
|
|
|
|
|
+ return { x: U(-226), y: U(-130), width: U(196), height: U(58) };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private outcomeRestartRect(): UiRect {
|
|
|
|
|
+ return { x: U(30), y: U(-130), width: U(196), height: U(58) };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private outcomeSingleRestartRect(): UiRect {
|
|
|
|
|
+ return { x: U(-118), y: U(-130), width: U(236), height: U(58) };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private syncOutcomeText() {
|
|
private syncOutcomeText() {
|
|
|
const visible = this.isOutcomeState();
|
|
const visible = this.isOutcomeState();
|
|
|
|
|
+ const win = this.state === 'win';
|
|
|
this.outcomeTitle.node.active = visible;
|
|
this.outcomeTitle.node.active = visible;
|
|
|
this.outcomeStats.node.active = visible;
|
|
this.outcomeStats.node.active = visible;
|
|
|
- this.outcomeAction.node.active = visible;
|
|
|
|
|
|
|
+ this.outcomeAction.node.active = visible && !win;
|
|
|
|
|
+ this.outcomeNextAction.node.active = visible && win;
|
|
|
|
|
+ this.outcomeRestartAction.node.active = visible && win;
|
|
|
if (!visible) return;
|
|
if (!visible) return;
|
|
|
if (this.state === 'win') {
|
|
if (this.state === 'win') {
|
|
|
- this.outcomeTitle.string = 'MISSION CLEAR';
|
|
|
|
|
- this.outcomeStats.string = `SCORE ${this.score.toString().padStart(5, '0')} HP ${Math.max(0, this.player.hp)}/6`;
|
|
|
|
|
- this.outcomeAction.string = 'TAP TO RESTART';
|
|
|
|
|
|
|
+ this.outcomeTitle.string = '通关成功';
|
|
|
|
|
+ this.outcomeStats.string = `得分 ${this.score.toString().padStart(5, '0')} 生命 ${Math.max(0, this.player.hp)}/6`;
|
|
|
|
|
+ this.outcomeAction.string = '';
|
|
|
|
|
+ this.outcomeNextAction.string = '选择下一关';
|
|
|
|
|
+ this.outcomeRestartAction.string = '重新开始';
|
|
|
|
|
+ this.outcomeNextAction.color = new Color(22, 31, 36, 255);
|
|
|
|
|
+ this.outcomeRestartAction.color = new Color(232, 252, 255, 255);
|
|
|
this.outcomeTitle.color = new Color(255, 232, 126, 255);
|
|
this.outcomeTitle.color = new Color(255, 232, 126, 255);
|
|
|
this.outcomeStats.color = new Color(225, 250, 255, 255);
|
|
this.outcomeStats.color = new Color(225, 250, 255, 255);
|
|
|
- this.outcomeAction.color = new Color(14, 29, 36, 255);
|
|
|
|
|
|
|
+ this.outcomeAction.color = new Color(255, 238, 154, 255);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
if (this.state === 'lose') {
|
|
if (this.state === 'lose') {
|
|
|
- this.outcomeTitle.string = 'MISSION FAILED';
|
|
|
|
|
- this.outcomeStats.string = `SCORE ${this.score.toString().padStart(5, '0')}`;
|
|
|
|
|
- this.outcomeAction.string = 'TAP TO RESTART';
|
|
|
|
|
|
|
+ this.outcomeTitle.string = '挑战失败';
|
|
|
|
|
+ this.outcomeStats.string = `得分 ${this.score.toString().padStart(5, '0')}`;
|
|
|
|
|
+ this.outcomeAction.string = '重新开始';
|
|
|
|
|
+ this.outcomeAction.node.setPosition(0, U(-100), 28);
|
|
|
this.outcomeTitle.color = new Color(255, 92, 82, 255);
|
|
this.outcomeTitle.color = new Color(255, 92, 82, 255);
|
|
|
this.outcomeStats.color = new Color(225, 250, 255, 255);
|
|
this.outcomeStats.color = new Color(225, 250, 255, 255);
|
|
|
this.outcomeAction.color = new Color(255, 226, 205, 255);
|
|
this.outcomeAction.color = new Color(255, 226, 205, 255);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- this.outcomeTitle.string = 'READY';
|
|
|
|
|
- this.outcomeStats.string = 'LEVEL 1';
|
|
|
|
|
- this.outcomeAction.string = 'TAP TO RESTART';
|
|
|
|
|
|
|
+ this.outcomeTitle.string = '下一关筹备中';
|
|
|
|
|
+ this.outcomeStats.string = '请先返回第一关继续体验';
|
|
|
|
|
+ this.outcomeAction.string = '重新开始';
|
|
|
|
|
+ this.outcomeAction.node.setPosition(0, U(-100), 28);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private isOutcomeState() {
|
|
private isOutcomeState() {
|
|
@@ -1563,7 +1744,7 @@ if (!this.backgroundSprite) return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private drawHud() {
|
|
private drawHud() {
|
|
|
- let status = this.boss && this.boss.hp > 0 ? `BOSS ${Math.ceil(this.boss.hp)}/180` : '';
|
|
|
|
|
|
|
+ let status = this.boss && this.boss.hp > 0 ? `BOSS ${Math.ceil(this.boss.hp)}/${this.boss.maxHp}` : '';
|
|
|
if (this.isOutcomeState()) status = '';
|
|
if (this.isOutcomeState()) status = '';
|
|
|
this.hud.string = status;
|
|
this.hud.string = status;
|
|
|
this.hudNode.setPosition(0, this.state === 'lose' ? -40 : H / 2 - U(34), 12);
|
|
this.hudNode.setPosition(0, this.state === 'lose' ? -40 : H / 2 - U(34), 12);
|
|
@@ -1572,9 +1753,7 @@ if (!this.backgroundSprite) return;
|
|
|
this.hud.color = this.state === 'lose' ? new Color(255, 240, 180, 255) : new Color(255, 255, 255, 255);
|
|
this.hud.color = this.state === 'lose' ? new Color(255, 240, 180, 255) : new Color(255, 255, 255, 255);
|
|
|
this.scoreHud.string = this.state === 'playing' ? `得分 ${this.score.toString().padStart(5, '0')}` : '';
|
|
this.scoreHud.string = this.state === 'playing' ? `得分 ${this.score.toString().padStart(5, '0')}` : '';
|
|
|
this.hpHud.string = this.state === 'playing' ? `生命 ${Math.max(0, this.player.hp)}/6` : '';
|
|
this.hpHud.string = this.state === 'playing' ? `生命 ${Math.max(0, this.player.hp)}/6` : '';
|
|
|
- this.weaponHud.string = this.state === 'playing'
|
|
|
|
|
- ? '按Q切换'
|
|
|
|
|
- : '';
|
|
|
|
|
|
|
+ this.weaponHud.string = '';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private hit(ax: number, ay: number, bx: number, by: number, radius: number) {
|
|
private hit(ax: number, ay: number, bx: number, by: number, radius: number) {
|
|
@@ -1595,16 +1774,12 @@ if (!this.backgroundSprite) return;
|
|
|
|
|
|
|
|
private onTouchStart(event: EventTouch) {
|
|
private onTouchStart(event: EventTouch) {
|
|
|
this.unlockAudio();
|
|
this.unlockAudio();
|
|
|
- const { x, y } = this.touchToGamePoint(event);
|
|
|
|
|
if (this.state === 'playing') {
|
|
if (this.state === 'playing') {
|
|
|
this.handlePointerStart(this.pointerIdFromTouch(event), event);
|
|
this.handlePointerStart(this.pointerIdFromTouch(event), event);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
if (!this.isOutcomeState()) return;
|
|
if (!this.isOutcomeState()) return;
|
|
|
- const inOutcome = Math.abs(x) < U(410) && y > U(-240) && y < U(190);
|
|
|
|
|
- if (inOutcome) {
|
|
|
|
|
- this.reset();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (this.handleOutcomeAction(event)) this.stopTouchEvent(event);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private onTouchMove(event: EventTouch) {
|
|
private onTouchMove(event: EventTouch) {
|
|
@@ -1673,9 +1848,9 @@ if (!this.backgroundSprite) return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private resolveDirectionalButtonFromPoints(points: Array<{ x: number; y: number }>): Extract<MobileAction, 'left' | 'right' | 'up'> | null {
|
|
private resolveDirectionalButtonFromPoints(points: Array<{ x: number; y: number }>): Extract<MobileAction, 'left' | 'right' | 'up'> | null {
|
|
|
- if (this.closestPointInCircle(points, this.jumpButtonCenter(), U(58))) return 'up';
|
|
|
|
|
- if (this.closestPointInCircle(points, this.leftButtonCenter(), U(58))) return 'left';
|
|
|
|
|
- if (this.closestPointInCircle(points, this.rightButtonCenter(), U(58))) return 'right';
|
|
|
|
|
|
|
+ if (this.closestPointInCircle(points, this.jumpButtonCenter(), U(64))) return 'up';
|
|
|
|
|
+ if (this.closestPointInCircle(points, this.leftButtonCenter(), U(64))) return 'left';
|
|
|
|
|
+ if (this.closestPointInCircle(points, this.rightButtonCenter(), U(64))) return 'right';
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1791,17 +1966,17 @@ if (!this.backgroundSprite) return;
|
|
|
|
|
|
|
|
private leftButtonCenter() {
|
|
private leftButtonCenter() {
|
|
|
const half = this.visibleWorldWidth() / 2;
|
|
const half = this.visibleWorldWidth() / 2;
|
|
|
- return { x: -half + U(96), y: U(-188) };
|
|
|
|
|
|
|
+ return { x: -half + U(72), y: U(-208) };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private rightButtonCenter() {
|
|
private rightButtonCenter() {
|
|
|
const half = this.visibleWorldWidth() / 2;
|
|
const half = this.visibleWorldWidth() / 2;
|
|
|
- return { x: -half + U(190), y: U(-188) };
|
|
|
|
|
|
|
+ return { x: -half + U(214), y: U(-208) };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private jumpButtonCenter() {
|
|
private jumpButtonCenter() {
|
|
|
const half = this.visibleWorldWidth() / 2;
|
|
const half = this.visibleWorldWidth() / 2;
|
|
|
- return { x: -half + U(143), y: U(-88) };
|
|
|
|
|
|
|
+ return { x: -half + U(143), y: U(-60) };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private fireButtonCenter() {
|
|
private fireButtonCenter() {
|