diff --git a/assets/script/game/common/config/heroSet.ts b/assets/script/game/common/config/heroSet.ts index 6ec8b324..7da9e3c2 100644 --- a/assets/script/game/common/config/heroSet.ts +++ b/assets/script/game/common/config/heroSet.ts @@ -186,49 +186,49 @@ export const HeroInfo: Record = { // 1. 基础近战型 5201:{uuid:5201,name:"兽人战士",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,ss:10, type:HType.warrior,lv:1,hp:60,ap:8,speed:180,skills:[6003], - rangeType: SkillRange.Long, + rangeType: SkillRange.Melee, buff:[],info:"标准炮灰:确保英雄能完成3次普攻积累天赋计数"}, // 2. 快速突击型 5301:{uuid:5301,name:"兽人斥候",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:1.2,ss:10, type:HType.assassin,lv:1,hp:40,ap:12,speed:400,skills:[6003], - rangeType: SkillRange.Long, + rangeType: SkillRange.Melee, buff:[],info:"快速突击:极高移速贴脸,检测护盾(7102)刷新率"}, // 3. 重型坦克型 5401:{uuid:5401,name:"兽人卫士",icon:"1001",path:"mo3", fac:FacSet.MON, kind:1,as:5.0,ss:10, type:HType.warrior,lv:1,hp:200,ap:15,speed:60,skills:[6003], - rangeType: SkillRange.Long, + rangeType: SkillRange.Melee, buff:[],info:"重型坦克:数值墙,检测玩家破甲(7008)与持续输出"}, // 4. 远程骚扰型 5501:{uuid:5501,name:"兽人射手",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,ss:10, type:HType.remote,lv:1,hp:50,ap:10,speed:90,skills:[6203], - rangeType: SkillRange.Long, + rangeType: SkillRange.Melee, buff:[],info:"远程骚扰:跨屏打击,迫使阵地分散或移动英雄"}, // 5. 特殊机制型 5601:{uuid:5601,name:"兽人自爆兵",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,ss:10, type:HType.assassin,lv:1,hp:80,ap:200,speed:220,skills:[6003], - rangeType: SkillRange.Long, + rangeType: SkillRange.Melee, buff:[],info:"特殊机制:极端伤害,漏怪即秒杀,检测减伤(7103)"}, // 召唤师:持续召唤小怪(后续可在技能系统中实现 SType.zhaohuan) 5602:{uuid:5602,name:"兽人召唤师",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,ss:10, type:HType.mage,lv:1,hp:150,ap:10,speed:100,skills:[6003], - rangeType: SkillRange.Long, + rangeType: SkillRange.Melee, buff:[],info:"战术目标:持续召唤小怪,检测英雄大招清场频率"}, // 治疗者:为周围怪物回血(此处以提升治疗效果和生命回复为基础被动) 5603:{uuid:5603,name:"兽人祭司",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,ss:10, type:HType.support,lv:1,hp:150,ap:10,speed:105,skills:[6003], - rangeType: SkillRange.Long, + rangeType: SkillRange.Melee, buff:[],info:"战术目标:为怪群回血,检测玩家沉默(7006)覆盖率"}, // 光环怪:为周围怪物提供增益(此处以Buff效果提升与移动速度提升为基础被动) // Attrs.BUFF_UP=60 (RATIO=1),Attrs.SPEED=63 (RATIO=1) 5604:{uuid:5604,name:"兽人图腾师",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,ss:10, type:HType.support,lv:1,hp:150,ap:10,speed:110,skills:[6003], - rangeType: SkillRange.Long, + rangeType: SkillRange.Melee, buff:[],info:"战术目标:提供加速光环,改变怪群推进节奏"}, // 6. 精英/BOSS型 5701:{uuid:5701,name:"兽人首领(BOSS)",icon:"1001",path:"mo4", fac:FacSet.MON, kind:1,as:2.5,ss:10, type:HType.warrior,lv:3,hp:2000,ap:60,speed:120,skills:[6003], - rangeType: SkillRange.Long, + rangeType: SkillRange.Melee, buff:[],info:"终极考验:极高HP,检测大招重置与辐射协同输出"}, }; diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index 23422c38..2c71ea15 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -8,20 +8,19 @@ import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet"; import { HeroInfo, HeroPos, HType } from "../common/config/heroSet"; import { GameEvent } from "../common/config/GameEvent"; import { Attrs} from "../common/config/HeroAttrs"; -import { HeroMoveComp } from "./HeroMove"; +import { MoveComp } from "./MoveComp"; import { mLogger } from "../common/Logger"; -import { HeroMasterComp } from "./HeroMasterComp"; /** 角色实体 */ @ecs.register(`Hero`) export class Hero extends ecs.Entity { HeroModel!: HeroAttrsComp; View!: HeroViewComp; - HeroMove!: HeroMoveComp; + HeroMove!: MoveComp; debugMode: boolean = false; // 是否启用调试模式 protected init() { this.addComponents( - HeroMoveComp, + MoveComp, HeroAttrsComp, ); } @@ -35,7 +34,6 @@ export class Hero extends ecs.Entity { this.remove(HeroViewComp); this.remove(HeroAttrsComp); - this.remove(HeroMasterComp) super.destroy(); } @@ -98,9 +96,10 @@ export class Hero extends ecs.Entity { model.back_chance=FightSet.BACK_CHANCE this.add(hv); oops.message.dispatchEvent(GameEvent.MasterCalled,{uuid:uuid}) - const move = this.get(HeroMoveComp); + const move = this.get(MoveComp); move.direction = 1; // 向右移动 move.targetX = 0; // 右边界' + move.baseY = pos.y; if(HeroInfo[uuid].type==HType.remote){ move.targetX = -100; // 右边界' } @@ -122,7 +121,7 @@ export class HeroLifecycleSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem { filter() { - return ecs.allOf(HeroMoveComp); + return ecs.allOf(MoveComp); } entityEnter(e: ecs.Entity): void { diff --git a/assets/script/game/hero/Mon.ts b/assets/script/game/hero/Mon.ts index 0a76a702..30d34539 100644 --- a/assets/script/game/hero/Mon.ts +++ b/assets/script/game/hero/Mon.ts @@ -8,14 +8,14 @@ import { HeroAttrsComp } from "./HeroAttrsComp"; import { BuffConf, SkillSet } from "../common/config/SkillSet"; import { getMonAttr, MonType } from "../map/RogueConfig"; import { HeroViewComp } from "./HeroViewComp"; -import { MonMoveComp } from "./MonMove"; +import { MoveComp } from "./MoveComp"; import { mLogger } from "../common/Logger"; /** 角色实体 */ @ecs.register(`Monster`) export class Monster extends ecs.Entity { HeroModel!: HeroAttrsComp; HeroView!: HeroViewComp; - MonMove!: MonMoveComp; + MonMove!: MoveComp; private debugMode: boolean = false; // 是否启用调试模式 // 多键对象池:Map @@ -40,7 +40,7 @@ export class Monster extends ecs.Entity { protected init() { this.addComponents( - MonMoveComp, + MoveComp, HeroAttrsComp, ); } @@ -135,10 +135,11 @@ export class Monster extends ecs.Entity { oops.message.dispatchEvent("monster_load",this) // 初始化移动参数,包括线路和生成顺序 - const move = this.get(MonMoveComp); + const move = this.get(MoveComp); move.reset(); move.direction = -1; // 向左移动 - move.targetX = -800; // 左边界 + move.targetX = Math.max(-320, Math.min(320, pos.x)); + move.baseY = pos.y; move.lane = lane; // 设置线路标识 move.spawnOrder = spawnOrder; // 设置生成顺序 smc.vmdata.mission_data.mon_num++ @@ -157,7 +158,7 @@ export class MonLifecycleSystem extends ecs.ComblockSystem debugMode: boolean = false; // 是否启用调试模式 filter() { - return ecs.allOf(MonMoveComp); + return ecs.allOf(MoveComp); } entityEnter(e: ecs.Entity): void { diff --git a/assets/script/game/hero/MonMove.ts b/assets/script/game/hero/MonMove.ts index 39f5afb0..1001ce94 100644 --- a/assets/script/game/hero/MonMove.ts +++ b/assets/script/game/hero/MonMove.ts @@ -77,8 +77,9 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda // 战斗状态:根据职业类型和rangeType执行智能战术 this.processCombatLogic(e, move, view, model, nearestEnemy); } else { - // 非战斗状态:向目标移动 - this.processMarchLogic(e, move, view, model); + // 非战斗状态:回归阵型 + move.targetY = 0; + this.processReturnFormation(e, move, view, model); model.is_atking = false; } } @@ -204,8 +205,7 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda /** 执行后撤逻辑 */ private performRetreat(view: HeroViewComp, move: MonMoveComp, model: HeroAttrsComp, currentX: number) { const safeRetreatX = currentX - move.direction * 50; - // 怪物活动范围通常宽一些 - if (safeRetreatX >= -450 && safeRetreatX <= 450) { + if (safeRetreatX >= -300 && safeRetreatX <= 300) { const retreatSpeed = (model.speed / 3) * 0.8; this.moveEntity(view, -move.direction, retreatSpeed); model.is_atking = false; @@ -216,11 +216,8 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda } } - /** - * 进军逻辑 (无敌人时) - * 策略:向目标X移动 (通常是屏幕左侧) - */ - private processMarchLogic(e: ecs.Entity, move: MonMoveComp, view: HeroViewComp, model: HeroAttrsComp) { + /** 回归阵型逻辑 */ + private processReturnFormation(e: ecs.Entity, move: MonMoveComp, view: HeroViewComp, model: HeroAttrsComp) { const currentX = view.node.position.x; const targetX = move.targetX; @@ -228,7 +225,6 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda const dir = targetX > currentX ? 1 : -1; const speed = model.speed / 3; - // 修正朝向 move.direction = dir; this.moveEntity(view, dir, speed); @@ -255,7 +251,7 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda } // 地图边界限制 - if (newX >= -450 && newX <= 450) { + if (newX >= -320 && newX <= 320) { view.node.setPosition(newX, newY, 0); view.status_change("move"); } else { diff --git a/assets/script/game/hero/MoveComp.ts b/assets/script/game/hero/MoveComp.ts new file mode 100644 index 00000000..0ffacaf5 --- /dev/null +++ b/assets/script/game/hero/MoveComp.ts @@ -0,0 +1,291 @@ +import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; +import { HeroViewComp } from "./HeroViewComp"; +import { HeroAttrsComp } from "./HeroAttrsComp"; +import { smc } from "../common/SingletonModuleComp"; +import { FacSet } from "../common/config/GameSet"; +import { HType } from "../common/config/heroSet"; +import { SkillRange } from "../common/config/SkillSet"; + +@ecs.register('MoveComp') +export class MoveComp extends ecs.Comp { + direction: number = 1; + targetX: number = 0; + moving: boolean = true; + targetY: number = 0; + baseY: number = 0; + lane: number = 0; + spawnOrder: number = 0; + + reset() { + this.direction = 1; + this.targetX = 0; + this.moving = true; + this.targetY = 0; + this.baseY = 0; + this.lane = 0; + this.spawnOrder = 0; + } +} + +interface MoveFacConfig { + moveMinX: number; + moveMaxX: number; + retreatMinX: number; + retreatMaxX: number; +} + +@ecs.register('MoveSystem') +export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { + private readonly facConfigs: Record = { + [FacSet.HERO]: { + moveMinX: -320, + moveMaxX: 320, + retreatMinX: -300, + retreatMaxX: 300, + }, + [FacSet.MON]: { + moveMinX: -320, + moveMaxX: 320, + retreatMinX: -300, + retreatMaxX: 300, + } + }; + + filter(): ecs.IMatcher { + return ecs.allOf(MoveComp, HeroViewComp, HeroAttrsComp); + } + + update(e: ecs.Entity) { + if (!smc.mission.play || smc.mission.pause) return; + const model = e.get(HeroAttrsComp); + const move = e.get(MoveComp); + const view = e.get(HeroViewComp); + if (!model || !move || !view || !view.node) return; + if (model.fac !== FacSet.HERO && model.fac !== FacSet.MON) return; + if (!move.moving) return; + if (model.fac === FacSet.MON && smc.mission.stop_mon_action) { + view.status_change("idle"); + return; + } + if (model.is_stop || model.is_dead || model.is_reviving || model.in_stun || model.in_frost) { + if (!model.is_reviving) view.status_change("idle"); + return; + } + + this.updateRenderOrder(e); + const nearestEnemy = this.findNearestEnemy(e); + if (nearestEnemy) { + this.processCombatLogic(e, move, view, model, nearestEnemy); + } else { + move.targetY = 0; + this.processReturnFormation(e, move, view, model); + model.is_atking = false; + } + } + + private processCombatLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) { + let rangeType = model.rangeType; + if (rangeType === undefined) { + if (model.type === HType.warrior || model.type === HType.assassin) { + rangeType = SkillRange.Melee; + } else if (model.type === HType.remote) { + rangeType = SkillRange.Long; + } else { + rangeType = SkillRange.Mid; + } + } + + switch (rangeType) { + case SkillRange.Melee: + this.processMeleeLogic(e, move, view, model, enemy); + break; + case SkillRange.Mid: + this.processMidLogic(e, move, view, model, enemy); + break; + case SkillRange.Long: + this.processLongLogic(e, move, view, model, enemy); + break; + default: + this.processMidLogic(e, move, view, model, enemy); // 默认中程 + break; + } + } + + private processMeleeLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) { + const currentX = view.node.position.x; + const enemyX = enemy.node.position.x; + const dist = Math.abs(currentX - enemyX); + const [minRange, maxRange] = this.resolveCombatRange(model, 0, 75); + + move.direction = enemyX > currentX ? 1 : -1; + + if (dist < minRange) { + this.performRetreat(view, move, model, currentX); + } else if (dist <= maxRange) { + view.status_change("idle"); + model.is_atking = true; + } else { + const speed = model.speed / 3; + this.moveEntity(view, move.direction, speed); + model.is_atking = false; + } + } + + private processMidLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) { + const currentX = view.node.position.x; + const enemyX = enemy.node.position.x; + const dist = Math.abs(currentX - enemyX); + + const [minRange, maxRange] = this.resolveCombatRange(model, 120, 360); + + move.direction = enemyX > currentX ? 1 : -1; + + if (dist < minRange) { + // 太近了,后撤 + this.performRetreat(view, move, model, currentX); + } else if (dist > maxRange) { + const speed = model.speed / 3; + this.moveEntity(view, move.direction, speed); + model.is_atking = false; + } else { + view.status_change("idle"); + model.is_atking = true; + } + } + + private processLongLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) { + const currentX = view.node.position.x; + const enemyX = enemy.node.position.x; + const dist = Math.abs(currentX - enemyX); + + const [minRange, maxRange] = this.resolveCombatRange(model, 360, 720); + + move.direction = enemyX > currentX ? 1 : -1; + + if (dist < minRange) { + // 太近了,后撤 (远程单位对距离更敏感) + this.performRetreat(view, move, model, currentX); + } else if (dist > maxRange) { + const speed = model.speed / 3; + this.moveEntity(view, move.direction, speed); + model.is_atking = false; + } else { + view.status_change("idle"); + model.is_atking = true; + } + } + + private performRetreat(view: HeroViewComp, move: MoveComp, model: HeroAttrsComp, currentX: number) { + const cfg = this.facConfigs[model.fac] || this.facConfigs[FacSet.HERO]; + const safeRetreatX = currentX - move.direction * 50; + if (safeRetreatX >= cfg.retreatMinX && safeRetreatX <= cfg.retreatMaxX) { + const retreatSpeed = (model.speed / 3) * 0.8; + this.moveEntity(view, -move.direction, retreatSpeed); + model.is_atking = false; + } else { + view.status_change("idle"); + model.is_atking = true; + } + } + + private processReturnFormation(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) { + const currentX = view.node.position.x; + const targetX = this.getFixedFormationX(model); + move.targetX = targetX; + + if (Math.abs(currentX - targetX) > 5) { + const dir = targetX > currentX ? 1 : -1; + const speed = model.speed / 3; + move.direction = dir; + this.moveEntity(view, dir, speed); + const newX = view.node.position.x; + if ((dir === 1 && newX > targetX) || (dir === -1 && newX < targetX)) { + view.node.setPosition(targetX, view.node.position.y, 0); + } + } else { + view.status_change("idle"); + } + } + + private getFixedFormationX(model: HeroAttrsComp): number { + let rangeType = model.rangeType; + if (rangeType === undefined || rangeType === null) { + if (model.type === HType.remote) { + rangeType = SkillRange.Long; + } else if (model.type === HType.mage || model.type === HType.support) { + rangeType = SkillRange.Mid; + } else { + rangeType = SkillRange.Melee; + } + } + const side = model.fac === FacSet.MON ? 1 : -1; + if (rangeType === SkillRange.Long) { + return 240 * side; + } + if (rangeType === SkillRange.Mid) { + return 200 * side; + } + return 0; + } + + private moveEntity(view: HeroViewComp, direction: number, speed: number) { + const model = view.ent.get(HeroAttrsComp); + const move = view.ent.get(MoveComp); + if (!model || !move) return; + const cfg = this.facConfigs[model.fac] || this.facConfigs[FacSet.HERO]; + const currentX = view.node.position.x; + const delta = speed * this.dt * direction; + let newX = view.node.position.x + delta; + if (currentX < cfg.moveMinX && direction < 0) { + view.status_change("idle"); + return; + } + if (currentX > cfg.moveMaxX && direction > 0) { + view.status_change("idle"); + return; + } + newX = Math.max(cfg.moveMinX, Math.min(cfg.moveMaxX, newX)); + const newY = view.node.position.y; + view.node.setPosition(newX, newY, 0); + view.status_change("move"); + } + + private resolveCombatRange(model: HeroAttrsComp, defaultMin: number, defaultMax: number): [number, number] { + const minRange = model.getCachedMinSkillDistance(); + const maxRange = model.getCachedMaxSkillDistance(); + if (maxRange <= 0) return [defaultMin, defaultMax]; + const safeMin = Math.max(0, Math.min(minRange, maxRange - 20)); + return [safeMin, maxRange]; + } + + private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null { + const currentView = entity.get(HeroViewComp); + if (!currentView?.node) return null; + + const currentPos = currentView.node.position; + const myFac = entity.get(HeroAttrsComp).fac; + + let nearest: HeroViewComp | null = null; + let minDis = Infinity; + + // 优化查询:一次遍历 + ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => { + const m = e.get(HeroAttrsComp); + if (m.fac !== myFac && !m.is_dead) { + const v = e.get(HeroViewComp); + if (v?.node) { + const d = Math.abs(currentPos.x - v.node.position.x); + if (d < minDis) { + minDis = d; + nearest = v; + } + } + } + }); + return nearest; + } + + private updateRenderOrder(entity: ecs.Entity) { + return; + } +} diff --git a/assets/script/game/hero/MoveComp.ts.meta b/assets/script/game/hero/MoveComp.ts.meta new file mode 100644 index 00000000..cc797c01 --- /dev/null +++ b/assets/script/game/hero/MoveComp.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "836ec21a-d77d-4830-92bd-1e65887c5927", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index 67176035..4adef92d 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -9,7 +9,6 @@ import { CardType, FightSet, CardKind } from "../common/config/GameSet"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; -import { HeroMasterComp } from "../hero/HeroMasterComp"; const { ccclass, property } = _decorator; @@ -252,16 +251,7 @@ export class MissionCardComp extends CCComp { */ fetchCards(level: number, forcedType?: CardType){ // 获取主角已有的属性倾向 (已拥有的永久属性Buff) - let preferredAttrs: number[] = []; - // @ts-ignore - const entities = ecs.query(ecs.allOf(HeroMasterComp)); - if (entities.length > 0) { - const role = entities[0]; - const heroAttrs = role.get(HeroAttrsComp); - if (heroAttrs && heroAttrs.BUFFS) { - preferredAttrs = Object.keys(heroAttrs.BUFFS).map(Number); - } - } + // 使用 CardSet 的 getCardOptions 获取卡牌 // 这里我们要获取 4 张卡牌 @@ -441,13 +431,10 @@ export class MissionCardComp extends CCComp { .to(0.1, { scale: new Vec3(1, 1, 1) }) .delay(0.5) // .call(() => { - // // 使用 HeroMasterComp 查找主角实体 // // @ts-ignore - // const entities = ecs.query(ecs.allOf(HeroMasterComp)); // let role = entities.length > 0 ? entities[0] : null; // if (!role) { - // mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] 未找到挂载 HeroMasterComp 的主角实体`); // } else { // mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] 成功定位主角实体: ${role.eid}`); // }