因为时间紧张也要准备四级和期末考试,所以原本的很多设想都没有实现,就简单实现了基本玩法,有很多的地方抠图都没有抠好,也有地方为了省事就直接输出了,给图为例,这篇博客写下主要为了记录不足。

虽然有很多地方不是很好,但也是我一行一行敲出来的,hhh。
主要类
map(地图)
整个地图被我用一个backmap[7][13]分割好了,每一块都是一块60*60的格子,backmap中的每个不同的值代表不同的图片。这里我用一个txt文件存储,想修改地图的时候就很方便,当然最初设置成这样是为了我想让玩家自己可以设置地图,和怪物的波数(当然最后没实现)。
class map
{
public:
void upload(radish &tem);//读取
void show(radish& tem);
private:
IMAGE background,lujin,wo1,wo2;
const int breadth = 60;
};
upload就是从txt读取文件,并且读取图片数据,其实就是构造函数了(我也不记得我当时干嘛这样搞)。
show 就是展示地图。
breadth这个值应该就是每个格子的宽度,虽然不记得我当时这样搞的目的。
radish
class radish//萝卜
{
public :
friend class map;//好像没什么用
radish(int a, int b) :x(a), y(b);
radish() ;
void injured();//受伤
void show();//展示
private:
int x, y,blood;//x和y坐标,blood血量
IMAGE tupian1,tupian2;
};
现在回过头去看发现这个完全写在map里没必要另外写一个类
injured 这个函数没有参数,是因为我只实现了一种怪物,没有搞其他怪物,没有想到要实现类似BOSS那种怪物。
monst
class monst//基类
{
public:
monst(int c) ;
void attack(radish& a);
void showblood();
void injured(int a);
void move();
int showx();
int showy();
bool if_death();//是否死亡
void huoqu(int &a, int &b);//获取这个怪物的坐标
virtual void showimage() = 0;//虚函数
int x, y;//坐标
private :
const double mmax = 100;
double blood;//血量
int v;//速度
bool st[7][13];//用来记录那些经过
int fx, fy;//用来位移
IMAGE tupian;
bool is_death;
int tt;//开始时间
int stt;//位移时间
};
在这里的move的函数实现了怪物根据backmap来走路,就是说怪物会一直走路,不会横穿,当然其实我的实现方法不是很好,会有bug,(当然是根据时间进行位移的)。
void monst::move()
{
stt = clock();
if (stt - tt < 100)return;
int dx[4] = { 0,1,-1,0 }, dy[4] = { 1,0,0,-1 };
//cout << x << y << "kkk" << endl;
//if (st[0][0])cout << "llllllll";
//cout << zx << zy << endl;
if (x % 60 == 0 && y % 60 == 0)
{
st[y / 60][x / 60] = false;
if (y / 60 == zx && x / 60 == zy) is_death = true, bradish.injured();
for (int i = 0; i < 4; i++)
{
int a = x / 60 + dy[i], b = y/60 + dx[i];
if (a>=0&&b>=0&&a<7&&b<13&&st[b][a])fx = a - x / 60, fy = b - y / 60;
//cout << a << b << "ll" << endl;
}
//cout << a << b <<"ll"<< endl;
}
x += fx, y += fy;
tt = clock();
}
很明显我就类似于走迷宫的方法去实现,这种就会出现bug
我想了一下应该可以给路径的方块进行编号类似1,2,3,4.每次只走向大一的格子
monst1
class monst1 :public monst
{
public :
void showimage();
monst1(int c) :monst(c) { t = 0; };//实现了多态
private:
int t;//时间
};
这个是由monst继承而来,monst定义了每个怪物的共有属性,而且在这个monst1的构造函数中定义了每个怪物的不同属性,类似于速度,这样就可以用monst的指针来实现多态。
weapon
class weapon//塔
{
public:
weapon();
void gr(gametime *l);
bool fandeath();
bool is_xiang(int t,int a, int b)
bool jianche(monst *tem);
virtual void showimage() = 0;
private:
bool is_death;
int gongjijuli;
protected:
int x, y;
int deng;//等级
int t;//产生时间
monst* gongji;//要攻击对象的地址
int harm[3];
int groop[2];
};
这个是塔的基类。
butter1
class bullet1 :public weapon//塔类型1
{
public:
bullet1(int a, int b,gametime*m) :weapon();
void showimage();
~bullet1();
private:
IMAGE tt1[3],tt2[3];
IMAGE guangshu1, guangshu2;
IMAGE tu1, tu2;
int worth;
double jiaodu;//用来旋转
};
gametime
class gametime
{
public:
friend bullet1;
friend weapon;
gametime();
//void settime();//设置时间波数
void generate(int lei, int size);
void mianban(ExMessage);
void check();//检测怪物是否死亡,并且展示怪物
void showtime();
void showmianban(int x, int y);
void showwwmian();
void showcaichan();//展示有多少钱
void tisheng();//提升等级
void jiancheshifou();
void showhuopao();
private:
int t;//时间;
vector<monst*>qun;//怪物的地址
vector <weapon*> ta;//塔的地址
vector<PII> ba;
vector<int> aa;
bool zhankai;//是否展开
int tzhankai;//展开时间
int zkx, zky;
IMAGE cha1, cha2;
IMAGE tusheng1, tusheng2;
IMAGE tu1, tu2;
int caichan;
};
这个是这个课设最重要的类,基本所有的函数都在这个类里调用。
主要难点
怪物走路
前面已经说过了。
维护金币数
杀死一个怪物需要增长金币,建立一个塔需要消耗金币,塔要升级也要消耗金币,可以将monst和weapon设为友元类,将gametime当参数(只有一个game time)
鼠标操作
塔的建立和升级都要用到鼠标。,那么点击一个位置就要知道是升级的界面,还是建立的界面,并且还要设置一段时间后页面自动关闭。
void gametime::mianban(ExMessage a)
{
int ddy = a.x / 60, ddx = (a.y - 100) / 60;
if (backmap[ddx][ddy] != -1 && backmap[ddx][ddy] != 1 && backmap[ddx][ddy] != 2)this->showmianban(a.x, a.y);
}
void gametime::showmianban(int x, int y)
{
if (zhankai)
{
int ddy = x / 60, ddx = (y - 100) / 60;
int sxy = zkx / 60, sxx = (zky - 100) / 60;
if (ddy == sxy + 1 && sxx == ddx&&backmap[sxx][sxy]!=0)
{
zhankai = false;
tzhankai = clock();
}
else if (sxy == ddy && sxx ==ddx)
{
if (caichan >= 160)
{
weapon* tem = new bullet1(zkx / 60 * 60, (zky - 100) / 60 * 60+100,this);
ta.push_back(tem);
//system("pause");
}
zhankai = false;
}
else if (ddy == sxy && sxx == ddx + 1 && backmap[sxx][sxy] != 0)
{
//cout << "jjjjjjjjj" << endl;
tisheng();
zhankai = false;
}
else
{
tzhankai = clock();
zhankai = true;
zkx = x, zky = y;
}
}
else
{
tzhankai = clock();
zhankai = true;
zkx = x, zky = y;
}
}
void gametime::showwwmian()
{
if (zhankai)
{
auto c = backmap[(zky - 100) / 60][zkx / 60];
//cout << c << "c=多少" << endl;
switch (c)
{
case 11:transparentimage2(zkx / 60 * 60, (zky - 100) / 60 * 60 - 60+100, &tusheng1, &tusheng2);
transparentimage2(zkx / 60 * 60+60, (zky - 100) / 60 * 60+100, &cha1, &cha2); break;
case 0: if (this->caichan >= 160)transparentimage(zkx / 60 * 60, (zky - 100) / 60 * 60+100, 60, 60, 70, 0, &tu1, &tu2);
else transparentimage(zkx / 60 * 60, (zky - 100) / 60 * 60+100, 60, 60, 130, 0, &tu1, &tu2); break;
}
int temt = clock();
if (temt - tzhankai >= 3000)zhankai = false;
}
//transparentimage( 200,100, 60, 60, 130, 0, &tu1, &tu2);
}
图片旋转
因为怪物会位移,塔的位置不同,所以火炮的方向必须要会旋转。
void bullet1::showimage()
{
IMAGE ttem1,ttem2;
if (jiaodu >= 6.28)jiaodu = 0;
transparentimage(x+10, y+10, 60, 50, 345, 20, &tu1, &tu2);
rotateimage(&ttem1, &tt1[deng], jiaodu, WHITE);
rotateimage(&ttem2, &tt2[deng], jiaodu, BLACK);
transparentimage2(x , y , &ttem2, &ttem1);
int tem1, tem2;
if (gongji == NULL)
{
if (jiaodu == 0)return;
if (jiaodu > 6.283 - jiaodu)
{
jiaodu += min(0.001, 6.283 - jiaodu);
return;
}
else
{
jiaodu -= min(0.001, jiaodu - 0);
return;
}
return;
}
gongji->huoqu(tem1, tem2);
tem2 += 100;
double len = sqrt((y - tem2 - 5) * 1.0 * (y - tem2 - 5) * 1.0 + (tem1 + 35 - x) * 1.0 * (tem1 + 35 - x));
double ss = acos((tem1 + 35 - x) * 1.0 / len);
//cout << tem2 << " " << tem1 << " " << y << " " << x << endl;
if (abs(ss - jiaodu) < 0.002)
{
IMAGE hh,hh2;
rotateimage(&hh, &guangshu1, jiaodu, WHITE);
rotateimage(&hh2, &guangshu2, jiaodu, BLACK);
double zzx= (tem1 + 95 + x)*1.0/2, zzy = (tem2 + 65 + y) * 1.0 / 2;
double minx = min(tem1 + 65, x + 30), miny = min(tem2+35, y + 30);
transparentimage(minx,miny,2*zzx-2*minx,2*zzy-2*miny,400-zzx+minx,400-zzy+miny,&hh2,&hh);
jiaodu = ss;
gongji->injured(harm[deng]);
if (gongji->if_death())gongji = NULL;
}
else
{
if (ss > jiaodu )
{
jiaodu += 0.001;
}
else
{
jiaodu -= 0.001;
}
};
}
这里的细节就有很多,需要慢慢调。
主要流程
准备界面
void initialize()
{
IMAGE img1,h1,h2;
//initgraph(1280, 720, EW_SHOWCONSOLE);
//setfillcolor(GREEN);
loadimage(&img1,_T("backgrondtupian.jpeg"));
loadimage(&h1, _T("开始游戏.png"));
loadimage(&h2, _T("开始游戏2.png"));
initgraph(780, 520, EW_SHOWCONSOLE);
putimage(0, 0, &img1);
setfillcolor(GREEN);
MymciSendString(_T("open background.mp3 alias BackMusic"), NULL);
MymciSendString("play BackMusic repeat", NULL);
transparentimage2(-50, 200, &h1, &h2);
ExMessage m;
while (1)
{
//fillroundrect(200, 550, 400, 650, 30, 30);
if (peekmessage(&m, EM_MOUSE) && m.lbutton)
{
if (m.x > 30 && m.x < 399 && m.y>362 && m.y < 446)
{
game();
return;
}
}
}
closegraph();
}
播放音乐
播放音乐用的mci可以实现很多操作,但我只用了循环播放音乐,更多细节可以看这篇博客。
void MymciSendString(const char * szCommand , char* szbuffer)//const string szCommand)
{
if (NULL == szbuffer)
{
if (0 != mciSendString(szCommand, NULL, 0, NULL))
{
printf("%s false!\n", szCommand);
}
else
{
printf("%s success!\n", szCommand);
}
}
else
{
if (0 != mciSendString(szCommand, szbuffer, 1024, NULL))
{
printf("%s false!\n", szCommand);
}
else
{
printf("%s success!\n", szCommand);
}
}
}
游戏过程
void game()
{
ExMessage m;
//initialize();
//game();
//initgraph(860, 550, EW_SHOWCONSOLE);
gametime l;
map a;
a.upload(bradish);
initgraph(860, 550, EW_SHOWCONSOLE);
//weapon* tem = new bullet1(240, 220, &l);
while (jieshu)
{
BeginBatchDraw();
a.show(bradish);
thread zh(showwwwwtime, &l, &a);
l.jiancheshifou();
if (peekmessage(&m, EM_MOUSE) && m.lbutton)
{
cout << m.x << m.y << endl;
l.mianban(m);
}
zh.join();
l.showwwmian();
EndBatchDraw();
cleardevice();
}
}
这里用了线程,没啥用虽然,就像试一下。
代优化的部分
- mci的只用来了播放音乐没有实现更多操作。
- 很多的代码都需要优化,很多地方都很蠢。
- 怪物的移动需要被优化,虽然想到方法。
- 玩家自己实现地图和怪物数量。