Files
pixelheros/assets/script/game/map/MissSkillsComp.ts
pan d9bccd7c93 refactor: 将代码中的card_lv统一替换为rarity
本次提交完成了全项目范围内术语的统一替换,将原本指代卡牌品质/等级的card_lv字段全部重命名为rarity,对齐了设计文档中的命名规范,避免了术语混淆:
1.  更新了所有配置文件、组件类、数据结构中的字段定义
2.  同步修改了注释、文档说明中的相关表述
3.  修正了英雄、技能、装备卡牌的品质显示逻辑
4.  调整了背景颜色切换、属性计算等相关业务逻辑
2026-09-02 16:42:54 +08:00

248 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file MissSkillsComp.ts
* @description 场上技能卡槽位管理器组件(UI 视图层)
*
* 职责:
* 1. 管理场上已使用的 **技能卡** 的可视化槽位(最多 10 个)。
* 2. 监听 UseSkillCard 事件,当玩家使用技能卡时实例化 SkillBoxComp 并放入空闲槽位。
* 3. 监听 RemoveSkillBox 事件,当技能生效完毕后回收槽位并重新排列。
*
* 关键设计:
* - slots 数组预定义了 10 个固定坐标位(2 行 × 5 列),
* 每个槽位记录是否占用及对应节点引用。
* - 当某个 SkillBox 销毁时,触发 rearrangeSlots 将剩余节点
* 紧凑地重排到前置槽位,避免视觉空洞。
* - SkillBox 的实例化使用 skill_box Prefab,在编辑器中绑定。
*
* 依赖:
* - SkillBoxComp(SkillBoxComp.ts)—— 单个技能卡的效果控制组件
* - GameEvent.UseSkillCard —— 技能卡使用事件
* - GameEvent.RemoveSkillBox —— 技能卡移除事件
* - smc.map.MapView.scene.entityLayer —— 技能节点的父容器(SKILL 节点)
*/
import { mLogger } from "../common/Logger";
import { _decorator, Node, Prefab, instantiate, Vec3 } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { SBox } from "./SBox";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { BoxSet } from "../common/config/GameSet";
import { SkillOverrides, SkillSet } from "../common/config/SkillSet";
import { smc } from "../common/SingletonModuleComp";
import { buffManager } from "../hero/BuffManager";
const { ccclass, property } = _decorator;
/** 技能槽位数据结构 */
interface SkillBoxSlot {
/** 该槽位的固定 X 坐标 */
x: number;
/** 该槽位的固定 Y 坐标 */
y: number;
/** 是否已被占用 */
used: boolean;
/** 占用该槽位的节点引用 */
node: Node | null;
}
/**
* MissSkillsComp —— 场上技能卡槽位管理器
*
* 在战斗场景中管理已激活的技能卡显示位置。
* 2 行 × 5 列 = 10 个槽位,不足时提示已满。
*/
@ccclass('MissSkillsComp')
@ecs.register('MissSkillsComp', false)
export class MissSkillsComp extends CCComp {
/** 调试日志开关 */
private debugMode: boolean = true;
/** 当前活跃实例(供其他系统查询槽位占用,避免直接 find 强引用) */
private static instance: MissSkillsComp | null = null;
/**
* 查询场上技能槽是否已全部占用。
* @returns 无空闲槽位时返回 true;实例不存在时返回 false
*/
public static isFull(): boolean {
const inst = MissSkillsComp.instance;
if (!inst) return false;
return inst.slots.every(slot => slot.used);
}
/** 技能卡 Prefab(在编辑器中赋值) */
@property({ type: Prefab })
private skill_box: Prefab = null;
/**
* 预定义的 6 个槽位坐标(单行):
*/
private slots: SkillBoxSlot[] = [
{ x: -310, y: BoxSet.GAME_LINE + 300, used: false, node: null },
{ x: -240, y: BoxSet.GAME_LINE + 300, used: false, node: null },
{ x: -170, y: BoxSet.GAME_LINE + 300, used: false, node: null },
{ x: -100, y: BoxSet.GAME_LINE + 300, used: false, node: null },
{ x: -30, y: BoxSet.GAME_LINE + 300, used: false, node: null },
{ x: 40, y: BoxSet.GAME_LINE + 300, used: false, node: null },
];
/** 注册事件监听 */
onLoad() {
MissSkillsComp.instance = this;
oops.message.on(GameEvent.UseSkillCard, this.onUseSkillCard, this);
oops.message.on(GameEvent.UseItemCard, this.onUseItemCard, this);
oops.message.on(GameEvent.RemoveSkillBox, this.onRemoveSkillBox, this);
}
/** 移除事件监听 */
onDestroy() {
super.onDestroy();
if (MissSkillsComp.instance === this) {
MissSkillsComp.instance = null;
}
oops.message.off(GameEvent.UseSkillCard, this.onUseSkillCard, this);
oops.message.off(GameEvent.UseItemCard, this.onUseItemCard, this);
oops.message.off(GameEvent.RemoveSkillBox, this.onRemoveSkillBox, this);
}
/**
* 处理技能卡移除事件:
* 1. 在 slots 中找到对应节点并释放。
* 2. 调用 rearrangeSlots 紧凑重排。
*
* @param event 事件名
* @param args 要移除的节点引用
*/
private onRemoveSkillBox(event: string, args: any) {
const node = args as Node;
let removed = false;
for (let i = 0; i < this.slots.length; i++) {
if (this.slots[i].node === node) {
this.slots[i].used = false;
this.slots[i].node = null;
removed = true;
break;
}
}
if (removed) {
this.rearrangeSlots();
}
}
/**
* 紧凑重排:将所有有效节点按顺序移到前置槽位。
* 确保视觉上不会出现中间空洞。
*/
private rearrangeSlots() {
// 收集所有有效节点
const validNodes: Node[] = [];
for (let i = 0; i < this.slots.length; i++) {
if (this.slots[i].used && this.slots[i].node && this.slots[i].node.isValid) {
validNodes.push(this.slots[i].node);
}
this.slots[i].used = false;
this.slots[i].node = null;
}
// 按顺序重新分配
for (let i = 0; i < validNodes.length; i++) {
if (i < this.slots.length) {
this.slots[i].used = true;
this.slots[i].node = validNodes[i];
validNodes[i].setPosition(new Vec3(this.slots[i].x, this.slots[i].y, 0));
}
}
}
/**
* 处理使用技能卡事件:提取 uuid 和 rarity 后调用 addSkill。
* @param event 事件名
* @param args 卡牌数据(含 uuid、rarity)
*/
private onUseSkillCard(event: string, args: any) {
const payload = args ?? event;
// 卡 uuid 仅用于查配置;真正施法用技能 id(payload.skill),缺省时退回 uuid(兼容技能卡直接用技能 id 的场景)
const skillUuid = Number(payload?.skill ?? payload?.uuid ?? 0);
const rarity = Math.max(1, Math.floor(Number(payload?.rarity ?? 1)));
if (!skillUuid) return;
// 药水/技能卡的 overrides(含 buff_value)需透传到技能实体
this.addSkill(skillUuid, rarity, payload?.overrides);
}
/**
* 处理药水卡使用事件:直接对全队存活英雄施加计时 buff,不经过 SBox 施法流程。
*
* 设计:
* 药水是"即时全队强化",无需节点、动画、槽位;
* 从技能配置取 timed_buff_id,遍历 heroGrid 对每个存活英雄 applyBuff,
* buff_value 覆写值透传到 ActiveBuff.value_override。
*
* @param event 事件名
* @param args 卡牌数据(含 skill、overrides)
*/
private onUseItemCard(event: string, args: any) {
const payload = args ?? event;
const skillId = Number(payload?.skill ?? 0);
const config = SkillSet[skillId];
if (!config) {
mLogger.warn(this.debugMode, "MissSkillsComp", `potion skill ${skillId} config not found`);
return;
}
const buffId = config.timed_buff_id;
if (buffId === undefined) {
mLogger.warn(this.debugMode, "MissSkillsComp", `potion skill ${skillId} has no timed_buff_id`);
return;
}
const buffValue = payload?.overrides?.buff_value;
let applied = 0;
for (const eid of smc.mission.heroGrid) {
if (eid <= 0) continue;
const entity = ecs.getEntityByEid(eid);
if (!entity) continue;
buffManager.applyBuff(entity, buffId, 0, 0, buffValue !== undefined ? { value: buffValue } : undefined);
applied++;
}
mLogger.log(this.debugMode, "MissSkillsComp", `potion applied: skill=${skillId} buff=${buffId} value=${buffValue} heroes=${applied}`);
}
start() {
}
/**
* 在场上添加一个技能卡:
* 1. 在 slots 中查找空闲位。
* 2. 实例化 skill_box Prefab 并放置在空闲位坐标。
* 3. 获取或添加 SkillBoxComp 并初始化。
*
* @param uuid 技能 UUID
* @param rarity 技能卡等级
*/
addSkill(uuid: number, rarity: number, overrides?: SkillOverrides) {
if (!this.skill_box) {
mLogger.error(this.debugMode, "MissSkillsComp", "skill_box prefab not set");
return;
}
// 查找空闲槽位
const emptyIndex = this.slots.findIndex(slot => !slot.used);
if (emptyIndex === -1) {
mLogger.warn(this.debugMode, "MissSkillsComp", "skill_box slots are full");
oops.gui.toast("技能槽位已满");
return;
}
// 使用 ECS 实体创建技能节点
let sbox = ecs.getEntity<SBox>(SBox);
let pos = new Vec3(this.slots[emptyIndex].x, this.slots[emptyIndex].y, 0);
let node = sbox.load(uuid, rarity, pos, this.skill_box, overrides);
this.slots[emptyIndex].used = true;
this.slots[emptyIndex].node = node;
}
/** ECS 组件移除时销毁节点 */
reset() {
this.node.destroy();
}
}