feat(英雄AI): 重构英雄移动系统,基于攻击距离类型实现智能战术走位
1. 新增SkillRange枚举定义近/中/远程攻击类型 2. 在HeroAttrsComp和hero配置中添加rangeType字段 3. 重写HeroMoveSystem,根据rangeType实现差异化移动策略 4. 移除技能施放的攻击状态限制,优化AI决策逻辑
This commit is contained in:
@@ -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, number> = {
|
||||
[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<number, SkillConfig> = {
|
||||
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<number, SkillConfig> = {
|
||||
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<number, SkillConfig> = {
|
||||
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<number, IEndAnm> = {
|
||||
};
|
||||
|
||||
export const CanSelectSkills = [6001, 6002, 6005, 6100, 6101, 6102, 6103];
|
||||
|
||||
|
||||
|
||||
@@ -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<number, heroInfo> = {
|
||||
// 刘邦 - 领导型战士(善于用人,知人善任)
|
||||
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<number, heroInfo> = {
|
||||
// 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,检测大招重置与辐射协同输出"},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; // 基础防御
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user