5 Commits

Author SHA1 Message Date
8c597ae008 refactor(skill): 技能初步完成,下一步完善伤害系统
重命名SMoveData组件并修复组件添加方式
将SMoveData组件重命名为SMoveDataComp以保持命名一致性
修复Skill类中组件添加方式,明确指定组件类型参数
2025-10-31 11:06:17 +08:00
3b21ee4048 refactor(技能系统): 重构技能施放系统并优化位置初始化
- 将SkillCastSystem重命名为SCastSystem和SACastSystem以区分不同功能
- 优化SMoveComp中位置属性的初始化,改为null避免不必要的对象创建
- 统一日志前缀使用系统名称提高可读性
- 在SACastSystem中添加目标位置检查逻辑
- 修复代码格式问题,统一缩进和注释风格
2025-10-31 11:05:44 +08:00
2b3b80b308 refactor(技能系统): 重构技能系统以使用s_uuid作为主键并优化技能施放逻辑
- 将HeroSkillsComp中的技能数组改为以s_uuid为键的对象存储
- 修改CSRequestComp使用s_uuid替代skillIndex
- 优化SkillCastSystem和SACastSystem的施放逻辑
- 为SMoveDataComp添加rePos方法处理技能位置计算
- 移除未使用的SDataComSystem代码
2025-10-31 10:47:05 +08:00
b38e63e200 refactor(skill): 重构技能组件目录结构并重命名施法请求组件
将技能相关组件从hero目录移动到skill目录
将CastSkillRequestComp重命名为CSRequestComp
更新相关引用和文档说明
2025-10-31 09:22:50 +08:00
a1c605238d refactor(hero): 重构技能系统变量命名和自动施法系统
将技能相关变量名从skillId改为s_uuid以提高一致性
重命名自动施法系统文件并优化目标选择方法命名
删除旧版自动施法系统文件,新增重构后的实现
2025-10-31 09:00:37 +08:00
19 changed files with 479 additions and 417 deletions

View File

@@ -18,7 +18,7 @@
## 更新摘要
**已做更改**
- 更新了组件注册机制部分以反映在多个系统中添加ECS注册装饰器的更改
- 添加了关于技能系统的新部分包括CastSkillRequestComp、SkillCastSystem、SkillCDSystem和SkillAutocastSystem
- 添加了关于技能系统的新部分包括CSRequestComp、SkillCastSystem、SkillCDSystem和SkillAutocastSystem
- 更新了实际案例分析,以包含新的技能系统实现
- 在扩展开发指南中添加了新的系统接口示例
- 更新了文档来源以包含新分析的文件
@@ -355,7 +355,7 @@ HeroViewComp实现了复杂的BUFF/DEBUFF管理系统
```mermaid
classDiagram
class CastSkillRequestComp {
class CSRequestComp {
+number skillIndex
+Vec3[] targetPositions
+reset() void
@@ -424,14 +424,14 @@ participant Cast as SkillCastSystem
participant Entity as Entity
participant Skill as SkillEnt
Auto->>Entity : update()
Auto->>Entity : add(CastSkillRequestComp)
Auto->>Entity : add(CSRequestComp)
Cast->>Entity : entityEnter()
Cast->>Entity : checkCastConditions()
Cast->>Entity : executeCast()
Cast->>Entity : playSkillEffect()
Cast->>Cast : createSkillEntity()
Cast->>Skill : load()
Cast->>Entity : remove(CastSkillRequestComp)
Cast->>Entity : remove(CSRequestComp)
```
**图表来源**
@@ -447,7 +447,7 @@ Cast->>Entity : remove(CastSkillRequestComp)
- `SkillCDSystem`:负责管理"技能冷却"
2. **标记驱动设计**
- 使用`CastSkillRequestComp`作为标记组件
- 使用`CSRequestComp`作为标记组件
- 避免了直接调用系统方法的耦合
- 符合ECS的声明式编程理念

View File

@@ -239,7 +239,7 @@ SkillConfig --> BuffConf
```mermaid
classDiagram
class CastSkillRequestComp {
class CSRequestComp {
+skillIndex : number
+targetPositions : Vec3[]
+reset() void
@@ -261,9 +261,9 @@ class SkillAutocastSystem {
+update(e : Entity) void
+selectTargets() Vec3[]
}
CastSkillRequestComp --> SkillCastSystem
CSRequestComp --> SkillCastSystem
SkillCastSystem --> SkillCDSystem
SkillAutocastSystem --> CastSkillRequestComp
SkillAutocastSystem --> CSRequestComp
```
**章节来源**

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "d2d775f5-1dc0-455e-89d7-4a17f43170e0",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -119,7 +119,7 @@ export class HeroLifecycleSystem extends ecs.ComblockSystem
implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem {
filter() {
return ecs.allOf(HeroAttrsComp);
return ecs.allOf(HeroMoveComp);
}
entityEnter(e: ecs.Entity): void {

View File

@@ -6,7 +6,7 @@ import { SkillSet } from "../common/config/SkillSet";
* 单个技能的运行时数据
*/
export interface SkillSlot {
uuid: number; // 技能配置ID
s_uuid: number; // 技能配置ID
cd: number; // 当前CD时间递减
cd_max: number; // 最大CD时间
cost: number; // MP消耗
@@ -31,66 +31,66 @@ export class HeroSkillsComp extends ecs.Comp {
// ==================== 技能槽位列表 ====================
/** 技能槽位数组最多4个技能 */
skills: SkillSlot[] = [];
skills: Record<number, SkillSlot> = {};
// ==================== 辅助方法 ====================
/**
* 初始化技能列表
* @param skillIds 技能配置ID数组
* @param sUuids 技能配置ID数组
*/
initSkills(skillIds: number[]) {
initSkills(sUuids: number[]) {
this.skills = [];
for (const skillId of skillIds) {
const config = SkillSet[skillId];
for (const s_uuid of sUuids) {
const config = SkillSet[s_uuid];
if (!config) {
console.warn(`[HeroSkills] 技能配置不存在: ${skillId}`);
console.warn(`[HeroSkills] 技能配置不存在: ${s_uuid}`);
continue;
}
this.skills.push({
uuid: config.uuid,
cd: 0, // 初始CD为0可立即施放
this.skills[s_uuid]={
s_uuid: config.uuid,
cd: 0,
cd_max: config.cd,
cost: config.cost,
level: 1
});
level: 1,
}
}
}
/**
* 添加单个技能
*/
addSkill(skillId: number) {
const config = SkillSet[skillId];
addSkill(s_uuid: number) {
const config = SkillSet[s_uuid];
if (!config) {
console.warn(`[HeroSkills] 技能配置不存在: ${skillId}`);
console.warn(`[HeroSkills] 技能配置不存在: ${s_uuid}`);
return;
}
this.skills.push({
uuid: config.uuid,
this.skills[s_uuid] = {
s_uuid: config.uuid,
cd: 0,
cd_max: config.cd,
cost: config.cost,
level: 1
});
};
}
/**
* 获取指定索引的技能
* 获取指定s_uuid的技能
*/
getSkill(index: number): SkillSlot | null {
return this.skills[index] ?? null;
getSkill(s_uuid: number): SkillSlot | null {
return this.skills[s_uuid] ?? null;
}
/**
* 检查技能是否可施放
* 检查技能是否可施放(通过索引)
* @param index 技能索引
* @param currentMp 当前MP值
*/
canCast(index: number, currentMp: number): boolean {
const skill = this.getSkill(index);
canCast(s_uuid: number, currentMp: number): boolean {
const skill = this.getSkill(s_uuid);
if (!skill) return false;
// 检查CD和MP
@@ -98,11 +98,35 @@ export class HeroSkillsComp extends ecs.Comp {
}
/**
* 重置技能CD开始冷却
* 检查技能是否可施放通过s_uuid
* @param s_uuid 技能配置ID
* @param currentMp 当前MP值
*/
canCastByUuid(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开始冷却通过索引
* @param index 技能索引
*/
resetCD(index: number) {
const skill = this.getSkill(index);
resetCD(s_uuid: number) {
const skill = this.getSkill(s_uuid);
if (skill) {
skill.cd = skill.cd_max;
}
}
/**
* 重置技能CD开始冷却通过s_uuid
* @param s_uuid 技能配置ID
*/
resetCDByUuid(s_uuid: number) {
const skill = this.getSkill(s_uuid);
if (skill) {
skill.cd = skill.cd_max;
}
@@ -113,7 +137,8 @@ export class HeroSkillsComp extends ecs.Comp {
* @param dt 时间增量
*/
updateCDs(dt: number) {
for (const skill of this.skills) {
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) {
@@ -128,15 +153,15 @@ export class HeroSkillsComp extends ecs.Comp {
*/
getReadySkills(currentMp: number): number[] {
const ready: number[] = [];
for (let i = 0; i < this.skills.length; i++) {
if (this.canCast(i, currentMp)) {
ready.push(i);
for (const s_uuid in this.skills) {
if (this.canCastByUuid(Number(s_uuid), currentMp)) {
ready.push(Number(s_uuid));
}
}
return ready;
}
reset() {
this.skills = [];
this.skills = {};
}
}

View File

@@ -18,7 +18,6 @@ const { ccclass, property } = _decorator;
/** 角色显示组件 */
export interface BuffInfo {
attr: Attrs;
value: number;
remainTime?: number;
}

View File

@@ -106,7 +106,7 @@ export class MonLifecycleSystem extends ecs.ComblockSystem
implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem {
filter() {
return ecs.allOf(HeroAttrsComp);
return ecs.allOf(MonMoveComp);
}
entityEnter(e: ecs.Entity): void {

View File

@@ -0,0 +1,277 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3, v3 } from "cc";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { SkillSet, SType } from "../common/config/SkillSet";
import { HeroSkillsComp, SkillSlot } from "./HeroSkills";
import { Skill } from "../skill/Skill";
/**
* ==================== 自动施法系统 ====================
*
* 职责:
* 1. 检测可施放的技能
* 2. 根据策略自动施法AI
* 3. 选择目标
* 4. 添加施法请求标记
*
* 设计理念:
* - 负责"何时施法"的决策
* - 通过添加 CSRequestComp 触发施法
* - 可被玩家输入系统或AI系统复用
* - 支持多种AI策略
*/
@ecs.register('SACastSystem')
export class SACastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
filter(): ecs.IMatcher {
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, HeroViewComp);
}
update(e: ecs.Entity): void {
const skills = e.get(HeroSkillsComp);
const heroAttrs = e.get(HeroAttrsComp);
const heroView = e.get(HeroViewComp);
if (!skills || !heroAttrs || !heroView) return;
// 检查基本条件
if (heroAttrs.is_dead || heroAttrs.isStun() || heroAttrs.isFrost()) return;
// 检查是否正在攻击(只有攻击时才释放技能)
if (!heroAttrs.is_atking) return;
// 获取所有可施放的技能
const readySkills = skills.getReadySkills(heroAttrs.mp);
if (readySkills.length === 0) return;
// 选择第一个可施放的伤害技能
for (const s_uuid of readySkills) {
const skill = skills.getSkill(s_uuid);
if (!skill) continue;
const config = SkillSet[skill.s_uuid];
if (!config || config.SType !== SType.damage) continue;
// ✅ 开始执行施法
this.startCast(e,skill);
// 一次只施放一个技能
break;
}
}
private startCast(e: ecs.Entity,skill:SkillSlot): void {
if (!skill||!e) return
const skills = e.get(HeroSkillsComp);
const heroAttrs = e.get(HeroAttrsComp);
const heroView = e.get(HeroViewComp);
// 3. 检查施法条件
if (!this.checkCastConditions(skills, heroAttrs, skill.s_uuid)) return
// 4. 执行施法
this.executeCast(e, skill.s_uuid, heroView);
// 5. 扣除资源和重置CD
heroAttrs.mp -= skill.cost;
skills.resetCD(skill.s_uuid);
}
/**
* 检查施法条件
*/
private checkCastConditions(skills: HeroSkillsComp, heroAttrs: HeroAttrsComp, s_uuid: number): boolean {
// 检查角色状态
if (heroAttrs.is_dead) {
return false;
}
// 检查控制状态(眩晕、冰冻)
if (heroAttrs.isStun() || heroAttrs.isFrost()) {
return false;
}
// 检查CD和MP
if (!skills.canCast(s_uuid, heroAttrs.mp)) {
return false;
}
return true;
}
/**
* 执行施法
*/
private executeCast(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp) {
const config = SkillSet[s_uuid];
if (!config) {
console.error("[SACastSystem] 技能配置不存在:", s_uuid);
return;
}
// 1. 播放施法动画
heroView.playSkillEffect(s_uuid);
// 2. 延迟创建技能实体(等待动画)
const delay = config.with ?? 0.3; // 施法前摇时间
heroView.scheduleOnce(() => {
this.createSkill(s_uuid, heroView);
}, delay);
const heroAttrs = casterEntity.get(HeroAttrsComp);
console.log(`[SACastSystem] ${heroAttrs?.hero_name ?? '未知'} 施放技能: ${config.name}`);
}
/**
* 创建技能实体
*/
private createSkill(s_uuid: number, caster: HeroViewComp) {
// 检查节点有效性
if (!caster.node || !caster.node.isValid) {
console.warn("[SACastSystem] 施法者节点无效");
return;
}
// 获取场景节点
const parent = caster.node.parent;
if (!parent) {
console.warn("[SACastSystem] 场景节点无效");
return;
}
// 获取目标位置
const targets = this.sTargets(caster);
if (targets.length === 0) {
console.warn("[SACastSystem] 没有找到有效目标");
return;
}
// 创建技能实体
const skill = ecs.getEntity<Skill>(Skill);
// 获取施法者位置作为起始位置
const startPos = caster.node.position.clone();
const targetPos = targets[0]; // 使用第一个目标位置
console.log(`[SACastSystem]: ${s_uuid}, 起始位置: ${startPos}, 目标位置: ${targetPos}`);
// 加载技能实体(包括预制体、组件初始化等)
skill.load(startPos, parent, s_uuid, targetPos, caster);
}
/**
* 选择目标位置
*/
private sTargets(caster: HeroViewComp): Vec3[] {
// 简化版:选择最前方的敌人
const targets: Vec3[] = [];
// 这里可以调用 SkillConComp 的目标选择逻辑
// 暂时返回默认位置
const heroAttrs = caster.ent.get(HeroAttrsComp);
const fac = heroAttrs?.fac ?? 0;
const defaultX = fac === 0 ? 400 : -400;
targets.push(v3(defaultX, 0, 0));
return targets;
}
/**
* 选择伤害技能目标
*/
private sDamageTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] {
const targets: Vec3[] = [];
const heroAttrs = caster.ent.get(HeroAttrsComp);
if (!heroAttrs) return targets;
// 寻找最近的敌人
const enemyPositions = this.findNearbyEnemies(caster, heroAttrs.fac, config.range || 300);
// 选择最多maxTargets个目标
for (let i = 0; i < Math.min(maxTargets, enemyPositions.length); i++) {
targets.push(enemyPositions[i]);
}
// 如果没有找到敌人,使用默认位置
if (targets.length === 0) {
targets.push(...this.sDefaultTargets(caster, heroAttrs.fac));
}
return targets;
}
/**
* 选择治疗技能目标
*/
private sHealTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] {
const targets: Vec3[] = [];
const heroAttrs = caster.ent.get(HeroAttrsComp);
if (!heroAttrs) return targets;
// 寻找血量最低的友军
const allyPositions = this.findLowHealthAllies(caster, heroAttrs.fac, config.range || 200);
for (let i = 0; i < Math.min(maxTargets, allyPositions.length); i++) {
targets.push(allyPositions[i]);
}
// 如果没有找到友军,治疗自己
if (targets.length === 0 && caster.node) {
targets.push(caster.node.position.clone());
}
return targets;
}
/**
* 选择BUFF技能目标
*/
private sBuffTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] {
// BUFF技能通常施放在自己或友军身上
return this.sHealTargets(caster, config, maxTargets);
}
/**
* 选择DEBUFF技能目标
*/
private sDebuffTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] {
// DEBUFF技能通常施放在敌人身上
return this.sDamageTargets(caster, config, maxTargets);
}
/**
* 选择默认目标
*/
private sDefaultTargets(caster: HeroViewComp, faction: number): Vec3[] {
const targets: Vec3[] = [];
const defaultX = faction === 0 ? 400 : -400;
targets.push(v3(defaultX, 0, 0));
return targets;
}
/**
* 查找附近的敌人
*/
private findNearbyEnemies(caster: HeroViewComp, faction: number, range: number): Vec3[] {
// 简化实现实际应该查询ECS中的敌方实体
const enemies: Vec3[] = [];
// 模拟敌人位置
const enemyX = faction === 0 ? 300 : -300;
enemies.push(v3(enemyX, 0, 0));
enemies.push(v3(enemyX + 50, 20, 0));
return enemies;
}
/**
* 查找血量低的友军
*/
private findLowHealthAllies(caster: HeroViewComp, faction: number, range: number): Vec3[] {
// 简化实现实际应该查询ECS中的友方实体并按血量排序
const allies: Vec3[] = [];
// 如果自己血量低,优先治疗自己
return allies;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "e76866cc-7379-436f-91ff-e71e790580a9",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,183 +0,0 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3, v3 } from "cc";
import { CastSkillRequestComp } from "./STagComps";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { SkillSet, SType } from "../common/config/SkillSet";
import { HeroSkillsComp } from "./HeroSkills";
/**
* ==================== 自动施法系统 ====================
*
* 职责:
* 1. 检测可施放的技能
* 2. 根据策略自动施法AI
* 3. 选择目标
* 4. 添加施法请求标记
*
* 设计理念:
* - 负责"何时施法"的决策
* - 通过添加 CastSkillRequestComp 触发施法
* - 可被玩家输入系统或AI系统复用
* - 支持多种AI策略
*/
@ecs.register('SkillAutocastSystem')
export class SkillAutocastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
filter(): ecs.IMatcher {
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, HeroViewComp);
}
update(e: ecs.Entity): void {
const skills = e.get(HeroSkillsComp);
const heroModel = e.get(HeroAttrsComp);
const heroView = e.get(HeroViewComp);
if (!skills || !heroModel || !heroView) return;
// 检查基本条件
if (heroModel.is_dead || heroModel.isStun() || heroModel.isFrost()) return;
// 检查是否正在攻击(只有攻击时才释放技能)
if (!heroModel.is_atking) return;
// 获取所有可施放的技能
const readySkills = skills.getReadySkills(heroModel.mp);
if (readySkills.length === 0) return;
// 选择第一个可施放的伤害技能
for (const skillIndex of readySkills) {
const skill = skills.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;
}
/**
* 选择伤害技能目标
*/
private selectDamageTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] {
const targets: Vec3[] = [];
const heroModel = caster.ent.get(HeroAttrsComp);
if (!heroModel) return targets;
// 寻找最近的敌人
const enemyPositions = this.findNearbyEnemies(caster, heroModel.fac, config.range || 300);
// 选择最多maxTargets个目标
for (let i = 0; i < Math.min(maxTargets, enemyPositions.length); i++) {
targets.push(enemyPositions[i]);
}
// 如果没有找到敌人,使用默认位置
if (targets.length === 0) {
targets.push(...this.selectDefaultTargets(caster, heroModel.fac));
}
return targets;
}
/**
* 选择治疗技能目标
*/
private selectHealTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] {
const targets: Vec3[] = [];
const heroModel = caster.ent.get(HeroAttrsComp);
if (!heroModel) return targets;
// 寻找血量最低的友军
const allyPositions = this.findLowHealthAllies(caster, heroModel.fac, config.range || 200);
for (let i = 0; i < Math.min(maxTargets, allyPositions.length); i++) {
targets.push(allyPositions[i]);
}
// 如果没有找到友军,治疗自己
if (targets.length === 0 && caster.node) {
targets.push(caster.node.position.clone());
}
return targets;
}
/**
* 选择BUFF技能目标
*/
private selectBuffTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] {
// BUFF技能通常施放在自己或友军身上
return this.selectHealTargets(caster, config, maxTargets);
}
/**
* 选择DEBUFF技能目标
*/
private selectDebuffTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] {
// DEBUFF技能通常施放在敌人身上
return this.selectDamageTargets(caster, config, maxTargets);
}
/**
* 选择默认目标
*/
private selectDefaultTargets(caster: HeroViewComp, faction: number): Vec3[] {
const targets: Vec3[] = [];
const defaultX = faction === 0 ? 400 : -400;
targets.push(v3(defaultX, 0, 0));
return targets;
}
/**
* 查找附近的敌人
*/
private findNearbyEnemies(caster: HeroViewComp, faction: number, range: number): Vec3[] {
// 简化实现实际应该查询ECS中的敌方实体
const enemies: Vec3[] = [];
// 模拟敌人位置
const enemyX = faction === 0 ? 300 : -300;
enemies.push(v3(enemyX, 0, 0));
enemies.push(v3(enemyX + 50, 20, 0));
return enemies;
}
/**
* 查找血量低的友军
*/
private findLowHealthAllies(caster: HeroViewComp, faction: number, range: number): Vec3[] {
// 简化实现实际应该查询ECS中的友方实体并按血量排序
const allies: Vec3[] = [];
// 如果自己血量低,优先治疗自己
return allies;
}
}

View File

@@ -1,17 +1,18 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3, v3 } from "cc";
import { CastSkillRequestComp } from "./STagComps";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { SkillSet, SType } from "../common/config/SkillSet";
import { SkillEnt } from "../skill/SkillEnt";
import { HeroSkillsComp } from "./HeroSkills";
import { Skill } from "../skill/Skill";
import { CSRequestComp } from "../skill/STagComps";
/**
* ==================== 技能施法系统 ====================
* ==================== 技能施法系统 手动施法====================
*
* 职责:
* 1. 监听 CastSkillRequestComp 标记组件
* 1. 监听 CSRequestComp 标记组件
* 2. 检查施法条件CD、MP、状态
* 3. 扣除资源MP
* 4. 创建技能实体
@@ -23,73 +24,73 @@ import { HeroSkillsComp } from "./HeroSkills";
* - 施法检查与执行分离
* - 自动处理资源消耗和CD重置
*/
@ecs.register('SkillCastSystem')
export class SkillCastSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
// @ecs.register('SCastSystem')
export class SCastSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
/**
* 过滤器:拥有技能数据 + 施法请求的实体
*/
filter(): ecs.IMatcher {
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, CastSkillRequestComp);
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, CSRequestComp);
}
/**
* 实体进入时触发(即请求施法时)
*/
entityEnter(e: ecs.Entity): void {
const skillsData = e.get(HeroSkillsComp);
const heroModel = e.get(HeroAttrsComp);
const request = e.get(CastSkillRequestComp);
const skillsComp = e.get(HeroSkillsComp);
const heroAttrs = e.get(HeroAttrsComp);
const request = e.get(CSRequestComp);
const heroView = e.get(HeroViewComp);
// 1. 验证数据完整性
if (!skillsData || !heroModel || !request || !heroView) {
console.warn("[SkillCastSystem] 数据不完整,取消施法");
e.remove(CastSkillRequestComp);
if (!skillsComp || !heroAttrs || !request || !heroView) {
console.warn("[SCastSystem] 数据不完整,取消施法");
e.remove(CSRequestComp);
return;
}
// 2. 获取技能数据
const skill = skillsData.getSkill(request.skillIndex);
const skill = skillsComp.getSkill(request.s_uuid);
if (!skill) {
console.warn(`[SkillCastSystem] 技能索引无效: ${request.skillIndex}`);
e.remove(CastSkillRequestComp);
console.warn(`[SCastSystem] 技能索引无效: ${request.s_uuid }`);
e.remove(CSRequestComp);
return;
}
// 3. 检查施法条件
if (!this.checkCastConditions(skillsData, heroModel, request.skillIndex)) {
e.remove(CastSkillRequestComp);
if (!this.checkCastConditions(skillsComp, heroAttrs, request.s_uuid)) {
e.remove(CSRequestComp);
return;
}
// 4. 执行施法
this.executeCast(e, skill, request.targetPositions, heroView);
this.executeCast(e, skill, request.targets, heroView);
// 5. 扣除资源和重置CD
heroModel.mp -= skill.cost;
skillsData.resetCD(request.skillIndex);
heroAttrs.mp -= skill.cost;
skillsComp.resetCD(request.s_uuid);
// 6. 移除请求标记
e.remove(CastSkillRequestComp);
e.remove(CSRequestComp);
}
/**
* 检查施法条件
*/
private checkCastConditions(skillsData: HeroSkillsComp, heroModel: HeroAttrsComp, skillIndex: number): boolean {
private checkCastConditions(skillsComp: HeroSkillsComp, heroAttrs: HeroAttrsComp, skillIndex: number): boolean {
// 检查角色状态
if (heroModel.is_dead) {
if (heroAttrs.is_dead) {
return false;
}
// 检查控制状态(眩晕、冰冻)
if (heroModel.isStun() || heroModel.isFrost()) {
if (heroAttrs.isStun() || heroAttrs.isFrost()) {
return false;
}
// 检查CD和MP
if (!skillsData.canCast(skillIndex, heroModel.mp)) {
if (!skillsComp.canCast(skillIndex, heroAttrs.mp)) {
return false;
}
@@ -99,10 +100,10 @@ export class SkillCastSystem extends ecs.ComblockSystem implements ecs.IEntityEn
/**
* 执行施法
*/
private executeCast(casterEntity: ecs.Entity, skill: any, targetPositions: Vec3[], heroView: HeroViewComp) {
private executeCast(casterEntity: ecs.Entity, skill: any, targets: Vec3[], heroView: HeroViewComp) {
const config = SkillSet[skill.uuid];
if (!config) {
console.error("[SkillCastSystem] 技能配置不存在:", skill.uuid);
console.error("[SCastSystem] 技能配置不存在:", skill.uuid);
return;
}
@@ -112,38 +113,32 @@ export class SkillCastSystem extends ecs.ComblockSystem implements ecs.IEntityEn
// 2. 延迟创建技能实体(等待动画)
const delay = config.with ?? 0.3; // 施法前摇时间
heroView.scheduleOnce(() => {
this.createSkillEntity(skill.uuid, heroView, targetPositions);
this.createSkill(skill.uuid, heroView, targets);
}, delay);
const heroModel = casterEntity.get(HeroAttrsComp);
console.log(`[SkillCastSystem] ${heroModel?.hero_name ?? '未知'} 施放技能: ${config.name}`);
const heroAttrs = casterEntity.get(HeroAttrsComp);
console.log(`[SCastSystem] ${heroAttrs?.hero_name ?? '未知'} 施放技能: ${config.name}`);
}
/**
* 创建技能实体
*/
private createSkillEntity(skillId: number, caster: HeroViewComp, targetPositions: Vec3[]) {
private createSkill(skillId: number, caster: HeroViewComp, targets: Vec3[]) {
// 检查节点有效性
if (!caster.node || !caster.node.isValid) {
console.warn("[SkillCastSystem] 施法者节点无效");
console.warn("[SCastSystem] 施法者节点无效");
return;
}
// 获取场景节点
const parent = caster.node.parent;
if (!parent) {
console.warn("[SkillCastSystem] 场景节点无效");
console.warn("[SCastSystem] 场景节点无效");
return;
}
// ✅ 使用现有的 SkillEnt 创建技能
const skillEnt = ecs.getEntity<SkillEnt>(SkillEnt);
skillEnt.load(
caster.node.position, // 起始位置
parent, // 父节点
skillId, // 技能ID
targetPositions, // 目标位置数组
caster, // 施法者
);
// const skill = ecs.getEntity<Skill>(Skill);
}
}

View File

@@ -1,106 +0,0 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3 } from "cc";
/**
* ==================== 技能数据组件 ====================
*
* 用途:
* - 存储单个技能的完整数据
* - 包含技能配置、状态、目标等信息
* - 可被技能系统读取和修改
*/
@ecs.register('SkillData')
export class SkillDataComp extends ecs.Comp {
/** 技能唯一ID */
skillId: number = 0;
/** 技能名称 */
name: string = "";
/** 技能等级 */
level: number = 1;
/** 技能类型 */
type: number = 0;
/** 消耗MP */
cost: number = 0;
/** 冷却时间 */
cooldown: number = 0;
/** 当前冷却剩余时间 */
currentCooldown: number = 0;
/** 伤害值 */
damage: number = 0;
/** 技能范围 */
range: number = 0;
/** 施法时间 */
castTime: number = 0;
/** 技能描述 */
description: string = "";
/** 是否已解锁 */
unlocked: boolean = false;
/** 技能图标路径 */
iconPath: string = "";
/** 额外属性数据 */
extraData: any = null;
reset() {
this.skillId = 0;
this.name = "";
this.level = 1;
this.type = 0;
this.cost = 0;
this.cooldown = 0;
this.currentCooldown = 0;
this.damage = 0;
this.range = 0;
this.castTime = 0;
this.description = "";
this.unlocked = false;
this.iconPath = "";
this.extraData = null;
}
/**
* 检查技能是否可以施放
*/
canCast(currentMP: number): boolean {
return this.unlocked &&
this.currentCooldown <= 0 &&
currentMP >= this.cost;
}
/**
* 更新冷却时间
*/
updateCooldown(deltaTime: number): void {
if (this.currentCooldown > 0) {
this.currentCooldown = Math.max(0, this.currentCooldown - deltaTime);
}
}
/**
* 重置冷却时间
*/
resetCooldown(): void {
this.currentCooldown = this.cooldown;
}
/**
* 获取冷却进度 (0-1)
*/
getCooldownProgress(): number {
if (this.cooldown <= 0) return 1;
return Math.max(0, 1 - (this.currentCooldown / this.cooldown));
}
}

View File

@@ -1,30 +1,33 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { BoxSet } from "../common/config/BoxSet";
import { HeroViewComp } from "../hero/HeroViewComp";
/** 业务层对象 */
@ecs.register('SDataCom')
export class SDataCom extends ecs.Comp {
/** 业务层组件移除时,重置所有数据为默认值 */
attrs:any=null
caster:HeroViewComp=null
group:BoxSet=BoxSet.HERO
s_uuid:number=0
reset() {
this.attrs=null
this.group=0
this.s_uuid=0
this.caster=null
}
}
/** 业务层业务逻辑处理对象 */
export class SDataComSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
filter(): ecs.IMatcher {
return ecs.allOf(SDataCom);
}
// /** 业务层业务逻辑处理对象 */
// export class SDataComSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
// filter(): ecs.IMatcher {
// return ecs.allOf(SDataCom);
// }
entityEnter(e: ecs.Entity): void {
// 注:自定义业务逻辑
// entityEnter(e: ecs.Entity): void {
// // 注:自定义业务逻辑
e.remove(SDataCom);
}
}
// e.remove(SDataCom);
// }
// }

View File

@@ -2,16 +2,17 @@ import { Vec3, v3 } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { BezierMove } from "../BezierMove/BezierMove";
import { RType, SkillSet } from "../common/config/SkillSet";
import { BoxSet } from "../common/config/BoxSet";
/**
*
*
*/
@ecs.register('SMoveData')
@ecs.register('SMoveDataComp')
export class SMoveDataComp extends ecs.Comp {
/** 起始位置 */
startPos: Vec3 = v3();
startPos: Vec3 = null;
/** 目标位置 */
targetPos: Vec3 = v3();
targetPos: Vec3 = null;
/** 移动速度 */
speed: number = 500;
/** 移动持续时间 */
@@ -29,6 +30,21 @@ export class SMoveDataComp extends ecs.Comp {
this.direction.set(0, 0, 0);
this.autoDestroy = true;
}
rePos(originalStart:Vec3){
if(!originalStart){
return
}
// 计算延长后的目标点坐标
const originalTarget = v3(this.targetPos.x, this.targetPos.y + BoxSet.ATK_Y);
const direction = new Vec3();
Vec3.subtract(direction, originalTarget, originalStart);
const distance = direction.length();
direction.normalize();
const extendedTarget = new Vec3();
Vec3.scaleAndAdd(extendedTarget, originalTarget, direction, 720);
this.startPos.set(originalStart);
this.targetPos.set(extendedTarget);
}
}
// /** 业务层业务逻辑处理对象 */

View File

@@ -9,17 +9,17 @@ import { Vec3 } from "cc";
* - AI系统
* -
*/
@ecs.register('CastSkillRequest')
export class CastSkillRequestComp extends ecs.Comp {
@ecs.register('CSRequest')
export class CSRequestComp extends ecs.Comp {
/** 技能索引(在 HeroSkillsComp.skills 中的位置) */
skillIndex: number = 0;
s_uuid: number = 0;
/** 目标位置数组(由请求者提供) */
targetPositions: Vec3[] = [];
targets: Vec3[] = [];
reset() {
this.skillIndex = 0;
this.targetPositions = [];
this.s_uuid = 0;
this.targets = [];
}
}
/**
@@ -33,7 +33,7 @@ export class CastSkillRequestComp extends ecs.Comp {
@ecs.register('SkillHit')
export class SkillHitComp extends ecs.Comp {
/** 技能ID */
skillId: number = 0;
s_uuid: number = 0;
/** 命中位置 */
hitPosition: Vec3 = new Vec3();
@@ -45,7 +45,7 @@ export class SkillHitComp extends ecs.Comp {
casterId: number = 0;
reset() {
this.skillId = 0;
this.s_uuid = 0;
this.hitPosition.set(0, 0, 0);
this.damage = 0;
this.casterId = 0;

View File

@@ -12,7 +12,8 @@ import { HType } from "../common/config/heroSet";
import { SkillView } from "./SkillView";
import { SDataCom } from "./SDataCom";
import { Attrs } from "../common/config/HeroAttrs";
import { SMoveDataComp } from "../hero/SMoveComp";
import { SMoveDataComp } from "../skill/SMoveComp";
import { HeroViewComp } from "../hero/HeroViewComp";
/** Skill 模块 */
@ecs.register(`Skill`)
@@ -29,13 +30,15 @@ export class Skill extends ecs.Entity {
/** 实始添加的数据层组件 */
protected init() {
this.addComponents<SDataCom>();
this.addComponents<SMoveDataComp>();
this.addComponents<SDataCom>(SDataCom);
this.addComponents<SMoveDataComp>(SMoveDataComp);
}
load(startPos: Vec3, parent: Node, uuid: number, targetPos: Vec3,casterAttrs:Attrs[]=[],scale:number=1,fac:FacSet=FacSet.MON,type:HType=HType.warrior,box_group:BoxSet=BoxSet.HERO) {
const config = SkillSet[uuid];
load(startPos: Vec3, parent: Node, s_uuid: number, targetPos: Vec3,
caster:HeroViewComp) {
const config = SkillSet[s_uuid];
let casterAttrs=caster.ent.get(HeroAttrsComp).Attrs
if (!config) {
console.error("[Skill] 技能配置不存在:", uuid);
console.error("[Skill] 技能配置不存在:", s_uuid);
return;
}
@@ -53,32 +56,35 @@ export class Skill extends ecs.Entity {
// 设置节点属性
node.setPosition(startPos);
if(fac==FacSet.MON){
if(casterAttrs.fac==FacSet.MON){
node.scale=v3(node.scale.x*-1,1,1)
}else{
if(type==HType.warrior){
if(scale<0){
if(casterAttrs.type==HType.warrior){
if(casterAttrs.node.scale<0){
node.scale=v3(node.scale.x*-1,node.scale.y,1)
}
}
}
// 添加技能组件
const SView = node.getComponent(SkillView); // 初始化技能参数
// 初始视图
const SView = node.getComponent(SkillView);
// 只设置必要的运行时属性,配置信息通过 SkillSet[uuid] 访问
// 核心标识
SView.s_uuid= uuid
SView.group= box_group
SView.s_uuid= s_uuid
SView.group= caster.box_group
this.add(SView);
const sDataCom = this.get(SDataCom);
// 初始化移动组件
const sMoveCom = this.get(SMoveDataComp);
sMoveCom.startPos=startPos
sMoveCom.targetPos=targetPos
sMoveCom.s_uuid=uuid
sDataCom.group=box_group
sMoveCom.s_uuid=s_uuid
// 初始化数据组件
const sDataCom = this.get(SDataCom);
sDataCom.group=caster.box_group
sDataCom.caster=caster
sDataCom.attrs=casterAttrs
sDataCom.s_uuid=uuid
sDataCom.s_uuid=s_uuid
}

View File

@@ -1,4 +1,4 @@
import { _decorator, Animation, Collider2D, Contact2DType, Vec3 } from "cc";
import { _decorator, Animation, CCInteger, Collider2D, Contact2DType, v3, Vec3 } 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 { HeroViewComp } from "../hero/HeroViewComp";
@@ -6,7 +6,7 @@ import { DTType, RType, SkillSet } from "../common/config/SkillSet";
import { BezierMove } from "../BezierMove/BezierMove";
import { BoxSet } from "../common/config/BoxSet";
import { SDataCom } from "./SDataCom";
import { SMoveDataComp } from "../hero/SMoveComp";
import { SMoveDataComp } from "./SMoveComp";
const { ccclass, property } = _decorator;
@@ -15,6 +15,11 @@ const { ccclass, property } = _decorator;
@ecs.register('SkillView', false)
export class SkillView extends CCComp {
/** 视图层逻辑代码分离演示 */
@property({ type: CCInteger })
atk_x: number = 0
@property({ type: CCInteger })
atk_y: number = 0
anim:Animation=null;
group:number=0;
SConf:any=null;
@@ -29,6 +34,9 @@ export class SkillView extends CCComp {
collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
}
const SMove=this.ent.get(SMoveDataComp)
// 计算延长后的目标点坐标
SMove.rePos(v3(this.node.position.x + this.atk_x, this.node.position.y + this.atk_y))
switch(this.SConf.RType){
case RType.linear:
this.do_linear(SMove.startPos,SMove.targetPos)
@@ -54,6 +62,7 @@ export class SkillView extends CCComp {
if (!this.SConf) return;
}
}
do_bezier(startPos:Vec3,targetPos:Vec3){
let bm=this.node.getComponent(BezierMove)
this.node.angle +=10