This commit is contained in:
PC-20230316NUNE\Administrator
2023-10-23 18:56:01 +08:00
parent ac94959a45
commit 77d44ee300
328 changed files with 16429 additions and 2 deletions
@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "20169a05-4939-4f5c-8cc5-cb46d02353a2",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,16 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-07 14:12:09
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-25 09:47:07
* @Description: 用于在 Inspector 中显示编辑按钮
*/
import { _decorator, Component } from "cc";
const {ccclass, disallowMultiple} = _decorator;
@ccclass("BehaviorButton")
@disallowMultiple
export default class BehaviorButton extends Component {
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "57329531-3fd5-4e01-af0c-7224cf8483a6",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,71 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-07 14:12:09
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-26 10:51:23
* @Description: 行为树日志输出选项
*/
import { _decorator, Component } from "cc";
const { ccclass, property, requireComponent, disallowMultiple } = _decorator;
import { IBehaviorLogOptions } from "../core/behavior/behavior-tree-interface";
import BehaviorButton from "./BehaviorButton";
export const DefaultLogOptions: IBehaviorLogOptions = {
logAbort: true,
logInterrupt: true,
logExecute: true,
logUpdate: false,
logEnter: false,
logExit: false,
logEnable: false,
logDisable: false,
logLoad: false,
logDestroy: false,
}
@ccclass("BehaviorLogOptions")
@requireComponent(BehaviorButton)
@disallowMultiple
export default class BehaviorLogOptions extends Component implements IBehaviorLogOptions {
@property({
tooltip: "当任务被中止时是否打印日志",
})
logAbort: boolean = true;
@property({
tooltip: "当中断产生时是否打印日志",
})
logInterrupt: boolean = true;
@property
logExecute: boolean = true;
@property
logUpdate: boolean = false;
@property
logLoad: boolean = false;
@property
logDestroy: boolean = false;
@property
logEnter: boolean = false;
@property
logExit: boolean = false;
@property
logEnable: boolean = false;
@property
logDisable: boolean = false;
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "0047be34-4c09-4525-a687-e428f9a9b00f",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,190 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:50:54
* @LastEditors: OreoWang
* @LastEditTime: 2022-07-15 09:30:18
* @Description:
*/
import { game, _decorator } from "cc";
import * as core from "../core/main";
import { BehaviorStatus } from "../core/main";
const logger = console;
export default class BehaviorManager {
/** 运行 */
running: Set<core.IBehaviorTree> = new Set();
/** 挂起 */
suspend: Set<core.IBehaviorTree> = new Set();
/**
* 行为树统一帧率
* 默认为 cc.game.frameRate
*/
private frameRate = 0;
/** 期望帧率对应的每帧时间(以 s 为单位) */
private frameTime = 0;
/** 每帧时间增量 */
private deltaTime = 0;
// /** 行为树持续时间(是每帧时间增量叠加后的时间总和) */
// private duration = 0;
// /** 行为树 tick 次数 */
// private ticks = 0;
protected static _instance: BehaviorManager = null;
static getInstance() {
if (!BehaviorManager._instance) {
BehaviorManager._instance = new BehaviorManager();
}
return BehaviorManager._instance;
}
static deleteInstance() {
BehaviorManager._instance = null;
}
constructor() {
// this.duration = 0
// this.ticks = 0
this.setFrameRate(Number(game.frameRate));
}
getFrameRate(){
return this.frameRate;
}
/**
* 设置行为树执行帧率,但真正的FPS还取决于 cc.game.frameRate
* 注意:行为树的帧率不会比 cc.game.frameRate 大
* @param frameRate
*/
setFrameRate(frameRate: number){
const gRate = Number(game.frameRate);
if(frameRate <= 0 || frameRate > gRate){
logger.warn("Invalid frame rate!");
frameRate = gRate;
}
if(this.frameRate != frameRate){
this.frameRate = frameRate;
this.frameTime = (1000 / this.frameRate) / 1000 - 0.0001;
this.running.forEach(context=>{
if(context.getFrameRate() > this.frameRate){
context.setFrameRate(this.frameRate);
}
})
this.suspend.forEach(context => {
if(context.getFrameRate() > this.frameRate){
context.setFrameRate(this.frameRate);
}
})
}
}
/**
* 将行为树添加到 running 集合中
* @param context
*/
runBehavior(context: core.IBehaviorTree) {
this.resumeBehavior(context);
}
/**
* 将行为树添加到集合中
* @description context.startWhenEnabled 为 true 时,行为树添加到 running 集合,否则添加到 suspend 集合
* @param context
*/
addBehavior(context) {
if (context.startWhenEnabled) {
this.resumeBehavior(context);
}
else {
this.pauseBehavior(context);
}
}
removeBehavior(context: core.IBehaviorTree) {
if (this.running.has(context)) {
this.running.delete(context);
}
if (this.suspend.has(context)) {
this.suspend.delete(context);
}
}
pauseBehavior(context: core.IBehaviorTree) {
if (!this.suspend.has(context)) {
context.onPause();
this.suspend.add(context);
}
if (this.running.has(context)) {
this.running.delete(context);
}
}
resumeBehavior(context: core.IBehaviorTree) {
if (!this.running.has(context)) {
context.onResume();
this.running.add(context);
}
if (this.suspend.has(context)) {
this.suspend.delete(context);
}
}
stopBehavior(context: core.IBehaviorTree) {
if (this.running.has(context)) {
context.onStop();
this.running.delete(context);
}
if (!this.suspend.has(context)) {
this.suspend.add(context);
}
}
/**
* 更新状态
* @param {*} delta 上一次tick时间间隔
*/
tick(delta: number) {
// this.duration += delta
// this.ticks += 1
this.running.forEach(context => {
const status = context.onTick(delta);
if (status != BehaviorStatus.Running) {
if (context.restartWhenComplete) {
context.onRestart();
}
else {
context.onFinished();
this.stopBehavior(context);
}
}
})
}
update(delta: number) {
this.deltaTime += delta;
if(this.deltaTime < this.frameTime){
return;
}
this.tick(delta);
this.deltaTime -= this.frameTime;
}
onEnable() {
this.suspend.forEach(context => {
if (context.isSuspended) {
this.resumeBehavior(context);
}
})
}
onDisable() {
this.running.forEach(context => {
if (context.pauseWhenDisabled) {
this.pauseBehavior(context);
}
})
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "aadd8789-6a9d-43da-aa6a-08a96c22acea",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,356 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-07 14:12:09
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-26 10:51:23
* @Description:
*/
import { _decorator, Component, JsonAsset } from "cc";
import { DEV } from "cc/env";
const { ccclass, property, requireComponent, disallowMultiple } = _decorator;
import * as btNode from "../node/main";
import * as btCore from "../core/main";
import { BehaviorEntity, IBehaviorTree, Blackboard, logger, BehaviorStatus, TTreeAsset, BehaviorNode, BehaviorTask, BehaviorEventTarget, TBehaviorTreeEventInterface } from "../core/main";
import BehaviorLogOptions, { DefaultLogOptions } from "./BehaviorLogOptions";
import BehaviorManager from "./BehaviorManager";
import { game } from "cc";
@ccclass("BehaviorTree")
// @requireComponent(BehaviorButton)
@requireComponent(BehaviorLogOptions)
@disallowMultiple
export default class BehaviorTree extends Component implements IBehaviorTree {
@property({
type: JsonAsset,
tooltip: "绑定行为树编辑器数据资源"
})
jsonAsset: JsonAsset = null;
@property({
tooltip: `设置当前行为树执行帧率,为 0 表示与 BehaviorManager.frameRate 保持一致。
如需统一设置帧率,可以使用 BehaviorManager.getInstance().setFrameRate(rate)。
注意:行为树的帧率不会比 cc.game.frameRate 大`,
min: 0,
step: 1,
})
frameRate = 0;
@property({
tooltip: "节点激活时开始运行"
})
startWhenEnabled = true;
@property({
tooltip: "节点禁用时暂停运行"
})
pauseWhenDisabled = false;
@property({
tooltip: "当一次行为树全部结束时,重新开始执行该行为树"
})
restartWhenComplete = false;
@property({
tooltip: "当重新开始执行行为树时,重置各节点数据"
})
resetValuesOnRestart = false;
@property({
tooltip: "当行为树状态变动时输出日志"
})
logTaskChanges = false;
/**
* 行为树事件委托对象
* 事件类型详见: IBehaviorTreeEventInterface
*/
delegate: BehaviorEventTarget<TBehaviorTreeEventInterface> = new BehaviorEventTarget<TBehaviorTreeEventInterface>();
/** 行为树执行日志粒度控制 */
logOptions: BehaviorLogOptions = null;
/** 行为树持续时间(是每帧时间增量叠加后的时间总和) */
duration = 0;
/** 行为树 tick 总次数 */
ticks = 0;
tickLoggers: Array<string> = [];
lastLoggers: Array<string> = [];
/** 所有节点(包括组合节点、任务节点、装饰器等所有节点) */
allNodes: Array<BehaviorNode> = [];
/** 所有任务节点(key为该节点在行为树编辑器中对应的序号) */
allTasks: Array<BehaviorTask> = [];
/** 行为树使用的黑板变量 */
blackboard: Blackboard = null;
/** 行为树当前状态 */
status: BehaviorStatus = BehaviorStatus.None;
/** 行为树是否已挂起 */
isSuspended: boolean = false;
/** 行为树是否已执行结束 */
isCompleted: boolean = false;
protected _inited = false;
// protected _utils: BehaviorTreeUtils = null;
protected _root: BehaviorEntity = null;
protected get _manager() {
return BehaviorManager.getInstance();
}
onLoad() {
if (!this.jsonAsset?.json) return;
this.reuse();
}
onDestroy() {
this.unuse();
}
unuse(){
if(!this._inited){
return;
}
this._inited = false;
this.status = BehaviorStatus.None;
this.duration = 0;
this.ticks = 0;
this.allNodes.length = 0;
this.allTasks.length = 0;
this.tickLoggers.length = 0;
this.lastLoggers.length = 0;
this.isSuspended = false;
this.isCompleted = false;
if (this._root) {
this._root.destroy();
this._root = null;
}
if (this.blackboard) {
this.blackboard.destroy();
this.blackboard = null;
}
this._manager.removeBehavior(this);
}
reuse(){
this.loadJsonAsset(this.jsonAsset);
}
loadJsonAsset(jsonAsset: JsonAsset){
if(!jsonAsset || !jsonAsset.json) return;
if(this._inited) return;
this._inited = true;
this.setFrameRate(this.frameRate);
this.jsonAsset = jsonAsset;
this.logOptions = this.getComponent(BehaviorLogOptions);
if(!this.logOptions){
this.logOptions = this.addComponent(BehaviorLogOptions);
for (const key in DefaultLogOptions) {
this.logOptions[key] = !!DefaultLogOptions[key];
}
}
const jsonObect = jsonAsset.json;
const json: TTreeAsset = jsonObect as TTreeAsset;
this.blackboard = new Blackboard(this, json.blackboard);
if (this.loadTree(json)) {
this._manager.addBehavior(this);
}
}
private loadTree(tree: TTreeAsset) {
if (!tree?.root) {
logger.error('load failed -- tree is invalid')
return false;
}
this._root = null;
this.allNodes.length = 0;
this.allTasks.length = 0;
this.tickLoggers.length = 0;
this.lastLoggers.length = 0;
let successs = false;
this.delegate.emit("onDeserializeBefore");
// 创建根节点
const options = tree.root.config?.label || {} as btCore.ILabelConfig;
if (options.uuid) {
const instance: BehaviorEntity = btCore.deserializeNode(null, tree.root, this);
if (instance) {
this._root = instance
successs = true;
}
else {
logger.error("Can't find class by uuid: ", options.uuid);
}
}
this.delegate.emit("onDeserializeAfter");
return successs;
}
getFrameRate(){
return this.frameRate;
}
/**
* 设置当前行为树执行帧率,但真正的FPS还取决于 BehaviorManager.frameRate 和 cc.game.frameRate
* 如需统一设置帧率,可以使用 BehaviorManager.getInstance().setFrameRate(rate)
* 注意:行为树的帧率不会比 cc.game.frameRate 大
* @param frameRate
*/
setFrameRate(frameRate: number){
const mRate = this._manager.getFrameRate();
if(frameRate <= 0 || frameRate > mRate){
if(frameRate != 0){
logger.warn(`Invalid frame rate! tree.frameRate=${frameRate}, manage.frameRate=${mRate}, game.frameRate=${game.frameRate}`);
}
frameRate = mRate;
}
this.frameRate = frameRate;
this.frameTime = (1000 / this.frameRate) / 1000 - 0.0001;
}
/** 期望帧率对应的每帧时间(以 s 为单位) */
private frameTime = 0;
/**
* 行为树反序列化每个节点回调
* @param node
*/
onDeserialize(node: BehaviorNode) {
this.allNodes.push(node);
if(node instanceof btNode.Task){
/** 开发环境下,检查任务节点的 tag 是否有重复。一般来说,tag 都是唯一的 */
if(DEV){
const {tag, order} = node.nodeConfig;
if(!!tag){
let temp = this.getTask(tag);
if(temp){
console.warn(`The node has duplicate tags. tag:${tag}, orders:[${temp.nodeConfig.order}, ${order}]`)
}
}
}
this.allTasks.push(node);
}
this.delegate.emit("onDeserialize", node);
}
/**
* 行为树根节点
* @returns
*/
getRoot(): btCore.BehaviorEntity | null{
return this._root;
}
/**
* 根据指定的 tag 获取某个任务
* @param tag string
* @returns
*/
getTask(tag: string): btNode.Task | null{
let node = this.allTasks.find(v=>v.nodeConfig.tag == tag);
return node;
}
/**
* 根据任务在行为树中的序号获取某个任务
* @param order number
* @returns
*/
getTaskByOrder(order: number): btNode.Task | null{
let node = this.allTasks.find(v=>v.nodeConfig.order == order);
return node;
}
/** 行为树组件被附加到的 cc.Node 节点 */
getTargetRoot() {
return this.node;
}
/**
* 根据子节点路径获取 cc.Node 节点
* @param path
* @returns
*/
getTargetByPath(path: string){
return btCore.getTargetByPath(this.node, path);
}
/** 某个类型的 log 是否启用 */
isLogEnabled(key: btCore.TBehaviorLogKey): boolean {
return this.logOptions[key];
}
private _isLogTaskChanged: boolean = false;
onHandleTreeLog(msg: string){
if(this.logTaskChanges){
if(!this._isLogTaskChanged){
let length = this.tickLoggers.length;
if(this.lastLoggers.length > length){
let temp = this.lastLoggers[length];
if(temp != msg){
this._isLogTaskChanged = true;
}
}
else{
this._isLogTaskChanged = true;
}
}
this.tickLoggers.push(msg);
}
}
onTick(delta: number) {
if (!this._root) {
logger.error('tick failed -- root is null')
return BehaviorStatus.None;
}
this.duration += delta;
this.ticks += 1;
this.tickLoggers.length = 0;
this._isLogTaskChanged = false;
this.status = this._root.execute();
if(this._isLogTaskChanged){
let msg = `[ BehaviorTree - <${this._root.nodeTitle}> onTick(${this.ticks}) : status = ${BehaviorStatus[this.status]} ]\n` + this.tickLoggers.join("\n");
logger.log(msg);
this.lastLoggers = this.tickLoggers;
this.tickLoggers = [];
}
// this.deltaTime -= this.frameTime;
return this.status;
}
onFinished() {
this.isCompleted = true;
}
onRestart() {
this.isCompleted = false;
if (this.resetValuesOnRestart) {
this._root.reset();
}
}
onPause() {
this.isSuspended = true;
};
onResume() {
this.isSuspended = false;
};
onStop() {
this.isSuspended = false;
this.isCompleted = false;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "e021fcd1-5fdd-4265-b7e2-b1677d99aa04",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "b984fcf4-64af-453f-b850-c2a4a8c63935",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "1ccd7aaf-8fa6-4e7d-9f9f-f1f3efa54f09",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "896d1aa0-bf42-4a94-a570-b3d697ce5c90",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "9a75f94e-4879-4193-bf1f-068858fa88e6",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "b69ca0bc-cd0a-4eb4-a933-16ab1e9e100c",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "a8421e4d-2f1b-4682-abae-ac35a211c620",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,107 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:43:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-03-22 18:11:37
* @Description:
*/
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:59:27
* @LastEditors: OreoWang
* @LastEditTime: 2022-03-22 14:41:21
* @Description:
*/
import { __private } from "cc";
// import * as _ from "../index";
declare module "cc" {
namespace Node {
namespace Behavior {
/**
* @en Declare a standard class as a CCClass, please refer to the [document](https://docs.cocos.com/creator3d/manual/zh/scripting/ccclass.html)
* @zh 将标准写法的类声明为 CC 类,具体用法请参阅[类型定义](https://docs.cocos.com/creator3d/manual/zh/scripting/ccclass.html)。
* @param name - The class name used for serialization.
* @example
* ```ts
* import { _decorator, Component } from 'cc';
* const {ccclass} = _decorator;
*
* // define a CCClass, omit the name
* @ccclass
* class NewScript extends Component {
* // ...
* }
*
* // define a CCClass with a name
* @ccclass('LoginData')
* class LoginData {
* // ...
* }
* ```
*/
export const btclass: ((name?: string) => ClassDecorator) & ClassDecorator;
/**
* @en Declare as a CCClass property with options
* @zh 声明属性为 CCClass 属性。
* @param options property options
*/
export function property(options?: __private.cocos_core_data_decorators_property_IPropertyOptions): __private.cocos_core_data_decorators_utils_LegacyPropertyDecorator;
/**
* @en Declare as a CCClass property with the property type
* @zh 标注属性为 cc 属性。<br/>
* 等价于`@property({type})`。
* @param type A {{ccclass}} type or a {{ValueType}}
*/
export function property(type: __private.cocos_core_data_decorators_property_PropertyType): __private.cocos_core_data_decorators_utils_LegacyPropertyDecorator;
/**
* @en Declare as a CCClass property
* @zh 标注属性为 cc 属性。<br/>
* 等价于`@property()`。
*/
export function property(...args: Parameters<__private.cocos_core_data_decorators_utils_LegacyPropertyDecorator>): void;
export enum BehaviorStatus {
Idle = -1,
Failure = 0,
Success = 1,
Running = 2
}
type ValueOf<T> = T[keyof T];
type TBehaviorStatus = ValueOf<typeof BehaviorStatus>;
export class Manager {
constructor (data?: any);
load(tree);
tick(delta: number);
reset();
}
export class BehaviorNode {
constructor (parent, config, context);
update(delta: number): TBehaviorStatus
}
export class Decorator extends BehaviorNode {
update(delta: number): TBehaviorStatus
}
export class Service extends BehaviorNode {
update(delta: number): TBehaviorStatus
}
export class Task extends BehaviorNode {
update(delta: number): TBehaviorStatus
}
export class Composite extends BehaviorNode {
update(delta: number): TBehaviorStatus
}
}
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "94a8a794-69d1-43f7-9645-587339155ebb",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "45a0138c-34cd-4cda-9adc-72a79ae5b000",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,56 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-07-18 11:55:37
* @Description: 组合节点
* https://docs.unrealengine.com/4.26/zh-CN/InteractiveExperiences/ArtificialIntelligence/BehaviorTrees/BehaviorTreeNodeReference/BehaviorTreeNodeReferenceComposites/
*/
import { BehaviorEntity } from "./behavior-node-entity";
import { BehaviorStatus } from "./behavior-status";
export class BehaviorComposite extends BehaviorEntity {
isComposite = true;
lastRunning = -1;
constructor(parent, config, context) {
super(parent, config, context)
this.isComposite = true
this.lastRunning = -1 // 上一次running的索引
}
public getLogSymbol(){
return "comp --"
}
abort(){
if(this.lastRunning != -1){
let child = this.children[this.lastRunning];
this.lastRunning = -1;
if(child){
child.abort();
}
}
return super.abort();
}
reset() {
super.reset()
this.lastRunning = -1;
}
disable(){
if(this.lastRunning != -1){
let child = this.children[this.lastRunning];
if(child){
child.status = this.status;
child.disable();
}
}
super.disable();
this.lastRunning = -1;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "fc7c1aca-767a-4f0e-b044-c68525175231",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,20 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-22 21:02:40
* @Description: 条件装饰器
* https://docs.unrealengine.com/4.26/zh-CN/InteractiveExperiences/ArtificialIntelligence/BehaviorTrees/BehaviorTreeNodeReference/BehaviorTreeNodeReferenceDecorators/
*/
import { BehaviorDecorator } from "./behavior-decorator";
import { BehaviorStatus } from "./behavior-status"
export class BehaviorConditional extends BehaviorDecorator{
isCondition = true;
public getLogSymbol(){
return "cond &?"
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "c3b96b68-f4a2-4842-9387-54e37444820a",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,20 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-22 12:56:35
* @Description: 装饰器
* https://docs.unrealengine.com/4.26/zh-CN/InteractiveExperiences/ArtificialIntelligence/BehaviorTrees/BehaviorTreeNodeReference/BehaviorTreeNodeReferenceDecorators/
*/
import { BehaviorElement } from "./behavior-node-element";
import { BehaviorStatus } from "./behavior-status"
export class BehaviorDecorator extends BehaviorElement {
isCondition = false;
public getLogSymbol(){
return "deco &@"
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "b1634f8a-3e56-422b-9be3-18fba853fb67",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,73 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-08 11:09:25
* @LastEditors: OreoWang
* @LastEditTime: 2022-05-17 14:44:54
* @Description:
*/
import { utils } from "../utils/utils";
import { IBehaviorNodeInterface } from "./behavior-node-interface";
import { BehaviorStatus } from "./behavior-status";
export function CloneEventOption(){
return utils.clone(BehaviorEventOption);
}
export const BehaviorEventOption = {
node: {
// uuid: '',
name: '',
path: '',
},
component: {
uuid: '',
name: '',
},
method: '',
data: '',
}
export type TBehaviorEventOption = typeof BehaviorEventOption;
export type AnyFunction = (...args: any[]) => any;
export type TBehaviorEventRecord<T> = {
[key in keyof T]: T[key]
}
export type TBehaviorEventInterface = Record<string, AnyFunction>;
export interface IBehaviorTreeEventInterface {
onDeserializeBefore: () => void;
onDeserializeAfter: () => void;
onDeserialize: (node: IBehaviorNodeInterface) => void;
}
export type TBehaviorTreeEventInterface = TBehaviorEventRecord<IBehaviorTreeEventInterface>;
export interface IBehaviorNodeEventInterface {
onEnter: (node: IBehaviorNodeInterface) => void;
onExit: (node: IBehaviorNodeInterface) => void;
onEnable: (node: IBehaviorNodeInterface) => void;
onDisable: (node: IBehaviorNodeInterface) => void;
onUpdate: (node: IBehaviorNodeInterface, status: BehaviorStatus) => BehaviorStatus;
}
export type TBehaviorNodeEventInterface = TBehaviorEventRecord<IBehaviorNodeEventInterface>;
export type TDelegateEvents = keyof IBehaviorNodeEventInterface;
export type TArrayDelegateEvents = Array<TDelegateEvents>;
export type TBehaviorEvents = {
[key in TDelegateEvents]?: TBehaviorEventOption
}
export interface IBehaviorEventListener<T extends TBehaviorEventInterface> {
on<Key extends keyof T>(key: Key, callback: T[Key], target?: unknown, once?: boolean): AnyFunction;
once<Key extends keyof T>(key: Key, callback: T[Key], target?: unknown): AnyFunction;
off<Key extends keyof T> (key: Key, callback?: AnyFunction, target?: any): void;
targetOff(typeOrTarget: any): void;
emit<Key extends keyof T>(key: Key, ...params: Parameters<T[Key]>): ReturnType<T[Key]>;
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "87489bb0-ed6c-4858-bcd0-8d2df16c49f3",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,88 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-08 11:09:25
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-21 17:46:56
* @Description:
*/
import { logger } from "../utils/logger";
import { TBehaviorEventOption, TBehaviorEvents, TBehaviorNodeEventInterface } from "./behavior-event-declaration";
import { BehaviorEventHandler } from "./behavior-event-handler";
import { BehaviorEventTarget } from "./behavior-event-target";
import { IBehaviorNodeInterface } from "./behavior-node-interface";
import { BehaviorStatus } from "./behavior-status";
import { IBehaviorTree } from "./behavior-tree-interface";
export class BehaviorEventDelegate extends BehaviorEventTarget<TBehaviorNodeEventInterface>{
protected _btNode: IBehaviorNodeInterface = null;
protected _events: TBehaviorEvents = null;
protected _context: IBehaviorTree = null;
private _onEnterHandler: BehaviorEventHandler = null;
private _onExitHandler: BehaviorEventHandler = null;
private _onEnableHandler: BehaviorEventHandler = null;
private _onDisableHandler: BehaviorEventHandler = null;
private _onUpdateHandler: BehaviorEventHandler = null;
constructor(btNode: IBehaviorNodeInterface, events: TBehaviorEvents, context: IBehaviorTree){
super();
if(!btNode || !events || !context) return;
this._btNode = btNode;
this._events = events;
this._context = context;
this._onEnterHandler = this.createHandler(events.onEnter);
this._onExitHandler = this.createHandler(events.onExit);
this._onUpdateHandler = this.createHandler(events.onUpdate);
this._onEnableHandler = this.createHandler(events.onEnable);
this._onDisableHandler = this.createHandler(events.onDisable);
}
protected createHandler(event: TBehaviorEventOption){
if(!event || !event.node?.path) return null;
let target = this._context.getTargetByPath(event.node.path);
if(!target){
logger.warn("getTargetByPath error: path =", event.node.path);
return null;
}
let component = target.getComponent(event.component.name);
if(!component){
logger.warn("cann't find component by name = " + event.component.name);
return null;
}
let handler = new BehaviorEventHandler();
handler.target = target;
handler.component = event.component.name;
handler.handler = event.method;
handler.customEventData = event.data;
return handler;
}
onEnter(){
this._onEnterHandler?.emit([this._btNode]);
this.emit("onEnter", this._btNode);
}
onEnable(){
this._onEnableHandler?.emit([this._btNode]);
this.emit("onEnable", this._btNode);
}
onUpdate(status: BehaviorStatus): BehaviorStatus{
if(this._onUpdateHandler){
status = this._onUpdateHandler.invoke(this._btNode, status);
}
status = this.emit("onUpdate", this._btNode, status);
return status;
}
onDisable(){
this._onDisableHandler?.emit([this._btNode]);
this.emit("onDisable", this._btNode);
}
onExit(){
this._onExitHandler?.emit([this._btNode]);
this.emit("onExit", this._btNode);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "350f3e0b-8ce3-447b-806c-8774edb942f4",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,45 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-08 11:09:25
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-21 17:47:27
* @Description:
*/
import { Node, EventHandler, isValid, js } from "cc";
import { logger } from "../utils/logger";
import { BehaviorStatus } from "./behavior-status";
export class BehaviorEventHandler extends EventHandler{
invoke(...params: any[]): BehaviorStatus{
const target = this.target;
if (!isValid(target)) { return; }
//@ts-ignore
this._genCompIdIfNeeded();
const compType: any = js._getClassById(this._componentId);
if(!compType){
logger.warn("invalid component type!");
return;
}
const comp = target!.getComponent(compType);
if (!isValid(comp)) {
logger.warn("invalid component type!");
return;
}
const handler = comp![this.handler];
if (typeof (handler) !== 'function') { return; }
if (this.customEventData != null && this.customEventData !== '') {
params = params.slice();
params.push(this.customEventData);
}
let res: BehaviorStatus = handler.apply(comp, params);
return res;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "8944dcea-05e3-4d6b-b6d2-6e695b0fc717",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,68 @@
import { EventTarget } from "cc";
import { AnyFunction, IBehaviorEventListener, TBehaviorEventInterface } from "./behavior-event-declaration";
type CallbackList = any;
export class BehaviorEventTarget<T extends TBehaviorEventInterface> implements IBehaviorEventListener<T> {
protected eventTarget = new EventTarget();
on<Key extends keyof T>(key: Key, callback: T[Key], target?: unknown, once?: boolean): AnyFunction{
return this.eventTarget.on(`${key}`, callback, target, once);
}
once<Key extends keyof T>(key: Key, callback: T[Key], target?: unknown): AnyFunction {
return this.eventTarget.once(`${key}`, callback, target);
}
off<Key extends keyof T> (key: Key, callback?: AnyFunction, target?: any): void {
this.eventTarget.off(`${key}`, callback, target);
}
targetOff (typeOrTarget: any): void {
this.eventTarget.targetOff(typeOrTarget);
}
emit<Key extends keyof T>(key: Key, ...params: Parameters<T[Key]>): ReturnType<T[Key]>{
//@ts-ignore
const list: CallbackList = this.eventTarget._callbackTable && this.eventTarget._callbackTable[key]!;
let arg0 = params[0];
let arg1 = params[1];
if (list) {
const rootInvoker = !list.isInvoking;
list.isInvoking = true;
const infos = list.callbackInfos;
for (let i = 0, len = infos.length; i < len; ++i) {
const info = infos[i];
if (info) {
const callback = info.callback;
const target = info.target;
// Pre off once callbacks to avoid influence on logic in callback
if (info.once) {
this.eventTarget.off(`${key}`, callback, target);
}
// Lazy check validity of callback target,
// if target is CCObject and is no longer valid, then remove the callback info directly
if (!info.check()) {
this.eventTarget.off(`${key}`, callback, target);
} else if (target) {
let result = callback.call(target, arg0, arg1);
if(typeof arg1 != "undefined"){
arg1 = result;
}
} else {
let result = callback(arg0, arg1);
if(typeof arg1 != "undefined"){
arg1 = result;
}
}
}
}
if (rootInvoker) {
list.isInvoking = false;
if (list.containCanceled) {
list.purgeCanceled();
}
}
}
return arg1 as ReturnType<T[Key]>;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "321f5098-933f-4df8-82c0-d6630ea4e9de",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,67 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-19 14:52:02
* @Description: entity
*/
import { BehaviorNode } from "./behavior-node"
import { BehaviorStatus } from "./behavior-status"
import { IElementConfig, IElementInfo, INodeInfo } from "./behavior-node-interface";
import { BehaviorEntity } from "./behavior-node-entity";
import { IBehaviorTree } from "./behavior-tree-interface";
export class BehaviorElement extends BehaviorNode {
/** element(附属节点) 元素的拥有者 */
owner: BehaviorEntity = null;
isCondition = false;
isInterrupter = false;
get parent(){
return this._parent as BehaviorEntity;
};
get nodeInfo() {
return this._nodeInfo as IElementInfo;
}
get nodeConfig() {
return this._nodeConfig as IElementConfig;
}
constructor(parent: BehaviorEntity, nodeInfo: INodeInfo, context: IBehaviorTree) {
super(parent, nodeInfo, context);
this._parent = parent.parent;
this.owner = parent;
}
public getLogSymbol(){
return "elem &-"
}
execute(status: BehaviorStatus) {
status = this.update(status);
this.status = status = super.execute(status);
return status;
}
executeDecorator(status: BehaviorStatus){
if(status == BehaviorStatus.None){
//装饰器默认都返回 Failure ,需要 execute 处理
status = BehaviorStatus.Failure;
}
this.enter();
this.enable();
status = this.execute(status);
this.disable();
this.exit();
return status;
}
executeInterrupt(parent: BehaviorEntity, child: BehaviorEntity): BehaviorStatus{
return BehaviorStatus.Failure;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "bfa1f9c5-63fa-49d0-b812-f8945a7403d6",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,293 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-26 17:10:32
* @Description: element entity
*/
import { BehaviorNode } from "./behavior-node";
import { BehaviorStatus } from "./behavior-status";
import { deserializeNode } from "./behavior-tree-utils";
import { logger } from "../utils/logger";
import { IEntityInfo, ILabelConfig, INodeInfo } from "./behavior-node-interface";
import { IBehaviorTree } from "./behavior-tree-interface";
import { BehaviorElement } from "./behavior-node-element";
import { BehaviorDecorator } from "./behavior-decorator";
import { BehaviorService } from "./behavior-service";
export class BehaviorEntity extends BehaviorNode {
get parent(): BehaviorEntity{
return this._parent as BehaviorEntity;
};
get nodeInfo() {
return this._nodeInfo as IEntityInfo;
}
get nodeConfig() {
return this._nodeConfig as ILabelConfig;
}
interrupters: Set<BehaviorDecorator> = new Set();
decorators: BehaviorDecorator[] = [];
services: BehaviorService[] = [];
children: BehaviorEntity[] = [];
constructor(parent: BehaviorEntity, nodeInfo: INodeInfo, context: IBehaviorTree) {
super(parent, nodeInfo, context);
}
public getLogSymbol(){
return "enti *-"
}
setLogEnabled(enabled: boolean, withChildren: boolean = false) {
super.setLogEnabled(enabled, withChildren);
this.decorators.forEach(v=>{
v.setLogEnabled(enabled, withChildren);
})
this.services.forEach(v=>{
v.setLogEnabled(enabled, withChildren);
})
if(withChildren){
this.children.forEach(v=>{
v.setLogEnabled(enabled, withChildren);
})
}
}
deserialize(){
let { $context, nodeInfo } = this;
if (nodeInfo.children) {
for (let child of nodeInfo.children) {
const options = child.config.label;
if(options.uuid){
const instance = deserializeNode<BehaviorEntity>(this, child, $context);
if(instance){
this.children.push(instance);
}
else{
logger.error("Can't find class by uuid: ", options.uuid);
}
}
else{
logger.error("Can't find class uuid: ", options);
}
}
}
if (nodeInfo.elements) {
for (let elem of nodeInfo.elements) {
const options = elem.config;
if(options.uuid){
const instance = deserializeNode<BehaviorElement>(this, elem, $context, true);
if(instance){
if (elem.type === 'decorator') {
this.decorators.push(instance)
} else if (elem.type === 'service') {
this.services.push(instance)
}
}
else{
logger.error("Can't find class by uuid: ", options.uuid);
}
}
}
}
super.deserialize();
}
destroy(){
for (let dec of this.decorators) {
dec.destroy()
}
for (let ser of this.services) {
ser.destroy()
}
for (let child of this.children) {
child.destroy()
}
this.decorators.length = 0;
this.services.length = 0;
this.children.length = 0;
this.clearInterrupter();
super.destroy();
}
reset(): void {
for (let dec of this.decorators) {
dec.reset()
}
for (let ser of this.services) {
ser.reset()
}
for (let child of this.children) {
child.reset()
}
super.reset();
}
disable(){
if(this.status==BehaviorStatus.Running){
this.status = BehaviorStatus.Abort;
}
super.disable();
}
abort(){
if(this.status==BehaviorStatus.Running){
this.disable();
}
else{
this.status = BehaviorStatus.Abort;
}
return this.onAbort(this.status);
}
onAbort(status: BehaviorStatus){
this.logLifeStatus("abort", status);
return status;
}
interrupt(){
this.onInterrupt(this.status);
return null;
}
onInterrupt(status: BehaviorStatus){
this.logLifeStatus("interrupt", status);
return status;
}
addInterrupter(node: BehaviorDecorator){
if(!this.interrupters.has(node)){
this.interrupters.add(node);
}
}
removeInterrupter(node: BehaviorDecorator){
if(this.interrupters.has(node)){
this.interrupters.delete(node);
}
}
clearInterrupter(){
this.interrupters.clear();
}
/**
*
* @describe
* @describe execute => condition(get result success) -> service -> self -> decorator => return
* @describe
* @describe execute => condition(get result fail) => return
* @param status
* @returns
*/
execute(status?: BehaviorStatus){
if(typeof status == 'undefined'){
status = this.status==BehaviorStatus.Running ? BehaviorStatus.Running : BehaviorStatus.None;
}
//如果当前节点是以打断其它节点的方式执行的,说明当前节点已经满足执行条件,这里不需要再执行条件判断了
if(status == BehaviorStatus.Interrupting){
}
else{
status = this.executeCondition(status);
//如果节点执行条件不成功
if(status != BehaviorStatus.Success){
if(this.status==BehaviorStatus.Running){
this.status = status;
this.disable();
}
else{
this.status = status;
}
return this.status;
}
}
this.enter();
if(this.status != BehaviorStatus.Running){
this.enable();
}
this.executeServices(status);
this.status = status = this.update(status);
this.logLifeStatus("execute");
this.status = status = this.executeDecorator(status);
if(status == BehaviorStatus.Running){
}
else{
if(status == BehaviorStatus.Failure){
}
this.disable();
}
this.exit();
return this.status;
}
/**
*
* @description ConditionalInverter.ts
* @param status
* @returns
*/
protected executeCondition(status: BehaviorStatus){
let array = this.decorators;
if(array.length > 0){
//装饰器的执行顺序都是从最后一个开始往上执行的
for (let index = array.length-1; index>=0; index--) {
const element = array[index];
if(!element.isInterrupter && element.isCondition){
//将上一个 status 作为参数传入
//该参数使用比较灵活,视具体的 element.execute 方法实现而定
//比如可实现类似 ConditionalInverter 条件反转逻辑
status = element.executeDecorator(status);
}
}
}
else{
//当节点没有挂载前置条件装饰器时,节点必然是可执行的
status = BehaviorStatus.Success;
}
return status;
}
/**
*
* @description Inverter.ts
* @param status
* @returns
*/
protected executeDecorator(status: BehaviorStatus){
let array = this.decorators;
//装饰器的执行顺序都是从最后一个开始往上执行的
for (let index = array.length-1; index>=0; index--) {
const element = array[index];
if(!element.isInterrupter && !element.isCondition){
//将上一个 status 作为参数传入
//该参数使用比较灵活,视具体的 element.execute 方法实现而定
//比如可实现类似 Inverter 结果反转逻辑
status = element.executeDecorator(status);
}
}
return status;
}
/**
* tick
* @param status
*/
protected executeServices(status: BehaviorStatus){
const array = this.services;
for (let index = array.length-1; index>=0; index--) {
const element = array[index];
element.update(status);
}
}
public checkCondition(status: BehaviorStatus){
return this.executeCondition(status);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "d187fd32-d28b-4feb-a544-bd09d1daf092",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,85 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-11 10:03:31
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-19 10:17:33
* @Description:
*/
import { IBehaviorEventListener, TBehaviorEvents, TBehaviorNodeEventInterface } from "./behavior-event-declaration";
import { BehaviorStatus } from "./behavior-status";
import { IBehaviorTree } from "./behavior-tree-interface";
export interface IBehaviorNodeInterface {
$context: IBehaviorTree;
delegate: IBehaviorEventListener<TBehaviorNodeEventInterface>;
parent: IBehaviorNodeInterface;
status: BehaviorStatus;
// isInterrupter: boolean;
// isElement: boolean;
// isCondition: boolean;
nodeInfo: INodeInfo;
nodeConfig: INodeConfig;
nodeType: TNodeType;
nodeTag: string;
nodeOrder: number;
nodeTitle: string;
setLogEnabled(enabled: boolean, withChildren: boolean);
}
export type TNodeOrder = number;
export type TNodeType = "selector" | "sequence" | "parallel" | "condition" | "decorator" | "service" | "task";
export interface ILabelConfig {
events: TBehaviorEvents,
group: string,
name: string,
tag: string,
title: string,
order: number
properties: {[key: string]: any},
uuid: string,
}
export interface IElementConfig extends ILabelConfig {
type: TNodeType,
isCondition: boolean,
label?: undefined,
}
export interface IEntityConfig {
type: TNodeType,
label: ILabelConfig,
}
export interface IElementInfo {
type: TNodeType,
config: IElementConfig,
}
export interface IEntityInfo {
type: TNodeType;
config: IEntityConfig;
// label: any;
// /** 平行节点可同时执行的数目 */
// threshold: number;
children: Array<IEntityInfo>;
elements: Array<IElementInfo>;
}
export type INodeInfo = IElementInfo | IEntityInfo;
export type INodeConfig = IElementConfig | ILabelConfig;
export interface INodeProperty {
default: any;
TYPE?: string;
path?: string;
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "db4c226a-e591-429f-85ce-431da6014933",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,194 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-08 11:09:25
* @LastEditors: OreoWang
* @LastEditTime: 2022-06-06 14:45:35
* @Description:
*/
import { IElementConfig, ILabelConfig, INodeInfo, INodeConfig } from "./behavior-node-interface";
import { BehaviorEventDelegate } from "./behavior-event-delegate";
import { IBehaviorNodeInterface } from "./behavior-node-interface";
import { BehaviorStatus, TBehaviorStatus } from "./behavior-status";
import { IBehaviorTree, TBehaviorLogKey } from "./behavior-tree-interface";
export class BehaviorNode implements IBehaviorNodeInterface {
$context: IBehaviorTree = null;
delegate: BehaviorEventDelegate = null;
status: TBehaviorStatus = BehaviorStatus.None;
/** 父节点 */
protected _parent: BehaviorNode = null;
get parent(){
return this._parent;
}
protected _nodeInfo: INodeInfo = null;
get nodeInfo() {
return this._nodeInfo;
}
protected _nodeConfig: INodeConfig = null;
get nodeConfig() {
return this._nodeConfig;
}
get nodeType(){
return this.nodeInfo.type;
}
get nodeTag(){
return this.nodeConfig.tag;
}
get nodeOrder(){
return this.nodeConfig.order;
}
get nodeTitle(){
return this.nodeConfig.title;
}
protected _isEnabled = false;
constructor(parent: BehaviorNode, nodeInfo: INodeInfo, context: IBehaviorTree) {
this.$context = context;
this._parent = parent;
this._nodeInfo = nodeInfo;
/** element */
if(!nodeInfo.config.label){
this._nodeConfig = nodeInfo.config as IElementConfig;
}
else{
this._nodeConfig = nodeInfo.config.label as ILabelConfig;
}
if(!this._nodeConfig.tag){
this._nodeConfig.tag = `TAG${this._nodeConfig.order}`;
}
this.delegate = new BehaviorEventDelegate(this, this.nodeConfig.events, context);
}
deserialize(){
this.$context.onDeserialize(this);
}
load(){
this.logLifeStatus("load");
}
destroy(){
this.delegate = null;
this.logLifeStatus("destroy");
}
enter(){
this.logLifeStatus("enter");
this.delegate.onEnter();
}
exit(){
this.logLifeStatus("exit");
this.delegate.onExit();
}
enable(){
if(this._isEnabled){
return;
}
this._isEnabled = true;
this.delegate.onEnable();
this.logLifeStatus("enable");
}
disable(){
if(!this._isEnabled){
return;
}
this._isEnabled = false;
this.delegate.onDisable();
this.logLifeStatus("disable");
}
update(status: BehaviorStatus): TBehaviorStatus {
return this.onUpdate(status);
}
execute(status?: BehaviorStatus): TBehaviorStatus{
return this.onExecute(status);
}
protected onUpdate(status: BehaviorStatus): TBehaviorStatus {
status = this.delegate.onUpdate(status);
this.logLifeStatus("update", status);
return status;
}
protected onExecute(status?: BehaviorStatus): TBehaviorStatus{
this.logLifeStatus("execute", status);
return status;
}
reset() {
this.status = BehaviorStatus.None;
}
_mapStageKey: Map<string, string> = new Map();
protected getStageKey(stage: string): TBehaviorLogKey{
let key = this._mapStageKey.get(stage);
if(!key){
key = `log${stage.substring(0, 1).toUpperCase()}${stage.substring(1)}`;
this._mapStageKey.set(stage, key);
}
return key as TBehaviorLogKey;
}
public clearStageKey(){
this._mapStageKey.clear();
}
public getLogSymbol(){
return "node --"
}
public getLogPrefix(){
return `bt-${this.getLogSymbol()} `;
}
protected isLogEnabled(stage: string){
let key = this.getStageKey(stage);
return this.$context.isLogEnabled(key) && this._logEnabled;
}
protected _logEnabled = true;
/**
* log
* @param enabled
* @param withChildren
*/
setLogEnabled(enabled: boolean, withChildren: boolean = false) {
this._logEnabled = enabled;
if(withChildren){
}
}
/**
*
* @param stage
* @param status
*/
protected logLifeStatus(stage: string, status?: BehaviorStatus){
if(typeof status == 'undefined'){
status = this.status;
}
if(this.isLogEnabled(stage)){
this.$context.onHandleTreeLog(`${this.getLogPrefix()} [${this.nodeConfig.order}]-[${stage}] [${this.nodeConfig.title}] ${BehaviorStatus[status]} ${this.getAppendedLog(stage, status)}`);
}
}
/**
*
* @param stage
* @param status
* @returns
*/
protected getAppendedLog(stage: string, status?: BehaviorStatus){
return '';
}
/**
*
* @param stage
* @param info
*/
public logLifeInfo(stage: string, info: string){
if(this.isLogEnabled(stage)){
this.$context.onHandleTreeLog(`${this.getLogPrefix()} [${this.nodeConfig.order}]-[${stage}] [${this.nodeConfig.title}] : info = ${info} `);
}
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "561ac289-ce77-4380-81c5-4c8c658f3277",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,112 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-26 17:16:25
* @Description:
*/
import { SharedNumber } from "../blackboard/shared-number";
import { BehaviorComposite } from "./behavior-composite";
import { BehaviorStatus } from "./behavior-status";
export class BehaviorParallel extends BehaviorComposite {
//0 全部成功 -1 全部失败 XXX 指定数目
threshold: SharedNumber = null;
_cacheStatus: Array<BehaviorStatus> = [];
constructor(parent, config, context) {
super(parent, config, context)
this._cacheStatus = [] // children执行状态Cache
}
public getLogSymbol(){
return "para *="
}
update(status: BehaviorStatus) {
let entity = this.interrupt();
if(entity){
}
let threshold = this.threshold && typeof this.threshold.value == 'number' ? this.threshold.value : 0;
if (threshold == 0 || threshold > this.children.length) {
threshold = this.children.length
}
else if(threshold < 0){
threshold = 0
}
// 执行子节点
let success = 0
let running = 0
for (let i = 0; i < this.children.length; i++) {
if (this._cacheStatus[i] == null || this._cacheStatus[i] === BehaviorStatus.Running) {
status = this.children[i].execute()
this._cacheStatus[i] = status
if (status === BehaviorStatus.Running) {
running++
}
}
if (this._cacheStatus[i] === BehaviorStatus.Success) {
success++
}
}
if (running === 0) {
status = (success === threshold) ? BehaviorStatus.Success : BehaviorStatus.Failure
} else {
status = BehaviorStatus.Running
}
return super.update(status);
}
interrupt(){
const running = this._cacheStatus.filter((status)=>{
return status == BehaviorStatus.Running;
})
// 执行子节点中断评估
if(this.interrupters.size>0 && running.length>0){
for(let i=0; i<this.children.length; i++){
const child = this.children[i];
if(child.status!=BehaviorStatus.Running){
for (const element of this.interrupters) {
let status = element.executeInterrupt(this, child);
if(status == BehaviorStatus.Success){
this.abort();
// Sequence 序列节点,需要从 0 开始重新执行
// this.lastRunning = i;
return child;
}
}
}
}
}
return null;
}
abort(){
if(this._cacheStatus.length > 0){
for (let index = 0; index < this._cacheStatus.length; index++) {
const status = this._cacheStatus[index];
if(status == BehaviorStatus.Running){
this._cacheStatus[index] = BehaviorStatus.None;
this.children[index].abort();
}
}
this._cacheStatus.length = 0;
}
return super.abort();
}
reset(){
super.reset();
this._cacheStatus.length = 0
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "725cc9b3-cb0c-471f-ad40-61d46b34f47c",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,71 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-26 17:14:42
* @Description:
*/
import { BehaviorComposite } from "./behavior-composite";
import { BehaviorStatus } from "./behavior-status";
export class BehaviorSelector extends BehaviorComposite {
public getLogSymbol(){
return "sele *?"
}
update(status: BehaviorStatus) {
let entity = this.interrupt();
if(entity){
}
// 执行子节点
let start = this.lastRunning >= 0 ? this.lastRunning : 0
this.lastRunning = -1
for (let i=start; i < this.children.length; i++) {
const child = this.children[i];
if(entity && child == entity){
status = child.execute(BehaviorStatus.Interrupting);
}
else{
status = child.execute();
}
if (status === BehaviorStatus.Success) {
break
}
else if (status === BehaviorStatus.Running) {
this.lastRunning = i
break
}
else{
continue;
}
}
return super.update(status);
}
interrupt(){
let start = this.lastRunning >= 0 ? this.lastRunning : 0
// 执行子节点中断评估
if(this.interrupters.size>0 && start>0){
for(let i=0; i<start; i++){
const child = this.children[i];
for (const element of this.interrupters) {
if(element.owner == this || element.owner == child){
let status = element.executeInterrupt(this, child);
if(status == BehaviorStatus.Success){
this.abort();
this.lastRunning = i;
return child;
}
break;
}
}
}
}
return null;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "32365a9f-4afa-41ef-9b02-43d2933043c9",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,70 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-26 17:17:26
* @Description:
*/
import { BehaviorComposite } from "./behavior-composite";
import { BehaviorStatus } from "./behavior-status";
export class BehaviorSequence extends BehaviorComposite {
public getLogSymbol(){
return "sequ *>"
}
update(status: BehaviorStatus) {
let entity = this.interrupt();
if(entity){
}
// 执行子节点
let start = this.lastRunning >= 0 ? this.lastRunning : 0
this.lastRunning = -1
for (let i=start; i < this.children.length; i++) {
const child = this.children[i];
if(entity && child == entity){
status = child.execute(BehaviorStatus.Interrupting);
}
else{
status = child.execute();
}
if (status === BehaviorStatus.Success) {
continue
}
else if (status === BehaviorStatus.Running) {
this.lastRunning = i
}
break;
}
return super.update(status);
}
interrupt(){
let start = this.lastRunning >= 0 ? this.lastRunning : 0
// 执行子节点中断评估
if(this.interrupters.size>0 && start>0){
for(let i=0; i<start; i++){
const child = this.children[i];
for (const element of this.interrupters) {
if(element.owner == this || element.owner == child){
let status = element.executeInterrupt(this, child);
if(status == BehaviorStatus.Success){
this.abort();
this.lastRunning = i;
return child;
}
break;
}
}
}
}
return null;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "59a0c931-0f73-470b-96ef-13276d1555cf",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,23 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-22 12:56:01
* @Description:
*/
import { BehaviorElement } from "./behavior-node-element";
import { BehaviorStatus } from "./behavior-status"
export class BehaviorService extends BehaviorElement {
public getLogSymbol(){
return "serv &$"
}
execute(status: BehaviorStatus) {
status = super.execute(status);
status = this.update(status);
return status;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "d33b70da-842d-4600-990a-18cef2ef057b",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,22 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:48:12
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-19 11:29:22
* @Description:
*/
export enum BehaviorStatus {
// Idle = -1,
None = 0,
Success,
Failure,
Running,
Abort,
Interrupting,
}
type ValueOf<T> = T[keyof T];
export type TBehaviorStatus = ValueOf<typeof BehaviorStatus>;
export type KBehaviorStatus = keyof typeof BehaviorStatus;
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "8fed9137-b8a3-457f-9f5d-1415b31e97fd",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,24 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:47:14
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-22 12:55:39
* @Description:
*/
import { BehaviorEntity } from "./behavior-node-entity";
import { BehaviorNode } from "./behavior-node"
import { BehaviorStatus } from "./behavior-status"
export class BehaviorTask extends BehaviorEntity {
public getLogSymbol(){
return "task **"
}
update(status: BehaviorStatus) {
//任务的默认实现直接返回 Failure,所以,在编辑器中使用 Task 节点时,必须指定 onUpdate Delegate
status = BehaviorStatus.Failure;
return super.update(status);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "a226a00c-a22b-4ceb-b0cb-5460e1f45e73",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,122 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-11 09:30:37
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-26 10:49:24
* @Description:
*/
import { JsonAsset } from 'cc';
import { Node } from 'cc';
import { Task } from '../../node/task/Task';
import { Blackboard } from '../blackboard/blackboard';
import { TBlackboardOption } from '../blackboard/blackboard-declaration';
import { IBehaviorEventListener, TBehaviorTreeEventInterface } from './behavior-event-declaration';
import { BehaviorNode } from './behavior-node';
import { BehaviorEntity } from './behavior-node-entity';
import { IEntityInfo, TNodeOrder } from './behavior-node-interface';
import { BehaviorStatus, TBehaviorStatus } from './behavior-status';
import { BehaviorTask } from './behavior-task';
export const BT_RUNTIME_VERSION = "1.1.0";
export interface IBehaviorTree {
/** 加载 Json 行为树 */
loadJsonAsset(jsonAsset: JsonAsset);
/**
* FPS还取决于 cc.game.frameRate
* cc.game.frameRate
* @param frameRate
*/
setFrameRate(frameRate: number);
getFrameRate(): number;
/** 行为树反序列化每个节点回调 */
onDeserialize(node: BehaviorNode);
/** 行为树根节点 */
getRoot(): BehaviorEntity | null;
/** 根据 tag 获取一个任务节点 */
getTask(tag: string): Task | null;
/** 根据 order 获取一个任务节点 */
getTaskByOrder(order: number): Task | null;
/** 行为树组件 BehaviorTree 被附加到的 cc.Node 节点 */
getTargetRoot(): Node;
/** 根据子节点路径获取 cc.Node 节点 */
getTargetByPath(path: string): Node | null;
/** 某个类型的 log 是否启用 */
isLogEnabled(key: TBehaviorLogKey): boolean;
onHandleTreeLog(msg: string): void;
onTick(delta: number): TBehaviorStatus;
onRestart();
onPause();
onResume();
onFinished();
onStop();
/**
*
* 事件类型详见: IBehaviorTreeEventInterface
*/
delegate: IBehaviorEventListener<TBehaviorTreeEventInterface>;
/** 行为树执行日志粒度控制 */
logOptions: IBehaviorLogOptions;
/** 行为树持续时间(是每帧时间增量叠加后的时间总和) */
duration: number;
/** 行为树 tick 总次数 */
ticks: number;
/** 行为树中的所有节点(包括任务节点、组合节点、装饰器等所有节点) */
allNodes: Array<BehaviorNode>;
/** 行为树中的所有任务节点*/
allTasks: Array<BehaviorTask>;
/** 行为树使用的黑板变量 */
blackboard: Blackboard;
/** 行为树当前状态 */
status: BehaviorStatus;
/** 行为树是否已挂起 */
isSuspended: boolean;
/** 行为树是否已执行结束 */
isCompleted: boolean;
startWhenEnabled: boolean;
pauseWhenDisabled: boolean;
restartWhenComplete: boolean;
resetValuesOnRestart: boolean;
logTaskChanges: boolean;
}
export interface IBehaviorLogOptions {
/** 当任务被中止时是否打印日志 */
logAbort: boolean;
/** 当中断产生时是否打印日志 */
logInterrupt: boolean;
logExecute: boolean;
logUpdate: boolean;
logLoad: boolean;
logDestroy: boolean;
logEnter: boolean;
logExit: boolean;
logEnable: boolean;
logDisable: boolean;
}
export type TBehaviorLogKey = keyof IBehaviorLogOptions;
export type TTreeAsset = {
root: IEntityInfo;
refs: Array<string>;
blackboard: TBlackboardOption;
version: string;
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "effa631a-9dab-465d-848f-00ff6ee553e6",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,193 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-11 09:30:37
* @LastEditors: OreoWang
* @LastEditTime: 2022-05-12 10:08:35
* @Description:
*/
import { js, Node } from "cc";
import { logger } from "../utils/logger";
import { BehaviorNode } from "./behavior-node";
import { IElementInfo, IElementConfig, IEntityInfo, INodeProperty, IEntityConfig, ILabelConfig } from "./behavior-node-interface";
import { IBehaviorTree } from "./behavior-tree-interface";
export function getTargetByPath(node: Node, path: string){
if(!path){
return null;
}
return node.getChildByPath(path);
}
export function deserializeNode<T extends BehaviorNode>(parent: BehaviorNode, info: IEntityInfo | IElementInfo, context: IBehaviorTree, isElement = false): T | null{
let config: ILabelConfig | IElementConfig;
if(isElement){
config = info.config as unknown as IElementConfig;
}
else{
config = (info.config as IEntityConfig).label;
}
const cls = js._getClassById(config.uuid) as unknown as typeof BehaviorNode;
if(!cls){
return null;
}
let instance: T = new cls(parent, info, context) as T;
let properties = config.properties;
for (const key in properties) {
const property: INodeProperty = properties[key];
if(typeof instance[key] == 'undefined'){
if(property.TYPE == "bt.SharedDynamic"){
if(property.path){
instance[key] = context.getTargetByPath(property.path);
}
else{
instance[key] = property.default;
}
}
}
else if(!property.TYPE){
instance[key] = property.default;
}
else if(property.TYPE == "cc.Enum"){
instance[key] = Number(property.default);
}
else if(property.TYPE == "cc.Node"){
if(property.path){
instance[key] = context.getTargetByPath(property.path);
}
}
else{
if(property.TYPE.startsWith("bt.Shared")){
if(property.TYPE == "bt.SharedDynamic"){
if(property.path){
instance[key] = context.getTargetByPath(property.path);
}
else{
instance[key] = property.default;
}
}
else{
let variable = context.blackboard.getVariable(property.default);
instance[key] = variable;
}
// console.warn("SharedVariable: ", instance[key]);
}
}
if(typeof instance[key] == "undefined" || instance[key] == null){
logger.warn(`Behavior [${instance.nodeConfig.order}]-[deserialize] [${instance.nodeConfig.title}]: key=${key}, value=${instance[key]}`);
}
}
instance.deserialize();
instance.load();
return instance;
}
export class BehaviorTreeUtils {
// _targetNodes: Map<string, Node> = null;
// constructor(){
// this._targetNodes = new Map();
// }
// setTarget(uuid: string, node: Node){
// this._targetNodes.set(uuid, node);
// }
// getTarget(uuid: string){
// if(!uuid){
// return null;
// }
// let node = this._targetNodes.get(uuid);
// if(!node){
// console.warn("getTarget null. uuid="+uuid);
// }
// return node;
// }
// getTargetByPath(node: Node, path: string){
// if(!path){
// return null;
// }
// return node.getChildByPath(path);
// }
// initAllTarget(root: Node, uuids: Array<string>){
// if(!root || uuids.length==0) return;
// //按广度优先算法遍历子节点
// let listParent = [root];
// while (uuids.length>0 && listParent.length>0) {
// let parent: Node = listParent.shift();
// let array = parent.children;
// for (let index = 0; index < array.length; index++) {
// const child: any = array[index];
// let fileId = child._prefab?.fileId;
// let handled = false;
// if(fileId){
// for (let i=uuids.length-1; i>=0; i--){
// let uuid = uuids[i];
// if(uuid.indexOf(fileId)>=0){
// this.setTarget(uuid, child);
// uuids.splice(i, 1);
// handled = true;
// break;
// }
// }
// }
// else{
// let i = uuids.indexOf(child.uuid);
// if(i>=0){
// this.setTarget(child.uuid, child);
// uuids.splice(i, 1);
// handled = true;
// }
// }
// if(!handled){
// listParent.push(child);
// }
// }
// }
// //当前场景中找不到某些uuid引用的node节点(可能是行为树json数据中引用的节点被删除了)
// if(uuids.length>0){
// logger.warn("Unable to find reference node by uuids: ", uuids.join(","))
// }
// }
// /**
// * 按广度优先算法遍历子节点
// * @param uuid string
// * @returns
// */
// searchTargetByUUID(root: Node, uuid: string) {
// if(!uuid || !root) return null;
// let node: Node = root.uuid==uuid ? root : null;
// if(node){
// return node;
// }
// //按广度优先算法遍历子节点
// let listParent = [root];
// while (!node && listParent.length>0) {
// let parent: Node = listParent.shift();
// let array = parent.children;
// for (let index = 0; index < array.length; index++) {
// const child = array[index];
// if(child.uuid == uuid){
// node = child;
// break;
// }
// else{
// listParent.push(child);
// }
// }
// }
// return node;
// }
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "598da6a4-fc00-4aaf-864c-8d7371d54ecb",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "a1c71c21-0a82-4733-a7dd-5954f90c94b1",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,46 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-20 09:28:26
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-20 14:48:14
* @Description:
*/
import { EventTarget, Enum } from "cc";
export const ENotifyObserver = Enum({
OnValueChange: "OnValueChange", //当黑板键的 value 值发生改变时
OnResultChange: "OnResultChange", //当黑板键的 value 值与 keyQuery 指定的比较
})
export const EAbortType = Enum({
None: 0,
Self: -1, //中断自身分支
LowerPriority: -1, //中断低优先级的兄弟分支
Both: -1, //以上两者
})
export const EQueryKey = Enum({
IsEqualTo: 0, //黑板键的值是否与指定值相等
IsNotEqualTo: -1,
IsSet: -1, //黑板键是否已指定
IsNotSet: -1,
})
export type TSharedVariableOption = {
name: string;
tooltip: string;
value: {
default: any;
TYPE: string;
path?: string;
}
}
export type TBlackboardOption = {
variables: Array<TSharedVariableOption>;
globals: Array<TSharedVariableOption>;
}
export interface IBlackboard extends EventTarget {
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "1cc920d6-87dd-4369-9e7f-832428d0e2ae",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,162 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:26:29
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-22 14:32:10
* @Description:
*/
import { js, EventTarget } from "cc";
import { IBehaviorTree } from "../behavior/behavior-tree-interface";
import { IBlackboard, TBlackboardOption } from "./blackboard-declaration";
import { SharedVariable } from "./shared-variable";
const logger = console;
export type TSharedVariable = { [key: string]: SharedVariable<any> };
export class Blackboard extends EventTarget implements IBlackboard {
/** 运行时全局静态数据(主要用于不同行为树间运行时数据共享) */
static globalData: Map<string, any> = new Map();
/** 运行时用户自定义数据(主要用于当前行为树运行时数据共享) */
userData: Map<string, any> = new Map();
context: IBehaviorTree = null;
/** 编辑器内导出的共享变量 */
variables: TSharedVariable = null;
/** 编辑器内导出的共享变量原始数据 */
data: TBlackboardOption = null;
/**
*
* @param data Object
*/
constructor(context: IBehaviorTree, data: TBlackboardOption) {
super();
this.init(context, data);
}
destroy(){
this.clearUserData();
}
init(context: IBehaviorTree, data: TBlackboardOption) {
this.context = context;
this.data = data;
const array = data?.variables;
if (!array) return;
this.variables = {};
for (let index = 0; index < array.length; index++) {
const element = array[index];
const property = element.value;
if (!property?.TYPE) {
logger.error("invalid type: ", element);
continue;
}
if (!property.TYPE.startsWith("bt.Shared")) {
logger.error("invalid type: ", element);
continue;
}
let SharedClass = js.getClassByName(property.TYPE);
if (SharedClass) {
let shared: SharedVariable<any> = new SharedClass(this, element.name) as unknown as any;
if (property.TYPE == "bt.SharedNode") {
if(property.path){
let node = context.getTargetByPath(property.path);
shared.value = node;
}
}
else {
shared.value = property.default;
}
this.variables[element.name] = shared;
}
else {
logger.error("getClassByName error: ", element);
}
}
}
/**
*
* @param key
* @returns
*/
getVariable(key: string) {
return this.variables[key];
}
/**
*
* variable.value = value
* @param key
* @param value
* @returns
*/
setVariableValue(key: string, value: any) {
let variable = this.getVariable(key);
if (!variable) {
logger.error('getVariable error: ', key);
return;
}
variable.value = value;
}
/**
*
*
* @param key
* @returns
*/
getUserData(key: string){
let value = this.userData.get(key);
return value;
}
/**
*
*
* @param key
* @param value
*/
setUserData(key: string, value: any){
this.userData.set(key, value);
}
/**
*
*/
clearUserData(){
this.userData.clear();
}
/**
*
*
* @param key
* @returns
*/
static getGlobalData(key: string){
let value = Blackboard.globalData.get(key);
return value;
}
/**
*
*
* @param key
* @param value
*/
static setGlobalData(key: string, value: any){
Blackboard.globalData.set(key, value);
}
/**
*
*/
static clearGlobalData(){
Blackboard.globalData.clear();
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "95e19727-fffb-4846-8dc8-c72825802c70",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,18 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-18 09:29:27
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-18 15:47:29
* @Description:
*/
import { SharedVariable } from "./shared-variable";
export class SharedArray<T> extends SharedVariable<T>{
get value(): Array<T> {
return this.getValue();
}
set value(v: Array<T>){
this.setValue(v);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "259766ca-379e-4f9d-9116-6128c72cd14c",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,22 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-12 09:30:24
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-20 09:34:46
* @Description:
*/
import { _decorator } from 'cc';
const {ccclass} = _decorator;
import { SharedVariable } from "./shared-variable";
@ccclass("bt.SharedBoolean")
export class SharedBoolean extends SharedVariable<boolean> {
get value(): boolean {
return this.getValue();
}
set value(v: boolean){
this.setValue(v);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "bd58f3fa-65d8-45e7-89f0-cd21ac4bd22d",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,18 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-18 09:29:27
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-18 15:46:57
* @Description:
*/
import { SharedVariable } from "./shared-variable";
export class SharedCustom<T> extends SharedVariable<T> {
get value(): T {
return this.getValue();
}
set value(v: T){
this.setValue(v);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "26f030af-e0cf-4d11-8d36-b6f572685316",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,23 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-12 09:30:24
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-18 15:46:40
* @Description:
*/
import { _decorator } from 'cc';
const {ccclass} = _decorator;
import { Node } from 'cc';
import { SharedVariable } from "./shared-variable";
@ccclass("bt.SharedNode")
export class SharedNode extends SharedVariable<Node> {
get value(): Node {
return this.getValue();
}
set value(v: Node){
this.setValue(v);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "150572af-2b72-44de-86e3-161d038d34fc",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,22 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-12 09:30:24
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-18 15:46:25
* @Description:
*/
import { _decorator } from 'cc';
const {ccclass} = _decorator;
import { SharedVariable } from "./shared-variable";
@ccclass("bt.SharedNumber")
export class SharedNumber extends SharedVariable<number> {
get value(): number {
return this.getValue();
}
set value(v: number){
this.setValue(v);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "affcec40-7522-4f92-a8ff-5600f3140517",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,22 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-12 09:30:24
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-18 15:53:00
* @Description:
*/
import { _decorator } from 'cc';
const {ccclass} = _decorator;
import { SharedVariable } from "./shared-variable";
@ccclass("bt.SharedString")
export class SharedString extends SharedVariable<string> {
get value(): string {
return this.getValue();
}
set value(v: string){
this.setValue(v);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "a5dd5afa-2306-4e03-a456-8aa72af30456",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,56 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:26:29
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-25 10:42:47
* @Description:
*/
import { utils } from "../utils/utils";
import { ENotifyObserver, IBlackboard } from "./blackboard-declaration";
export class SharedVariable<T> {
get value(): any {
return this.getValue();
}
set value(v: any){
this.setValue(v);
}
name: string = "";
original: any;
protected _value: any;
protected blackboard: IBlackboard = null;
constructor(blackboard: IBlackboard, name: string, value?: T){
this.blackboard = blackboard;
this.name = name;
if(typeof value != 'undefined'){
this.value = value;
}
}
protected getValue(){
return this._value;
}
protected setValue(v: any){
let oldValue = this._value;
this._value = v;
if(typeof this.original == 'undefined' || this.original == null){
this.original = v;
}
this.blackboard?.emit(ENotifyObserver.OnValueChange, this, oldValue);
}
reset(){
this.value = this.original;
}
toString(){
return `${this.value}`;
}
}
export class SharedDynamic {
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "58b35a2b-1f6e-4f3f-82cb-98257e3010f6",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "d35544ca-a4f2-4bdc-b3c4-bdb65e991b49",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,177 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-21 21:24:23
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-21 15:54:10
* @Description:
*/
/*
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
type GroupOptions = { name: string; } & Partial<{
id: string;
name: string;
displayOrder: number;
style: string;
}>;
export interface IExposedAttributes {
baseon?: string;
events?: {
onUpdate?: boolean,
onEnabled?: boolean,
onDisabled?: boolean,
};
/**
*
*/
type?: any;
/**
*
*/
visible?: boolean | (() => boolean);
/**
*
*/
displayName?: string;
/**
*
*/
displayOrder?: number;
/**
* @en Editor tooltip of this property.
* @zh
*/
tooltip?: string;
/**
* @en The group name where this property is organized into, on property inspector.
* @zh
*/
group?: string | GroupOptions;
/**
*
*/
multiline?: boolean;
/**
*
* `readonly` `true`
* `true`
* @default false
*/
readonly?: boolean | {
/**
*
* `true`
* @default true
*/
deep?: boolean;
};
/**
*
*/
min?: number;
/**
*
*/
max?: number;
/**
*
*/
step?: number;
/**
*
*/
range?: number[];
/**
*
*/
slide?: boolean;
/**
*
*/
serializable?: boolean;
/**
*
*/
formerlySerializedAs?: string;
/**
*
*/
editorOnly?: boolean;
/**
*
*/
override?: boolean;
/**
*
*/
animatable?: boolean;
/**
*
*/
unit?: string;
/**
*
*/
radian?: boolean;
/**
*
* `@property` `@serializable``@editable`
*
* `true`
* - `@property` `.serializable === false`
* - `@property` `.visible === false` 线
*
* - `@property` `.serializable === true`
* - `@property` `.visible === true`
*/
__noImplicit?: boolean;
}
export interface IAcceptableAttributes extends IExposedAttributes {
_short?: boolean;
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "94eead2a-2fbb-4861-90d9-3543783c2442",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,239 @@
/*
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/**
* @packageDocumentation
* @module decorator
*/
import { CCClass, Enum, js, Node } from 'cc';
import { DEV, EDITOR } from 'cc/env';
import { BehaviorComposite } from '../behavior/behavior-composite';
import { BehaviorConditional } from '../behavior/behavior-conditional';
import { BehaviorDecorator } from '../behavior/behavior-decorator';
import { CloneEventOption, TArrayDelegateEvents, TBehaviorEventOption } from '../behavior/behavior-event-declaration';
import { BehaviorParallel } from '../behavior/behavior-parallel';
import { BehaviorSelector } from '../behavior/behavior-selector';
import { BehaviorSequence } from '../behavior/behavior-sequence';
import { BehaviorService } from '../behavior/behavior-service';
import { BehaviorTask } from '../behavior/behavior-task';
import { SharedVariable, SharedDynamic } from '../blackboard/shared-variable';
import { doValidateMethodWithProps_DEV } from './preprocess-class';
import { CACHE_KEY, emptyDecoratorFn, getClassCache, getSubDict, makeEditorClassDecoratorFn, makeSmartClassDecorator } from './tools';
//@ts-ignore
const legacyCC = window.cc;
/**
* @en Declare a standard class as a CCClass, please refer to the [document](https://docs.cocos.com/creator3d/manual/zh/scripting/ccclass.html)
* @zh CC [](https://docs.cocos.com/creator3d/manual/zh/scripting/ccclass.html)。
* @param name - The class name used for serialization.
* @example
* ```ts
* import { _decorator, Component } from 'cc';
* const {ccclass} = _decorator;
*
* // define a CCClass, omit the name
* @ccclass
* class NewScript extends Component {
* // ...
* }
*
* // define a CCClass with a name
* @ccclass('LoginData')
* class LoginData {
* // ...
* }
* ```
*/
export const ccclass: ((name?: string) => ClassDecorator) & ClassDecorator = makeSmartClassDecorator<string>((constructor, name) => {
let base = js.getSuper(constructor);
// console.log("base: ---------> ", constructor.name, base.name);
if (base === Object) {
base = null;
}
const proto = {
name,
extends: base,
ctor: constructor,
};
let properties: any = {};
let options: any = {};
const cache = constructor[CACHE_KEY];
if (cache) {
const decoratedProto = cache.proto;
if (decoratedProto) {
if(EDITOR){
options = getSubDict(decoratedProto, 'options');
properties = getSubDict(decoratedProto, 'properties');
for (const key in properties) {
let value = properties[key];
if(value.type){
let tooltip: string = value.tooltip;
if(tooltip){
let pos1 = tooltip.indexOf("{{");
let pos2 = tooltip.indexOf("}}");
if(pos1>=0&&pos2>0&&pos2>pos1){
let match = tooltip.substring(pos1, pos2+2);
let baseon = tooltip.substring(pos1+2, pos2)
value.tooltip = tooltip.replace(match, baseon);
value.BASEON = baseon;
}
}
if(js.isChildClassOf(value.type, Node)){
delete value.type;
value.TYPE = "cc.Node";
value.path = "";
}
else if(js.isChildClassOf(value.type, SharedVariable)){
value.TYPE = `bt.${value.type.name}`;
delete value.type;
}
else if(js.isChildClassOf(value.type, SharedDynamic)){
value.TYPE = `bt.SharedDynamic`;
delete value.type;
}
else if(typeof value.type == 'object'){
if(Enum.isEnum(value.type)){
value.TYPE = `cc.Enum`;
value.enum = value.type;
}
else{
console.warn(`[oreo-behavior-tree] unsupported property: key=${key} type=`, value.type);
}
delete value.type;
}
else if(typeof value.type == 'function'){
console.error(`[oreo-behavior-tree] unsupported property: key=${key} type=`, value.type);
}
}
}
}
// decoratedProto.properties = createProperties(ctor, decoratedProto.properties);
js.mixin(proto, decoratedProto);
}
constructor[CACHE_KEY] = undefined;
}
const res = CCClass(proto);
// validate methods
if (DEV) {
const propNames = Object.getOwnPropertyNames(constructor.prototype);
for (let i = 0; i < propNames.length; ++i) {
const prop = propNames[i];
if (prop !== 'constructor') {
const desc = Object.getOwnPropertyDescriptor(constructor.prototype, prop);
const func = desc && desc.value;
if (typeof func === 'function') {
doValidateMethodWithProps_DEV(func, prop, js.getClassName(constructor), constructor, base);
}
}
}
}
const frame = legacyCC._RF.peek();
if(!frame || frame.cls != constructor){
console.warn("invalid btclass: ", constructor.name);
return;
}
js._setClassId(frame.uuid, res);
if(EDITOR){
let events: {[key: string]: TBehaviorEventOption} = {};
if(options.events instanceof Array){
options.events.forEach(event => {
events[event] = CloneEventOption();
});
}
let info = {
group: '',
type: '',
name: js.getClassName(constructor),
uuid: frame.uuid,
properties,
events: events,
};
if(js.isChildClassOf(constructor, BehaviorService)){
info.type = 'service';
}
else if(js.isChildClassOf(constructor, BehaviorTask)){
info.type = 'task';
}
else if(js.isChildClassOf(constructor, BehaviorDecorator)){
info.group = 'decorator';
info.type = 'decorator';
if(js.isChildClassOf(constructor, BehaviorConditional)){
info.type = 'condition';
}
}
else if(js.isChildClassOf(constructor, BehaviorComposite)){
info.group = 'composite';
info.type = 'composite';
if(js.isChildClassOf(constructor, BehaviorSelector)){
info.type = 'selector';
}
else if(js.isChildClassOf(constructor, BehaviorSequence)){
info.type = 'sequence';
}
else if(js.isChildClassOf(constructor, BehaviorParallel)){
info.type = 'parallel';
}
}
if(!info.group){
info.group = info.type;
}
// console.log("btclass-registered", info);
Editor.Message.broadcast("btclass-registered", info);
// Editor.Message.send("oreo-behavior-creator", "btclass-registered", info);
// //@ts-ignore
// EditorExtends.emit('btclass-registered');
}
return res;
});
/**
* @zh @delegate(['onUpdate', 'onEnable'])
* @param events: TArrayDelegateEvents
* @example
* @bt.ccclass
* @bt.delegate(['onUpdate', 'onEnable'])
* class NewTask extends bt.Task {
* // ...
* }
*/
export const delegate: (events: TArrayDelegateEvents) => ClassDecorator = EDITOR ? makeEditorClassDecoratorFn('events') : emptyDecoratorFn;
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "1aabef5f-820c-421f-afc2-1822a2c2ec2b",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,264 @@
/*
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { error, errorID, js, warn, warnID } from "cc";
import { DEV, EDITOR, TEST } from "cc/env";
//@ts-ignore
const legacyCC = window.cc;
// import { DEV, EDITOR, TEST } from 'internal:constants';
// import { error, errorID, warn, warnID } from '../../platform/debug';
// import * as js from '../../utils/js';
// import { PrimitiveType } from './attribute';
// import { legacyCC } from '../../global-exports';
// 增加预处理属性这个步骤的目的是降低 CCClass 的实现难度,将比较稳定的通用逻辑和一些需求比较灵活的属性需求分隔开。
const SerializableAttrs = {
default: {},
serializable: {},
editorOnly: {},
formerlySerializedAs: {},
};
/**
* notify
*/
function parseNotify (val, propName, notify, properties) {
if (val.get || val.set) {
if (DEV) {
warnID(5500);
}
return;
}
if (val.hasOwnProperty('default')) {
// 添加新的内部属性,将原来的属性修改为 getter/setter 形式
// (以 _ 开头将自动设置property 为 visible: false
const newKey = `_N$${propName}`;
val.get = function () {
return this[newKey];
};
val.set = function (value) {
const oldValue = this[newKey];
this[newKey] = value;
notify.call(this, oldValue);
};
const newValue = {};
properties[newKey] = newValue;
// 将不能用于get方法中的属性移动到newValue中
for (const attr in SerializableAttrs) {
const v = SerializableAttrs[attr];
if (val.hasOwnProperty(attr)) {
newValue[attr] = val[attr];
if (!v.canUsedInGet) {
delete val[attr];
}
}
}
} else if (DEV) {
warnID(5501);
}
}
function parseType (val, type, className, propName) {
const STATIC_CHECK = (EDITOR && DEV) || TEST;
if (Array.isArray(type)) {
if (STATIC_CHECK && 'default' in val) {
if (!legacyCC.Class.isArray(val.default)) {
warnID(5507, className, propName);
}
}
if (type.length > 0) {
val.type = type = type[0];
} else {
return errorID(5508, className, propName);
}
}
if (typeof type === 'function') {
if (type === String) {
val.type = legacyCC.String;
if (STATIC_CHECK) {
warnID(3608, `"${className}.${propName}"`);
}
} else if (type === Boolean) {
val.type = legacyCC.Boolean;
if (STATIC_CHECK) {
warnID(3609, `"${className}.${propName}"`);
}
} else if (type === Number) {
val.type = legacyCC.Float;
if (STATIC_CHECK) {
warnID(3610, `"${className}.${propName}"`);
}
}
} else if (STATIC_CHECK) {
switch (type) {
case 'Number':
warnID(5510, className, propName);
break;
case 'String':
warn(`The type of "${className}.${propName}" must be CCString, not "String".`);
break;
case 'Boolean':
warn(`The type of "${className}.${propName}" must be CCBoolean, not "Boolean".`);
break;
case 'Float':
warn(`The type of "${className}.${propName}" must be CCFloat, not "Float".`);
break;
case 'Integer':
warn(`The type of "${className}.${propName}" must be CCInteger, not "Integer".`);
break;
case null:
warnID(5511, className, propName);
break;
}
}
if (EDITOR && typeof type === 'function') {
if (legacyCC.Class._isCCClass(type) && val.serializable !== false && !js._getClassId(type, false)) {
warnID(5512, className, propName, className, propName);
}
}
}
function getBaseClassWherePropertyDefined_DEV (propName, cls) {
if (DEV) {
let res;
for (; cls && cls.__props__ && cls.__props__.indexOf(propName) !== -1; cls = cls.$super) {
res = cls;
}
if (!res) {
error('unknown error');
}
return res;
}
}
function _wrapOptions (isGetset: boolean, _default, type?: Function | Function[]) {
const res: {
default?: any,
_short?: boolean,
type?: any,
} = isGetset ? { _short: true } : { _short: true, default: _default };
if (type) {
res.type = type;
}
return res;
}
export class PrimitiveType<T> {
public name: string;
public default: T;
constructor (name: string, defaultValue: T) {
this.name = name;
this.default = defaultValue;
}
public toString () {
return this.name;
}
}
export function getFullFormOfProperty (options, isGetset) {
const isLiteral = options && options.constructor === Object;
if (!isLiteral) {
if (Array.isArray(options) && options.length > 0) {
return _wrapOptions(isGetset, [], options);
} else if (typeof options === 'function') {
const type = options;
return _wrapOptions(isGetset, js.isChildClassOf(type, legacyCC.ValueType) ? new type() : null, type);
} else if (options instanceof PrimitiveType) {
return _wrapOptions(isGetset, options.default);
} else {
return _wrapOptions(isGetset, options);
}
}
return null;
}
export function preprocessAttrs (properties, className, cls) {
for (const propName in properties) {
let val = properties[propName];
const fullForm = getFullFormOfProperty(val, false);
if (fullForm) {
val = properties[propName] = fullForm;
}
if (val) {
if (EDITOR) {
if ('default' in val) {
if (val.get) {
errorID(5513, className, propName);
} else if (val.set) {
errorID(5514, className, propName);
} else if (legacyCC.Class._isCCClass(val.default)) {
val.default = null;
errorID(5515, className, propName);
}
}
}
if (DEV && !val.override && cls.__props__.indexOf(propName) !== -1) {
// check override
const baseClass = js.getClassName(getBaseClassWherePropertyDefined_DEV(propName, cls));
warnID(5517, className, propName, baseClass, propName);
}
const notify = val.notify;
if (notify) {
if (DEV) {
error('not yet support notify attributes.');
} else {
parseNotify(val, propName, notify, properties);
}
}
if ('type' in val) {
parseType(val, val.type, className, propName);
}
}
}
}
const CALL_SUPER_DESTROY_REG_DEV = /\b\._super\b|destroy.*\.call\s*\(\s*\w+\s*[,|)]/;
export function doValidateMethodWithProps_DEV (func, funcName, className, cls, base) {
if (cls.__props__ && cls.__props__.indexOf(funcName) >= 0) {
// find class that defines this method as a property
const baseClassName = js.getClassName(getBaseClassWherePropertyDefined_DEV(funcName, cls));
errorID(3648, className, funcName, baseClassName);
return false;
}
if (funcName === 'destroy'
&& js.isChildClassOf(base, legacyCC.Component)
&& !CALL_SUPER_DESTROY_REG_DEV.test(func)
) {
error(`Overwriting '${funcName}' function in '${className}' class without calling super is not allowed. Call the super function in '${funcName}' please.`);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "3eb5d8a6-f527-404e-a3ab-471c4331a344",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,190 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-21 21:07:10
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-13 18:18:50
* @Description:
*/
/*
/**
* @packageDocumentation
* @module decorator
*/
import { CCBoolean, CCFloat, CCInteger, CCString, errorID, js, warnID } from "cc";
import { BUILD, DEV, EDITOR, TEST } from "cc/env";
// import { DEV, EDITOR, TEST } from 'internal:constants';
// import { CCString, CCInteger, CCFloat, CCBoolean } from '../utils/attribute';
import { IExposedAttributes } from './attribute-defines';
import { LegacyPropertyDecorator, getSubDict, getClassCache } from './tools';
// import { warnID, errorID } from '../../platform/debug';
// import { js } from '../../utils/js';
import { getFullFormOfProperty } from './preprocess-class';
export type SimplePropertyType = Function | string | typeof CCString | typeof CCInteger | typeof CCFloat | typeof CCBoolean;
export type PropertyType = SimplePropertyType | SimplePropertyType[];
/**
* @zh CCClass
* @en CCClass property options
*/
export type IPropertyOptions = IExposedAttributes
/**
* @en Declare as a CCClass property with options
* @zh CCClass
* @param options property options
*/
export function property (options?: IPropertyOptions): LegacyPropertyDecorator;
/**
* @en Declare as a CCClass property with the property type
* @zh cc <br/>
* `@property({type})`
* @param type A {{ccclass}} type or a {{ValueType}}
*/
export function property (type: PropertyType): LegacyPropertyDecorator;
/**
* @en Declare as a CCClass property
* @zh cc <br/>
* `@property()`
*/
export function property (...args: Parameters<LegacyPropertyDecorator>): void;
export function property (
target?: Parameters<LegacyPropertyDecorator>[0],
propertyKey?: Parameters<LegacyPropertyDecorator>[1],
descriptor?: Parameters<LegacyPropertyDecorator>[2],
) {
let options: IPropertyOptions | PropertyType | null = null;
function normalized (
target: Parameters<LegacyPropertyDecorator>[0],
propertyKey: Parameters<LegacyPropertyDecorator>[1],
descriptor: Parameters<LegacyPropertyDecorator>[2],
) {
const cache = getClassCache(target.constructor);
if (cache) {
const ccclassProto = getSubDict(cache, 'proto');
const properties = getSubDict(ccclassProto, 'properties');
genProperty(target.constructor, properties, propertyKey, options, descriptor, cache);
}
}
if (target === undefined) {
// @property() => LegacyPropertyDecorator
return property({
type: undefined,
});
} else if (typeof propertyKey === 'undefined') {
// @property(options) => LegacyPropertyDescriptor
// @property(type) => LegacyPropertyDescriptor
options = target;
return normalized;
} else {
// @property
normalized(target, propertyKey, descriptor);
}
}
function getDefaultFromInitializer (initializer) {
let value;
try {
value = initializer();
} catch (e) {
// just lazy initialize by CCClass
return initializer;
}
if (typeof value !== 'object' || value === null) {
// string boolean number function undefined null
return value;
} else {
// The default attribute will not be used in ES6 constructor actually,
// so we don't need to simplify into `{}` or `[]` or vec2 completely.
return initializer;
}
}
function extractActualDefaultValues (ctor) {
let dummyObj;
try {
dummyObj = new ctor();
} catch (e) {
if (DEV) {
warnID(3652, js.getClassName(ctor), e);
}
return {};
}
return dummyObj;
}
function genProperty (
ctor,
properties,
propertyKey: Parameters<LegacyPropertyDecorator>[1],
options,
descriptor: Parameters<LegacyPropertyDecorator>[2] | undefined,
cache,
) {
let fullOptions;
const isGetset = descriptor && (descriptor.get || descriptor.set);
if (options) {
fullOptions = getFullFormOfProperty(options, isGetset);
}
const existsPropertyRecord = properties[propertyKey];
const propertyRecord = js.mixin(existsPropertyRecord || {}, fullOptions || options || {});
if (isGetset) {
// typescript or babel
if (DEV && options && ((fullOptions || options).get || (fullOptions || options).set)) {
const errorProps = getSubDict(cache, 'errorProps');
if (!errorProps[propertyKey]) {
errorProps[propertyKey] = true;
warnID(3655, propertyKey, js.getClassName(ctor), propertyKey, propertyKey);
}
}
if (descriptor!.get) {
propertyRecord.get = descriptor!.get;
}
if (descriptor!.set) {
propertyRecord.set = descriptor!.set;
}
} else { // Target property is non-accessor
if (DEV && (propertyRecord.get || propertyRecord.set)) {
// Specify "accessor options" for non-accessor property is forbidden.
errorID(3655, propertyKey, js.getClassName(ctor), propertyKey, propertyKey);
return;
}
if (descriptor) {
// In case of Babel, if an initializer is given for class field.
// That initializer is passed to `descriptor.initializer`.
// babel
if (descriptor.initializer) {
propertyRecord.default = getDefaultFromInitializer(descriptor.initializer);
}
} else {
// In case of TypeScript, we can not directly capture the initializer.
// We have to be hacking to extract the value.
const actualDefaultValues = cache.default || (cache.default = extractActualDefaultValues(ctor));
if (actualDefaultValues.hasOwnProperty(propertyKey)) {
propertyRecord.default = actualDefaultValues[propertyKey];
}
}
if ((EDITOR && !BUILD) || TEST) {
if (!fullOptions && options && options.hasOwnProperty('default')) {
warnID(3653, propertyKey, js.getClassName(ctor));
}
}
}
properties[propertyKey] = propertyRecord;
// console.log("property: ", js.getClassName(ctor), propertyKey, propertyRecord);
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "82f57f20-9c27-4ac5-a013-170478a42254",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,165 @@
/*
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { CCClass, error, js } from "cc";
import { DEV } from "cc/env";
/**
* @packageDocumentation
* @module decorator
*/
// import { DEV } from 'internal:constants';
// import { CCClass } from '../class';
// import { error } from '../../platform/debug';
// import { js } from '../../utils/js';
export type BabelPropertyDecoratorDescriptor = PropertyDescriptor & { initializer?: any };
/**
* @en
* The signature compatible with both TypeScript legacy decorator and Babel legacy decorator.
* The `descriptor` argument will only appear in Babel case.
* @zh
* TypeScript legacy Babel legacy
* `descriptor` Babel
*/
export type LegacyPropertyDecorator = (target: Object, propertyKey: string | symbol, descriptor?: BabelPropertyDecoratorDescriptor) => void;
/**
* @en
* A class(or property) decorator which does nothing.
* @zh
*
*/
export const emptyDecorator: ClassDecorator & LegacyPropertyDecorator = () => {};
/**
* @en
* A function which ignore all arguments and return the `emptyDecorator`.
* @zh
* `emptyDecorator`
*/
export const emptyDecoratorFn = () => emptyDecorator;
/**
* @en
* Acts like `emptyDecorator` if called in form of `@x`;
* acts like `emptyDecoratorFn` if called in form of `@x(...args)`.
* @zh
* `@x` `emptyDecorator` `@x(...args)` `emptyDecoratorFn`
*/
export const emptySmartClassDecorator = makeSmartClassDecorator(() => {});
/**
* @en
* Make a smart class decorator which can properly handle the following form decorator syntax:
* - `@x`
* - `@x(arg0)`
*
* and forward both the decorated class and the `arg0` (in first form, `arg0` is forward as `undefined`) to
* `decorate`.
* @zh
*
* - `@x`
* - `@x(arg0)`
*
* `arg0``arg0` `undefined` `decorate`
* @param decorate
*/
export function makeSmartClassDecorator<TArg> (
decorate: <TFunction extends Function>(constructor: TFunction, arg?: TArg) => ReturnType<ClassDecorator>,
): ClassDecorator & ((arg?: TArg) => ClassDecorator) {
return proxyFn;
function proxyFn(...args: Parameters<ClassDecorator>): ReturnType<ClassDecorator>;
function proxyFn(arg?: TArg): ClassDecorator;
function proxyFn (target?: Parameters<ClassDecorator>[0] | TArg): ReturnType<ClassDecorator> {
if (typeof target === 'function') {
// If no parameter specified
return decorate(target);
} else {
return function <TFunction extends Function> (constructor: TFunction) {
return decorate(constructor, target);
};
}
}
}
function writeEditorClassProperty<TValue> (constructor: Function, propertyName: string, value: TValue) {
const cache = getClassCache(constructor, propertyName);
if (cache) {
const proto = getSubDict(cache, 'proto');
let options = getSubDict(proto, 'options');
options[propertyName] = value;
}
}
/**
* @en
* Make a function which accept an argument value and return a class decorator.
* The decorator sets the editor property `propertyName`, on the decorated class, into that argument value.
* @zh
*
* `propertyName`
* @param propertyName The editor property.
*/
export function makeEditorClassDecoratorFn<TValue> (propertyName: string): (value: TValue) => ClassDecorator {
return (value: TValue) => <TFunction extends Function>(constructor: TFunction) => {
writeEditorClassProperty(constructor, propertyName, value);
};
}
/**
* Make a smart class decorator.
* The smart decorator sets the editor property `propertyName`, on the decorated class, into:
* - `defaultValue` if the decorator is called with `@x` form, or
* - the argument if the decorator is called with an argument, eg, the `@x(arg0)` form.
* @zh
*
* `propertyName`
* - `@x` `defaultValue`
* - `@x(arg0)`
* @param propertyName The editor property.
*/
export function makeSmartEditorClassDecorator<TValue> (propertyName: string, defaultValue: TValue) {
return makeSmartClassDecorator<TValue>((constructor, decoratedValue?: TValue) => {
writeEditorClassProperty(constructor, propertyName, (defaultValue !== undefined) ? defaultValue : decoratedValue);
});
}
// caches for class construction
export const CACHE_KEY = '__ccclassCache__';
export function getClassCache (ctor, decoratorName?) {
if (DEV && CCClass._isCCClass(ctor)) {
error('`@%s` should be used after @ccclass for class "%s"', decoratorName, js.getClassName(ctor));
return null;
}
return getSubDict(ctor, CACHE_KEY);
}
export function getSubDict (obj, key) {
return obj[key] || (obj[key] = {});
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "69d4c7f7-18ed-4452-a74f-50d6db40c57f",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,49 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:04:47
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-26 17:25:51
* @Description:
*/
export * from "./behavior/behavior-composite";
export * from "./behavior/behavior-conditional";
export * from "./behavior/behavior-decorator";
export * from "./behavior/behavior-event-declaration";
export * from "./behavior/behavior-event-delegate";
export * from "./behavior/behavior-event-handler";
export * from "./behavior/behavior-event-target";
export * from "./behavior/behavior-node-element";
export * from "./behavior/behavior-node-entity";
export * from "./behavior/behavior-node-interface";
export * from "./behavior/behavior-node";
export * from "./behavior/behavior-selector";
export * from "./behavior/behavior-sequence";
export * from "./behavior/behavior-parallel";
export * from "./behavior/behavior-service";
export * from "./behavior/behavior-status";
export * from "./behavior/behavior-task";
export * from "./behavior/behavior-tree-interface";
export * from "./behavior/behavior-tree-utils";
export * from "./blackboard/blackboard-declaration";
export * from "./blackboard/blackboard";
export * from "./blackboard/shared-array";
export * from "./blackboard/shared-boolean";
export * from "./blackboard/shared-custom";
export * from "./blackboard/shared-node";
export * from "./blackboard/shared-number";
export * from "./blackboard/shared-string";
export * from "./blackboard/shared-variable";
export * from "./decorators/btclass";
export * from "./decorators/property";
export * from "./utils/utils";
export * from "./utils/logger";
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "3cf28b90-1cb9-44c3-abe4-e30f4a03aefd",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "8bb41585-a9fe-4cd0-ad72-d904d4c652d4",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "b541da94-35a3-4d86-9f0e-1334a78c2e24",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,17 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:29:32
* @LastEditors: OreoWang
* @LastEditTime: 2022-03-22 17:20:11
* @Description:
*/
const SHOW_LOG = true;
export const logger = {
log: SHOW_LOG?console.log.bind(console, "[LOG]"):()=>{},
warn: SHOW_LOG?console.warn.bind(console, "[WARN]"):()=>{},
error: SHOW_LOG?console.error.bind(console, "[ERROR]"):()=>{},
debug: SHOW_LOG?console.debug.bind(console, "[DEBUG]"):()=>{},
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "37b5d1b0-d9d4-40fa-8378-9adf28df5c95",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "5bb2d5d7-ed47-480f-acf2-1745f492bc42",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,140 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-03-22 21:29:32
* @LastEditors: OreoWang
* @LastEditTime: 2022-03-22 17:20:11
* @Description:
*/
// const SHOW_LOG = true;
export const utils = {
// log: SHOW_LOG?console.log.bind(console, "[LOG]"):()=>{},
// warn: SHOW_LOG?console.warn.bind(console, "[WARN]"):()=>{},
// error: SHOW_LOG?console.error.bind(console, "[ERROR]"):()=>{},
// debug: SHOW_LOG?console.debug.bind(console, "[DEBUG]"):()=>{},
/**
*
*/
isString: function (val) {
return typeof val === 'string'
},
/**
*
*/
isBoolean: function (val) {
return typeof val === 'boolean'
},
/**
*
*/
isNumber: function (val) {
return typeof val === 'number'
},
/**
* typeof
*/
typeOf: function (value) {
let s = typeof value
if (s === 'object') {
if (value) {
if (value instanceof Array) {
return 'array'
} else if (value instanceof Object) {
return s
}
let className = Object.prototype.toString.call(/** @type {!Object} */(value))
if (className === '[object Window]') {
return 'object'
}
// 判断是否为数组类型
if (className === '[object Array]' ||
(typeof value.length === 'number' &&
typeof value.splice !== 'undefined' &&
typeof value.propertyIsEnumerable !== 'undefined' &&
!value.propertyIsEnumerable('splice'))) {
return 'array'
}
// 判断是否为函数类型
if (className === '[object Function]' ||
(typeof value.call !== 'undefined' &&
typeof value.propertyIsEnumerable !== 'undefined' &&
!value.propertyIsEnumerable('call'))) {
return 'function'
}
} else {
return 'null'
}
} else if (s === 'function' && typeof value.call === 'undefined') {
return 'object'
}
return s
},
/**
*
*/
isNull: function (val) {
return val == null
},
/**
*
*/
isFunction: function (val) {
return utils.typeOf(val) === 'function'
},
/**
*
*/
isObject: function (val) {
var type = typeof val
return (type === 'object' && val != null) || type === 'function'
},
/**
*
* @param obj
* @param key
* @param val
* @param enumerable
*/
def: function (obj, key, val, enumerable = false) {
if(!utils.isObject(obj)) return;
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
},
/**
*
*/
clone(obj) {
if (null == obj || "object" != typeof obj) return obj;
if (obj instanceof Array || obj instanceof Object) {
var copy = (obj instanceof Array) ? [] : {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr))
copy[attr] = this.clone(obj[attr]);
}
return copy;
}
return obj
},
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "b3dc9700-2ef4-49ef-b6d0-175ddb9c74aa",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "4a581265-6ff2-4b28-bc1d-a9624b75912f",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}
@@ -0,0 +1,2 @@
- 这里主要展示各类型节点的继承方式
- 为了不污染编辑器中的节点类型,各文件中已注释掉 @bt.ccclass(xxx),在实际使用时,需要取消注释才能向编辑器注册节点
@@ -0,0 +1,11 @@
{
"ver": "1.0.2",
"importer": "text",
"imported": true,
"uuid": "bd6fb0b0-bdb8-4aca-afba-ba1d72c7aec8",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,25 @@
/*
* @Author: OreoWang
* @Email: ihc523@163.com
* @Date: 2022-04-14 12:13:53
* @LastEditors: OreoWang
* @LastEditTime: 2022-04-22 21:05:13
* @Description:
*/
import * as bt from "../main"
// @bt.ccclass("TestCondition")
export class TestCondition extends bt.Conditional {
@bt.property({
type: bt.SharedNumber,
})
count: bt.SharedNumber = null;
@bt.property({
type: bt.SharedNode,
})
target: bt.SharedNode = null;
load(): void {
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "bdb1465b-c734-40ee-a4bb-3e0dcee75ae6",
"files": [],
"subMetas": {},
"userData": {}
}

Some files were not shown because too many files have changed in this diff Show More