闲聊js13: 实现一个关键的,最小化的,非场景图类型的精灵系统(上)

本篇目的:

  • 两种类型的精灵系统:场景图类型和非常场景图类型
  • UML静态类结构图
  • 一个关键的,最小化的,非场景图类型的精灵系统的实现源码

最小化的概念:

为了演示动画和数学方面的基础知识,在前面花了一点时间,撰写了10多篇文章,描述了如何实现一个可以演示用的渲染引擎(系统)。

而本篇继续上面的内容,实现一个最小化的,但是可运行的,并且蕴含关键技术的精灵系统 。后面我们会按需进行渲染系统和这个动画精灵系统的扩展。

场景图概念:

场景图是指:
将整个场景中每个entity(2D动画中,可以称为精灵)以层次节点的方式表示,他们之间形成具有父子关系的树结构,该树可以称为场景树(可以将树结构堪称是图结构的一种特殊方式)。

一直以来,场景图在UI引擎,2D动画引擎中占绝对地位。

3D引擎中,场景图使用也非常广泛(当然也有蛮多引擎没有使用场景图)。

可以说,2D中,场景图是必须的,但是3D中,场景图是可选的。

简单来说,2D使用场景图方式可以解决前后顺序,深度关系的问题,而3D中,因为有z轴深度值,因此使用场景图是非必须的

树结构的表示的威力巨大,但是今天我们就不闲聊这块内容。

后续我会花时间来详谈这块东西,这个是整个UI/2D引擎的精华部分!

为什么我们现在实现的是非场景图类型:

很简单,时间来不及。我先实现非场景图类型的系统,推进我们的文章往前走。

空闲时间,实现场景图基础结构,然后再融合到我们的精灵系统中。

这样也有好处,让大家非常清晰的了解到非场景图的好处和劣处,以及场景图的优点和缺点!

一张简单的UML类结构图(成员操作就懒得列出,以后有时间再添加进去):

sprite_system_uml.png

源码实现:

  • BLFRender:

闲聊js:nodejs中的类定义和继承的套路
闲聊js:创建一个演示用的渲染库1
闲聊js:js面向对象编程(es6和jsface库技术选型)
闲聊js:创建一个演示用的渲染库2 (es6版本)
闲聊js:创建一个演示用的渲染库3(尺寸这些事)
闲聊js:创建一个演示用的渲染库4(渲染表面,像素格式,光栅化,位块传输,图形与图像)
闲聊js:创建一个演示用的渲染库5(封装常用的渲染方法)
闲聊js:创建一个演示用的渲染库6(图像显示)
闲聊js:创建一个演示用的渲染库7(渲染状态及点集绘制)
闲聊js:创建一个演示用的渲染库8(颜色和像素操作)
闲聊js:创建一个演示用的渲染库9(关键的裁剪操作)
闲聊js:创建一个演示用的渲染库10(坐标轴绘制、空间变换及总结与展望)

  • BLFSprite:
class BLFSprite {
    constructor(name = '') {
        this.typeName = "BASE";
        this.name = name;
        this.color = "rgba(255,0,0,1)";
    }

    //点的碰检:
    /*虚函数,如有需要,子类需override
    default实现:目前暂时返回false
    todo:后面会实现绑定盒/圈系统,再重新实现default基类行为
    */
    hitTest(x, y) {
        return false;
    }

    //更新:
    /*虚函数,如有需要,子类需override
    default实现,啥都不干
    */
    update(msec) {
        //console.log("update BLESprite");
        console.log("update sprite:" + this.name + " with type:" + this.typeName);
    }

    //渲染:
    /*虚函数,如有需要,子类需override
    default实现:绘制背景
    */
    render(render) {
        render.clear();
        render.drawGrid('white', 'black', 20, 20);
    }

     //尺寸:
    /*虚函数,如有需要,子类需要override
    default实现: 返回背景大小
    */
    getSize() {
        return new Size(render.getCanvasWidth(), render.getCanvasHeight());
    }
}
  • BLFSpriteManager(非场景图的场景管理类):
class BLFSpriteManager {

    constructor() {
        this.sprites = [];
    }

    addSprite(sprite) {
        this.sprites.push(sprite);
    }

    //动态类型语言的好处
    //根据参数类型有针对性处理
    removeSprite(sprite) {
        let idx = -1;
        if (typeof(sprite) === "number")
            idx = sprite;
        else
            idx = this.sprites.indexOf(sprite);

        if (idx === -1)
            return false;

        this.sprites.splice(idx, 1);
        return true;
    }

    getSprite(idx) {
        if (idx < 0 || idx >= this.sprites.length)
            return;

        return this.sprites[idx];
    }

    getCount() {
        return this.sprites.length;
    }
}
  • BLFEngine2D:
class BLFEngine2D {
    constructor(context) {
        this.render = new BLFRender(context);
        this.sprMgr = new BLFSpriteManager();
    }

    //依次调用所有精灵的update方法
    //各个精灵的更新都在精灵的update中进行
   //依次调用所有精灵的update方法
    //各个精灵的更新都在精灵的update中进行
    updateAll(msec) {
        for (let i = 0; i < this.sprMgr.getCount(); i++) {
            this.sprMgr.getSprite(i).update(msec);
        }
    }

    renderAll() {
        for (let i = 0; i < this.sprMgr.getCount(); i++) {
            this.sprMgr.getSprite(i).render(this.render);
        }
    }

    printAll() {
        let arr = [];
        for (let i = 0; i < this.sprMgr.getCount(); i++) {
            arr.push(this.sprMgr.getSprite(i).name);
        }

        console.log(JSON.stringify(arr, null, ""));
    }

    run(msec) {
        this.updateAll(msec);
        this.renderAll();
    }
}

很简单的代码,但是的确,UI/2D/3D引擎最基础的宏观运行就是如此!

  • 上面算是基本框架,我们实现一个rect精灵用于演示:
/*
关于es6中的super关键字一个前提,两个用法有:
一个前提:
       只有使用了extends的子类才能使用super关键字
两个用法:
       1. 函数调用形式:
               super([基类构造函数参数列表]),必须在子类构造函数中调用super()
               this调用父类的成员属性必须在super()调用后才ok!!!!
       2. 非函数调用形式:
               在子类的成员函数中调用基类类方法时使用super关键字而不是super()函数,切记!
*/
class BLFRectSprite extends BLFSprite {
    constructor(rect = new Rect(0, 0, 100, 50), name = '') {
        //super([基类构造函数参数列表])
        super(name);

        //this调用父类的成员属性必须在super()调用后才ok!!!!
        this.typeName = "RECT";
        this.rect = rect;
    }

    render(render) {
        render.drawRect(this.rect, this.color);
    }

    getSize() {
        return new Size(this.rect.with, this.rect.height);
    }
}

测试代码:

<body>
    <canvas id="myCanvas" width="800" height="600" style="border: 1px solid black">你的浏览器还不支持哦</canvas>
    <script>
        let canvas = document.getElementById("myCanvas");
        let context = canvas.getContext('2d');

        let engine = new BLFEngine2D(context);
        engine.sprMgr.addSprite(new BLFSprite("background"));
        engine.sprMgr.addSprite(new BLFRectSprite(new Rect(0, 0, 50, 25), "spRect"));
        engine.run();
        engine.printAll();
    </script>
</body>
engine_result.png

下一篇,动起来,转起来!

附:
场景图实际是树结构,关于树结构核心是各种遍历算法,在我以前的一篇文章中设计模式在UI系统开发中的应用(导读),有提供一张详细的树结构遍历总结表,大家可以去看一下(虽然是c++描述,但可以用于任何语言)

推荐阅读更多精彩内容