Files
heros/assets/script/game/skill/HeroSkillSystem.ts
2025-02-08 10:57:27 +08:00

390 lines
14 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;
case CdType.HeroPower:
return view.pw >= view.pwm;
}
}
/** 施放技能 */
private castSkill(caster: ecs.Entity, skillId: number, config: typeof SkillSet[keyof typeof SkillSet]) {
const view = caster.get(HeroViewComp);
const comp = caster.get(HeroSkillsComp);
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;
case CdType.HeroPower:
view.as.max()
view.pw = view.pw-view.pwm; // 重置能量计时器
break;
}
// 选择目标
const targets = this.selectTargets(caster, config);
if (targets.length === 0) return;
// 创建技能实体
const skillEntity = ecs.getEntity<Skill>(Skill);
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);
});
}
/** 选择技能目标 */
private selectTargets(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;
// 第一阶段:基础目标筛选
let candidates = ecs.query(ecs.allOf(HeroViewComp)).filter(e => {
const view = e.get(HeroViewComp);
// 根据技能目标类型筛选
switch(config.TargetGroup) {
case TargetGroup.Enemy:
return view.fac !== team;
case TargetGroup.Ally:
return view.fac === team && e !== caster;
case TargetGroup.Self:
return e === caster;
default:
return true;
}
});
// 第二阶段:位置/血量等精细筛选
switch(config.TargetType) {
case TargetType.Frontline:
return this.filterFrontRow(candidates, isEnemyTeam);
case TargetType.Backline:
return this.filterBackRow(candidates, isEnemyTeam);
case TargetType.LowestHP:
return this.filterLowestHealth(candidates);
case TargetType.HighestHP:
return this.filterHighestHealth(candidates);
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 filterBackRow(entities: ecs.Entity[], isEnemyTeam: number): ecs.Entity[] {
// 敌方最后排是x坐标最小的我方最后排是x坐标最大的
const keyPos = isEnemyTeam ?
Math.max(...entities.map(e => e.get(HeroViewComp).node.position.x)) :
Math.min(...entities.map(e => e.get(HeroViewComp).node.position.x));
return entities.filter(e =>
Math.abs(e.get(HeroViewComp).node.position.x - keyPos) < 10
);
}
/** 筛选血量最低单位 */
private filterLowestHealth(entities: ecs.Entity[]): ecs.Entity[] {
const minHp = Math.min(...entities.map(e => e.get(HeroViewComp).hp));
return entities.filter(e => e.get(HeroViewComp).hp === minHp);
}
/** 筛选血量最高单位 */
private filterHighestHealth(entities: ecs.Entity[]): ecs.Entity[] {
const maxHp = Math.max(...entities.map(e => e.get(HeroViewComp).hp));
return entities.filter(e => e.get(HeroViewComp).hp === maxHp);
}
/** 随机选择目标 */
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]) {
const casterView = caster.get(HeroViewComp);
const targetView = target.get(HeroViewComp);
// 直接计算伤害(包含防御减免)
const damageResult = this.calculateDamage(caster, target, config);
this.applyDamage(target, damageResult);
// 播放技能特效
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 targetView = target.get(HeroViewComp);
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);
// 闪避判定
if (Math.random()*100 < targetView.dodge) {
result.isDodged = true
return result
}
// 护甲减伤
if (!result.ignoreDefense) {
const effectiveArmor = Math.min(targetView.def, 300); // 最大减伤75%
const damageReduction = effectiveArmor / (effectiveArmor + 100);
final *= (1 - damageReduction);
final = Math.round(final); // 四舍五入取整
}
// 暴击判定
let isCrit = false;
if (result.canCrit) {
const critRate = sourceView.crit;
if (Math.random() * 100 < critRate) {
final *= 1.5;
isCrit = true;
}
}
result.value = Math.max(1, final); // 确保最小伤害为1
result.isCrit = isCrit;
return result;
}
private applyDamage(target: ecs.Entity, result: any) {
this._damageQueue.push({
timer: result.delay,
callback: () => {
const view = target.get(HeroViewComp);
if (!view?.ent.has(HeroViewComp)) return;
if(result.isDodged){
view.BUFFCOMP.tooltip(5,"*闪避*");
return;
}
let remainingDamage = result.value;
if (view.shield > 0) {
const shieldAbsorb = Math.min(view.shield, remainingDamage);
view.shield -= shieldAbsorb;
remainingDamage -= shieldAbsorb;
if (view.shield <= 0) {
view.BUFFCOMP.show_shield(false);
}
}
if (remainingDamage > 0) {
view.hp -= remainingDamage;
if(view.hp <= 0) {
view.BUFFCOMP.dead()
view.exp_add(view.dexp)
view.to_grave();
}
view.showDamage(result.value, result.isCrit);
} else {
view.BUFFCOMP.tooltip(5,"*吸收*");
}
}
});
}
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逻辑...
}
/** 外部调用重置冷却 */
public resetSkillCooldown(entity: ecs.Entity, skillId: number) {
const comp = entity.get(HeroSkillsComp);
comp.resetCooldown(skillId);
}
/** 重置所有技能冷却 */
public resetAllCooldowns(entity: ecs.Entity) {
const comp = entity.get(HeroSkillsComp);
comp.resetAllCooldowns();
}
}