@@ -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 shouldStopAtMinRa nge = this . shouldStopAtMinSkillRange ( e ) ;
const shouldStop = shouldStopInFace || shouldStopAtMinRange ;
model . is_atking = this . checkEnemiesInSkillRange ( e ) ;
// 更新渲染层级
// 2. 异常状态检查 (死亡/复活/眩晕/冰冻)
if ( model . is_stop || model . is_dead || model . is_revivi ng || 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 ;
} ) ;
}
}
}