14

我正在编写 XNA C# 中的俄罗斯方块克隆,我不确定在高层次上处理游戏数据结构方面的最佳方法。

我对碰撞检测、旋转、动画等非常满意。我需要知道存储“掉落块”的最佳方法——即不再受玩家控制的块。

我认为每个 Tetromino 块都应该存储在它自己的类中,该类由一个 4x4 数组组成,以便块可以轻松旋转。然后问题是我如何将 tetromino 的最终位置存储到游戏网格中,然后将 tetromino 切割成单独的块(对于每个单元格),然后设置主游戏网格的相应位置来保存这些相同的块,然后消失一次 tetromino它已到达最终位置。也许我的方法有一些缺点。

我应该为可以存储的主游戏网格创建一个 10x20 矩阵吗?或者我应该使用堆栈或队列以某种方式存储丢弃的块。或者也许有一些更好的方法/数据结构来存储东西?

我确信我的方法会奏效,但我正在伸出手来看看是否有人知道更好的方法,或者我的方法是否足够好?

PS 不是作业,这将是我的作品集的一个项目。谢谢。

4

10 回答 10

20

一旦一个块是不动的,就没有什么可以将它与任何其他现在不动的块区分开来了。在这方面,我认为将整个网格存储为矩阵是最有意义的,其中每个正方形要么被填充,要么被填充(如果是块的颜色,则连同块的颜色一起)。

我觉得矩阵有很多优点。它将使碰撞检测变得简单(无需与多个对象进行比较,只需与矩阵上的位置进行比较)。将其存储为矩阵还可以更容易地确定何时创建了整行。最重要的是,当一条线消失时,您不必担心拼接不动的 Tetromino。当你这样做时,你可以一举将整个矩阵向下移动。

于 2009-02-19T16:51:15.070 回答
4

这听起来像家庭作业,但我对俄罗斯方块的面向对象方法的看法是让每个单独的正方形都是一个对象,并且“块”(tetrominos)和网格本身都是相同正方形对象的集合。

块对象管理下落方块的旋转和位置,网格处理显示它们和销毁已完成的行。每个块都将具有与之关联的颜色或纹理,这些颜色或纹理将由它来自的原始块对象提供,但网格底部的方块将没有其他指示它们曾经是同一原始块的一部分。

详细地说,当您创建一个新的块对象时,它会在网格上创建一组具有相同颜色/纹理的 4 个正方形。网格管理它们的显示。所以当方块触到底部时,你就忘记了方块,方块仍然被网格引用。

旋转和放下是只有一个块需要处理的操作,并且只有一个它的四个正方形(尽管它需要能够查询网格以确保旋转可以适合)。

于 2009-02-19T16:50:47.657 回答
3

在我看来,实际上不让块看起来像自主块是许多俄罗斯方块克隆的一大失败。我特别努力确保我的克隆总是看起来正确,无论该块仍然“在游戏中”还是被丢弃。这意味着稍微超越简单的矩阵数据结构,并提出支持块部分之间“连接”概念的东西。

我有一个名为的类BlockGrid,它用作 aBlockBoard. BlockGrid有一个抽象(C++ 中的纯虚拟)方法,称为AreBlockPartsSameBlock子类必须重写以确定两个不同的块部分是否属于同一个块。对于 中的实现,如果两个位置都有块部分Block,它会简单地返回。true对于 中的实现,如果两个位置包含相同的Board,则返回。trueBlock

该类BlockGrid使用这些信息来“填充”渲染块中的​​细节,使它们看起来像块。

于 2009-02-19T17:17:27.380 回答
2

使用数组将是处理俄罗斯方块的最简单方法。您在屏幕上看到的内容与内存中使用的结构之间存在直接关联。使用堆栈/队列将是一种过度杀伤和不必要的复杂。

您可以拥有 2 个下落块的副本。一个用于展示(Alpha),另一个用于移动(Beta)。

您将需要一个类似的结构


class FallingBlock
{
  int pos_grid_x;
  int pos_grid_y;
  int blocks_alpha[4][4];
  int blocks_beta[4][4];

  function movedDown();
  function rotate(int direction();
  function checkCollision();
  function revertToAlpha();
  function copyToBeta()
};

_beta 数组将被移动或旋转,并针对电路板进行碰撞检查。如果有冲突,将其恢复为 _alpha,如果没有,则将 _beta 复制到 _alpha。

如果在movedDown() 上发生碰撞,方块的生命就结束了,_alpha 网格将不得不复制到游戏板上并删除 FallingBlock 对象。

董事会当然必须是另一种结构,例如:


class Board
{
  int gameBoard[10][20];

  //some functions go here
}

我用 int 来表示一个块,每个值(如 1、2、3)表示不同的纹理或颜色(0 表示一个空点)。

一旦块成为游戏板的一部分,它只需要显示纹理/颜色标识符。

于 2009-02-19T16:53:57.883 回答
2

实际上我几天前才这样做,除了在 WPF 而不是 XNA 中。这是我所做的:

编辑:似乎我对“阻止”的定义与其他人不同。我定义的块是构成 Tetromino 的 4 个单元之一,而实际的 Tetromino 本身就是一块。

将块作为具有 X、Y 坐标和颜色的结构。(后来我加了一个 bool IsSet 来表示它是在一个浮动块中还是在实际板上,但这只是因为我想在视觉上区分它们)

作为 Block 上的方法,我有 Left、Right、Down 和 Rotate(Block center),它们返回一个新的移位块。这使我可以在不知道该块的形状或方向的情况下旋转或移动任何块。

我有一个通用的 Piece 对象,它包含它包含的所有块的列表以及作为中心的块的索引,它用作旋转中心。

然后我做了一个 PieceFactory 可以生产所有不同的碎片,并且不需要知道它是什么类型的碎片,我可以(并且确实)轻松添加由多于或少于 4 个块组成的碎片的变体,而无需创建任何新类

板由一个字典组成,它是当前板上的所有块,以及可配置的板的尺寸。您也可以使用矩阵,但使用字典我只需要遍历没有空格的块。

于 2009-02-19T17:11:19.940 回答
2

我的解决方案(设计),用 Python 中的示例作为伪代码的良好替代品。

使用 20 x 10 的网格,四联骨牌会掉下来。

Tetromino 由具有坐标 (x,y) 和颜色属性的块组成。

所以,例如,T形四肢骨看起来像这样......

     . 4 5 6 7 8 .
  .
  19     # # #
  20       #
  .   

因此,T 形是坐标为 (5,19)、(6,19)、(7,19)、(6,20) 的块的集合。

移动形状只需对组中的所有坐标应用简单的变换即可。例如,要向下移动形状,将 (0,1)、左 (-1,0) 或右 (1,0) 添加到集合中构成形状的所有坐标中。

这也让您可以使用一些简单的三角函数将形状旋转 90 度。规则是,当相对于原点旋转 90 度时,(x,y) 等于 (-y,x)。

这是一个解释它的例子。从上方取T形,以(6,19)为中心块旋转。为简单起见,将其设为集合中的第一个坐标,因此...

 t_shape = [ [6,19], [5,19], [7,19], [6,20] ]

然后,这是一个将坐标集合旋转 90 度的简单函数

def rotate( shape ):
    X=0      # for selecting the X and Y coords
    Y=1

    # get the middle block
    middle = shape[0]   

    # work out the coordinates of the other blocks relative to the
    # middle block
    rel = []
    for coords in shape:
        rel.append( [ coords[X]-middle[X], coords[Y]-middle[Y] ] )

    # now rotate 90-degrees; x,y = -y, x
    new_shape = []
    for coords in rel:
        new_shape.append( [ middle[X]-coords[Y], middle[Y]+coords[X] ] )

    return new_shape

现在,如果您将此函数应用于我们的 T 形坐标集合...

    new_t_shape = rotate( t_shape )

    new_t_shape
    [[6, 19], [6, 18], [6, 20], [5, 19]]

在坐标系中绘制它,它看起来像这样......

     . 4 5 6 7 8 .
  .
  18       #
  19     # #
  20       #
  .   

这对我来说是最难的一点,希望这对某人有所帮助。

于 2009-03-24T10:16:58.527 回答
1

请记住,以前的混淆 C 代码竞赛的获胜者用不到 512 字节的混淆 C 实现了一个非常好的俄罗斯方块游戏(适用于 BSD unix 上的 VT100 终端):

long h[4];t(){h[3]-=h[3]/3000;setitimer(0,h,0);}c,d,l,v[]={(int)t,0,2},w,s,I,K
=0,i=276,j,k,q[276],Q[276],*n=q,*m,x=17,f[]={7,-13,-12,1,8,-11,-12,-1,9,-1,1,
12,3,-13,-12,-1,12,-1,11,1,15,-1,13,1,18,-1,1,2,0,-12,-1,11,1,-12,1,13,10,-12,
1,12,11,-12,-1,1,2,-12,-1,12,13,-12,12,13,14,-11,-1,1,4,-13,-12,12,16,-11,-12,
12,17,-13,1,-1,5,-12,12,11,6,-12,12,24};u(){for(i=11;++i<264;)if((k=q[i])-Q[i]
){Q[i]=k;if(i-++I||i%12<1)printf("\033[%d;%dH",(I=i)/12,i%12*2+28);printf(
"\033[%dm  "+(K-k?0:5),k);K=k;}Q[263]=c=getchar();}G(b){for(i=4;i--;)if(q[i?b+
n[i]:b])return 0;return 1;}g(b){for(i=4;i--;q[i?x+n[i]:x]=b);}main(C,V,a)char*
*V,*a;{h[3]=1000000/(l=C>1?atoi(V[1]):2);for(a=C>2?V[2]:"jkl pq";i;i--)*n++=i<
25||i%12<2?7:0;srand(getpid());system("stty cbreak -echo stop u");sigvec(14,v,
0);t();puts("\033[H\033[J");for(n=f+rand()%7*4;;g(7),u(),g(0)){if(c<0){if(G(x+
12))x+=12;else{g(7);++w;for(j=0;j<252;j=12*(j/12+1))for(;q[++j];)if(j%12==10){
for(;j%12;q[j--]=0);u();for(;--j;q[j+12]=q[j]);u();}n=f+rand()%7*4;G(x=17)||(c
=a[5]);}}if(c==*a)G(--x)||++x;if(c==a[1])n=f+4**(m=n),G(x)||(n=m);if(c==a[2])G
(++x)||--x;if(c==a[3])for(;G(x+12);++w)x+=12;if(c==a[4]||c==a[5]){s=sigblock(
8192);printf("\033[H\033[J\033[0m%d\n",w);if(c==a[5])break;for(j=264;j--;Q[j]=
0);while(getchar()-a[4]);puts("\033[H\033[J\033[7m");sigsetmask(s);}}d=popen(
"stty -cbreak echo stop \023;cat - HI|sort -rn|head -20>/tmp/$$;mv /tmp/$$ HI\
;cat HI","w");fprintf(d,"%4d on level %1d by %s\n",w,l,getlogin());pclose(d);}

http://www.ioccc.org/1989/tromp.hint

于 2009-02-19T16:49:31.973 回答
1

我绝不是俄罗斯方块专家,但正如你所描述的,10x20 矩阵对我来说似乎是一个自然的选择。

当需要检查您是否完成了一条线并处理它时,它将变得非常容易。只需遍历 2d 数组,查看每个位置的布尔值,看看它们是否加起来为 10 个块位置。

但是,如果有一条完整的线路,您将需要进行一些手动清理。不得不把一切都放下。尽管归根结底,这并不是什么大不了的事。

于 2009-02-19T16:51:25.037 回答
0

使用 Simon Peverett 逻辑,这是我在 c# 中得到的结果

public class Tetromino 
{
    // Block is composed of a Point called Position and the color
    public Block[] Blocks { get; protected internal set; }

    // Constructors, etc.

    // Rotate the tetromino by 90 degrees, clock-wise
    public void Rotate() 
    {
        Point middle = Blocks[0].Position;
        List<Point> rel = new List<Point>();
        foreach (Block b in Blocks)
            rel.Add(new Point(b.Position.x - middle.x, b.Position.y - middle.y));

        List<Block> shape = new List<Block>();
        foreach (Point p in rel)
            shape.Add(new Block(middle.x - p.y, middle.y + p.x));

        Blocks = shape.ToArray();
    }

    public void Translate(Point p)
    {
        // Block Translation: Position+= p; 
        foreach (Block b in Blocks)
            b.Translate(p);
    }
}

注意:使用 XNA,Point结构可以交换为Vector2D

于 2013-08-12T17:40:08.377 回答
0

在我的示例(Java)中 - 所有数字都有块列表 - 可以在需要时删除。同样在我的 Board 类中,我有一个数字列表和一个由用户控制的字段变量数字。当图形“着陆”时 - 它进入其他图形列表,并且用户可以控制新图形。这里有一个更好的解释:http: //bordiani.wordpress.com/2014/10/20/tetris-in-java-part-i-overview/

于 2014-10-20T21:10:13.453 回答