flysun027

有思想的博客

导航

自己动手编写俄罗斯方块

Posted on 2018-07-12 21:27  flysun027  阅读(2894)  评论(0编辑  收藏  举报

一、寄言
  很多编程爱好者都编写过俄罗斯方块游戏。我也非常喜欢这个游戏,时不时的把模拟器翻出来活动活动手指,于是就试着亲手实现一下这一经典游戏,相信很多人和我有同样的想法。制作这个游戏的目的是希望以一种最简单的方式来快速实现一些可用的功能,因此抛开了很多的复杂概念,只展示出基本的设计思想并未过多涉及语言细节。这里记下其中的过程以备日后有资料可查。

二、准备开始
  我们先来重温一下经典的红白机电视游戏机上的俄罗斯方块游戏吧。注意这可不是在浪费时间。我们要认真的记录下他的基本信息,因为我们需要有个明确的目标不至于使我们在设计时陷入混乱。

  俄罗斯方块游戏的基本信息:
    1.全部游戏过程是在一个高24宽10的方格阵中进行
    2.游戏能产生7种由4个方格组成的大方块和他们的旋转变形
    3.活动大方块可以下落、变形,在下落到有静止方格时停止
    4.一行方格落满时,这行方格消除,其上方的方格下落补齐
    5.游戏在大方块出口无法放置新的大方块时结束
    6.一行方格消去时游戏得分增加
    7.分数达到一定限度是等级增加,游戏速度加快

三、游戏引擎
  好了有了这些我们就有了一个明确的设计目标,可以开始进行具体设计工作了。在此还要提到一个非常重要的概念“游戏引擎”,是的游戏引擎,他不只是在大型3D游戏中发挥着他不可替代的作用,在我们的游戏中同样重要和有用。游戏引擎很好的隐藏了一些与游戏无关的代码,这些代码主要负责处理琐碎的事务:创建游戏、处理显示、处理键盘事件、提供绘图方法、为游戏提供循环,并且具有良好的扩展性。因此,让我们的注意力集中到游戏过程本身,使游戏开发过程尽可能简单和直接。
  下面是裁剪的游戏引擎代码(JAVA applet):
     public class BoxEngine extends Applet implements Runnable, KeyListener
     {
          public int appletWidth;
          public int appletHeight;
          private Image imageBuffer;//创建双缓冲图像
          public Graphics graphics;//提供绘图方法
          ......
          public void run()
          {
               while (true)
               {
                    //设置游戏循环
                    ......
                    try { Thread.sleep(delay); }
                    catch (InterruptedException e) { }
               }
          }

          public void keyPressed(KeyEvent e)
          {
               ......
               repaint();//按下键后强绘一帧,保证实时性
          }

     }
  这里游戏引擎提供了外部环境的基本信息、绘图方法、键盘响应事件、和游戏循环,好了这些对于我们已经足够用了。到此,我们的游戏从引擎中获得这些基本的信息和功能就很方便了。

四、设计游戏

  1)游戏流程设计

  这里省略看代码吧。

  2)游戏地图设计

  将游戏地图用一个数组来表示是个好主意,这样其他的游戏函数可以轻松的操纵这个数组public int gameMap[][];。实际上最上面的两行并不显示出来,因为那里永远不会见到被填充的方格。实心的部分是边界为了防止大方块越出游戏区域设置的一道“墙”。这里还有一系列对数组值的定义:=0 无方块、>1 活动方块、>10 非活动方块、=10 墙。

     0 1 2 3 4 5 6 7 8 9101112
   0■□□□□□□□□□□■■
   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■■■■■■■■■■■■■

  3)大方块设计

  大方块的设计的好坏直接关系到操纵他的难易程度。可以发现所有大方块的变形都可以在4x4的方格中完成。所以用一个二维数组来存放他们的编码。编码方式由左到右、由上到下,1 有方格、0 无方格。

    方块形态---->
    00       01       02       03
 方□□□□ □□□□ □□□□ □□□□
 块□□□□ □□□□ □□□□ □□□□
 种□■■□ □■■□ □■■□ □■■□
 类□■■□ □■■□ □■■□ □■■□
 | 10       11       12       13
 | □□□□ □■□□ □□□□ □■□□
 ^ □□□□ □■□□ □□□□ □■□□
  □□□□ □■□□ □□□□ □■□□
  ■■■■ □■□□ ■■■■ □■□□
    20       21       22       23
  □□□□ □□□□ □□□□ □□□□
  □□□□ □■□□ □□□□ □■□□
  ■■□□ ■■□□ ■■□□ ■■□□
  □■■□ ■□□□ □■■□ ■□□□
    30       31       32       33
  □□□□ □□□□ □□□□ □□□□
  □□□□ ■□□□ □□□□ ■□□□
  □■■□ ■■□□ □■■□ ■■□□
  ■■□□ □■□□ ■■□□ □■□□
    40       41       42       43
  □□□□ □□□□ □□□□ □□□□
  □□□□ ■□□□ □□□□ □■□□
  □■□□ ■■□□ ■■■□ ■■□□
  ■■■□ ■□□□ □■□□ □■□□
    50       51       52       53
  □□□□ □□□□ □□□□ □□□□
  □□□□ ■■□□ □□□□ □■□□
  ■□□□ ■□□□ ■■■□ □■□□
  ■■■□ ■□□□ □□■□ ■■□□
    60       61       62       63
  □□□□ □□□□ □□□□ □□□□
  □□□□ ■□□□ □□□□ ■■□□
  □□■□ ■□□□ ■■■□ □■□□
  ■■■□ ■■□□ ■□□□ □■□□
  大方块颜色定义:洋红 1、桔黄 2、绿 3、青 4。

  数组高一位由两个选择子(方块种类,方块形态)来共同构成,低位是16位编码。

  4)小地图设计

  小地图是显示形态为0下一个要出现的大方块。因此,只需要4x2就够用了。
    例:
      0 1 2 3
   0□■■□
   1■■□□

  5)重点操作函数设计

  这里说一下比较有意思的一组函数,大方块移动函数包括了左移、右移、下移、变形。其设计的目标是使大方块在地图内有尽可能多的自由度。最主要的改变在变形函数上。变形函数一共提供两次变形,在第一次变形失败后,第二次变形是左移一次的变形,如果再次失败则右移回位。
  例:
   6■□□□□□□□□■□■■
   7■□□□□□□□□■■■■
   8■□□□□□□□□□■■■
   9■□□□□□□□□□□■■ 第一次变形失败

   6■□□□□□□□■□□■■
   7■□□□□□□□■■□■■
   8■□□□□□□□□■□■■
   9■□□□□□□□□□□■■ 左移

   6■□□□□□□□□■■■■
   7■□□□□□□□■■□■■
   8■□□□□□□□□□□■■
   9■□□□□□□□□□□■■ 第二次变形

  代码:
     public void change()
     {
          if (isMove(4) == 1)//第一次变形
               state = (++state) % 4;//变形成功
          else
          {
               int left = left();//左移
               if (isMove(4) == 1)//第二次变形
                    state = (++state) % 4;//变形成功
               else
                    if (left == 1) siteX++;//变形失败
          }
     }

  另一个是等级设置函数,他的功能是0~100分设置等级为1,100~200分设置等级为2 ... 800~900分设置等级为9。因为我比较懒不想用if else这样一大串的代码所以就另想了个办法来实现。函数有一个外部计数器,只要分数比计数大就改变等级和这个计数,这样一个条件判断就完成了。

  代码:
     private int stemp;

     private void setLevel()
     {
          if (stemp <= 900 && score >= stemp)
          {
               level++;
               be.delay = ((10 - level) * 50);
               stemp += 100;
          }
     }

 

完整源码:

java 版

链接:https://pan.baidu.com/s/1yulBpz6gxCeJvM4YaHM1JA
提取码:rerf

C#版

链接:https://pan.baidu.com/s/1ISo24gNI8dLPyx2wxMg-dw?pwd=mwc5
提取码:mwc5