feat(skill): 重构技能系统,新增技能数据组件和移动组件
refactor(skill): 移除旧技能组件和文档,优化技能配置结构 fix(skill): 修正技能预制体配置错误,统一技能运行类型字段 docs(skill): 删除过时的技能系统说明文档 perf(skill): 优化技能加载逻辑,减少资源消耗 style(skill): 调整代码格式,提高可读性
This commit is contained in:
@@ -1,24 +1,30 @@
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { BoxSet } from "../common/config/BoxSet";
|
||||
|
||||
/** 业务层对象 */
|
||||
@ecs.register('SkillCom')
|
||||
export class SkillComComp extends ecs.Comp {
|
||||
@ecs.register('SDataCom')
|
||||
export class SDataCom extends ecs.Comp {
|
||||
/** 业务层组件移除时,重置所有数据为默认值 */
|
||||
attrs:any=null
|
||||
group:BoxSet=BoxSet.HERO
|
||||
s_uuid:number=0
|
||||
reset() {
|
||||
|
||||
this.attrs=null
|
||||
this.group=0
|
||||
this.s_uuid=0
|
||||
}
|
||||
}
|
||||
|
||||
/** 业务层业务逻辑处理对象 */
|
||||
export class SkillComSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
|
||||
export class SDataComSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(SkillComComp);
|
||||
return ecs.allOf(SDataCom);
|
||||
}
|
||||
|
||||
entityEnter(e: ecs.Entity): void {
|
||||
// 注:自定义业务逻辑
|
||||
|
||||
|
||||
e.remove(SkillComComp);
|
||||
e.remove(SDataCom);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "4338992d-a768-4089-b1d2-dd8695712fc4",
|
||||
"uuid": "6ded70d5-55f5-48b5-b48b-8883b26d1169",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
@@ -1,34 +1,95 @@
|
||||
import { instantiate, Node, Prefab, v3, Vec3 } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { Hero } from "../hero/Hero";
|
||||
import { Monster } from "../hero/Mon";
|
||||
import { ECSEntity } from "db://oops-framework/libs/ecs/ECSEntity";
|
||||
import { SkillSet } from "../common/config/SkillSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { AtkConCom } from "./AtkConCom";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { BoxSet, FacSet } from "../common/config/BoxSet";
|
||||
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";
|
||||
|
||||
/** Skill 模块 */
|
||||
@ecs.register(`Skill`)
|
||||
export class Skill extends ecs.Entity {
|
||||
/** ---------- 数据层 ---------- */
|
||||
// SkillModel!: SkillModelComp;
|
||||
SDataCom!: SDataCom;
|
||||
SMoveCom!: SMoveDataComp
|
||||
|
||||
/** ---------- 业务层 ---------- */
|
||||
// SkillBll!: SkillBllComp;
|
||||
|
||||
/** ---------- 视图层 ---------- */
|
||||
// SkillView!: SkillViewComp;
|
||||
SView!: SkillView;
|
||||
|
||||
/** 实始添加的数据层组件 */
|
||||
protected init() {
|
||||
// this.addComponents<ecs.Comp>();
|
||||
this.addComponents<SDataCom>();
|
||||
this.addComponents<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];
|
||||
if (!config) {
|
||||
console.error("[Skill] 技能配置不存在:", uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载预制体
|
||||
const path = `game/skill/atk/${config.sp_name}`;
|
||||
const prefab:Prefab = oops.res.get(path, Prefab);
|
||||
if (!prefab) {
|
||||
console.error("[Skill] 预制体加载失败:", path);
|
||||
return;
|
||||
}
|
||||
// console.log("load skill startPos",startPos)
|
||||
const node: Node = instantiate(prefab);
|
||||
console.log("load skill node",node)
|
||||
node.parent = parent;
|
||||
// 设置节点属性
|
||||
node.setPosition(startPos);
|
||||
|
||||
if(fac==FacSet.MON){
|
||||
node.scale=v3(node.scale.x*-1,1,1)
|
||||
}else{
|
||||
if(type==HType.warrior){
|
||||
if(scale<0){
|
||||
node.scale=v3(node.scale.x*-1,node.scale.y,1)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 添加技能组件
|
||||
const SView = node.getComponent(SkillView); // 初始化技能参数
|
||||
// 只设置必要的运行时属性,配置信息通过 SkillSet[uuid] 访问
|
||||
// 核心标识
|
||||
SView.s_uuid= uuid
|
||||
SView.group= 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
|
||||
sDataCom.attrs=casterAttrs
|
||||
sDataCom.s_uuid=uuid
|
||||
|
||||
}
|
||||
|
||||
/** 模块资源释放 */
|
||||
destroy() {
|
||||
// 注: 自定义释放逻辑,视图层实现 ecs.IComp 接口的 ecs 组件需要手动释放
|
||||
this.remove(SDataCom);
|
||||
this.remove(SkillView)
|
||||
super.destroy();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Skill 模块业务逻辑系统组件,如无业务逻辑处理可删除此对象 */
|
||||
export class EcsSkillSystem extends ecs.System {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// this.add(new ecs.ComblockSystem());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,105 @@
|
||||
import { _decorator } 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 { _decorator, Animation, Collider2D, Contact2DType, 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";
|
||||
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";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/** 视图层对象 */
|
||||
@ccclass('SkillViewComp')
|
||||
@ecs.register('SkillView', false)
|
||||
export class SkillViewComp extends CCComp {
|
||||
export class SkillView extends CCComp {
|
||||
/** 视图层逻辑代码分离演示 */
|
||||
anim:Animation=null;
|
||||
group:number=0;
|
||||
SConf:any=null;
|
||||
s_uuid:number=1001
|
||||
start() {
|
||||
// var entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象
|
||||
// this.on(ModuleEvent.Cmd, this.onHandler, this);
|
||||
this.SConf = SkillSet[this.s_uuid]
|
||||
this.anim=this.node.getComponent(Animation)
|
||||
this.node.active = true;
|
||||
let collider = this.getComponent(Collider2D);
|
||||
if(collider) {
|
||||
collider.group = this.group;
|
||||
collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
|
||||
}
|
||||
const SMove=this.ent.get(SMoveDataComp)
|
||||
switch(this.SConf.RType){
|
||||
case RType.linear:
|
||||
this.do_linear(SMove.startPos,SMove.targetPos)
|
||||
break
|
||||
case RType.bezier:
|
||||
this.do_bezier(SMove.startPos,SMove.targetPos)
|
||||
break
|
||||
case RType.fixed:
|
||||
this.do_fixedStart(SMove.startPos,SMove.targetPos)
|
||||
break
|
||||
case RType.fixedEnd:
|
||||
this.do_fixedEnd(SMove.startPos,SMove.targetPos)
|
||||
break
|
||||
}
|
||||
|
||||
/** 全局消息逻辑处理 */
|
||||
// private onHandler(event: string, args: any) {
|
||||
// switch (event) {
|
||||
// case ModuleEvent.Cmd:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
onBeginContact (seCol: Collider2D, oCol: Collider2D) {
|
||||
// console.log(this.scale+"碰撞开始 ",seCol,oCol);
|
||||
if(seCol.node.position.x-oCol.node.position.x > 100 ) return
|
||||
let target = oCol.getComponent(HeroViewComp)
|
||||
if(oCol.group!=this.group){
|
||||
if(target == null) return;
|
||||
if (!this.SConf) return;
|
||||
}
|
||||
}
|
||||
do_bezier(startPos:Vec3,targetPos:Vec3){
|
||||
let bm=this.node.getComponent(BezierMove)
|
||||
this.node.angle +=10
|
||||
// bm.speed=700
|
||||
if(this.group==BoxSet.MONSTER) {bm.controlPointSide=-1 }
|
||||
bm.rotationSmoothness=0.6
|
||||
bm.moveTo(targetPos)
|
||||
}
|
||||
do_linear(startPos:Vec3,targetPos:Vec3){
|
||||
let bm=this.node.getComponent(BezierMove)
|
||||
let s_x=startPos.x
|
||||
let s_y=startPos.y
|
||||
let t_x=targetPos.x
|
||||
let t_y=targetPos.y
|
||||
// 设定目标x
|
||||
targetPos.x = 400;
|
||||
if(this.group == BoxSet.MONSTER) {
|
||||
bm.controlPointSide = -1;
|
||||
targetPos.x = -400;
|
||||
}
|
||||
// 计算斜率
|
||||
const k = (t_y - s_y) / (t_x - s_x);
|
||||
// 按直线公式计算新的y
|
||||
targetPos.y = k * (targetPos.x - s_x) + s_y;
|
||||
bm.controlPointOffset=0
|
||||
bm.rotationSmoothness=0.6
|
||||
bm.moveTo(targetPos);
|
||||
}
|
||||
do_fixedEnd(startPos:Vec3,targetPos:Vec3){
|
||||
this.node.setPosition(targetPos.x > 360?300:targetPos.x,this.node.position.y,0)
|
||||
this.do_anim()
|
||||
}
|
||||
do_fixedStart(startPos:Vec3,targetPos:Vec3){
|
||||
this.node.setPosition(startPos.x > 360?300:startPos.x,this.node.position.y,0)
|
||||
this.do_anim()
|
||||
}
|
||||
do_anim(){
|
||||
if(this.node.getComponent(Animation)){
|
||||
let anim = this.node.getComponent(Animation);
|
||||
//console.log("[SkillCom]:has anim",anim)
|
||||
anim.on(Animation.EventType.FINISHED, this.onAnimationFinished, this);
|
||||
}
|
||||
}
|
||||
onAnimationFinished(){
|
||||
|
||||
}
|
||||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||
reset() {
|
||||
this.node.destroy();
|
||||
|
||||
9
assets/script/game/skill/components.meta
Normal file
9
assets/script/game/skill/components.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "e8a3bd61-1102-4fb8-8eca-c795cad7ef52",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/script/game/skill/systems.meta
Normal file
9
assets/script/game/skill/systems.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "d3d7bbfc-9c24-4551-8bb5-7a40d7c271cd",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
# 新技能系统 - 快速开始
|
||||
|
||||
## 🚀 3步开始使用
|
||||
|
||||
### **步骤 1:注册系统到 Main.ts**
|
||||
|
||||
```typescript
|
||||
// Main.ts
|
||||
import { SkillCastSystem, SkillCDSystem, SkillAutocastSystem } from './game/hero/HSkillSystem';
|
||||
|
||||
protected async initEcsSystem() {
|
||||
// 技能系统(按顺序)
|
||||
oops.ecs.add(new SkillCDSystem()); // CD更新
|
||||
oops.ecs.add(new SkillAutocastSystem()); // 自动施法
|
||||
oops.ecs.add(new SkillCastSystem()); // 施法执行
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **步骤 2:角色已自动拥有技能**
|
||||
|
||||
✅ `Hero.ts` 和 `Mon.ts` 已自动添加 `HeroSkillsComp`
|
||||
✅ 加载角色时自动初始化技能
|
||||
✅ 无需手动配置
|
||||
|
||||
```typescript
|
||||
// Hero.ts - load() 方法已自动处理
|
||||
skillsComp.initSkills(hero.skills); // ✅ 已完成
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **步骤 3:技能自动施放!**
|
||||
|
||||
**无需额外代码**,系统会自动处理:
|
||||
|
||||
```typescript
|
||||
// 每帧自动运行:
|
||||
// 1. SkillCDSystem 更新CD
|
||||
// 2. SkillAutocastSystem 检测可施放技能
|
||||
// ├─ CD好了? ✅
|
||||
// ├─ MP够? ✅
|
||||
// ├─ 正在攻击? ✅
|
||||
// └─ 添加 CastSkillRequestComp 标记
|
||||
// 3. SkillCastSystem 执行施法
|
||||
// ├─ 检查条件
|
||||
// ├─ 扣除MP
|
||||
// ├─ 重置CD
|
||||
// ├─ 播放动画
|
||||
// └─ 创建技能实体
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎮 进阶使用
|
||||
|
||||
### **手动施法(玩家点击技能按钮)**
|
||||
|
||||
```typescript
|
||||
// UI 按钮点击事件
|
||||
onSkillButton1Clicked() {
|
||||
const skillCon = this.heroNode.getComponent(SkillConComp);
|
||||
skillCon.manualCastSkill(0); // 施放第0个技能
|
||||
}
|
||||
|
||||
onSkillButton2Clicked() {
|
||||
const skillCon = this.heroNode.getComponent(SkillConComp);
|
||||
skillCon.manualCastSkill(1); // 施放第1个技能
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **强制施法(天赋触发、事件触发)**
|
||||
|
||||
```typescript
|
||||
// 天赋系统
|
||||
doTalentEffect(heroEntity: ecs.Entity) {
|
||||
const request = heroEntity.add(CastSkillRequestComp);
|
||||
request.skillIndex = 2; // 施放第2个技能
|
||||
request.targetPositions = [v3(200, 0, 0)];
|
||||
}
|
||||
|
||||
// 事件触发(如复仇:受伤时施放技能)
|
||||
onDamaged(heroEntity: ecs.Entity) {
|
||||
const request = heroEntity.add(CastSkillRequestComp);
|
||||
request.skillIndex = 0;
|
||||
request.targetPositions = this.selectEnemies();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **查询技能状态**
|
||||
|
||||
```typescript
|
||||
const hero = ecs.getEntity<Hero>(Hero);
|
||||
const skillsComp = hero.get(HeroSkillsComp);
|
||||
|
||||
// 检查技能是否就绪
|
||||
if (skillsComp.canCast(0, heroModel.mp)) {
|
||||
console.log("技能1可以施放!");
|
||||
}
|
||||
|
||||
// 获取所有就绪技能
|
||||
const readySkills = skillsComp.getReadySkills(heroModel.mp);
|
||||
console.log(`可施放技能数量: ${readySkills.length}`);
|
||||
|
||||
// 获取技能CD
|
||||
const skill0 = skillsComp.getSkill(0);
|
||||
console.log(`技能1剩余CD: ${skill0.cd.toFixed(2)}秒`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 禁用自动施法
|
||||
|
||||
如果只想手动控制技能,注释掉自动施法系统:
|
||||
|
||||
```typescript
|
||||
protected async initEcsSystem() {
|
||||
oops.ecs.add(new SkillCDSystem());
|
||||
// oops.ecs.add(new SkillAutocastSystem()); // ❌ 禁用自动施法
|
||||
oops.ecs.add(new SkillCastSystem());
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 与原系统对比
|
||||
|
||||
| 指标 | 旧系统(SkillConComp.update) | 新系统(HSkillSystem) |
|
||||
|------|------------------------------|----------------------|
|
||||
| **职责** | CD更新 + 施法判定 + 执行 | 3个独立系统 |
|
||||
| **扩展性** | 低(所有逻辑耦合) | 高(系统独立) |
|
||||
| **代码位置** | 分散在 View 层 | 集中在数据/业务层 |
|
||||
| **测试** | 难(依赖 View) | 易(独立系统) |
|
||||
| **手动施法** | 需额外实现 | 标记组件即可 |
|
||||
| **ECS 规范** | 不符合 | ✅ 完全符合 |
|
||||
|
||||
---
|
||||
|
||||
## 📊 架构图
|
||||
|
||||
```
|
||||
玩家实体(Hero/Monster)
|
||||
├── HeroAttrsComp(属性:hp, mp, 状态)
|
||||
├── HeroSkillsComp(技能:skills[], CD管理)⭐ 新增
|
||||
├── HeroViewComp(视图:动画、UI)
|
||||
└── BattleMoveComp(移动)
|
||||
|
||||
技能系统(HSkillSystem)
|
||||
├── SkillCDSystem ─────────→ HeroSkillsComp
|
||||
│ └─ 每帧更新CD
|
||||
├── SkillAutocastSystem ───→ HeroSkillsComp + HeroAttrsComp
|
||||
│ └─ AI决策施法
|
||||
└── SkillCastSystem ───────→ 监听 CastSkillRequestComp
|
||||
├─ 检查条件
|
||||
├─ 扣除MP
|
||||
├─ 重置CD
|
||||
└─ 创建技能实体
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
运行游戏后检查:
|
||||
|
||||
- [ ] 角色加载后拥有技能(查看 HeroSkillsComp.skills)
|
||||
- [ ] 技能CD自动递减(观察 skill.cd 变化)
|
||||
- [ ] 攻击时自动施放技能(观察技能特效)
|
||||
- [ ] 施放后MP减少、CD重置
|
||||
- [ ] 控制状态(眩晕/冰冻)时不施放技能
|
||||
|
||||
---
|
||||
|
||||
## 🎉 完成!
|
||||
|
||||
**新技能系统已完全集成到项目中!**
|
||||
|
||||
✅ 无需修改原有战斗逻辑
|
||||
✅ 无需修改技能实体(复用 SkillEnt)
|
||||
✅ 自动与战斗系统集成
|
||||
✅ 支持多种施法方式
|
||||
|
||||
**开始享受清晰的架构吧!** 🚀
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"ver": "1.0.1",
|
||||
"importer": "text",
|
||||
"imported": true,
|
||||
"uuid": "a851deeb-51c4-4c8d-990f-0d460fe8848b",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
# 新技能系统使用说明
|
||||
|
||||
## 📊 架构概览
|
||||
|
||||
基于 **oops-framework ECS** 架构设计的完整施法系统。
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ 文件结构
|
||||
|
||||
```
|
||||
技能系统文件:
|
||||
├── HeroSkills.ts # 数据层:技能槽位数据
|
||||
├── HSkillSystem.ts # 业务层:3个系统
|
||||
│ ├── SkillCastSystem # 施法系统
|
||||
│ ├── SkillCDSystem # CD更新系统
|
||||
│ └── SkillAutocastSystem # 自动施法系统(AI)
|
||||
└── SkillEnt.ts # 技能实体(复用现有)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 设计理念
|
||||
|
||||
### **数据层(HeroSkillsComp)**
|
||||
|
||||
**职责**:存储角色拥有的技能列表和CD状态
|
||||
|
||||
```typescript
|
||||
@ecs.register('HeroSkills')
|
||||
export class HeroSkillsComp extends ecs.Comp {
|
||||
skills: SkillSlot[] = []; // 技能槽位数组
|
||||
|
||||
// 数据方法
|
||||
initSkills(skillIds: number[]) { } // 初始化技能
|
||||
canCast(index, mp): boolean { } // 检查可施放
|
||||
resetCD(index) { } // 重置CD
|
||||
updateCDs(dt) { } // 更新CD
|
||||
getReadySkills(mp): number[] { } // 获取就绪技能
|
||||
}
|
||||
```
|
||||
|
||||
**技能槽位数据**:
|
||||
```typescript
|
||||
interface SkillSlot {
|
||||
uuid: number; // 技能配置ID
|
||||
cd: number; // 当前CD(递减)
|
||||
cd_max: number; // 最大CD
|
||||
cost: number; // MP消耗
|
||||
level: number; // 技能等级
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **业务层(3个系统)**
|
||||
|
||||
#### **1. SkillCastSystem(施法系统)⭐**
|
||||
|
||||
**职责**:监听施法请求,执行施法
|
||||
|
||||
```typescript
|
||||
export class SkillCastSystem extends ecs.ComblockSystem
|
||||
implements ecs.IEntityEnterSystem {
|
||||
|
||||
// 筛选:拥有技能 + 请求标记的实体
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, CastSkillRequestComp);
|
||||
}
|
||||
|
||||
// 处理施法请求
|
||||
entityEnter(e: ecs.Entity): void {
|
||||
// 1. 检查施法条件(CD、MP、状态)
|
||||
// 2. 扣除MP
|
||||
// 3. 重置CD
|
||||
// 4. 播放动画
|
||||
// 5. 创建技能实体
|
||||
// 6. 移除请求标记
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **2. SkillCDSystem(CD更新系统)**
|
||||
|
||||
**职责**:每帧自动更新所有技能CD
|
||||
|
||||
```typescript
|
||||
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);
|
||||
skillsData.updateCDs(this.dt); // 自动递减CD
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **3. SkillAutocastSystem(自动施法系统)**
|
||||
|
||||
**职责**:AI自动选择和施放技能
|
||||
|
||||
```typescript
|
||||
export class SkillAutocastSystem extends ecs.ComblockSystem
|
||||
implements ecs.ISystemUpdate {
|
||||
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, HeroViewComp);
|
||||
}
|
||||
|
||||
update(e: ecs.Entity): void {
|
||||
// 1. 检查角色状态
|
||||
// 2. 获取可施放技能
|
||||
// 3. 选择目标
|
||||
// 4. 添加施法请求标记 ← 触发 SkillCastSystem
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 数据流程
|
||||
|
||||
```
|
||||
方式1:自动施法(AI)
|
||||
SkillAutocastSystem.update()
|
||||
├─ 检测可施放技能
|
||||
├─ 选择目标
|
||||
└─ 添加 CastSkillRequestComp 标记
|
||||
↓
|
||||
SkillCastSystem.entityEnter() ← 自动触发
|
||||
├─ 检查施法条件
|
||||
├─ 扣除MP
|
||||
├─ 重置CD
|
||||
├─ 播放施法动画
|
||||
├─ 创建 SkillEnt
|
||||
└─ 移除 CastSkillRequestComp
|
||||
|
||||
方式2:手动施法(玩家点击)
|
||||
UI.onClick()
|
||||
└─ skillConComp.manualCastSkill(index)
|
||||
└─ 添加 CastSkillRequestComp 标记
|
||||
↓
|
||||
(后续流程同方式1)
|
||||
|
||||
方式3:强制施法(天赋、事件触发)
|
||||
TalentSystem
|
||||
└─ heroEntity.add(CastSkillRequestComp)
|
||||
↓
|
||||
(后续流程同方式1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 使用示例
|
||||
|
||||
### **示例 1:初始化角色技能**
|
||||
|
||||
```typescript
|
||||
// Hero.ts - load() 方法中
|
||||
const skillsComp = this.get(HeroSkillsComp);
|
||||
skillsComp.initSkills(hero.skills); // [6001, 6005, 6010]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **示例 2:自动施法(默认)**
|
||||
|
||||
**无需额外代码**,`SkillAutocastSystem` 会自动处理:
|
||||
|
||||
```typescript
|
||||
// 每帧自动检测:
|
||||
// - 是否有可施放技能?
|
||||
// - CD好了?MP够?
|
||||
// - 正在攻击?
|
||||
// ✅ 自动添加施法请求标记 → 触发施法
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **示例 3:手动施法(玩家点击)**
|
||||
|
||||
```typescript
|
||||
// UI 按钮点击
|
||||
onSkillButton1Click() {
|
||||
const skillCon = this.heroNode.getComponent(SkillConComp);
|
||||
skillCon.manualCastSkill(0); // 施放第0个技能
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **示例 4:强制施法(天赋触发)**
|
||||
|
||||
```typescript
|
||||
// 天赋系统
|
||||
doTalentEffect(heroEntity: ecs.Entity) {
|
||||
// ✅ 添加施法请求标记
|
||||
const request = heroEntity.add(CastSkillRequestComp);
|
||||
request.skillIndex = 1; // 施放第1个技能
|
||||
request.targetPositions = [v3(100, 0, 0)];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 系统注册(Main.ts)
|
||||
|
||||
```typescript
|
||||
protected async initEcsSystem() {
|
||||
// ✅ 注册技能系统(按顺序)
|
||||
oops.ecs.add(new SkillCDSystem()); // 1. CD更新
|
||||
oops.ecs.add(new SkillAutocastSystem()); // 2. 自动施法AI
|
||||
oops.ecs.add(new SkillCastSystem()); // 3. 施法执行
|
||||
|
||||
// 战斗系统
|
||||
oops.ecs.add(new HeroAtkSystem());
|
||||
oops.ecs.add(new HeroAttrSystem());
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 迁移清单
|
||||
|
||||
### ✅ **已完成**
|
||||
|
||||
| 任务 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| 创建 HeroSkillsComp | ✅ | 技能数据组件 |
|
||||
| 创建 SkillCastSystem | ✅ | 施法执行系统 |
|
||||
| 创建 SkillCDSystem | ✅ | CD更新系统 |
|
||||
| 创建 SkillAutocastSystem | ✅ | 自动施法系统 |
|
||||
| 更新 Hero.ts | ✅ | 添加 HeroSkillsComp |
|
||||
| 更新 Mon.ts | ✅ | 添加 HeroSkillsComp |
|
||||
| 从 HeroAttrsComp 移除 skills | ✅ | 数据迁移完成 |
|
||||
| 更新 SkillConComp | ✅ | 使用新系统 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 与战斗系统集成
|
||||
|
||||
**技能系统只负责"施法",伤害结算由战斗系统处理**
|
||||
|
||||
```
|
||||
SkillCastSystem(施法)
|
||||
↓
|
||||
创建 SkillEnt(技能实体)
|
||||
↓
|
||||
SkillEnt 碰撞检测
|
||||
↓
|
||||
AtkConCom.single_damage()
|
||||
↓
|
||||
HeroAtkSystem.doAttack() ← 统一战斗系统
|
||||
├─ 暴击判定
|
||||
├─ 闪避判定
|
||||
├─ 护盾吸收
|
||||
├─ 修改数据
|
||||
└─ 触发视图
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 优点总结
|
||||
|
||||
| 优点 | 说明 |
|
||||
|------|------|
|
||||
| **数据分离** | 技能数据独立组件,不污染 HeroAttrsComp |
|
||||
| **标记驱动** | 使用 CastSkillRequestComp 标记组件,符合 ECS |
|
||||
| **职责清晰** | CD更新、施法检查、执行分离成独立系统 |
|
||||
| **易于扩展** | 添加新施法方式(手动/自动/强制)无需改动核心 |
|
||||
| **易于测试** | 可单独测试每个系统 |
|
||||
| **代码复用** | 手动/自动/强制施法共用同一套逻辑 |
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
**完整的、规范的、基于 oops-framework 的技能施法系统!**
|
||||
|
||||
✅ 符合 ECS 架构(数据/业务/视图分离)
|
||||
✅ 使用标记组件驱动,完全解耦
|
||||
✅ 复用现有 SkillEnt,无需重写
|
||||
✅ 与战斗系统完美集成
|
||||
✅ 支持自动/手动/强制多种施法方式
|
||||
✅ 代码清晰,注释详细
|
||||
|
||||
**可直接投入生产使用!** 🚀
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"ver": "1.0.1",
|
||||
"importer": "text",
|
||||
"imported": true,
|
||||
"uuid": "2df12e82-84f3-40f1-a838-145f935d4cc1",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user