这次我们来学习创建3架飞机,通过面对对象的思想,逐层抽象。
然后放到Final文件夹下,为了便于管理,我们在Final文件夹中创建一个名为plane的文件夹,然后再把图片资源放进去。并且重命名,可以自己随便起名字,我的名字是为了好记忆,根据名字就知道是哪款战机。
我们层层抽象,我们的三个飞机对应最下面的三个类,这里不是对象。
然后我们开始写代码:
首先,在template.h文件的app类外(或者你可以写在 #define COMPANY_NAME “My Company” 的下方)声明我们三个图片的编号。
(由于其他地方会用到,所以声明为全局变量)
extern int IMG_f11;
extern int IMG_tm;
extern int IMG_redStar;
既然是全局变量,我们必须在template.cpp中再次说明一下(可以不赋初值)。
int IMG_f11;
int IMG_tm;
int IMG_redStar;
然后,我们加载图像资源。(由于我们放到了plane文件夹,所以这里路径与之前背景图略有不同。同样放在Begin函数中。)
IMG_f11 = agk::LoadImage("plane/F-11.png");
IMG_redStar = agk::LoadImage("plane/redStar.png");
IMG_tm = agk::LoadImage("plane/TM.png");
我们根据我们上面的分析,依次添加类。
在弹出框中,输入类名为Object,然后勾选虚拟析构函数,这是为了我们之后多态使用
物体的图像用图像编号即可存储,所以有_imageID。(我的个人习惯,以下划线开头代表是变量,大写字母开头代表是函数)
物体还有一个唯一编号_spriteID,这个可以正好用上精灵的编号(创建精灵时的返回值)。
物体的函数有功能,这个功能是为多态准备的,而且我们不可能直接创建一个物体类,所以是一个纯虚函数。
对于构造函数,传个参数用于保存图像编号。
class Object
{
public:
Object(int _imageID);
virtual ~Object();
virtual void Func()=0;
protected:
int _imageID;
int _spriteID;
};
构造函数用来创建一个精灵,使用我们的图像编号,并保存精灵的编号。
析构函数用来删除这个精灵。
#include "Object.h"
#include"template.h"
Object::Object(int _imageID)
{
this->_imageID = _imageID;
_spriteID=agk::CreateSprite(_imageID);
}
Object::~Object()
{
agk::DeleteSprite(_spriteID);
}
创建方法同上,类名是Actor,基类是Object,任然是虚拟析构函数
根据角色类的信息,我们加入了以下变量。
为什么血量和速度不使用int型呢?
这是为了避免做乘法时,乘小数得到的结果不准确。
构造函数用于初始化全部变量。
根据函数移动,增加了Move函数,同样也是纯虚的。
#include "Object.h"
class Actor :
public Object
{
public:
Actor(int team,float _hp,float _speed,float _x,float _y,int _imageID);
virtual ~Actor();
virtual void Move() = 0;
protected:
int team;
float _hp;
float _speed;
float _x;
float _y;
};
构造函数给自己的变量赋值,并且给其父类Object赋_imageID的值,最后设置精灵的位置。
析构函数使用Object的析构函数。
#include "Actor.h"
#include"template.h"
Actor::Actor(int team, float _hp, float _speed, float _x, float _y,int _imageID):Object(_imageID)
{
this->team = team;
this->_hp = _hp;
this->_speed = _speed;
this->_x = _x;
this->_y = _y;
agk::SetSpritePosition(_spriteID, _x, _y);
}
Actor::~Actor()
{
Object::~Object();
}
类名是Plane,基类是Actor。这次不用虚拟析构了,原因我们之后说。
可以看到构造函数的参数越来越多了,不过没关系,之后就省事了。
由于我们这篇文章只讲飞机,没有子弹,所以子弹类型就先省略,相应的攻击方法也不能使用(但可以先声明一个空方法Attack)。
我们将Move函数重写,这样就可以移动我们的飞机。
由于此类已不是虚基类,所以必须实现Func方法。
攻击速度我要说明一下,代表每秒钟发射的子弹数。
既然是子弹数,那为什么不是整数呢?
那是因为如果2秒发一颗子弹,整数能表示1.5吗?
#include "Actor.h"
class Plane :
public Actor
{
public:
Plane(float _fireSpeed,int team, float _hp, float _speed, float _x, float _y, int _imageID);
~Plane();
void Attack();
void Move();
void Func();
protected:
float _fireSpeed;
//子弹类型
};
构造方法依然只是赋值,无其他功能。
Attack函数我们之后有了子弹类再实现。
Move函数用来移动飞机,GetDirectionX在电脑上用来获取X方向的值,按下键盘右键时返回1,键盘左键返回-1,左右都不按返回0。
GetDirectionY在电脑上用来获取Y方向的值,按下键盘上键时返回-1,键盘下键返回1,上下都不按返回0。
在Func函数中调用Move方法。
#include "Plane.h"
#include"template.h"
Plane::Plane(float _fireSpeed, int team, float _hp, float _speed, float _x, float _y, int _imageID):Actor(team,_hp,_speed,_x,_y,_imageID)
{
this->_fireSpeed = _fireSpeed;
}
Plane::~Plane()
{
Actor::~Actor();
}
void Plane::Attack()
{
//根据射速发射子弹
}
void Plane::Move()
{
_x = agk::GetSpriteX(_spriteID) + agk::GetDirectionX()*_speed;
_y = agk::GetSpriteY(_spriteID) + agk::GetDirectionY()*_speed;
agk::SetSpritePosition(_spriteID, _x, _y);
}
void Plane::Func()
{
Move();
}
后面这三个类大同小异,我就不一一截图了。
构造函数的参数只有此飞机的出生坐标。
#include "Plane.h"
class TM :
public Plane
{
public:
TM(float _x,float _y);
~TM();
};
构造函数给子类赋具体的值,不再是变量。SetSpriteSize用于设置精灵大小,由于我的图片资源像素较大,所以使用这个函数调整大小,宽为60像素,高度自适应(-1时等比例缩放)。
#include "TM.h"
#include"template.h"
TM::TM(float _x, float _y):Plane(1,1,10,2,_x,_y,IMG_tm)
{
agk::SetSpriteSize(_spriteID, 60, -1);
}
TM::~TM()
{
}
#include "Plane.h"
class F11 :
public Plane
{
public:
F11(float _x, float _y);
~F11();
};
#include "F11.h"
#include"template.h"
F11::F11(float _x, float _y) :Plane(2, 1, 10, 2, _x, _y, IMG_f11)
{
agk::SetSpriteSize(_spriteID, 60, -1);
}
F11::~F11()
{
}
#include "Plane.h"
class RedStar :
public Plane
{
public:
RedStar(float _x, float _y);
~RedStar();
};
#include "RedStar.h"
#include"template.h"
RedStar::RedStar(float _x, float _y) :Plane(1, 1, 10, 2, _x, _y, IMG_redStar)
{
agk::SetSpriteSize(_spriteID, 60, -1);
}
RedStar::~RedStar()
{
}
所有的类都写好了,我们开始使用它们。
首先必须包含三种飞机的头文件。
在Begin函数之前声明三个指针。
在Begin函数中,必须在加载图像之后,创建三个对象并给指针赋值。
在Loop函数中,调用它们的Func函数。(之后我会教大家使用多态,更方便)
在End函数中,删除指针(必须)。
// Includes
#include "template.h"
#include"TM.h"
#include"F11.h"
#include"RedStar.h"
// Namespace
using namespace AGK;
app App;
int IMG_f11;
int IMG_tm;
int IMG_redStar;
TM *tm;
F11 *f11;
RedStar *rs;
void app::Begin(void)
{
agk::SetVirtualResolution(DEVICE_WIDTH, DEVICE_HEIGHT);
agk::SetClearColor(255, 0, 255); // light blue
agk::SetSyncRate(30, 0);
agk::SetScissor(0, 0, 0, 0);
agk::SetWindowTitle("Space");
agk::UseNewDefaultFonts(1);
IMG_bg1=agk::LoadImage("bg1.jpg");
IMG_f11 = agk::LoadImage("plane/F-11.png");
IMG_redStar = agk::LoadImage("plane/redStar.png");
IMG_tm = agk::LoadImage("plane/TM.png");
SPR_bg1=agk::CreateSprite(IMG_bg1);
agk::SetSpriteColor(SPR_bg1,252, 157, 154, 255);
tm = new TM(100, 300);
f11 = new F11(250, 300);
rs = new RedStar(400, 300);
}
int app::Loop(void)
{
tm->Func();
f11->Func();
rs->Func();
agk::Print(agk::ScreenFPS());
agk::Print("Thank you for reading!");
agk::Sync();
return 0; // return 1 to close app
}
void app::End(void)
{
delete tm;
delete f11;
delete rs;
agk::Message("感谢使用");
}
由于担心有些朋友不清楚template.h的内容,我写在下面。
#ifndef _H_AGK_TEMPLATE_
#define _H_AGK_TEMPLATE_
// Link to GDK libraries
#include "AGK.h"
#define DEVICE_WIDTH 640
#define DEVICE_HEIGHT 480
#define DEVICE_POS_X 32
#define DEVICE_POS_Y 32
#define FULLSCREEN false
// used to make a more unique folder for the write path
#define COMPANY_NAME "My Company"
extern int IMG_f11;
extern int IMG_tm;
extern int IMG_redStar;
class app
{
public:
// constructor
app() { memset(this, 0, sizeof(app)); }
// main app functions - mike to experiment with a derived class for this..
void Begin(void);
int Loop(void);
void End(void);
private:
int IMG_bg1;
int SPR_bg1;
};
extern app App;
#endif
// Allow us to use the LoadImage function name
#ifdef LoadImage
#undef LoadImage
#endif
下次再见,拜拜!