小游戏个人

  这次我主要负责写英雄类,伤害类和主函数。

  之前用过用过游戏引擎写过几个小游戏,所以一开始大概有点思路。

主函数

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坐标分别移动多少。

  指定方向移动:这个移动方式的最多也最简单,向指定方向走几步就可以了。

  跟随使用者:或者使用者的坐标,再通过计算使自己和使用者相对坐标保持一定就可以了。

  消失方式有四种,其中跟踪的子弹要碰撞到指定目标后或目标已经死亡才会消失,其余的就是碰撞敌人或者一段时间或者两者都可以者三种方式消失。写起来也比较简单不多废话。

  这次作业是第二次打这么长的代码,还是没什么经验,并且刚开始写的时候,还没学继承多态,也没有第一次写长代码时候说改几百行就改几百行的精力和毅力,所以新学东西都没有用上,导致这次代码写得非常长。

 

  

原文地址:https://www.cnblogs.com/Ike-shadow/p/9240248.html