使用Qt开发的停车游戏

3 篇文章 0 订阅

一个使用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

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值