/** @format */ import {ComAttackable} from '../ECS/components/ComAttackable' import {ComCocosNode} from '../ECS/components/ComCocosNode' import {ComMovable} from '../ECS/components/ComMovable' import {ComTransform} from '../ECS/components/ComTransform' import {SysBehaviorTree} from '../ECS/systems/SysBehaviorTree' import {EventAttack, EventDie, EventSkill, EventStopSkill} from '../ECS/core/NodeEvent' import {ComFindEnemy} from '../ECS/components/ComFindEnemy' import {ComRole} from '../ECS/components/ComRole' import {ComDizzy} from '../ECS/components/ComDizzy' import {ComSkillAbel} from '../ECS/components/ComSkillAbel' import {ccUtils} from '../utils/ccUtils' import {FightWorld} from '../ECS/worlds/FightWorld' import {Data} from '../GameControl' import {FILTER_CAN_ATTACK_ENEMY, FILTER_CAN_ATTACK_FRIEND} from '../ECS/systems/SysFindEnemy' import {Log} from '../utils/LogUtils' import {BUFF_EFFECT_TYPE, BUFF_TYPE, ENTRY, ROLE_TYPE, SKILL_TYPE} from '../enums/Enum' import {EventProcess} from '../ECS/core/EventProcess' export namespace BT { export class BlackBoard {} export class ExecuteContext { executor: SysBehaviorTree world: FightWorld bb: BlackBoard entity: number dt: number public init(executor: SysBehaviorTree, world: FightWorld) { this.executor = executor this.world = world } public set(entity: number, dt: number, bb: BlackBoard) { this.entity = entity this.dt = dt this.bb = bb } } /** 节点状态 */ export enum NodeState { Executing, // 执行中 Success, // 成功 Fail, // 失败 } /** 节点类型 */ export enum NodeType { // 组合节点 LockedSequence, // 锁状态顺序节点 Sequence, // Selector, // 选择节点 RandomSelector, // 随机选择节点 Parallel, // 并行节点 RootSelector, //行为树根选择节点,每帧会循环进入所有子节点 // 修饰节点 Inverter, // 逆变节点 Success, // 成功节点 Fail, // 失败节点 Repeater, // 重复节点 RetryTillSuccess, // 重复直到成功 // 叶子结点 Wait, // 等待 Action, WalkToCloseTarget, // 往最近的敌人移动 往敌方X方向移动,到达条件会往敌方X,Y方向一起移动 MeleeWalkToTarget, // 近战移动到即将攻击目标的攻击点 MeleeMonitor, // 近战监测目标敌人是否进入近战接触范围,进入近战接触范围,会往敌方X,Y方向一起移动 MeleeXMonitor, // 当X轴没有敌人再去判断近战监测范围 AttackMonitor, // 监测目标敌人是否进入攻击范围 Attack, // 攻击 WillAttackNotWillAttack, // 将要攻击的敌人将要攻击 WillAttack, //将要攻击敌人 WillBeAttack, //将要被攻击 HasAttackEnemy, //有正在攻击的敌人 FindEnemy, // 有没有最近的敌人 HasBeAttack, // 有没有最近的敌人 BeDizzy, // 被晕眩 Dizzy, // 晕眩中 HasSkill, // 是否有技能能施放 CastSkill, // 技能施放中 Skill, // 技能施放 Stop, //停止移动 StopAttack, //停止攻击 StopSkill, //停止吟唱技能 } export class NodeBase { public type: NodeType public state: NodeState = NodeState.Success public failLog: string public successLog: string constructor(type: NodeType) { this.type = type } } let debug = CC_DEV /** 组合节点 */ class CombineNode extends NodeBase { public children: NodeBase[] = [] public constructor(type: NodeType, children: NodeBase[]) { super(type) this.children = children } } export class SequenceNode extends CombineNode { public currIdx = 0 public ignoreFailure = false constructor(children: NodeBase[], ignoreFailture = false) { super(NodeType.Sequence, children) this.ignoreFailure = ignoreFailture } } /** 依次执行子节点, 遇到执行失败的则退出并返回失败, 全部执行成功则返回成功 */ export class LockedSequenceNode extends CombineNode { public currIdx = 0 public ignoreFailure = false constructor(children: NodeBase[], ignoreFailture = false) { super(NodeType.LockedSequence, children) this.ignoreFailure = ignoreFailture } } /** 依次执行子节点, 遇到执行成功的则退出并返回成功, 全部执行失败则返回失败 */ export class SelectorNode extends CombineNode { public currIdx: number = -1 constructor(children: NodeBase[]) { super(NodeType.Selector, children) } } /** 行为树根节点,类似SelectorNode,但是会比SelectorNode多一些逻辑,进入节点后会直接update子节点 */ export class RootSelectorNode extends CombineNode { public currIdx: number = -1 public isMelee = true constructor(children: NodeBase[], isMelee = true) { super(NodeType.RootSelector, children) this.isMelee = isMelee } } /** 根据权重随机选择执行某个子节点 */ export class RandomSelectorNode extends CombineNode { public weights: number[] // 权重 public currIdx = -1 // 选中的节点 constructor(children: NodeBase[], weigets?: number[]) { super(NodeType.RandomSelector, children) this.weights = weigets ? weigets : new Array(children.length).fill(1) } } /** 并行执行所有子节点, 全部执行完毕后返回 */ export class ParallelNode extends CombineNode { public ignoreFailture = true constructor(children: NodeBase[], ignoreFailture?: boolean) { super(NodeType.Parallel, children) this.ignoreFailture = ignoreFailture } } /** 修饰节点 */ class DecoratorNode extends NodeBase { public child: NodeBase = null public constructor(type: NodeType, child: NodeBase) { super(type) this.child = child } } /** 返回子节点执行结果的取反值 */ export class InverterNode extends DecoratorNode { constructor(child: NodeBase) { super(NodeType.Inverter, child) } } /** 子节点执行完毕后, 必定返回成功 */ export class SuccessNode extends DecoratorNode { constructor(child: NodeBase) { super(NodeType.Success, child) } } /** 子节点执行完毕后, 必定返回失败 */ export class FailNode extends DecoratorNode { constructor(child: NodeBase) { super(NodeType.Fail, child) } } /** 子节点执行重复repeatCount次后返回成功 */ export class RepeaterNode extends DecoratorNode { public repeatCount = 1 public currRepeatCount = 0 public mustSuccess = false // 子节点必须支持成功才增加重复次数 constructor(child: NodeBase, repeatCount: number, mustSuccess = false) { super(NodeType.Repeater, child) this.repeatCount = repeatCount this.mustSuccess = mustSuccess } } /** 子节点重复执行直到返回成功 */ export class RetryTillSuccess extends DecoratorNode { timeout: number // 超时时间 countDown: number // 剩余时间 constructor(child: NodeBase, timeout: number) { super(NodeType.RetryTillSuccess, child) this.timeout = timeout } } /** 叶子结点 */ export class WaitNode extends NodeBase { public waitSeconds: number public countDown: number constructor(seconds: number) { super(NodeType.Wait) this.waitSeconds = seconds } } export class WalkToCloseTargetNode extends NodeBase { public speed: number public isCloseX: boolean constructor(speed: number, isCloseX: boolean) { super(NodeType.WalkToCloseTarget) this.speed = speed this.isCloseX = isCloseX } } /** 移动到目标攻击位置后 返回成功 */ export class MeleeWalkToTargetNode extends NodeBase { public speed: number constructor(speed: number) { super(NodeType.MeleeWalkToTarget) this.speed = speed } } export class MeleeMonitorNode extends NodeBase { constructor() { super(NodeType.MeleeMonitor) } } export class MeleeXMonitorNode extends NodeBase { constructor() { super(NodeType.MeleeXMonitor) } } export class AttackMonitorNode extends NodeBase { constructor() { super(NodeType.AttackMonitor) } } export class AttackNode extends NodeBase { constructor() { super(NodeType.Attack) } } /** 将要攻击的目标 */ export class WillAttackNode extends NodeBase { constructor() { super(NodeType.WillAttack) } } /** 将要被攻击的目标 */ export class WillBeAttackNode extends NodeBase { constructor() { super(NodeType.WillBeAttack) } } /** 有正在攻击的目标 */ export class HasAttackEnemyNode extends NodeBase { constructor() { super(NodeType.HasAttackEnemy) } } export class WillAttackNotWillAttackNode extends NodeBase { constructor() { super(NodeType.WillAttackNotWillAttack) } } export class BeDizzyNode extends NodeBase { constructor() { super(NodeType.BeDizzy) } } export class DizzyNode extends NodeBase { constructor() { super(NodeType.Dizzy) } } export class HasSkillNode extends NodeBase { constructor() { super(NodeType.HasSkill) } } export class SkillNode extends NodeBase { constructor() { super(NodeType.Skill) } } export class CastSkillNode extends NodeBase { constructor() { super(NodeType.CastSkill) } } export class FindEnemyNode extends NodeBase { constructor() { super(NodeType.FindEnemy) } } export class HasBeAttackNode extends NodeBase { constructor() { super(NodeType.HasBeAttack) } } export class StopNode extends NodeBase { constructor() { super(NodeType.Stop) } } export class StopAttackNode extends NodeBase { constructor() { super(NodeType.StopAttack) } } export class StopSkillNode extends NodeBase { constructor() { super(NodeType.StopSkill) } } class NodeHandler { onEnter: (node: NodeBase, context: ExecuteContext) => void onUpdate: (node: NodeBase, context: ExecuteContext) => void } export const NodeHandlers: NodeHandler[] = [] /** Sequence node */ NodeHandlers[NodeType.Sequence] = { onEnter(node: SequenceNode, context: ExecuteContext): void { node.currIdx = 0 context.executor.onEnterBTNode(node.children[node.currIdx], context) node.state = NodeState.Executing }, onUpdate(node: SequenceNode, context: ExecuteContext): void { if (node.currIdx < 0 || node.currIdx >= node.children.length) { // 越界了, 不应该发生, 直接认为是失败了 node.state = NodeState.Fail if (debug) node.failLog = '越界了, 不应该发生, 直接认为是失败了' return } // 检查前置条件是否满足 for (let i = 0; i < node.currIdx; i++) { context.executor.updateBTNode(node.children[i], context) if (node.children[i].state !== NodeState.Success) { //当前置条件不满足,节点应该失败 node.state = NodeState.Fail if (debug) node.failLog = '队列行为被前置打断===Sequence Fail==>' + node.children[i].failLog return } } context.executor.updateBTNode(node.children[node.currIdx], context) const state = node.children[node.currIdx].state if (node.currIdx == 4 && context.entity == debugEntity) console.log('state', state, state == NodeState.Executing) if (state == NodeState.Executing) return if (state == NodeState.Fail && !node.ignoreFailure) { node.state = NodeState.Fail if (debug) node.failLog = '队列中断==>' + node.children[node.currIdx].failLog return } if (state == NodeState.Success && node.currIdx == node.children.length - 1) { node.state = NodeState.Success if (debug) node.successLog = '队列成功==>' + node.children[node.currIdx].successLog return } context.executor.onEnterBTNode(node.children[++node.currIdx], context) }, } // 子节点某个节点在中途失败,不影响后续节点执行,后续节点执行完结束队列,举个例子 攻击动作1秒 --》 等待2秒,如果攻击动作在0.5秒被打断,这个队列节点也要等到2秒后结束 /** LockedSequence node */ NodeHandlers[NodeType.LockedSequence] = { onEnter(node: LockedSequenceNode, context: ExecuteContext): void { node.currIdx = 0 context.executor.onEnterBTNode(node.children[node.currIdx], context) node.state = NodeState.Executing }, onUpdate(node: LockedSequenceNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return if (node.currIdx < 0 || node.currIdx >= node.children.length) { // 越界了, 不应该发生, 直接认为是失败了 node.state = NodeState.Fail return } context.executor.updateBTNode(node.children[node.currIdx], context) let state = node.children[node.currIdx].state if (state == NodeState.Executing) return if (state === NodeState.Fail && !node.ignoreFailure) { node.state = NodeState.Fail return } if (state === NodeState.Success && node.currIdx == node.children.length - 1) { node.state = NodeState.Success return } context.executor.onEnterBTNode(node.children[++node.currIdx], context) }, } /** Selector node */ NodeHandlers[NodeType.Selector] = { onEnter(node: SelectorNode, context: ExecuteContext): void { node.currIdx = 0 context.executor.onEnterBTNode(node.children[node.currIdx], context) node.state = NodeState.Executing }, onUpdate(node: SelectorNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return if (node.currIdx < 0 || node.currIdx >= node.children.length) { // 越界了, 认为是失败了 node.state = NodeState.Fail return } context.executor.updateBTNode(node.children[node.currIdx], context) let state = node.children[node.currIdx].state if (state == NodeState.Executing) return // 执行到最后一个都失败了, 那边selector失败了 if (state === NodeState.Fail && node.currIdx == node.children.length - 1) { node.state = NodeState.Fail return } if (state == NodeState.Success) { node.state = NodeState.Success return } context.executor.onEnterBTNode(node.children[++node.currIdx], context) }, } export let debugEntity: number = -1 let debugEntityCurIdx: number = -1 let rootSelectorLog = ['晕眩', '放技能', '攻击', '往X轴敌人移动', '停止'] let meleeRootSelectorLog = [ '晕眩', '放技能', '寻敌', '往X轴敌人移动', '往敌人移动', '将被攻击', '将攻击敌人', '攻击', '停止', ] /** UpdateSelector node */ NodeHandlers[NodeType.RootSelector] = { onEnter(node: RootSelectorNode, context: ExecuteContext): void { node.currIdx = 0 context.executor.onEnterBTNode(node.children[node.currIdx], context) node.state = NodeState.Executing this.onUpdate(node, context) }, onUpdate(node: RootSelectorNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return if (node.currIdx < 0 || node.currIdx >= node.children.length) { // 越界了, 认为是失败了 node.state = NodeState.Fail return } while (node.currIdx < node.children.length) { context.executor.updateBTNode(node.children[node.currIdx], context) let state = node.children[node.currIdx].state if (state == NodeState.Executing) break // 执行到最后一个都失败了, 那边selector失败了 if (state === NodeState.Fail && node.currIdx == node.children.length - 1) { node.state = NodeState.Fail break } if (state == NodeState.Success) { if (context.entity == debugEntity && node.currIdx != debugEntityCurIdx) { let failStr = '' let debugRootLog = node.isMelee ? meleeRootSelectorLog : rootSelectorLog for (let i = 0; i < node.currIdx; i++) { failStr += `[${debugRootLog[i]}]` + '(' + node.children[i].failLog + ')' } console.log('--------', context.entity, '--------', 'RootSelector Fail ', failStr) console.log( 'RootSelector Success ', debugRootLog[node.currIdx], node.children[node.currIdx].successLog, '--------', context.entity, '--------', ) node.currIdx = debugEntityCurIdx } node.state = NodeState.Success break } context.executor.onEnterBTNode(node.children[++node.currIdx], context) } }, } /** Selector node */ NodeHandlers[NodeType.RandomSelector] = { onEnter(node: RandomSelectorNode, context: ExecuteContext): void { // 根据权重随机获取idx let totalWeight = 0 for (const weight of node.weights) { totalWeight += weight } let randomWeight = Math.random() * totalWeight for (let i = 0; i < node.weights.length; i++) { randomWeight -= node.weights[i] if (randomWeight <= 0) { node.currIdx = i break } } context.executor.onEnterBTNode(node.children[node.currIdx], context) node.state = NodeState.Executing }, onUpdate(node: RandomSelectorNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return let n = node.children[node.currIdx] context.executor.updateBTNode(n, context) node.state = n.state }, } /** Parallel node */ NodeHandlers[NodeType.Parallel] = { onEnter(node: ParallelNode, context: ExecuteContext): void { for (const n of node.children) { context.executor.onEnterBTNode(n, context) } node.state = NodeState.Executing }, onUpdate(node: ParallelNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return let end = true for (const child of node.children) { context.executor.updateBTNode(child, context) if (child.state === NodeState.Executing) { end = false continue } if (child.state == NodeState.Fail) { node.state = NodeState.Fail return } } if (end) { node.state = NodeState.Success } }, } /** Inverter node */ NodeHandlers[NodeType.Inverter] = { onEnter(node: InverterNode, context: ExecuteContext): void { context.executor.onEnterBTNode(node.child, context) node.state = NodeState.Executing }, onUpdate(node: InverterNode, context: ExecuteContext): void { context.executor.updateBTNode(node.child, context) if (node.child.state === NodeState.Executing) return if (node.child.state == NodeState.Success) { node.state = NodeState.Fail if (debug) node.failLog = node.child.successLog } if (node.child.state == NodeState.Fail) { node.state = NodeState.Success if (debug) node.successLog = node.child.failLog } }, } /** Success node */ NodeHandlers[NodeType.Success] = { onEnter(node: SuccessNode, context: ExecuteContext): void { context.executor.onEnterBTNode(node.child, context) node.state = NodeState.Executing }, onUpdate(node: SuccessNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return context.executor.updateBTNode(node.child, context) if (node.child.state === NodeState.Executing) return node.state = NodeState.Success }, } /** Fail node */ NodeHandlers[NodeType.Fail] = { onEnter(node: FailNode, context: ExecuteContext): void { context.executor.onEnterBTNode(node.child, context) node.state = NodeState.Executing }, onUpdate(node: FailNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return context.executor.updateBTNode(node.child, context) if (node.child.state === NodeState.Executing) return node.state = NodeState.Fail }, } /** Repeater node */ NodeHandlers[NodeType.Repeater] = { onEnter(node: RepeaterNode, context: ExecuteContext): void { node.currRepeatCount = 0 context.executor.onEnterBTNode(node.child, context) node.state = NodeState.Executing }, onUpdate(node: RepeaterNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return context.executor.updateBTNode(node.child, context) if (node.child.state === NodeState.Executing) return if (!node.mustSuccess || node.child.state == NodeState.Success) node.currRepeatCount++ if (node.currRepeatCount >= node.repeatCount) { node.state = NodeState.Success return } context.executor.onEnterBTNode(node.child, context) }, } /** RetryTillSuccess node */ NodeHandlers[NodeType.RetryTillSuccess] = { onEnter(node: RetryTillSuccess, context: ExecuteContext): void { node.countDown = node.timeout context.executor.onEnterBTNode(node.child, context) node.state = NodeState.Executing }, onUpdate(node: RetryTillSuccess, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return node.countDown -= context.dt context.executor.updateBTNode(node.child, context) if (node.child.state === NodeState.Executing) return if (node.child.state == NodeState.Success) { node.state = NodeState.Success return } if (node.countDown > 0) { context.executor.onEnterBTNode(node.child, context) return } node.state = NodeState.Fail }, } /** Wait node */ NodeHandlers[NodeType.Wait] = { onEnter(node: WaitNode, context: ExecuteContext): void { node.countDown = node.waitSeconds node.state = NodeState.Executing }, onUpdate(node: WaitNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return node.countDown -= context.dt if (node.countDown <= 0) { node.state = NodeState.Success } }, } //-------------------------------------行为能执行的条件,每次行为update要执行条件-------------------------------------------------- /** FindEnemy node 战场上有没有敌人 */ NodeHandlers[NodeType.FindEnemy] = { onEnter(node: FindEnemyNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: FindEnemyNode, context: ExecuteContext): void { let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) node.state = !context.world.isDie(comFindEnemy?.enemy) ? NodeState.Success : NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '找到敌人==>' + comFindEnemy.enemy.toString() } else { node.failLog = '战场上没有敌人' } } }, } /** MeleeMonitor node 是否进入Y轴近战监视区域 */ NodeHandlers[NodeType.MeleeMonitor] = { onEnter(node: MeleeMonitorNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: MeleeMonitorNode, context: ExecuteContext): void { let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) node.state = comFindEnemy && comFindEnemy.cObject && comFindEnemy.cObject.containsBody.size > 0 ? BT.NodeState.Success : BT.NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '敌人进入Y轴近战监视区域==>' + comFindEnemy.cObject.containsBody.keysArr().toString() } else { node.failLog = 'Y轴近战监视区域没有敌人' } } }, } /** MeleeMonitor node 是否进入X轴近战监视区域 */ NodeHandlers[NodeType.MeleeXMonitor] = { onEnter(node: MeleeXMonitorNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: MeleeXMonitorNode, context: ExecuteContext): void { let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) node.state = comFindEnemy && comFindEnemy.cObjectMeleeX && comFindEnemy.cObjectMeleeX.containsBody.size > 0 ? BT.NodeState.Success : BT.NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '敌人进入X轴近战监视区域==>' + comFindEnemy.cObjectMeleeX.containsBody.keysArr().toString() } else { node.failLog = 'X轴近战监视区域没有敌人' } } }, } /** AttackMonitor node 是否进入攻击范围*/ NodeHandlers[NodeType.AttackMonitor] = { onEnter(node: AttackMonitorNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: AttackMonitorNode, context: ExecuteContext): void { let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) node.state = comFindEnemy && comFindEnemy.attackCObject && comFindEnemy.attackCObject.containsBody.size > 0 ? BT.NodeState.Success : BT.NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '敌人进入攻击范围==>' + comFindEnemy.attackCObject.containsBody.keysArr().toString() } else { node.failLog = '没有敌人进入攻击范围' } } }, } /** WillAttackNotWillAttack node 近战即将攻击的目标有没有即将攻击的目标*/ NodeHandlers[NodeType.WillAttackNotWillAttack] = { onEnter(node: WillAttackNotWillAttackNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: WillAttackNotWillAttackNode, context: ExecuteContext): void { let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) let comMovable = context.world.getComponent(comFindEnemy?.willAttackMelee, ComMovable) node.state = comMovable.speed ? NodeState.Fail : NodeState.Success if (debug) { if (node.state == NodeState.Success) { node.successLog = '近战即将攻击的目标正在走路==>' + comFindEnemy?.willAttackMelee } else { node.failLog = '近战即将攻击的目标停止了==>' + comFindEnemy?.willAttackMelee } } }, } /** WillAttackNode node 近战即将攻击敌人*/ NodeHandlers[NodeType.WillAttack] = { onEnter(node: WillAttackNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: WillAttackNode, context: ExecuteContext): void { let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) node.state = !context.world.isDie(comFindEnemy?.willAttackMelee) ? BT.NodeState.Success : BT.NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '近战有即将攻击的敌人==>' + comFindEnemy?.willAttackMelee } else { node.failLog = '近战没有即将攻击敌人' } } }, } /** WillAttackNode node 近战即将被敌人攻击*/ NodeHandlers[NodeType.WillBeAttack] = { onEnter(node: WillBeAttackNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: WillBeAttackNode, context: ExecuteContext): void { let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) node.state = !context.world.isDie(comFindEnemy?.willBeAttackMelee) ? BT.NodeState.Success : BT.NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '近战即将被敌人攻击==>' + comFindEnemy?.willBeAttackMelee } else { node.failLog = '近战没有近战即将被敌人攻击' } } }, } /** HasAttackEnemy node 近战有没有正在攻击的敌人*/ NodeHandlers[NodeType.HasAttackEnemy] = { onEnter(node: HasAttackEnemyNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: HasAttackEnemyNode, context: ExecuteContext): void { let comAttackable = context.world.getComponent(context.entity, ComAttackable) node.state = !context.world.isDie(comAttackable?.curAttack) ? BT.NodeState.Success : BT.NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '近战正在攻击的敌==>' + comAttackable?.curAttack } else { node.failLog = '近战没有正在攻击的敌人' } } }, } /** BeDizzy node 是否被晕眩*/ NodeHandlers[NodeType.BeDizzy] = { onEnter(node: BeDizzyNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: BeDizzyNode, context: ExecuteContext): void { let comDizzy = context.world.getComponent(context.entity, ComDizzy) node.state = comDizzy && comDizzy.countDown > 0 ? NodeState.Success : NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '被晕眩=' } else { node.failLog = '没被晕眩' } } }, } /** HasSkill node 有没有技能可以施放*/ // 所有的判断条件每次都要判断 NodeHandlers[NodeType.HasSkill] = { onEnter(node: HasSkillNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: HasSkillNode, context: ExecuteContext): void { let comSkillAbel = context.world.getComponent(context.entity, ComSkillAbel) if (!comSkillAbel) { //console.log('HasSkill onUpdate', '目标没有ComSkillAbel', context.entity) node.state = NodeState.Fail return } node.state = comSkillAbel && comSkillAbel.skillConfig && comSkillAbel.roles.length >= comSkillAbel.skillConfig.minRole ? NodeState.Success : NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = `有技能可以施放===>${ comSkillAbel.skillConfig.name }==>技能目标${comSkillAbel.roles.toString()}` } else { node.failLog = `没有技能可以施放===>技能目标人数:${comSkillAbel.roles.length}技能:${comSkillAbel.skillConfig?.name}正在施放:${comSkillAbel.dirty}` } } }, } /** CastSkill node 正在施放技能*/ // 所有的判断条件每次都要判断 NodeHandlers[NodeType.CastSkill] = { onEnter(node: HasSkillNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: HasSkillNode, context: ExecuteContext): void { let comSkillAbel = context.world.getComponent(context.entity, ComSkillAbel) if (!comSkillAbel) { //console.log('HasSkill onUpdate', '目标没有ComSkillAbel', context.entity) node.state = NodeState.Fail return } node.state = comSkillAbel.dirty ? NodeState.Success : NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '正在施放技能' } else { node.failLog = '没有正在施放技能' } } }, } /** BeDizzy node 是否即将被攻击*/ NodeHandlers[NodeType.HasBeAttack] = { onEnter(node: HasBeAttackNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: HasBeAttackNode, context: ExecuteContext): void { let comAttackable = context.world.getComponent(context.entity, ComAttackable) node.state = comAttackable && comAttackable.beAttacks.length > 0 ? BT.NodeState.Success : BT.NodeState.Fail if (debug) { if (node.state == NodeState.Success) { node.successLog = '被近战包围==>' + comAttackable.beAttacks.toString() } else { node.failLog = '没有被近战包围' } } }, } //-------------------------------------行为-------------------------------------------------- /** MeleeWalkToTarget node */ NodeHandlers[NodeType.MeleeWalkToTarget] = { onEnter(node: MeleeWalkToTargetNode, context: ExecuteContext): void { let comTrans = context.world.getComponent(context.entity, ComTransform) let comMovable = context.world.getComponent(context.entity, ComMovable) let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) let targetComTrans = context.world.getComponent(comFindEnemy.willAttackMelee, ComTransform) let targetComMovable = context.world.getComponent(comFindEnemy.willAttackMelee, ComMovable) let targetPos = context.world.moveToMeleeAttackPos( context.entity, comTrans, targetComTrans, targetComMovable, ) comMovable.pointIdx = 0 comMovable.points.length = 0 comMovable.points.push(targetPos) //加速前往近战攻击点 context.world.changeMoveSpeed(context.entity, node.speed) node.state = NodeState.Executing //console.log(' onEnter comFindEnemy.willAttackMelee', context.entity, comFindEnemy.willAttackMelee) }, onUpdate(node: MeleeWalkToTargetNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return let comMovable = context.world.getComponent(context.entity, ComMovable) let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) let comAttackable = context.world.getComponent(context.entity, ComAttackable) //在进入行为之前满足条件,这时候运行到下一帧,可能在SysFindEnemy系统中修改了死亡的敌人comFindEnemy.willAttackMelee=0 if (context.world.isDie(comFindEnemy.willAttackMelee)) { Log.error('MeleeWalkToTarget onUpdate', '将要攻击的敌人不存在', comFindEnemy.willAttackMelee) node.state = BT.NodeState.Fail return } if ( comMovable.points.length == 0 || comMovable.pointIdx < 0 || comMovable.pointIdx >= comMovable.points.length ) { // 把将要击打变成击打 comAttackable.curAttack = comFindEnemy.willAttackMelee let enemyComFindEnemy = context.world.getComponent(comAttackable.curAttack, ComFindEnemy) let enemyComAttackable = context.world.getComponent(comAttackable.curAttack, ComAttackable) // 敌人击打数组加入自己 enemyComAttackable.beAttacks.push(context.entity) //如果敌人等待着自己将敌人攻击赋值 if (enemyComFindEnemy && enemyComFindEnemy.willBeAttackMelee == context.entity) { enemyComFindEnemy.willBeAttackMelee = 0 enemyComAttackable.curAttack = context.entity comAttackable.beAttacks.push(comAttackable.curAttack) } comFindEnemy.willAttackMelee = 0 node.state = BT.NodeState.Success if (debug) { if (node.state == NodeState.Success) { node.successLog = '走到了近战攻击点==>' + comAttackable.curAttack.toString() } } } }, } /** WalkToClosedTarget node */ NodeHandlers[NodeType.WalkToCloseTarget] = { onEnter(node: WalkToCloseTargetNode, context: ExecuteContext): void { let comTrans = context.world.getComponent(context.entity, ComTransform) let comMovable = context.world.getComponent(context.entity, ComMovable) let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) let comCocosNode = context.world.getComponent(context.entity, ComCocosNode) let comRole = context.world.getComponent(context.entity, ComRole) let comSpComCocosNode = context.world.getComponent(comRole.spineEntity, ComCocosNode) let targetComTrans = context.world.getComponent(comFindEnemy.enemy, ComTransform) comMovable.pointIdx = 0 comMovable.points.length = 0 comMovable.points.push( cc.v2(comTrans.x, comTrans.y), cc.v2(targetComTrans.x, node.isCloseX ? comTrans.y : targetComTrans.y), ) context.world.changeMoveSpeed(context.entity, node.speed) comMovable.closeTarget = comFindEnemy.enemy comTrans.dir.x = targetComTrans.x > comTrans.x ? 1 : -1 comCocosNode.node?.getComponent(EventProcess)?.syncDir(comTrans.dir) comSpComCocosNode?.node?.getComponent(EventProcess)?.syncDir(comTrans.dir) node.state = NodeState.Executing }, onUpdate(node: WalkToCloseTargetNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return let comTrans = context.world.getComponent(context.entity, ComTransform) let comMovable = context.world.getComponent(context.entity, ComMovable) let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) let targetComTrans = context.world.getComponent(comFindEnemy.enemy, ComTransform) if (comMovable.points[1]) { comMovable.points[1].x = targetComTrans.x comMovable.points[1].y = node.isCloseX ? comTrans.y : targetComTrans.y } if ( comMovable.points.length == 0 || comMovable.pointIdx < 0 || comMovable.pointIdx >= comMovable.points.length ) { node.state = BT.NodeState.Success } }, } /** Attack node */ NodeHandlers[NodeType.Attack] = { onEnter(node: AttackNode, context: ExecuteContext): void { //当攻击目标死亡,前置条件被打断,被再次执行攻击行为,这个时候要return if (node.state == NodeState.Executing) { return } let comCocosNode = context.world.getComponent(context.entity, ComCocosNode) let comAttackable = context.world.getComponent(context.entity, ComAttackable) let comRole = context.world.getComponent(context.entity, ComRole) let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) //远程每次切换最近的目标 if (!comAttackable.isMelee) { comAttackable.curAttack = comFindEnemy.enemy } let enemyComTransform = context.world.getComponent(comAttackable.curAttack, ComTransform) let comTransform = context.world.getComponent(context.entity, ComTransform) //console.log('攻击节点 ------------------->', context.entity) comTransform.dir.x = comTransform?.x - enemyComTransform?.x < 0 ? 1 : -1 let comSpineTransform = context.world.getComponent(comRole.spineEntity, ComTransform) if (comSpineTransform) { comSpineTransform.dir.x = comTransform.dir.x } let attackSpeed = Math.clampValue(comRole.attackSpeed, Data.game.minAttackSpeed, Data.game.maxAttackSpeed) let attackScale = Data.game.rateNum / attackSpeed let buffs = comRole.buffs let attackIndex = 0 for (let i = 0; i < buffs.length; i++) { let tmpIndex = buffs[i].buffCfg.effectType.indexOf(BUFF_EFFECT_TYPE.changeSpineAttack) if (tmpIndex >= 0) { attackIndex = buffs[i].buffCfg.effectParm[tmpIndex][0] } } comCocosNode.events.push( new EventAttack(comTransform.dir, !comAttackable.isMelee, attackScale, attackIndex), ) //同步spine攻击 let comSpineNode = context.world.getComponent(comRole.spineEntity, ComCocosNode) comSpineNode?.events.push( new EventAttack(comTransform.dir, !comAttackable.isMelee, attackScale, attackIndex), ) comAttackable.countDown = (comRole.attackTime + comRole.attackCD) * attackScale * Data.game.gameSpeed comAttackable.dirty = true comAttackable.hurtFrame = comAttackable.countDown - comRole.roleCfg.hurtFrame * attackScale * comRole.attackTime comAttackable.hurtFrameCompleted = false node.state = NodeState.Executing }, onUpdate(node: AttackNode, context: ExecuteContext): void { if (node.state != NodeState.Executing) return let comAttackable = context.world.getComponent(context.entity, ComAttackable) let comFindEnemy = context.world.getComponent(context.entity, ComFindEnemy) let comRole = context.world.getComponent(context.entity, ComRole) //可能当前攻击敌人死亡 // if (context.world.isDie(comAttackable?.curAttack)) { // //console.log('Attack onUpdate', '当前攻击敌人死亡', context.entity, comAttackable?.curAttack) // comAttackable.dirty = false // comAttackable.countDown = 0 // node.state = NodeState.Fail // } if (comAttackable && comAttackable.countDown <= 0) { node.state = NodeState.Success if (debug) { if (node.state == NodeState.Success) { node.successLog = '攻击行为成功==>' + comAttackable?.curAttack } } let comBeAttackRole: ComRole = context.world.getComponent(comAttackable.curAttack, ComRole) //当正在攻击对面基地的时候,敌军有别的单位,不在打基地了--》修改为Y轴探测范围内有人不再攻击基地 if (context.world.isBase(comBeAttackRole)) { let filter = context.world.getFilter( comRole.group == Data.game.gFriend ? FILTER_CAN_ATTACK_ENEMY : FILTER_CAN_ATTACK_FRIEND, ) if (filter.entities.size > 1 && comFindEnemy.cObject.containsBody.size > 1) { comAttackable.curAttack = 0 } } } }, } /** Skill node */ NodeHandlers[NodeType.Skill] = { onEnter(node: SkillNode, context: ExecuteContext): void { let comSkillAbel = context.world.getComponent(context.entity, ComSkillAbel) let comCocosNode = context.world.getComponent(context.entity, ComCocosNode) let comRole = context.world.getComponent(context.entity, ComRole) if (!comSkillAbel || !comCocosNode || !comRole) { !comSkillAbel && console.log('Skill onEnter', '目标没有ComSkillAbel', context.entity) !comCocosNode && console.log('Skill onEnter', '目标没有ComCocosNode', context.entity) return } //console.log('施放技能', comSkillAbel.skillConfig.name) if (CC_DEV) ccUtils.setLabel(comSkillAbel.skillConfig.name, comCocosNode.node, 'skill') //技能无CD的buff只放一次 let curSkillIndex = 0 for (let i = 0; i < comRole.skills.length; i++) { if (comRole.skills[i].ID == comSkillAbel.skillConfig.ID) { curSkillIndex = i let cd = comRole.skillCDs[i] if (cd > 0 && cd != Data.game.cdMax) { //词条减CD if (comRole.entryMap.get(ENTRY.skillCD)?.num) { cd *= 1 - comRole.entryMap.get(ENTRY.skillCD).num / Data.game.rateNum } //buff减CD for (let j = 0; j < comRole.buffs.length; j++) { let buffCfg = comRole.buffs[j].buffCfg if (buffCfg.buffType == BUFF_TYPE.skillCD && buffCfg.attrRate[curSkillIndex]) { cd *= 1 - buffCfg.attrRate[curSkillIndex] / Data.game.rateNum } } if (cd < 0) cd = 0 } comRole.skillCountDowns[i] = (cd == 0 && comSkillAbel.skillConfig.type != SKILL_TYPE.holo) || cd == Data.game.cdMax ? Infinity : cd } } comSkillAbel.dirty = true comSkillAbel.startDirty = true comSkillAbel.skillDirty = true //吟唱时间 comSkillAbel.countDown = comSkillAbel.skillConfig.castDuration > comRole.castTime[curSkillIndex] ? comSkillAbel.skillConfig.castDuration : comRole.castTime[curSkillIndex] let timeScale = 1 - comSkillAbel.skillConfig.castFrame if (timeScale > 1) timeScale = 1 if (timeScale < 0) timeScale = 0 comSkillAbel.castSkillTime = timeScale * comSkillAbel.countDown comCocosNode.events.push( new EventSkill(comSkillAbel.countDown, comRole.skills.indexOf(comSkillAbel.skillConfig)), ) //同步spine吟唱 let comSpineNode = context.world.getComponent(comRole.spineEntity, ComCocosNode) comSpineNode?.events.push( new EventSkill(comSkillAbel.countDown, comRole.skills.indexOf(comSkillAbel.skillConfig)), ) node.state = NodeState.Executing }, onUpdate(node: SkillNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return let comSkillAbel = context.world.getComponent(context.entity, ComSkillAbel) let comCocosNode = context.world.getComponent(context.entity, ComCocosNode) // 技能开始的时候没有技能施放目标停止施放技能 if (comSkillAbel.skillConfig && comSkillAbel.roles.length == 0 && comSkillAbel.skillConfig.minRole > 0) { Log.error('Skill onUpdate', '没有技能施放目标停止施放技能', context.entity, comSkillAbel.skillConfig) let comRole = context.world.getComponent(context.entity, ComRole) let curSkillIndex = comRole.skills.findIndex(value => value.ID == comSkillAbel.skillConfig.ID) if (curSkillIndex >= 0) { //让技能可以重新被施放 comRole.skillCountDowns[curSkillIndex] = 0 } comSkillAbel.dirty = false node.state = NodeState.Fail } if (comSkillAbel && comSkillAbel.countDown <= 0) { comSkillAbel.dirty = false node.state = NodeState.Success if (debug) { if (node.state == NodeState.Success) { node.successLog = '技能行为成功==>' + context.entity } } if (debug) ccUtils.setLabel('', comCocosNode.node, 'skill') } }, } /** Dizzy node */ NodeHandlers[NodeType.Dizzy] = { onEnter(node: DizzyNode, context: ExecuteContext): void { let comDizzy = context.world.getComponent(context.entity, ComDizzy) if (!comDizzy) { //console.log('Dizzy onEnter', '目标没有ComDizzy', context.entity) return } comDizzy.dirty = true node.state = NodeState.Executing }, onUpdate(node: DizzyNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return }, } /** StopNode node */ NodeHandlers[NodeType.Stop] = { onEnter(node: StopNode, context: ExecuteContext): void { node.state = NodeState.Executing }, onUpdate(node: StopNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return context.world.changeMoveSpeed(context.entity, 0) node.state = NodeState.Success if (debug) { if (node.state == NodeState.Success) { node.successLog = '停止行为成功==>' + context.entity } } }, } /** StopAttack node */ NodeHandlers[NodeType.StopAttack] = { onEnter(node: StopAttackNode, context: ExecuteContext): void { let comAttackable = context.world.getComponent(context.entity, ComAttackable) if (!comAttackable) { Log.error('StopAttack onEnter', '目标没有ComAttackable', context.entity) return } node.state = NodeState.Executing }, onUpdate(node: StopAttackNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return let comAttackable = context.world.getComponent(context.entity, ComAttackable) comAttackable.dirty = false comAttackable.countDown = 0 node.state = NodeState.Success if (debug) { if (node.state == NodeState.Success) { node.successLog = '停止攻击行为成功==>' + context.entity } } }, } /** StopAttack node */ NodeHandlers[NodeType.StopSkill] = { onEnter(node: StopAttackNode, context: ExecuteContext): void { let comSkill = context.world.getComponent(context.entity, ComSkillAbel) if (!comSkill) { Log.error('StopSkill onEnter', '目标没有ComSkill', context.entity) return } node.state = NodeState.Executing }, onUpdate(node: StopAttackNode, context: ExecuteContext): void { if (node.state !== NodeState.Executing) return let comSkill = context.world.getComponent(context.entity, ComSkillAbel) let comRole = context.world.getComponent(context.entity, ComRole) let comSpineCocosNode = context.world.getComponent(comRole?.spineEntity, ComCocosNode) comSpineCocosNode.events.push(new EventStopSkill()) comSkill.dirty = false comSkill.countDown = Infinity comSkill.skillConfig = null comSkill.isInShield = false node.state = NodeState.Success if (debug) { if (node.state == NodeState.Success) { node.successLog = '停止技能行为成功==>' + context.entity } } }, } }