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