Behind Tetris5 – Game Core

Tetris5的游戏逻辑核心移植自本科的一个J2ME课程设计,基于性能方面的考虑,使用该核心设计的手机版俄罗斯方块可以在手机上流畅运行,那么移植到JavaScript上的性能将会有保证,并且毕竟JavaScript和Java还是有一些联系,从Java移植的难度也相对较小。JavaScript和Java都是面向对象的编程语言,但是它们在对象的实现方法上还是有一些不同。Tetris5主要使用了两种对象定义方法:函数对象和单次对象。函数对象的定义和使用方法例如下面的例子(Block.js):

var Pos = function () {
this.x = 0;
this.y = 0;
};

var Block = function (type, initPos) {
this.blockType = type;
this.pos = new Pos(); // 使用之前定义的Pos对象
this.pos.x = initPos.x;
this.pos.y = initPos.y;
this.style = 0;
// ...
this.Down = function() {
this.pos.y--;
return this.pos;
};
// ...
};

这种定义可以创建多个函数实例,在不同的场合使用。所以像位置和块对象采用了这种方法,它们在游戏进行的过程中会被多次创建和销毁。

另一种对象定义方法则是定义单次对象,例如Game对象:

var Game = {
// ...
m_ground : new Array(10),
m_base : new Array(10),
// ...
init : function (gameUI) {
Game.m_running = false;
Game.m_effecting = false;
Game.m_iScore = 0;
Game.m_nLevel = 0;
Game.m_nInitLevel = 0;
Game.m_nextType = -1;
Game.m_paused = false;
Game.m_cheat = false;
Game.m_isAutoUpBase = 0;
Game.m_gameUI = gameUI;
// ...
},
// ...
};

Game对象每次页面加载后只会根据生成一次,之后一直由该对象根据GameUI提供的操作控制游戏数据,直至用户关闭页面。GameUI也是一个单词对象,它负责接收用户操作,显示界面,游戏时读取Game对象数据,渲染游戏画面。

Tetris5的对象结构示意图如下:

struct

debug

页面载入完成后,开始初始化GameUI对象,初始化的内容包括调整界面、绑定按键和鼠标操作、准备Canvas、读取之前保存的数据或者初始化玩家信息 和载入资源,当资源载入完成后,GamUI将会把自己的m_game成员变量指向Game对象,让Game.m_gameUI指向自己,最后显示出菜单, 响应玩家操作。在菜单中,GameUI响应并且处理按键和鼠标操作,切换界面。如果游戏开始,GameUI则把玩家的操作变换成游戏操作,发送给Game 对象。Game对象储存有当前下落块和底部积累块的信息,当前下落块是一个Block对象,底部积累块信息则是一个数组,其中0表示空位置,1-6表示有 颜色块处于该位置,如右图所示。

Game中封装的游戏逻辑根据GameUI发送来的操作移动下落块,当下落块到底部后,将下落块写入底部积累块的数组,重新在顶部创建一个新的Block对象,作为新下落块。操作完成后,GameUI会调用Game.Display()得到一个数组,该数组将下落块和底部积累块的信息整合在一起,GameUI.paint()函数根据该数组,在Canvas上渲染游戏。Game对象封装的下降计时器也会直接操作下降块使其自动下落,下落完成后,Game对象通过自己的m_gameUI成员变量要求GameUI重绘。

使用JavaScript和Canvas渲染游戏画面十分简单,根据块的位置和块图片的大小计算出块左上角的坐标,将相应颜色的块图片绘制出来即可。主要代码如下:

var i = 0;
var j = 19;
for (; j >= 0; --j) {
i = 0;
for (; i < 10; ++i) {
var curBlock = GameUI.m_gameVector[i][j];
// ...
if (GameUI.m_canvasDraw.fillRect) {
GameUI.m_canvasDraw.drawImage(
GameUI.m_blockImgs[curBlock], i * 18, (19 - j) * 18);
}
// ...
}
}

GameUI.m_canvasDraw在GameUI初始化时通过GameUI.m_canvas.getContext(’2d’)做了准备,将Canvas元素的2D上下文预先取出。

由于JavaScript单线程的特点,使得Game对象在渲染游戏时与原始的Java版本在实现的细节上有一些区别。Java在Canvas的repaint()调用后会立即返回,继续响应操作,就可能会出现画面并未真正渲染完成,而游戏数据已经发生变化。使得在性能较低的设备上运行时,下落块可能会出现跳跃的现象。而JavaScript的Canvas操作都是同步函数,操作没有完成前不会返回,所以GameUI必须将游戏完全渲染完成才能继续响应用户操作,这样就不需要特别考虑数据同步的问题。包括在显示消去动画效果的时候,Java版本需要通过一些路障变量同步操作,而JavaScript版本则不需要这些路障变量,因为在显示动画时,GameUI不会立即响应操作。

玩家的游戏数据使用HTML5中新的localStorage存储,localStorage在浏览器端提供了一个简单的key/value存储方案,它不像Cookies,并不在浏览器和服务器之间传送,只能本地访问。localStorage是网络应用存储一些个人设置的完善的Cookies替代品,它不需要传送至服务器,可以加快访问速度,也可以减少隐私数据泄漏的问题。localStorage的使用很简单,就像一般的JavaScript key/value关联数组一样:

存储:

localStorage["m_curBlock.blockType"] = GameUI.m_game.m_curBlock.blockType;
localStorage["m_curBlock.pos.x"] = GameUI.m_game.m_curBlock.pos.x;
localStorage["m_curBlock.pos.y"] = GameUI.m_game.m_curBlock.pos.y;
localStorage["m_curBlock.style"] = GameUI.m_game.m_curBlock.style;
localStorage["m_curBlock.color"] = GameUI.m_game.m_curBlock.color;

读取:

var blockPos = new Pos();
blockPos.x = parseInt(localStorage["m_curBlock.pos.x"]);
blockPos.y = parseInt(localStorage["m_curBlock.pos.y"]);
GameUI.m_game.m_curBlock =
new Block(parseInt(localStorage["m_curBlock.blockType"]), blockPos);
GameUI.m_game.m_curBlock.style =
parseInt(localStorage["m_curBlock.style"]);
GameUI.m_game.m_curBlock.color =
parseInt(localStorage["m_curBlock.color"]);

要注意的是,目前localStorage只能存储字符串类型的数据,所以在存储时,对象需要拆分成单独的属性进行存储。在读取时,数字需要转换,对象需要重新组装。

One thought on “Behind Tetris5 – Game Core

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>