Files
heros/assets/script/game/hero/HeroSkills.ts
walkpan 8152523e10 feat(战斗系统): 实现基于技能距离的智能移动和攻击逻辑
重构英雄和怪物移动系统,引入技能距离缓存机制
在HeroAttrsComp中添加技能距离缓存管理
修改HeroSkillsComp以支持技能距离计算
更新移动系统使用技能距离判断攻击时机和停止位置
调整怪物配置统一使用水球技能
2025-11-03 22:59:56 +08:00

266 lines
8.0 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 { Attrs } from "../common/config/HeroAttrs";
import { HeroInfo } from "../common/config/heroSet";
import { SkillSet } from "../common/config/SkillSet";
import { HeroAttrsComp } from "./HeroAttrsComp";
/**
* ==================== 技能槽位数据 ====================
* 单个技能的运行时数据
*/
export interface SkillSlot {
s_uuid: number; // 技能配置ID
cd: number; // 当前CD时间递减
cd_max: number; // 最大CD时间
cost: number; // MP消耗
level: number; // 技能等级(预留)
dis: number; // 攻击距离
}
/**
* ==================== 英雄技能数据组件 ====================
*
* 职责:
* 1. 存储角色拥有的技能列表
* 2. 管理技能CD状态
* 3. 提供技能查询接口
*
* 设计理念:
* - 只存数据,不含施法逻辑
* - CD 更新由 HSkillSystem 负责
* - 施法判定由 HSkillSystem 负责
*/
@ecs.register('HeroSkills')
export class HeroSkillsComp extends ecs.Comp {
// ==================== 技能槽位列表 ====================
/** 技能槽位数组最多4个技能 */
skills: Record<number, SkillSlot> = {};
// ==================== 辅助方法 ====================
/**
* 初始化技能列表
* @param sUuids 技能配置ID数组
* @param uuid 英雄UUID
* @param entity 实体对象(用于更新技能距离缓存)
*/
initSkills(sUuids: number[], uuid: number, entity?: ecs.Entity) {
this.skills = [];
for (let i = 0; i < sUuids.length; i++) {
const s_uuid = sUuids[i];
const config = SkillSet[s_uuid];
if (!config) {
console.warn(`[HeroSkills] 技能配置不存在: ${s_uuid}`);
continue;
}
// 第0个技能的 cd_max 取 herosinfo[uuid].as
const cdMax = i === 0 ? HeroInfo[uuid].as : config.cd;
this.skills[s_uuid] = {
s_uuid: config.uuid,
cd: 0,
cd_max: cdMax,
cost: config.cost,
level: 1,
dis: Number(config.dis),
};
}
// 更新技能距离缓存
if (entity) {
const attrsComp = entity.get(HeroAttrsComp);
if (attrsComp) {
attrsComp.updateSkillDistanceCache(this);
}
}
}
/**
* 添加单个技能
* @param s_uuid 技能配置ID
* @param entity 实体对象(用于更新技能距离缓存)
*/
addSkill(s_uuid: number, entity?: ecs.Entity) {
const config = SkillSet[s_uuid];
if (!config) {
console.warn(`[HeroSkills] 技能配置不存在: ${s_uuid}`);
return;
}
this.skills[s_uuid] = {
s_uuid: config.uuid,
cd: 0,
cd_max: config.cd,
cost: config.cost,
level: 1,
dis: Number(config.dis),
};
// 更新技能距离缓存
if (entity) {
const attrsComp = entity.get(HeroAttrsComp);
if (attrsComp) {
attrsComp.updateSkillDistanceCache(this);
}
}
}
/**
* 获取指定s_uuid的技能
*/
getSkill(s_uuid: number): SkillSlot | null {
return this.skills[s_uuid] ?? null;
}
/**
* 检查技能是否可施放通过s_uuid
* @param s_uuid 技能配置ID
* @param currentMp 当前MP值
*/
canCast(s_uuid: number, currentMp: number): boolean {
const skill = this.getSkill(s_uuid);
if (!skill) return false;
// 检查CD和MP
return skill.cd <= 0 && currentMp >= skill.cost;
}
/**
* 重置技能CD开始冷却通过索引
*/
resetCD(s_uuid: number) {
let attrsCom = this.ent.get(HeroAttrsComp);
if (!attrsCom) return;
const skill = this.getSkill(s_uuid);
if (!skill) return;
// 普通攻击skills[0])受 AS 影响,其余技能受 SS 影响
const isNormalAttack = s_uuid === this.skills[0]?.s_uuid;
const speedAttr = isNormalAttack ? Attrs.AS : Attrs.SS;
const speedBonus = attrsCom.Attrs[speedAttr] / 100; // 100 表示 100% 提速
const speedMultiplier = 1 / (1 + speedBonus); // 提速 100% => cd 减半
skill.cd = skill.cd_max * speedMultiplier;
if (skill) {
skill.cd = skill.cd_max;
}
}
/**
* 更新所有技能CD每帧调用
* @param dt 时间增量
*/
updateCDs(dt: number) {
for (const s_uuid in this.skills) {
const skill = this.skills[Number(s_uuid)];
if (skill.cd > 0) {
skill.cd -= dt;
if (skill.cd < 0) {
skill.cd = 0;
}
}
}
}
/**
* 获取所有可施放的技能索引
*/
getReadySkills(currentMp: number): number[] {
const ready: number[] = [];
for (const s_uuid in this.skills) {
if (this.canCast(Number(s_uuid), currentMp)) {
ready.push(Number(s_uuid));
}
}
return ready;
}
/**
* 检查技能攻击距离是否足够
* @param s_uuid 技能配置ID
* @param distance 目标距离
* @returns 是否在攻击范围内
*/
canReachTarget(s_uuid: number, distance: number): boolean {
const skill = this.getSkill(s_uuid);
if (!skill) {
return false;
}
return distance <= skill.dis;
}
/**
* 获取技能的攻击距离
* @param s_uuid 技能配置ID
* @returns 攻击距离如果技能不存在返回0
*/
getSkillDistance(s_uuid: number): number {
const skill = this.getSkill(s_uuid);
return skill ? skill.dis : 0;
}
/**
* 获取可施放技能中的最远攻击距离
* @param mp 当前MP值
* @returns 最远攻击距离如果没有可用技能返回0
*/
getMaxSkillDistance(mp: number): number {
const readySkills = this.getReadySkills(mp);
if (readySkills.length === 0) return 0;
let maxDistance = 0;
for (const s_uuid of readySkills) {
const skill = this.getSkill(s_uuid);
if (skill && skill.dis > maxDistance) {
maxDistance = skill.dis;
}
}
return maxDistance;
}
/**
* 获取可施放技能中的最近攻击距离
* @param mp 当前MP值
* @returns 最近攻击距离如果没有可用技能返回0
*/
getMinSkillDistance(mp: number): number {
const readySkills = this.getReadySkills(mp);
if (readySkills.length === 0) return 0;
let minDistance = Number.MAX_VALUE;
for (const s_uuid of readySkills) {
const skill = this.getSkill(s_uuid);
if (skill && skill.dis < minDistance) {
minDistance = skill.dis;
}
}
return minDistance === Number.MAX_VALUE ? 0 : minDistance;
}
/**
* 获取所有技能中的最小攻击距离不考虑MP限制
* 用于移动停止判断,让英雄在合适位置等待回蓝
* @returns 最小攻击距离如果没有技能返回0
*/
getAbsoluteMinSkillDistance(): number {
const skillIds = Object.keys(this.skills).map(Number);
if (skillIds.length === 0) return 0;
let minDistance = Number.MAX_VALUE;
for (const s_uuid of skillIds) {
const skill = this.getSkill(s_uuid);
if (skill && skill.dis < minDistance) {
minDistance = skill.dis;
}
}
return minDistance === Number.MAX_VALUE ? 0 : minDistance;
}
reset() {
this.skills = {};
}
}