feat: 实现技能卡牌系统并添加相关配置

- 在 GameSet 中新增技能卡牌释放起始坐标常量
- 卡牌使用组件增加技能卡释放事件分发
- 任务英雄组件监听技能卡事件并转发给技能施放系统
- 卡牌组件支持技能卡牌的显示和等级星级
- 卡牌配置中添加技能卡牌池和对应的配置信息
- 技能施放系统扩展以支持卡牌技能的直接触发
This commit is contained in:
walkpan
2026-04-05 23:07:18 +08:00
parent 5d188bb5aa
commit dc9c6cc94a
6 changed files with 137 additions and 6 deletions

View File

@@ -80,6 +80,13 @@ export const CardPoolList: CardConfig[] = [
{ uuid: 5105, type: CardType.Hero, cost: 3, weight: 25, pool_lv: 5, kind: CKind.Hero, hero_lv: 1 },
{ uuid: 5304, type: CardType.Hero, cost: 3, weight: 25, pool_lv: 5, kind: CKind.Hero, hero_lv: 1 },
// 技能卡牌
{ uuid: 6005, type: CardType.Skill, cost: 2, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1 },
{ uuid: 6104, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1 },
{ uuid: 6205, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 3, kind: CKind.Skill, card_lv: 1 },
{ uuid: 6304, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1 }, // 群体治疗
{ uuid: 6406, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 4, kind: CKind.Skill, card_lv: 1 }, // 群体全能
{ uuid: 7001, type: CardType.SpecialUpgrade, cost: 6, weight: 16, pool_lv: 1 ,kind: CKind.Card },
{ uuid: 7002, type: CardType.SpecialUpgrade, cost: 6, weight: 14, pool_lv: 2 ,kind: CKind.Card },
{ uuid: 7101, type: CardType.SpecialRefresh, cost: 4, weight: 14, pool_lv: 1 ,kind: CKind.Card },
@@ -110,6 +117,20 @@ export interface SpecialRefreshCardConfig extends CardConfig {
refreshHeroType: SpecialRefreshHeroType
}
/** 技能卡牌配置补充 */
export interface SkillCardConfig extends CardConfig {
name: string
info: string
}
export const SkillCardList: Record<number, SkillCardConfig> = {
6005: { uuid: 6005, type: CardType.Skill, cost: 2, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1, name: "半月斩", info: "施放一道半月剑气,对触碰到的敌人造成伤害" },
6104: { uuid: 6104, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1, name: "闪光箭雨", info: "施放闪光箭雨,对多个敌人造成伤害并暴击" },
6205: { uuid: 6205, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 3, kind: CKind.Skill, card_lv: 1, name: "月光波", info: "施放月波,对触碰到的多个敌人造成高额伤害" },
6304: { uuid: 6304, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1, name: "神圣治疗", info: "恢复场上随机3个友方单位的生命值" },
6406: { uuid: 6406, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 4, kind: CKind.Skill, card_lv: 1, name: "全体鼓舞", info: "为随机3个友方单位增加攻击力和生命上限" },
}
/** 功能卡定义表 */

View File

@@ -33,8 +33,9 @@ export enum FightSet {
FiIGHT_TIME=60*10,//战斗时间
BACK_CHANCE=40,//击退概率
FROST_TIME=3,//冰冻时间
SKILL_CAST_DELAY=0.15
SKILL_CAST_DELAY=0.15,
CSKILL_START_X=-340,
CSKILL_START_Y=30,
}
export enum IndexSet {

View File

@@ -7,7 +7,7 @@ import { Skill } from "../skill/Skill";
import { smc } from "../common/SingletonModuleComp";
import { HType } from "../common/config/heroSet";
import { Attrs } from "../common/config/HeroAttrs";
import { BoxSet, FightSet } from "../common/config/GameSet";
import { BoxSet, FacSet, FightSet } from "../common/config/GameSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
@@ -40,10 +40,91 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
oops.message.off(GameEvent.TriggerSkill, this.onTriggerSkill, this);
}
private onTriggerSkill(event: string, args: { s_uuid: number, heroAttrs: HeroAttrsComp, heroView: HeroViewComp, triggerType?: string }) {
if (!args || !args.s_uuid || !args.heroAttrs || !args.heroView) return;
private onTriggerSkill(event: string, args: {
s_uuid: number,
heroAttrs?: HeroAttrsComp,
heroView?: HeroViewComp,
triggerType?: string,
isCardSkill?: boolean,
card_lv?: number,
targetPos?: Vec3
}) {
if (!args || !args.s_uuid) return;
// 卡牌技能直接触发
if (args.isCardSkill) {
this.forceCastCardSkill(args.s_uuid, args.card_lv || 1, args.targetPos || new Vec3(FightSet.CSKILL_START_X, FightSet.CSKILL_START_Y, 0));
return;
}
// 常规英雄技能触发
if (!args.heroAttrs || !args.heroView) return;
this.forceCastTriggerSkill(args.s_uuid, args.heroAttrs, args.heroView, args.triggerType);
}
/**
* 强制执行卡牌技能
* 卡牌技能没有施法者主体,直接从指定坐标释放,或者对全体/随机友方生效
*/
public forceCastCardSkill(s_uuid: number, cardLv: number, spawnPos: Vec3) {
const config = SkillSet[s_uuid];
if (!config) return;
// 如果是敌方目标,没有战斗时不释放
const isEnemyTarget = !this.isSelfSkill(config.TGroup) && !this.isFriendlySkill(config.TGroup);
if (isEnemyTarget && !smc.mission.in_fight) return;
let isFriendly = false;
let targetEids: number[] = [];
if (this.isFriendlySkill(config.TGroup) || this.isSelfSkill(config.TGroup)) {
isFriendly = true;
targetEids = this.collectFriendlyTargetEids(FacSet.HERO, undefined, true); // 获取所有英雄阵营的目标
}
const sUp = SkillUpList[s_uuid] ? SkillUpList[s_uuid] : SkillUpList[1001];
const cNum = Math.min(2, Math.max(0, Math.floor(sUp.num ?? 0)));
const castTimes = 1 + cNum;
// 构造一个模拟的 HeroAttrsComp 用于数值计算,只包含基础卡牌伤害计算所需的属性
const mockAttrs = new HeroAttrsComp();
mockAttrs.ap = 100; // 基准攻击力,可以根据设计需求调整
mockAttrs.critical = 0;
mockAttrs.freeze_chance = 0;
mockAttrs.back_chance = 0;
mockAttrs.puncture = 0;
mockAttrs.fac = FacSet.HERO;
for (let i = 0; i < castTimes; i++) {
if (isFriendly) {
const friendlyTargets = this.resolveFriendlyTargets(targetEids, FacSet.HERO);
if (friendlyTargets.length === 0) continue;
this.applyFriendlySkillEffects(s_uuid, cardLv, config, null as any, mockAttrs, friendlyTargets, null);
} else {
const enemyTargetPos = this.resolveRepeatCastTargetPos(new Vec3(spawnPos.x + 300, spawnPos.y, spawnPos.z), i);
this.createSkillEntityForCard(s_uuid, cardLv, mockAttrs, spawnPos, enemyTargetPos, i);
}
}
}
/** 专用于卡牌施放的技能实体生成 */
private createSkillEntityForCard(s_uuid: number, skillLv: number, mockAttrs: HeroAttrsComp, startPos: Vec3, targetPos: Vec3 | null, castIndex: number = 0) {
const scene = smc.map.MapView.scene;
const parent = scene.entityLayer?.node?.getChildByName("SKILL");
if (!parent || !targetPos) return;
const skill = ecs.getEntity<Skill>(Skill);
const actualStartPos = this.resolveRepeatCastStartPos(startPos, castIndex);
// 伪造一个简单的 heroView 供 Skill 初始化使用,只包含方向信息
const mockView = {
node: { scale: new Vec3(1, 1, 1), position: actualStartPos },
ent: { eid: -1 },
box_group: BoxSet.HERO
} as any;
skill.load(actualStartPos, parent, s_uuid, targetPos.clone(), mockView, mockAttrs, skillLv, 0);
}
/** 空施法计划:用于“当前无可施法技能”时的统一返回 */
private readonly emptyCastPlan = { skillId: 0, skillLv: 1, isFriendly: false, targetPos: null as Vec3 | null, targetEids: [] as number[] };
/** 近战英雄默认施法射程 */

View File

@@ -2,7 +2,7 @@ import { mLogger } from "../common/Logger";
import { _decorator, Animation, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, resources, Light } 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 { CardConfig, CardType, SpecialRefreshCardList, SpecialUpgradeCardList, CKind } from "../common/config/CardSet";
import { CardConfig, CardType, SpecialRefreshCardList, SpecialUpgradeCardList, CKind, SkillCardList } from "../common/config/CardSet";
import { CardUseComp } from "./CardUseComp";
import { HeroInfo } from "../common/config/heroSet";
import { SkillSet } from "../common/config/SkillSet";
@@ -412,6 +412,15 @@ export class CardComp extends CCComp {
this.oinfo_node.active = false;
this.info_node.getChildByName("ap").getChildByName("val").getComponent(Label).string = `${(hero?.ap ?? 0) * heroLv}`;
this.info_node.getChildByName("hp").getChildByName("val").getComponent(Label).string = `${(hero?.hp ?? 0) * heroLv}`;
}else if(this.card_type===CardType.Skill){
const skill = SkillSet[this.card_uuid];
const skillCard = SkillCardList[this.card_uuid];
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
this.setLabel(this.name_node, `${spSuffix}${skillCard?.name || skill?.name || ""}${spSuffix}`);
this.info_node.active = false;
this.oinfo_node.active = true;
this.oinfo_node.getChildByName("info").getComponent(Label).string = `${skillCard?.info || skill?.info || ""}`;
}else{
const specialCard = this.card_type === CardType.SpecialUpgrade
? SpecialUpgradeCardList[this.card_uuid]

View File

@@ -51,6 +51,7 @@ export class CardUseComp extends CCComp {
oops.message.dispatchEvent(GameEvent.CallHero, used);
return "hero";
case CardType.Skill:
oops.message.dispatchEvent(GameEvent.UseSkillCard, used);
return "skill";
case CardType.SpecialUpgrade:
case CardType.SpecialRefresh:

View File

@@ -49,16 +49,34 @@ export class MissionHeroCompComp extends CCComp {
this.on(GameEvent.MissionEnd,this.clear_heros,this)
/** 全局消息监听 */
oops.message.on(GameEvent.CallHero,this.call_hero,this)
oops.message.on(GameEvent.UseSkillCard,this.onUseSkillCard,this)
}
onDestroy(){
/** 清理监听,避免节点销毁后仍响应消息 */
oops.message.off(GameEvent.CallHero,this.call_hero,this)
oops.message.off(GameEvent.UseSkillCard,this.onUseSkillCard,this)
oops.message.off(GameEvent.FightReady,this.fight_ready,this)
oops.message.off(GameEvent.Zhaohuan,this.zhao_huan,this)
oops.message.off(GameEvent.MissionEnd,this.clear_heros,this)
}
/** 响应卡牌释放技能 */
private onUseSkillCard(event: string, args: any) {
const payload = args ?? event;
const uuid = Number(payload?.uuid ?? 0);
const card_lv = Math.max(1, Math.floor(Number(payload?.card_lv ?? 1)));
if (!uuid) return;
// 分发给 SCastSystem 处理(使用特定的坐标 x=-340, y=30
oops.message.dispatchEvent(GameEvent.TriggerSkill, {
s_uuid: uuid,
isCardSkill: true,
card_lv: card_lv,
targetPos: v3(-340, 30, 0)
});
}
start() {
// this.test_call()
}