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);
}
那麼現在是每過一幀, 思考一次
全部寫完, 看看效果