唯杰地图 logo唯杰地图
VJMAP
EN

行为组件

vjmap3d/references/advance/behavior.md 17.0 KB419 行
原文

Behavior 行为组件是基于行为树(Behavior Tree)的概念,这是一种用于设计的强大方法,相比传统的状态机,它在表达复杂的决策逻辑和行为序列上更为直观和灵活。

行为树的基本组成部分:

  1. Actions(行为节点):

    • 这些是最基础的节点,代表了可以执行的具体行为,如移动到指定位置、播放动画等。它们通常是行为树的叶子节点,直接产生动作而没有子节点。
  2. Composites(组合节点):

    • 组合节点用来组织和控制其他节点(子节点)的执行逻辑。主要包括:
      • Sequence(顺序节点):子节点按顺序执行,只有当所有子节点成功执行后,该Sequence节点才被视为成功。
      • Selector(选择节点):从左至右尝试执行子节点,一旦有一个子节点成功,该Selector节点即停止尝试后续节点,并视为成功。
      • Parallel(并行节点):根据设置可以同时或当所有/任意子节点满足条件时决定其成功或失败状态。
  3. Decorator(装饰节点):

    • 装饰节点为子节点添加额外的逻辑条件或修改其行为结果,例如重复执行、在一定时间内尝试、逻辑非(取反子节点结果)等,只有一个子节点,并且会影响其输出。
  4. Condition(条件节点):

    • 虽然未直接提及于原始信息中,但条件节点在行为树中常用于基于某些条件来决定是否执行后续行为,它们本质上是一种特殊的Action,返回True或False来表示条件是否满足。

常用行为组件

行为组件名 描述
ConditionalBehavior 判断条件行为组件
LogMessageBehavior 日志输出行为组件
ActionBehavior 自定义行为组件
DelayBehavior 延迟行为组件
ParallelBehavior 并行行为组件
SequenceBehavior 顺序行为组件
DestoryBehavior 销毁行为组件
PromiseBehavior Promise行为组件
SucceedingBehavior 成功行为组件
FailingBehavior 失败行为组件
RepeatBehavior 重复行为组件
SendEventBehavior 发送事件行为组件
WaitForEventBehavior 等待事件行为组件
BranchBehavior 分支行为组件(相当于 if/else:BranchBehavior.from(条件, 成功分支, 失败分支))
FilterBehavior 过滤行为组件
RepeatUntilFailureBehavior 重复直到失败行为组件
CompositeBehavior 组合行为组件
IgnoreFailureBehavior 忽略失败行为组件
RepeatUntilSuccessBehavior 重复直至成功行为组件
ConditionBehavior 自定义条件行为组件
InvertStatusBehavior 反转行为组件
SelectorBehavior 选择行为组件
RotationBehavior 旋转行为组件
AnimationBehavior 动画行为组件

用法

简单示例

let entity = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/car.glb", {
    toEntity: true
});
entity.scale.set(5, 5, 5)
entity.addTo(app);
// 下面代码行为树逻辑
// - 循环五次
//   --  顺序执行
//       --- 选中
//       --- 等3秒
//       --- 不选中,辉光
//       --- 等5秒
//       --- 不辉光
let behavior = vjmap3d.RepeatBehavior.from(vjmap3d.SequenceBehavior.from([
    new vjmap3d.ActionBehavior(() => { entity.selected = true }),
    vjmap3d.DelayBehavior.from(3),
    new vjmap3d.ActionBehavior(() => {
        entity.selected = false;
        entity.bloom = true;
    }),
    vjmap3d.DelayBehavior.from(5),
    new vjmap3d.ActionBehavior(() => { entity.bloom = false })
]), 5)

behavior.onFinalized.add(() => {
    app.logInfo("行为组件执行结束", "success")
})
entity.add(vjmap3d.BehaviorModule, behavior)

⚠️ entity.add(BehaviorModule, b) 只对首个行为生效:EntityBase.add 在模块已存在时直接 return findModule——第二个参数静默丢弃(行为既不入队也不 initialize)。同一实体追加行为必须走 entity.getModule(vjmap3d.BehaviorModule).addBehavior(b)(或首次就 addModule 拿模块再 addBehavior)。

⚠️ Behavior 实例没有 .status 字段:状态是 tick() 的返回值(BehaviorStatus.Running/Succeeded/…),完成/中断经 onFinalized 信号通知;别去 b.status 上轮询。

ℹ️ DestoryBehavior 的拼写错误是导出名本身(Destory 非 Destroy)——照抄正确名 vjmap3d.DestoryBehavior.create()。

let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {
        gridHelper: { visible: true } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
let entity = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/car.glb", {
    toEntity: true
});
entity.scale.set(5, 5, 5)
entity.addTo(app);
// 下面代码行为树逻辑
// - 循环五次
//   --  顺序执行
//       --- 选中
//       --- 等3秒
//       --- 不选中,辉光
//       --- 等5秒
//       --- 不辉光
let behavior = vjmap3d.RepeatBehavior.from(vjmap3d.SequenceBehavior.from([
    new vjmap3d.ActionBehavior(() => { entity.selected = true }),
    vjmap3d.DelayBehavior.from(3),
    new vjmap3d.ActionBehavior(() => {
        entity.selected = false;
        entity.bloom = true;
    }),
    vjmap3d.DelayBehavior.from(5),
    new vjmap3d.ActionBehavior(() => { entity.bloom = false })
]), 5)

behavior.onFinalized.add(() => { app.logInfo("行为组件执行结束", "success") }) entity.add(vjmap3d.BehaviorModule, behavior)

复杂示例

let entity = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/car.glb", {
    splitSubEntity: true,
    toEntity: true,
});
entity.scale.set(5, 5, 5)
entity.addTo(app);
const aEntry = new vjmap3d.AnimationTrack(['scale', 'alpha', 'v']);
aEntry.addKey(0, [1, 0.2, 0]);
aEntry.addKey(3, [2, 1, 0.8]);
aEntry.addTransition(0, vjmap3d.Easings.easeInCubic);
//
const aExit = new vjmap3d.AnimationTrack(['scale', 'alpha', 'v']);
aExit.addKey(0, [2, 1, 0]);
aExit.addKey(4, [1, 0, 1]);
aExit.addTransition(0, vjmap3d.Easings.easeInOutCubic);
//
// 下面代码行为树逻辑
// - 顺序执行
//   -- 循环六次
//       --- 动画行为
//       --- 并行,有一个满足退出 (让旋转执行五秒后退出)
//       --- 动画行为(反方向)
//   -- 删除实体
let oldScale = entity.scale.x
entity.add(vjmap3d.BehaviorModule, vjmap3d.SequenceBehavior.from([
    vjmap3d.RepeatBehavior.from(vjmap3d.SequenceBehavior.from([
        new vjmap3d.AnimationBehavior(new vjmap3d.AnimationTrackPlayback(aEntry, (scale, alpha, v) => {
            entity.scale.set(oldScale * scale, oldScale *  scale, oldScale * scale);
            entity.setOpacity(alpha);
        })),
        vjmap3d.ParallelBehavior.from([
            vjmap3d.RotationBehavior.fromJSON({resetWhenFinalize: true}),
            vjmap3d.DelayBehavior.from(5)
        ], vjmap3d.ParallelBehaviorPolicy.RequireOne),
        new vjmap3d.AnimationBehavior(new vjmap3d.AnimationTrackPlayback(aExit, (scale, alpha, v) => {
            entity.scale.set(oldScale * scale, oldScale * scale, oldScale * scale);
            entity.setOpacity(alpha);
        }))
    ]), 6),
    vjmap3d.DestoryBehavior.create() // 删除实体
]))
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {
        gridHelper: { visible: true } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
let entity = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/car.glb", {
    splitSubEntity: true,
    toEntity: true,
});
entity.scale.set(5, 5, 5)
entity.addTo(app);
const aEntry = new vjmap3d.AnimationTrack(['scale', 'alpha', 'v']);
aEntry.addKey(0, [1, 0.2, 0]);
aEntry.addKey(3, [2, 1, 0.8]);
aEntry.addTransition(0, vjmap3d.Easings.easeInCubic);
//
const aExit = new vjmap3d.AnimationTrack(['scale', 'alpha', 'v']);
aExit.addKey(0, [2, 1, 0]);
aExit.addKey(4, [1, 0, 1]);
aExit.addTransition(0, vjmap3d.Easings.easeInOutCubic);
//
// 下面代码行为树逻辑
// - 顺序执行
//   -- 循环六次
//       --- 动画行为
//       --- 并行,有一个满足退出 (让旋转执行五秒后退出)
//       --- 动画行为(反方向)
//   -- 删除实体
let oldScale = entity.scale.x
entity.add(vjmap3d.BehaviorModule, vjmap3d.SequenceBehavior.from([
    vjmap3d.RepeatBehavior.from(vjmap3d.SequenceBehavior.from([
        new vjmap3d.AnimationBehavior(new vjmap3d.AnimationTrackPlayback(aEntry, (scale, alpha, v) => {
            entity.scale.set(oldScale * scale, oldScale *  scale, oldScale * scale);
            entity.setOpacity(alpha);
        })),
        vjmap3d.ParallelBehavior.from([
            vjmap3d.RotationBehavior.fromJSON({resetWhenFinalize: true}),
            vjmap3d.DelayBehavior.from(5)
        ], vjmap3d.ParallelBehaviorPolicy.RequireOne),
        new vjmap3d.AnimationBehavior(new vjmap3d.AnimationTrackPlayback(aExit, (scale, alpha, v) => {
            entity.scale.set(oldScale * scale, oldScale * scale, oldScale * scale);
            entity.setOpacity(alpha);
        }))
    ]), 6),
    vjmap3d.DestoryBehavior.create() // 删除实体
]))

事件行为示例

let entity = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/car.glb", {
    splitSubEntity: true,
    toEntity: true,
});
entity.scale.set(5, 5, 5)
entity.addTo(app);
entity.pointerEvents = true;
entity.add(vjmap3d.BehaviorModule, vjmap3d.ParallelBehavior.from([
    vjmap3d.RepeatBehavior.from(vjmap3d.SequenceBehavior.from([
        vjmap3d.WaitForEventBehavior.fromJSON({ event: "mouseover"}),
        new vjmap3d.ActionBehavior(() => { entity.outline = true }),
    ])),
    vjmap3d.RepeatBehavior.from(vjmap3d.SequenceBehavior.from([
        vjmap3d.WaitForEventBehavior.fromJSON({ event: "mouseout"}),
        new vjmap3d.ActionBehavior(() => { entity.outline = false }),
    ]))
]));
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {
        gridHelper: { visible: true } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
let entity = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/car.glb", {
    splitSubEntity: true,
    toEntity: true,
});
entity.scale.set(5, 5, 5)
entity.addTo(app);
entity.pointerEvents = true;
entity.add(vjmap3d.BehaviorModule, vjmap3d.ParallelBehavior.from([
    vjmap3d.RepeatBehavior.from(vjmap3d.SequenceBehavior.from([
        vjmap3d.WaitForEventBehavior.fromJSON({ event: "mouseover"}),
        new vjmap3d.ActionBehavior(() => { entity.outline = true }),
    ])),
    vjmap3d.RepeatBehavior.from(vjmap3d.SequenceBehavior.from([
        vjmap3d.WaitForEventBehavior.fromJSON({ event: "mouseout"}),
        new vjmap3d.ActionBehavior(() => { entity.outline = false }),
    ]))
]));

自定义行为组件

// 自定义闪烁行为组件
class FlashBehavior extends vjmap3d.Behavior {
    constructor() {
        super();
        this.typeName = "FlashBehavior"
        this.seconds = 30; // 闪烁时间,默认30s
        this.totalTimeDelta = 0; // 运行总共花的时间
    }
    static fromJSON(param = {}) {
        const r = new FlashBehavior();
        r.fromJSON(param);
        return r;
    }
    fromJSON(param = {}) {
        this.seconds = param?.seconds ?? 30; // 与构造函数的默认值保持一致(秒)
    }
    initialize(context) {
        super.initialize(context);
        this.context.entity.visible = true
        this.totalTimeDelta = 0; 
    }
    tick(timeDelta) {
        this.totalTimeDelta += timeDelta;
        let tm = Math.floor(this.totalTimeDelta);
        let visible = tm % 2 == 0
        if (visible) {
           if (!this.context.entity.visible) this.context.entity.visible = true;
        } else {
            if (this.context.entity.visible) this.context.entity.visible = false;
        }
        if (this.totalTimeDelta < this.seconds ) {
            // 如果时间还在设置的时间内,则继续执行
            return vjmap3d.BehaviorStatus.Running;
        } else {
            // 超过地设置的时间,成功返回
            return vjmap3d.BehaviorStatus.Succeeded;
        }
    }
    finalize() {
        super.finalize();
        this.context.entity.visible = true
    }
}
let entity = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/car.glb", {
    splitSubEntity: true,
    toEntity: true,
});
entity.scale.set(5, 5, 5)
entity.addTo(app);
let mod = entity.addModule(vjmap3d.BehaviorModule);
// 闪烁十分钟后删除实体
mod.addBehavior(vjmap3d.SequenceBehavior.from([
    FlashBehavior.fromJSON({seconds: 600}),
    vjmap3d.DestoryBehavior.create()
]))
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {
        gridHelper: { visible: true } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
// 自定义闪烁行为组件
class FlashBehavior extends vjmap3d.Behavior {
    constructor() {
        super();
        this.typeName = "FlashBehavior"
        this.seconds = 30; // 闪烁时间,默认30s
        this.totalTimeDelta = 0; // 运行总共花的时间
    }
    static fromJSON(param = {}) {
        const r = new FlashBehavior();
        r.fromJSON(param);
        return r;
    }
    fromJSON(param = {}) {
        this.seconds = param?.seconds ?? 30; // 与构造函数的默认值保持一致(秒)
    }
    initialize(context) {
        super.initialize(context);
        this.context.entity.visible = true
        this.totalTimeDelta = 0; 
    }
    tick(timeDelta) {
        this.totalTimeDelta += timeDelta;
        let tm = Math.floor(this.totalTimeDelta);
        let visible = tm % 2 == 0
        if (visible) {
           if (!this.context.entity.visible) this.context.entity.visible = true;
        } else {
            if (this.context.entity.visible) this.context.entity.visible = false;
        }
        if (this.totalTimeDelta < this.seconds ) {
            // 如果时间还在设置的时间内,则继续执行
            return vjmap3d.BehaviorStatus.Running;
        } else {
            // 超过地设置的时间,成功返回
            return vjmap3d.BehaviorStatus.Succeeded;
        }
    }
    finalize() {
        super.finalize();
        this.context.entity.visible = true
    }
}
let entity = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/car.glb", {
    splitSubEntity: true,
    toEntity: true,
});
entity.scale.set(5, 5, 5)
entity.addTo(app);
let mod = entity.addModule(vjmap3d.BehaviorModule);
// 闪烁十分钟后删除实体
mod.addBehavior(vjmap3d.SequenceBehavior.from([
    FlashBehavior.fromJSON({seconds: 600}),
    vjmap3d.DestoryBehavior.create()
]))