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的对象结构示意图如下:
页面载入完成后,开始初始化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);
}
// ...
}
}[/sourcecode]
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关联数组一样:
存储:
[sourcecode language="javascript"]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;[/sourcecode]
读取:
[sourcecode language="javascript"]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"]);[/sourcecode]
要注意的是,目前localStorage只能存储字符串类型的数据,所以在存储时,对象需要拆分成单独的属性进行存储。在读取时,数字需要转换,对象需要重新组装。
很酷。