From 55646c3a117fa6ec4eeec9930e25e92ff96c1340 Mon Sep 17 00:00:00 2001 From: panw Date: Thu, 30 Oct 2025 15:12:49 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=86=20=E6=8A=80?= =?UTF-8?q?=E8=83=BD=E7=B3=BB=E7=BB=9F=EF=BC=8C=E8=BF=98=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/resources/game/heros/ha1.prefab | 44 +-- assets/resources/game/heros/hc1.prefab | 44 +-- assets/resources/game/heros/hh1.prefab | 44 +-- assets/resources/game/heros/hk1.prefab | 44 +-- assets/resources/game/heros/hm1.prefab | 44 +-- assets/resources/game/heros/hm2.prefab | 44 +-- assets/resources/game/heros/hz1.prefab | 44 +-- assets/resources/game/heros/mo1.prefab | 44 +-- assets/resources/game/heros/mo2.prefab | 44 +-- assets/resources/game/heros/mo3.prefab | 44 +-- assets/resources/game/heros/mo4.prefab | 44 +-- .../common/ecs/position/BattleMoveSystem.ts | 67 ++-- assets/script/game/hero/HSkillSystem.ts | 270 ++++++++++++++++ ...roConComp.ts.meta => HSkillSystem.ts.meta} | 2 +- assets/script/game/hero/Hero.ts | 23 +- assets/script/game/hero/HeroAttrsComp.ts | 10 +- assets/script/game/hero/HeroConComp.ts | 28 -- assets/script/game/hero/HeroSkills.ts | 142 +++++++++ assets/script/game/hero/HeroSkills.ts.meta | 9 + assets/script/game/hero/HeroViewComp.ts | 5 +- assets/script/game/hero/Mon.ts | 26 +- assets/script/game/hero/SkillConComp.ts | 42 ++- assets/script/game/skill/SkillEnt.ts | 2 +- assets/script/game/skill/快速开始.md | 189 +++++++++++ assets/script/game/skill/快速开始.md.meta | 11 + .../script/game/skill/新技能系统使用说明.md | 296 ++++++++++++++++++ .../game/skill/新技能系统使用说明.md.meta | 11 + 27 files changed, 1022 insertions(+), 595 deletions(-) create mode 100644 assets/script/game/hero/HSkillSystem.ts rename assets/script/game/hero/{HeroConComp.ts.meta => HSkillSystem.ts.meta} (70%) delete mode 100644 assets/script/game/hero/HeroConComp.ts create mode 100644 assets/script/game/hero/HeroSkills.ts create mode 100644 assets/script/game/hero/HeroSkills.ts.meta create mode 100644 assets/script/game/skill/快速开始.md create mode 100644 assets/script/game/skill/快速开始.md.meta create mode 100644 assets/script/game/skill/新技能系统使用说明.md create mode 100644 assets/script/game/skill/新技能系统使用说明.md.meta diff --git a/assets/resources/game/heros/ha1.prefab b/assets/resources/game/heros/ha1.prefab index 8075ad27..23639e31 100644 --- a/assets/resources/game/heros/ha1.prefab +++ b/assets/resources/game/heros/ha1.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/hc1.prefab b/assets/resources/game/heros/hc1.prefab index a5e30bde..106ee922 100644 --- a/assets/resources/game/heros/hc1.prefab +++ b/assets/resources/game/heros/hc1.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/hh1.prefab b/assets/resources/game/heros/hh1.prefab index 8ab036db..d7d2d3c9 100644 --- a/assets/resources/game/heros/hh1.prefab +++ b/assets/resources/game/heros/hh1.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/hk1.prefab b/assets/resources/game/heros/hk1.prefab index 89c33425..9c95de47 100644 --- a/assets/resources/game/heros/hk1.prefab +++ b/assets/resources/game/heros/hk1.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/hm1.prefab b/assets/resources/game/heros/hm1.prefab index 0414a9ce..1948b862 100644 --- a/assets/resources/game/heros/hm1.prefab +++ b/assets/resources/game/heros/hm1.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/hm2.prefab b/assets/resources/game/heros/hm2.prefab index 91ba1610..91c07ef4 100644 --- a/assets/resources/game/heros/hm2.prefab +++ b/assets/resources/game/heros/hm2.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/hz1.prefab b/assets/resources/game/heros/hz1.prefab index deb1a4fe..bed26452 100644 --- a/assets/resources/game/heros/hz1.prefab +++ b/assets/resources/game/heros/hz1.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/mo1.prefab b/assets/resources/game/heros/mo1.prefab index 2ca8a4f5..f621616f 100644 --- a/assets/resources/game/heros/mo1.prefab +++ b/assets/resources/game/heros/mo1.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1020,42 +1014,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/mo2.prefab b/assets/resources/game/heros/mo2.prefab index e776a54d..160f1b84 100644 --- a/assets/resources/game/heros/mo2.prefab +++ b/assets/resources/game/heros/mo2.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/mo3.prefab b/assets/resources/game/heros/mo3.prefab index a39801bd..6e0a2987 100644 --- a/assets/resources/game/heros/mo3.prefab +++ b/assets/resources/game/heros/mo3.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/game/heros/mo4.prefab b/assets/resources/game/heros/mo4.prefab index dc4a7bda..6d50e3b9 100644 --- a/assets/resources/game/heros/mo4.prefab +++ b/assets/resources/game/heros/mo4.prefab @@ -50,16 +50,10 @@ }, { "__id__": 61 - }, - { - "__id__": 63 - }, - { - "__id__": 65 } ], "_prefab": { - "__id__": 67 + "__id__": 63 }, "_lpos": { "__type__": "cc.Vec3", @@ -1023,42 +1017,6 @@ "__type__": "cc.CompPrefabInfo", "fileId": "23j+p5lLdC+r4iKSVeLNM4" }, - { - "__type__": "6f882ofb1pO9Z6gIaAZLCeF", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 64 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a2jPQ8TNhPj67IyZiPimbD" - }, - { - "__type__": "846e0MH5V5Lw6nbs4fImtZx", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 66 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "a0ZuO8pcZBVLCGR/YlgT1g" - }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/script/game/common/ecs/position/BattleMoveSystem.ts b/assets/script/game/common/ecs/position/BattleMoveSystem.ts index 8e0249aa..19f42664 100644 --- a/assets/script/game/common/ecs/position/BattleMoveSystem.ts +++ b/assets/script/game/common/ecs/position/BattleMoveSystem.ts @@ -5,6 +5,7 @@ import { smc } from "../../SingletonModuleComp"; import { FacSet } from "../../config/BoxSet"; import { HType } from "../../config/heroSet"; import { Attrs } from "../../config/HeroAttrs"; +import { HeroAttrsComp } from "../../../hero/HeroAttrsComp"; @ecs.register('BattleMoveSystem') export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { @@ -16,20 +17,21 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU update(e: ecs.Entity) { if(!smc.mission.play||smc.mission.pause) return const move = e.get(BattleMoveComp); + const model = e.get(HeroAttrsComp); const view = e.get(HeroViewComp); if (!move.moving) return; const shouldStop = this.checkEnemiesInFace(e); - view.is_atking = this.checkEnemiesInRange(e, view.Attrs[Attrs.DIS]); + model.is_atking = this.checkEnemiesInRange(e, model.Attrs[Attrs.DIS]); // 更新渲染层级 this.updateRenderOrder(e); // 同步状态 if (!shouldStop) { //在攻击范围内停止移动 - // if(view.fac==1){ - if(view.is_stop||view.is_dead||view.isStun()||view.isFrost()) { + // if(model.fac==1){ + if(model.is_stop||model.is_dead||model.isStun()||model.isFrost()) { view.status_change("idle"); return; //停止移动或者死亡不移动 } @@ -39,9 +41,9 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU return; } // 英雄阵营特殊逻辑:根据职业区分行为 - if (view.fac == FacSet.HERO) { + if (model.fac == FacSet.HERO) { const hasEnemies = this.checkEnemiesExist(e); - const isWarrior = view.type === HType.warrior; + const isWarrior = model.type === HType.warrior; // 战士职业:有敌人就向敌人前进 if (isWarrior && hasEnemies) { @@ -62,7 +64,7 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU } // 继续向敌人方向移动 - const delta = (view.Attrs[Attrs.SPEED]/3) * this.dt * move.direction; + const delta = (model.Attrs[Attrs.SPEED]/3) * this.dt * move.direction; const newX = view.node.position.x + delta; // 对于战士,允许更自由的移动范围 @@ -89,7 +91,7 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU if (Math.abs(currentX - finalTargetX) > 1) { // 确定移动方向 const direction = currentX > finalTargetX ? -1 : 1; - const delta = (view.Attrs[Attrs.SPEED]/3) * this.dt * direction; + const delta = (model.Attrs[Attrs.SPEED]/3) * this.dt * direction; const newX = view.node.position.x + delta; // 设置朝向 @@ -121,7 +123,7 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU } // 计算移动量 - const delta =(view.Attrs[Attrs.SPEED]/3) * this.dt * move.direction; + const delta =(model.Attrs[Attrs.SPEED]/3) * this.dt * move.direction; const newX = view.node.position.x + delta; // 限制移动范围 @@ -140,16 +142,16 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU // 因为敌人在面前而暂时停止,不设置moving为false,保持检查状态 } - // console.log(`[${view.hero_name}] 类型:${view.type} 是否停止:${shouldStop} 方向:${move.direction} 位置:${view.node.position.x.toFixed(1)}`); + // console.log(`[${model.hero_name}] 类型:${model.type} 是否停止:${shouldStop} 方向:${move.direction} 位置:${model.node.position.x.toFixed(1)}`); } /** 检查是否存在敌人 */ private checkEnemiesExist(entity: ecs.Entity): boolean { - const team = entity.get(HeroViewComp).fac; + const team = entity.get(HeroAttrsComp).fac; let hasEnemies = false; - ecs.query(ecs.allOf(HeroViewComp)).some(e => { - const view = e.get(HeroViewComp); - if (view.fac !== team && !view.is_dead) { + ecs.query(ecs.allOf(HeroAttrsComp)).some(e => { + const model = e.get(HeroAttrsComp); + if (model.fac !== team && !model.is_dead) { hasEnemies = true; return true; } @@ -160,17 +162,18 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU /** 找到最近的敌人 */ private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null { const currentPos = entity.get(HeroViewComp).node.position; - const team = entity.get(HeroViewComp).fac; - let nearestEnemy: HeroViewComp | null = null; + const team = entity.get(HeroAttrsComp).fac; + let nearestEnemy: HeroAttrsComp | null = null; let minDistance = Infinity; - ecs.query(ecs.allOf(HeroViewComp)).forEach(e => { + ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => { + const model = e.get(HeroAttrsComp); const view = e.get(HeroViewComp); - if (view.fac !== team && !view.is_dead) { + if (model.fac !== team && !model.is_dead) { const distance = Math.abs(currentPos.x - view.node.position.x); if (distance < minDistance) { minDistance = distance; - nearestEnemy = view; + nearestEnemy = model; } } }); @@ -188,8 +191,8 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU /** 检测是否在墓地 */ private checkInGrave(entity: ecs.Entity): boolean { - const view = entity.get(HeroViewComp); - return view.node.position.x === -1000 || view.node.position.x === 1000; + const model = entity.get(HeroViewComp); + return model.node.position.x === -1000 || model.node.position.x === 1000; } /** 检测攻击范围内敌人 */ @@ -198,9 +201,9 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU const team = entity.get(HeroViewComp).fac; let found = false; ecs.query(ecs.allOf(HeroViewComp)).some(e => { - const view = e.get(HeroViewComp); - const distance = Math.abs(currentPos.x - view.node.position.x); - if (view.fac !== team) { + const model = e.get(HeroViewComp); + const distance = Math.abs(currentPos.x - model.node.position.x); + if (model.fac !== team) { if (distance <= range) { found = true; return true; @@ -215,9 +218,9 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU const team = entity.get(HeroViewComp).fac; let found = false; ecs.query(ecs.allOf(HeroViewComp)).some(e => { - const view = e.get(HeroViewComp); - const distance = Math.abs(currentPos.x - view.node.position.x); - if (view.fac !== team) { + const model = e.get(HeroViewComp); + const distance = Math.abs(currentPos.x - model.node.position.x); + if (model.fac !== team) { if (distance <= 75) { found = true; return true; @@ -247,8 +250,8 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU // 设置渲染顺序:x坐标越大的显示在上层(index越大,层级越高) sortedUnits.forEach((unit, index) => { - const view = unit.get(HeroViewComp); - view.node.setSiblingIndex(index); // 直接使用index,x坐标大的index大,层级高 + const model = unit.get(HeroViewComp); + model.node.setSiblingIndex(index); // 直接使用index,x坐标大的index大,层级高 }); } @@ -260,11 +263,11 @@ export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemU return ecs.query(ecs.allOf(HeroViewComp)).some(e => { if (e === currentEntity) return false; // 排除自己 - const view = e.get(HeroViewComp); - if (view.fac !== currentView.fac) return false; // 只检查同阵营 - if (view.is_dead) return false; // 排除死亡单位 + const model = e.get(HeroViewComp); + if (model.fac !== currentView.fac) return false; // 只检查同阵营 + if (model.is_dead) return false; // 排除死亡单位 - const distance = Math.abs(view.node.position.x - targetX); + const distance = Math.abs(model.node.position.x - targetX); return distance < occupationRange; // 如果距离小于占用范围,认为被占用 }); } diff --git a/assets/script/game/hero/HSkillSystem.ts b/assets/script/game/hero/HSkillSystem.ts new file mode 100644 index 00000000..456b0237 --- /dev/null +++ b/assets/script/game/hero/HSkillSystem.ts @@ -0,0 +1,270 @@ +import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; +import { Vec3, v3 } from "cc"; +import { HeroSkillsComp } from "./HeroSkills"; +import { HeroAttrsComp } from "./HeroAttrsComp"; +import { HeroViewComp } from "./HeroViewComp"; +import { SkillSet, SType } from "../common/config/SkillSet"; +import { SkillEnt } from "../skill/SkillEnt"; +import { smc } from "../common/SingletonModuleComp"; + +/** + * ==================== 施法请求标记组件 ==================== + * + * 用途: + * - 标记角色想要施放某个技能 + * - 由外部(如AI系统、玩家输入)添加 + * - 施法系统处理后自动移除 + */ +@ecs.register('CastSkillRequest') +export class CastSkillRequestComp extends ecs.Comp { + /** 技能索引(在 HeroSkillsComp.skills 中的位置) */ + skillIndex: number = 0; + + /** 目标位置数组(由请求者提供) */ + targetPositions: Vec3[] = []; + + reset() { + this.skillIndex = 0; + this.targetPositions = []; + } +} + +/** + * ==================== 技能施法系统 ==================== + * + * 职责: + * 1. 监听 CastSkillRequestComp 标记组件 + * 2. 检查施法条件(CD、MP、状态) + * 3. 扣除资源(MP) + * 4. 创建技能实体 + * 5. 触发施法动画 + * 6. 移除请求标记 + * + * 设计理念: + * - 使用标记组件驱动,符合 ECS 理念 + * - 施法检查与执行分离 + * - 自动处理资源消耗和CD重置 + */ +export class SkillCastSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem { + + /** + * 过滤器:拥有技能数据 + 施法请求的实体 + */ + filter(): ecs.IMatcher { + return ecs.allOf(HeroSkillsComp, HeroAttrsComp, CastSkillRequestComp); + } + + /** + * 实体进入时触发(即请求施法时) + */ + entityEnter(e: ecs.Entity): void { + const skillsData = e.get(HeroSkillsComp); + const heroModel = e.get(HeroAttrsComp); + const request = e.get(CastSkillRequestComp); + const heroView = e.get(HeroViewComp); + + // 1. 验证数据完整性 + if (!skillsData || !heroModel || !request || !heroView) { + console.warn("[SkillCastSystem] 数据不完整,取消施法"); + e.remove(CastSkillRequestComp); + return; + } + + // 2. 获取技能数据 + const skill = skillsData.getSkill(request.skillIndex); + if (!skill) { + console.warn(`[SkillCastSystem] 技能索引无效: ${request.skillIndex}`); + e.remove(CastSkillRequestComp); + return; + } + + // 3. 检查施法条件 + if (!this.checkCastConditions(skillsData, heroModel, request.skillIndex)) { + e.remove(CastSkillRequestComp); + return; + } + + // 4. 执行施法 + this.executeCast(e, skill, request.targetPositions, heroView); + + // 5. 扣除资源和重置CD + heroModel.mp -= skill.cost; + skillsData.resetCD(request.skillIndex); + + // 6. 移除请求标记 + e.remove(CastSkillRequestComp); + } + + /** + * 检查施法条件 + */ + private checkCastConditions(skillsData: HeroSkillsComp, heroModel: HeroAttrsComp, skillIndex: number): boolean { + // 检查角色状态 + if (heroModel.is_dead) { + return false; + } + + // 检查控制状态(眩晕、冰冻) + if (heroModel.isStun() || heroModel.isFrost()) { + return false; + } + + // 检查CD和MP + if (!skillsData.canCast(skillIndex, heroModel.mp)) { + return false; + } + + return true; + } + + /** + * 执行施法 + */ + private executeCast(casterEntity: ecs.Entity, skill: any, targetPositions: Vec3[], heroView: HeroViewComp) { + const config = SkillSet[skill.uuid]; + if (!config) { + console.error("[SkillCastSystem] 技能配置不存在:", skill.uuid); + return; + } + + // 1. 播放施法动画 + heroView.playSkillEffect(skill.uuid); + + // 2. 延迟创建技能实体(等待动画) + const delay = config.with ?? 0.3; // 施法前摇时间 + heroView.scheduleOnce(() => { + this.createSkillEntity(skill.uuid, heroView, targetPositions); + }, delay); + + const heroModel = casterEntity.get(HeroAttrsComp); + console.log(`[SkillCastSystem] ${heroModel?.hero_name ?? '未知'} 施放技能: ${config.name}`); + } + + /** + * 创建技能实体 + */ + private createSkillEntity(skillId: number, caster: HeroViewComp, targetPositions: Vec3[]) { + // 检查节点有效性 + if (!caster.node || !caster.node.isValid) { + console.warn("[SkillCastSystem] 施法者节点无效"); + return; + } + + // 获取场景节点 + const parent = caster.node.parent; + if (!parent) { + console.warn("[SkillCastSystem] 场景节点无效"); + return; + } + + // ✅ 使用现有的 SkillEnt 创建技能 + const skillEnt = ecs.getEntity(SkillEnt); + skillEnt.load( + caster.node.position, // 起始位置 + parent, // 父节点 + skillId, // 技能ID + targetPositions, // 目标位置数组 + caster, // 施法者 + 0 // 额外伤害(暂时为0) + ); + } +} + +/** + * ==================== 技能CD更新系统 ==================== + * + * 职责: + * 1. 每帧更新所有角色的技能CD + * 2. 自动递减CD时间 + * + * 设计理念: + * - 独立的CD管理系统 + * - 只负责时间递减,不处理施法逻辑 + */ +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); + if (!skillsData) return; + + // 更新所有技能CD + skillsData.updateCDs(this.dt); + } +} + +/** + * ==================== 自动施法系统 ==================== + * + * 职责: + * 1. 检测可施放的技能 + * 2. 根据策略自动施法(AI) + * 3. 选择目标 + * 4. 添加施法请求标记 + * + * 设计理念: + * - 负责"何时施法"的决策 + * - 通过添加 CastSkillRequestComp 触发施法 + * - 可被玩家输入系统或AI系统复用 + */ +export class SkillAutocastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { + + filter(): ecs.IMatcher { + return ecs.allOf(HeroSkillsComp, HeroAttrsComp, HeroViewComp); + } + + update(e: ecs.Entity): void { + const skillsData = e.get(HeroSkillsComp); + const heroModel = e.get(HeroAttrsComp); + const heroView = e.get(HeroViewComp); + + if (!skillsData || !heroModel || !heroView) return; + + // 检查基本条件 + if (heroModel.is_dead || heroModel.isStun() || heroModel.isFrost()) return; + + // 检查是否正在攻击(只有攻击时才释放技能) + if (!heroView.is_atking) return; + + // 获取所有可施放的技能 + const readySkills = skillsData.getReadySkills(heroModel.mp); + if (readySkills.length === 0) return; + + // 选择第一个可施放的伤害技能 + for (const skillIndex of readySkills) { + const skill = skillsData.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; + } +} \ No newline at end of file diff --git a/assets/script/game/hero/HeroConComp.ts.meta b/assets/script/game/hero/HSkillSystem.ts.meta similarity index 70% rename from assets/script/game/hero/HeroConComp.ts.meta rename to assets/script/game/hero/HSkillSystem.ts.meta index 56698ccf..917f7569 100644 --- a/assets/script/game/hero/HeroConComp.ts.meta +++ b/assets/script/game/hero/HSkillSystem.ts.meta @@ -2,7 +2,7 @@ "ver": "4.0.24", "importer": "typescript", "imported": true, - "uuid": "846e0307-e55e-4bc3-a9db-b387c89ad671", + "uuid": "500ce1a5-24eb-4d18-ac90-11301a372f0e", "files": [], "subMetas": {}, "userData": {} diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index 0cda9824..84a6ce2b 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -11,29 +11,27 @@ import { GameEvent } from "../common/config/GameEvent"; import { SkillSet } from "../common/config/SkillSet"; import { time } from "console"; import { getNeAttrs, getAttrs ,Attrs} from "../common/config/HeroAttrs"; -import { TalComp } from "./TalComp"; -import { EBusComp } from "./EBusComp"; +import { HeroSkillsComp } from "./HeroSkills"; /** 角色实体 */ @ecs.register(`Hero`) export class Hero extends ecs.Entity { HeroModel!: HeroAttrsComp; + HeroSkills!: HeroSkillsComp; View!: HeroViewComp; BattleMove!: BattleMoveComp; protected init() { this.addComponents( BattleMoveComp, HeroAttrsComp, - TalComp, - EBusComp, + HeroSkillsComp, ); } destroy(): void { this.remove(HeroViewComp); this.remove(HeroAttrsComp); - this.remove(TalComp); - this.remove(EBusComp); + this.remove(HeroSkillsComp); super.destroy(); } @@ -54,6 +52,7 @@ export class Hero extends ecs.Entity { // console.log("hero load",pos) var hv = node.getComponent(HeroViewComp)!; const model = this.get(HeroAttrsComp); + const skillsComp = this.get(HeroSkillsComp); let hero = HeroInfo[uuid]; // 共用英雄数据 // 设置 View 层属性(表现相关) @@ -68,16 +67,8 @@ export class Hero extends ecs.Entity { model.fac = FacSet.HERO; model.is_master = true; - // 设置技能 - for(let i=0; i= skill.cost; + } + + /** + * 重置技能CD(开始冷却) + * @param index 技能索引 + */ + resetCD(index: number) { + const skill = this.getSkill(index); + if (skill) { + skill.cd = skill.cd_max; + } + } + + /** + * 更新所有技能CD(每帧调用) + * @param dt 时间增量 + */ + updateCDs(dt: number) { + for (const skill of this.skills) { + if (skill.cd > 0) { + skill.cd -= dt; + if (skill.cd < 0) { + skill.cd = 0; + } + } + } + } + + /** + * 获取所有可施放的技能索引 + */ + getReadySkills(currentMp: number): number[] { + const ready: number[] = []; + for (let i = 0; i < this.skills.length; i++) { + if (this.canCast(i, currentMp)) { + ready.push(i); + } + } + return ready; + } + + reset() { + this.skills = []; + } +} \ No newline at end of file diff --git a/assets/script/game/hero/HeroSkills.ts.meta b/assets/script/game/hero/HeroSkills.ts.meta new file mode 100644 index 00000000..88e6b99b --- /dev/null +++ b/assets/script/game/hero/HeroSkills.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "a23e1b81-c0c9-4aff-bdee-ca5e033792f3", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/hero/HeroViewComp.ts b/assets/script/game/hero/HeroViewComp.ts index c9c0a373..d0f1deb9 100644 --- a/assets/script/game/hero/HeroViewComp.ts +++ b/assets/script/game/hero/HeroViewComp.ts @@ -26,17 +26,17 @@ export interface BuffInfo { @ecs.register('HeroView', false) // 定义ECS 组件 export class HeroViewComp extends CCComp { // ==================== View 层属性(表现相关)==================== - EBus:any=null! as: HeroSpine = null! status:String = "idle" scale: number = 1; // 显示方向 box_group:number = BoxSet.HERO; // 碰撞组 + is_atking:boolean = false; // 是否正在攻击 // ==================== UI 节点引用 ==================== private top_node: Node = null!; // ==================== 直接访问 HeroAttrsComp ==================== - private get model() { + get model() { return this.ent.get(HeroAttrsComp); } @@ -50,7 +50,6 @@ export class HeroViewComp extends CCComp { private damageInterval: number = 0.01; // 伤害数字显示间隔 onLoad() { this.as = this.getComponent(HeroSpine); - this.EBus=this.ent.get(EBusComp); //console.log("[HeroViewComp]:hero view comp ",this.FIGHTCON) this.on(GameEvent.FightEnd,this.do_fight_end,this) const collider = this.node.getComponent(BoxCollider2D); diff --git a/assets/script/game/hero/Mon.ts b/assets/script/game/hero/Mon.ts index 3b39cd35..62173555 100644 --- a/assets/script/game/hero/Mon.ts +++ b/assets/script/game/hero/Mon.ts @@ -9,14 +9,14 @@ import { BattleMoveComp } from "../common/ecs/position/BattleMoveComp"; import { SkillConComp } from "./SkillConComp"; import { BuffConf, SkillSet } from "../common/config/SkillSet"; import { getNeAttrs, getAttrs ,Attrs} from "../common/config/HeroAttrs"; -import { TalComp } from "./TalComp"; import { getMonAttr, MonType } from "../map/RogueConfig"; -import { EBusComp } from "./EBusComp"; import { HeroViewComp } from "./HeroViewComp"; +import { HeroSkillsComp } from "./HeroSkills"; /** 角色实体 */ @ecs.register(`Monster`) export class Monster extends ecs.Entity { HeroModel!: HeroAttrsComp; + HeroSkills!: HeroSkillsComp; HeroView!: HeroViewComp; BattleMove!: BattleMoveComp; @@ -24,16 +24,14 @@ export class Monster extends ecs.Entity { this.addComponents( BattleMoveComp, HeroAttrsComp, - TalComp, - EBusComp, + HeroSkillsComp, ); } destroy(): void { this.remove(HeroViewComp); this.remove(HeroAttrsComp); - this.remove(TalComp); - this.remove(EBusComp); + this.remove(HeroSkillsComp); super.destroy(); } @@ -54,6 +52,7 @@ export class Monster extends ecs.Entity { var view = node.getComponent(HeroViewComp)!; const model = this.get(HeroAttrsComp); + const skillsComp = this.get(HeroSkillsComp); let hero = HeroInfo[uuid]; // 共用英雄数据 // 设置 View 层属性(表现相关) view.scale = scale; @@ -82,18 +81,9 @@ export class Monster extends ecs.Entity { model.Attrs[Attrs.MAP] = map; model.Attrs[Attrs.SPEED] = hero.speed; model.Attrs[Attrs.DIS] = hero.dis; - // 初始化师兄 - - // 设置技能 - for(let i=0; i skills[i].cd_max&&this.HeroView.mp >= skills[i].cost){ - - if(SkillSet[skills[i].uuid].SType==SType.damage&&this.HeroView.is_atking){ - this.castSkill(SkillSet[skills[i].uuid]) - this.HeroView.skills[i].cd = 0 - this.HeroView.mp -= skills[i].cost - } - } - - } - - } + // 已由 SkillCDSystem 和 SkillAutocastSystem 处理 + // 此方法可以删除或改为手动施法的入口 + } + + /** + * 手动施放技能(玩家点击技能按钮) + * @param skillIndex 技能索引 + */ + manualCastSkill(skillIndex: number) { + if (!this.HeroEntity) return; + + // 选择目标 + const targets = this.selectTargets(1); + + // ✅ 通过添加标记组件请求施法 + const request = this.HeroEntity.add(CastSkillRequestComp) as CastSkillRequestComp; + request.skillIndex = skillIndex; + request.targetPositions = targets; } diff --git a/assets/script/game/skill/SkillEnt.ts b/assets/script/game/skill/SkillEnt.ts index 990b613d..6e4e27dd 100644 --- a/assets/script/game/skill/SkillEnt.ts +++ b/assets/script/game/skill/SkillEnt.ts @@ -1,6 +1,6 @@ import { instantiate, Node, Prefab, v3, Vec3 } from "cc"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; -import { Attrs, SkillSet } from "../common/config/SkillSet"; +import { SkillSet } from "../common/config/SkillSet"; import { oops } from "db://oops-framework/core/Oops"; import { smc } from "../common/SingletonModuleComp"; import { FacSet } from "../common/config/BoxSet"; diff --git a/assets/script/game/skill/快速开始.md b/assets/script/game/skill/快速开始.md new file mode 100644 index 00000000..0439c6de --- /dev/null +++ b/assets/script/game/skill/快速开始.md @@ -0,0 +1,189 @@ +# 新技能系统 - 快速开始 + +## 🚀 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); +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) +✅ 自动与战斗系统集成 +✅ 支持多种施法方式 + +**开始享受清晰的架构吧!** 🚀 + diff --git a/assets/script/game/skill/快速开始.md.meta b/assets/script/game/skill/快速开始.md.meta new file mode 100644 index 00000000..604fa4ac --- /dev/null +++ b/assets/script/game/skill/快速开始.md.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.0.1", + "importer": "text", + "imported": true, + "uuid": "a851deeb-51c4-4c8d-990f-0d460fe8848b", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/skill/新技能系统使用说明.md b/assets/script/game/skill/新技能系统使用说明.md new file mode 100644 index 00000000..684716ad --- /dev/null +++ b/assets/script/game/skill/新技能系统使用说明.md @@ -0,0 +1,296 @@ +# 新技能系统使用说明 + +## 📊 架构概览 + +基于 **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,无需重写 +✅ 与战斗系统完美集成 +✅ 支持自动/手动/强制多种施法方式 +✅ 代码清晰,注释详细 + +**可直接投入生产使用!** 🚀 + diff --git a/assets/script/game/skill/新技能系统使用说明.md.meta b/assets/script/game/skill/新技能系统使用说明.md.meta new file mode 100644 index 00000000..a8e6de63 --- /dev/null +++ b/assets/script/game/skill/新技能系统使用说明.md.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.0.1", + "importer": "text", + "imported": true, + "uuid": "2df12e82-84f3-40f1-a838-145f935d4cc1", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +}