Files
heros/assets/script/game/hero/HSkillSystem.ts

270 lines
8.5 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.

import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3, v3 } from "cc";
import { HeroSkillsComp } from "./HeroSkills";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { SkillSet, SType } from "../common/config/SkillSet";
import { SkillEnt } from "../skill/SkillEnt";
import { smc } from "../common/SingletonModuleComp";
/**
* ==================== 施法请求标记组件 ====================
*
* 用途:
* - 标记角色想要施放某个技能
* - 由外部如AI系统、玩家输入添加
* - 施法系统处理后自动移除
*/
@ecs.register('CastSkillRequest')
export class CastSkillRequestComp extends ecs.Comp {
/** 技能索引(在 HeroSkillsComp.skills 中的位置) */
skillIndex: number = 0;
/** 目标位置数组(由请求者提供) */
targetPositions: Vec3[] = [];
reset() {
this.skillIndex = 0;
this.targetPositions = [];
}
}
/**
* ==================== 技能施法系统 ====================
*
* 职责:
* 1. 监听 CastSkillRequestComp 标记组件
* 2. 检查施法条件CD、MP、状态
* 3. 扣除资源MP
* 4. 创建技能实体
* 5. 触发施法动画
* 6. 移除请求标记
*
* 设计理念:
* - 使用标记组件驱动,符合 ECS 理念
* - 施法检查与执行分离
* - 自动处理资源消耗和CD重置
*/
export class SkillCastSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
/**
* 过滤器:拥有技能数据 + 施法请求的实体
*/
filter(): ecs.IMatcher {
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, CastSkillRequestComp);
}
/**
* 实体进入时触发(即请求施法时)
*/
entityEnter(e: ecs.Entity): void {
const skillsData = e.get(HeroSkillsComp);
const heroModel = e.get(HeroAttrsComp);
const request = e.get(CastSkillRequestComp);
const heroView = e.get(HeroViewComp);
// 1. 验证数据完整性
if (!skillsData || !heroModel || !request || !heroView) {
console.warn("[SkillCastSystem] 数据不完整,取消施法");
e.remove(CastSkillRequestComp);
return;
}
// 2. 获取技能数据
const skill = skillsData.getSkill(request.skillIndex);
if (!skill) {
console.warn(`[SkillCastSystem] 技能索引无效: ${request.skillIndex}`);
e.remove(CastSkillRequestComp);
return;
}
// 3. 检查施法条件
if (!this.checkCastConditions(skillsData, heroModel, request.skillIndex)) {
e.remove(CastSkillRequestComp);
return;
}
// 4. 执行施法
this.executeCast(e, skill, request.targetPositions, heroView);
// 5. 扣除资源和重置CD
heroModel.mp -= skill.cost;
skillsData.resetCD(request.skillIndex);
// 6. 移除请求标记
e.remove(CastSkillRequestComp);
}
/**
* 检查施法条件
*/
private checkCastConditions(skillsData: HeroSkillsComp, heroModel: HeroAttrsComp, skillIndex: number): boolean {
// 检查角色状态
if (heroModel.is_dead) {
return false;
}
// 检查控制状态(眩晕、冰冻)
if (heroModel.isStun() || heroModel.isFrost()) {
return false;
}
// 检查CD和MP
if (!skillsData.canCast(skillIndex, heroModel.mp)) {
return false;
}
return true;
}
/**
* 执行施法
*/
private executeCast(casterEntity: ecs.Entity, skill: any, targetPositions: Vec3[], heroView: HeroViewComp) {
const config = SkillSet[skill.uuid];
if (!config) {
console.error("[SkillCastSystem] 技能配置不存在:", skill.uuid);
return;
}
// 1. 播放施法动画
heroView.playSkillEffect(skill.uuid);
// 2. 延迟创建技能实体(等待动画)
const delay = config.with ?? 0.3; // 施法前摇时间
heroView.scheduleOnce(() => {
this.createSkillEntity(skill.uuid, heroView, targetPositions);
}, delay);
const heroModel = casterEntity.get(HeroAttrsComp);
console.log(`[SkillCastSystem] ${heroModel?.hero_name ?? '未知'} 施放技能: ${config.name}`);
}
/**
* 创建技能实体
*/
private createSkillEntity(skillId: number, caster: HeroViewComp, targetPositions: Vec3[]) {
// 检查节点有效性
if (!caster.node || !caster.node.isValid) {
console.warn("[SkillCastSystem] 施法者节点无效");
return;
}
// 获取场景节点
const parent = caster.node.parent;
if (!parent) {
console.warn("[SkillCastSystem] 场景节点无效");
return;
}
// ✅ 使用现有的 SkillEnt 创建技能
const skillEnt = ecs.getEntity<SkillEnt>(SkillEnt);
skillEnt.load(
caster.node.position, // 起始位置
parent, // 父节点
skillId, // 技能ID
targetPositions, // 目标位置数组
caster, // 施法者
0 // 额外伤害暂时为0
);
}
}
/**
* ==================== 技能CD更新系统 ====================
*
* 职责:
* 1. 每帧更新所有角色的技能CD
* 2. 自动递减CD时间
*
* 设计理念:
* - 独立的CD管理系统
* - 只负责时间递减,不处理施法逻辑
*/
export class SkillCDSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
filter(): ecs.IMatcher {
return ecs.allOf(HeroSkillsComp);
}
update(e: ecs.Entity): void {
const skillsData = e.get(HeroSkillsComp);
if (!skillsData) return;
// 更新所有技能CD
skillsData.updateCDs(this.dt);
}
}
/**
* ==================== 自动施法系统 ====================
*
* 职责:
* 1. 检测可施放的技能
* 2. 根据策略自动施法AI
* 3. 选择目标
* 4. 添加施法请求标记
*
* 设计理念:
* - 负责"何时施法"的决策
* - 通过添加 CastSkillRequestComp 触发施法
* - 可被玩家输入系统或AI系统复用
*/
export class SkillAutocastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
filter(): ecs.IMatcher {
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, HeroViewComp);
}
update(e: ecs.Entity): void {
const skillsData = e.get(HeroSkillsComp);
const heroModel = e.get(HeroAttrsComp);
const heroView = e.get(HeroViewComp);
if (!skillsData || !heroModel || !heroView) return;
// 检查基本条件
if (heroModel.is_dead || heroModel.isStun() || heroModel.isFrost()) return;
// 检查是否正在攻击(只有攻击时才释放技能)
if (!heroView.is_atking) return;
// 获取所有可施放的技能
const readySkills = skillsData.getReadySkills(heroModel.mp);
if (readySkills.length === 0) return;
// 选择第一个可施放的伤害技能
for (const skillIndex of readySkills) {
const skill = skillsData.getSkill(skillIndex);
if (!skill) continue;
const config = SkillSet[skill.uuid];
if (!config || config.SType !== SType.damage) continue;
// ✅ 添加施法请求标记组件
const request = e.add(CastSkillRequestComp) as CastSkillRequestComp;
request.skillIndex = skillIndex;
request.targetPositions = this.selectTargets(heroView);
// 一次只施放一个技能
break;
}
}
/**
* 选择目标位置
*/
private selectTargets(caster: HeroViewComp): Vec3[] {
// 简化版:选择最前方的敌人
const targets: Vec3[] = [];
// 这里可以调用 SkillConComp 的目标选择逻辑
// 暂时返回默认位置
const heroModel = caster.ent.get(HeroAttrsComp);
const fac = heroModel?.fac ?? 0;
const defaultX = fac === 0 ? 400 : -400;
targets.push(v3(defaultX, 0, 0));
return targets;
}
}