消消方块阵换皮表情
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

294 lines
9.3 KiB

import { _decorator, AssetManager, assetManager, AudioClip, AudioSource, CCInteger, Collider2D, Component, find, Node, Vec3 } from 'cc';
import { SuDaceMap } from './SuDaceMap';
import { SuDaceEnemy } from './SuDaceEnemy';
const { ccclass, property } = _decorator;
@ccclass('SuDace')
export class SuDace extends Component {
@property
autoSpawn = true;
/** 5 波触发时间(秒):A 波建议填 0 */
@property({ type: [CCInteger], displayName: "波次时间" })
waveTimes: number[] = [0, 4, 9, 15, 22];
/** 5 波出怪数:对应 A,C,E,G,I */
@property({ type: [CCInteger], displayName: "波次出怪数" })
waveCounts: number[] = [6, 8, 10, 12, 15];
@property({ displayName: "怪物血量" })
enemyHp = 3;
@property({ displayName: "怪物移动速度" })
enemyMoveSpeed = 100;
@property({ displayName: "武器攻击力" })
weaponAttack = 1;
static instance: SuDace = null;
/** 玩家阵亡后设为 true,停止刷怪等逻辑 */
gameFailed = false;
map: SuDaceMap = null;
enemyPool: Node[] = [];
bulletPool: Node[] = [];
stonePool: Node[] = [];
private elapsed = 0;
private nextWaveIndex = 0;
private spawnedCount = 0;
private killedCount = 0;
public levelCleared = false;
btnBack: Node = null;
audioSource: AudioSource = null;
audioKeys: string[] = ["开宝箱", "受击", "攻击"];
audios: Map<string, AudioClip> = new Map<string, AudioClip>();
protected onLoad(): void {
SuDace.instance = this;
this.map = find("map", this.node).getComponent(SuDaceMap);
this.btnBack = find("btnBack", this.node);
this.audioSource = this.node.getComponent(AudioSource);
if (!this.audioSource) {
this.audioSource = this.node.addComponent(AudioSource);
}
this.loadAudio();
}
protected onEnable(): void {
this.btnBack.on(Node.EventType.TOUCH_END, this.click_btnBack, this);
}
protected onDisable(): void {
this.btnBack.off(Node.EventType.TOUCH_END, this.click_btnBack, this);
}
click_btnBack(): void {
this.node.emit("_onMiniGameClose", false);
}
protected onDestroy(): void {
// 只清理当前实例,避免“旧实例延迟销毁”把新实例误清空
if (SuDace.instance === this) {
SuDace.instance = null;
}
}
protected start(): void {
this.restartStage();
}
onPlayerDefeated(): void {
if (this.gameFailed) return;
this.gameFailed = true;
this.node.emit("_onMiniGameEnd", false);
console.log('[SuDace] game over — player defeated');
}
protected update(dt: number): void {
if (this.gameFailed) return;
if (!SuDace.instance) {
return;
}
if (!this.autoSpawn || !this.map?.enemyNode) return;
this.elapsed += dt;
// 按波次时间触发,可连续 5 波;时间和数量都允许为 0
while (this.nextWaveIndex < this.getWaveLimit()) {
const triggerTime = this.getWaveTime(this.nextWaveIndex);
if (this.elapsed < triggerTime) break;
const count = this.getWaveCount(this.nextWaveIndex);
if (count > 0) {
for (let i = 0; i < count; i++) {
const m = this.map.generateMonster();
if (!m) {
console.warn('[SuDace] generateMonster returned null.');
break;
}
this.setupEnemy(m);
this.spawnedCount++;
}
}
this.nextWaveIndex++;
}
// 通关:5 波都触发完,且所有已生成怪物都被击杀
if (!this.levelCleared &&
this.nextWaveIndex >= this.getWaveLimit() &&
this.killedCount >= this.spawnedCount &&
this.map.enemyNode.children.length === 0) {
this.levelCleared = true;
console.log('[SuDace] stage clear');
this.node.emit("_onMiniGameEnd", true);
}
}
private restartStage(): void {
this.elapsed = 0;
this.nextWaveIndex = 0;
this.spawnedCount = 0;
this.killedCount = 0;
this.levelCleared = false;
this.gameFailed = false;
this.map?.player?.resetForStage();
console.log(`[SuDace] stage start, totalSpawn=${this.getTotalPlannedCount()}`);
}
private getWaveLimit(): number {
// 最多 5 波
const n = Math.max(this.waveTimes.length, this.waveCounts.length);
return Math.min(5, n);
}
private getWaveTime(index: number): number {
const t = this.waveTimes[index] ?? 0;
return Math.max(0, t);
}
private getWaveCount(index: number): number {
const c = this.waveCounts[index] ?? 0;
return Math.max(0, Math.floor(c));
}
private getTotalPlannedCount(): number {
let sum = 0;
const n = this.getWaveLimit();
for (let i = 0; i < n; i++) sum += this.getWaveCount(i);
return sum;
}
private setupEnemy(monster: Node): void {
const enemy = monster.getComponent(SuDaceEnemy) ?? monster.addComponent(SuDaceEnemy);
enemy.init(1, this.enemyHp, this.enemyMoveSpeed);
}
/**回收怪物 */
recycleMonster(monster: Node): void {
if (!monster?.isValid) return;
// 可能从碰撞回调触发:延迟到下一帧回收,避免 "Can not active RigidBody in contact listener."
const col = monster.getComponent(Collider2D) ?? monster.getComponentInChildren(Collider2D);
if (col) col.enabled = false;
this.scheduleOnce(() => {
if (!monster?.isValid) return;
monster.active = false;
monster.setParent(null);
monster.setPosition(0, 0, 0);
monster.destroy();
this.killedCount++;
}, 0);
}
/**回收子弹 */
recycleBullet(bullet: Node): void {
if (!bullet?.isValid) return;
const col = bullet.getComponent(Collider2D) ?? bullet.getComponentInChildren(Collider2D);
if (col) col.enabled = false;
this.scheduleOnce(() => {
if (!bullet?.isValid) return;
bullet.active = false;
bullet.setParent(null);
bullet.setPosition(0, 0, 0);
// this.bulletPool.push(bullet);
bullet.destroy();
}, 0);
}
/**回收落石 */
recycleStone(stone: Node): void {
if (!stone?.isValid) return;
const col = stone.getComponent(Collider2D) ?? stone.getComponentInChildren(Collider2D);
if (col) col.enabled = false;
this.scheduleOnce(() => {
if (!stone?.isValid) return;
stone.active = false;
stone.setParent(null);
stone.setPosition(0, 0, 0);
stone.destroy();
// this.stonePool.push(stone);
}, 0);
}
/**获取一个离玩家最近的怪物 */
getNearestMonster(): Node {
let nearestMonster: Node = null;
let nearestDistance: number = 9999999999;
for (let monster of this.map.enemyNode.children) {
if (!monster.active) continue;
if (this.map.player.node.scale.x < 0) {
if (monster.worldPosition.x > this.map.player.node.worldPosition.x) continue;
} else {
if (monster.worldPosition.x < this.map.player.node.worldPosition.x) continue;
}
let distance = Vec3.distance(monster.worldPosition, this.map.player.node.worldPosition);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestMonster = monster;
}
}
return nearestMonster as Node;
}
playAtk(): void {
if (!this.audios.has("攻击")) return;
this.audioSource.playOneShot(this.audios.get("攻击"));
}
playHit(): void {
if (!this.audios.has("受击")) return;
this.audioSource.playOneShot(this.audios.get("受击"));
}
playOpen(): void {
if (!this.audios.has("开宝箱")) return;
this.audioSource.playOneShot(this.audios.get("开宝箱"));
}
loadAudio(): void {
let bundleName = "sound/";
let path = "sound/";
this.loadbundle(bundleName, (bundle) => {
for (let audio of this.audioKeys) {
this.loadRes(bundle, path + audio, (asset) => {
this.audios.set(audio, asset);
asset.addRef();
});
}
});
}
loadbundle(name, callback: Function) {
let bundle = assetManager.getBundle("soudace");
if (!bundle) {
assetManager.loadBundle("soudace", (err, bundle) => {
if (err) {
console.error(err);
this.loadbundle(name, callback);
} else {
callback && callback(bundle);
}
});
} else {
callback && callback(bundle);
}
}
loadRes(bundle: AssetManager.Bundle, name: string, callback: Function) {
bundle.load(name, AudioClip, (err, asset) => {
if (err) {
console.error(err);
this.loadRes(bundle, name, callback);
} else {
callback && callback(asset);
}
});
}
}