Qt小遊戲開發:貪吃蛇
阿新 • • 發佈:2019-01-11
週末沒事,手寫小遊戲繼續~
預覽
步驟
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)); }
3 貪吃蛇移動、吃果實、得分、遊戲結束邏輯 貪吃蛇的移動是個狀態機,總是朝著當前的方向前進,移動的邏輯是,不斷地消除尾部節點,並新增一個新的頭部結點,如果碰到了果實,則不消除尾部結點 每次吃到了果實後會重新隨機生成一個果實,並且保證果實不會與貪吃蛇身體重疊。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; } }
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