H5版俄罗斯方块(2)---游戏的基本框架和实现

 

前言:
  上文中谈到了H5版俄罗斯方块的需求和目标, 这次要实现一个可玩的版本. 但饭要一口一口吃, 很多东西并非一蹴而就. 本文将简单实现一个可玩的俄罗斯方块版本. 下一步会引入AI, 最终采用cocos2d-js来重构之.
  本系列的文章链接如下:
  1). 需求分析和目标创新 
  这些博文和代码基本是同步的, 并不确定需求是否会改变, 进度是否搁置, 但期翼自己能坚持和实现.

演示&下载:
  初步版本效果较为简陋, 其大致效果如图所示:
  
  其代码下载地址为: http://pan.baidu.com/s/1dDm4B0P
  该版本参考了不少cnblogs网友的同类版本, 尤其是博主"奕秋"的"Canvas俄罗斯方块". 再次表示感谢.

模型和构建:
  • 方块抽象和建模
  俄罗斯方块由7种方块组成, 各有特点. 当然也不能忽视方块的旋转特性, 这边采用了"Arika Rotation System"体系, 其具体的旋转变化如下所示:
     
  因此对方块进行抽象, 构建一个基类Shape, 然后7种形状继承于基类Shape来构建.

1
2
3
4
5
6
7
function Shape(x, y, idx, color, shapes) {
  this.x = x;
  this.y = y;
  this.idx = idx;
  this.color = color;
  this.shapes = shapes;
};

  注: 这边的属性依次为x,y坐标, 颜色, 变换形状数组及索引标识.
  由于JavaScript没有所谓类和继承的概念, 需要依靠原型链来模拟构建, 这边我们采用寄生组合式继承方式来实现. 以L型方块为例.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// *) L型方块的转换变换矩阵
LShape.SHAPES = [
  [
    [0, 1, 0, 0],
    [0, 1, 0, 0],
    [0, 1, 1, 0],
    [0, 0, 0, 0]
  ],
  [
    [0, 0, 0, 0],
    [1, 1, 1, 0],
    [1, 0, 0, 0],
    [0, 0, 0, 0]
  ],
  [
    [0, 1, 1, 0],
    [0, 0, 1, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 0]
  ],
  [
    [0, 0, 0, 0],
    [0, 0, 1, 0],
    [1, 1, 1, 0],
    [0, 0, 0, 0]
  ]
];
function LShape(x, y, idx, color) {
  // *) 继承父类的属性变量
  Shape.call(this, x, y, idx, color, LShape.SHAPES);
};
// *) 继承父类的抽象方法
LShape.prototype = new Shape();

  对方块的生成, 可以采用工厂方法, 不过为了简便, 并没有采用.

1
2
3
4
5
6
7
8
function createShape() {
    var shapeTypes = [LShape, JShape, IShape, OShape, TShape, SShape, ZShape];
    var colorTypes = ["red", "green", "blue", "pink"]
    var shapeIdx = Math.floor(Math.random() * 100) % shapeTypes.length;
    var shapePos = Math.floor(Math.random() * 100) % 4;
    var colorIdx = Math.floor(Math.random() * 100) % colorTypes.length;
    return new shapeTypes[shapeIdx](4, 0, shapePos, colorTypes[colorIdx]);
}

  • 按键事件的注册和处理
  俄罗斯方块需要对向左, 向右, 变换的按键事件作出及时的反应外, 还需要对向下的长按键进行处理.
  对于向左, 向右, 变换的按键事件, 简单注册按键事件即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
document.body.addEventListener("keydown", function(e) {
  gameEnv.keydown(e.keyCode);
  switch(e.keyCode) {
  case 37:
    gameScene.keydown(ActionType.ACTION_LEFT);
    break;
  case 38:
    gameScene.keydown(ActionType.ACTION_CHANGE);
    break;
  case 39:
    gameScene.keydown(ActionType.ACTION_RIGHT);
    break;
  case 40:
    gameScene.keydown(ActionType.ACTION_DOWN);
    break;
  }
});

  但对于向下的长按键处理, 除了监听键盘事件外, 还需要在游戏主逻辑循环中, 添加轮询该按键状态.
  因此我们引入一个按键数组, 用于记录按键的状态.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function GameEnv() {
  this.presskeys = new Array(256);
}
GameEnv.prototype.reset = function() {
  for ( var i = 0; i < this.presskeys.length; i++ ) {
    this.presskeys[i] = false;
  }
}
GameEnv.prototype.keyup = function(keyCode){
  if ( keyCode >= 0 && keyCode < 256 ) {
    this.presskeys[keyCode] = false;
  }
};
GameEnv.prototype.keydown = function(keyCode) {
  if ( keyCode >= 0 && keyCode < 256 ) {
    this.presskeys[keyCode] = true;
  }
};

  而游戏的主循环如下所示:

1
2
3
4
5
6
7
var fps = 30 || 0;
setInterval(gameLogic, fps);
 
function gameLogic() {
  gameScene.updateGame();
  gameScene.renderGame(ctx);
}

  以上是我觉得有些小难度的地方, 其余的都是数据结构和canvas绘图的一些知识点, 这边暂略过.

总结:
  一个游戏, 说简单也简单, 说难也难. 难是因为它是个系统工程, 涉及图形绘制, 语音播放, 以及javascript的事件模型等. 当前的俄罗斯方块已有个基本框架, 初步能玩. 虽然离最终设定的目标还有些距离, 但"千里之行, 始于足下", 终是一个很好的开头.

写在最后:
  
如果你觉得这篇文章对你有帮助, 请小小打赏下. 其实我想试试, 看看写博客能否给自己带来一点小小的收益. 无论多少, 都是对楼主一种由衷的肯定.

   

 

posted on   mumuxinfei  阅读(1487)  评论(0编辑  收藏  举报

编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示