[11] لعبة تحطيم الطوب



في هذا الجزء من سلسلة دروس Qt4 c++ سوف نقوم بصنع لعبة سهلة .
لعبة تحطيم الطوبة هي لعبة أركيد مطورة من قبل شركة أتاري المحدودة وكان ذلك عام 1976 .
في هذه اللعبة سوف يقوم اللاعب بتحريك قاذف الكرة على الشاشة ليقوم بعكس إتجاه الكرة حيث أن هدفه هو تحطيم الطوبة في أعلى الشاشة .
التطوير:-
في لعبتنا هناك قاذف(عاكس) الكرة و كرة و 30 طوبة ولدينا لكل منها صورة في المجلد inkscape . وأستخدمنا حدث المؤقت لإنشاء دورة اللعبة . سوف لن نتعامل مع الزوايا بكل بساة سوف نغير الإتجاهات فقط أعلى أو أسفل او يمين ويسار .وقد ألهمت الفكرة من لعبة pybreakout التي طورة بواسطة مكتبة PyGame بواسطة ناثان دوسن.
شيفرة اللعبة سهلة الفهم حيث أنها لاتحتوي على إضافات أثناء اللعب أو مستويات أو نتائج لذلك هي سهلة الفهم .
paddle.h

  1. #ifndef PADDLE_H
  2. #define PADDLE_H
  3.  
  4. #include <QImage>
  5. #include <QRect>
  6.  
  7. class Paddle
  8. {
  9.  
  10. public:
  11. Paddle();
  12. ~Paddle();
  13.  
  14. public:
  15. void resetState();
  16. void moveLeft(int);
  17. void moveRight(int);
  18. QRect getRect();
  19. QImage & getImage();
  20.  
  21. private:
  22. QImage image;
  23. QRect rect;
  24.  
  25. };
  26.  
  27. #endif

ملف الرأس هذا لكائن القاذف(العاكس).
paddle.cpp

  1. #include 'paddle.h'
  2.  
  3.  
  4. Paddle::Paddle()
  5. {
  6. image.load('paddle.png');
  7.  
  8. rect = image.rect();
  9. resetState();
  10. }
  11.  
  12. Paddle::~Paddle()
  13. {
  14. printf('paddle deletedn');
  15. }
  16.  
  17. void Paddle::moveLeft(int left)
  18. {
  19. if (rect.left() >= 2)
  20. rect.moveTo(left, rect.top());
  21. }
  22.  
  23. void Paddle::moveRight(int right)
  24. {
  25. if (rect.right() <= 298)
  26. rect.moveTo(right, rect.top());
  27. }
  28.  
  29. void Paddle::resetState()
  30. {
  31. rect.moveTo(200, 360);
  32. }
  33.  
  34. QRect Paddle::getRect()
  35. {
  36. return rect;
  37. }
  38.  
  39. QImage & Paddle::getImage()
  40. {
  41. return image;
  42. }
يمكن تحريك القاذف(العاكس) يمنى أو يسرى.
  1. Paddle::Paddle()
  2. {
  3. image.load('paddle.png');
  4.  
  5. rect = image.rect();
  6. resetState();
  7. }

في دالة البناء قمنا بتحميل صورة القاذف(العاكس) وحصلنا على مستطيل الصورة وقمنا بنقلها الى موقع المنتصف.
  1. void Paddle::moveLeft(int left)
  2. {
  3. if (rect.left() >= 2)
  4. rect.moveTo(left, rect.top());
  5. }

هذه الدالة لتحريك المستطيل الى جهة اليسار .
brick.h

  1. #ifndef BRICK_H
  2. #define BRICK_H
  3.  
  4. #include <QImage>
  5. #include <QRect>
  6.  
  7. class Brick
  8. {
  9.  
  10. public:
  11. Brick(int, int);
  12. ~Brick();
  13.  
  14. public:
  15. void resetState();
  16. bool isDestroyed();
  17. void setDestroyed(bool);
  18. QRect getRect();
  19. void setRect(QRect);
  20. QImage & getImage();
  21.  
  22. private:
  23. QImage image;
  24. QRect rect;
  25. int position;
  26. bool destroyed;
  27.  
  28. };
  29.  
  30. #endif

ملف الرأس هذا لكائن الطوبة .
brick.cpp

  1. #include 'brick.h'
  2.  
  3.  
  4. Brick::Brick(int x, int y)
  5. {
  6. image.load('brickie.png');
  7. destroyed = FALSE;
  8. rect = image.rect();
  9. rect.translate(x, y);
  10. }
  11.  
  12. Brick::~Brick() {
  13.  
  14. printf('Brick deletedn');
  15. }
  16.  
  17. QRect Brick::getRect()
  18. {
  19. return rect;
  20. }
  21.  
  22. void Brick::setRect(QRect rct)
  23. {
  24. rect = rct;
  25. }
  26.  
  27. QImage & Brick::getImage()
  28. {
  29. return image;
  30. }
  31.  
  32. bool Brick::isDestroyed()
  33. {
  34. return destroyed;
  35. }
  36.  
  37. void Brick::setDestroyed(bool destr)
  38. {
  39. destroyed = destr;
  40. }
  41. bool Brick::isDestroyed()
  42. {
  43. return destroyed;
  44. }

لدى كائن الطوبة متغير destroyed فإذا كان المتغير يساوي true فهذا يعني أن الطوبة لن يتم رسمها على الشاشة .
ball.h

  1. #ifndef BALL_H
  2. #define BALL_H
  3.  
  4. #include <QImage>
  5. #include <QRect>
  6.  
  7. class Ball
  8. {
  9.  
  10. public:
  11. Ball();
  12. ~Ball();
  13.  
  14. public:
  15. void resetState();
  16. void moveBottom(int);
  17. void moveTop(int);
  18. void moveLeft(int);
  19. void moveRight(int);
  20. void autoMove();
  21. void setXDir(int);
  22. void setYDir(int);
  23. int getXDir();
  24. int getYDir();
  25. QRect getRect();
  26. QImage & getImage();
  27.  
  28. private:
  29. int angle;
  30. int speed;
  31. int xdir;
  32. int ydir;
  33. bool stuck;
  34. QImage image;
  35. QRect rect;
  36.  
  37. };
  38.  
  39. #endif.

ملف الرأس هذا لكائن الكرة .
ball.cpp

  1. #include 'ball.h'
  2.  
  3.  
  4. Ball::Ball()
  5. {
  6.  
  7. xdir = 1;
  8. ydir = -1;
  9.  
  10. image.load('ball.png');
  11.  
  12. rect = image.rect();
  13. resetState();
  14.  
  15. }
  16.  
  17. Ball::~Ball() {
  18. printf('Ball deletedn');
  19. }
  20.  
  21.  
  22. void Ball::autoMove()
  23. {
  24. rect.translate(xdir, ydir);
  25.  
  26. if (rect.left() == 0) {
  27. xdir = 1;
  28. }
  29.  
  30. if (rect.right() == 300) {
  31. xdir = -1;
  32. }
  33.  
  34. if (rect.top() == 0) {
  35. ydir = 1;
  36. }
  37. }
  38.  
  39. void Ball::resetState()
  40. {
  41. rect.moveTo(230, 355);
  42. }
  43.  
  44. void Ball::moveBottom(int bottom)
  45. {
  46. rect.moveBottom(bottom);
  47. }
  48.  
  49. void Ball::moveTop(int top)
  50. {
  51. rect.moveTop(top);
  52. }
  53.  
  54. void Ball::moveLeft(int left)
  55. {
  56. rect.moveLeft(left);
  57. }
  58.  
  59. void Ball::moveRight(int right)
  60. {
  61. rect.moveRight(right);
  62. }
  63.  
  64. void Ball::setXDir(int x)
  65. {
  66. xdir = x;
  67. }
  68.  
  69. void Ball::setYDir(int y)
  70. {
  71. ydir = y;
  72. }
  73.  
  74. int Ball::getXDir()
  75. {
  76. return xdir;
  77. }
  78.  
  79. int Ball::getYDir()
  80. {
  81. return ydir;
  82. }
  83.  
  84. QRect Ball::getRect()
  85. {
  86. return rect;
  87. }
  88.  
  89. QImage & Ball::getImage()
  90. {
  91. return image;
  92. }

الدالة autoMove() تستدعى في كل دورة في اللعبة لتحريك الكرة على الشاشة اذا حدث إنصدام سوف يتغير إتجاه الكرة .
breakout.h

  1. #ifndef BREAKOUT_H
  2. #define BREAKOUT_H
  3.  
  4. #include 'ball.h'
  5. #include 'brick.h'
  6. #include 'paddle.h'
  7. #include <QWidget>
  8. #include <QKeyEvent>
  9.  
  10. class Breakout : public QWidget
  11. {
  12. Q_OBJECT
  13.  
  14. public:
  15. Breakout(QWidget *parent = 0);
  16. ~Breakout();
  17.  
  18. protected:
  19. void paintEvent(QPaintEvent *event);
  20. void timerEvent(QTimerEvent *event);
  21. void keyPressEvent(QKeyEvent *event);
  22.  
  23. void startGame();
  24. void pauseGame();
  25. void stopGame();
  26. void victory();
  27. void checkCollision();
  28.  
  29. private:
  30. int x;
  31. int timerId;
  32. Ball *ball;
  33. Paddle *paddle;
  34. Brick * bricks[30];
  35. bool gameOver;
  36. bool gameWon;
  37. bool gameStarted;
  38. bool paused;
  39.  
  40. };
  41.  
  42. #endif

ملف الرأس هذا لكائن اللعبة تحطيم الطوبة .
  1. int x;
  2. int timerId;

المتغير x يخزن موقع القاذف(العاكس) على محور س. أما المتغير timerId فهو يستخدم ل تعريف كائن المؤقت وهي ضرورية أثناء وضع اللعبة في حالة إيقاف.
breakout.cpp

  1. #include 'breakout.h'
  2. #include <QPainter>
  3. #include <QApplication>
  4.  
  5. Breakout::Breakout(QWidget *parent)
  6. : QWidget(parent)
  7. {
  8.  
  9. x = 0;
  10. gameOver = FALSE;
  11. gameWon = FALSE;
  12. paused = FALSE;
  13. gameStarted = FALSE;
  14. ball = new Ball();
  15. paddle = new Paddle();
  16.  
  17.  
  18. int k = 0;
  19. for (int i=0; i<5; i++) {
  20. for (int j=0; j<6; j++) {
  21. bricks[k] = new Brick(j*40+30, i*10+50);
  22. k++;
  23. }
  24. }
  25. }
  26.  
  27. Breakout::~Breakout() {
  28. delete ball;
  29. delete paddle;
  30. for (int i=0; i<30; i++) {
  31. delete bricks[i];
  32. }
  33. }
  34.  
  35. void Breakout::paintEvent(QPaintEvent *event)
  36. {
  37. QPainter painter(this);
  38.  
  39. if (gameOver) {
  40. QFont font('Courier', 15, QFont::DemiBold);
  41. QFontMetrics fm(font);
  42. int textWidth = fm.width('Game Over');
  43.  
  44. painter.setFont(font);
  45. int h = height();
  46. int w = width();
  47.  
  48. painter.translate(QPoint(w/2, h/2));
  49. painter.drawText(-textWidth/2, 0, 'Game Over');
  50. }
  51. else if(gameWon) {
  52. QFont font('Courier', 15, QFont::DemiBold);
  53. QFontMetrics fm(font);
  54. int textWidth = fm.width('Victory');
  55.  
  56. painter.setFont(font);
  57. int h = height();
  58. int w = width();
  59.  
  60. painter.translate(QPoint(w/2, h/2));
  61. painter.drawText(-textWidth/2, 0, 'Victory');
  62. }
  63. else {
  64. painter.drawImage(ball->getRect(),
  65. ball->getImage());
  66. painter.drawImage(paddle->getRect(),
  67. paddle->getImage());
  68.  
  69. for (int i=0; i<30; i++) {
  70. if (!bricks[i]->isDestroyed())
  71. painter.drawImage(bricks[i]->getRect(),
  72. bricks[i]->getImage());
  73. }
  74. }
  75. }
  76.  
  77. void Breakout::timerEvent(QTimerEvent *event)
  78. {
  79. ball->autoMove();
  80. checkCollision();
  81. repaint();
  82. }
  83.  
  84.  
  85.  
  86. void Breakout::keyPressEvent(QKeyEvent *event)
  87. {
  88. switch (event->key()) {
  89. case Qt::Key_Left:
  90. {
  91. int x = paddle->getRect().x();
  92. for (int i=1; i<=5; i++)
  93. paddle->moveLeft(x--);
  94. break;
  95. }
  96. case Qt::Key_Right:
  97. {
  98. int x = paddle->getRect().x();
  99. for (int i=1; i<=5; i++)
  100. paddle->moveRight(x++);
  101. }
  102. break;
  103. case Qt::Key_P:
  104. {
  105. pauseGame();
  106. }
  107. break;
  108. case Qt::Key_Space:
  109. {
  110. startGame();
  111. }
  112. break;
  113. case Qt::Key_Escape:
  114. {
  115. qApp->exit();
  116. }
  117. break;
  118. default:
  119. QWidget::keyPressEvent(event);
  120. }
  121. }
  122.  
  123. void Breakout::startGame()
  124. {
  125. if (!gameStarted) {
  126. ball->resetState();
  127. paddle->resetState();
  128.  
  129. for (int i=0; i<30; i++) {
  130. bricks[i]->setDestroyed(FALSE);
  131. }
  132. gameOver = FALSE;
  133. gameWon = FALSE;
  134. gameStarted = TRUE;
  135. timerId = startTimer(10);
  136. }
  137. }
  138.  
  139. void Breakout::pauseGame()
  140. {
  141. if (paused) {
  142. timerId = startTimer(10);
  143. paused = FALSE;
  144. } else {
  145. paused = TRUE;
  146. killTimer(timerId);
  147. }
  148. }
  149.  
  150. void Breakout::stopGame()
  151. {
  152. killTimer(timerId);
  153. gameOver = TRUE;
  154. gameStarted = FALSE;
  155. }
  156.  
  157. void Breakout::victory()
  158. {
  159. killTimer(timerId);
  160. gameWon = TRUE;
  161. gameStarted = FALSE;
  162. }
  163.  
  164. void Breakout::checkCollision()
  165. {
  166.  
  167. if (ball->getRect().bottom() > 400)
  168. stopGame();
  169.  
  170. for (int i=0, j=0; i<30; i++) {
  171. if (bricks[i]->isDestroyed()) {
  172. j++;
  173. }
  174. if (j==30)
  175. victory();
  176. }
  177.  
  178. if ((ball->getRect()).intersects(paddle->getRect())) {
  179.  
  180. int paddleLPos = paddle->getRect().left();
  181. int ballLPos = ball->getRect().left();
  182.  
  183. int first = paddleLPos + 8;
  184. int second = paddleLPos + 16;
  185. int third = paddleLPos + 24;
  186. int fourth = paddleLPos + 32;
  187.  
  188. if (ballLPos < first) {
  189. ball->setXDir(-1);
  190. ball->setYDir(-1);
  191. }
  192.  
  193. if (ballLPos >= first && ballLPos < second) {
  194. ball->setXDir(-1);
  195. ball->setYDir(-1*ball->getYDir());
  196. }
  197.  
  198. if (ballLPos >= second && ballLPos < third) {
  199. ball->setXDir(0);
  200. ball->setYDir(-1);
  201. }
  202.  
  203. if (ballLPos >= third && ballLPos < fourth) {
  204. ball->setXDir(1);
  205. ball->setYDir(-1*ball->getYDir());
  206. }
  207.  
  208. if (ballLPos > fourth) {
  209. ball->setXDir(1);
  210. ball->setYDir(-1);
  211. }
  212.  
  213.  
  214. }
  215.  
  216.  
  217. for (int i=0; i<30; i++) {
  218. if ((ball->getRect()).intersects(bricks[i]->getRect())) {
  219.  
  220. int ballLeft = ball->getRect().left();
  221. int ballHeight = ball->getRect().height();
  222. int ballWidth = ball->getRect().width();
  223. int ballTop = ball->getRect().top();
  224.  
  225. QPoint pointRight(ballLeft + ballWidth + 1, ballTop);
  226. QPoint pointLeft(ballLeft - 1, ballTop);
  227. QPoint pointTop(ballLeft, ballTop -1);
  228. QPoint pointBottom(ballLeft, ballTop + ballHeight + 1);
  229.  
  230. if (!bricks[i]->isDestroyed()) {
  231. if(bricks[i]->getRect().contains(pointRight)) {
  232. ball->setXDir(-1);
  233. }
  234.  
  235. else if(bricks[i]->getRect().contains(pointLeft)) {
  236. ball->setXDir(1);
  237. }
  238.  
  239. if(bricks[i]->getRect().contains(pointTop)) {
  240. ball->setYDir(1);
  241. }
  242.  
  243. else if(bricks[i]->getRect().contains(pointBottom)) {
  244. ball->setYDir(-1);
  245. }
  246.  
  247. bricks[i]->setDestroyed(TRUE);
  248. }
  249. }
  250. }
  251.  
  252. }

هنا مطق اللعبة.
  1. int k = 0;
  2. for (int i=0; i<5; i++) {
  3. for (int j=0; j<6; j++) {
  4. bricks[k] = new Brick(j*40+30, i*10+50);
  5. k++;
  6. }
  7. }

دالة بناء كائن لعبة تحطيم الطوبة ,قمنا بإنشاء 30 طوبة .
  1. painter.drawImage(ball->getRect(),
  2. ball->getImage());
  3. painter.drawImage(paddle->getRect(),
  4. paddle->getImage());
  5.  
  6. for (int i=0; i<30; i++) {
  7. if (!bricks[i]->isDestroyed())
  8. painter.drawImage(bricks[i]->getRect(),
  9. bricks[i]->getImage());
  10. }

اذا لم يحدث فوز أو خسارة يتم رسم الكرة وقاذف(عاكس) الكرة و مجموعة الطوب في paintEvent() .
  1. void Breakout::timerEvent(QTimerEvent *event)
  2. {
  3. ball->autoMove();
  4. checkCollision();
  5. repaint();
  6. }

في timerEvent() يتم تحريك الكرة والتأكد ما إذا أصطدمت الكرة مع القاذف(العاكس) او الطوبة ثم يتم إعادة رسم النافذة .
  1. case Qt::Key_Left:
  2. {
  3. int x = paddle->getRect().x();
  4. for (int i=1; i<=5; i++)
  5. paddle->moveLeft(x--);
  6. break;
  7. }

اذا تم الضغط على المفتاح left يتم إستدعاء الدالة moveLeft() من كائن القاذف(العاكس) يتم إستدعاء الدالة لخمسة مرات وبذلك الحركة تكون أكثرة واقعية (نعومة) .
  1. void Breakout::stopGame()
  2. {
  3. killTimer(timerId);
  4. gameOver = TRUE;
  5. gameStarted = FALSE;
  6. }

دالة stopGame() يتم حذف حدث المؤقت ويتم تغيير قيم المتغيرات للشكل المناسب.
  1. if (ball->getRect().bottom() > 400)
  2. stopGame();

اذا ما أصطدمت الكرة بالقاع يتم إيقاف اللعبة (إنهائها).
  1. for (int i=0, j=0; i<30; i++) {
  2. if (bricks[i]->isDestroyed()) {
  3. j++;
  4. }
  5. if (j==30)
  6. victory();
  7. }

يتم التأكد من عدد الطوب الذي تم تحطيمه اذا كان ماتم تحطيمه 30 طوبة فهذا دليل على الفوز .
i
  1. f (ballLPos < first) {
  2. ball->setXDir(-1);
  3. ball->setYDir(-1);
  4. }

اذا صدمت الكرة في الجزء الأعلى من القاذف(العاكس) يتم تغيير الإتجاه الى الشمال الشرقي.
  1. if(bricks[i]->getRect().contains(pointTop)) {
  2. ball->setYDir(1);
  3. }


اذا أصطدمت الكرة بقعر الطوبة يتم تغيير إتجاه الكرة لتتحرك نحو الأسفل.


الشكل: لعبة تحطيم الطوب .



تمت طباعة الدرس من موقع مكتبة الدروس
http://qt-ar.org/lessons/show41.html