这次我主要负责写英雄类,伤害类和主函数。
之前用过用过游戏引擎写过几个小游戏,所以一开始大概有点思路。
主函数
ALL_ini();//初始化游戏
while (1)//死循环 { GetLocalTime(&now);//开始执行代码的时间 //游戏此处执行 ALL_draw();//绘制窗口 ALL_play();//游戏运行 GetLocalTime(&last); //执行完代码的时间 sleep_time= find_time(last, now); Sleep(max(49 - sleep_time,0));//休息一段时间 GetLocalTime(&last); //执行完代码的时间
if ( win()) { break; } }
主函数,游戏进行时的死循环代码。几段和时间有关的代码是用来控制帧率,也就是游戏1s中执行多少次的。每次运行完一次游戏,计算一下运行需要多久,然后通过Sleep函数控制休息时间,已控制每次循环开始的时间相差是固定的。
最后一个是判断双方基地炸了没的代码。
图片的读取和显示
这个大概是这次任务碰到的最大的两个问题之一,主要是因为不知道去哪里找相关的代码,然后大部分的例子都有用到窗口类之类的东西,所以看半天也看不懂是啥意思。
最开始找到的显示图片的是用IMAGE这个类,但是PNG图片不能用,只能显示jpg的图片。因为受不了jpg的白底,所以后面又找到了CImage这个类。然后我就被CImage里hdc这个参数卡了很久很久。后面突然想通
hwnd = initgraph(640, 480);//创建窗口 hdc = GetDC(hwnd);//获得窗口hdc bac.Draw(hdc, 0, 0);
先获得hwnd,然后获得hdc,然后再在窗口上画就可以了。虽然到现在还是没搞懂这些代码是啥意思。但是大致知道怎么用了。
闪屏问题的解决
这个问题其实到游戏几乎制作完成以后我才去解决,因为之前绘制的图片比较少,并没有出现闪屏问题,但是虽然绘制的图片增加,闪屏越来越严重。
然后去百度,但是都是说什么双缓冲,一些完全听不懂的名词,完全没看懂。不过无意间发现CImage这个类有个一个函数GetDC(),然后猜,既然这个图片也有DC,那肯定也可以在这个图片上绘制其他图片。
所以我就先获得一张空白图片的DC,然后把其他所有图片都覆盖上去,在把这张绘制了全部图片的图片绘制到窗口上,这样每次游戏运行就只绘制了一张图片,闪屏的问题就解决了。PS:不过所以不透明度低于255的图片都会变得很奇怪,所以我就把几乎所有
图片透明度都调到了255。不过技能CD中的动画就变得很难看了,要啥自行车。
游戏运行的核心
所有的物体,都有这样一个函数。按照游戏引擎的叫法,这个叫同步事件。
void step( list<tower> &Solid, list<Army> &Army_Slime, list<hurt> &Magic,queue<long long > &dead, hero &player_red,hero &player_blue,long long &object_ID)
每个执行一个上面那个死循环么,所有物体都同步时间都会得到一次执行。
其中的几个参数分别是,防御塔的链表(包括基地和泉水),小兵的列表,所有伤害(子弹,英雄的技能等等)的列表,死亡物体的ID队列。和两个英雄以及所以物体的ID。游戏运行时,就是把三个列表里的物体和两个英雄的同步时间执行一下(本来英雄也想写成列表的,就俩个觉得没必要就没写,但是导致代码量增加了一点)。object_ID也是这个游戏中一个重要的变量,每次创建一个物体,都会获得当前的object_ID的值,然后再object_ID++,以确保任何物体的ID不会重复,object_ID的其中一个作用就是清除物体。而清除物体还需要上面的第三个参数dead队列。在物体的运行过程中,出现需要死亡的情况(比如小兵没血了)又不好立即把自己移除出自己所在的列表时,会把自己的ID加入到dead这个队列。在所有物体的同步时间执行结束以后,会清除ID存在于dead队列的物体。当一个物体要
检测碰撞也需要用到那几个列表,比如当小兵判断是否会受到伤害时,需要判断伤害这个列表中所有敌方子弹是否会碰撞到自己。
游戏运行的处的代码;
void ALL_play()//游戏在此运行 //已经写完 { for (list<hurt>::iterator i = Magic.begin(); i != Magic.end(); ++i)//特效同步 i->step(Solid, Army_Slime, Magic, dead, player_red, player_blue, object_ID); player_red.step(Solid, Army_Slime, Magic, dead, player_red, player_blue, object_ID); player_blue.step(Solid, Army_Slime, Magic, dead, player_red, player_blue, object_ID); for (list<tower>::iterator i = Solid.begin(); i != Solid.end(); ++i)//防御塔同步 i->step(Solid, Army_Slime, Magic, dead, player_red, player_blue,object_ID); for (list<Army>::iterator i = Army_Slime.begin(); i != Army_Slime.end(); ++i)//小兵同步 i->step(Solid, Army_Slime, Magic, dead, player_red, player_blue, object_ID); while (dead.empty() == false)//清除已死的东东。 { long long temp = dead.front(); dead.pop(); for (list<tower>::iterator i = Solid.begin(); i != Solid.end(); ) { if (i->ID == temp) i = Solid.erase(i); else ++i; }//删除防御塔 for (list<Army>::iterator i = Army_Slime.begin(); i != Army_Slime.end(); )//防御塔同步 { if (i->ID == temp) i = Army_Slime.erase(i); else ++i; }//删除小兵 for (list<hurt>::iterator i = Magic.begin(); i != Magic.end(); )//防御塔同步 { if (i->ID == temp) i = Magic.erase(i); else ++i; }//删除特效 } count_time++;//计算运行次数 这句话卵用没有,我也不知道写这个干啥 }
绘制函数,在每帧结束重绘整个窗口,比较简单不多废话
void ALL_draw(/*绘制函数*/) { hdc2 = bac.GetDC(); bac_ini.Draw(hdc2, 0, 0); Draw_num(1280, 64, player_red.LV, hdc2); Draw_num(1280+128, 64, player_red.EXE, hdc2); Draw_num(1280 + 128*2, 64, player_red.weak_time, hdc2); Draw_num(96, 672, player_blue.LV, hdc2); Draw_num(96+128, 672, player_blue.EXE, hdc2); Draw_num(96 + 128*2, 672, player_blue.weak_time, hdc2); wall.Draw(hdc2, 0, 192);//城墙 wall.Draw(hdc2, 0, 544);//城墙 背景层次最低 for (list<tower>::iterator i = Solid.begin(); i != Solid.end(); ++i) { if ( i->who==0 ) i->draw_myself(hdc2, temp_pic, strip_hp); //此处绘制绘制泉水 } player_red.draw_myself(hdc2, temp_pic, strip_hp);//绘制英雄 player_blue.draw_myself(hdc2, temp_pic,strip_hp);// for (list<Army>::iterator i = Army_Slime.begin(); i != Army_Slime.end(); ++i) i->draw_myself(hdc2, temp_pic, strip_hp); //此处小兵 for (list<tower>::iterator i = Solid.begin(); i != Solid.end(); ++i) { if (i->who != 0) i->draw_myself(hdc2, temp_pic, strip_hp); //此处绘制绘制水晶和防御塔 } for (list<hurt>::iterator i = Magic.begin(); i != Magic.end(); ++i) i->draw_myself(hdc2, temp_pic, strip_hp); //此处绘制伤害
Draw_num(120, 32, player_red.hp, hdc2); Draw_num(120, 96, player_red.mp, hdc2); Draw_num(1600, 640, player_blue.hp, hdc2); Draw_num(1600, 640+64, player_blue.mp, hdc2); bac.Draw(hdc, 0, 0); }
英雄类
写这个类的时候也吃了个没知识的亏,试过各种控制英雄的方法,后面用getch成功了,一开始单单控制一个英雄的时候什么问题都没有,但是当我引入其他物体的时候就发现,getch会和getchar一样,运行到这一处时会停止,直到某个按键按下。
·后面找了很久终于某篇博客中找到了这么一句话
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)
和前面找的东西一样,我看不懂这是什么意思。不过知道了加了这句以后,就相当于多了一个KEY_DOWN(x)的函数,x是一个字符,作用是判断当前情况下x字符对应的按键是否按下。英雄的控制问题就迎刃而解了。
英雄类的同步事件。一开始写的时候觉得可能很复杂,不过把技能当成一个类分出去就简单很多了
同步时间里,除了每个类都有的碰撞检测外,英雄类还有一些升级和控制的碰到。四方向移动的控制很简单,只需要判断要前往的位置有没有固体就可以了,而技能的释放在技能作为一个类分离以后也简单了很多。直接用一个for语句就写完了。
void hero::step(list<tower> &Solid, list<Army> &Army_Slime, list<hurt> &Magic, queue<long long > &dead, hero &player_red, hero &player_blue,long long &object_ID) {//同步事件需要传入所有链表 move = 0; max_hp = ini_max_hp + 600 * (LV - 1); max_mp = ini_max_mp + 300 * (LV - 1);//最大MP增长 //升级事件 if ( EXE>=20 ) LV = 2; if (EXE >= 50) LV = 3; if (EXE >= 90) LV = 4; if (EXE >= 140) LV = 5; //死亡事件 被催眠 //碰到水晶持续回血 碰到敌方攻击收到伤害 for (list<tower>::iterator i = Solid.begin(); i != Solid.end(); ++i)//防御塔 { if ( i->who==0 && i->color==color )//是我方泉水 if ( place_meet(x + wide, y + hight, i->x + i->wide, i->y + i->hight, R + i->R)) { hp += max_hp / 100; mp += max_mp / 100; } } if (wudi == 0)//无敌效果 { for (list<hurt>::iterator i = Magic.begin(); i != Magic.end(); ++i)//伤害 { if ( i->who==1 )//追踪形技能切目标不是我 if ( i->aim_ID!=ID ) continue; if (color != i->color)//敌人的法术 if (place_meet(x + wide, y + hight, i->x + i->wide, i->y + i->hight, R + i->R))//被攻击到 { hp -= i->atk; if (i->who == 3)//百万吨拳击附带伤害 hp -= (max_hp - hp)/100; if (i->who == 2 && angry==0)//催眠粉 狂暴时无法被催眠 { sleep = 1; weak_time = 20; } } } } else { wudi_time--; if (wudi_time<=0) { wudi = 0; } } if (fast == 1) { fast_time--; if (fast_time <= 0) fast = 0; speed = ini_speed*2; } else speed = ini_speed ; if (angry == 1)//狂暴 { atk = ini_atk[LV] * 2; angry_time--; if ( angry_time <=0 ) angry = 0; } else atk = ini_atk[LV] ; if (hp <= 0)//死亡后回到泉水 { sleep = 1; hp =10; weak_time = 200;//苏醒时间 if (color == red) { x = 64; y = 360; } else { x = 1632; y = 360; } } if (sleep == 0/*被催眠*/)//被催眠或者死亡 { //操作已经写完。。 if (KEY_DOWN(key_left)) { if (place_meet_Solid(x - speed, y, Solid, Army_Slime, Magic, dead, player_red, player_blue)) x -= speed; fx = 180; sprite_index = spr_left; move = 1; } if (KEY_DOWN(key_up)) { if (place_meet_Solid(x, y - speed, Solid, Army_Slime, Magic, dead, player_red, player_blue)) y -= speed; fx = 90; sprite_index = spr_up; move = 1; } if (KEY_DOWN(key_down)) { if (place_meet_Solid(x, y + speed, Solid, Army_Slime, Magic, dead, player_red, player_blue)) y += speed; fx = 270; sprite_index = spr_down; move = 1; } if (KEY_DOWN(key_right)) { if (place_meet_Solid(x + speed, y, Solid, Army_Slime, Magic, dead, player_red, player_blue)) x += speed; fx = 0; sprite_index = spr_right; move = 1; } for (int i = 1; i <= 5; i++) if (KEY_DOWN(key_SK[i]) && LV >= i) { if (SK[i].can_use_skill(SK_CDSUM[i], mp))//释放技能 { SK[i].skill_use(this, Magic, object_ID); mp -= SK[i].cost; SK_CDSUM[i] = 0; } } } else { weak_time--;//苏醒 if (weak_time<=0) sleep = 0; } if (my_time % 2 == 0) if (move) image_index++;//动画 for (int i = 1; i <= 5; i++) { if ( SK_CDSUM[i]<SK[i].CD ) SK_CDSUM[i]++;//积累CD值 } if (hp > max_hp) hp = max_hp; if (mp>max_mp) mp = max_mp; if (my_time % 2 == 0) if (move) image_index++;//动画 image_index %= 3; my_time++; }
伤害类
原来我以为这个类会特别难写,因为设计了不是少技能,但是当我把这些技能一一按情况分开,再把伤害的行为步骤分类以后,也就发现只是多了一两个判断而已。
注:部分技能描述与实际不符,请以实物为准
其实构造函数没有必要有两个但是,我懒得改之前的代码,所以就将就了下。同步事件代码我就不贴了,无非也就是判断碰撞,按移动方式移动,以及一些其他的改变(比如大字爆和阳光烈焰的精灵会逐渐变大,水炮会反弹)
其中移动方式三种,跟踪移动,小兵防御塔的攻击和水箭龟的泡泡攻击,这个移动方式每回合需要先通过目标的ID找到目标的位置。然后计算出x和y坐标分别移动多少。
指定方向移动:这个移动方式的最多也最简单,向指定方向走几步就可以了。
跟随使用者:或者使用者的坐标,再通过计算使自己和使用者相对坐标保持一定就可以了。
消失方式有四种,其中跟踪的子弹要碰撞到指定目标后或目标已经死亡才会消失,其余的就是碰撞敌人或者一段时间或者两者都可以者三种方式消失。写起来也比较简单不多废话。
这次作业是第二次打这么长的代码,还是没什么经验,并且刚开始写的时候,还没学继承多态,也没有第一次写长代码时候说改几百行就改几百行的精力和毅力,所以新学东西都没有用上,导致这次代码写得非常长。