From e571ae2caf6594120bd22f247182e24c2be1d2eb Mon Sep 17 00:00:00 2001 From: walkpan Date: Sun, 2 Feb 2025 14:48:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E7=A2=B0=E6=92=9E=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/script/Main.ts | 6 +- assets/script/game/common/config/SkillSet.ts | 17 +- .../common/ecs/position/BattleMoveComp.ts | 17 + .../common/ecs/position/BattleMoveSystem.ts | 51 +++ assets/script/game/hero/Hero.ts | 64 ++-- assets/script/game/hero/HeroModelComp.ts | 13 +- assets/script/game/hero/HeroViewComp.ts | 9 +- assets/script/game/hero/Mon.ts | 10 +- assets/script/game/module.meta | 9 + assets/script/game/skill.meta | 2 +- assets/script/game/skill/EcsSkillSystem.ts | 11 + .../script/game/skill/EcsSkillSystem.ts.meta | 9 + assets/script/game/skill/ProjectileComp.ts | 79 ++++ .../script/game/skill/ProjectileComp.ts.meta | 9 + assets/script/game/skill/SkillAnimation.ts | 55 +++ .../script/game/skill/SkillAnimation.ts.meta | 9 + assets/script/game/skill/SkillSystem.ts | 187 +++++++++ assets/script/game/skill/SkillSystem.ts.meta | 9 + ecs.md | 357 ++++++++++++++++++ 19 files changed, 857 insertions(+), 66 deletions(-) create mode 100644 assets/script/game/common/ecs/position/BattleMoveComp.ts create mode 100644 assets/script/game/common/ecs/position/BattleMoveSystem.ts create mode 100644 assets/script/game/module.meta create mode 100644 assets/script/game/skill/EcsSkillSystem.ts create mode 100644 assets/script/game/skill/EcsSkillSystem.ts.meta create mode 100644 assets/script/game/skill/ProjectileComp.ts create mode 100644 assets/script/game/skill/ProjectileComp.ts.meta create mode 100644 assets/script/game/skill/SkillAnimation.ts create mode 100644 assets/script/game/skill/SkillAnimation.ts.meta create mode 100644 assets/script/game/skill/SkillSystem.ts create mode 100644 assets/script/game/skill/SkillSystem.ts.meta create mode 100644 ecs.md diff --git a/assets/script/Main.ts b/assets/script/Main.ts index ba054f10..ad27a32f 100644 --- a/assets/script/Main.ts +++ b/assets/script/Main.ts @@ -7,6 +7,7 @@ import { UIConfigData } from './game/common/config/GameUIConfig'; import { smc } from './game/common/SingletonModuleComp'; import { Initialize } from './game/initialize/Initialize'; import { EcsPositionSystem } from './game/common/ecs/position/EcsPositionSystem'; +import { EcsSkillSystem } from './game/skill/EcsSkillSystem'; const { ccclass, property } = _decorator; @@ -30,9 +31,10 @@ export class Main extends Root { } protected async initEcsSystem() { - oops.ecs.add(new EcsPositionSystem()) - // oops.ecs.add(new EcsAccountSystem()); + oops.ecs.add(new EcsPositionSystem()); + oops.ecs.add(new EcsSkillSystem()); // oops.ecs.add(new EcsRoleSystem()); // oops.ecs.add(new EcsInitializeSystem()); + } } \ No newline at end of file diff --git a/assets/script/game/common/config/SkillSet.ts b/assets/script/game/common/config/SkillSet.ts index db67c1c0..5d2471a7 100644 --- a/assets/script/game/common/config/SkillSet.ts +++ b/assets/script/game/common/config/SkillSet.ts @@ -117,5 +117,20 @@ export const SkillSet = { 6031:{uuid:6031,path:"6031",type:1,tg:0,fname:"buff_do",flash:true,with:false,debuff:0,depb:0,debtime:0,derate:0,in:2,count:1,def:20,apup:0,ap:70,mhp:0,hp:70,cd:1,shield:0,speed:120,sonsk:0,hero:5211,name:"召唤仆从",sp_name:"zhaohuan",info:"召唤一个与施法者等级相同的骷髅战士为我方而战"}, 6032:{uuid:6032,path:"6032",type:1,tg:0,fname:"",flash:false,with:false,debuff:0,depb:0,debtime:0,derate:0,in:2,count:1,def:0,apup:0,ap:100,mhp:0,hp:5,cd:1,shield:0,speed:120,sonsk:0,hero:0,name:"自愈",sp_name:"heath_small",info:"主动:自己回复自身5%最大生命值的生命"}, 6033:{uuid:6033,path:"6033",type:1,tg:3,fname:"",flash:false,with:false,debuff:4,depb:100,debtime:1,derate:20,in:1,count:1,def:0,apup:0,ap:500,mhp:0,hp:0,cd:1,shield:0,speed:500,sonsk:6035,hero:0,name:"爆锤",sp_name:"cuida",info:"捶爆前方目标,造成300%攻击的伤害,震慑敌人,本局内全部敌方降低对方10%攻击力"}, - 6034:{uuid:6034,path:"6034",type:1,tg:3,fname:"",flash:false,with:false,debuff:4,depb:100,debtime:1,derate:20,in:1,count:1,def:0,apup:0,ap:80,mhp:0,hp:0,cd:1,shield:0,speed:350,sonsk:0,hero:0,name:"暴风箭",sp_name:"bingyu",info:"射出能量暴风箭攻击最前方范围敌人,每波造成80%攻击的伤害"} + 6034:{uuid:6034,path:"6034",type:1,tg:3,fname:"",flash:false,with:false,debuff:4,depb:100,debtime:1,derate:20,in:1,count:1,def:0,apup:0,ap:80,mhp:0,hp:0,cd:1,shield:0,speed:350,sonsk:0,hero:0,name:"暴风箭",sp_name:"bingyu",info:"射出能量暴风箭攻击最前方范围敌人,每波造成80%攻击的伤害"}, + 7001: { + prefab: "arrow", // 预制体路径 + range: 500, // 攻击距离 + width: 30, // 攻击宽度 + penetrate: 3, // 最大穿透数 + speed: 800, // 飞行速度(像素/秒) + hitInterval: 0.1 // 伤害间隔(秒) + }, + 8001: { + prefab: "fireball", + speed: 600, + range: 800, + penetrate: 2, + collisionRadius: 50 // 碰撞检测半径 + } }; \ No newline at end of file diff --git a/assets/script/game/common/ecs/position/BattleMoveComp.ts b/assets/script/game/common/ecs/position/BattleMoveComp.ts new file mode 100644 index 00000000..eae17a44 --- /dev/null +++ b/assets/script/game/common/ecs/position/BattleMoveComp.ts @@ -0,0 +1,17 @@ +import { ecs } from "../../../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; +@ecs.register('BattleMove') + +export class BattleMoveComp extends ecs.Comp { + /** 移动方向:1向右,-1向左 */ + direction: number = 1; + /** 目标x坐标 */ + targetX: number = 0; + /** 是否处于移动状态 */ + moving: boolean = true; + + reset() { + this.direction = 1; + this.targetX = 0; + this.moving = true; + } +} \ No newline at end of file diff --git a/assets/script/game/common/ecs/position/BattleMoveSystem.ts b/assets/script/game/common/ecs/position/BattleMoveSystem.ts new file mode 100644 index 00000000..3b6c8dec --- /dev/null +++ b/assets/script/game/common/ecs/position/BattleMoveSystem.ts @@ -0,0 +1,51 @@ +import { HeroViewComp } from "../../../hero/HeroViewComp"; +import { BattleMoveComp } from "./BattleMoveComp"; +import { ecs } from "../../../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; + +@ecs.register('BattleMoveSystem') +export class BattleMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { + filter(): ecs.IMatcher { + return ecs.allOf(BattleMoveComp, HeroViewComp); + } + + update(e: ecs.Entity) { + const move = e.get(BattleMoveComp); + const view = e.get(HeroViewComp); + + if (!move.moving) return; + + // 检测攻击范围内是否有敌人 + const hasEnemy = this.checkEnemiesInRange(e, view.dis); + + if (!hasEnemy) { + // 计算移动量 + const delta = view.speed * this.dt * move.direction; + const newX = view.node.position.x + delta; + + // 限制移动范围 + if (this.validatePosition(newX, move)) { + view.node.setPosition(newX, view.node.position.y, 0); + } + } + } + + /** 验证目标位置有效性 */ + private validatePosition(newX: number, move: BattleMoveComp): boolean { + // 我方不能超过右边界,敌方不能超过左边界 + return move.direction === 1 ? + newX <= move.targetX : + newX >= move.targetX; + } + + /** 检测攻击范围内敌人 */ + private checkEnemiesInRange(entity: ecs.Entity, range: number): boolean { + const currentPos = entity.get(HeroViewComp).node.position; + const team = entity.get(HeroViewComp).fac; + + return ecs.query(ecs.allOf(HeroViewComp)).some(e => { + const view = e.get(HeroViewComp); + return view.fac !== team && + Math.abs(currentPos.x - view.node.position.x) <= range; + }); + } +} \ No newline at end of file diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index 08785b17..25371810 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -1,36 +1,33 @@ - import { instantiate, Node, Prefab, Vec3 ,v3,resources,SpriteFrame,Sprite,SpriteAtlas} from "cc"; -import { UICallbacks } from "../../../../extensions/oops-plugin-framework/assets/core/gui/layer/Defines"; import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; -import { UIID } from "../common/config/GameUIConfig"; import { smc } from "../common/SingletonModuleComp"; import { HeroModelComp } from "./HeroModelComp"; -import { HeroSpine } from "./HeroSpine"; import { HeroViewComp } from "./HeroViewComp"; import { BoxSet } from "../common/config/BoxSet"; -import { RandomManager } from "../../../../extensions/oops-plugin-framework/assets/core/common/random/RandomManager"; import { HeroInfo } from "../common/config/heroSet"; -import { MoveToComp } from "../common/ecs/position/MoveTo"; import { Talents } from "../common/config/TalentSet"; -import { MonModelComp } from "./MonModelComp"; +import { SkillsComp } from "../skill/SkillSystem"; +import { BattleMoveComp } from "../common/ecs/position/BattleMoveComp"; /** 角色实体 */ @ecs.register(`Hero`) + export class Hero extends ecs.Entity { - // 数据层 HeroModel!: HeroModelComp; - // 视图层 - HeroView!: HeroViewComp; - + HeroView!: HeroViewComp; + Skills!: SkillsComp; + BattleMove!: BattleMoveComp; protected init() { - - + this.addComponents( + SkillsComp, + BattleMoveComp + ); } + destroy(): void { this.remove(HeroViewComp); - this.remove(MoveToComp); super.destroy(); } @@ -42,18 +39,14 @@ export class Hero extends ecs.Entity { var path = "game/heros/"+HeroInfo[uuid].path; var prefab: Prefab = oops.res.get(path, Prefab)!; var node = instantiate(prefab); - var scene = smc.map.MapView.scene; node.parent = scene.entityLayer!.node! - node.setPosition(pos) this.hero_init(uuid,node,scale,box_group,is_call,lv) oops.message.dispatchEvent("hero_load",this) } - hero_init(uuid:number=1001,node:Node,scale:number=1,box_group=BoxSet.HERO,is_call:boolean=false,lv:number=1){ var hv = node.getComponent(HeroViewComp)!; - // console.log("hero_init",buff) let hero= HeroInfo[uuid] // 共用英雄数据 let role =smc.heros[uuid] if(is_call){ @@ -61,6 +54,7 @@ export class Hero extends ecs.Entity { } let talents=Talents; hv.scale = scale; + hv.fac = 0; hv.box_group = box_group; hv.hero_uuid= uuid; hv.hero_name= hero.name; @@ -74,13 +68,11 @@ export class Hero extends ecs.Entity { hv.cpw=hero.cpw; hv.dpw=hero.dpw; hv.dopw=hero.dopw; - hv.lv = role.lv; hv.type = hero.type; let slv= Math.floor(( hv.lv) / 5); let sklv=slv if(sklv >= 5) sklv=5; - hv.sk1 = hero.sk1[sklv]; hv.sk2 = hero.sk2[sklv]; hv.sk3 = hero.sk3[sklv]; @@ -104,27 +96,17 @@ export class Hero extends ecs.Entity { hv.cexp=hero.cexp hv.doexp=hero.doexp hv.dexp=hero.dexp; - this.add(hv); + + // 初始化多个技能组件 + const skills = this.get(SkillsComp); + + // 初始化移动参数 + const move = this.get(BattleMoveComp); + move.direction = 1; // 向右移动 + move.targetX = 800; // 右边界 + console.log("hero_init",skills,move); + } - set_ratio(uuid:number){ - let ratio=1; - switch (HeroInfo[uuid].level) { - case 2: - ratio=1.05 - break; - case 3: - ratio=1.1 - break; - case 4: - ratio=1.15 - break; - case 5: - ratio=1.2 - break; - default: - ratio=1 - } - return ratio; - } + } \ No newline at end of file diff --git a/assets/script/game/hero/HeroModelComp.ts b/assets/script/game/hero/HeroModelComp.ts index bb11fa51..45e7543c 100644 --- a/assets/script/game/hero/HeroModelComp.ts +++ b/assets/script/game/hero/HeroModelComp.ts @@ -5,19 +5,8 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec */ @ecs.register('HeroModel') export class HeroModelComp extends ecs.Comp { - /** 角色编号 */ - id: number = -1; - /** 角色名 */ - name: string = "mon"; - /** speed */ - // speed: number = 0; - /** 动画名资源 */ - anim: string = "mon"; - reset() { - this.id = -1; - // this.speed = 0; - this.name = ""; + } } diff --git a/assets/script/game/hero/HeroViewComp.ts b/assets/script/game/hero/HeroViewComp.ts index ba5dcaa1..a5577978 100644 --- a/assets/script/game/hero/HeroViewComp.ts +++ b/assets/script/game/hero/HeroViewComp.ts @@ -1,9 +1,3 @@ -/* - * @Author: dgflash - * @Date: 2021-11-18 17:42:59 - * @LastEditors: dgflash - * @LastEditTime: 2022-08-17 12:36:18 - */ import { Vec3, _decorator , v3,Collider2D,Contact2DType,Label,RigidBody2D ,Node,Prefab,instantiate,ProgressBar, Component, Material, Sprite, math, clamp, Game, tween} from "cc"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; @@ -42,11 +36,12 @@ export class HeroViewComp extends CCComp { slv:number =1; scale: number = 1; /** 角色阵营 1:hero -1 :mon */ type: number = 0; /**角色类型 0近战 1 远程 2 辅助 */ - + fac:number=0; //阵营 0:hero 1:monster box_group:number = BoxSet.HERO; atk_range:number = 150; + is_dead:boolean = false; //是否摧毁 is_stop:boolean = false; is_atking:boolean = false; diff --git a/assets/script/game/hero/Mon.ts b/assets/script/game/hero/Mon.ts index debc03df..1a1da4c4 100644 --- a/assets/script/game/hero/Mon.ts +++ b/assets/script/game/hero/Mon.ts @@ -1,4 +1,3 @@ - /* * @Author: dgflash * @Date: 2021-11-18 17:47:56 @@ -20,6 +19,7 @@ import { HeroInfo } from "../common/config/heroSet"; import { MoveToComp } from "../common/ecs/position/MoveTo"; import { Talents } from "../common/config/TalentSet"; import { MonModelComp } from "./MonModelComp"; +import { BattleMoveComp } from "../common/ecs/position/BattleMoveComp"; /** 角色实体 */ @ecs.register(`Monster`) export class Monster extends ecs.Entity { @@ -45,7 +45,7 @@ export class Monster extends ecs.Entity { scale=-1 let box_group=BoxSet.MONSTER console.log("mon load",uuid) - this.addComponents( MonModelComp); + this.addComponents( MonModelComp, BattleMoveComp); var path = "game/heros/"+HeroInfo[uuid].path; var prefab: Prefab = oops.res.get(path, Prefab)!; var node = instantiate(prefab); @@ -55,6 +55,11 @@ export class Monster extends ecs.Entity { node.setPosition(pos) this.hero_init(uuid,node,scale,box_group,is_boss,is_call,lv) oops.message.dispatchEvent("monster_load",this) + + // 初始化移动参数 + const move = this.get(BattleMoveComp); + move.direction = -1; // 向左移动 + move.targetX = -800; // 左边界 } hero_init(uuid:number=1001,node:Node,scale:number=1,box_group=BoxSet.HERO,is_boss:boolean=false,is_call:boolean=false,lv:number=1) { @@ -64,6 +69,7 @@ export class Monster extends ecs.Entity { let talent= smc.vmdata.talent //角色英雄数据 let talents=Talents; hv.scale = scale; + hv.fac = 1; hv.is_boss = is_boss; hv.box_group = box_group; hv.hero_uuid= uuid; diff --git a/assets/script/game/module.meta b/assets/script/game/module.meta new file mode 100644 index 00000000..ad751953 --- /dev/null +++ b/assets/script/game/module.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "8003b099-fab7-454f-b142-3f767dc597ee", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/skill.meta b/assets/script/game/skill.meta index 6afe16f3..391061ab 100644 --- a/assets/script/game/skill.meta +++ b/assets/script/game/skill.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.0", + "ver": "1.2.0", "importer": "directory", "imported": true, "uuid": "a80a879f-d214-454c-a574-18f080ae0d91", diff --git a/assets/script/game/skill/EcsSkillSystem.ts b/assets/script/game/skill/EcsSkillSystem.ts new file mode 100644 index 00000000..07e1c8c8 --- /dev/null +++ b/assets/script/game/skill/EcsSkillSystem.ts @@ -0,0 +1,11 @@ +import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; +import { SkillSystem } from "./SkillSystem"; +import { SkillAnimationSystem } from "./SkillAnimation"; +export class EcsSkillSystem extends ecs.System { + constructor() { + super(); + this.add(new SkillSystem()); + this.add(new SkillAnimationSystem()); + } + +} diff --git a/assets/script/game/skill/EcsSkillSystem.ts.meta b/assets/script/game/skill/EcsSkillSystem.ts.meta new file mode 100644 index 00000000..75b6af90 --- /dev/null +++ b/assets/script/game/skill/EcsSkillSystem.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "ea1f8dae-6593-46bf-b20b-a301b495a434", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/skill/ProjectileComp.ts b/assets/script/game/skill/ProjectileComp.ts new file mode 100644 index 00000000..153a5f03 --- /dev/null +++ b/assets/script/game/skill/ProjectileComp.ts @@ -0,0 +1,79 @@ +import { Vec3 } from "cc"; +import { ecs } from "db://oops-framework/libs/ecs/ECS"; +import { HeroViewComp } from "../hero/HeroViewComp"; + +// 投射物组件 +@ecs.register('Projectile') +export class ProjectileComp extends ecs.Comp { + speed: number = 500; // 飞行速度 + direction: Vec3 = Vec3.RIGHT;// 飞行方向 + maxDistance: number = 1000; // 最大射程 + traveled: number = 0; // 已飞行距离 + penetrate: number = 3; // 穿透次数 + targets = new Set(); // 已命中目标 + + reset() { + this.speed = 500; + this.direction.set(Vec3.RIGHT); + this.maxDistance = 1000; + this.traveled = 0; + this.penetrate = 3; + this.targets.clear(); + } +} + +// 投射物系统 +@ecs.register('ProjectileSystem') +export class ProjectileSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { + filter(): ecs.IMatcher { + return ecs.allOf(ProjectileComp, HeroViewComp); + } + + update(e: ecs.Entity): void { + const proj = e.get(ProjectileComp); + const view = e.get(HeroViewComp); + + // 移动计算 + const delta = proj.direction.multiplyScalar(proj.speed * this.dt); + view.node.position = view.node.position.add(delta); + proj.traveled += delta.length(); + + // 碰撞检测(使用ECS组件检测) + this.checkCollision(e); + + // 超出射程销毁 + if (proj.traveled >= proj.maxDistance) { + e.destroy(); + } + } + + private checkCollision(e: ecs.Entity) { + const proj = e.get(ProjectileComp); + const view = e.get(HeroViewComp); + + // 获取范围内所有敌人 + // const enemies = ecs.getEntities(HeroViewComp).filter(entity => { + // const enemyView = entity.get(HeroViewComp); + // return enemyView.boxGroup !== view.boxGroup && + // Vec3.distance(view.node.position, enemyView.node.position) <= 50; // 碰撞半径 + // }); + + // enemies.forEach(enemy => { + // if (!proj.targets.has(enemy)) { + // this.applyDamage(e, enemy); + // proj.targets.add(enemy); + // proj.penetrate--; + // } + // }); + + if (proj.penetrate <= 0) { + e.destroy(); + } + } + + private applyDamage(projectile: ecs.Entity, target: ecs.Entity) { + const projView = projectile.get(HeroViewComp); + const targetView = target.get(HeroViewComp); + // targetView.takeDamage(projView.attack); + } +} \ No newline at end of file diff --git a/assets/script/game/skill/ProjectileComp.ts.meta b/assets/script/game/skill/ProjectileComp.ts.meta new file mode 100644 index 00000000..3be79b2e --- /dev/null +++ b/assets/script/game/skill/ProjectileComp.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "f563271b-ee28-4cc0-86ec-6947e9fe454e", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/skill/SkillAnimation.ts b/assets/script/game/skill/SkillAnimation.ts new file mode 100644 index 00000000..787607a5 --- /dev/null +++ b/assets/script/game/skill/SkillAnimation.ts @@ -0,0 +1,55 @@ +import { ecs ,} from "db://oops-framework/libs/ecs/ECS"; +import { HeroViewComp } from "../hero/HeroViewComp"; +import { SkillsComp } from "./SkillSystem"; +import { SkillSet } from "../common/config/SkillSet"; +import { Node } from "cc"; + +// 动画组件 +@ecs.register('SkillAnimation') +export class SkillAnimationComp extends ecs.Comp { + prefab: Node | null = null; // 预制体实例 + damageTriggerTime = 0.3; // 伤害触发时间(秒) + elapsed = 0; // 已播放时间 + + reset() { + this.prefab?.destroy(); + this.prefab = null; + this.damageTriggerTime = 0.3; + this.elapsed = 0; + } +} + +// 动画系统 +@ecs.register('SkillAnimationSystem') +export class SkillAnimationSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { + filter(): ecs.IMatcher { + return ecs.allOf(SkillAnimationComp, HeroViewComp); + } + + update(e: ecs.Entity): void { + const anim = e.get(SkillAnimationComp); + anim.elapsed += this.dt; + + // 伤害触发检测 + // if (!anim.hitted && anim.elapsed >= anim.hitTime) { + // this.applyDamage(e); + // anim.hitted = true; + // } + + // 更新动画状态 + // if (anim.elapsed >= anim.duration) { + // e.remove(SkillAnimationComp); + // } + } + + private applyDamage(e: ecs.Entity) { + const skill = e.get(SkillsComp); + const view = e.get(HeroViewComp); + + // 添加伤害标记组件 + + } + + // 在角色组件中实现目标查找 + +} \ No newline at end of file diff --git a/assets/script/game/skill/SkillAnimation.ts.meta b/assets/script/game/skill/SkillAnimation.ts.meta new file mode 100644 index 00000000..daac5ac8 --- /dev/null +++ b/assets/script/game/skill/SkillAnimation.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "8c885682-d6d1-4bca-a06b-edb7e1389c08", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/skill/SkillSystem.ts b/assets/script/game/skill/SkillSystem.ts new file mode 100644 index 00000000..d5b2a73a --- /dev/null +++ b/assets/script/game/skill/SkillSystem.ts @@ -0,0 +1,187 @@ +import { Node, Vec3 } from "cc"; +import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; +import { HeroViewComp } from "../hero/HeroViewComp"; +import { SkillSet } from "../common/config/SkillSet"; +import { SkillAnimationComp } from "./SkillAnimation"; +import { ProjectileComp } from "./ProjectileComp"; + +/** 技能触发组件 */ +@ecs.register('HerosSkills') +export class SkillsComp extends ecs.Comp { + /** 技能ID */ + skillId: number = 0; + /** 目标位置/实体 */ + target: Vec3 | Node | null = null; + /** 当前冷却时间 */ + currentCooldown: number = 0; + + reset() { + this.skillId = 0; + this.target = null; + this.currentCooldown = 0; + } +} + +/** 技能系统 */ +@ecs.register('SkillSystem') +export class SkillSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { + filter(): ecs.IMatcher { + return ecs.allOf(SkillsComp, HeroViewComp); + } + + update(e: ecs.Entity) { + let skill = e.get(SkillsComp); + let view = e.get(HeroViewComp); + + if (this.canCastSkill(skill, view)) { + this.castSkill(e, skill); + } + } + + private canCastSkill(skill: SkillsComp, view: HeroViewComp): boolean { + return skill.currentCooldown <= 0 && + view.pw >= view.pwm; + } + + private castSkill(entity: ecs.Entity, skill: SkillsComp) { + const skillData = SkillSet[skill.skillId]; + + // // 创建飞弹实体 + // const projectile = ecs.createEntity(); + // const projView = projectile.add(HeroViewComp); + // projView.node = instantiate(skillData.prefab); + // projView.node.setParent(entity.get(HeroViewComp).node.parent); + // projView.node.setPosition(entity.get(HeroViewComp).node.position); + + // // 添加投射物组件 + // projectile.add(ProjectileComp, { + // speed: skillData.speed, + // direction: entity.get(HeroViewComp).node.scale.x > 0 ? Vec3.RIGHT : Vec3.LEFT, + // maxDistance: skillData.range, + // penetrate: skillData.penetrate + // }); + + // 应用冷却时间 + skill.currentCooldown = skillData.cooldown; + + // 根据技能类型处理 + switch(skillData.type) { + case 'damage': + this.handleDamage(entity, skillData); + break; + case 'heal': + this.handleHeal(entity, skillData); + break; + case 'projectile': + this.castProjectileSkill(entity, skillData); + break; + } + + // 播放动画(示例) + // entity.get(HeroViewComp).playAnimation(skillData.anim); + + // 触发完成回调 + entity.remove(SkillsComp); + } + + private handleDamage(entity: ecs.Entity, data: any) { + const view = entity.get(HeroViewComp); + // 实现伤害逻辑... + } + + private handleHeal(entity: ecs.Entity, data: any) { + const view = entity.get(HeroViewComp); + // 实现治疗逻辑... + } + + private castProjectileSkill(entity: ecs.Entity, skillData: any) { + const view = entity.get(HeroViewComp); + + // 创建飞弹实体 + + } + + private exit(e: ecs.Entity) { + e.remove(SkillsComp); + } +} + +// 动画系统 +@ecs.register('SkillAnimationSystem') +export class SkillAnimationSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { + filter(): ecs.IMatcher { + return ecs.allOf(SkillAnimationComp); + } + + update(e: ecs.Entity): void { + const anim = e.get(SkillAnimationComp); + anim.elapsed += this.dt; + + // 触发伤害检测 + if (anim.elapsed >= anim.damageTriggerTime) { + e.add(DamageLineComp); // 添加伤害区域组件 + e.remove(SkillAnimationComp); + } + } +} + +// 直线型伤害区域组件 +@ecs.register('DamageLine') +export class DamageLineComp extends ecs.Comp { + startPos: Vec3 = new Vec3(); + direction: Vec3 = Vec3.RIGHT; + length: number = 300; + width: number = 50; + + reset() { + this.startPos.set(); + this.direction.set(Vec3.RIGHT); + this.length = 300; + this.width = 50; + } +} + +// 直线伤害系统 +@ecs.register('DamageLineSystem') +export class DamageLineSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { + filter(): ecs.IMatcher { + return ecs.allOf(DamageLineComp, HeroViewComp); + } + + update(e: ecs.Entity): void { + const line = e.get(DamageLineComp); + const caster = e.get(HeroViewComp); + + // 根据角色朝向调整方向 + line.direction = caster.node.scale.x > 0 ? Vec3.RIGHT : new Vec3(-1, 0, 0); + + // 获取直线区域内的目标 + const targets = this.getTargetsInLine( + caster.node.worldPosition, + line.direction, + line.length, + line.width, + caster.box_group + ); + + // 应用伤害 + targets.forEach(target => { + + }); + + e.remove(DamageLineComp); + } + + private getTargetsInLine(origin: Vec3, dir: Vec3, length: number, width: number, team: number): ecs.Entity[] { + return []; + } + + private isInLine(origin: Vec3, dir: Vec3, target: Vec3, length: number, width: number): boolean { + return false; // 临时返回值保持类型安全 + // const toTarget = target.subtract(origin); + // const projection = Vec3.project(toTarget, dir); + } +} + + + diff --git a/assets/script/game/skill/SkillSystem.ts.meta b/assets/script/game/skill/SkillSystem.ts.meta new file mode 100644 index 00000000..0462ccf0 --- /dev/null +++ b/assets/script/game/skill/SkillSystem.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "c927f5c9-45cb-4400-8734-c706e615e4a4", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/ecs.md b/ecs.md new file mode 100644 index 00000000..50b1f859 --- /dev/null +++ b/ecs.md @@ -0,0 +1,357 @@ +# 简介 +libs/ecs 这是一个 Typescript 语言版的Entity-Component-System框架架。 + +# 使用说明 +创建实体 +```Typescript +ecs.getEntity(ecs.Entity); +``` + +## 组件 +自定义组件必须继承ecs.Comp,并且需要使用ecs.register注册组件。 +```TypeScript +@ecs.register('Hello') +export class HelloComponent extends ecs.Comp { + info: string; + data: number; + + // 组件被回收前会调用这个方法。 + reset() { + this.info = ''; + this.data = 0; + } +} +``` + +## ecs.register功能 +- 能通过```entity.Hello```获得组件对象; +- 将组件的构造函数存入ecs上下文中,并且给该类组件分配一个组件id。 + +## 实体 +为了能利用Typescript的类型提示机制,在使用实体的时候需要用户自己继承ecs.Entity。 +```TypeScript +ecs.register('HelloEntity') +export class HelloEntity extends ecs.Entity { + Hello: HelloComponent; // 这里的Hello要和ecs.register中填入的参数一致 +} +``` + +- 管理子实体 +```TypeScript +// 添加子实体 +entity.addChild(ecs.Entity); + +// 移除子实体 +entity.removeChild(ecs.Entity); +``` + +- 添加组件: +```TypeScript +entity.add(HelloComponent); // 添加组件时会优先从组件缓存池中获取无用的组件对象,如果没有才会新创建一个组件对象 +``` + +- 添加组件对象:注意,外部创建的组件对象ecs系统不负责回收,需要用户自己管理该组件对象的声明周期。 +```Typescript +let compObj = new HelloComponent(); +entity.add(compObj) +``` + +- 删除组件: +```TypeScript +entity.remove(HelloComponent); // 组件对象会从实体身上移除并放入组件缓存池中 +``` + +- 删除组件但不删除组件对象身上:实际开发中,组件有很多属性,如果删除了后面再添加,属性值还原是个麻烦的问题, +remove方法可以删除组件,但是不真正从实体身上移除该组件对象,这样下次重新添加组件时还是会添加那个组件对象。 +```Typescript +entity.remove(HelloComponent, false) +``` + +- 获得组件对象 +```TypeScript +entity.Hello; // 见上方自定义实体操作 + +entity.get(HelloComponent); +``` + +- 判断是否拥有组件: +```TypeScript +entity.has(HelloComponent); + +!!entity.Hello; +``` + +- 销毁实体: +```TypeScript +entity.destroy() // 销毁实体时会先删除实体身上的所有组件,然后将实体放入实体缓存池中 +``` + +## 实体筛选 +目前提供了四种类型的筛选能力,但是这四种筛选能力可以组合从而提供更强大的筛选功能。 +- anyOf: 用来描述包含任意一个这些组件的实体; +- allOf: 用来;描述同时包含了这些组件的实体 +- onlyOf: 用来描述只包含了这些组件的实体;不是特殊情况不建议使用onlyOf,因为onlyOf会监听所有组件的添加和删除事件; +- excludeOf: 表示不包含所有这里面的组件(与关系); + +使用方式: + +- 表示同时拥有多个组件 +```TypeScript +ecs.allOf(AComponent, BComponent, CComponent); +``` +- 表示拥有任意一个组件 +```Typescript +ecs.anyOf(AComponent, BComponent); +``` +- 表示拥有某些组件,并且不包含某些组件 +```Typescript +// 不包含CComponent或者DComponent +ecs.allOf(AComponent, BComponent).excludeOf(CComponent, DComponent); + +// 不同时包含CComponent和DComponent +ecs.allOf(AComponent, BComponent).excludeOf(CComponent).excludeOf(DComponent); +``` + +### 直接查询并获得实体 +```Typescript +ecs.query(ecs.allOf(Comp1, Comp2)) +``` + +## 系统 +- ecs.System: 用来组合某一功能所包含的System; +- ecs.RootSystem: System的root; +- ecs.ComblockSystem: 抽象类,组合式的System。默认情况,如果该System有实体,则每帧都会执行update方法; +- ecs.IEntityEnterSystem: 实现这个接口表示关注实体的首次进入; +- ecs.IEntityRemoveSystem: 实现这个接口表示关注实体的移除; +- ecs.ISystemFirstUpdate: 实现这个接口会在System第一次执行update前执行一次firstUpdate +- ecs.ISystemUpdate:实现这个接口会在System中每帧出发update方法 + +# 怎么使用 +1、声明组件 +```TypeScript +@ecs.register('Node') +export class NodeComponent extends ecs.Comp { + val: cc.Node = null; + + reset() { + this.val = null; + } +} + +@ecs.reigster('Move') +export class MoveComponent extends ecs.Comp { + heading: cc.Vec2 = cc.v2(); + speed: number = 0; + + reset() { + this.heading.x = 0; + this.heading.y = 0; + this.speed = 0; + } +} + +@ecs.register('Transform') +export class TransformComponent extends ecs.Comp { + position: cc.Vec2 = cc.v2(); + angle: number; + reset() { + + } +} + +export class AvatarEntity extends ecs.Entity { + Node: NodeComponent; + Move: MoveComponent; + Transform: TransformComponent; +} +``` + +2、创建系统 +```TypeScript +export class RoomSystem extends ecs.RootSystem { + constructor() { + super(); + this.add(new MoveSystem()); + this.add(new RenderSystem()); + } +} + +export class MoveSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem, ecs.ISystemUpdate { + init() { + + } + + filter(): ecs.IMatcher { + return ecs.allOf(MoveComponent, TransformComponent); + } + + // 实体第一次进入MoveSystem会进入此方法 + entityEnter(e: AvatarEntity) { + e.Move.speed = 100; + } + + // 每帧都会更新 + update(e: AvatarEntity) { + let moveComp = e.Move; // e.get(MoveComponent); + lel position = e.Transform.position; + + position.x += moveComp.heading.x * moveComp.speed * this.dt; + position.y += moveComp.heading.y * moveComp.speed * this.dt; + + e.Transform.angle = cc.misc.lerp(e.Transform.angle, Math.atan2(moveComp.speed.y, moveComp.speed.x) * cc.macro.DEG, dt); + } +} + +export class RenderSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem, ecs.ISystemUpdate { + filter(): ecs.IMatcher { + return ecs.allOf(NodeComponent, TransformComponent); + } + + // 实体第一次进入MoveSystem会进入此方法 + entityEnter(e: AvatarEntity) { + e.Node.val.active = true; + } + + entityRemove(e: AvatarEntity) { + + } + + update(e: AvatarEntity) { + e.Node.val.setPosition(e.Transform.position); + e.Node.val.angle = e.Transform.angle; + } +} +``` + +3、驱动ecs框架 +```TypeScript +const { ccclass, property } = cc._decorator; +@ccclass +export class GameControllerBehaviour extends Component { + rootSystem: RootSystem = null; + + onLoad() { + this.rootSystem = new RootSystem(); + this.rootSystem.init(); + } + + createAvatar(node: cc.Node) { + let entity = ecs.createEntityWithComps(NodeComponent, TransformComponent, MoveComponent); + entity.Node.val = node; + } + + update(dt: number) { + this.rootSystem.execute(dt); + } +} + +``` + +# 和Cocos Creator的组件混合使用 +## 创建基类 +```Typescript +import { Component, _decorator } from "cc"; +import { ecs } from "../../../Libs/ECS"; +const { ccclass, property } = _decorator; + +@ccclass('CCComp') +export abstract class CCComp extends Component implements ecs.IComp { + static tid: number = -1; + static compName: string; + + canRecycle: boolean; + ent: ecs.Entity; + + onLoad() { + this.ent = ecs.createEntity(); + this.ent.add(this); + } + + abstract reset(): void; +} +``` + +## 创建ecs组件并且赋予序列化的功能,这样就能在Cocos Creator的“属性检查器”上修改参数 +```Typescript +import { _decorator, toDegree, v3, Node, Vec3 } from "cc"; +import { ecs } from "../../../Libs/ECS"; +const { ccclass, property } = _decorator; + +let outV3 = v3(); +@ccclass('MovementComponent') +@ecs.register('Movement') +export class MovementComponent extends CCComp { + pos: Vec3 = v3(); + angle: number = 0; + speed: number = 0; + + @property + acceleration: number = 0; + + @property + private _maxSpeed: number = 0; + @property + set maxSpeed(val: number) { + this._maxSpeed = val; + } + get maxSpeed() { + return this._maxSpeed; + } + + @property + heading: Vec3 = v3(); + + @property + targetHeading: Vec3 = v3(); + + reset() { + + } + + update(dt: number) { + if(!Vec3.equals(this.heading, this.targetHeading, 0.01)) { + Vec3.subtract(outV3, this.targetHeading, this.heading); + outV3.multiplyScalar(0.025); + this.heading.add(outV3); + this.heading.normalize(); + this.angle = toDegree(Math.atan2(this.heading.y, this.heading.x)) - 90; + } + + this.speed = Math.min(this.speed + this.acceleration * dt, this._maxSpeed); + + this.pos.add3f(this.heading.x * this.speed * dt, this.heading.y * this.speed * dt, 0); + } + + calcAngle() { + this.angle = toDegree(Math.atan2(this.heading.y, this.heading.x)) - 90; + return this.angle; + } +} + +``` + +## 创建面向Cocos Creator的组件 +```Typescript +import { Component, _decorator } from "cc"; +const { ccclass, property } = _decorator; +@ccclass('Player') +@ecs.register('Player', false) +export class Player extends CCComp { + @property({ + type: MovementComponent + }) + movement: MovementComponent; + + onLoad() { + super.onLoad(); + + // 添加MovementComponent组件对象 + this.ent.add(this.movement); + } +} +``` + +# 调试 +添加如下代码 +```TypeScript +windows['ecs'] = ecs; \ No newline at end of file