diff --git a/assets/script/game/common/config/SkillSet.ts b/assets/script/game/common/config/SkillSet.ts index 635f4da3..35a1edac 100644 --- a/assets/script/game/common/config/SkillSet.ts +++ b/assets/script/game/common/config/SkillSet.ts @@ -43,17 +43,30 @@ export enum SType { damage = 0, heal = 1, shield = 2, - atk_speed = 3, - power_up = 4, - ap_up = 5, - dod_up = 6, - crit_up = 7, - crit_dmg_up = 8, - wfuny_up = 9, - zhaohuan = 10, - buff = 11, + zhaohuan = 3, + buff = 4, } +/** + * 攻击距离类型分类 + * 用于AI决策和技能配置规范化 + */ +export enum SkillRange { + /** 近战: < 150 (贴脸输出) */ + Melee = 0, + /** 中程: 150 - 400 (投掷/短法术) */ + Mid = 1, + /** 远程: > 400 (弓箭/长法术) */ + Long = 2 +} + +/** 距离类型的标准数值定义 */ +export const SkillDisVal: Record = { + [SkillRange.Melee]: 120, + [SkillRange.Mid]: 360, + [SkillRange.Long]: 720 +}; + //受伤动画名称 export enum AtkedName { atked = "atked", @@ -169,6 +182,7 @@ export interface SkillConfig { speed:number, // 移动速度 with:number, // 宽度(暂时无用) dis:Number, // 距离/范围 + rangeType?: SkillRange, // [新增] 距离类型标记,用于AI辅助判断 ready:number, // 前摇时间 EAnm:number, // 结束动画ID DAnm:number, // 命中后动画ID @@ -183,50 +197,58 @@ export interface SkillConfig { export const SkillSet: Record = { 5000:{uuid:5000,name:"反伤",sp_name:"thorns",icon:"3036",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, ap:0,cd:60,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:800, + rangeType: SkillRange.Long, ready:0,EAnm:0,DAnm:9001,RType:RType.fixed,EType:EType.animationEnd, buffs:[],neAttrs:[],info:"反伤", }, // ========== 基础攻击 ========== 6001-6099 6001: { uuid:6001,name:"挥击",sp_name:"atk_s1",icon:"3036",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, - ap:100,cd:1,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:120, + ap:100,cd:1,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:SkillDisVal[SkillRange.Melee], + rangeType: SkillRange.Melee, ready:0,EAnm:0,DAnm:9001,RType:RType.fixed,EType:EType.animationEnd, buffs:[],neAttrs:[],info:"向最前方敌人扔出石斧,造成100%攻击的伤害", }, 6002: { uuid:6002,name:"挥砍",sp_name:"atk_s4",icon:"3036",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, - ap:100,cd:1,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:120, + ap:100,cd:1,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:SkillDisVal[SkillRange.Melee], + rangeType: SkillRange.Melee, ready:0,EAnm:0,DAnm:9001,RType:RType.fixed,EType:EType.animationEnd, buffs:[],neAttrs:[],info:"向最前方敌人扔出石斧,造成100%攻击的伤害", }, 6005: { uuid:6005,name:"水球",sp_name:"m_water_ball_1",icon:"3039",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, - ap:100,cd:5,t_num:1,hit_num:1,hit:2,hitcd:0.3,speed:720,with:90,dis:360, + ap:100,cd:5,t_num:1,hit_num:1,hit:2,hitcd:0.3,speed:720,with:90,dis:SkillDisVal[SkillRange.Mid], + rangeType: SkillRange.Mid, ready:8001,EAnm:0,DAnm:9001,RType:RType.linear,EType:EType.collision, buffs:[],neAttrs:[],info:"召唤水球攻击前方敌人,造成100%魔法攻击的伤害", }, // ========== 基础buff ========== 6100-6199 6100: { uuid:6100,name:"治疗",sp_name:"buff_wind",icon:"3036",TGroup:TGroup.Self,SType:SType.heal,act:"atk",DTType:DTType.single, - ap:30,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:720, + ap:30,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:SkillDisVal[SkillRange.Long], + rangeType: SkillRange.Long, ready:0,EAnm:0,DAnm:9001,RType:RType.fixed,EType:EType.animationEnd, buffs:[],neAttrs:[],info:"治疗自己,回复30%最大生命值", }, 6101:{ uuid:6101,name:"魔法盾",sp_name:"buff_wind",icon:"3036",TGroup:TGroup.Self,SType:SType.shield,act:"atk",DTType:DTType.single, - ap:30,cd:7,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:720, + ap:30,cd:7,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:SkillDisVal[SkillRange.Long], + rangeType: SkillRange.Long, ready:0,EAnm:0,DAnm:9001,RType:RType.fixed,EType:EType.animationEnd, buffs:[],neAttrs:[],info:"获得30%最大生命值的护盾,持续60秒", }, 6102:{ uuid:6102,name:"强壮",sp_name:"buff_wind",icon:"3036",TGroup:TGroup.Team,SType:SType.buff,act:"atk",DTType:DTType.single, - ap:30,cd:10,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:720, + ap:30,cd:10,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:SkillDisVal[SkillRange.Long], + rangeType: SkillRange.Long, ready:0,EAnm:0,DAnm:9001,RType:RType.fixed,EType:EType.animationEnd, buffs:[{buff:Attrs.AP,BType:BType.VALUE,value:10,time:30,chance:1}],neAttrs:[],info:"增加目标10%攻击力,持续30秒", }, 6103:{ uuid:6103,name:"群体强壮",sp_name:"buff_wind",icon:"3036",TGroup:TGroup.Team,SType:SType.buff,act:"atk",DTType:DTType.range, - ap:30,cd:10,t_num:3,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:720, + ap:30,cd:10,t_num:3,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,dis:SkillDisVal[SkillRange.Long], + rangeType: SkillRange.Long, ready:0,EAnm:0,DAnm:9001,RType:RType.fixed,EType:EType.animationEnd, buffs:[{buff:Attrs.AP,BType:BType.RATIO,value:10,time:30,chance:1}],neAttrs:[],info:"增加目标10%攻击力,持续30秒", }, @@ -235,7 +257,8 @@ export const SkillSet: Record = { uuid:6201, name:"怪物近战", sp_name:"atk_s1", icon:"3036", TGroup:TGroup.Enemy, SType:SType.damage, act:"atk", DTType:DTType.single, ap:100, cd:1, t_num:1, hit_num:1, hit:1, hitcd:0.2, speed:0, with:0, - dis:50, // 近战距离 + dis:50, // 怪物近战特殊距离 + rangeType: SkillRange.Melee, ready:0, EAnm:0, DAnm:9001, RType:RType.fixed, EType:EType.animationEnd, buffs:[], neAttrs:[], info:"怪物基础近战攻击", }, @@ -243,7 +266,8 @@ export const SkillSet: Record = { uuid:6203, name:"怪物射击", sp_name:"arrow_1", icon:"3039", TGroup:TGroup.Enemy, SType:SType.damage, act:"atk", DTType:DTType.single, ap:80, cd:2, t_num:1, hit_num:1, hit:1, hitcd:0.2, speed:800, with:0, - dis:600, // 远程距离 + dis:600, // 怪物远程特殊距离 + rangeType: SkillRange.Long, ready:0, EAnm:0, DAnm:9001, RType:RType.linear, EType:EType.collision, buffs:[], neAttrs:[], info:"怪物基础远程攻击", }, @@ -262,5 +286,3 @@ export const EAnmConf: Record = { }; export const CanSelectSkills = [6001, 6002, 6005, 6100, 6101, 6102, 6103]; - - diff --git a/assets/script/game/common/config/heroSet.ts b/assets/script/game/common/config/heroSet.ts index e8e55f5f..5ff74587 100644 --- a/assets/script/game/common/config/heroSet.ts +++ b/assets/script/game/common/config/heroSet.ts @@ -1,7 +1,7 @@ import { v3 } from "cc" import { BoxSet, FacSet } from "./GameSet" import { smc } from "../SingletonModuleComp" -import { BuffConf } from "./SkillSet" +import { BuffConf, SkillRange } from "./SkillSet" export enum AttrSet { ATTR_MAX = 85, @@ -114,6 +114,7 @@ export interface heroInfo { ap: number; // 攻击力 def: number; // 防御(伤害减免) dis: number; // 攻击距离(像素) + rangeType: SkillRange; // 攻击距离类型 (近/中/远) speed: number; // 移动速度(像素/秒) skills: number[]; // 携带技能ID列表 buff: BuffConf[]; // 自带buff配置(通常为空,由技能动态添加) @@ -129,36 +130,43 @@ export const HeroInfo: Record = { // 刘邦 - 领导型战士(善于用人,知人善任) 5001:{uuid:5001,name:"刘邦",path:"hk1", fac:FacSet.HERO, kind:1,as:1.5, type:HType.warrior,lv:1,hp:200,mp:200,def:0,ap:15,dis:120,speed:120,skills:[6001,6002], + rangeType: SkillRange.Melee, buff:[],tal:[],info:"楚汉争霸领袖,领导统御型战士"}, // 荆轲 - 刺客(敏捷型,高速度和暴击率) 5002:{uuid:5002,name:"荆轲",path:"hc1", fac:FacSet.HERO, kind:1,as:1.5, type:HType.assassin,lv:1,hp:80,mp:60,def:0,ap:22,dis:120,speed:180,skills:[6002,6001], + rangeType: SkillRange.Melee, buff:[],tal:[],info:"战国刺客,刺杀专精敏捷型刺客"}, // 赵武灵王 - 远程射手(胡服骑射,机动型高移动速度和远程攻击) 5005:{uuid:5005,name:"赵武灵王",path:"ha1", fac:FacSet.HERO, kind:2,as:1.5, type:HType.remote,lv:1,hp:100,mp:80,def:0,ap:18,dis:450,speed:140,skills:[6002,6001], + rangeType: SkillRange.Long, buff:[],tal:[],info:"胡服骑射改革者,机动型高远程输出"}, // 张良 - 智谋法师(运筹帷幄,智谋型法师) 5007:{uuid:5007,name:"张良",path:"hh1", fac:FacSet.HERO, kind:2,as:1.5, type:HType.mage,lv:1,hp:88,mp:135,def:0,ap:15,dis:350,speed:100,skills:[6002,6001], + rangeType: SkillRange.Mid, buff:[],tal:[],info:"运筹帷幄谋士,智谋型法师"}, // 屈原 - 元素法师(离骚诗韵,元素型高魔法输出) 5008:{uuid:5008,name:"屈原",path:"hm1", fac:FacSet.HERO, kind:2,as:1.5, type:HType.mage,lv:1,hp:85,mp:140,def:0,ap:16,dis:400,speed:90,skills:[6002,6001], + rangeType: SkillRange.Mid, buff:[],tal:[],info:"离骚诗韵,元素型高魔法输出"}, // 孙膑 - 谋略法师(兵法谋略,谋略型法师) 5009:{uuid:5009,name:"孙膑",path:"hm2", fac:FacSet.HERO, kind:2,as:1.5, type:HType.mage,lv:1,hp:92,mp:135,def:0,ap:14,dis:420,speed:95,skills:[6002,6001], + rangeType: SkillRange.Long, buff:[],tal:[],info:"兵法谋略,谋略型法师"}, // 萧何 - 后勤辅助(后勤保障,后勤型辅助) 5010:{uuid:5010,name:"萧何",path:"hz1", fac:FacSet.HERO, kind:2,as:1.5, type:HType.support,lv:1,hp:115,mp:145,def:0,ap:8,dis:380,speed:105,skills:[6002,6001], + rangeType: SkillRange.Mid, buff:[],tal:[],info:"后勤保障,后勤型辅助"}, @@ -169,40 +177,49 @@ export const HeroInfo: Record = { // 1. 基础近战型 5201:{uuid:5201,name:"兽人战士",path:"mo1", fac:FacSet.MON, kind:1,as:3.0, type:HType.warrior,lv:1,hp:40,mp:100,def:0,ap:5,dis:60,speed:180,skills:[6005], + rangeType: SkillRange.Melee, buff:[],tal:[],info:"标准炮灰:确保英雄能完成3次普攻积累天赋计数"}, // 2. 快速突击型 5301:{uuid:5301,name:"兽人斥候",path:"mo1", fac:FacSet.MON, kind:1,as:1.2, type:HType.assassin,lv:1,hp:30,mp:100,def:0,ap:12,dis:50,speed:400,skills:[6005], + rangeType: SkillRange.Melee, buff:[],tal:[],info:"快速突击:极高移速贴脸,检测护盾(7102)刷新率"}, // 3. 重型坦克型 5401:{uuid:5401,name:"兽人卫士",path:"mo1", fac:FacSet.MON, kind:1,as:5.0, type:HType.warrior,lv:1,hp:400,mp:100,def:5,ap:25,dis:90,speed:60,skills:[6005], + rangeType: SkillRange.Melee, buff:[],tal:[],info:"重型坦克:数值墙,检测玩家破甲(7008)与持续输出"}, // 4. 远程骚扰型 5501:{uuid:5501,name:"兽人射手",path:"mo1", fac:FacSet.MON, kind:1,as:3.0, type:HType.remote,lv:1,hp:50,mp:100,def:0,ap:15,dis:800,speed:90,skills:[6203], + rangeType: SkillRange.Long, buff:[],tal:[],info:"远程骚扰:跨屏打击,迫使阵地分散或移动英雄"}, // 5. 特殊机制型 5601:{uuid:5601,name:"兽人自爆兵",path:"mo1", fac:FacSet.MON, kind:1,as:3.0, type:HType.assassin,lv:1,hp:60,mp:100,def:0,ap:250,dis:50,speed:220,skills:[6005], + rangeType: SkillRange.Melee, buff:[],tal:[],info:"特殊机制:极端伤害,漏怪即秒杀,检测减伤(7103)"}, // 召唤师:持续召唤小怪(后续可在技能系统中实现 SType.zhaohuan) 5602:{uuid:5602,name:"兽人召唤师",path:"mo1", fac:FacSet.MON, kind:1,as:3.0, type:HType.mage,lv:1,hp:120,mp:300,def:5,ap:8,dis:380,speed:100,skills:[6005], + rangeType: SkillRange.Mid, buff:[],tal:[],info:"战术目标:持续召唤小怪,检测英雄大招清场频率"}, // 治疗者:为周围怪物回血(此处以提升治疗效果和生命回复为基础被动) 5603:{uuid:5603,name:"兽人祭司",path:"mo1", fac:FacSet.MON, kind:1,as:3.0, type:HType.support,lv:1,hp:120,mp:300,def:5,ap:6,dis:90,speed:105,skills:[6005], + rangeType: SkillRange.Melee, buff:[],tal:[],info:"战术目标:为怪群回血,检测玩家沉默(7006)覆盖率"}, // 光环怪:为周围怪物提供增益(此处以Buff效果提升与移动速度提升为基础被动) // Attrs.BUFF_UP=60 (RATIO=1),Attrs.SPEED=63 (RATIO=1) 5604:{uuid:5604,name:"兽人图腾师",path:"mo1", fac:FacSet.MON, kind:1,as:3.0, type:HType.support,lv:1,hp:100,mp:250,def:5,ap:7,dis:90,speed:110,skills:[6005], + rangeType: SkillRange.Melee, buff:[],tal:[],info:"战术目标:提供加速光环,改变怪群推进节奏"}, // 6. 精英/BOSS型 5701:{uuid:5701,name:"兽人首领(BOSS)",path:"mo1", fac:FacSet.MON, kind:1,as:2.5, type:HType.warrior,lv:3,hp:25000,mp:500,def:20,ap:80,dis:120,speed:120,skills:[6005], + rangeType: SkillRange.Melee, buff:[],tal:[],info:"终极考验:极高HP,检测大招重置与辐射协同输出"}, - }; \ No newline at end of file + }; diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index a99f62a5..ea3d30e0 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -83,7 +83,7 @@ export class Hero extends ecs.Entity { model.fac = FacSet.HERO; model.is_master = is_master; model.is_friend = is_friend - + model.rangeType = hero.rangeType; // 只有主角才挂载天赋组件 if (is_master) { this.add(TalComp); diff --git a/assets/script/game/hero/HeroAttrsComp.ts b/assets/script/game/hero/HeroAttrsComp.ts index 521393cd..7778f6f5 100644 --- a/assets/script/game/hero/HeroAttrsComp.ts +++ b/assets/script/game/hero/HeroAttrsComp.ts @@ -2,7 +2,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; import { GameEvent } from "../common/config/GameEvent"; import { Attrs, AttrsType, BType, NeAttrs } from "../common/config/HeroAttrs"; -import { BuffConf } from "../common/config/SkillSet"; +import { BuffConf, SkillRange } from "../common/config/SkillSet"; import { HeroInfo, AttrSet } from "../common/config/heroSet"; import { HeroSkillsComp } from "./HeroSkills"; import { smc } from "../common/SingletonModuleComp"; @@ -22,7 +22,7 @@ export class HeroAttrsComp extends ecs.Comp { lv: number = 1; type: number = 0; // 0近战 1远程 2辅助 fac: number = 0; // 0:hero 1:monster - + rangeType:SkillRange = SkillRange.Melee; // ==================== 基础属性(有初始值) ==================== base_ap: number = 0; // 基础攻击 base_def: number = 5; // 基础防御 diff --git a/assets/script/game/hero/HeroMove.ts b/assets/script/game/hero/HeroMove.ts index fd669032..8a66bd6e 100644 --- a/assets/script/game/hero/HeroMove.ts +++ b/assets/script/game/hero/HeroMove.ts @@ -6,13 +6,14 @@ import { smc } from "../common/SingletonModuleComp"; import { FacSet } from "../common/config/GameSet"; import { HType } from "../common/config/heroSet"; import { Attrs } from "../common/config/HeroAttrs"; +import { SkillRange } from "../common/config/SkillSet"; /** 英雄移动组件 */ @ecs.register('HeroMove') export class HeroMoveComp extends ecs.Comp { - /** 移动方向:1向右,-1向左 */ + /** 移动方向:1向右,-1向左 */ direction: number = 1; - /** 目标x坐标 */ + /** 目标x坐标(阵型位置) */ targetX: number = 0; /** 是否处于移动状态 */ moving: boolean = true; @@ -24,7 +25,7 @@ export class HeroMoveComp extends ecs.Comp { } } -/** 英雄移动系统 - 专门处理英雄的移动逻辑 */ +/** 英雄移动系统 - 智能战斗移动逻辑 */ @ecs.register('HeroMoveSystem') export class HeroMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { filter(): ecs.IMatcher { @@ -32,291 +33,259 @@ export class HeroMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpd } update(e: ecs.Entity) { - if (!smc.mission.play ) return; - if(smc.mission.pause) return - const move = e.get(HeroMoveComp); + // 1. 全局状态检查 + if (!smc.mission.play || smc.mission.pause) return; + const model = e.get(HeroAttrsComp); + const move = e.get(HeroMoveComp); const view = e.get(HeroViewComp); - // 只处理英雄 + // 只处理己方英雄且处于可移动状态 if (model.fac !== FacSet.HERO) return; if (!move.moving) return; - const shouldStopInFace = this.checkEnemiesInFace(e); - const shouldStopAtMinRange = this.shouldStopAtMinSkillRange(e); - const shouldStop = shouldStopInFace || shouldStopAtMinRange; - model.is_atking = this.checkEnemiesInSkillRange(e); - - // 更新渲染层级 + // 2. 异常状态检查 (死亡/复活/眩晕/冰冻) + if (model.is_stop || model.is_dead || model.is_reviving || model.isStun() || model.isFrost()) { + if (!model.is_reviving) view.status_change("idle"); + return; + } + this.updateRenderOrder(e); - if (!shouldStop) { - if (model.is_stop || model.is_dead || model.is_reviving || model.isStun() || model.isFrost()) { - if (model.is_reviving) return; - view.status_change("idle"); - return; - } - + // 3. 核心移动逻辑分发 + const nearestEnemy = this.findNearestEnemy(e); - - // 英雄阵营特殊逻辑:根据职业区分行为 - const hasEnemies = this.checkEnemiesExist(e); - const isWarrior = model.type === HType.warrior||model.type===HType.assassin; - - // 战士职业:有敌人就向敌人前进 - if (isWarrior && hasEnemies) { - const nearestEnemy = this.findNearestEnemy(e); - if (nearestEnemy) { - const enemyX = nearestEnemy.node.position.x; - const currentX = view.node.position.x; - - // 根据敌人位置调整移动方向和朝向 - if (enemyX > currentX) { - move.direction = 1; // 向右移动 - } else { - move.direction = -1; // 向左移动 - } - - // 继续向敌人方向移动 - const delta = (model.Attrs[Attrs.SPEED]/3) * this.dt * move.direction; - const newX = view.node.position.x + delta; - - // 对于战士,允许更自由的移动范围 - if (newX >= -280 && newX <= 280) { // 使用地图边界 - view.status_change("move"); - view.node.setPosition(newX, view.node.position.y, 0); - } else { - view.status_change("idle"); - } - } - return; - } - - // 其他职业或战士无敌人时:回到预定点 - const currentX = view.node.position.x; - let finalTargetX = move.targetX; - - // 检查预定点是否已被占用 - if (this.isPositionOccupied(finalTargetX, e)) { - finalTargetX = move.targetX - 50; // 往前50的位置 - } - - // 如果不在目标位置,移动到目标位置 - if (Math.abs(currentX - finalTargetX) > 1) { - // 确定移动方向 - const direction = currentX > finalTargetX ? -1 : 1; - const delta = (model.Attrs[Attrs.SPEED]/3) * this.dt * direction; - const newX = view.node.position.x + delta; - - // 确保不会超过目标位置 - if (direction === 1 && newX > finalTargetX) { - view.node.setPosition(finalTargetX, view.node.position.y, 0); - } else if (direction === -1 && newX < finalTargetX) { - view.node.setPosition(finalTargetX, view.node.position.y, 0); - } else { - view.node.setPosition(newX, view.node.position.y, 0); - } - view.status_change("move"); - } else { - view.status_change("idle"); - // 到达目标位置后,面向右侧(敌人方向) - move.direction = 1; - } + if (nearestEnemy) { + // 战斗状态:根据职业类型和rangeType执行智能战术 + this.processCombatLogic(e, move, view, model, nearestEnemy); } else { - if (!model.is_atking) { - view.status_change("idle"); - } - // 因为敌人在面前而暂时停止,不设置moving为false,保持检查状态 + // 非战斗状态:回归阵型 + this.processReturnFormation(e, move, view, model); + model.is_atking = false; } } - /** 检查是否存在敌人 */ - private checkEnemiesExist(entity: ecs.Entity): boolean { - const team = entity.get(HeroAttrsComp).fac; - let hasEnemies = false; - ecs.query(ecs.allOf(HeroAttrsComp)).some(e => { - const model = e.get(HeroAttrsComp); - if (model.fac !== team && !model.is_dead) { - hasEnemies = true; - return true; - } - }); - return hasEnemies; + /** + * 战斗移动逻辑分发 + * 根据 rangeType 决定走位策略 + */ + private processCombatLogic(e: ecs.Entity, move: HeroMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) { + // 优先使用 rangeType 判断,如果没有则回退到 type 判断 + let rangeType = model.rangeType; + + // 兼容性处理:如果数据未配置 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; + } } - /** 找到最近的敌人 */ + /** + * 近战逻辑 (Melee) + * 策略:无脑突进,贴脸输出 + * 范围:< 75 (攻击距离) + */ + private processMeleeLogic(e: ecs.Entity, move: HeroMoveComp, 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 attackRange = 75; // 保持原有的近战判定 + + move.direction = enemyX > currentX ? 1 : -1; + + if (dist <= attackRange) { + view.status_change("idle"); + model.is_atking = true; + } else { + const speed = model.Attrs[Attrs.SPEED] / 3; + this.moveEntity(view, move.direction, speed); + model.is_atking = false; + } + } + + /** + * 中程逻辑 (Mid) + * 策略:保持在中距离,灵活输出 + * 范围:120 - 360 + */ + private processMidLogic(e: ecs.Entity, move: HeroMoveComp, 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 = 120; + const maxRange = 360; + + move.direction = enemyX > currentX ? 1 : -1; + + if (dist < minRange) { + // 太近了,后撤 + this.performRetreat(view, move, model, currentX); + } else if (dist > maxRange) { + // 太远了,追击 + const speed = model.Attrs[Attrs.SPEED] / 3; + this.moveEntity(view, move.direction, speed); + model.is_atking = false; + } else { + // 距离合适,站桩输出 + view.status_change("idle"); + model.is_atking = true; + } + } + + /** + * 远程逻辑 (Long) + * 策略:保持在远距离,最大化生存 + * 范围:360 - 720 + */ + private processLongLogic(e: ecs.Entity, move: HeroMoveComp, 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 = 360; + const maxRange = 720; + + move.direction = enemyX > currentX ? 1 : -1; + + if (dist < minRange) { + // 太近了,后撤 (远程单位对距离更敏感) + this.performRetreat(view, move, model, currentX); + } else if (dist > maxRange) { + // 太远了,追击 + const speed = model.Attrs[Attrs.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: HeroMoveComp, model: HeroAttrsComp, currentX: number) { + const safeRetreatX = currentX - move.direction * 50; + if (safeRetreatX >= -300 && safeRetreatX <= 300) { + const retreatSpeed = (model.Attrs[Attrs.SPEED] / 3) * 0.8; + this.moveEntity(view, -move.direction, retreatSpeed); + model.is_atking = false; + } else { + // 退无可退,被迫反击 + view.status_change("idle"); + model.is_atking = true; + } + } + + /** + * 回归阵型逻辑 + * 策略:无敌人时回到预设的 targetX + */ + private processReturnFormation(e: ecs.Entity, move: HeroMoveComp, view: HeroViewComp, model: HeroAttrsComp) { + const currentX = view.node.position.x; + let targetX = move.targetX; + + // 简单的防重叠偏移 + if (this.isPositionOccupied(targetX, e)) { + targetX -= 50; + } + + if (Math.abs(currentX - targetX) > 5) { + const dir = targetX > currentX ? 1 : -1; + const speed = model.Attrs[Attrs.SPEED] / 3; + + // 修正朝向:回正 + move.direction = 1; + + 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"); + move.direction = 1; // 归位后默认朝右 + } + } + + /** 通用移动执行 */ + private moveEntity(view: HeroViewComp, direction: number, speed: number) { + const delta = speed * this.dt * direction; + const newX = view.node.position.x + delta; + + // 地图边界限制 (硬限制) + if (newX >= -320 && newX <= 320) { + view.node.setPosition(newX, view.node.position.y, 0); + view.status_change("move"); + } else { + view.status_change("idle"); + } + } + + // --- 辅助方法 --- + private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null { const currentView = entity.get(HeroViewComp); - if (!currentView || !currentView.node) return null; + if (!currentView?.node) return null; const currentPos = currentView.node.position; - const team = entity.get(HeroAttrsComp).fac; - let nearestEnemyView: HeroViewComp | null = null; - let minDistance = Infinity; + const myFac = entity.get(HeroAttrsComp).fac; + let nearest: HeroViewComp | null = null; + let minDis = Infinity; + + // 优化查询:一次遍历 ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => { - const model = e.get(HeroAttrsComp); - const view = e.get(HeroViewComp); - if (model.fac !== team && !model.is_dead && view && view.node) { - const distance = Math.abs(currentPos.x - view.node.position.x); - if (distance < minDistance) { - minDistance = distance; - nearestEnemyView = view; + 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 nearestEnemyView; + return nearest; } - /** 检测攻击范围内敌人 */ - private checkEnemiesInRange(entity: ecs.Entity, range: number): boolean { - const currentView = entity.get(HeroViewComp); - if (!currentView || !currentView.node) return false; - - const currentPos = currentView.node.position; - const team = entity.get(HeroAttrsComp).fac; - let found = false; - ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => { - const model = e.get(HeroAttrsComp); - const view = e.get(HeroViewComp); - if (!view || !view.node) return false; - const distance = Math.abs(currentPos.x - view.node.position.x); - if (model.fac !== team && !model.is_dead) { - if (distance <= range) { - found = true; - return true; - } - } + private isPositionOccupied(targetX: number, self: ecs.Entity): boolean { + const myFac = self.get(HeroAttrsComp).fac; + return ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => { + if (e === self) return false; + const m = e.get(HeroAttrsComp); + if (m.fac !== myFac || m.is_dead) return false; + + const v = e.get(HeroViewComp); + return Math.abs(v.node.position.x - targetX) < 30; }); - return found; } - /** 检测面前是否有敌人 */ - private checkEnemiesInFace(entity: ecs.Entity): boolean { - const currentView = entity.get(HeroViewComp); - if (!currentView || !currentView.node) return false; - - const currentPos = currentView.node.position; - const team = entity.get(HeroAttrsComp).fac; - let found = false; - ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => { - const model = e.get(HeroAttrsComp); - const view = e.get(HeroViewComp); - if (!view || !view.node) return false; - const distance = Math.abs(currentPos.x - view.node.position.x); - if (model.fac !== team && !model.is_dead) { - if (distance <= 75) { - found = true; - return true; - } - } - }); - return found; - } - - /** 检测技能攻击范围内敌人 */ - private checkEnemiesInSkillRange(entity: ecs.Entity): boolean { - const currentView = entity.get(HeroViewComp); - const heroAttrs = entity.get(HeroAttrsComp); - - if (!currentView || !currentView.node || !heroAttrs) return false; - - const currentPos = currentView.node.position; - const team = heroAttrs.fac; - - // 使用缓存的最远技能攻击距离判断攻击时机 - const maxSkillDistance = heroAttrs.getCachedMaxSkillDistance(); - if (maxSkillDistance === 0) return false; - - let found = false; - ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => { - const model = e.get(HeroAttrsComp); - const view = e.get(HeroViewComp); - if (!view || !view.node) return false; - const distance = Math.abs(currentPos.x - view.node.position.x); - if (model.fac !== team && !model.is_dead) { - if (distance <= maxSkillDistance) { - found = true; - return true; - } - } - }); - return found; - } - - /** 更新渲染层级 */ private updateRenderOrder(entity: ecs.Entity) { - const currentView = entity.get(HeroViewComp); - const currentModel = entity.get(HeroAttrsComp); - - // 查找所有英雄单位 - const allUnits = ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)) - .filter(e => { - const otherModel = e.get(HeroAttrsComp); - return otherModel.fac === currentModel.fac; // 按阵营分组 - }) - .map(e => e); - - // 按x坐标排序:x坐标越大(越前面)的显示在上层 - const sortedUnits = allUnits.sort((a, b) => { - const viewA = a.get(HeroViewComp); - const viewB = b.get(HeroViewComp); - if (!viewA || !viewA.node || !viewB || !viewB.node) return 0; - const posA = viewA.node.position.x; - const posB = viewB.node.position.x; - return posA - posB; // x坐标从小到大排序 - }); - - // 设置渲染顺序:x坐标越大的显示在上层(index越大,层级越高) - // sortedUnits.forEach((unit, index) => { - // const model = unit.get(HeroViewComp); - // model.node.setSiblingIndex(index); // 直接使用index,x坐标大的index大,层级高 - // }); + // 渲染层级逻辑... } - - /** 检查指定位置是否已被占用 */ - private isPositionOccupied(targetX: number, currentEntity: ecs.Entity): boolean { - const currentModel = currentEntity.get(HeroAttrsComp); - const occupationRange = 30; // 定义占用范围为30像素 - - return ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => { - if (e === currentEntity) return false; // 排除自己 - - const model = e.get(HeroAttrsComp); - const view = e.get(HeroViewComp); - if (model.fac !== currentModel.fac) return false; // 只检查同阵营 - if (model.is_dead) return false; // 排除死亡单位 - - const distance = Math.abs(view.node.position.x - targetX); - return distance < occupationRange; // 如果距离小于占用范围,认为被占用 - }); - } - - /** 检查是否应该基于最近技能距离停止移动 */ - private shouldStopAtMinSkillRange(entity: ecs.Entity): boolean { - const currentView = entity.get(HeroViewComp); - const heroAttrs = entity.get(HeroAttrsComp); - - if (!currentView || !currentView.node || !heroAttrs) return false; - - const currentPos = currentView.node.position; - const team = heroAttrs.fac; - - // 使用缓存的最近技能攻击距离 - const minSkillDistance = heroAttrs.getCachedMinSkillDistance(); - if (minSkillDistance === 0) return false; - - // 检查是否有敌人在最近技能距离内 - return ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => { - const model = e.get(HeroAttrsComp); - const view = e.get(HeroViewComp); - if (!view || !view.node) return false; - const distance = Math.abs(currentPos.x - view.node.position.x); - if (model.fac !== team && !model.is_dead) { - return distance <= minSkillDistance; - } - return false; - }); - } -} \ No newline at end of file +} diff --git a/assets/script/game/hero/SACastSystem.ts b/assets/script/game/hero/SACastSystem.ts index 8602bcdd..2fa08b5d 100644 --- a/assets/script/game/hero/SACastSystem.ts +++ b/assets/script/game/hero/SACastSystem.ts @@ -52,9 +52,8 @@ export class SACastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdat // 检查基本条件 if (heroAttrs.is_dead || heroAttrs.is_reviving || heroAttrs.isStun() || heroAttrs.isFrost()) return; - // 检查是否正在攻击(只有攻击时才释放技能) - if (!heroAttrs.is_atking) return; - + // 移除 is_atking 检查,实现只要距离和CD满足即施法 + // if (!heroAttrs.is_atking) return; const readySkills = skills.getReadySkills(); if (readySkills.length === 0) return;