<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                #(32):貪吃蛇游戲(2) 下面我們繼續上一章的內容。在上一章中,我們已經完成了地圖的設計,當然是相當簡單的。在我們的游戲中,另外的主角便是蛇和食物。下面我們便開始這部分的開發。 我們的地圖是建立在`QGraphicsScene`的基礎之上的,所以,里面的對象應該是`QGraphicsItem`實例。通常,我們會把所有的圖形元素(這里便是游戲中需要的對象,例如蛇、食物等)設計為`QGraphicsItem`的子類,在這個類中添加繪制自身的代碼以及動畫邏輯。這也是面向對象的開發方式:封裝自己的屬性和操作。在我們的游戲中,應該有三個對象:蛇 Snake、食物 Food 以及墻 Wall。 我們從食物開始。因為它是最簡單的。我們將其作為一個紅色的小圓餅,大小要比地圖中的一個方格要小,因此我們可以將其放置在一個方格中。正如上面分析的那樣,我們的`Food`類需要繼承`QGraphicsItem`。按照接口約束,`QGraphicsItem`的子類需要重寫至少兩個函數:`boundingRect()`和`paint()`。 `boundingRect()`返回一個用于包裹住圖形元素的矩形,也就是這個圖形元素的范圍。需要注意的是,這個矩形必須能夠**完全包含**圖形元素。所謂“完全包含”,意思是,在圖形元素有動畫的時候,這個矩形也必須將整個圖形元素包含進去。如果范圍矩形過小。圖形會被剪切;如果范圍矩形過大,就會影響性能。 `paint()`的作用是使用`QPainter`將圖形元素繪制出來。 下面是 food.h 和 food.cpp 的內容: ~~~ ////////// food.h ////////// #ifndef FOOD_H #define FOOD_H #include <QGraphicsItem> class Food : public QGraphicsItem { public: Food(qreal x, qreal y); QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *); QPainterPath shape() const; }; #endif // FOOD_H ////////// food.cpp ////////// #include <QPainter> #include "constants.h" #include "food.h" static const qreal FOOD_RADIUS = 3; Food::Food(qreal x, qreal y) { setPos(x, y); setData(GD_Type, GO_Food); } QRectF Food::boundingRect() const { return QRectF(-TILE_SIZE, -TILE_SIZE, TILE_SIZE * 2, TILE_SIZE * 2 ); } void Food::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { painter->save(); painter->setRenderHint(QPainter::Antialiasing); painter->fillPath(shape(), Qt::red); painter->restore(); } QPainterPath Food::shape() const { QPainterPath p; p.addEllipse(QPointF(TILE_SIZE / 2, TILE_SIZE / 2), FOOD_RADIUS, FOOD_RADIUS); return p; } ~~~ 雖然這段代碼很簡單,我們還是有必要解釋一下。構造函數接受兩個參數:x 和 y,用于指定該元素的坐標。`setData()`函數是我們之后要用到的,這里簡單提一句,它的作用為該圖形元素添加額外的數據信息,類似于散列一樣的鍵值對的形式。`boundingRect()`簡單地返回一個`QRect`對象。由于我們的元素就是一個圓形,所以我們返回的是一個簡單的矩形。注意,這個矩形的范圍實際是四倍于實際區域的:以元素坐標 (x, y) 為中心,邊長為`TILE_SIZE * 2`的正方形。我們還重寫了`shape()`函數。這也是一個虛函數,但是并不是必須覆蓋的。這個函數返回的是元素實際的路徑。所謂路徑,可以理解成元素的矢量輪廓線,就是`QPainterPath`所表示的。我們使用`addEllipse()`函數,添加了一個圓心為 (TILE_SIZE / 2, TILE_SIZE / 2),半徑 FOOD_RADIUS 的圓,其范圍是左上角為 (x, y) 的矩形。由于設置了`shape()`函數,`paint()`反而更簡單。我們所要做的,就是把`shape()`函數定義的路徑繪制出來。注意,我們使用了`QPainter::save()`和`QPainter::restore()`兩個函數,用于保存畫筆狀態。 現在我們有了第一個圖形元素,那么,就讓我們把它添加到場景中吧!對于一個游戲,通常需要有一個中心控制的類,用于控制所有游戲相關的行為。我們將其取名為`GameController`。 `GameController`的工作是,初始化場景中的游戲對象,開始游戲循環。每一個游戲都需要有一個游戲循環,類型于事件循環。想象一個每秒滴答 30 次的表。每次響起滴答聲,游戲對象才有機會執行相應的動作:移動、檢查碰撞、攻擊或者其它一些游戲相關的活動。為方便起見,我們將這一次滴答成為一幀,那么,每秒 30 次滴答,就是每秒 30 幀。游戲循環通常使用定時器實現,因為應用程序不僅僅是一個游戲循環,還需要響應其它事件,比如游戲者的鼠標鍵盤操作。正因為如此,我們不能簡單地使用無限的 for 循環作為游戲循環。 在 Graphics View Framework 中,每一幀都應該調用一個稱為`advance()`的函數。`QGraphicsScene::advance()`會調用場景中每一個元素自己的`advance()`函數。所以,如果圖形元素需要做什么事,必須重寫`QGraphicsItem`的`advance()`,然后在游戲循環中調用這個函數。 `GameController`創建并開始游戲循環。當然,我們也可以加入`pause()`和`resume()`函數。現在,我們來看看它的實現: ~~~ GameController::GameController(QGraphicsScene *scene, QObject *parent) : QObject(parent), scene(scene), snake(new Snake(this)) { timer.start(1000/33); Food *a1 = new Food(0, -50); scene->addItem(a1); scene->addItem(snake); scene->installEventFilter(this); resume(); } ~~~ `GameController`的構造函數。首先開啟充當游戲循環的定時器,定時間隔是 1000 / 33 毫秒,也就是每秒 30(1000 / 33 = 30)幀。`GameController`有兩個成員變量:scene 和 snake,我們將第一個食物和蛇都加入到場景中。同時,我們為`GameController`添加了事件過濾器,以便監聽鍵盤事件。這里我們先不管這個事件過濾器,直接看看后面的代碼: ~~~ void GameController::pause() { disconnect(&timer, SIGNAL(timeout()), scene, SLOT(advance())); } void GameController::resume() { connect(&timer, SIGNAL(timeout()), scene, SLOT(advance())); } ~~~ `pause()`和`resume()`函數很簡答:我們只是連接或者斷開定時器的信號。當我們把這一切都準備好之后,我們把`GameController`添加到`MainWindow`中: ~~~ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), game(new GameController(scene, this)) { ... } ~~~ 由于`GameController`在構造時已經開始游戲循環,因此我們不需要另外調用一個所謂的“start”函數。這樣,我們就把第一個食物添加到了游戲場景: [![](https://box.kancloud.cn/2015-12-29_56823256a476a.png)](http://files.devbean.net/images/2012/12/snake_1.png) 接下來是有關蛇的處理。 蛇要更復雜一些。在我們的游戲中,蛇是由黃色的小方塊組成,這是最簡單的實現方式了。第一個是蛇的頭部,緊接著是它的身體。對此,我們有兩個必須面對的困難: 1. 蛇具有復雜得多的形狀。因為蛇的形狀隨著游戲者的控制而不同,因此,我們必須找出一個能夠恰好包含蛇頭和所有身體塊的矩形。這也是 boundingRect() 函數所要解決的問題。 2. 蛇會長大(比如吃了食物之后)。因此,我們需要在蛇對象中增加一個用于代表蛇身體長度的`growing`變量:當`growing`為正數時,蛇的身體增加一格;當`growing`為負數時,蛇的身體減少一格。 3. `advance()`函數用于編碼移動部分,這個函數會在一秒內調用 30 次(這是我們在`GameController`的定時器中決定的)。 我們首先從`boundingRect()`開始看起: ~~~ QRectF Snake::boundingRect() const { qreal minX = head.x(); qreal minY = head.y(); qreal maxX = head.x(); qreal maxY = head.y(); foreach (QPointF p, tail) { maxX = p.x() > maxX ? p.x() : maxX; maxY = p.y() > maxY ? p.y() : maxY; minX = p.x() < minX ? p.x() : minX; minY = p.y() < minY ? p.y() : minY; } QPointF tl = mapFromScene(QPointF(minX, minY)); QPointF br = mapFromScene(QPointF(maxX, maxY)); QRectF bound = QRectF(tl.x(), // x tl.y(), // y br.x() - tl.x() + SNAKE_SIZE, // width br.y() - tl.y() + SNAKE_SIZE //height ); return bound; } ~~~ 這個函數的算法是:遍歷蛇身體的每一個方塊,找出所有部分的最大的 x 坐標和 y 坐標,以及最小的 x 坐標和 y 坐標。這樣,夾在其中的便是蛇身體的外圍區域。 `shape()`函數決定了蛇身體的形狀,我們遍歷蛇身體的每一個方塊向路徑中添加: ~~~ QPainterPath Snake::shape() const { QPainterPath path; path.setFillRule(Qt::WindingFill); path.addRect(QRectF(0, 0, SNAKE_SIZE, SNAKE_SIZE)); foreach (QPointF p, tail) { QPointF itemp = mapFromScene(p); path.addRect(QRectF(itemp.x(), itemp.y(), SNAKE_SIZE, SNAKE_SIZE)); } return path; } ~~~ 在我們實現了`shape()`函數的基礎之上,`paint()`函數就很簡單了: ~~~ void Snake::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { painter->save(); painter->fillPath(shape(), Qt::yellow); painter->restore(); } ~~~ 現在我們已經把蛇“畫”出來。下一章中,我們將讓它“動”起來,從而完成我們的貪吃蛇游戲。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看