Cocos2dx项目实战(7)——中国象棋

许久没有做一个完整的项目了,这次就让我们来做一个经典游戏吧。

本文仅供个人记录和复习,不用于其他用途

前言

源代码我已开源至GitHub,这个是最基础的版本,AI对战和网络对战的版本会后续补充。

设计思路

  • 生成棋盘
  • 移动棋子
  • 走棋规则
  • 游戏结束

有了这些,那么就让我们一个个来解决。

生成棋盘

观察棋盘你会发现,棋子以将为分界线,左右对称;以楚河汉界为分界线,上下对称。这样,我们就不需要生成所有的棋子,我们只需要生成左下角的棋子,然后按照左右对称、上下对称的顺序进行复制即可。

由于棋盘图片制作的比较好,行列的距离基本相等,所以我们不必弄清所有位置的坐标,只要按照下面的公式进行计算即可:

棋子在X轴的位置 = 原点偏移位置 + 列数 × 列宽

棋子在Y轴的位置 = 原点偏移位置 + 行数 × 行宽

具体的实行方法为:找到棋盘左下角的位置坐标、测试出行宽列宽、代入计算公式。先来看看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 初始化棋子
for (int i = 0; i != 32; i++)
{
_chessArray[i] = Chess::create();
_chessArray[i]->initChess(i);
addChild(_chessArray[i]);
}
void SceneGame::createPlate()
{
// 摆放棋盘
Sprite *plate = Sprite::create("background.png");
addChild(plate);
plate->setPosition(Vec2(0, 0));
plate->setAnchorPoint(Vec2(0, 0));
// 将棋盘缩放到对应大小
plate->setScale(Director::getInstance()->getWinSize().height / plate->getContentSize().height);
}

这段代码主要用于初始化棋子,同时导入棋盘图片。这里要注意一点,游戏窗口的大小为960X640,而棋盘的高度已经超出了这个值,所以必须要进行缩放操作(以高度为准进行等比例缩小)。缩放后的画面可以参考第一张图,左边是棋盘,右边是预留的控制面板。

导入完棋盘后,再来看看如何初始化棋子:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
void Chess::initChess(int id)
{
struct
{
TYPE type;
int row;
int col;
} proper[9] = {
{ CHE, 0, 0 },
{ MA, 0, 1 },
{ XIANG, 0, 2 },
{ SHI, 0, 3 },
{ BING, 3, 2 },
{ BING, 3, 0 },
{ PAO, 2, 1 },
{ JIANG, 0, 4 },
{ BING, 3, 4 }
};
_id = id;
_red = id < 16;
// 棋盘是对称的,可以进行复制
if (id <= 8)
{
this->_row = proper[id].row;
this->_col = proper[id].col;
this->_type = proper[id].type;
}
else if (id > 8 && id < 16)
{
int index = id - 9;
this->_row = proper[index].row;
this->_col = 8 - proper[index].col;
this->_type = proper[index].type;
}
else if (id >= 16)
{
int index = id - 16;
if (index <= 8)
{
this->_row = 9 - proper[index].row;
this->_col = 8 - proper[index].col;
this->_type = proper[index].type;
}
else if (index > 8)
{
index -= 9;
this->_row = 9 - proper[index].row;
this->_col = proper[index].col;
this->_type = proper[index].type;
}
}
// 设置纹理
Texture2D *texture;
switch (_type)
{
case CHE:
if (_red)
{
texture = TextureCache::getInstance()->addImage("rche.png");
}
else
{
texture = TextureCache::getInstance()->addImage("bche.png");
}
break;
case MA:
if (_red)
{
texture = TextureCache::getInstance()->addImage("rma.png");
}
else
{
texture = TextureCache::getInstance()->addImage("bma.png");
}
break;
case PAO:
if (_red)
{
texture = TextureCache::getInstance()->addImage("rpao.png");
}
else
{
texture = TextureCache::getInstance()->addImage("bpao.png");
}
break;
case BING:
if (_red)
{
texture = TextureCache::getInstance()->addImage("rbing.png");
}
else
{
texture = TextureCache::getInstance()->addImage("bzu.png");
}
break;
case JIANG:
if (_red)
{
texture = TextureCache::getInstance()->addImage("rshuai.png");
}
else
{
texture = TextureCache::getInstance()->addImage("bjiang.png");
}
break;
case SHI:
if (_red)
{
texture = TextureCache::getInstance()->addImage("rshi.png");
}
else
{
texture = TextureCache::getInstance()->addImage("bshi.png");
}
break;
case XIANG:
if (_red)
{
texture = TextureCache::getInstance()->addImage("rxiang.png");
}
else
{
texture = TextureCache::getInstance()->addImage("bxiang.png");
}
break;
}
setTexture(texture);
setTextureRect(Rect(0, 0, texture->getContentSize().width, texture->getContentSize().height));
// 设置位置
setPosition(fromPlate());
}

代码有点长,不过很好理解。proper是一个结构体数组,结构体的成员为typerowcol,也就是象棋类型、所位于的行、所位于的列。另外,花括号中的内容则是卒、马、炮等棋子应该处在的位置(只记录了从左下角开始的四分之一的棋子位置,其他的通过对称即可生成)。_id表示棋子的编号(具体可参考第一张图上的数字),_red表示红黑两色,真为红,假为黑。

再往下看,这段代码是通过左右对称、上下对称,简练的生成了整个棋盘。当然,事情到这里还没完,我们还得根据各个棋子的类型来为之添加纹理图片,添加完毕之后才会得到完整的棋盘。