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

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "20169a05-4939-4f5c-8cc5-cb46d02353a2",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -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 {
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "57329531-3fd5-4e01-af0c-7224cf8483a6",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "0047be34-4c09-4525-a687-e428f9a9b00f",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
})
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "aadd8789-6a9d-43da-aa6a-08a96c22acea",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "e021fcd1-5fdd-4265-b7e2-b1677d99aa04",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "b984fcf4-64af-453f-b850-c2a4a8c63935",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "1ccd7aaf-8fa6-4e7d-9f9f-f1f3efa54f09",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "896d1aa0-bf42-4a94-a570-b3d697ce5c90",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "9a75f94e-4879-4193-bf1f-068858fa88e6",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "b69ca0bc-cd0a-4eb4-a933-16ab1e9e100c",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "a8421e4d-2f1b-4682-abae-ac35a211c620",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "94a8a794-69d1-43f7-9645-587339155ebb",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "45a0138c-34cd-4cda-9adc-72a79ae5b000",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "fc7c1aca-767a-4f0e-b044-c68525175231",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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 &?"
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "c3b96b68-f4a2-4842-9387-54e37444820a",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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 &@"
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "b1634f8a-3e56-422b-9be3-18fba853fb67",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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]>;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "87489bb0-ed6c-4858-bcd0-8d2df16c49f3",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "350f3e0b-8ce3-447b-806c-8774edb942f4",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "8944dcea-05e3-4d6b-b6d2-6e695b0fc717",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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]>;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "321f5098-933f-4df8-82c0-d6630ea4e9de",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "bfa1f9c5-63fa-49d0-b812-f8945a7403d6",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "d187fd32-d28b-4feb-a544-bd09d1daf092",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "db4c226a-e591-429f-85ce-431da6014933",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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} `);
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "561ac289-ce77-4380-81c5-4c8c658f3277",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "725cc9b3-cb0c-471f-ad40-61d46b34f47c",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "32365a9f-4afa-41ef-9b02-43d2933043c9",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "59a0c931-0f73-470b-96ef-13276d1555cf",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "d33b70da-842d-4600-990a-18cef2ef057b",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "8fed9137-b8a3-457f-9f5d-1415b31e97fd",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "a226a00c-a22b-4ceb-b0cb-5460e1f45e73",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "effa631a-9dab-465d-848f-00ff6ee553e6",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;
// }
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "598da6a4-fc00-4aaf-864c-8d7371d54ecb",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "a1c71c21-0a82-4733-a7dd-5954f90c94b1",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -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 {
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "1cc920d6-87dd-4369-9e7f-832428d0e2ae",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "95e19727-fffb-4846-8dc8-c72825802c70",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "259766ca-379e-4f9d-9116-6128c72cd14c",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "bd58f3fa-65d8-45e7-89f0-cd21ac4bd22d",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "26f030af-e0cf-4d11-8d36-b6f572685316",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "150572af-2b72-44de-86e3-161d038d34fc",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "affcec40-7522-4f92-a8ff-5600f3140517",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "a5dd5afa-2306-4e03-a456-8aa72af30456",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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 {
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "58b35a2b-1f6e-4f3f-82cb-98257e3010f6",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "d35544ca-a4f2-4bdc-b3c4-bdb65e991b49",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "94eead2a-2fbb-4861-90d9-3543783c2442",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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;

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "1aabef5f-820c-421f-afc2-1822a2c2ec2b",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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.`);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "3eb5d8a6-f527-404e-a3ab-471c4331a344",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "82f57f20-9c27-4ac5-a013-170478a42254",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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] = {});
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "69d4c7f7-18ed-4452-a74f-50d6db40c57f",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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";

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "3cf28b90-1cb9-44c3-abe4-e30f4a03aefd",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "8bb41585-a9fe-4cd0-ad72-d904d4c652d4",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "b541da94-35a3-4d86-9f0e-1334a78c2e24",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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]"):()=>{},
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "37b5d1b0-d9d4-40fa-8378-9adf28df5c95",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "5bb2d5d7-ed47-480f-acf2-1745f492bc42",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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
},
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "b3dc9700-2ef4-49ef-b6d0-175ddb9c74aa",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "4a581265-6ff2-4b28-bc1d-a9624b75912f",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,2 @@
- 这里主要展示各类型节点的继承方式
- 为了不污染编辑器中的节点类型,各文件中已注释掉 @bt.ccclass(xxx),在实际使用时,需要取消注释才能向编辑器注册节点

View File

@@ -0,0 +1,11 @@
{
"ver": "1.0.2",
"importer": "text",
"imported": true,
"uuid": "bd6fb0b0-bdb8-4aca-afba-ba1d72c7aec8",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -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 {
}
}

View File

@@ -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