由于不是规则的俄罗斯方块,在消除时,很可能产生不规则的图形,因此,如何判断是否达到消除条件,以及进行方块的裁剪将是本游戏的一个关键问题。
我在做这个游戏时,采用的是最直接的方法,也就是最笨的方法,直接进行裁剪判定。如果有比较好的算法,希望大家可以和我交流。
首先,接上一篇,由于我们创建的刚体模型,需要是凸多边形,因此,每个初始方块都由四个正方形小方块构成。如图:
而本游戏中,方块的旋转应该是任意的(而不是原版游戏中每次旋转都是90度)。因此,当上面的方块停落时,很有可能是这样的方向:
因此,该方块在消除时,可能会发生这样的消除:
这是就需要进行裁剪,同时要满足裁剪后内部刚体还是凸多边形。其实很明显,我们只要分别裁剪里面的几个小方块即可。显而易见,一个凸多边形在经过一条直线切割后,生成的图形还是凸多边形。
接下来就是这个裁剪该如何做的问题了。
虽然每次消除是消除两条直线中间的部分,但是无论是两条直线分割图形和一条直线分割图形其实并无本质区别。因此,这种情况下,只考虑单线裁剪。
现在,我们开始考虑单线情况下的裁剪,如图:
裁剪线与图形整体可能存在相交和不想交问题,即使相交,也可能只与部分内部方块相交,而对于其中的一个方块,还要考虑其和哪一个边相交,故而去裁剪那一个边。
由于最终是按边裁剪,因此我们不考虑整体、不考虑构成整体的小方块,而是考虑构成小方块的几条边。
因此可能会出现以下几种情况,其中蓝点代表起始点,绿点代表终止点(一个边是一条向量):
这种情况是两点在裁剪线上部,不需要进行裁剪。
这种情况是两点在裁剪线下部,也不需要进行裁剪。
这种情况是裁剪线与边相交,两点分列裁剪线两侧,但是起始点(蓝点)在下,终止点(绿点)在上。
此时需要进行裁剪,起始点(蓝点)和裁剪点(裁剪线与边相交的点)成为裁剪完成后下边图形的边,裁剪点(裁剪线与边相交的点)和终止点(绿点)为裁剪完成后上边图形的边。
这种情况是裁剪线与边相交,两点分列裁剪线两侧,但是起始点(蓝点)在上,终止点(绿点)在下。
此时需要进行裁剪,起始点(蓝点)和裁剪点(裁剪线与边相交的点)成为裁剪完成后上边图形的边,裁剪点(裁剪线与边相交的点)和终止点(绿点)为裁剪完成后下边图形的边。
我们没有必要逐一去计算交点,然后判断。因此可以使用上面的方法,先判断情况,然后再根据情况适时进行计算裁剪。
这里使用的思想是,裁剪完成后,生成上部图形和下部图形。裁剪线上部编码为0,下部编码为1。以顺时针方向取方块中的两点,起始点和终止点。直接根据两点的y值,判断两点的编码值。
如果编码值相同,说明两点在裁剪线的同一侧,此时边必定没有与裁剪线相交。因此,不进行裁剪。但是需要根据编码值,将这两点按顺序归入上部图形或者下部图形。
如果编码值不同,进行裁剪,得到裁剪点。然后根据起始点位置,如果起始点编码为0,在裁剪线上部,则将起始点和裁剪点顺次归入上部图形中,将裁剪点和终止点顺次归入下部图形中;反之,则将起始点和裁剪点顺次归入下部图形中,将裁剪点和终止点顺次归入上部图形中。
算法思想大致如此,代码实现如下:
Vector<BaseBlock *> * BaseBlock::bottomLineCutting(float y) { //定义上下多边形集 std::vector<Vec2 *> * topBlock; std::vector<Vec2 *> * bottomBlock; std::vector<int> * topVecsNumber; std::vector<int> * bottomVecsNumber; topBlock = new std::vector<Vec2 *>(); bottomBlock = new std::vector<Vec2 *>(); topVecsNumber = new std::vector<int>(); bottomVecsNumber = new std::vector<int>(); bool isCutting = false; for(int i=0; i<shapeAmount; i++) { //定义上、下二段裁剪点集 std::vector<Vec2> * topShape; std::vector<Vec2> * bottomShape; topShape = new std::vector<Vec2>(); bottomShape = new std::vector<Vec2>(); //逐边裁剪 for(int j=0; j<shapeVecAmount->at(i); j++) { Vec2 startPoint = this->coordinateSpin(shapeVecs->at(i)[j]); Vec2 endPoint = this->coordinateSpin(shapeVecs->at(i)[(j+1)%shapeVecAmount->at(i)]); int cStart = 0; int cEnd = 0; if((fabs(startPoint.y - y) < 1e-6) && (fabs(endPoint.y - y) < 1e-6)) { if(topShape->size() != 0) cStart = cEnd = 0; else cStart = cEnd = 1; } else if(fabs(startPoint.y - y) < 1e-6) { if(endPoint.y - y < 1e-6) cStart = cEnd = 1; } else if(fabs(endPoint.y - y) < 1e-6) { if(startPoint.y - y < 1e-6) cStart = cEnd = 1; } else { if(startPoint.y - y < 1e-6) cStart = 1; if(endPoint.y - y < 1e-6) cEnd = 1; } if(cStart == cEnd) { //两顶点在同一边,无需裁剪 if(cStart == 0) { //顶点在上边,记录到上边顶点集 topShape->push_back(coordinateGoBack(startPoint)); // topShape->push_back(coordinateGoBack(endPoint)); } else { //顶点在下边,记录到下边顶点集 bottomShape->push_back(coordinateGoBack(startPoint)); // bottomShape->push_back(coordinateGoBack(endPoint)); } } else { //两顶点在不同边,需要进行裁剪 float cutting_x = startPoint.x + (endPoint.x - startPoint.x) * (y - startPoint.y) / (endPoint.y - startPoint.y); isCutting = true; if(cStart == 0) { //起点在上,终点在下时 topShape->push_back(coordinateGoBack(startPoint)); topShape->push_back(coordinateGoBack(Vec2(cutting_x,y))); // bottomShape->push_back(coordinateGoBack(endPoint)); bottomShape->push_back(coordinateGoBack(Vec2(cutting_x,y))); } else { //起点在下,终点在上时 bottomShape->push_back(coordinateGoBack(startPoint)); bottomShape->push_back(coordinateGoBack(Vec2(cutting_x,y))); // topShape->push_back(coordinateGoBack(startPoint)); topShape->push_back(coordinateGoBack(Vec2(cutting_x,y))); } } } //添加至方块集 Vec2 * topTempShape = new Vec2[topShape->size()]; Vec2 * bottomTempShape = new Vec2[bottomShape->size()]; for(int index = 0; index < topShape->size(); index++) { topTempShape[index] = topShape->at(index); } for(int index = 0; index < bottomShape->size(); index++) { bottomTempShape[index] = bottomShape->at(index); } if(topShape->size() != 0) { topBlock->push_back(topTempShape); topVecsNumber->push_back(topShape->size()); } if(bottomShape->size() != 0) { bottomBlock->push_back(bottomTempShape); bottomVecsNumber->push_back(bottomShape->size()); } } if(isCutting) { Vector<BaseBlock *> * baseBlockSet = new Vector<BaseBlock *>(); DrawNode * draw = DrawNode::create(); Texture2D * texture = this->getTexture(); RenderTexture * texture1 = RenderTexture::create(this->getContentSize().width,this->getContentSize().height); auto base1 = BaseBlock::createWithTexture(this->getTexture()); auto base2 = BaseBlock::createWithTexture(this->getTexture()); base1->initForm(topBlock,topVecsNumber,topBlock->size()); base2->initForm(bottomBlock,bottomVecsNumber,bottomBlock->size()); base1->setPosition(this->getPosition()); base2->setPosition(this->getPosition()); base1->setRotation(this->getRotation()); base2->setRotation(this->getRotation()); baseBlockSet->pushBack(base1); baseBlockSet->pushBack(base2); return baseBlockSet; } else { if(bottomBlock->size() != 0) { Vector<BaseBlock *> * baseBlockSet = new Vector<BaseBlock *>(); auto base = BaseBlock::createWithTexture(this->getTexture()); baseBlockSet->pushBack(base); return baseBlockSet; } else { return NULL; } } }