[ECS框架系列]-与四叉树的一次结合(1)

795 阅读5分钟

前言

大家好,IT侠最近研究了下四叉树,本来想用cocos creator写,但是发现电脑装不了dashboard(cocos牛逼,是我电脑太渣了) 所以就有了这个H5的demo。

写本篇文章有2个出发点:

  1. 在H5下使用ECS框架;
  2. 用ECS框架与四叉树结合来辅助物体之间碰撞检测;

因为东西比较多,所以我打算分2篇文章来介绍。本篇我们就来介绍下如何在H5下使用ECS框架。

因为demo是基于ECS框架,所以如果不了解ECS框架的话,可以看看我的ECS框架系列文章,获取我已经开源的ECS框架。或者扫文末的二维码联系我,给我“加个鸡腿”支持一下!

19.9 交个朋友。

正文

最终效果

先给大家看看demo运行起来的效果,看看四叉树在对象很多的情况下的威力

  • 每一个"田字"矩形是一个4叉树节点,方便调试的使用
  • 高亮黄色的就是需要检查是否与主角发生碰撞的块
  • 高亮紫色的就是当前和主角发生碰撞的块

四叉树应用

技术栈

  • 「typescript编译器」

    • 因为ECS框架和四叉树用的是TS写的所以需要**「tsc」**工具将ts编译回js
  • 「require.js」

    • 一个模块加载器,用来加载**「tsc」**工具编译后的**「amd」**规范的模块文件
  • 「hammer.js」

    • 多点触摸手势库,用来监听处理用户输入(鼠标移动,点击 停止等)

代码实现

先给大家看看项目基本文件结构:

    /css                   css文件
    /img                   图片资源
    /src
        /game              ECS系统文件
            character.js   角色系统
            collision.js   碰撞系统
            decay.js       衰减系统
            enemy.js       敌人系统
            main.js        逻辑入口文件
            render.js      渲染系统
            sketch.js      四叉树划线辅助系统
            time.js        时间系统
            userInput      用户输入系统

    /lib                   代码库
        /js                tsc编译后的js文件夹
        /ecs               ECS框架文件
        hammer.js          触摸行为库
        quadTree.ts        四叉树
        require.js         模块加载器
        tscofig.json       tsc编译配置
    /misc 
        utils.js           工具函数库
    index.html             项目入口文件

项目入口核心代码

加载必要的css文件和库文件

    <!--加载 .css 文件 --> 
    <link rel="stylesheet" type="text/css" href="./css/main.css">
    <!--加载 require.js 文件 --> 
    <script src="./src/lib/require.js"></script>
    <style>

        #output{
            position: absolute;
            top:700px;
        }

    </style>

下面的代码加载main.js文件 获取canvas,主角图片等对象,方便后面ECS框架使用:

  <script>
        //异步加载main.js 获取到的canvas,主角图片等对象
        require(['./src/game/main.js'], function (gameStart) {
             //获取canvas节点
            let _canvas =  document.getElementById('game-canvas');
            let _dom = {

                __output:document.getElementById("output")
            } 
            //加载主角使用的图片资源
            let img = new Image(20,20);
            img.src = "./img/boxghost.png"
            // 加载完毕后回调函数里调用main.js来启动ECS框架
            img.onload = function(){
                //执行初始化ECS框架
                gameStart(_dom,_canvas,img);

            }

        });

   </script>

main.js是业务逻辑的入口文件,做了以下的事情:

  • 用来初始化ECS框架,并将上述的系统加载到ECS上
  • 创建canvas实体和 主角实体
  • 主循环逻辑,每秒60帧

加载必要的模块:

  define(
    [
        "require",
         "exports",
         "module",
         '../lib/js/ecs/src/supEcs.js',
         "../misc/utils.js",
         "./time.js",
         "./render.js",
         "./character.js",
         "./userInput.js",
         "./enemy.js",
         "./decay.js",
         "../lib/js/quadTree.js",
         "./sketch.js",
         "./collision"
        ], function (require, exports,module,{ecs},utils,time,render,character,userInput,enemy,decay,{QuadTree},sketch,collision) {

加载必要系统:

        let toy = ecs(); //toy 就是ecs对象实例
        toy.QuadTree = QuadTree; // 将四叉树挂在ecs实例上
        Object.assign(toy,domInfo);
        utils(toy); //将工具函数挂在到ecs上
        time(toy); // 挂载时间系统
        render(toy);// 挂载渲染系统
        sketch(toy);//挂载四叉树划线辅助系统  删除或者注释改系统辅助线就会消失
        character(toy);//挂载角色系统
        userInput(toy);//挂载用户输入系统
        enemy(toy);//挂载敌人系统
        collision(toy);//挂载碰撞系统
        decay(toy);//挂载衰弱系统 按一定规则让块变小最后消失
        toy.startSystems(); // 将上面所有挂载的系统启动

系统运行起来了,接下来就是创建实体并给实体挂载相关组件,等待系统捕获和处理!

       //生成time实体,并给实体挂载一个Time组件。之后time系统就会开始工作,开始计算dt等逻辑
       let timeEnt = toy.ent({
                 Time:{}
             });
       // 生成 canvas实体,并挂载 Canvas组件。之后render系统便会捕获该实体,用来做渲染的工作
       let canvasEnt = toy.ent({
           Canvas:{
               canvas:_canvas,
               context:_canvas.getContext("2d"),
           }
       });
      // 这里创建一个临时实体,挂载一个Born组件。
      //之后character系统会捕获,将主角实体创建出来。
      //之后这个临时的实体和其身上的组件将会被删除
       toy.comOnce("Born",{
           props:{
                x:canvasEnt.Canvas.canvas.width/2,
                y:canvasEnt.Canvas.canvas.height/2,
                lastX:canvasEnt.Canvas.canvas.width/2,
                lastY:canvasEnt.Canvas.canvas.height/2,
                width:40,
                height:40,
                img:heroImg
           },
           isHero:true,
           hp:40
        });

其他的系统也会因为有相关的组件的实体产生从而将自己关心的实体捕获到,并对其处理。篇幅有限这里我简单的讲一下time系统

time系统:

    let _time = toy.system("TimeSystem",1);
      //当有Time组件的实体产生,下面的函数就会执行
      _time.on("Time",function(ent){
          ent.Time.last = 0; 
          ent.Time.dt = 0;
          ent.Time.times=0;
          ent.Time.time = 0;
      });

      // 下面的update函数每一帧都会执行,只要Time组件的实体还存在
      // 就会每一帧来计算当前dt,当前游戏运行时间等,将其更新到Time组件上
      _time.onUpdate("Time",function(dt,es){
          if(!es[0]){
              return;
          }

          let ent = es[0];
          ent.Time.dt = dt;
          ent.Time.times += 1;
          ent.Time.time += dt;
      });

最后就是主循环(mainloop)逻辑

      var loop = function (timestamp){
      let gameOver = toy.getFirstEnt("GameOver");
      if(gameOver){
          return;
      }
      if(!timeEnt.Time.last){
          timeEnt.Time.last = timestamp;
      }
      let diff = timestamp - timeEnt.Time.last;
      let dt = 0;
      if(diff >0){
          dt = diff/1000 //毫秒转成秒
      }

      timeEnt.Time.last = timestamp;
      //执行ecs中所有系统上的update方法
      toy.world.update(dt)
      requestAnimationFrame(loop);
  };
  requestAnimationFrame(loop);

结束语

通过上述逻辑,ECS框架就在h5上完美的运行起来了。下一篇文件我在再来说说四叉树与ECS结合的实现

谢谢看到最后(ps: 「点赞」 「在看」 支持一下呗)

ECS系列文章

[ECS系列] 框架中必不可少的工具类

[ECS系列] World的设计思路

[ECS系列] 实体,组件,系统的设计思路

[ECS系列] 欢迎来到ECS的世界

更多精彩

CocosCreator 教学系列-对象池

无限嵌套的富文本打字机

CocosCreator 教学系列-打字机效果(1)

CocosCreator 教学系列-打字机效果(2)

公众号

我是IT侠来了,下面是我的公众号,专注于分享IT圈内各种技术干货,内容涉及后端技术,前端技术等等,希望大家喜欢。再次感谢关注。

本人微信号