一个使用Qt C++开发的停车游戏,游戏规则如下:使用键盘方向键控制小车进入指定车位即过关,如果与树、水池、其它车辆碰撞则游戏失败。
涉及到的知识点:
- Qt GUI框架的使用以及基于Qt的简单游戏开发
- 游戏UI的设计
- C++的继承和多态、C++虚函数的使用
- Qt坐标系统变换之QMatrix的应用
- Qt绘图
- 关于不规则的物体的边缘检测
实现过程:
1.开发环境
Qt creator 4.2.1 + Qt5.8.0
2.使用PS进行UI设计
使用photoshop绘制出游戏界面,其中草地背景、车、车位、树、水池各自占一个图层,方便进行切分与导出。
3.图片切分
依次将各个对象导出为PNG图片。
4.基本框架
所有对游戏对象的操作都在QT事件中,按照事件队列顺序进行处理,没有多线程,因此不用考虑线程间同步,大大简化了游戏开发过程。
所有对象都继承自object,具有一些公共的属性。同时object定义了void tick()和void paint(QPainter &painter)接口,用于对象的移动和自身的绘制。
画面绘制频率是每秒30帧。
5.抽象基类的设计
主要对象分为车、车位、树、水池、碰撞效果。由于这些对象有一些共有特性,如位置,方向,物体边界,使用的图像等,因此设计一个抽象基类,即能减少代码量,又能统一管理。
class object
{
public:
object();
object(TYPE type, QPoint pos, DIRECTION direction);
TYPE getType() const;
void setType(const TYPE &value);
QPoint getPos() const;
void setPos(const QPoint &value);
QPixmap getPix() const;
void setPix(const QString &value);
QPolygon getPolygon() const;
void setPolygon(const QPolygon &value);
DIRECTION getDirection() const;
void setDirection(const DIRECTION &value);
virtual void tick() = 0;
virtual void paint(QPainter &painter) = 0;
protected:
TYPE type;//类型
QPoint pos;//位置
DIRECTION direction;//方向
QPolygon polygon;//边界
QPixmap pix;//画图图像
};
其中用到的宏定义
//类型
enum TYPE{
t_car,//车
t_tree,//树
t_water,//水池
t_park, //车位
t_collision//碰撞效果
};
//方向
enum DIRECTION{
d_up,//向上
d_down,//向下
d_left,//向左
d_right//向下
};
6.小车类的设计
车是可移动的的对象,有速度,有行驶状态,有不同的颜色,因此在小车的类中增加速度、颜色、行驶状态等成员。其它类没有动态效果,比较简单,这里就不再赘述。
class car : public object
{
public:
car();
car(COLOR color, QPoint pos, DIRECTION direction);
COLOR getColor() const;
void setColor(const COLOR &value);
STATE getState() const;
void setState(const STATE &value);
DIRECTION getDir() const;
void setDir(const DIRECTION &value);
int getSpeed() const;
void setSpeed(int value);
virtual void tick();
virtual void paint(QPainter &painter);
private:
COLOR color;//车的颜色
STATE state;//车的状态
int speed;//速度
};
其中用到的宏定义
//车的颜色
enum COLOR{
c_yellow,
c_red,
c_green
};
//车的状态
enum STATE{
s_stop,//停止
s_run//前进
};
7.对象创建
objs是一个对象集合,bjsQVector<object*>objs,保存所有的游戏对象,统一管理。
void Widget::init()
{
//创建小车
objs << new car(c_yellow, QPoint(90,530), d_up);
objs << new car(c_yellow, QPoint(700,91), d_left);
objs << new car(c_red, QPoint(700,192), d_left);
objs << new car(c_yellow, QPoint(700,418), d_left);
objs << new car(c_green, QPoint(700,530), d_left);
//创建树
objs << new tree(QPoint(77,74));
objs << new tree(QPoint(267,166));
objs << new tree(QPoint(491,256));
//创建车位
objs << new park(QPoint(700,91), d_left);
objs << new park(QPoint(700,192), d_left);
objs << new park(QPoint(700,296), d_left);
objs << new park(QPoint(700,418), d_left);
objs << new park(QPoint(700,530), d_left);
//创建水池
objs << new water(QPoint(331,403), d_left);
//碰撞效果
//objs << new collision(QPoint(200,200));
}
7.按键控制
覆盖QWidget的event事件,捕获键盘按键事件,实现对小车的控制。按键盘上下左右按钮,小车可向这4个方向调头并移动。
bool Widget::event(QEvent *event)
{
if(state != playing){
return QWidget::event(event);
}
if(event->type() == QEvent::KeyPress){//有按钮按下
QKeyEvent *e = static_cast<QKeyEvent *>(event);
if(!e->isAutoRepeat()){
car *c = static_cast<car *>(objs[0]);
switch(e->key()){
case Qt::Key_Up://向上移动
c->setState(s_run);
c->setDir(d_up);
break;
case Qt::Key_Down://向下移动
c->setState(s_run);
c->setDir(d_down);
break;
case Qt::Key_Left://向左移动
c->setState(s_run);
c->setDir(d_left);
break;
case Qt::Key_Right://向右移动
c->setState(s_run);
c->setDir(d_right);
break;
case Qt::Key_Escape://ESC关闭游戏
close();
break;
}
}
}
else if(event->type() == QEvent::KeyRelease){//按钮抬起
QKeyEvent *e = static_cast<QKeyEvent *>(event);
if(!e->isAutoRepeat()){
car *c = static_cast<car *>(objs[0]);
c->setState(s_stop);//停止移动
}
}
return QWidget::event(event);
}
8.绘制动画帧
每个对象实现paint接口来绘制自己。其中使用QMatrix类实现视图变换,实现旋转,移动等操作。
void car::paint(QPainter &painter)
{
QMatrix m;
m.translate(pos.x(), pos.y());//移动对象到其显示位置
switch (direction) {//旋转对象,如车头有4个方向
case d_up:
m.rotate(90);
break;
case d_down:
m.rotate(-90);
break;
case d_left:
break;
case d_right:
m.rotate(180);
break;
}
painter.setMatrix(m);//应用变换
painter.drawPixmap(polygon.boundingRect(), pix);//画图
}
先画草地背景,再依次绘制各游戏对象。
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
//画背景
painter.resetMatrix();
painter.drawPixmap(0,0, background);
//画各个对象
for(object *o :objs){
o->paint(painter);
}
QWidget::paintEvent(event);
}
9.碰撞检测
object类中的QPolygon成员是一个多边形,代表对象的边缘,QPolygon由一系列坐标组成。以图片左上角为原点,依次确定对象边缘点。
小技巧:可使用PS中的信息窗口结合鼠标确定大概的边缘点。
将所有的点连接起来就是物体的轮廓了。点越多越精确。绿色线条代表的是我提取的轮廓。有了边缘数据就可以进行碰撞检测了。
对象轮廓的初始化
确定好每个对象的轮廓后,在对象构造函数中初始化。注意,要规范化,将对象的中心移动到坐标原点(0,0)处,以便于使用QMatrix进行视图变换。(109,46)是图片的长和宽,除2就是图片的中心坐标。
car::car(COLOR _color, QPoint _pos, DIRECTION _dir)
: object(t_car, _pos, _dir)
{
//.........省略1000字.......
QVector<QPoint> points;
points << QPoint(2, 33);
points << QPoint(2, 17);
points << QPoint(12, 6);
points << QPoint(90, 6);
points << QPoint(107, 16);
points << QPoint(108, 35);
points << QPoint(95, 43);
points << QPoint(28, 43);
points << QPoint(11, 42);
for(QPoint &pt :points){//规范化:移动对象中心至坐标原点,很重要!
pt -= QPoint(109/2, 46/2);
}
polygon << points;
}
检测函数
r1,r2代表两个对象的边缘。返回相交,不相交,包含三个信息。
小车与水池相交则失败,小车与车位包含则通关。
int Widget::intersected(const QPolygon &r1, const QPolygon &r2)
{
//统计两个多边形相交的点数
int count = 0;
for(auto pt :r1){
if(r2.containsPoint(pt,Qt::OddEvenFill))
count++;
}
if(count == 0)//不相交
return 0;
else if(count < r1.size())//部分相交
return 1;
else//包含
return 2;
}
定时器刷新与碰撞检测
当小车与其它物体碰撞,显示碰撞火花效果,提示游戏失败,当小车停进车位,游戏通关。
void Widget::timerEvent(QTimerEvent *event)
{
//碰撞检测
if(state == playing){
for(object *o :objs){
o->tick();
}
car *car_obj = static_cast<car *>(objs[0]);
for(auto iter = objs.begin() + 1; iter != objs.end(); iter++){
object *obj = *iter;
QPolygon car_pol = car_obj->getPolygon();
QPolygon other_pol = obj->getPolygon();
if(obj->getType() != t_park){
int ret = intersected(car_pol, other_pol);
if(ret == 0){
}
else if(ret == 1){//小车与其它物体碰撞,游戏失败
QPoint pt = getCollPoint(car_obj->getDirection(), car_pol.boundingRect());
objs << new collision(pt);
car_obj->setState(s_stop);
state = pause;
killTimer(timer_id);
emit finish(0);
break;
}
}
else {
int ret = intersected(car_pol, other_pol);
if(ret == 2){//小车停进车位,游戏通关
car_obj->setState(s_stop);
state = pause;
killTimer(timer_id);
emit finish(1);
break;
}
}
}
}
//刷新界面(画图)
update();
}
10.源代码下载
完整代码请下载源码查看:
百度网盘: 百度网盘 请输入提取码 ,抽取码:bbkp
CSDN:car-game.tar.gz_qt游戏方向-互联网文档类资源-CSDN下载
11.技术交流
QQ:390919875