diff --git a/BehaviorTree.dio b/BehaviorTree.dio
new file mode 100644
index 0000000..236e5a8
--- /dev/null
+++ b/BehaviorTree.dio
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ECS.dio b/ECS.dio
new file mode 100644
index 0000000..70c654c
--- /dev/null
+++ b/ECS.dio
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
old mode 100755
new mode 100644
index 09b0019..576fa74
--- a/README.md
+++ b/README.md
@@ -1,2 +1,420 @@
-# hello-world
-Hello world new project template.
+# 在Cocos中使用ECS + BehaviorTree 实现格斗AI
+### 成品展示
+
+demo 角色AI包含了巡逻, 追踪, 攻击, 躲避攻击, 受伤打断攻击, 攻击打断闪避等. 可以细心看一下二图.
+
+巡逻中
+
+
+
+追逐打斗
+
+
+
+
+
+完整展示视频链接:
+
+https://www.bilibili.com/video/bv1hi4y1Q7Uv
+
+
+
+代码链接在文章末位.
+
+
+
+### 1. 写一个ECS框架
+
+**ECS** 全称 Entity - Component - System(实体 - 组件 - 系统).
+
+**组件只有属性没有行为, 系统只有行为没有属性.**
+
+-------
+
+本来这里想介绍一下ECS的概念, 但是写起来感觉像是记流水账, 网上已经有很多ECS的介绍文章了, 所以觉得不如贴几篇我个人觉得写的好的, 谈一谈我的理解.
+
+[浅谈《守望先锋》中的 ECS 构架](https://blog.codingnow.com/2017/06/overwatch_ecs.html)
+
+这篇文章应该是最早一批介绍ECS架构的文章了, 看过好几遍, 不仅全面的介绍了ECS架构, 还对比了ECS和传统游戏开发架构的区别. 以及在网络同步时的处理.
+
+[游戏开发中的ECS 架构概述](https://zhuanlan.zhihu.com/p/30538626)
+
+这篇文章比较接地气
+
+> ***ECS***,即 Entity-Component-System(实体-组件-系统) 的缩写,其模式遵循[组合优于继承](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Composition_over_inheritance)原则,游戏内的每一个基本单元都是一个**实体**,每个**实体**又由一个或多个**组件**构成,每个**组件**仅仅包含代表其特性的数据(即在组件中没有任何方法),例如:移动相关的组件`MoveComponent`包含速度、位置、朝向等属性,一旦一个实体拥有了`MoveComponent`组件便可以认为它拥有了移动的能力,**系统**便是来处理拥有一个或多个相同**组件**的**实体**集合的工具,其只拥有行为(即在系统中没有任何数据),在这个例子中,处理移动的**系统**仅仅关心拥有移动能力的**实体**,它会遍历所有拥有`MoveComponent`**组件**的**实体**,并根据相关的数据(速度、位置、朝向等),更新实体的位置。
+>
+> **实体**与**组件**是一个一对多的关系,**实体**拥有怎样的能力,完全是取决于其拥有哪些**组件**,通过动态添加或删除**组件**,可以在(游戏)运行时改变**实体**的行为。
+
+
+
+**这段话对于ECS的关系也是我设计的框架遵守的规则.** 即Component只包含属性, System只包含行为.
+
+
+
+#### 1. 这个ECS框架做了什么
+
+World-Entity-Component-System的关系图
+
+
+
+**World每帧根据顺序调用所有的System, System中会处理对应的Component. 在这个过程中, 使用者可以动态的创建或销毁Entity, 给Entity添加或移除Component.**
+
+
+
+为了更高效的维护Entity-Component-System的关系, 我采取了一些办法.
+
+###### 1. 所有的Component都通过其对应的ComponentPool维护.
+
+例如MoveComponent会生成一个MoveComponentPool进行维护, 方便实现复用. 外面不直接持有Component, 而是通过component在pool中的index索引便可以在对应的pool中获取到对应的Component.
+
+###### 2. Entity使用自增Id标识.
+
+外部不需要持有Entity对象, 或者说没有Entity对象, 所有的Entity都是一个Id, 通过这个Id在world内进行操作.
+
+###### 3. System通过Filter类管理关注的Entity.
+
+上文中提到System为了处理其关心的ComponentA, 会遍历所有拥有该ComponentA的Entity. 但是怎么判断哪下Entity有这个ComponentA呢? 传统的方法会遍历所有的Entity, 判断其是否有ComponentA, 这种方案明显是不够高效的. 所以这里引入了Filter类, 其方法的核心是空间换时间并有一套判断规则(接收某些类型的组件, 拒接某些类型的组件), 当每次进行AddComponent和RemoveComponent 或者RemoveEntity等会影响实体的操作时, 会通知所有的Filter有实体进行了修改,Filter会判断该实体是否还符合条件(也就是判断是否有ComponentA), 选择是否在Filter中保留该实体. 那么当System需要遍历某些特定的Entity时, 就可以直接通过对应的Filter就可以获得了.
+
+###### 4. Entity和Component的关系可以用一张二维表维护.
+
+| | ComponentA | ComponentB | ComponentC | ComponentD |
+|:-------:|:----------:|:----------:|:----------:|:----------:|
+| Entity1 | -1 | 0 | -1 | 0 |
+| Entity2 | -1 | 1 | 0 | -1 |
+
+如上述表格中Component数字的意思是其在Pool中的index索引, -1表示没有.
+
+所以Entity1有组件ComponentB, ComponentD.Entity1有组件ComponentB, ComponentC.
+
+**还有最后一个问题就是 如何将Entity和Component转换成0~N的整数以方便构建二维数组呢?**
+
+对于Entity可以通过id的自增实现, 每创建一个Entity, id++.
+
+而Component可以根据类型的枚举值得到唯一标识. 如下面的枚举值.
+
+```typescript
+export enum ComType {
+ ComCocosNode = 0,
+ ComMovable = 1,
+ ComNodeConfig = 2,
+ ComBehaviorTree = 3,
+ ComTransform = 4,
+ ComMonitor = 5,
+ ComRoleConfig = 6,
+ ComAttackable = 7,
+ ComBeAttacked = 8
+}
+```
+
+这样就可以构建出上面的二维表了.
+
+
+
+最后还可以通过ts的注解, 将ComType注入到对应的Component类中. 将type和component一一对应起来.
+
+```typescript
+// 项目中一个Component.
+@ECSComponent(ComType.ComMovable)
+export class ComMovable {
+ public running = false;
+ public speed = 0;
+ public points: cc.Vec2[] = [];
+ public pointIdx = 0;
+ public keepDir = false;
+ public speedDirty = false;
+}
+```
+
+###### 小结: 到这一步就已经完成框架部分了, 再展示一下ComMovable对应的SysMovable. 这个System会每帧根据ComMovable的当前状态, 计算出下一帧的ComMovable状态.
+
+这里插入一下对于Filter的更详细的介绍, Filter的判断规则是通过参数判断接收某些类型的组件, 拒接某些类型的组件. 比如这个参数[ComMovable, ComTransform, ComCocosNode])表示这个Filter保存的是同时含有ComMovable,ComTransform,ComCocosNode组件的实体.
+
+```
+const FILTER_MOVE = GenFilterKey([ComMovable, ComTransform, ComCocosNode]);
+export class SysMovable extends ECSSystem {
+ /** 更新 */
+ public onUpdate(world: ECSWorld, dt:number): void {
+ world.getFilter(FILTER_MOVE).walk((entity: number) => {
+ let comMovable = world.getComponent(entity, ComMovable);
+ let comTrans = world.getComponent(entity, ComTransform);
+ if(comMovable.speed <= 0 || comMovable.pointIdx >= comMovable.points.length) {
+ return ;
+ }
+ if(!comMovable.running) {
+ comMovable.running = true;
+ }
+ let moveLen = comMovable.speed * dt;
+ while(moveLen > 0 && comMovable.pointIdx < comMovable.points.length) {
+ let nextPoint = comMovable.points[comMovable.pointIdx];
+ let offsetX = nextPoint.x - comTrans.x;
+ let offsetY = nextPoint.y - comTrans.y;
+ let offsetLen = Math.sqrt(offsetX * offsetX + offsetY * offsetY);
+ if(offsetLen <= moveLen) {
+ moveLen -= offsetLen;
+ comTrans.x = nextPoint.x;
+ comTrans.y = nextPoint.y;
+ comMovable.pointIdx ++;
+ continue;
+ }
+ if(!comMovable.keepDir) {
+ comTrans.dir.x = offsetX / offsetLen || comTrans.dir.x;
+ comTrans.dir.y = offsetY / offsetLen;
+ }
+ comTrans.x += moveLen * offsetX / offsetLen;
+ comTrans.y += moveLen * offsetY / offsetLen;
+
+ moveLen = -1;
+ }
+ if(comMovable.pointIdx >= comMovable.points.length) {
+ comMovable.speed = 0;
+ comMovable.speedDirty = true;
+ }
+ return false;
+ });
+ }
+}
+```
+
+
+
+#### 2. ECS框架和Cocos Creator结合
+
+
+
+ECS框架本身的实现不难, 核心代码只有几百行的样子, 但是如何将这个框架和Cocos Creator结合起来, 或者说怎么展示一个Node, 并可以通过ECS的方式操控Node呢?
+
+
+
+以上面的ComMovable和SysMovable为例, ECS本身更多的是数据上的逻辑处理, Entity添加了ComMovable组件就获得了SysMovable的能力, 那么给Entity添加一个显示Node的组件(ComCocosNode), 在通过一个处理ComCocosNode的System(SysCocosView)是不是就实现了展示node的能力呢.
+
+
+
+###### 1. 设计结合Cocos中Node的组件
+
+基于这个想法我设计了ComCocosNode.
+
+```typescript
+@ECSComponent(ComType.ComCocosNode)
+export class ComCocosNode {
+ public node: cc.Node = null;
+ public loaded = false;
+ public events: EventBase[] = [];
+}
+```
+
+ComCocosNode中有node属性. 通过Entity获取到ComCocosNode组件就可以修改node的属性了, 而events是因为对于node我们不仅有同步的处理, 也有一些异步的处理, 比如播放一系列动画, 这种可以通过添加事件的方式, 即在system不直接调用node的组件方法, 而是让组件自己读取event, 自己去处理.
+
+
+
+这个时候node还没有赋值, 所以我又设计了一个组件ComNodeConfig
+
+```typescript
+@ECSComponent(ComType.ComNodeConfig)
+export class ComNodeConfig {
+ id = 0; // 唯一标识
+ prefabUrl = ''
+ layer = 0; // 层级
+}
+```
+
+这里可能会有人有疑问, 为什么不把这两个组件的属性合并到一起, 这个其实是为了方便配置, ComNodeConfig的属性都是可以直接配置在表上的, 这样的话就方便配置同学了.
+
+
+
+###### 2. 设计处理ComNodeConfig的System
+
+最后通过一个SysCocosView系统, 这个系统处理的实体是 有ComNodeConfig组件, 但是没有ComCocosNode组件的实体. 每次遍历时根据ComNodeConfig组件的prefabUrl加载prefab生成node, 根据layer层级将node添加到指定位置, 然后给这个实体添加ComCocosNode组件, 将node值赋上. 这样下一次就不会处理这个实体了. 下面的代码是demo已经完成后的代码了, 我就不还原到刚开始时候的样子了.
+
+```typescript
+const FILTER_COCOS_NODE = GenFillterKey([ComNodeConfig], [ComCocosNode]);
+const FILTER_NODE_EVENT = GenFillterKey([ComCocosNode, ComTransform]);
+export class SysCocosView extends ECSSystem implements ITouchProcessor {
+
+ onUpdate(world:ECSWorld, dt:number) {
+ world.getFilter(FILTER_COCOS_NODE).walk((entity: number) => {
+ let comNodeConfig = world.getComponent(entity, ComNodeConfig);
+ let comView = world.addComponent(entity, ComCocosNode);
+ let comRoleConfig = world.getComponent(entity, ComRoleConfig);
+ this._loadView(world, entity, comNodeConfig).then((node: cc.Node) => {
+ console.log('load view success');
+ });
+ return false;
+ });
+
+ world.getFilter(FILTER_NODE_EVENT).walk((entity: number) => {
+ let comCocosNode = world.getComponent(entity, ComCocosNode);
+ if(!comCocosNode.loaded) return ;
+ let eventProcess = comCocosNode.node.getComponent(EventProcess);
+ if(!eventProcess) return ;
+ let comTrans = world.getComponent(entity, ComTransform);
+ eventProcess.sync(comTrans.x, comTrans.y, comTrans.dir);
+ while(comCocosNode.events.length) {
+ let event = comCocosNode.events.shift();
+ eventProcess.processEvent(event);
+ }
+ return true;
+ });
+ }
+}
+```
+
+
+
+### 写一个BehaviorTree(行为树)
+
+###### 1. BehaviroTree是什么?
+
+介绍行为树网上也有很多文章了, 我这里也就不赘述了.
+
+[游戏AI之决策结构—行为树 - KillerAery - 博客园](https://www.cnblogs.com/KillerAery/p/10007887.html)
+
+[AI 行为树的工作原理 | indienova 独立游戏](https://indienova.com/indie-game-development/ai-behavior-trees-how-they-work/)
+
+> 行为树的名字很好地解释了它是什么。不像有限状态机(Finite State Machine)或其他用于 AI 编程的系统,行为树是一棵用于控制 AI 决策行为的、包含了层级节点的树结构。树的最末端——叶子,就是这些 AI 实际上去做事情的命令;连接树叶的树枝,就是各种类型的节点,这些节点决定了 AI 如何从树的顶端根据不同的情况,来沿着不同的路径来到最终的叶子这一过程。
+>
+> 行为树可以非常地“深”,层层节点向下延伸。凭借调用实现具体功能的子行为树,开发者可以建立相互连接的行为树库来做出非常让人信服的 AI 行为。并且,行为树的开发是高度迭代的,你可以从一个很简单的行为开始,然后做一些分支来应对不同的情境或是实现不同的目标,让 AI 的诉求来驱动行为,或是允许 AI 在行为树没有覆盖到的情境下使用备用方案等等。
+
+树的最末端叶子是AI实际上做的事的命令可以称为行为, 行为是需要用户编写的具体的动作, 比如移动到某位置, 攻击, 闪避等. 联系根到叶子的节点的中间节点可以称为决策节点, 决策节点并没有实际的行为,而是决定是否可以向下执行到叶子节点.
+
+
+
+如何决定呢?
+
+每一个结点执行后都会返回一个状态, 状态有三种1, Success. 2, Fail. 3, Running.
+
+Success和Fail很好理解, 比如一个监视节点, 看到了敌人返回success, 没看到返回Fail.
+
+但是对于一个需要执行一段时间的节点, 比如1s内移动五步, 在不到1s时去看节点的状态, 这个时候返回成功或者失败都是不合理的, 所以这种情况应该返回Running表示这个节点还在执行中. 等下一帧在继续判断.
+
+
+
+这个时候我们可以设计这样一个节点, 它的状态是和子节点状态挂钩的, 按顺序执行子节点,如果遇到了执行失败的结点则返回失败, 如果全部执行成功则返回成功. 这种结点可以称为Sequence.
+
+类似的结点还有Select, 这个节点的状态是按顺序执行子节点,如果全部执行失败则返回失败, 如果遇到执行成功则返回成功.
+
+下面是一个实际项目Sequence的实现.
+
+```typescript
+/** Sequence node */
+NodeHandlers[NodeType.Sequence] = {
+ onEnter(node: SequenceNode, context: ExecuteContext) : void {
+ node.currIdx = 0;
+ context.executor.onEnterBTNode(node.children[node.currIdx], context);
+ node.state = NodeState.Executing;
+ },
+ onUpdate(node: SequenceNode, context: ExecuteContext) : void {
+ if(node.currIdx < 0 || node.currIdx >= node.children.length) {
+ // 越界了, 不应该发生, 直接认为是失败了
+ node.state = NodeState.Fail;
+ return;
+ }
+ // 检查前置条件是否满足
+ for(let i=0; i
+
+
+
+###### 2. 决策的时效性
+
+根据我看的一些文档, 对于行为树的决策是每一帧都要更新的, 比如现在有一个场景, 用户可以输入文本, 输入move让方块A向前移动10格子, 输入stop方块A停止移动. 那么对于行为树来说, 每一帧都要判断当前用户输入的是move,还是stop, 从而下达是移动还是停止的行为.
+
+对于移动, 行为是sequence([用户输入move, 移动]); 用ActionA代替
+
+对于停止, 行为是sequence([用户输入stop, 停止]);用ActionB代替
+
+最终行为是select([ActionA, ActionB])
+
+> sequence表示按顺序执行子行为, 如果遇到子行为执行失败, 那么立刻停止, 并返回失败, 如果全部执行成功, 那么返回成功.
+
+> select表示按顺序执行子行为, 如果遇到子行为执行成功, 那么立即停止, 并返回成功. 如果全部执行失败, 那么返回失败.
+
+假设现在用户点击一下, 那么每帧都需要从头一层层向下判断执行, 直到判断到移动再执行.
+
+当然这是有必要的, 对于行为来说 确定自己能否应该执行是十分重要的.
+
+但是这对执行一个 持续性的行为很不友好. 假设还是上面的场景, 用户输入sleep, 方块停止移动2s. 就是sequence([用户输入sleep, 停止移动2s]). 这个时候行为树是 select([ActionA, ActionB, ActionC]);
+
+那么当我输入sleep, 方块停止移动的时间内, 输入move, 那么下一帧的决策就进入了ActionA, 导致方块移动. 停止移动2s的行为被打断了.
+
+这个问题我称为行为树决策的时效性, 也就是行为得到的决策, 并不能维持一定时间.
+
+这个决策其实目前只是sequence和Select 才拥有的.
+
+## 如何解决:
+
+因为我是自己想的, 所以解决方案可能不是最优的.
+
+首先我加入了具有时效性的LockedSequence, LockedSelect, 也就是当前行为的决策一旦做出, 就必须在当前行为完全结束后才能被修改.
+
+```
+class NodeHandler {
+
+onEnter:(node: NodeBase, context: ExecuteContext) => void;
+
+onUpdate:(node: NodeBase, context: ExecuteContext) => void;
+
+}
+```
+
+引入一丢丢代码, 在onEnter时, 将当前行为的状态置为 running, 在onUpdate时判断, 如果当前状态不是running就return. 所以一旦状态确定, 就不会再onUpdate中被修改, 直到下一次进入onEnter.
+
+这个时候在带入上述的场景就没问题了, 当用户输入sleep时, 方块停止移动2s, 在2s内输入move, 并不会导致ActionC的决策更改, 直到停止移动2s的行为结束, 进入下一个周期后才会进入ActionA
+
+但是这个同样也导致了另一个问题, 就是并行的行为. 比如假设一个场景, 士兵一边巡逻, 一边观察是否有敌人, 如果有敌人, 那么停止巡逻, 去追击敌人.
+
+行为树如下:
+
+ActionA = 巡逻
+
+ActionB = sequence([观察是否有敌人, 追击敌人]);
+
+repeat(sequence([ActionB, ActionA]))
+
+因为上面的方法在行为结束前不会修改决策, 那么就会出现, 士兵先观察是否有敌人, 没有就去巡逻, 巡逻完了, 再去观察是否有敌人, 这就太蠢了.
+
+我解决上面的问题的方案是添加一个新的决策Node, 这个Node就是处理并行行为的.
+
+parallel 的能力是 顺序处理子节点, 但是并不需要等待前一个节点执行完毕后才能执行后一个. 当有行为返回失败时, 立即退出返回失败, 当所有行为返回成功时, 停止返回成功.
+
+行为树如下:
+
+repeat(selector([parallel([Inverter(观察是否有敌人), 巡逻]), 追击敌人]))
+
+> Inverter表示取反
+
+当前没有发现有敌人, 那么行为在巡逻, parallel还在running阶段, 因为巡逻不会失败, 所以最后一种情况是 如果发现敌人, 那么parallel立即返回失败, 那么行为就到了追击敌人了.
+
+
+
+最后展示一下Demo中的行为树.
+
+
+
diff --git a/assets/Scene/helloworld.fire b/assets/Scene/helloworld.fire
index 21f83de..baba273 100755
--- a/assets/Scene/helloworld.fire
+++ b/assets/Scene/helloworld.fire
@@ -82,18 +82,21 @@
},
{
"__id__": 18
+ },
+ {
+ "__id__": 26
}
],
"_active": true,
"_components": [
{
- "__id__": 26
+ "__id__": 34
},
{
- "__id__": 27
+ "__id__": 35
},
{
- "__id__": 28
+ "__id__": 36
}
],
"_prefab": null,
@@ -698,8 +701,8 @@
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
- "_string": "button",
- "_N$string": "button",
+ "_string": "add a",
+ "_N$string": "add a",
"_fontSize": 20,
"_lineHeight": 40,
"_enableWrapText": false,
@@ -1083,8 +1086,8 @@
],
"_srcBlendFactor": 770,
"_dstBlendFactor": 771,
- "_string": "button",
- "_N$string": "button",
+ "_string": "add b",
+ "_N$string": "add b",
"_fontSize": 20,
"_lineHeight": 40,
"_enableWrapText": false,
@@ -1256,6 +1259,391 @@
"handler": "onClick2",
"customEventData": ""
},
+ {
+ "__type__": "cc.Node",
+ "_name": "New Button",
+ "_objFlags": 0,
+ "_parent": {
+ "__id__": 2
+ },
+ "_children": [
+ {
+ "__id__": 27
+ }
+ ],
+ "_active": true,
+ "_components": [
+ {
+ "__id__": 32
+ }
+ ],
+ "_prefab": null,
+ "_opacity": 255,
+ "_color": {
+ "__type__": "cc.Color",
+ "r": 255,
+ "g": 255,
+ "b": 255,
+ "a": 255
+ },
+ "_contentSize": {
+ "__type__": "cc.Size",
+ "width": 100,
+ "height": 40
+ },
+ "_anchorPoint": {
+ "__type__": "cc.Vec2",
+ "x": 0.5,
+ "y": 0.5
+ },
+ "_trs": {
+ "__type__": "TypedArray",
+ "ctor": "Float64Array",
+ "array": [
+ 473.095,
+ 245.37,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ "_eulerAngles": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "_skewX": 0,
+ "_skewY": 0,
+ "_is3DNode": false,
+ "_groupIndex": 0,
+ "groupIndex": 0,
+ "_id": "85qrRc4bZJc5jmPaLOoN5t"
+ },
+ {
+ "__type__": "cc.Node",
+ "_name": "Background",
+ "_objFlags": 512,
+ "_parent": {
+ "__id__": 26
+ },
+ "_children": [
+ {
+ "__id__": 28
+ }
+ ],
+ "_active": true,
+ "_components": [
+ {
+ "__id__": 30
+ },
+ {
+ "__id__": 31
+ }
+ ],
+ "_prefab": null,
+ "_opacity": 255,
+ "_color": {
+ "__type__": "cc.Color",
+ "r": 255,
+ "g": 255,
+ "b": 255,
+ "a": 255
+ },
+ "_contentSize": {
+ "__type__": "cc.Size",
+ "width": 100,
+ "height": 40
+ },
+ "_anchorPoint": {
+ "__type__": "cc.Vec2",
+ "x": 0.5,
+ "y": 0.5
+ },
+ "_trs": {
+ "__type__": "TypedArray",
+ "ctor": "Float64Array",
+ "array": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 0
+ ]
+ },
+ "_eulerAngles": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "_skewX": 0,
+ "_skewY": 0,
+ "_is3DNode": false,
+ "_groupIndex": 0,
+ "groupIndex": 0,
+ "_id": "0ekZoSTDRBLICbpC5YU3je"
+ },
+ {
+ "__type__": "cc.Node",
+ "_name": "Label",
+ "_objFlags": 512,
+ "_parent": {
+ "__id__": 27
+ },
+ "_children": [],
+ "_active": true,
+ "_components": [
+ {
+ "__id__": 29
+ }
+ ],
+ "_prefab": null,
+ "_opacity": 255,
+ "_color": {
+ "__type__": "cc.Color",
+ "r": 0,
+ "g": 0,
+ "b": 0,
+ "a": 255
+ },
+ "_contentSize": {
+ "__type__": "cc.Size",
+ "width": 100,
+ "height": 40
+ },
+ "_anchorPoint": {
+ "__type__": "cc.Vec2",
+ "x": 0.5,
+ "y": 0.5
+ },
+ "_trs": {
+ "__type__": "TypedArray",
+ "ctor": "Float64Array",
+ "array": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ "_eulerAngles": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "_skewX": 0,
+ "_skewY": 0,
+ "_is3DNode": false,
+ "_groupIndex": 0,
+ "groupIndex": 0,
+ "_id": "b1O0CAOdZBz5+5gIn3dR5n"
+ },
+ {
+ "__type__": "cc.Label",
+ "_name": "",
+ "_objFlags": 0,
+ "node": {
+ "__id__": 28
+ },
+ "_enabled": true,
+ "_materials": [
+ {
+ "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
+ }
+ ],
+ "_srcBlendFactor": 770,
+ "_dstBlendFactor": 771,
+ "_string": "debug",
+ "_N$string": "debug",
+ "_fontSize": 20,
+ "_lineHeight": 40,
+ "_enableWrapText": false,
+ "_N$file": null,
+ "_isSystemFontUsed": true,
+ "_spacingX": 0,
+ "_batchAsBitmap": false,
+ "_styleFlags": 0,
+ "_underlineHeight": 0,
+ "_N$horizontalAlign": 1,
+ "_N$verticalAlign": 1,
+ "_N$fontFamily": "Arial",
+ "_N$overflow": 1,
+ "_N$cacheMode": 1,
+ "_id": "6cPxNByvpCFJiTXSiCxOaC"
+ },
+ {
+ "__type__": "cc.Sprite",
+ "_name": "",
+ "_objFlags": 0,
+ "node": {
+ "__id__": 27
+ },
+ "_enabled": true,
+ "_materials": [
+ {
+ "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432"
+ }
+ ],
+ "_srcBlendFactor": 770,
+ "_dstBlendFactor": 771,
+ "_spriteFrame": {
+ "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952"
+ },
+ "_type": 1,
+ "_sizeMode": 0,
+ "_fillType": 0,
+ "_fillCenter": {
+ "__type__": "cc.Vec2",
+ "x": 0,
+ "y": 0
+ },
+ "_fillStart": 0,
+ "_fillRange": 0,
+ "_isTrimmedMode": true,
+ "_atlas": null,
+ "_id": "adrDAfICJAQ4XbjieFeAUn"
+ },
+ {
+ "__type__": "cc.Widget",
+ "_name": "",
+ "_objFlags": 0,
+ "node": {
+ "__id__": 27
+ },
+ "_enabled": true,
+ "alignMode": 0,
+ "_target": null,
+ "_alignFlags": 45,
+ "_left": 0,
+ "_right": 0,
+ "_top": 0,
+ "_bottom": 0,
+ "_verticalCenter": 0,
+ "_horizontalCenter": 0,
+ "_isAbsLeft": true,
+ "_isAbsRight": true,
+ "_isAbsTop": true,
+ "_isAbsBottom": true,
+ "_isAbsHorizontalCenter": true,
+ "_isAbsVerticalCenter": true,
+ "_originalWidth": 100,
+ "_originalHeight": 40,
+ "_id": "d4dYinX5hGWaxVDkLesoz7"
+ },
+ {
+ "__type__": "cc.Button",
+ "_name": "",
+ "_objFlags": 0,
+ "node": {
+ "__id__": 26
+ },
+ "_enabled": true,
+ "_normalMaterial": null,
+ "_grayMaterial": null,
+ "duration": 0.1,
+ "zoomScale": 1.2,
+ "clickEvents": [
+ {
+ "__id__": 33
+ }
+ ],
+ "_N$interactable": true,
+ "_N$enableAutoGrayEffect": false,
+ "_N$transition": 3,
+ "transition": 3,
+ "_N$normalColor": {
+ "__type__": "cc.Color",
+ "r": 230,
+ "g": 230,
+ "b": 230,
+ "a": 255
+ },
+ "_N$pressedColor": {
+ "__type__": "cc.Color",
+ "r": 200,
+ "g": 200,
+ "b": 200,
+ "a": 255
+ },
+ "pressedColor": {
+ "__type__": "cc.Color",
+ "r": 200,
+ "g": 200,
+ "b": 200,
+ "a": 255
+ },
+ "_N$hoverColor": {
+ "__type__": "cc.Color",
+ "r": 255,
+ "g": 255,
+ "b": 255,
+ "a": 255
+ },
+ "hoverColor": {
+ "__type__": "cc.Color",
+ "r": 255,
+ "g": 255,
+ "b": 255,
+ "a": 255
+ },
+ "_N$disabledColor": {
+ "__type__": "cc.Color",
+ "r": 120,
+ "g": 120,
+ "b": 120,
+ "a": 200
+ },
+ "_N$normalSprite": {
+ "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952"
+ },
+ "_N$pressedSprite": {
+ "__uuid__": "e9ec654c-97a2-4787-9325-e6a10375219a"
+ },
+ "pressedSprite": {
+ "__uuid__": "e9ec654c-97a2-4787-9325-e6a10375219a"
+ },
+ "_N$hoverSprite": {
+ "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952"
+ },
+ "hoverSprite": {
+ "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952"
+ },
+ "_N$disabledSprite": {
+ "__uuid__": "29158224-f8dd-4661-a796-1ffab537140e"
+ },
+ "_N$target": {
+ "__id__": 27
+ },
+ "_id": "749WE4zm1JhpiMAmdyMCeA"
+ },
+ {
+ "__type__": "cc.ClickEvent",
+ "target": {
+ "__id__": 2
+ },
+ "component": "",
+ "_componentId": "e1b90/rohdEk4SdmmEZANaD",
+ "handler": "onClick3",
+ "customEventData": ""
+ },
{
"__type__": "cc.Canvas",
"_name": "",
diff --git a/assets/Script/Core/RoleEventProcess.ts b/assets/Script/Core/RoleEventProcess.ts
index 12d9a20..eb02598 100644
--- a/assets/Script/Core/RoleEventProcess.ts
+++ b/assets/Script/Core/RoleEventProcess.ts
@@ -59,7 +59,12 @@ export default class RoleEventProcess extends EventProcess {
break;
case EventType.GraphicsDraw:
- this._graphicsDraw(event as EventGraphicsDraw);
+ if(cc.debug.isDisplayStats()) {
+ this._graphicsDraw(event as EventGraphicsDraw);
+ }else {
+ this._graphicsDraw(null);
+ }
+
break;
@@ -76,7 +81,7 @@ export default class RoleEventProcess extends EventProcess {
}
private _graphicsDraw(event: EventGraphicsDraw) {
- if(event.points.length <= 0) {
+ if(!event || event.points.length <= 0) {
this.graphics.clear();
return ;
}
diff --git a/assets/Script/ECS/components/ComMovable.ts b/assets/Script/ECS/components/ComMovable.ts
index 39fa82d..0166722 100755
--- a/assets/Script/ECS/components/ComMovable.ts
+++ b/assets/Script/ECS/components/ComMovable.ts
@@ -7,8 +7,6 @@ export class ComMovable {
public speed = 0;
public points: cc.Vec2[] = [];
public pointIdx = 0;
-
public keepDir = false;
-
public speedDirty = false;
}
\ No newline at end of file
diff --git a/assets/Script/ECS/systems/SysAttack.ts b/assets/Script/ECS/systems/SysAttack.ts
index d8c671d..bf1ea3e 100644
--- a/assets/Script/ECS/systems/SysAttack.ts
+++ b/assets/Script/ECS/systems/SysAttack.ts
@@ -23,7 +23,13 @@ export class SysAttack extends ECSSystem {
}
/** */
public onEntityLeave(world: ECSWorld, entity: number): void {
-
+ let filter = world.getFilter(FILTER_ATTACKABLE);
+ // 判断当前monitor是否
+ filter.entities.forEach((value: boolean, otherEntity: number) => {
+ let comBeAttacked = world.getComponent(otherEntity, ComBeAttacked);
+ if(!comBeAttacked) return ;
+ if(comBeAttacked.attacker == entity) comBeAttacked.attacker = -1;
+ });
}
/** 更新 */
public onUpdate(world: ECSWorld, dt: number): void {
diff --git a/assets/Script/ECS/systems/SysCocosView.ts b/assets/Script/ECS/systems/SysCocosView.ts
index 05d6b71..0c7ad43 100755
--- a/assets/Script/ECS/systems/SysCocosView.ts
+++ b/assets/Script/ECS/systems/SysCocosView.ts
@@ -19,9 +19,18 @@ const FILTER_NODE_EVENT = GenFillterKey([ComCocosNode, ComTransform]);
export class SysCocosView extends ECSSystem implements ITouchProcessor {
onTouchStart(worldPos: cc.Vec2, world: ECSWorld): boolean {
+
return false;
}
+ onTouchMove(worldPos: cc.Vec2, world: ECSWorld): void {
+
+ }
+
+ onTouchCancel(worldPos: cc.Vec2, world: ECSWorld): void {
+
+ }
+
onAdd(world: ECSWorld) {
}
diff --git a/assets/Script/ECS/systems/SysMonitor.ts b/assets/Script/ECS/systems/SysMonitor.ts
index d1f4a9b..9f753ea 100644
--- a/assets/Script/ECS/systems/SysMonitor.ts
+++ b/assets/Script/ECS/systems/SysMonitor.ts
@@ -21,8 +21,8 @@ export class SysMonitor extends ECSSystem {
/** */
public onEntityLeave(world: ECSWorld, entity: number): void {
let filter = world.getFilter(FILTER_MONITOR);
- // 判断当前monitor是否
- filter.entities.forEach((value: boolean, otherEntity: number) => {
+ // 判断当前monitor是否
+ filter.entities.forEach((value: boolean, otherEntity: number) => {
let comMonitor = world.getComponent(otherEntity, ComMonitor);
if(!comMonitor) return ;
for(let i=comMonitor.others.length-1; i>=0; i--) {
@@ -60,12 +60,6 @@ export class SysMonitor extends ECSSystem {
let _check = (com: ComTransform) => {
return (a.sub(cc.v2(com.x, com.y)).len() < comMonitor.aroundLen || isInTriangle(cc.v2(com.x, com.y), a, b, c) || isInTriangle(cc.v2(com.x, com.y), b, c, d))
}
- // for(let i=comMonitor.others.length-1; i>=0; i--) {
- // const com = world.getComponent(comMonitor.others[i], ComTransform);
- // if(!com || !_check(com)) {
- // comMonitor.others.splice(i, 1);
- // }
- // }
if(comMonitor.others.indexOf(otherEntity) == -1 && _check(comTransOther)) {
comMonitor.others.push(otherEntity);
diff --git a/assets/Script/Main.ts b/assets/Script/Main.ts
index 7e4ce61..51e72ae 100644
--- a/assets/Script/Main.ts
+++ b/assets/Script/Main.ts
@@ -28,6 +28,8 @@ export default class Main extends cc.Component {
this.regiestTouchEvent();
+ //this.regiestTouchHandler();
+
}
onClick1() {
@@ -38,6 +40,10 @@ export default class Main extends cc.Component {
this.ecsController.createRoleEntity("Cyborg");
}
+ onClick3() {
+ cc.debug.setDisplayStats(!cc.debug.isDisplayStats());
+ }
+
protected update(dt: number): void {
if(this._world) this._world.update(dt);
}
@@ -54,14 +60,20 @@ export default class Main extends cc.Component {
this._touchHandler[i].onTouchStart(e.getLocation(), this._world);
}
}
- private _onTouchMove() {
-
+ private _onTouchMove(e: cc.Event.EventTouch) {
+ for(let i = 0; i < this._touchHandler.length; i++) {
+ this._touchHandler[i].onTouchMove(e.getLocation(), this._world);
+ }
}
- private _onTouchEnd() {
-
+ private _onTouchEnd(e: cc.Event.EventTouch) {
+ for(let i = 0; i < this._touchHandler.length; i++) {
+ this._touchHandler[i].onTouchEnd(e.getLocation(), this._world);
+ }
}
- private _onTouchCancel() {
-
+ private _onTouchCancel(e: cc.Event.EventTouch) {
+ for(let i = 0; i < this._touchHandler.length; i++) {
+ this._touchHandler[i].onTouchCancel(e.getLocation(), this._world);
+ }
}
public regiestTouchHandler(handler: ITouchProcessor) {
diff --git a/images/2022-03-30-00-58-10-image.png b/images/2022-03-30-00-58-10-image.png
new file mode 100644
index 0000000..1058c14
Binary files /dev/null and b/images/2022-03-30-00-58-10-image.png differ
diff --git a/images/2022-03-30-01-04-53-image.png b/images/2022-03-30-01-04-53-image.png
new file mode 100644
index 0000000..d6c9f21
Binary files /dev/null and b/images/2022-03-30-01-04-53-image.png differ
diff --git a/images/2022-03-30-01-11-22-image.png b/images/2022-03-30-01-11-22-image.png
new file mode 100644
index 0000000..cdc5fac
Binary files /dev/null and b/images/2022-03-30-01-11-22-image.png differ
diff --git a/images/巡逻.gif b/images/巡逻.gif
new file mode 100644
index 0000000..8f3eacd
Binary files /dev/null and b/images/巡逻.gif differ
diff --git a/images/打斗.gif b/images/打斗.gif
new file mode 100644
index 0000000..5e5e256
Binary files /dev/null and b/images/打斗.gif differ
diff --git a/role1behaviortree.dio b/role1behaviortree.dio
index d3e9fa0..1e1480a 100644
--- a/role1behaviortree.dio
+++ b/role1behaviortree.dio
@@ -1,6 +1,6 @@
-
+
@@ -8,7 +8,7 @@
-
+
@@ -28,7 +28,7 @@
-
+
@@ -40,13 +40,13 @@
-
+
-
+
@@ -55,28 +55,28 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -102,7 +102,7 @@
-
+
@@ -114,7 +114,7 @@
-
+
@@ -123,19 +123,19 @@
-
+
-
+
-
+
-
+