本篇目的:
- 两种类型的精灵系统:场景图类型和非常场景图类型
- UML静态类结构图
- 一个关键的,最小化的,非场景图类型的精灵系统的实现源码
最小化的概念:
为了演示动画和数学方面的基础知识,在前面花了一点时间,撰写了10多篇文章,描述了如何实现一个可以演示用的渲染引擎(系统)。
而本篇继续上面的内容,实现一个最小化的,但是可运行的,并且蕴含关键技术的精灵系统 。后面我们会按需进行渲染系统和这个动画精灵系统的扩展。
场景图概念:
场景图是指:
将整个场景中每个entity(2D动画中,可以称为精灵)以层次节点的方式表示,他们之间形成具有父子关系的树结构,该树可以称为场景树(可以将树结构堪称是图结构的一种特殊方式)。
一直以来,场景图在UI引擎,2D动画引擎中占绝对地位。
3D引擎中,场景图使用也非常广泛(当然也有蛮多引擎没有使用场景图)。
可以说,2D中,场景图是必须的,但是3D中,场景图是可选的。
简单来说,2D使用场景图方式可以解决前后顺序,深度关系的问题,而3D中,因为有z轴深度值,因此使用场景图是非必须的
树结构的表示的威力巨大,但是今天我们就不闲聊这块内容。
后续我会花时间来详谈这块东西,这个是整个UI/2D引擎的精华部分!
为什么我们现在实现的是非场景图类型:
很简单,时间来不及。我先实现非场景图类型的系统,推进我们的文章往前走。
空闲时间,实现场景图基础结构,然后再融合到我们的精灵系统中。
这样也有好处,让大家非常清晰的了解到非场景图的好处和劣处,以及场景图的优点和缺点!
一张简单的UML类结构图(成员操作就懒得列出,以后有时间再添加进去):
源码实现:
- 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>
下一篇,动起来,转起来!
附:
场景图实际是树结构,关于树结构核心是各种遍历算法,在我以前的一篇文章中设计模式在UI系统开发中的应用(导读),有提供一张详细的树结构遍历总结表,大家可以去看一下(虽然是c++描述,但可以用于任何语言)