cocos creator中使用行为树(BehaviorTree) 三
这一节我们不对AI添加新的功能, 而是修改一下以前的代码.
因为本节修改了上两节的代码, 也为了后面的内容大家方便, 我把这个项目上传到了github上
https://github.com/kirikayakazuto/BehaviorTreeGame
- 添加思考时间间隔参数, 表示没过多少时间思考一次
- 在tick上添加参数(dt: number), 表示距离上一次调用的时间, 用来做平滑处理, 方便帧同步
在第一节上, 我们设置的思考时间是每一帧思考一次, 如果1s内跑了60帧的话, 那么就是思考60次, 然而大部分时间我们并不需要如此频繁的思考调用, 所有我们添加一个思考时间间隔
打开GameScene脚本, 添加
一个属性stepInterval = 60; 单位ms 每过60ms思考一次
import PlayerCtl from "./PlayerCtl";
import { PlayerDir } from "./GameInterface";
const {ccclass, property} = cc._decorator;
/**
* 一个简单的样例, 用于学习行为树AI
* 怪物1, 默认在一段位置内巡逻, 一旦发现目标(玩家), 则发动攻击
*/
@ccclass
export default class GameScene extends cc.Component {
@property(PlayerCtl)
playerCtl: PlayerCtl = null;
@property(cc.Node)
master: cc.Node = null;
stepInterval = 60; // 思考间隔 单位ms
restRunningSecond = 0; // 当前执行的时间
isRunning = false; // 是否正在执行
onLoad() {
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
}
start () {
this.playerCtl.init(this);
}
onKeyDown(event: any) {
switch(event.keyCode) {
case cc.KEY.left:
case cc.KEY.a:
this.playerCtl.setDirection(PlayerDir.left);
break;
case cc.KEY.right:
case cc.KEY.d:
this.playerCtl.setDirection(PlayerDir.right);
break;
}
}
onKeyUp(event: any) {
switch(event.keyCode) {
case cc.KEY.left:
case cc.KEY.a:
case cc.KEY.right:
case cc.KEY.d:
this.playerCtl.setDirection(PlayerDir.stop);
break;
}
}
onDestroy() {
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
}
update(dt: number) {
let ms = dt;
this.playerCtl.playerUpdate(ms);
if(!this.isRunning) {
this.restRunningSecond = this.stepInterval / 1000;
this.isRunning = true;
}
this.restRunningSecond -= ms;
if(this.restRunningSecond <= 0) {
this.isRunning = false;
this.runBehaviorTree();
}
}
runBehaviorTree() {
this.master.getComponent("BehaviorTree").tick();
}
}
这样, 就降低了思考次数. 接下来, 我们实现功能二
首先我们看到runBeHaviorTree方法, 添加参数ms: number
runBehaviorTree(ms: number) {
this.master.getComponent("BehaviorTree").tick();
}
接下来就是怎么将ms通过BehaviorTree脚本的tick方法传到Attck脚本的tick方法中
打开BehaviorTree脚本, 进行一波源码分析, 找到tick方法
tick: function(target){
let t = {};
if(target != undefined){t = target;}
this.tree.tick(t,this.blackboard)
}
tick方法接收一个参数target对象, 不传就是一个空对象{}, 暂时不知道t有什么用,
方法内部是调用了 this.tree.tick(t. this.blackboard);
又看到 this.tree = new b3.BehaviorTree(); 打开b3, 找到BehaviorTree方法
可以看到BehaviorTree = new b3.Class();
在找到b3.Class方法, 返回的是一个cls方法, 即BehaviorTree是一个有cls为构造函数实例化的对象
在回到BehaviorTree对象的tick方法, 有两个参数target, blackboard, 和我们在this.tree.tick(t,this.blackboard)这里传入的参数对应
我们在看看BehaviorTree的tick方法是怎么实现的
重点代码, 先是创建一个tick对象, 在execute执行这个tick
找到_execute方法
挨个调用, 重点是_tick方法, 在找到tick方法
到这里就全部结束了
我们可以看到, 最终是将tick传出, 所以我门只要把dt属性添加到tick中就好了,
找到tick对象, 添加dt属性
/**
* Initialization method.
*
* @method initialize
* @constructor
**/
p.initialize = function() {
// set by BehaviorTree
this.tree = null;
this.debug = null;
this.target = null;
this.blackboard = null;
// updated during the tick signal
this._openNodes = [];
this._nodeCount = 0;
this.dt = 0;
}
那么在BehaviorTree的tick方法中
添加好dt
在到BehaviorTree脚本中, 添加上dt
在到GameScene中的runBehaviorTree方法中
测试一下
成功在attack脚本中获得到了dt
把dt加上, 恩 大功告成!