cocos creator中使用行为树(BehaviorTree) 一
前提:
- 本文使用的cocos creator版本是1.10.2, 由于creator每个版本都有一些兼容性的坑, 所以还请大家与我的版本保持一致.
- 本文全部使用Typescript编写.
- 我也是第一次使用行为树制作游戏ai, 本文是自己学习的, 写的不对的, 望大家海涵!
首先感谢论坛大佬提供的行为树可视化制作插件, 附链接.
开始编写... 啦啦啦
行为树可视化制作插件的使用我就不讲了, 论坛讲的很清楚, 但是有一点很重要, 我使用1.10.2版本按照论坛的方法操作会报错,
解决方法: 打开b3core.0.1.0module.js文件, 找到var Composite = b3.class(b3.z), 将其改为var Composite =b3.Class(b3.BaseNode);
问题解决
那么我们开始行为树第一个AI, 称为monsterSimple. 他的功能是 一旦敌人靠近到一定范围, 立即发出警告!
打开cocos creator, 按照论坛的方法操作后, 我们拥有了
这两个脚本我们之后在说, 先把player(我们控制的对象)搞出来
建立一个新场景,取名GameScene..
创建结点player, 红色那个turn表示玩家当前的朝向,
创建控制脚本PlayerCtl
import GameScene from "./GameSCene"
const {ccclass, property} = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {
direction = 0; // 当前状态 -1表示向左, 1表示向用, 0表示不动
faceTo = 1; // 当前面向
speed = 100;
gameCtl: GameScene = null;
// onLoad () {}
start () {
}
init(gameCtl: GameScene) {
this.gameCtl = gameCtl;
}
/**
* 设置玩家方向
* @param dir
*/
setDirection(dir: number) {
this.direction = dir;
this.faceTo = dir == 0 ? this.faceTo : dir;
this.node.scaleX = this.faceTo;
}
/**
* 更新玩家位置
* @param dt
*/
playerUpdate(dt: number) {
if(this.direction == 0) {
return ;
}
this.node.x += this.direction * dt * this.speed;
}
}
就是一些简单的设置, 简单的控制player结点左右移动
另附上GameScene
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) // 到编辑器, 把player结点拖过来, 当然要先把playerCtl绑在结点上
playerCtl: PlayerCtl = null;
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);
this.runBehaviorTree();
}
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) {
// this.runBehaviorTree();
this.playerCtl.playerUpdate(dt);
}
}
浏览器运行, 现在就可以通过左右键(或者a/s键)控制玩家左右移动.
那么现在开始进入正题
建立一个monster结点
把BehaviorTree脚本挂在该结点上
点击编辑, 打开行为树编辑工具
恩, 一个简单的树搞定, 简单说一下这个树的工作流程, 单纯是自己理解的, 可能不对
每次行为开始, 即被调用的该行为树(BehaviorTree)(我们这就是指monster上挂的BehaviorTree脚本)的tick方法
先进入root(这个并不是必要的), 在进入(Look)结点(顺序由上到下), 在Look结点判断玩家(player)是否进入monster的监视范围内
如果进入了, 那么return SUCCESS, 否则return FAILURE, 如果是SUCCESS, 那么继续向下执行, 进入Attack结点, 执行对应的攻击逻辑
大概就是这么一个流程, 开始敲
新建Look脚本, Attack脚本
const {ccclass, property} = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {
// LIFE-CYCLE CALLBACKS:
// onLoad () {}
start () {
}
enter (tick,b3,treeNode){
console.log("enter");
}
open (tick,b3,treeNode){
console.log("open");
}
tick (tick,b3,treeNode){
console.log("tick");
}
close (tick,b3,treeNode){
console.log("close");
}
exit (tick,b3,treeNode){
console.log("exit");
}
// update (dt) {}
}
两个脚本是一样的, 这里就放了一个
对了, 我们还没编辑Look 的监视范围, 在打开可视化编辑工具
点击Look结点
添加一个参数, 名字是radio, 值为15, 这个15就是监视范围, 具体数值我们之后在调整
保存之后, 回到代码编辑页面
因为我们需要实时的知道玩家的位置, 用于确认玩家和敌人的距离(我们这里只计算x轴)
在playerCtl中添加getPosition方法
getPosition() {
return this.node.position;
}
现在就是让我们在Look脚本中获取玩家位置了, 但是我还没想好怎么方便的获取, 这个留到下次, 这次我们用个垃圾方法
在Look脚本中
@property(PlayerCtl) // 获得玩家
playerCtl: PlayerCtl = null;
修改tick方法
tick (tick,b3,treeNode){
let radio = treeNode.parameter.radio;
let playerX = this.playerCtl.getPosition().x;
if(Math.abs(playerX - this.node.x) < radio) {
this.setTips("发现你啦!");
return b3.SUCCESS;
}else {
this.setTips("敌人");
return b3.FAILURE;
}
}
setTips(str: string) {
this.node.getChildByName("str").getComponent(cc.Label).string = str;
}
恩, 刚刚测试了一下, 监视范围设置15太小了, 我改成200了, 不改也无所谓, 提一下
回到GameScene中
现在的问题就是如何让敌人不断的思考, 以及设置敌人思考的时间间隔
添加方法runBehaviorTree
runBehaviorTree() {
this.master.getComponent("BehaviorTree").tick();
}
在update中调用该方法
update(dt: number) {
this.runBehaviorTree();
this.playerCtl.playerUpdate(dt);
}
那么现在是每过一帧, 思考一次
全部写完, 看看效果