消消方块阵换皮表情
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.

832 lines
31 KiB

1 week ago
import { _decorator, Component, Node, Prefab, instantiate, director, Vec3, Label, UITransform, v3, Vec2, tween, BoxCollider2D, easing, isValid, Collider2D } from 'cc';
import { BattleType, ChapterDifficulty } from '../../manager/ChapterDataManager';
import { ConstEnum, ConstManager } from '../../manager/ConstManager';
import { Monster, MonsterState } from './Monster';
import { MapAgent } from './MapAgent';
import { ConstNumType } from '../../game/ConfigProjectData';
import { GEvent } from 'db://assets/mx/module/event/GEvent';
import { BattleResLoader } from '../../game/BattleResLoader';
const { ccclass, property } = _decorator;
interface MonsterConfig {
monsterId: number;
totalCount: number;
wave: number;
}
interface SpawnEvent {
time: number;
monsterId: number;
monsterGroup: number;
spawnPoint: number; // 出怪口编号
}
interface PendingSpawn {
monsterId: number;
totalCount: number;
monsterGroup: number;
spawnPointRange: [number, number];
}
interface SpecialSpawnEvent {
time: number;
spawnPoint: number;
monsterId: number;
}
@ccclass('MonsterSpawner')
export class MonsterSpawner extends Component {
// @property({ type: [Prefab] })
// private monsterPrefabs: Prefab[] = []; // 怪物预制体数组
tempMonsterParent: Node = null;
@property(Label)
debugLabel: Label = null;
private spawnEvents: SpawnEvent[] = [];
private specialSpawnEvents: SpecialSpawnEvent[] = [];
private nextEventIndex: number = 0;
private nextSpecialEventIndex: number = 0;
//每次出怪时间间隔一秒
private createMonsterTimeInterval: number = 1;
/** 每帧最多生成多少只怪,防止一帧爆量卡顿 */
/** 每帧最多实例化怪物数,避免出怪帧尖刺(线上卡顿率 11%→14%) */
private spawnBudgetPerFrame: number = 4;
/** 按波暂存(monster1 / monster2_1 等会合并到同一波) */
private pendingByWave: PendingSpawn[][] = [];
protected onEnable(): void {
GEvent.Ins.on(GEvent.InfiniteModeWaveReset, this.resetInfiniteModeWave, this);
}
protected onDisable(): void {
GEvent.Ins.off(GEvent.InfiniteModeWaveReset, this.resetInfiniteModeWave, this);
}
update(dt: number) {
if (!gg.game.isBattleContextActive()) return;
if (!gg.game.CurentBattle.canUpdateFrameMonster()) return;
//检查生成怪物CurentWaveTime
const t = gg.game.CurentBattle.CurentWaveTime;
let budget = this.spawnBudgetPerFrame;
while (
budget-- > 0 &&
this.nextEventIndex < this.spawnEvents.length &&
this.spawnEvents[this.nextEventIndex].time <= t
) {
//console.log('生成怪物', this.spawnEvents[this.nextEventIndex])
const event = this.spawnEvents[this.nextEventIndex++];
//无限模式下,最后一波怪物生成完后,重置怪物生成事件索引
// console.log('生成怪物', event.monsterId, event.spawnPoint, event.monsterGroup)
gg.game.CurentBattle.spawnMonster(event.monsterId, event.spawnPoint, event.monsterGroup);
}
if (director.getTotalFrames() % 20 === 0) {
this.sortMonstersByYDis()
}
}
/**
*
*/
public initMonsterConfigs() {
this.createMonsterTimeInterval = gg.data.table.getConst(ConstNumType.DefaultMonsterInterval);
// 章节配置
let config: ITableChapter = gg.game.CurentBattle.CurentConfigChapter2 || gg.game.CurentBattle.CurentConfigChapter;
if(gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Ya){
// 解析monster1配置 (1-3出怪口随机)
this.parseSpawnPointConfig(config.monster1, [4, 6]);
// 解析monster2_1配置 (4-6出怪口)
this.parseSpawnPointConfig(config.monster2_1, [4, 6]);
}else{
// 解析monster1配置 (1-3出怪口随机)
this.parseSpawnPointConfig(config.monster1, [1, 3]);
// 解析monster2_1配置 (4-6出怪口)
this.parseSpawnPointConfig(config.monster2_1, [4, 6]);
}
}
/** 指定波次(0-based)涉及的怪物 id */
getMonsterIdsForWave(waveIdx: number): number[] {
const pendings = this.pendingByWave[waveIdx];
if (!pendings?.length) return [];
const ids = new Set<number>();
for (let i = 0; i < pendings.length; i++) {
const id = pendings[i].monsterId;
if (Number.isFinite(id)) ids.add(id);
}
return Array.from(ids);
}
private _collectMonsterModels(monsterIds: number[]): string[] {
const models: string[] = [];
for (let i = 0; i < monsterIds.length; i++) {
const data = gg.data.project.getMonsterDataByID(monsterIds[i]);
const model = data?.model;
if (typeof model === 'string' && model.length > 0) {
models.push(model);
}
}
return models;
}
/** 按波次预加载怪物 prefab(增量,不重复加载) */
preloadWaveMonsters(waveIdx: number): void {
if (waveIdx < 0 || !this.pendingByWave?.length) return;
const monsterIds = this.getMonsterIdsForWave(waveIdx);
if (!monsterIds.length) return;
const models = this._collectMonsterModels(monsterIds);
if (models.length > 0) {
BattleResLoader.preloadMonsterModels(models);
}
}
// 解析出怪口配置
parseSpawnPointConfig(configString: string, spawnPointRange: [number, number]) {
if (!configString) return;
const segments = configString.split('|');
const monsterNumScale = gg.game.monsterNumScale > 0 ? gg.game.monsterNumScale : 1;
for (let waveIdx = 0; waveIdx < segments.length; waveIdx++) {
const segment = segments[waveIdx];
if (!segment) continue;
if (!this.pendingByWave[waveIdx]) this.pendingByWave[waveIdx] = [];
const configStrings = segment.split(';');
for (const configStr of configStrings) {
if (!configStr) continue;
const parts = configStr.split(',').map(part => parseInt(part.trim(), 10));
if (parts.length !== 3) continue;
const monsterId = parts[0];
const totalCountRaw = parts[1];
let monsterGroup = parts[2];
if (!Number.isFinite(monsterId) || !Number.isFinite(totalCountRaw)) continue;
const totalCount = Math.max(0, Math.round(totalCountRaw * monsterNumScale));
if (totalCount <= 0) continue;
this.pendingByWave[waveIdx].push({
monsterId,
totalCount,
monsterGroup,
spawnPointRange,
});
}
}
// 将“按波暂存”的配置展开为按时间递增的 SpawnEvent(1 秒 1 只)
this.rebuildSpawnEventsFromPending();
}
/** 计算指定波次(0-based)在刷怪时间轴上的起始时间 */
private calcWaveStartTime(waveIdx: number): number {
let waveStartTime = 0;
const end = Math.min(waveIdx, this.pendingByWave.length);
for (let i = 0; i < end; i++) {
waveStartTime += this.calcWaveDuration(i);
}
return waveStartTime;
}
/** 单波刷怪时长(与 rebuildSpawnEventsFromPending 中 lane 并行规则一致) */
private calcWaveDuration(waveIdx: number): number {
const pendings = this.pendingByWave[waveIdx];
if (!pendings?.length) return 0;
const lanes = new Map<string, number>();
for (const p of pendings) {
const key = `${p.spawnPointRange[0]}-${p.spawnPointRange[1]}`;
lanes.set(key, (lanes.get(key) ?? 0) + p.totalCount);
}
let maxLaneLen = 0;
for (const len of lanes.values()) {
if (len > maxLaneLen) maxLaneLen = len;
}
return maxLaneLen * this.createMonsterTimeInterval;
}
/**
* 0-based
*/
seekToWave(waveIdx: number) {
if (waveIdx < 0) return;
const waveStartTime = this.calcWaveStartTime(waveIdx);
const battle = gg.game.CurentBattle;
if (battle) {
battle.CurentWaveTime = waveStartTime;
}
while (
this.nextEventIndex < this.spawnEvents.length
&& this.spawnEvents[this.nextEventIndex].time < waveStartTime
) {
this.nextEventIndex++;
}
console.log(
'[jiujiuwoya] seekToWave',
waveIdx + 1,
'time=',
waveStartTime,
'nextEventIndex=',
this.nextEventIndex,
);
}
private rebuildSpawnEventsFromPending() {
this.spawnEvents.length = 0;
this.nextEventIndex = 0;
// 波次时间不清零:每一波有自己的起始时间 waveStartTime。
// 同一波内,不同出怪口范围(monster1 / monster2_1 等)并行各自 1 秒 1 只。
// 下一波从上一波“最长 lane 完成的时间”开始,保证并行波次不交叉。
let waveStartTime = 0;
for (let waveIdx = 0; waveIdx < this.pendingByWave.length; waveIdx++) {
const pendings = this.pendingByWave[waveIdx];
if (!pendings || pendings.length === 0) continue;
// 按出怪口范围分 lane(1-3 一条、4-6 一条…),lane 内按顺序刷;不同 lane 并行
const lanes = new Map<string, { range: [number, number]; items: PendingSpawn[]; laneLen: number }>();
for (const p of pendings) {
const key = `${p.spawnPointRange[0]}-${p.spawnPointRange[1]}`;
let lane = lanes.get(key);
if (!lane) {
lane = { range: p.spawnPointRange, items: [], laneLen: 0 };
lanes.set(key, lane);
}
lane.items.push(p);
lane.laneLen += p.totalCount;
}
let maxLaneLen = 0;
for (const lane of lanes.values()) {
if (lane.laneLen > maxLaneLen) maxLaneLen = lane.laneLen;
let laneCursor = 0;
const [minSp, maxSp] = lane.range;
const span = Math.max(0, maxSp - minSp);
for (const p of lane.items) {
for (let i = 0; i < p.totalCount; i++) {
const spawnPoint = minSp + Math.floor(Math.random() * (span + 1));
this.spawnEvents.push({
time: waveStartTime + laneCursor,
monsterId: p.monsterId,
monsterGroup: p.monsterGroup,
spawnPoint,
});
laneCursor += this.createMonsterTimeInterval;
}
}
}
// 同一波生成完后,将下一波起点推进到本波最长 lane 的结束时间
waveStartTime += maxLaneLen * this.createMonsterTimeInterval;
}
// 多 lane 并行会产生同 time 多事件,统一按时间排序,确保 update() while 可顺序消费
this.spawnEvents.sort((a, b) => a.time - b.time);
// console.log('this.spawnEvents++++++++===', this.spawnEvents, this.createMonsterTimeInterval)
}
// 解析特殊出怪口配置
parseSpecialSpawnConfig(configString: string) {
if (configString == '') return;
//|91,2,301010|101,5,301011|111,2,301012|111,1,501012|121,5,301013|131,2,301014|141,5,301015|151,2,301016|161,5,301017|171,2,301018|181,5,301019|191,2,301020|201,5,301021|211,2,301022|221,5,301023|231,6,601024
const events = configString.split('|');
for (const eventStr of events) {
const parts = eventStr.split(',').map(part => Number(part.trim()));
if (parts.length !== 3) continue;
const [time, spawnPoint, monsterId] = parts;
if (gg.game.CurentSelectBattleType == BattleType.BoxMode
|| gg.game.CurentSelectBattleType == BattleType.GachaMode
) {
if (monsterId == 30011 || monsterId == 40011) {
continue;
}
}
this.specialSpawnEvents.push({
time,
spawnPoint,
monsterId
});
}
// 按时间排序特殊事件
this.specialSpawnEvents.sort((a, b) => a.time - b.time);
}
// 生成所有怪物生成事件
generateSpawnEvents() {
// 按时间排序所有事件
this.spawnEvents.sort((a, b) => a.time - b.time);
}
// 开始生成怪物
startSpawning() {
// this.currentTime = 0;
this.nextEventIndex = 0;
this.nextSpecialEventIndex = 0;
if (this.debugLabel) {
this.debugLabel.string = "开始生成怪物...";
}
}
//刷新所有怪物路径
refreshAllMonsterPath() {
// for (let value of this.node.children) {
// value.getComponent(MapAgent)?.updatePath()
// }
}
/**暂停所有怪物行为 */
pauseAllMonsterAction() {
for (let value of this.node.children) {
let a = value.getComponent(MapAgent)
if (a) a.CanMove = false;
}
}
/**恢复所有怪物行为 */
resumeAllMonsterAction() {
for (let value of this.node.children) {
let a = value.getComponent(MapAgent)
if (a) a.CanMove = (value.getComponent(Monster).Data.id != 40041) &&
(value.getComponent(Monster).state === MonsterState.MOVING_FORWARD || value.getComponent(Monster).state === MonsterState.PATHFINDING);
}
}
getAllMonster() {
const n = this.node;
if (!n?.isValid) return [];
return n.children;
}
getAllMonsterCount() {
const n = this.node;
if (!n?.isValid) return 0;
return n.children.length;
}
/** 所有刷怪事件是否已处理完成 */
isAllSpawnFinished() {
return this.nextEventIndex >= this.spawnEvents.length
&& this.nextSpecialEventIndex >= this.specialSpawnEvents.length;
}
/** 获取当前场上仍会计入通关统计的怪物数量 */
getAliveCountForWin() {
const n = this.node;
if (!n?.isValid) return 0;
let count = 0;
for (const child of n.children) {
const m = child.getComponent(Monster);
if (!m || !m.Data || m.IsDead) continue;
// 与 BattleCore.monsterDie 统计条件保持一致
if (m.Data.id != 40041 && m.Data.type != 8) {
count++;
}
}
return count;
}
// 打印调试信息
printDebugInfo() {
// console.log("普通生成事件列表:", this.spawnEvents);
// console.log("特殊生成事件列表:", this.specialSpawnEvents);
if (this.debugLabel) {
let debugText = "怪物生成计划:\n";
// 显示普通生成事件
debugText += "--- 普通生成事件 ---\n";
for (const event of this.spawnEvents) {
debugText += `时间 ${event.time.toFixed(2)}: 出怪口 ${event.spawnPoint} -> 怪物 ${event.monsterId}\n`;
}
// 显示特殊生成事件
debugText += "\n--- 特殊生成事件 ---\n";
for (const event of this.specialSpawnEvents) {
debugText += `时间 ${event.time}: 出怪口 ${event.spawnPoint} -> 怪物 ${event.monsterId}\n`;
}
this.debugLabel.string = debugText;
}
}
// 重置生成系统
reset() {
// this.currentTime = 0;
this.nextEventIndex = 0;
this.nextSpecialEventIndex = 0;
this.spawnEvents = [];
this.specialSpawnEvents = [];
if (this.debugLabel) {
this.debugLabel.string = "系统已重置";
}
}
// 获取指定区域内的所有怪物(圆形范围与怪物轴对齐包围盒有重叠即算在区域内)
getMonstersInArea(center: Vec3, radius: number) {
const cx = center.x;
const cy = center.y;
const r2 = radius * radius;
return this.node.children.filter(monster => {
const pos = monster.worldPosition;
const worldScale = monster.worldScale;
let centerX = pos.x;
let centerY = pos.y;
let halfW = 0;
let halfH = 0;
const col = monster.getComponent(BoxCollider2D);
if (col) {
const absScaleX = Math.abs(worldScale.x);
const absScaleY = Math.abs(worldScale.y);
centerX += col.offset.x * absScaleX;
centerY += col.offset.y * absScaleY;
halfW = (col.size.width * absScaleX) / 2;
halfH = (col.size.height * absScaleY) / 2;
} else {
// 兜底:没有碰撞器时使用 UITransform 尺寸
const tf = monster.getComponent(UITransform);
if (tf) {
halfW = (tf.width * Math.abs(worldScale.x)) / 2;
halfH = (tf.height * Math.abs(worldScale.y)) / 2;
}
}
if (halfW <= 0 || halfH <= 0) return false;
const left = centerX - halfW;
const right = centerX + halfW;
const bottom = centerY - halfH;
const top = centerY + halfH;
const closestX = Math.max(left, Math.min(cx, right));
const closestY = Math.max(bottom, Math.min(cy, top));
const dx = cx - closestX;
const dy = cy - closestY;
return dx * dx + dy * dy <= r2;
});
}
//所有怪物回到出生点
setAllMonsterToBornPos() {
for (let value of this.node.children) {
value.getComponent(Monster).teleportToBornPos()
}
// //重新刷新路线
// this.refreshAllMonsterPath()
}
/**设置怪物透明度 */
setAllMonsterAlpha(alpha: number = 255) {
for (let value of this.node.children) {
value.opacity = alpha
}
}
/**设置怪物红月状态*/
setAllMonsterBloodMoon(show = true) {
for (let value of this.node.children) {
value.getComponent(Monster).setBloodMoon(show);
}
}
//记录摆放障碍物前的位置
recordAllMonsterBeforePos() {
for (let value of this.node.children) {
value.getComponent(Monster).recordSetObstacleBeforePos()
}
}
//还原到摆放障碍物前的位置
resetAllMonsterBeforePos() {
for (let value of this.node.children) {
value.getComponent(Monster).resetSetObstacleBeforePos()
}
}
/**对所有怪物按Y轴排序 */
sortMonstersByYDis() {
this.node.children.sort((a, b) => {
return b.worldPosition.y - a.worldPosition.y
})
}
/**清空所有怪物 */
clearAllMonster() {
for (let value of this.node.children) {
value.getComponent(Monster).clearNode()
}
}
/**获取所有怪物路径距离 */
getAllMonsterPathDis() {
let disArr = []
// for (let value of this.node.children) {
// if (value.getComponent(MapAgent)) {
// let dis = value.getComponent(MapAgent).getRemainPathDistance();
// disArr.push(dis)
// }
// }
return disArr
}
/**
*
* state ATTACKING
*/
getAllPathDisMinMonster(weaponNode: Node) {
if (!weaponNode || !weaponNode.isValid) return null;
const scored: Array<{ m: Node; dis: number; attacking: boolean }> = [];
const monsters = this.node.children;
for (let i = 0; i < monsters.length; i++) {
const mNode = monsters[i];
if (!mNode || !mNode.isValid) continue;
const mComp = mNode.getComponent(Monster);
if (!mComp || mComp.getIsDead()) continue;
const agent = mNode.getComponent(MapAgent);
if (!agent) continue;
const dis = agent.getNearestMonster(weaponNode);
if (!Number.isFinite(dis)) continue;
scored.push({
m: mNode,
dis,
attacking: mComp.state === MonsterState.ATTACKING,
});
}
if (scored.length <= 0) return null;
const attackingPool = scored.filter((s) => s.attacking);
const pool = attackingPool.length > 0 ? attackingPool : scored;
let best = pool[0];
for (let j = 1; j < pool.length; j++) {
if (pool[j].dis < best.dis) best = pool[j];
}
return best.m;
}
/**获取所有怪物路径距离最短值 */
getAllMonsterMinPath() {
let minDis = Math.min(...this.getAllMonsterPathDis())
return minDis
}
/**
*
*/
getDroneTargetMonster(droneNode: Node, radius: number) {
const all = this.node.children.slice();
if (all.length <= 0) return null;
const wallNode = gg.game.CurentBattle.WallTargetNode;
const motoNode = wallNode?.find('摩托车');
const targetPos = (motoNode && motoNode.isValid) ? motoNode.worldPosition : wallNode?.worldPosition;
if (!targetPos) return all[0] || null;
let arr = [];
for (let monster of all) {
let dis = Vec3.distance(monster.worldPosition, targetPos)
arr.push({ m: monster, d: dis });
}
arr.sort((a, b) => {
return a.d - b.d;
})
if (arr.length > 0) {
return arr[0].m;
}
return null
}
/**
*
* @param index
* @param count
*/
getLaserTargetMonster(index, count) {
let targetArr = []
for (let value of this.node.children) {
//如果不是锁定状态且在左侧
// if (!value.getComponent(Monster).getIsLockByLaser(index) && value.position.y < 500) {
// targetArr.push(value)
// }
}
let result = []; // 存储结果的数组
if (targetArr.length > 0) {
// targetArr = targetArr.sort((a, b) => {
// return Vec3.distance(a, gg.game.CurentBattle.WallTargetNode) - Vec3.distance(b, gg.game.CurentBattle.WallTargetNode)
// });
//取出前count个
result = targetArr.slice(0, count)
for (let value of result) {
value.getComponent(Monster).setIsLockByLaser(index, true)
}
}
return result
}
/**
* @param startPos
* @param count
* @param isfa
* @returns
*/
getFireworkGyroTargetMonster(startPos, count, isfa = false) {
if (this.node.children.length <= 0) {
return []
}
//返回目标
let targets: Node[] = [];
let arr = [];
for (let monster of this.node.children) {
let dis = 0;
if (startPos)
dis = Vec3.distance(monster.worldPosition, startPos)
else
dis = monster.getComponent(MapAgent).getRemainPathDistance();
arr.push({ m: monster, d: dis });
}
//排序
arr.sort((a, b) => {
if (isfa)//如果选择的是最远目标,降序排序
return b.d - a.d;
else///如果选择的是最近目标,升序排序
return a.d - b.d;
})
//找出所有已经存在的陀螺
let curentAllGyro = gg.game.CurentBattle.SkillController.node.children.filter(c => c.name == "weapon22");
for (let i = 0; i < arr.length; i++) {
//数量够了结束遍历
if (targets.length >= count) break;
//跳过已经死亡的怪物
let n: Node = arr[i].m;
let m: Monster = n.getComponent(Monster);
if (!isValid(n) || m.IsDead) continue;
//跳过已经被选中的怪物
let curentNodes = curentAllGyro.concat(targets);
//如果当前所有场上已经存在的陀螺和已经选取的目标怪物距离本怪物的距离小于100,则跳过(拉开每个陀螺的间距)
if (curentNodes.find(c => Vec3.distance(c.worldPosition, n.worldPosition) <= 100)) continue;
targets.push(n);
}
return targets
}
//获取怪物距离最近的<=n个怪物不重复,如果怪物数目不足n个,返回符合条件的怪物就行
getMonsterNearestCountPar(weaponNode: Node, count: number = 1): Node[] {
if (!weaponNode || !isValid(weaponNode) || count <= 0) return [];
const monsters = this.node.children;
if (!monsters || monsters.length <= 0) return [];
const arr: Array<{ n: Node; d: number }> = [];
for (let i = 0; i < monsters.length; i++) {
const mNode = monsters[i];
if (!mNode || !isValid(mNode)) continue;
const mComp = mNode.getComponent(Monster);
if (!mComp || mComp.getIsDead()) continue;
const agent = mNode.getComponent(MapAgent);
const dis = agent ? agent.getNearestMonster(weaponNode) : Vec3.distance(weaponNode.worldPosition, mNode.worldPosition);
if (!Number.isFinite(dis)) continue;
arr.push({ n: mNode, d: dis });
}
if (arr.length <= 0) return [];
arr.sort((a, b) => a.d - b.d);
const out: Node[] = [];
const limit = Math.min(arr.length, Math.floor(count));
for (let i = 0; i < limit; i++) {
const n = arr[i].n;
if (n && isValid(n)) out.push(n);
}
return out;
}
/**
* 线
* fromNode/线 <= count
* state ATTACKING
* @param count
* @param fromNode 退
* @param _isWholeScene
* @returns { node, num, isPrimarySide }[]使 .node
*/
getMonsterParabolaPos(count: number = 0, fromNode: Node | null = null, _isWholeScene: boolean = false) {
if (count <= 0) {
return [];
}
const monsters = this.node.children;
const scored: Array<{ node: Node; d: number; attacking: boolean }> = [];
for (let i = 0; i < monsters.length; i++) {
const mNode = monsters[i];
if (!mNode?.isValid) continue;
const mComp = mNode.getComponent(Monster);
if (!mComp || mComp.getIsDead()) continue;
const agent = mNode.getComponent(MapAgent);
let d: number;
if (fromNode?.isValid) {
d = agent ? agent.getNearestMonster(fromNode) : Vec3.distance(fromNode.worldPosition, mNode.worldPosition);
} else {
if (!agent) continue;
d = agent.getRemainPathDistance();
}
if (!Number.isFinite(d)) continue;
scored.push({
node: mNode,
d,
attacking: mComp.state === MonsterState.ATTACKING,
});
}
if (scored.length <= 0) {
return [];
}
const attackingPool = scored.filter((s) => s.attacking);
const pool = attackingPool.length > 0 ? attackingPool : scored;
pool.sort((a, b) => a.d - b.d);
const need = Math.min(Math.floor(count), pool.length);
const result: Array<{ node: Node; num: number; isPrimarySide: boolean }> = [];
const seen = new Set<string>();
for (let i = 0; i < pool.length && result.length < need; i++) {
const { node } = pool[i];
if (!node?.isValid) continue;
const id = node.uuid;
if (seen.has(id)) continue;
seen.add(id);
result.push({ node, num: 0, isPrimarySide: true });
}
return result;
}
/**
*
* @param flashlight
* @param scale
* @returns
*/
getFlashlighttMonster(flashlight: Node, scale: number): Node[] {
// 竖向光束:美术尺寸为 窄边×长边 = baseWidth × baseHeight(如 120×1500),与缩放
// 局部坐标:长边沿 +Y 从锚点向前延伸,窄边沿 X 对称(与旧版「长边沿 +X、窄边沿 Y」对调)
const baseWidth = 120;
const baseHeight = 1500;
const beamLength = baseHeight * scale;
const halfThickness = (baseWidth * scale) / 2;
// 获取手电筒的世界位置和角度
const flashlightPos = flashlight.worldPosition;
const flashlightAngle = flashlight.angle;
// 存储在手电筒范围内的怪物
const monstersInRange: Node[] = [];
// 遍历所有怪物,检查是否在手电筒范围内
for (const monsterNode of this.node.children) {
// 获取怪物的世界位置
const monsterPos = monsterNode.worldPosition;
// 将怪物坐标转换为手电筒局部坐标系
// 计算怪物相对于手电筒的偏移量
const dx = monsterPos.x - flashlightPos.x;
const dy = monsterPos.y - flashlightPos.y;
// 旋转坐标(将手电筒旋转角度转换为弧度,注意符号)
// 这里使用负数是因为我们需要将怪物坐标旋转回手电筒未旋转时的坐标系
const radian = -flashlightAngle * Math.PI / 180;
// 应用旋转公式:x' = x*cosθ - y*sinθ, y' = x*sinθ + y*cosθ
const localX = dx * Math.cos(radian) - dy * Math.sin(radian);
const localY = dx * Math.sin(radian) + dy * Math.cos(radian);
// 竖向矩形:|localX| <= 半窄边,localY 从锚点沿光束向前 [0, beamLength]
// 若预制体光束沿 -Y 或中心对称,需改 localY 区间
if (Math.abs(localX) <= halfThickness && localY >= 0 && localY <= beamLength) {
monstersInRange.push(monsterNode);
}
}
return monstersInRange;
}
resetInfiniteModeWave() {
if (gg.game.CurentBattle.BattleType == BattleType.InfiniteMode) {
if (this.nextEventIndex == this.spawnEvents.length) {
console.log('无尽模式最后一波怪物生成完后,重置怪物生成事件索引', gg.game.CurentBattle.BattleTime)
this.nextEventIndex = 0;
//gg.game.CurentBattle.MonsterDeadNum = 0
}
}
}
}