很久以前玩的那种2D六角俄罗斯方块在线玩,现在到哪里...

cocos2d-x学习笔记----开发简单的俄罗斯方块
五一长假,必须在家等快递,网络坏了,手头的书都看完了,百无聊赖,于是用cocos2d-x写了个俄罗斯方块,比较粗糙,只有简单的gameplay部分,算是对前一阵学习的一个小小总结,其实,俄罗斯方块这种游戏不适合练习cocos2d-x的,嘿嘿。
代码已经放入google code了
/p/cc-tetris/
20像素一个小格子(tile),全屏分成16*24个格子,每个方块(tetris)占4个格子,组成各类图形。
当一行16个格子装满后,此行内容消失,上边整体下降一行。当向上溢出时,游戏结束。
4种操作方式,触摸屏幕左半部,Tetris向左,触摸屏幕右半部,tetris向右,向上滑屏,Tetris变型,向下滑屏Tetris快速下降。
只有一个场景和一个layer,gameplay类
将屏幕分成16*24个格子,格子对应类Tile,由TileMap类管理。
Tetris是图形的抽象基类,每种图形对应一个派生类。
Chip为格子和图形内容类,用于显示等。
三,场景和层
这部分简单,照着helloworld抄一下就行了
因为游戏是竖着玩的,需要修改RootViewController.cpp,如下
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
return UIInterfaceOrientationIsPortrait(
interfaceOrientation );
四,gameplay实现
1. 成员变量如下
eGameState m_ 游戏状态
Tetris * m_curT 玩家当前操作的图形
TetrisChip * *m_curC 当前图形对应的那4个chip,
int m_curDownT 图形每隔一段时间下落一层,由这个变量定时
int m_collisionT
图形上下左右是否有“邻居”,有的话设置对应的碰撞标志位。
TileMap m_tileM 管理16*24个tile
下面三个变量用来判断触摸类型
CCPoint m_touchBeginP&
CCPoint m_touchEndP
bool m_touchM
GamePlay::update(ccTime dt)
int tick = (int)(dt *
1000); di是表示秒的浮点型,游戏需要表示毫秒的整型作为tick
if(m_state == GAME_START
|| m_state ==
GAME_RUN_TETRIS_STICK)
当游戏刚开始,或者上一个图形下落完毕,生成一个新图形
generateTetris();
m_state = GAME_RUN_TETRIS_MOVE;
m_collisionType =
Tetris::COLLISION_NONE;
else if(m_state
==GAME_RUN_TETRIS_MOVE )
if(m_curDownTick
m_curDownTick -=
m_curDownTick = DOWN_INTERNAL;
下落之前先判断一下碰撞类型,然后决定是否下落
m_collisionType =
checkCollision();
if(m_collisionType
Tetris::COLLISION_BOTTOM)
如果图形与底部的格子碰撞,说明图形下落完毕,将其插入tilemap或者游戏结束
if(addTileToMap()
checkGameOver())
m_state = GAME_RUN_TETRIS_STICK;
m_state = GAME_OVER;
normalDown();
GamePlay::ccTouchesBegan(CCSet *pTouches,
CCEvent *pEvent)
此方法是cocos2d-x的标准操作,取touch集合第一个touch,将其位置转成opengl坐标,没办法,这些坐标太乱了,touch默认坐标是屏幕坐标,左上角为远点,cocos默认坐标是opengl坐标,左下角是原点。
CCSetIterator it =
pTouches-&begin();
CCTouch* touch = (CCTouch*)(*it);
m_touchBeginPos =
touch-&locationInView(
touch-&view() );
m_touchBeginPos =
CCDirector::sharedDirector()-&convertToGL(
m_touchBeginPos );
m_touchMove = false;
GamePlay::ccTouchesMoved(CCSet *pTouches,
CCEvent *pEvent)
m_touchMove = true;
GamePlay::ccTouchesEnded(CCSet *pTouches,
CCEvent *pEvent)
CCSetIterator it =
pTouches-&begin();
CCTouch* touch = (CCTouch*)(*it);
m_touchEndPos =
touch-&locationInView(
touch-&view() );
m_touchEndPos =
CCDirector::sharedDirector()-&convertToGL(
m_touchEndPos );
if(!m_touchMove)
如果没有move,比较简单,判断触摸点在屏幕左边还是右边
if(m_touchBeginPos.x
TileMap::getScreenMidWidth())
normalLeft();
normalRight();
如果move了,那么看手指是否上下滑动超过50像素,这个纠正值得有,否则太灵敏了
if(m_touchBeginPos.y
m_touchEndPos.y)
rotation();
if(m_touchBeginPos.y
m_touchEndPos.y)
quickDown();
m_touchMove = false;
使用简单和优雅的方法实现碰撞,不比担心效率,毕竟元素太少了
首先需要加一圈辅助格子,里面填满东西,这样便可统一处理图形与场景内元素和场景边缘碰撞。
TileMap::TileMap()
tileArraySize(x,y);
for(int i = 0; i
for(int j = 0; j
m_tiles[i][j] = 0;
// use the empty tetris to help check collision
for(int i = 0; i
m_tiles[0][i] = new
Chip(0,i));
m_tiles[x-1][i] = new
Chip(x-1,i));
for(int i = 1; i
& x - 1; ++ i)
m_tiles[i][0] = new
Chip(i,0));
只需遍历所有格子(包括那圈辅助格子),碰撞检测完全交给Tetris就行
GamePlay::checkCollision(void)
int collisionType = 0;
TileMap::tileArraySize(x, y);
for(int i =0; i
for(int j = 0; j
Tile * tile =
m_tileMap.getTile(i, j);
collisionType |=
m_curTetris-&checkCollision(tile-&getChip());
return collisionT
遍历图形的4个chip,判断图形的chip与目标chip的位置来判断碰撞类型
Tetris::checkCollision(Chip * chip)
int type =
COLLISION_NONE;
chip-&getTileX();
chip-&getTileY();
for(int i = 0; i
& CHIP_NUM; ++ i)
m_chips[i]-&getTileX();
m_chips[i]-&getTileY();
if( x0 & x1
&& y0 == y1
&& x0 + 1 == x1)
type |= COLLISION_LEFT;
if(x1 & x0
&& y0 == y1
&& x1 + 1 == x0)
type |= COLLISION_RIGHT;
if(y0 & y1
&& x1 == x0
&& y1 + 1 == y0)
type |= COLLISION_TOP;
if(y1 & y0
&& x1 == x0
&& y0 + 1 == y1)
type |= COLLISION_BOTTOM;
下左右三种移动,直接调用tetris对应的方法即可,这里有一处不优雅的地方,left和right检测了碰撞,而down没有检测,因为在在update检测过了。
GamePlay::normalDown(void)
CCAssert(m_curTetris,"");
m_curTetris-&tileDown();
void&&GamePlay::normalLeft(void)
CCAssert(m_curTetris,&"");
m_collisionType&=&checkCollision();
if(m_collisionType&&&Tetris::COLLISION_LEFT)
m_curTetris-&tileLeft();
void&GamePlay::normalRight(void)
CCAssert(m_curTetris,&"");
m_collisionType&=&checkCollision();
if(m_collisionType&&&Tetris::COLLISION_RIGHT)
m_curTetris-&tileRight();
快速向下移动也容易,循环调用down方法,直到监测到向下碰撞为止。
GamePlay::quickDown(void)
CCAssert(m_curTetris,
m_collisionType =
checkCollision();
if(m_collisionType
Tetris::COLLISION_BOTTOM)
normalDown();
9年前第一次做俄罗斯方块时候发现两件事,一是每个图形由四个小格子组成,二是变性就是讲基础图形顺时针旋转,玩了那么多年都没发现,唉。
变性过程中可能碰到其它非空格子,这种情况不允许变性操作。做这个时候卡了一下,拿了一篇纸画格子,枚举了每种情况,写了n多代码,最后效果却不好。后来推到重来,干脆就让它先变形,然后判断是否冲突,若是冲突再变回去。这下变形简单了,因为检测冲突远比事先判断要容易很多很多。
GamePlay::rotation(void)
CCAssert(m_curTetris,
m_curTetris-&rotation();
if(rotationCollision())
m_curTetris-&unRotation();
GamePlay::rotationCollision(void)
TileMap::tileArraySize(x, y);
for(int i =0; i
for(int j = 0; j
Tile * tile =
m_tileMap.getTile(i, j);
for(int k = 0; k
& Tetris::CHIP_NUM; ++
TetrisChip * chip = m_curChips[k];
CCAssert(chip, "");
if(chip-&getTileX() ==
chip-&getTileY() == j)
return true;
return false;
变形和反变形只需调用tetris的相关方法
其实就是给4种(不到4种的也算4种,多余的算重复)编号,加加减减后重新排列chip
Tetris::rotation(void)
if(++ m_shape
&= CHIP_NUM)
m_shape = 0;
reShape();
Tetris::unRotation(void)
if(-- m_shape &
m_shape = CHIP_NUM -
reShape();
reShape发放根据shape编号,将chip重新排布,每个派生类都要override该方法
TTetris::TTetris(TetrisChip
** chipList, int left, int top)
: Tetris(chipList, left, top)
reShape();
TTetris::reShape(void)
switch(m_shape)
m_chips[0]-&setTile(m_left
m_chips[1]-&setTile(m_left
m_chips[2]-&setTile(m_left
m_chips[3]-&setTile(m_left
+ 1, m_top -
m_chips[0]-&setTile(m_left
m_chips[1]-&setTile(m_left,
m_top - 1);
m_chips[2]-&setTile(m_left
+ 1,& m_top
m_chips[3]-&setTile(m_left
+ 1, m_top -
m_chips[0]-&setTile(m_left
m_chips[1]-&setTile(m_left,
m_top - 1);
m_chips[2]-&setTile(m_left
+ 1,& m_top
m_chips[3]-&setTile(m_left
+ 2, m_top -
m_chips[0]-&setTile(m_left,
m_chips[1]-&setTile(m_left,
m_top - 1);
m_chips[2]-&setTile(m_left
+ 1,& m_top
m_chips[3]-&setTile(m_left,
m_top - 2);
bool&GamePlay::addTileToMap(void)
delete&m_curTetris;
m_curTetris&=&NULL;
先判断是否可以将图形的chip加入到tilemap中,如果不能就gameover了
for(int&i =&0; i &&Tetris::CHIP_NUM; ++ i)
TetrisChip&* p
=&m_curChips[i];
p-&getTileX();
p-&getTileY();
if(m_tileMap.setTile(x, y, p))
m_curChips[i] =&NULL;&//
tileMap will destroy it
return&false;
加完后清除满行
clearfullTiles();
return&true;
判断一个满行后,就将其删除,然后将以上所有行都下移,继续这个操作,直到没有满行为止
void GamePlay::clearfullTiles(void)
TileMap::tileArraySize(x, y);
bool finished = true;
for(int v = y - 1; v
& 0; -- v)
bool full = true;
for(int h = 1; h & x
- 1; ++ h)
if(!m_tileMap.getTile(h, v))
full = false;
m_tileMap.delTilesForLine(v);
finished = false;
for(int i = v + 1; i
m_tileMap.moveLine(i, i - 1);
if(finished)
删除和移动都不难,只要算好坐标就行,调试起来挺麻烦的
void TileMap::delTile(int x,
CCAssert(m_tiles[x][y], "");
delete m_tiles[x][y];
m_tiles[x][y] = NULL;
void TileMap::delTilesForLine(int
tileArraySize(w, h);
for(int i = 1; i & w
- 1; ++ i)
delTile(i, y);
void TileMap::moveLine(int from,
tileArraySize(w, h);
for(int i = 1 ; i &
w-1; ++ i)
m_tiles[i][to] = m_tiles[i][from];
if(m_tiles[i][to])
m_tiles[i][to]-&getChip()-&setTile(i, to);
m_tiles[i][from] =
8. gameover
两种情况游戏结束,一是无法将tetris的chip加入到tilemap中,上边讨论过
另一种情况是当最上方的那行的某个tile被占用了
bool GamePlay::checkGameOver(void)
TileMap::tileArraySize(x, y);
for(int i = 1; i & x
- 1; ++ i)
if(m_tileMap.getTile(i, y - 1))
return true;
return false;
五,chip与显示
chip主要用于显示一个小格子的内容,tile和tetris都会用到它。因为需要两种Chip,一种做做边框,隐藏的,一种显示在场景中,所以使用两层类结构,chip为基类,派生类TetrisChip用于显示
TetrisChip::TetrisChip(CCNode
生成一个sprite,然后将其加入layer,这里对应的就是gameplay
CCAssert(layer, "");
m_sprite = CCSprite::spriteWithFile("HelloWorld.png",
CCRect(220,100, 20,20));
CCAssert(m_sprite, "");
layer-&addChild(m_sprite);
TetrisChip::~TetrisChip()
析构时候就是讲该sprite移出layer,它会被自动释放掉,我特别讨厌cocos的这种内存管理,模仿objc干嘛呀,搞的与c++不统一,容易出错。
m_layer-&removeChild(m_sprite, true);
void TetrisChip::setTile(int x,
设置tile坐标后需要更新一下位置
Chip::setTile(x, y);
updatePosition();
void TetrisChip::updatePosition(void)
float posX, posY;
TileMap::tileToPos(m_tileX, m_tileY, posX, posY);
CCAssert(m_sprite, "");
m_sprite-&setPosition(CCPointMake(posX,
六,Tetris建模
一个抽象基类,每种图形对应一个派生类
成员变量如下
TetrisChip *
m_chips[CHIP_NUM]; 4个chip
int m_ 图形标号,用于旋转
定义一个工厂方法随机生成一个图形,注意,Tetris只是引用chip,他的所属关系属于gameplay,本来不想这么做,想在下落完毕后移交一次所属关系,可是惧怕cocos的内存管理,唉。
Tetris * TetrisFactory::generateATetris(TetrisChip ** chipList, int
left, int top)
int index = rand() % 7;
switch(index)
return new LineTetris(chipList, left,
return new ALTetris(chipList, left,
return new LTetris(chipList, left,
return new TTetris(chipList, left,
return new BoxTetris(chipList, left,
return new AHTetris(chipList, left,
return new HTetris(chipList, left,
CCAssert(false, "");
return NULL;
void GamePlay::generateTetris(void)
// TODO: random
TileMap::getBornTile(left, top);
for(int i = 0; i &
Tetris::CHIP_NUM;
m_curChips[i] = new
TetrisChip(this);
m_curTetris = TetrisFactory::generateATetris(m_curChips, left, top);
CCAssert(m_curTetris, "");
七,格子管理
TileMap其实就是一个讲屏幕坐标转换成格子坐标的管理类,代码基本上前面已经引用过了。
Tile * m_tiles[TILE_NUM_X +
2][TILE_NUM_Y +
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

参考资料

 

随机推荐