Files
heros/assets/script/game/skill/HeroSkillSystem.ts
2025-03-26 00:10:05 +08:00

328 lines
12 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.

/**
* 这是一个 使用cocos creator 3.8.2引擎 开发的游戏英雄技能系统TS脚本
* SkillSet 技能配置表
* HeroViewComp 英雄视图组件
* HeroSkillsComp 英雄技能组件
* Skill 技能实体
* SkillCom 技能视图组件
* 目前实现功能:
* 1. 技能冷却
* 2. 技能释放
* 3. 技能目标选择
* 4. 基础攻击技能的伤害应用 及view的伤害显示
* 未完成功能
* 1. 技能的动画显示和运动
* 2. debuff技能的实现和动画显示
* 3. buff技能实现和动画显示
*/
import { Node, Vec3 } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroViewComp } from "../hero/HeroViewComp";
import { HeroSkillsComp } from "./heroSkillsComp";
import { SkillSet, TargetGroup, TargetType } from "../common/config/SkillSet";
import { CdType } from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { Skill } from "../skills/Skill";
import { SkillCom } from "../skills/SkillCom";
import { AnimType } from "../common/config/SkillSet";
/** 技能系统 */
@ecs.register('HeroSkillSystem')
export class HeroSkillSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
// private updateInterval: number = 0.1; // 每0.1秒更新一次
// private accumulator: number = 0;
private _timers: { [key: string]: number } = {};
private _damageQueue: Array<{ timer: number; callback: () => void }> = [];
init(): void {
oops.message.on(GameEvent.MissionEnd, this.clear_timer, this);
}
filter(): ecs.IMatcher {
return ecs.allOf(HeroSkillsComp, HeroViewComp);
}
update(e: ecs.Entity) {
// 处理伤害队列
this.processDamageQueue();
const view = e.get(HeroViewComp);
const skills = e.get(HeroSkillsComp);
// 使用固定时间步长更新
// 只在攻击状态触发技能
if (view.is_atking) {
this.processSkills(e, skills);
}
// 更新所有技能冷却
skills.skills.forEach(skillId => {
this.updateCooldown(skills, skillId);
});
}
private processDamageQueue() {
const delta = this.dt;
for (let i = this._damageQueue.length - 1; i >= 0; i--) {
this._damageQueue[i].timer -= delta;
if (this._damageQueue[i].timer <= 0) {
this._damageQueue[i].callback();
this._damageQueue.splice(i, 1);
}
}
}
/** 处理所有技能逻辑 */
private processSkills(entity: ecs.Entity, comp: HeroSkillsComp) {
comp.skills.forEach(skillId => {
const config = SkillSet[skillId];
if (!config) return;
// 检查释放条件
if (this.checkSkillCondition(entity, config)) {
this.castSkill(entity, skillId, config);
}
});
}
/** 更新技能冷却 */
private updateCooldown(comp: HeroSkillsComp, skillId: number) {
let cd = comp.cooldowns.has(skillId) ? comp.cooldowns.get(skillId)! : 0;
if (cd > 0) {
comp.cooldowns.set(skillId, cd - this.dt);
}
}
/** 检查技能释放条件 */
private checkSkillCondition(entity: ecs.Entity, config: typeof SkillSet[keyof typeof SkillSet]): boolean {
const view = entity.get(HeroViewComp);
const comp = entity.get(HeroSkillsComp);
switch(config.CdType){
case CdType.SkillCD:
return (comp.cooldowns.get(config.uuid as number) ?? 0) <= 0;
case CdType.HeroCD:
return view.at >= view.cd;
}
}
/** 施放技能 */
private castSkill(caster: ecs.Entity, skillId: number, config: typeof SkillSet[keyof typeof SkillSet]) {
const view = caster.get(HeroViewComp);
const comp = caster.get(HeroSkillsComp);
const skillEntity = ecs.getEntity<Skill>(Skill);
console.log(view.hero_name+"施放技能:"+config.uuid+"=>"+view.hero_name);
// 处理CD和消耗
switch(config.CdType) {
case CdType.SkillCD:
view.as.max()
comp.cooldowns.set(skillId, config.cd); // 重置冷却时间
break;
case CdType.HeroCD:
view.as.atk()
view.at = view.at-view.cd; // 重置普攻计时器
break;
}
if (config.TargetGroup === TargetGroup.Enemy) {
const targets = this.selectEnemyTargets(caster, config);
if (targets.length === 0) return;
skillEntity.load(
view.node.position, // 起始位置
view.fac, // 阵营
view.node.parent, // 父节点
config.uuid, // 技能ID
targets[0]?.get(HeroViewComp).node.position // 目标位置
);
targets.forEach(target => {
this.applySkillEffect(caster, target, config,skillEntity);
console.log("施放技能:"+config.uuid+"=>"+target.get(HeroViewComp).hero_name);
});
}
if (config.TargetGroup === TargetGroup.Ally) {
const targets = this.selectAllyTargets(caster, config);
if (targets.length === 0) return;
targets.forEach(target => {
this.applySkillEffect(caster, target, config,skillEntity);
});
}
if (config.TargetGroup === TargetGroup.Self) {
this.applySkillEffect(caster, caster, config,skillEntity);
}
}
private selectEnemyTargets(caster: ecs.Entity, config: typeof SkillSet[keyof typeof SkillSet]): ecs.Entity[] {
const casterView = caster.get(HeroViewComp);
const team = casterView.fac;
const isEnemyTeam = team === 0 ? 1 : 0;
const candidates= ecs.query(ecs.allOf(HeroViewComp)).filter(e => e.get(HeroViewComp).fac !== team);
return this.filterFrontRow(candidates, isEnemyTeam);
}
private selectAllyTargets(caster: ecs.Entity, config: typeof SkillSet[keyof typeof SkillSet]): ecs.Entity[] {
const casterView = caster.get(HeroViewComp);
const team = casterView.fac;
const candidates= ecs.query(ecs.allOf(HeroViewComp)).filter(e => e.get(HeroViewComp).fac === team);
// 第二阶段:位置/血量等精细筛选
switch(config.TargetType) {
case TargetType.Melee:
return candidates.filter(e => e.get(HeroViewComp).type === 0);
case TargetType.Ranged:
return candidates.filter(e => e.get(HeroViewComp).type === 1);
case TargetType.SupportClass:
return candidates.filter(e => e.get(HeroViewComp).type === 2);
case TargetType.Random:
return this.pickRandomTarget(candidates, config.count || 1);
default:
return candidates;
}
}
/** 筛选最前排单位 */
private filterFrontRow(entities: ecs.Entity[], isEnemyTeam: number): ecs.Entity[] {
// 敌方最前排是x坐标最大的我方最前排是x坐标最小的
const keyPos = isEnemyTeam ?
Math.min(...entities.map(e => e.get(HeroViewComp).node.position.x)) :
Math.max(...entities.map(e => e.get(HeroViewComp).node.position.x));
return entities.filter(e =>
Math.abs(e.get(HeroViewComp).node.position.x - keyPos) < 10
);
}
/** 随机选择目标 */
private pickRandomTarget(entities: ecs.Entity[], count: number): ecs.Entity[] {
const shuffled = [...entities].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
}
/** 应用技能效果 */
private applySkillEffect(caster: ecs.Entity, target: ecs.Entity, config: typeof SkillSet[keyof typeof SkillSet],skillEntity:ecs.Entity) {
const casterView = caster.get(HeroViewComp);
const targetView = target.get(HeroViewComp);
// 直接计算伤害(包含防御减免)
const damageResult = this.calculateDamage(caster, target, config);
// 将施法者传入applyDamage方法
this.applyDamage(caster, target, damageResult,skillEntity);
// 播放技能特效
casterView.playSkillEffect(config.uuid);
console.log(`${casterView.hero_name}${targetView.hero_name} 造成 ${damageResult.value}伤害`);
}
private calculateDamage(caster: ecs.Entity, target: ecs.Entity, config: typeof SkillSet[keyof typeof SkillSet]) {
const result = {
value: 0,
isCrit: false,
isDodged: false,
delay: 0.3, // 默认延迟
ignoreDefense: false,
canCrit: true,
}
// 计算延迟时间
if (config.AnimType === AnimType.parabolic) {
const sourcePos = caster.get(HeroViewComp).node.position;
const targetPos = target.get(HeroViewComp).node.position;
// 计算距离除以速度得到时间
const distance = Math.abs(targetPos.x - sourcePos.x);
result.delay = distance / config.speed;
}
const sourceView = caster.get(HeroViewComp);
let final = sourceView.ap * config.ap / 100;
// 伤害浮动±10%
const damageFloat = 0.9 + Math.random() * 0.2; // 0.9~1.1
final *= damageFloat;
final = Math.round(final);
result.value = Math.max(1, final); // 确保最小伤害为1
result.isCrit = false;
return result;
}
private applyDamage(caster: ecs.Entity, target: ecs.Entity, result: any,skillEntity:ecs.Entity) {
const casterView = caster.get(HeroViewComp);
const targetView = target.get(HeroViewComp);
const skillView = skillEntity.get(SkillCom);
if (!casterView || !targetView || !skillView) return;
// 计算距离
const distance = this.calculateDistance(
skillView.node.position,
targetView.node.position
);
// 假设技能效果移动速度为 500 units/second
const EFFECT_SPEED = 500;
// 计算实际延迟时间(秒)
const actualDelay = distance / EFFECT_SPEED;
this._damageQueue.push({
timer: actualDelay,
callback: () => {
if (!targetView?.ent.has(HeroViewComp)) return;
let remainingDamage = result.value;
if (targetView.shield > 0) {
const shieldAbsorb = Math.min(targetView.shield, remainingDamage);
targetView.shield -= shieldAbsorb;
remainingDamage -= shieldAbsorb;
if (targetView.shield <= 0) {
targetView.BUFFCOMP.show_shield(false);
}
}
if (remainingDamage > 0) {
targetView.hp -= remainingDamage;
if(targetView.hp <= 0) {
targetView.BUFFCOMP.dead()
targetView.to_grave();
}
targetView.showDamage(result.value, true);
} else {
targetView.BUFFCOMP.tooltip(5,"*吸收*");
}
}
});
}
// 添加计算距离的辅助方法
private calculateDistance(pos1: Vec3, pos2: Vec3): number {
const dx = pos2.x - pos1.x;
const dy = pos2.y - pos1.y;
return Math.sqrt(dx * dx + dy * dy);
}
public clear_timer() {
console.log("clear_timer");
Object.values(this._timers).forEach(clearTimeout);
}
onDestroy() {
Object.values(this._timers).forEach(clearTimeout);
}
/** 应用负面状态 */
private applyDebuff(target: ecs.Entity, config: typeof SkillSet[keyof typeof SkillSet]) {
// 实现debuff逻辑...
}
}