Qt小游戏开发:贪吃蛇

来源:互联网 发布:dsdtfixer mac 最新版 编辑:程序博客网 时间:2024/04/29 06:09

周末没事,手写小游戏继续~


预览



步骤

1 定义数据结构
逻辑与界面分离,游戏场景是个二维数组区域,贪吃蛇是若干个连续的坐标点集合,用动态链表维护,果实是一个随机坐标点。
const int BLOCK_SIZE=25; //单个方块单元的边长const int MARGIN=5; //场景边距const int AREA_ROW=15; //场景行数const int AREA_COL=15; //场景列数
    QPoint foodPoint; //果实出现坐标    QList<QPoint> snake; //贪吃蛇结构
const int TIME_INTERVAL=500; //定时器间隔时间enum Direction{    UP,    DOWN,    LEFT,    RIGHT};

2 添加界面刷新和键盘监听
首先需要定时器来控制贪吃蛇的移动以及界面的刷新,绑定定时器的信号槽
gameTimer=new QTimer(this);    connect(gameTimer,SIGNAL(timeout()),this,SLOT(SnakeUpdate()));
窗口重绘
void Widget::paintEvent(QPaintEvent *event){    QPainter painter(this);    //绘制游戏场景    painter.setBrush(Qt::yellow);    painter.setPen(Qt::blue);    painter.drawRect(MARGIN,MARGIN,AREA_COL*BLOCK_SIZE,AREA_ROW*BLOCK_SIZE);    //绘制贪吃蛇    painter.setBrush(Qt::red);    painter.setPen(Qt::green);    for(int i=0;i<snake.size();i++)        painter.drawRect(MARGIN+snake[i].x()*BLOCK_SIZE,MARGIN+snake[i].y()*BLOCK_SIZE,BLOCK_SIZE,BLOCK_SIZE);    //绘制果实    painter.setBrush(Qt::green);    painter.drawEllipse(MARGIN+foodPoint.x()*BLOCK_SIZE,MARGIN+foodPoint.y()*BLOCK_SIZE,BLOCK_SIZE,BLOCK_SIZE);    //绘制游戏分数    painter.setPen(Qt::black);    painter.setFont(QFont("Arial",14));    painter.drawText(MARGIN*3+AREA_COL*BLOCK_SIZE,MARGIN+2*BLOCK_SIZE,"score: "+QString::number(score));}
键盘监听
void Widget::keyPressEvent(QKeyEvent *event){    //贪吃蛇的方向是个状态机,注意各个方向之间切换有限制    switch(event->key())    {    case Qt::Key_Up:        if(dir!=DOWN)            dir=UP;        break;    case Qt::Key_Down:        if(dir!=UP)            dir=DOWN;        break;    case Qt::Key_Left:        if(dir!=RIGHT)            dir=LEFT;        break;    case Qt::Key_Right:        if(dir!=LEFT)            dir=RIGHT;        break;    case Qt::Key_P:        PauseResumeGame();        break;    default:        break;    }}

3 贪吃蛇移动、吃果实、得分、游戏结束逻辑
贪吃蛇的移动是个状态机,总是朝着当前的方向前进,移动的逻辑是,不断地消除尾部节点,并添加一个新的头部结点,如果碰到了果实,则不消除尾部结点
每次吃到了果实后会重新随机生成一个果实,并且保证果实不会与贪吃蛇身体重叠。
void Widget::SnakeUpdate(){    //贪吃蛇移动的策略是每次删除尾部,然后添加新的头部,维护一个动态链表    switch(dir)    {    case UP:        snake.push_front(QPoint(snake.front().x(),snake.front().y()-1));        break;    case DOWN:        snake.push_front(QPoint(snake.front().x(),snake.front().y()+1));        break;    case LEFT:        snake.push_front(QPoint(snake.front().x()-1,snake.front().y()));        break;    case RIGHT:        snake.push_front(QPoint(snake.front().x()+1,snake.front().y()));        break;    default:        break;    }    //如果吃到了果实,则尾部不删除,否则删除尾部更新头部    if(snake.contains(foodPoint))    {        score+=1; //得分        GenerateFood(); //重新生成果实    }    else        snake.pop_back();    //游戏是否结束    if(IsGameOver())    {        GameOver();        return; //赶在重绘之前跳出函数,防止贪吃蛇真的出界    }    update(); //重绘,比repaint函数效果好}
void Widget::GenerateFood(){    //随机产生位置    foodPoint.setX(rand()%AREA_COL);    foodPoint.setY(rand()%AREA_ROW);    //如果与贪吃蛇位置冲突,重新生成    if(snake.contains(foodPoint))        GenerateFood();}
当贪吃蛇头部出界或者撞到了自身则判定为游戏结束
bool Widget::IsGameOver(){    int x=snake.front().x();    int y=snake.front().y();    //出边界    if(x<0||x>AREA_COL-1||y<0||y>AREA_ROW-1)        return true;    //撞了自己    for(int i=3;i<snake.size();i++)        if(snake[i]==snake.front())            return true;    return false;}

4 游戏控制逻辑
游戏初始化
void Widget::InitGame(){    //初始化贪吃蛇,初始长度5,注意头部在前,尾部在后    for(int j=4;j>=0;j--)        snake.push_back(QPoint(j,0));    dir=RIGHT;//初始时往右走    //初始化果实    srand(time(0));    GenerateFood();    //初始化游戏分数    score=0;    //初始化暂停变量    isPause=false;    //初始化计时器    gameTimer=new QTimer(this);    connect(gameTimer,SIGNAL(timeout()),this,SLOT(SnakeUpdate()));    gameTimer->start(TIME_INTERVAL);}
游戏暂停和恢复
void Widget::PauseResumeGame(){    //暂停和恢复定时器    if(!isPause)    {        isPause=!isPause;        gameTimer->stop();    }    else    {        isPause=!isPause;        gameTimer->start(TIME_INTERVAL);    }}
游戏结束
void Widget::GameOver(){    gameTimer->stop();    QMessageBox::information(this,"failed","game over!");}

源码下载:
csdn:贪吃蛇
github: snake



0 0
原创粉丝点击