Compare commits
6 Commits
72b31037f5
...
e7b0d55e36
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7b0d55e36 | ||
|
|
4a6f19ae8b | ||
|
|
9dd0861df1 | ||
|
|
af78a3e02b | ||
|
|
f86c08a77d | ||
|
|
32997f0a04 |
@@ -1453,7 +1453,7 @@
|
||||
"__id__": 72
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 80
|
||||
|
||||
@@ -69,7 +69,7 @@ export class Hero extends ecs.Entity {
|
||||
// 英雄等级在当前规则下上限为 3,避免超配表范围
|
||||
if(hero_lv>3) hero_lv=3
|
||||
// 英雄尺寸随等级做轻量放大,强化成长反馈
|
||||
let size=1.3
|
||||
let size=1.15
|
||||
// 根据配置路径加载英雄预制体
|
||||
var path = "game/heros/"+HeroInfo[uuid].path;
|
||||
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
||||
|
||||
@@ -126,7 +126,7 @@ export class Monster extends ecs.Entity {
|
||||
// 怪物默认朝左,表现缩放固定为负向
|
||||
scale=-1
|
||||
// 当前怪物尺寸固定,保留变量便于后续扩展
|
||||
let size=1
|
||||
let size=1.15
|
||||
var scene = smc.map.MapView.scene;
|
||||
// 根据配置读取怪物预制体路径
|
||||
var path = "game/heros/"+HeroInfo[uuid].path;
|
||||
@@ -136,6 +136,8 @@ export class Monster extends ecs.Entity {
|
||||
if (!node) {
|
||||
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
||||
node = instantiate(prefab);
|
||||
// 记录预制体的初始缩放值,防止对象池复用时累乘导致无限变大
|
||||
(node as any)._baseScale = node.scale.clone();
|
||||
}
|
||||
|
||||
// 统一挂到实体显示层 HERO 节点下
|
||||
@@ -147,7 +149,8 @@ export class Monster extends ecs.Entity {
|
||||
collider.enabled = false;
|
||||
}
|
||||
|
||||
node.setScale(size*node.scale.x,size*node.scale.y);
|
||||
let baseScale = (node as any)._baseScale || v3(1, 1, 1);
|
||||
node.setScale(size * baseScale.x, size * baseScale.y);
|
||||
node.setPosition(pos)
|
||||
const model = this.get(HeroAttrsComp);
|
||||
// 从配置表获取怪物静态数据
|
||||
|
||||
@@ -49,8 +49,8 @@ interface MoveFacConfig {
|
||||
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||
/** 近战判定射程(来自 heroSet) */
|
||||
private readonly meleeAttackRange = HeroDisVal[HType.Melee];
|
||||
private readonly heroFrontAnchorX = -30;
|
||||
private readonly monFrontAnchorX = 30;
|
||||
private readonly heroFrontAnchorX = -50;
|
||||
private readonly monFrontAnchorX = 50;
|
||||
/** 常规同阵营横向最小间距 */
|
||||
private readonly allySpacingX = 60;
|
||||
/** 纵向判定为同排的最大 Y 差 */
|
||||
|
||||
@@ -372,7 +372,7 @@ export class MissionCardComp extends CCComp {
|
||||
payload.cancel = true;
|
||||
payload.reason = "hero_limit";
|
||||
oops.gui.toast(`英雄已满 (${current}/${heroMax})`);
|
||||
this.playHeroNumDeniedAnim();
|
||||
// this.playHeroNumDeniedAnim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,24 +972,24 @@ export class MissionCardComp extends CCComp {
|
||||
private updateHeroNumUI(animate: boolean, isIncrease: boolean) {
|
||||
this.syncMissionHeroData();
|
||||
if (!animate || !isIncrease) return;
|
||||
this.playHeroNumGainAnim();
|
||||
// this.playHeroNumGainAnim();
|
||||
}
|
||||
|
||||
private playHeroNumGainAnim() {
|
||||
if (!this.hero_num_node || !this.hero_num_node.isValid) return;
|
||||
const iconNode = this.hero_num_node.getChildByName("icon");
|
||||
const numNode = this.hero_num_node.getChildByName("num");
|
||||
this.playHeroNumNodePop(iconNode, 1.2);
|
||||
this.playHeroNumNodePop(numNode, 1.2);
|
||||
}
|
||||
// private playHeroNumGainAnim() {
|
||||
// if (!this.hero_num_node || !this.hero_num_node.isValid) return;
|
||||
// const iconNode = this.hero_num_node.getChildByName("icon");
|
||||
// const numNode = this.hero_num_node.getChildByName("num");
|
||||
// this.playHeroNumNodePop(iconNode, 1.2);
|
||||
// this.playHeroNumNodePop(numNode, 1.2);
|
||||
// }
|
||||
|
||||
private playHeroNumDeniedAnim() {
|
||||
if (!this.hero_num_node || !this.hero_num_node.isValid) return;
|
||||
const iconNode = this.hero_num_node.getChildByName("icon");
|
||||
const numNode = this.hero_num_node.getChildByName("num");
|
||||
this.playHeroNumNodePop(iconNode, 1.2);
|
||||
this.playHeroNumNodePop(numNode, 1.2);
|
||||
}
|
||||
// private playHeroNumDeniedAnim() {
|
||||
// if (!this.hero_num_node || !this.hero_num_node.isValid) return;
|
||||
// const iconNode = this.hero_num_node.getChildByName("icon");
|
||||
// const numNode = this.hero_num_node.getChildByName("num");
|
||||
// this.playHeroNumNodePop(iconNode, 1.2);
|
||||
// this.playHeroNumNodePop(numNode, 1.2);
|
||||
// }
|
||||
|
||||
private playHeroNumNodePop(node: Node | null, scalePeak: number) {
|
||||
this.playNodeScalePop(node, scalePeak, 0.08, 0.1);
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
*
|
||||
* 职责:
|
||||
* 1. 管理每一波怪物的 **生成计划**:根据 WaveSlotConfig 分配怪物到固定槽位。
|
||||
* 2. 管理 6 个固定刷怪槽位的占用状态,支持 Boss 占 2 格。
|
||||
* 2. 管理 5 个固定刷怪槽位的占用状态,支持 Boss 占 2 格。
|
||||
* 3. 处理特殊插队刷怪请求(MonQueue),优先于常规刷新。
|
||||
* 4. 自动推进波次:当前波所有怪物被清除后自动进入下一波。
|
||||
*
|
||||
* 关键设计:
|
||||
* - 全场固定 6 个槽位(索引 0-5),每个槽位占固定 X 坐标。
|
||||
* - Boss 占 2 个连续槽位,只能放在 0、2、4 号位。
|
||||
* - 全场固定 5 个槽位(索引 0-4),每个槽位占固定 X 坐标。
|
||||
* - Boss 默认占 3 个连续槽位,只要有连续三格空闲即可。
|
||||
* - slotOccupiedEids 记录每个槽位占用的怪物 ECS 实体 ID。
|
||||
* - resetSlotSpawnData(wave) 在每波开始时读取配置,分配并立即生成所有怪物。
|
||||
* - refreshSlotOccupancy() 定期检查槽位占用的实体是否仍存活,清除已死亡的占用。
|
||||
@@ -57,13 +57,13 @@ export class MissionMonCompComp extends CCComp {
|
||||
/** Boss 的渲染优先级偏移(确保 Boss 始终渲染在最前) */
|
||||
private static readonly BOSS_RENDER_PRIORITY = 1000000;
|
||||
/** 第一个槽位的 X 坐标起点 */
|
||||
private static readonly MON_SLOT_START_X = 30;
|
||||
private static readonly MON_SLOT_START_X = 50;
|
||||
/** 槽位间的 X 间距 */
|
||||
private static readonly MON_SLOT_X_INTERVAL = 60;
|
||||
/** 怪物出生掉落高度 */
|
||||
private static readonly MON_DROP_HEIGHT = 280;
|
||||
/** 最大槽位数 */
|
||||
private static readonly MAX_SLOTS = 6;
|
||||
private static readonly MAX_SLOTS = 5;
|
||||
|
||||
// ======================== 编辑器属性 ========================
|
||||
|
||||
@@ -86,7 +86,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
/** 槽位占用状态:记录每个槽位当前占用的怪物 ECS 实体 ID,null 表示空闲 */
|
||||
private slotOccupiedEids: Array<number | null> = Array(6).fill(null);
|
||||
private slotOccupiedEids: Array<number | null> = Array(5).fill(null);
|
||||
/** 全局生成顺序计数器(用于渲染层级排序) */
|
||||
private globalSpawnOrder: number = 0;
|
||||
/** 插队刷怪处理计时器 */
|
||||
@@ -177,14 +177,23 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
const item = this.MonQueue[0];
|
||||
const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) || MonList[MonType.LongBoss].includes(item.uuid);
|
||||
const slotsPerMon = isBoss ? 2 : 1;
|
||||
const isLongBoss = MonList[MonType.LongBoss].includes(item.uuid);
|
||||
const slotsPerMon = isBoss ? 3 : 1;
|
||||
|
||||
// 查找空闲槽位
|
||||
let slotIndex = -1;
|
||||
if (slotsPerMon === 2) {
|
||||
// Boss 只能放在 0, 2, 4(需要连续 2 格空闲)
|
||||
for (const idx of [0, 2, 4]) {
|
||||
if (!this.slotOccupiedEids[idx] && !this.slotOccupiedEids[idx + 1]) {
|
||||
if (slotsPerMon === 3) {
|
||||
// 构造可用索引列表
|
||||
let allowedIndices = [];
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS - 2; i++) {
|
||||
allowedIndices.push(i);
|
||||
}
|
||||
// 远程 Boss 插队时优先尝试从后往前找
|
||||
if (isLongBoss) {
|
||||
allowedIndices.reverse();
|
||||
}
|
||||
for (const idx of allowedIndices) {
|
||||
if (!this.slotOccupiedEids[idx] && !this.slotOccupiedEids[idx + 1] && !this.slotOccupiedEids[idx + 2]) {
|
||||
slotIndex = idx;
|
||||
break;
|
||||
}
|
||||
@@ -309,59 +318,53 @@ export class MissionMonCompComp extends CCComp {
|
||||
const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot;
|
||||
this.slotOccupiedEids = Array(MissionMonCompComp.MAX_SLOTS).fill(null);
|
||||
|
||||
let bosses: any[] = [];
|
||||
let normals: any[] = [];
|
||||
let allMons: any[] = [];
|
||||
|
||||
// 按类型分类
|
||||
// 解析配置
|
||||
for (const slot of config) {
|
||||
const slotsPerMon = slot.slotsPerMon || 1;
|
||||
const isBoss = slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss;
|
||||
const slotsPerMon = slot.slotsPerMon || (isBoss ? 3 : 1);
|
||||
for (let i = 0; i < slot.count; i++) {
|
||||
const uuid = this.getRandomUuidByType(slot.type);
|
||||
const upType = this.getRandomUpType();
|
||||
const req = { uuid, isBoss, upType, monLv: wave, slotsPerMon };
|
||||
if (isBoss || slotsPerMon === 2) {
|
||||
bosses.push(req);
|
||||
} else {
|
||||
normals.push(req);
|
||||
}
|
||||
allMons.push(req);
|
||||
}
|
||||
}
|
||||
|
||||
this.waveTargetCount = bosses.length + normals.length;
|
||||
this.waveTargetCount = allMons.length;
|
||||
this.waveSpawnedCount = 0;
|
||||
|
||||
// Boss 优先分配(只能放在 0, 2, 4)
|
||||
let bossAllowedIndices = [0, 2, 4];
|
||||
let assignedSlots = new Array(MissionMonCompComp.MAX_SLOTS).fill(null);
|
||||
|
||||
for (const boss of bosses) {
|
||||
// 统一按顺序分配(根据所需格数找连续空位)
|
||||
for (const mon of allMons) {
|
||||
let placed = false;
|
||||
for (const idx of bossAllowedIndices) {
|
||||
if (!assignedSlots[idx] && !assignedSlots[idx + 1]) {
|
||||
assignedSlots[idx] = boss;
|
||||
|
||||
if (mon.slotsPerMon === 3) {
|
||||
// 需要 3 格,占满连续的 3 格
|
||||
for (let idx = 0; idx < MissionMonCompComp.MAX_SLOTS - 2; idx++) {
|
||||
if (!assignedSlots[idx] && !assignedSlots[idx + 1] && !assignedSlots[idx + 2]) {
|
||||
assignedSlots[idx] = mon;
|
||||
assignedSlots[idx + 1] = "occupied"; // 占位标记
|
||||
assignedSlots[idx + 2] = "occupied"; // 占位标记
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!placed) {
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] No slot for boss!");
|
||||
} else {
|
||||
// 只需要 1 格
|
||||
for (let idx = 0; idx < MissionMonCompComp.MAX_SLOTS; idx++) {
|
||||
if (!assignedSlots[idx]) {
|
||||
assignedSlots[idx] = mon;
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 普通怪填充剩余空位
|
||||
for (const normal of normals) {
|
||||
let placed = false;
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
if (!assignedSlots[i]) {
|
||||
assignedSlots[i] = normal;
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!placed) {
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] No slot for normal monster!");
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] No slot for monster! uuid:", mon.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
* 4. 提供全局刷怪强度偏差系数(SpawnPowerBias)。
|
||||
*
|
||||
* 设计说明:
|
||||
* - 战场固定 6 个占位槽(索引 0-5)。
|
||||
* - Boss 占 2 个槽位,只能放在 0、2、4 号位(确保有连续 2 格)。
|
||||
* - 战场固定 5 个占位槽(索引 0-4)。
|
||||
* - Boss 默认占 3 个槽位,只要有连续 3 格空闲即可放置。
|
||||
* - MissionMonComp 在每波开始时读取本配置,决定刷怪组合。
|
||||
*
|
||||
* 注意:
|
||||
@@ -77,7 +77,7 @@ export const MonList = {
|
||||
[MonType.Melee]: [6001,6002,6003], // 近战怪池
|
||||
[MonType.Long]: [6004,6005], // 远程怪池
|
||||
[MonType.Support]: [6005], // 辅助怪池
|
||||
[MonType.MeleeBoss]:[6006,6015], // 近战 Boss 池
|
||||
[MonType.MeleeBoss]:[6006,6105], // 近战 Boss 池
|
||||
[MonType.LongBoss]:[6104], // 远程 Boss 池
|
||||
}
|
||||
|
||||
@@ -109,48 +109,36 @@ export interface IWaveSlot {
|
||||
// - type: 怪物类型 (参考 MonType,如近战 0,远程 1,Boss 3 等)。
|
||||
// - count: 该类型的怪在场上同时存在几个。
|
||||
// - slotsPerMon: (可选) 单个怪物体积占用几个占位坑,默认为 1。
|
||||
// 大型 Boss 设为 2,它会跨占位降落。
|
||||
// 大型 Boss 默认设为 3,它会跨占位降落。
|
||||
//
|
||||
// 【规则约束】:
|
||||
// - 全场固定 6 个槽位(索引 0-5)。
|
||||
// - Boss 固定占用 2 个位置,且只能出现在 1、3、5 号位(对应索引 0, 2, 4)。
|
||||
// - 每波怪物总槽位占用不能超过 6。
|
||||
// - 全场固定 5 个槽位(索引 0-4)。
|
||||
// - Boss 默认占用 3 个位置,只要有连续 3 格即可。
|
||||
// - 每波怪物总槽位占用不能超过 5。
|
||||
// =========================================================================================
|
||||
|
||||
/** 各波次的怪物占位配置(key = 波次编号) */
|
||||
export const WaveSlotConfig: { [wave: number]: IWaveSlot[] } = {
|
||||
/** 第 1 波:3 近战 + 3 远程 */
|
||||
|
||||
/** 第 1 波:2 近战 + 1 近战Boss(默认占3格) */
|
||||
1: [
|
||||
{ type: MonType.Melee, count: 3 },
|
||||
{ type: MonType.Long, count: 3 }
|
||||
{ type: MonType.Melee, count: 2 },
|
||||
{ type: MonType.MeleeBoss, count: 1 }
|
||||
],
|
||||
/** 第 2 波:2 近战 + 2 远程 + 2 辅助 */
|
||||
/** 第 2波:2 近战 + 1 远程Boss(默认占3格) */
|
||||
2: [
|
||||
{ type: MonType.Melee, count: 2 },
|
||||
{ type: MonType.Long, count: 2 },
|
||||
{ type: MonType.Support, count: 2 }
|
||||
],
|
||||
/** 第 3 波:2 近战 + 1 近战Boss(占2格) + 2 远程 */
|
||||
3: [
|
||||
{ type: MonType.Melee, count: 2 },
|
||||
{ type: MonType.MeleeBoss, count: 1, slotsPerMon: 2 },
|
||||
{ type: MonType.Long, count: 2 }
|
||||
],
|
||||
/** 第 4 波:2 近战 + 2 远程 + 1 远程Boss(占2格) */
|
||||
4: [
|
||||
{ type: MonType.Melee, count: 2 },
|
||||
{ type: MonType.Long, count: 2 },
|
||||
{ type: MonType.LongBoss, count: 1, slotsPerMon: 2 }
|
||||
{ type: MonType.LongBoss, count: 1 }
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认占位配置:
|
||||
* 当 WaveSlotConfig 中找不到对应波次时使用此兜底配置。
|
||||
* 默认 3 近战 + 3 远程。
|
||||
* 默认 2 近战 + 3 远程。
|
||||
*/
|
||||
export const DefaultWaveSlot: IWaveSlot[] = [
|
||||
{ type: MonType.Melee, count: 3 },
|
||||
{ type: MonType.Melee, count: 2 },
|
||||
{ type: MonType.Long, count: 3 }
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user