mirror of
https://github.com/kirikayakazuto/CocosCreator_ECS
synced 2025-10-30 19:05:52 +00:00
ECS Demo
This commit is contained in:
644
assets/Script/Common/BehaviorTree.ts
Normal file
644
assets/Script/Common/BehaviorTree.ts
Normal file
@@ -0,0 +1,644 @@
|
||||
import { ComAttackable } from "../ECS/components/ComAttackable";
|
||||
import { ComCocosNode } from "../ECS/components/ComCocosNode";
|
||||
import { ComMonitor } from "../ECS/components/ComMonitor";
|
||||
import { ComMovable } from "../ECS/components/ComMovable";
|
||||
import { ComRoleConfig } from "../ECS/components/ComRoleConfig";
|
||||
import { ComTransform } from "../ECS/components/ComTransform";
|
||||
import { ECSWorld } from "../ECS/lib/ECSWorld";
|
||||
import { SysBehaviorTree } from "../ECS/systems/SysBehaviorTree";
|
||||
import { EventAttack, EventHPChange, EventHurt } from "../Struct/NodeEvent";
|
||||
export namespace BT {
|
||||
|
||||
export class BlackBoard {
|
||||
|
||||
}
|
||||
|
||||
export class ExecuteContext {
|
||||
executor: SysBehaviorTree;
|
||||
world: ECSWorld;
|
||||
bb: BlackBoard;
|
||||
|
||||
entity: number;
|
||||
dt: number;
|
||||
public init(executor: SysBehaviorTree, world: ECSWorld) {
|
||||
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 {
|
||||
// 组合节点
|
||||
Sequence, // 顺序节点
|
||||
Selector, // 选择节点
|
||||
RandomSelector, // 随机选择节点
|
||||
Parallel, // 并行节点
|
||||
|
||||
// 修饰节点
|
||||
Inverter, // 逆变节点
|
||||
Success, // 成功节点
|
||||
Fail, // 失败节点
|
||||
Repeater, // 重复节点
|
||||
RetryTillSuccess, // 重复直到成功
|
||||
|
||||
// 叶子结点
|
||||
Wait, // 等待
|
||||
Action,
|
||||
WalkToPos,
|
||||
WalkToRandomPos, //
|
||||
WalkToTarget,
|
||||
Monitor, // 监视
|
||||
Attack,
|
||||
|
||||
EnoughAttr, // 死亡
|
||||
GoDeath, // 检查是否
|
||||
}
|
||||
|
||||
export class NodeBase {
|
||||
public type: NodeType;
|
||||
public state: NodeState = NodeState.Success;
|
||||
|
||||
constructor(type: NodeType) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
/** 组合节点 */
|
||||
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 SelectorNode extends CombineNode {
|
||||
public currIdx:number = -1;
|
||||
|
||||
constructor(children: NodeBase[], ) {
|
||||
super(NodeType.Selector, children);
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据权重随机选择执行某个子节点 */
|
||||
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 WalkToPosNode extends NodeBase {
|
||||
public speed:number;
|
||||
public targetPos: cc.Vec2;
|
||||
constructor(speed: number, pos: cc.Vec2) {
|
||||
super(NodeType.WalkToPos);
|
||||
this.speed = speed;
|
||||
this.targetPos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
export class WalkToRandomPosNode extends NodeBase {
|
||||
public speed: number;
|
||||
public size: cc.Size;
|
||||
constructor(speed: number, size: cc.Size) {
|
||||
super(NodeType.WalkToRandomPos);
|
||||
this.speed = speed;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
export class WalkToTargetNode extends NodeBase {
|
||||
public speed: number;
|
||||
constructor(speed: number) {
|
||||
super(NodeType.WalkToTarget);
|
||||
this.speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
export class MonitorNode extends NodeBase {
|
||||
constructor() {
|
||||
super(NodeType.Monitor);
|
||||
}
|
||||
}
|
||||
|
||||
export class AttackNode extends NodeBase {
|
||||
constructor(waitSeconds: number) {
|
||||
super(NodeType.Attack);
|
||||
}
|
||||
}
|
||||
|
||||
export class EnoughAttrNode extends NodeBase {
|
||||
public com: {prototype: any};
|
||||
public attr: string;
|
||||
public value: number;
|
||||
constructor(com: {prototype: any}, attr: string, value: number) {
|
||||
super(NodeType.EnoughAttr);
|
||||
this.com = com;
|
||||
this.attr = attr;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
export class GoDeathNode extends NodeBase {
|
||||
public waitSeconds: number;
|
||||
public countDown: number;
|
||||
constructor(waitSeconds: number) {
|
||||
super(NodeType.GoDeath);
|
||||
this.waitSeconds = waitSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
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.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);
|
||||
}
|
||||
};
|
||||
|
||||
/** 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(node.child.state == NodeState.Fail) node.state = NodeState.Success;
|
||||
}
|
||||
};
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Wait node */
|
||||
NodeHandlers[NodeType.WalkToPos] = {
|
||||
onEnter(node: WalkToPosNode, context: ExecuteContext) : void {
|
||||
let comTrans = context.world.getComponent(context.entity, ComTransform);
|
||||
let comMovable = context.world.getComponent(context.entity, ComMovable);
|
||||
comMovable.pointIdx = 0;
|
||||
comMovable.points.length = 0;
|
||||
comMovable.points.push(cc.v2(comTrans.x, comTrans.y), node.targetPos);
|
||||
comMovable.speed = node.speed;
|
||||
comMovable.speedDirty = true;
|
||||
node.state = NodeState.Executing;
|
||||
comMovable.running = false;
|
||||
},
|
||||
onUpdate(node: WalkToPosNode, context: ExecuteContext) : void {
|
||||
if(node.state !== NodeState.Executing) return ;
|
||||
let comMovable = context.world.getComponent(context.entity, ComMovable);
|
||||
if(comMovable.points.length == 0 || comMovable.pointIdx < 0 || comMovable.pointIdx >= comMovable.points.length) {
|
||||
node.state = BT.NodeState.Success;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** WalkToRandomPos node */
|
||||
NodeHandlers[NodeType.WalkToRandomPos] = {
|
||||
onEnter(node: WalkToRandomPosNode, context: ExecuteContext) : void {
|
||||
let comTrans = context.world.getComponent(context.entity, ComTransform);
|
||||
let comMovable = context.world.getComponent(context.entity, ComMovable);
|
||||
comMovable.pointIdx = 0;
|
||||
comMovable.points.length = 0;
|
||||
let targetX = node.size.width * Math.random() - node.size.width/2;
|
||||
let targetY = node.size.height * Math.random() - node.size.height/2;
|
||||
comMovable.points.push(cc.v2(comTrans.x, comTrans.y), cc.v2(targetX, targetY));
|
||||
comMovable.speed = node.speed;
|
||||
comMovable.speedDirty = true;
|
||||
node.state = NodeState.Executing;
|
||||
comMovable.running = false;
|
||||
},
|
||||
onUpdate(node: WalkToPosNode, context: ExecuteContext) : void {
|
||||
if(node.state !== NodeState.Executing) return ;
|
||||
let comMovable = context.world.getComponent(context.entity, ComMovable);
|
||||
if(comMovable.points.length == 0 || comMovable.pointIdx < 0 || comMovable.pointIdx >= comMovable.points.length) {
|
||||
node.state = BT.NodeState.Success;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** WalkToTarget node */
|
||||
NodeHandlers[NodeType.WalkToTarget] = {
|
||||
onEnter(node: WalkToTargetNode, context: ExecuteContext) : void {
|
||||
let comTrans = context.world.getComponent(context.entity, ComTransform);
|
||||
let comMovable = context.world.getComponent(context.entity, ComMovable);
|
||||
let comMonitor = context.world.getComponent(context.entity, ComMonitor);
|
||||
if(comMonitor.others.length <= 0) return ;
|
||||
let target = context.world.getComponent(comMonitor.others[0], ComTransform);
|
||||
let xOffdet = Math.sign(comTrans.x - target.x) * 10;
|
||||
comMovable.pointIdx = 0;
|
||||
comMovable.points.length = 0;
|
||||
comMovable.points.push(cc.v2(comTrans.x, comTrans.y), cc.v2(target.x + xOffdet, target.y));
|
||||
comMovable.speed = node.speed;
|
||||
comMovable.speedDirty = true;
|
||||
node.state = NodeState.Executing;
|
||||
comMovable.running = false;
|
||||
|
||||
},
|
||||
onUpdate(node: WalkToTargetNode, context: ExecuteContext) : void {
|
||||
if(node.state !== NodeState.Executing) return ;
|
||||
let comTrans = context.world.getComponent(context.entity, ComTransform);
|
||||
let comMonitor = context.world.getComponent(context.entity, ComMonitor);
|
||||
let comMovable = context.world.getComponent(context.entity, ComMovable);
|
||||
if(comMovable.points.length == 0 || comMovable.pointIdx < 0 || comMovable.pointIdx >= comMovable.points.length) {
|
||||
node.state = BT.NodeState.Success;
|
||||
return ;
|
||||
}
|
||||
if(comMonitor.others.length <= 0) {
|
||||
node.state = BT.NodeState.Fail;
|
||||
return ;
|
||||
}
|
||||
let target = context.world.getComponent(comMonitor.others[0], ComTransform);
|
||||
let xOffdet = Math.sign(comTrans.x - target.x) * 10;
|
||||
comMovable.points[1].x = target.x + xOffdet;
|
||||
comMovable.points[1].y = target.y;
|
||||
}
|
||||
};
|
||||
|
||||
/** Monitor node */
|
||||
NodeHandlers[NodeType.Monitor] = {
|
||||
onEnter(node: MonitorNode, context: ExecuteContext) : void {
|
||||
let comMonitor = context.world.getComponent(context.entity, ComMonitor);
|
||||
if(!comMonitor) return ;
|
||||
node.state = NodeState.Executing;
|
||||
},
|
||||
onUpdate(node: MonitorNode, context: ExecuteContext) : void {
|
||||
let comMonitor = context.world.getComponent(context.entity, ComMonitor);
|
||||
node.state = comMonitor.others.length > 0 ? BT.NodeState.Success : BT.NodeState.Fail;
|
||||
}
|
||||
};
|
||||
|
||||
/** Monitor node */
|
||||
NodeHandlers[NodeType.Attack] = {
|
||||
onEnter(node: AttackNode, context: ExecuteContext) : void {
|
||||
node.state = NodeState.Executing;
|
||||
|
||||
let comCocosNode = context.world.getComponent(context.entity, ComCocosNode);
|
||||
if(!comCocosNode.loaded) return ;
|
||||
|
||||
comCocosNode.events.push(new (EventAttack));
|
||||
let comAttackable = context.world.getComponent(context.entity, ComAttackable);
|
||||
comAttackable.duration = 1.2;
|
||||
comAttackable.countDown = comAttackable.duration;
|
||||
comAttackable.dirty = true;
|
||||
comAttackable.hurtArea = cc.v2(20, 10);
|
||||
comAttackable.hurtFrame = 0.5;
|
||||
comAttackable.mustAttackFrame = 0.6;
|
||||
comAttackable.attack = 10;
|
||||
},
|
||||
onUpdate(node: AttackNode, context: ExecuteContext) : void {
|
||||
if(node.state !== NodeState.Executing) return ;
|
||||
let comAttackable = context.world.getComponent(context.entity, ComAttackable);
|
||||
|
||||
if(comAttackable.countDown <= 0) {
|
||||
node.state = NodeState.Success;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** EnoughAttr node */
|
||||
NodeHandlers[NodeType.EnoughAttr] = {
|
||||
onEnter(node: EnoughAttrNode, context: ExecuteContext) : void {
|
||||
let com = context.world.getComponent(context.entity, node.com);
|
||||
if(!com) return ;
|
||||
node.state = NodeState.Executing;
|
||||
},
|
||||
onUpdate(node: EnoughAttrNode, context: ExecuteContext) : void {
|
||||
let com = context.world.getComponent(context.entity, node.com);
|
||||
if(!com) return ;
|
||||
node.state = com[node.attr] >= node.value ? NodeState.Success : NodeState.Fail;
|
||||
}
|
||||
};
|
||||
|
||||
/** GoDeath node */
|
||||
NodeHandlers[NodeType.GoDeath] = {
|
||||
onEnter(node: GoDeathNode, context: ExecuteContext) : void {
|
||||
node.countDown = node.waitSeconds;
|
||||
node.state = NodeState.Executing;
|
||||
},
|
||||
onUpdate(node: GoDeathNode, context: ExecuteContext) : void {
|
||||
if(node.countDown <= 0) {
|
||||
node.state = NodeState.Success;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
10
assets/Script/Common/BehaviorTree.ts.meta
Normal file
10
assets/Script/Common/BehaviorTree.ts.meta
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ver": "1.1.0",
|
||||
"uuid": "6cd218b5-3651-451d-90b5-5765b038f824",
|
||||
"importer": "typescript",
|
||||
"isPlugin": false,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
"loadPluginInEditor": false,
|
||||
"subMetas": {}
|
||||
}
|
||||
297
assets/Script/Common/CocosHelper.ts
Normal file
297
assets/Script/Common/CocosHelper.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
export class LoadProgress {
|
||||
public url: string;
|
||||
public completedCount: number;
|
||||
public totalCount: number;
|
||||
public item: any;
|
||||
public cb?: Function;
|
||||
}
|
||||
|
||||
/** 一些cocos api 的封装, promise函数统一加上sync后缀 */
|
||||
export default class CocosHelper {
|
||||
|
||||
public static async callInNextTick() {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, 0);
|
||||
})
|
||||
}
|
||||
|
||||
/** 加载进度 */
|
||||
public static loadProgress = new LoadProgress();
|
||||
|
||||
/** 等待时间, 秒为单位 */
|
||||
public static sleepSync = function(dur: number): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
cc.Canvas.instance.scheduleOnce(() => {
|
||||
resolve(true);
|
||||
}, dur);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param target
|
||||
* @param repeat -1,表示永久执行
|
||||
* @param tweens
|
||||
*/
|
||||
public static async runRepeatTweenSync(target: any, repeat: number, ...tweens: cc.Tween[]) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let selfTween = cc.tween(target);
|
||||
for(const tmpTween of tweens) {
|
||||
selfTween = selfTween.then(tmpTween);
|
||||
}
|
||||
if(repeat < 0) {
|
||||
cc.tween(target).repeatForever(selfTween).start();
|
||||
}else {
|
||||
cc.tween(target).repeat(repeat, selfTween).call(() => {
|
||||
resolve(true);
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
/** 同步的tween */
|
||||
public static async runTweenSync(target: any, ...tweens: cc.Tween[]): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let selfTween = cc.tween(target);
|
||||
for(const tmpTween of tweens) {
|
||||
selfTween = selfTween.then(tmpTween);
|
||||
}
|
||||
selfTween.call(() => {
|
||||
resolve();
|
||||
}).start();
|
||||
});
|
||||
}
|
||||
/** 停止tween */
|
||||
public stopTween(target: any) {
|
||||
cc.Tween.stopAllByTarget(target);
|
||||
}
|
||||
public stopTweenByTag(tag: number) {
|
||||
cc.Tween.stopAllByTag(tag);
|
||||
}
|
||||
/** 同步的动作, 在2.4.x action已经被废弃了, 不建议使用 */
|
||||
public static async runActionSync(node: cc.Node, ...actions: cc.FiniteTimeAction[]) {
|
||||
if(!actions || actions.length <= 0) return ;
|
||||
return new Promise((resolve, reject) => {
|
||||
actions.push(cc.callFunc(() => {
|
||||
resolve(true);
|
||||
}));
|
||||
node.runAction(cc.sequence(actions));
|
||||
});
|
||||
}
|
||||
|
||||
/** 同步的动画 */
|
||||
public static async runAnimSync(node: cc.Node, animName?: string | number) {
|
||||
let anim = node.getComponent(cc.Animation);
|
||||
if(!anim) return ;
|
||||
let clip: cc.AnimationClip = null;
|
||||
if(!animName) clip = anim.defaultClip;
|
||||
else {
|
||||
let clips = anim.getClips();
|
||||
if(typeof(animName) === "number") {
|
||||
clip = clips[animName];
|
||||
}else if(typeof(animName) === "string") {
|
||||
for(let i=0; i<clips.length; i++) {
|
||||
if(clips[i].name === animName) {
|
||||
clip = clips[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!clip) return ;
|
||||
await CocosHelper.sleepSync(clip.duration);
|
||||
}
|
||||
|
||||
/** 加载资源异常时抛出错误 */
|
||||
public static loadResThrowErrorSync<T>(url: string, type: typeof cc.Asset, onProgress?: (completedCount: number, totalCount: number, item: any) => void): Promise<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static _loadingMap: {[key: string]: Function[]} = {};
|
||||
public static loadRes<T>(url: string, type: typeof cc.Asset, callback: Function ) {
|
||||
if(this._loadingMap[url]) {
|
||||
this._loadingMap[url].push(callback);
|
||||
return ;
|
||||
}
|
||||
this._loadingMap[url] = [callback];
|
||||
this.loadResSync<T>(url, type).then((data: any) => {
|
||||
let arr = this._loadingMap[url];
|
||||
for(const func of arr) {
|
||||
func(data);
|
||||
}
|
||||
this._loadingMap[url] = null;
|
||||
delete this._loadingMap[url];
|
||||
});
|
||||
}
|
||||
/** 加载资源 */
|
||||
public static loadResSync<T>(url: string, type: typeof cc.Asset, onProgress?: (completedCount: number, totalCount: number, item: any) => void): Promise<T>{
|
||||
return new Promise((resolve, reject) => {
|
||||
if(!onProgress) onProgress = this._onProgress;
|
||||
cc.resources.load(url, type, onProgress, (err, asset: any) => {
|
||||
if (err) {
|
||||
cc.error(`${url} [资源加载] 错误 ${err}`);
|
||||
resolve(null);
|
||||
}else {
|
||||
resolve(asset);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 加载进度
|
||||
* cb方法 其实目的是可以将loader方法的progress
|
||||
*/
|
||||
private static _onProgress(completedCount: number, totalCount: number, item: any) {
|
||||
CocosHelper.loadProgress.completedCount = completedCount;
|
||||
CocosHelper.loadProgress.totalCount = totalCount;
|
||||
CocosHelper.loadProgress.item = item;
|
||||
CocosHelper.loadProgress.cb && CocosHelper.loadProgress.cb(completedCount, totalCount, item);
|
||||
}
|
||||
/**
|
||||
* 寻找子节点
|
||||
*/
|
||||
public static findChildInNode(nodeName: string, rootNode: cc.Node): cc.Node {
|
||||
if(rootNode.name == nodeName) {
|
||||
return rootNode;
|
||||
}
|
||||
for(let i=0; i<rootNode.childrenCount; i++) {
|
||||
let node = this.findChildInNode(nodeName, rootNode.children[i]);
|
||||
if(node) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 获得Component的类名 */
|
||||
public static getComponentName(com: Function) {
|
||||
let arr = com.name.match(/<.*>$/);
|
||||
if(arr && arr.length > 0) {
|
||||
return arr[0].slice(1, -1);
|
||||
}
|
||||
return com.name;
|
||||
}
|
||||
/** 加载bundle */
|
||||
public static loadBundleSync(url: string, options: any): Promise<cc.AssetManager.Bundle> {
|
||||
return new Promise((resolve, reject) => {
|
||||
cc.assetManager.loadBundle(url, options, (err: Error, bundle: cc.AssetManager.Bundle) => {
|
||||
if(!err) {
|
||||
cc.error(`加载bundle失败, url: ${url}, err:${err}`);
|
||||
resolve(null);
|
||||
}else {
|
||||
resolve(bundle);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 路径是相对分包文件夹路径的相对路径 */
|
||||
public static loadAssetFromBundleSync(bundleName: string, url: string) {
|
||||
let bundle = cc.assetManager.getBundle(bundleName);
|
||||
if(!bundle) {
|
||||
cc.error(`加载bundle中的资源失败, 未找到bundle, bundleUrl:${bundleName}`);
|
||||
return null;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
bundle.load(url, (err, asset: cc.Asset | cc.Asset[]) => {
|
||||
if(err) {
|
||||
cc.error(`加载bundle中的资源失败, 未找到asset, url:${url}, err:${err}`);
|
||||
resolve(null);
|
||||
}else {
|
||||
resolve(asset);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 通过路径加载资源, 如果这个资源在bundle内, 会先加载bundle, 在解开bundle获得对应的资源 */
|
||||
public static loadAssetSync(url: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
cc.resources.load(url, (err, assets: cc.Asset | cc.Asset[]) => {
|
||||
if(!err) {
|
||||
cc.error(`加载asset失败, url:${url}, err: ${err}`);
|
||||
resolve(null);
|
||||
}else {
|
||||
this.addRef(assets);
|
||||
resolve(assets);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/** 释放资源 */
|
||||
public static releaseAsset(assets: cc.Asset | cc.Asset[]) {
|
||||
this.decRes(assets);
|
||||
}
|
||||
/** 增加引用计数 */
|
||||
private static addRef(assets: cc.Asset | cc.Asset[]) {
|
||||
if(assets instanceof Array) {
|
||||
for(const a of assets) {
|
||||
a.addRef();
|
||||
}
|
||||
}else {
|
||||
assets.addRef();
|
||||
}
|
||||
}
|
||||
/** 减少引用计数, 当引用计数减少到0时,会自动销毁 */
|
||||
private static decRes(assets: cc.Asset | cc.Asset[]) {
|
||||
if(assets instanceof Array) {
|
||||
for(const a of assets) {
|
||||
a.decRef();
|
||||
}
|
||||
}else {
|
||||
assets.decRef();
|
||||
}
|
||||
}
|
||||
|
||||
/** 截图 */
|
||||
public static captureScreen(camera: cc.Camera, prop?: cc.Node | cc.Rect) {
|
||||
let newTexture = new cc.RenderTexture();
|
||||
let oldTexture = camera.targetTexture;
|
||||
let rect: cc.Rect = cc.rect(0, 0, cc.visibleRect.width, cc.visibleRect.height);
|
||||
if(prop) {
|
||||
if(prop instanceof cc.Node) {
|
||||
rect = prop.getBoundingBoxToWorld();
|
||||
}else {
|
||||
rect = prop;
|
||||
}
|
||||
}
|
||||
newTexture.initWithSize(cc.visibleRect.width, cc.visibleRect.height, cc.game['_renderContext'].STENCIL_INDEX8);
|
||||
camera.targetTexture = newTexture;
|
||||
camera.render();
|
||||
camera.targetTexture = oldTexture;
|
||||
|
||||
let buffer = new ArrayBuffer(rect.width * rect.height * 4);
|
||||
let data = new Uint8Array(buffer);
|
||||
newTexture.readPixels(data, rect.x, rect.y, rect.width, rect.height);
|
||||
return data;
|
||||
}
|
||||
|
||||
public static tweenFloat(from: number, to: number, duration: number, onUpdate: (t: number) => void, onComplete?: Function, autoStart: boolean = true) {
|
||||
let o: Record<string, number> = { _value: from };
|
||||
Object.defineProperty(o, 'value', {
|
||||
get: () => o._value,
|
||||
set: (v: number) => { o._value = v; onUpdate && onUpdate(o._value); },
|
||||
});
|
||||
let tween = cc.tween(o).to(duration, { value: to }).call(onComplete);
|
||||
if (autoStart) {
|
||||
tween.start();
|
||||
}
|
||||
return tween;
|
||||
}
|
||||
|
||||
public static tweenVec2(from: cc.Vec2, to: cc.Vec2, duration: number, onUpdate: (t: cc.Vec2) => void, onComplete?: Function, autoStart: boolean = true) {
|
||||
let o: Record<string, cc.Vec2> = {_value: from};
|
||||
Object.defineProperty(o, 'value', {
|
||||
get: () => o._value,
|
||||
set: (v: cc.Vec2) => { o._value = v; onUpdate && onUpdate(o._value); },
|
||||
});
|
||||
let tween = cc.tween(o).to(duration, { value: to }).call(onComplete);
|
||||
if (autoStart) {
|
||||
tween.start();
|
||||
}
|
||||
return tween;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
10
assets/Script/Common/CocosHelper.ts.meta
Normal file
10
assets/Script/Common/CocosHelper.ts.meta
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ver": "1.1.0",
|
||||
"uuid": "dc730152-9513-4cfe-b6f9-8d6cfdfc0e23",
|
||||
"importer": "typescript",
|
||||
"isPlugin": false,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
"loadPluginInEditor": false,
|
||||
"subMetas": {}
|
||||
}
|
||||
77
assets/Script/Common/FrameAnimation.ts
Normal file
77
assets/Script/Common/FrameAnimation.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
const {ccclass, property} = cc._decorator;
|
||||
|
||||
@ccclass('FrameConfig')
|
||||
class FrameConfig {
|
||||
@property(cc.Integer) frames: number = 0; //
|
||||
@property(cc.Integer) frameInterval = 1; // 帧数
|
||||
|
||||
offsetFrame = 0; // 偏移量
|
||||
}
|
||||
|
||||
@ccclass
|
||||
export default class FrameAnimation extends cc.Component {
|
||||
|
||||
@property(cc.Sprite) sprite: cc.Sprite = null;
|
||||
@property(cc.SpriteAtlas) spriteAtlas: cc.SpriteAtlas = null;
|
||||
@property(FrameConfig) frameConfigs: FrameConfig[] = [];
|
||||
|
||||
@property(cc.Boolean) playOnLoad = false;
|
||||
@property(cc.Boolean) loop = false;
|
||||
@property(cc.Integer) defaultConfig = 0;
|
||||
|
||||
private _passInterval = 0;
|
||||
private _currFrame = 0;
|
||||
private _currFrameConfig: FrameConfig = null;
|
||||
private _playing = false;
|
||||
private _loop = false;
|
||||
private _callback: Function;
|
||||
|
||||
start () {
|
||||
if(!this.sprite) this.sprite = this.getComponent(cc.Sprite);
|
||||
|
||||
let offset = 0;
|
||||
for(const config of this.frameConfigs) {
|
||||
config.offsetFrame += offset;
|
||||
offset += config.frames;
|
||||
}
|
||||
|
||||
this._loop = this.loop;
|
||||
|
||||
if(this.playOnLoad) {
|
||||
this._currFrameConfig = this.frameConfigs[this.defaultConfig];
|
||||
this._playing = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
play(configIdx: number, loop: boolean, callback?: Function) {
|
||||
this._currFrameConfig = this.frameConfigs[configIdx];
|
||||
this._loop = loop;
|
||||
this._currFrame = 0;
|
||||
this._playing = true;
|
||||
this._callback = callback;
|
||||
}
|
||||
|
||||
stop() {
|
||||
this._playing = false;
|
||||
}
|
||||
|
||||
update (dt: number) {
|
||||
if(!this._playing) return ;
|
||||
|
||||
this._passInterval ++;
|
||||
if(this._passInterval < this._currFrameConfig.frameInterval) return ;
|
||||
this._passInterval = 0;
|
||||
|
||||
this.sprite.spriteFrame = this.spriteAtlas.getSpriteFrames()[this._currFrameConfig.offsetFrame + this._currFrame];
|
||||
this._currFrame ++;
|
||||
|
||||
if(this._currFrame < this._currFrameConfig.frames) return ;
|
||||
|
||||
if(this._loop) this._currFrame = 0;
|
||||
else {
|
||||
this._playing = false;
|
||||
this._callback && this._callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
assets/Script/Common/FrameAnimation.ts.meta
Normal file
10
assets/Script/Common/FrameAnimation.ts.meta
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ver": "1.1.0",
|
||||
"uuid": "60f21b10-95a9-4650-83ae-ff5522171c04",
|
||||
"importer": "typescript",
|
||||
"isPlugin": false,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
"loadPluginInEditor": false,
|
||||
"subMetas": {}
|
||||
}
|
||||
Reference in New Issue
Block a user