Учим Python, делая крутые игры 2018. Invent your owncomputer gameswith python
Скачать 6.56 Mb.
|
События и игровой цикл Все наши текстовые игры осуществляли полный вывод, пока не достигли бы вызова функции input(). Тогда программа остановилась бы и ждала, когда пользователь что-то наберет и нажмет клавишу Enter. Но pygame-программы Создание графики 337 постоянно находятся в игровом цикле , выполняя каждую строку кода в этом цикле около 100 раз в секунду. Игровой цикл постоянно проверяет наличие новых событий, обновляет состояние окна и отображает окно на экране. События генерируются pygame каждый раз, когда пользователь нажимает клавишу на клавиатуре, кнопку мыши, перемещает курсор или выполняет иные распознаваемые программой действия, которые должны повлиять на что-то в игре. Event — объект типа данных pygame.event.Event. Строка 59 — начало игрового цикла. 58. # Запуск игрового цикла. 59. while True: Условие для инструкции while установлено истинным, чтобы этот цикл выполнялся бесконечно. Цикл прервется исключительно в том случае, если событие приведет к завершению работы программы. Получение объектов Event Функция pygame.event.get() проверяет любые новые объекты pygame.event. Event (сокращенно объекты Event ), созданные с момента последнего вызова pygame.event.get() . Эти события возвращаются как список объектов Event, кото- рые программа затем выполнит, чтобы произвести некоторые действия в ответ на событие. Все объекты Event имеют атрибут с именем type, который сообщает нам тип события. В этой главе нам нужно использовать только тип события QUIT , который сигнализирует о выходе пользователя из программы. 60. for event in pygame.event.get(): 61. if event.type == QUIT: В строке 60 мы используем цикл for для итерации по каждому объекту Event в списке, возвращаемом pygame.event.get(). Если атрибут type события равен константе QUIT, содержащейся в модуле pygame.locals, который мы им- портировали в начале программы, тогда понятно, что было сгенерировано событие QUIT. Модуль pygame генерирует событие QUIT, когда пользователь закрывает окно программы или когда компьютер выключается и пытается завершить все запущенные программы. Далее мы сообщим программе что делать, когда она обнаруживает событие QUIT. Выход из программы Если событие QUIT было сгенерировано, программа последовательно вы- зовет функции pygame.quit() и sys.exit(). 62. pygame.quit() 63. sys.exit() Функция pygame.quit() является, по сути, противоположностью init(). Вы должны вызвать ее перед выходом из программы. Если забудете, процесс IDLE может зависнуть после завершения работы вашей программы . Ин- струкции в строках 62 и 63 завершают работу pygame и программы. Заключение В этой главе мы рассмотрели множество новых тем, которые позволят нам сделать гораздо более занимательные игры. Взамен обычной работы с текстом и вызовов функций print() и input(), pygame-программа выглядит как окно (созданное методом pygame.display.set_mode()), в котором мы можем отображать разные объекты. Функции рисования модуля pygame позволяют отображать в этом окне фигуры разного цвета. Кроме того, вы можете соз- давать текст различного размера. Такие рисунки могут находиться в любых позициях внутри окна, в отличие от текста, создаваемого функцией print(). pygame -программы могут быть намного интереснее текстовых игр. Теперь давайте узнаем, как создавать игры с анимированной графикой. Анимированная графика 339 18 АНИМИРОВАННАЯ ГРАФИКА Теперь, когда у нас есть некоторые на- выки работы с модулем pygame, мы на- пишем программу c анимацией блоков, перемещающихся в окне. У этих блоков разные цвета и размеры, а перемещаются они только по диагонали. Для создания ани- мации мы будем на несколько пикселей перемещать блоки при каждом переборе игрового цикла. Тогда возникнет эффект, будто блоки двигаются по экрану. В ЭТОЙ ГЛАВЕ РАССМАТРИВАЮТСЯ СЛЕДУЮЩИЕ ТЕМЫ: • Анимация объектов в игровом цикле • Изменение направления движения объекта Пример запуска игры программы Когда вы запустите программу , ее окно будет выглядеть примерно так, как показано на рис. 18.1. Блоки будут отскакивать от границ окна. Исходный код программы В редакторе файлов создайте новый файл, вы- брав команду меню File ⇒ New File (Файл ⇒ Но- вый файл). В открывшемся окне введите приве- денный ниже исходный код и сохраните файл под именем animation.py. Затем нажмите клавишу F5 Make sure you’re using Python 3, not Python 2! УБЕ ДИТЕСЬ, ЧТО ИСПО ЛЬЗУЕТЕ PY THON 3, А НЕ PY THON 2! 340 Глава 18 и запустите программу. Если при выполнении программы возникают ошиб- ки, сравните код, который вы набрали, с оригинальным кодом с помощью онлайн-инструмента на сайте inventwithpython.com/diff/. Рис. 18.1. Снимок программы с анимированными блоками animation.py 1. import pygame, sys, time 2. from pygame.locals import * 3. 4. # Установка pygame. 5. pygame.init() 6. 7. # Настройка окна. 8. WINDOWWIDTH = 400 9. WINDOWHEIGHT = 400 10. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32) 11. pygame.display.set_caption('Анимация') 12. 13. # Создание переменных направления. 14. DOWNLEFT = 'downleft' 15. DOWNRIGHT = 'downright' 16. UPLEFT = 'upleft' 17. UPRIGHT = 'upright' Анимированная графика 341 18. 19. MOVESPEED = 4 20. 21. # Настройка цвета. 22. WHITE = (255, 255, 255) 23. RED = (255, 0, 0) 24. GREEN = (0, 255, 0) 25. BLUE = (0, 0, 255) 26. 27. # Создание структуры данных блока. 28. b1 = {'rect':pygame.Rect(300, 80, 50, 100), 'color':RED, 'dir':UPRIGHT} 29. b2 = {'rect':pygame.Rect(200, 200, 20, 20), 'color':GREEN, 'dir':UPLEFT} 30. b3 = {'rect':pygame.Rect(100, 150, 60, 60), 'color':BLUE, 'dir':DOWNLEFT} 31. boxes = [b1, b2, b3] 32. 33. # Запуск игрового цикла. 34. while True: 35. # Проверка наличия события QUIT. 36. for event in pygame.event.get(): 37. if event.type == QUIT: 38. pygame.quit() 39. sys.exit() 40. 41. # Создание на поверхности белого фона. 42. windowSurface.fill(WHITE) 43. 44. for b in boxes: 45. # Перемещение структуры данных блока. 46. if b['dir'] == DOWNLEFT: 47. b['rect'].left -= MOVESPEED 48. b['rect'].top += MOVESPEED 49. if b['dir'] == DOWNRIGHT: 50. b['rect'].left += MOVESPEED 51. b['rect'].top += MOVESPEED 52. if b['dir'] == UPLEFT: 53. b['rect'].left -= MOVESPEED 54. b['rect'].top -= MOVESPEED 55. if b['dir'] == UPRIGHT: 56. b['rect'].left += MOVESPEED 57. b['rect'].top -= MOVESPEED 58. 342 Глава 18 59. # Проверка, переместился ли блок за пределы окна. 60. if b['rect'].top < 0: 61. # Прохождение блока через верхнюю границу. 62. if b['dir'] == UPLEFT: 63. b['dir'] = DOWNLEFT 64. if b['dir'] == UPRIGHT: 65. b['dir'] = DOWNRIGHT 66. if b['rect'].bottom > WINDOWHEIGHT: 67. # Прохождение блока через нижнюю границу. 68. if b['dir'] == DOWNLEFT: 69. b['dir'] = UPLEFT 70. if b['dir'] == DOWNRIGHT: 71. b['dir'] = UPRIGHT 72. if b['rect'].left < 0: 73. # Прохождение блока через левую границу. 74. if b['dir'] == DOWNLEFT: 75. b['dir'] = DOWNRIGHT 76. if b['dir'] == UPLEFT: 77. b['dir'] = UPRIGHT 78. if b['rect'].right > WINDOWWIDTH: 79. # Прохождение блока через правую границу. 80. if b['dir'] == DOWNRIGHT: 81. b['dir'] = DOWNLEFT 82. if b['dir'] == UPRIGHT: 83. b['dir'] = UPLEFT 84. 85. # Создание блока на поверхности. 86. pygame.draw.rect(windowSurface, b['color'], b['rect']) 87. 88. # Вывод окна на экран. 89. pygame.display.update() 90. time.sleep(0.02) Перемещение и контроль отскока блоков В этой программе у нас будет три блока разного цвета, движущихся и от- скакивающих от границ окна. В следующих главах мы используем эту про- грамму в качестве основы для создания игры, в которой одним из блоков можно будет управлять. Для этого сначала нужно определиться с желаемым способом перемещения блоков. Анимированная графика 343 Каждый блок будет двигаться в одном из четырех диагональных направ- лений. Когда блок сталкивается с границей окна, он должен отскакивать и двигаться в противоположном диагональном направлении. Блоки будут от- скакивать так, как показано на рис. 18.2. Рис. 18.2. Как будут отскакивать блоки Новое направление, в котором движется блок после отскока, зависит от двух факторов: в каком направлении он двигался перед отскоком и от какой границы окна отскочил. Существуют восемь возможных способов отскока — по два разных способа для каждой из четырех границ. Например, если блок движется вниз и направо, а затем отскакивает от нижней границы окна, нуж- но, чтобы новым направлением блока было вверх и вправо. Мы можем использовать объект Rect для представления положения и размера блока, кортеж из трех целых чисел для представления цвета блока и одного целого числа, представляющего, в каком из четырех диагональных направлений в данный момент двигается блок. Игровой цикл установит координаты x и y блока в объекте Rect и при каждой итерации отобразит на экране все блоки в их текущем положении. По мере работы цикла блоки будут постепенно перемещаться по экрану, что- бы это выглядело, будто они плавно двигаются и отскакивают. Создание констант Строки с 1 по 5 устанавливают наши модули и инициализируют pygame, аналогично тому, что мы делали в главе 17. 1. import pygame, sys, time 2. from pygame.locals import * 3. 4. # Установка pygame. 5. pygame.init() 344 Глава 18 6. 7. # Настройка окна. 8. WINDOWWIDTH = 400 9. WINDOWHEIGHT = 400 10. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32) 11. pygame.display.set_caption('Анимация') В строках 8 и 9 мы определяем две константы для ширины и высоты окна, после чего в строке 10 используем эти константы для задания переменной windowSurface , которая будет представлять окно pygame. Код в строке 11 со- держит функцию set_caption(), позволяющую вывести название в заголовке окна — 'Анимация'. В этой программе вы увидите, что значения ширины и высоты окна ис- пользуются не только для вызова функции set_mode(). Мы будем использовать константы на случай, если вы когда-либо захотите изменить размер окна, — тогда вам нужно будет изменить код только в строках 8 и 9. Поскольку во время выполнения программы ширина и высота окна никогда не меняются, константы — неплохая мысль. Константы для направлений Мы будем использовать константы для каждого из четырех направлений, в которых могут перемещаться блоки. 13. # Создание переменных направления. 14. DOWNLEFT = 'downleft' 15. DOWNRIGHT = 'downright' 16. UPLEFT = 'upleft' 17. UPRIGHT = 'upright' Для этих направлений вы могли бы использовать любое значение вместо константы. Например, можно использовать строку 'downleft' непосредствен- но для представления диагонального направления вниз и влево, и повторять строку каждый раз, когда вам нужно указать это направление. Однако если вы хоть раз опечатаетесь в значении 'downleft', то получите ошибку, из-за которой ваша программа будет вести себя странно, даже несмотря на то что сбоя не случится. Если вместо этого вы использовали константы и случайно ошиблись в имени переменной, Python обнаружит, что переменной с таким именем не Анимированная графика 345 существует, и устроит сбой программы, выдав ошибку. Это все равно будет довольно значительным дефектом, но, по крайней мере, вы сразу узнаете об этом и сможете все исправить. Мы также создаем константу, чтобы определить, как быстро блоки долж- ны двигаться. 19. MOVESPEED = 4 Значение 4 константы MOVESPEED сообщает программе, на сколько пик- селей должен перемещаться каждый блок при каждой итерации игрового цикла . Константы для цвета Код в строках 22–25 устанавливает константы для цветов. Помните, pygame использует кортежи из трех целых значений для совокупностей красного, зе- леного и синего цветов, называемых моделью RGB. Целые числа находятся в диапазоне от 0 до 255. 21. # Настройка цвета. 22. WHITE = (255, 255, 255) 23. RED = (255, 0, 0) 24. GREEN = (0, 255, 0) 25. BLUE = (0, 0, 255) Константы используются для удобства чтения кода, так же, как и в pygame- программе «Привет, мир!». Создание структуры данных блока Теперь определим блоки. Чтобы было проще, создадим словарь в виде структуры данных (см. раздел «Словари» в главе 9) для представления каж- дого перемещающегося блока. Словарь будет иметь ключи 'rect' (с объектом Rect в качестве значения), 'color' (с кортежем из трех целых чисел в качестве значения) и 'dir' (с одной из констант направления в качестве значения). Пока что мы создадим только три блока, но вы можете создать больше бло- ков, определив больше структур данных. При помощи кода анимации, кото- рый мы будем использовать позже, вы можете анимировать столько блоков, сколько определите при создании своих структур данных. 346 Глава 18 Переменная b1 будет хранить одну из этих структур данных блока. 27. # Создание структуры данных блока. 28. b1 = {'rect':pygame.Rect(300, 80, 50, 100), 'color':RED, 'dir':UPRIGHT} Верхний левый угол этого блока расположен в позиции с координатами x=300 и y=80. Его ширина составляет 50 пикселей, а высота — 100 пикселей. Блок окрашен в красный (RED), а начальное направление — UPRIGHT (вправо вверх). Код в строках 29 и 30 создает еще две аналогичные структуры данных для блоков, у которых разные размеры, положения, цвета и направления. 29. b2 = {'rect':pygame.Rect(200, 200, 20, 20), 'color':GREEN, 'dir':UPLEFT} 30. b3 = {'rect':pygame.Rect(100, 150, 60, 60), 'color':BLUE, 'dir':DOWNLEFT} 31. boxes = [b1, b2, b3] Если вам понадобилось извлечь блок или значение из списка, это можно сделать с помощью индексов и ключей. Ввод значения boxes[0] обращается к структуре данных словаря в b1. Если бы мы ввели boxes[0]['color'], выра- жение обратилось бы к ключу 'color' в b1, поэтому значение boxes[0]['color'] равнялось бы (255, 0, 0). Вы можете ссылаться на любое из значений в любой из структур данных блока, используя переменную boxes. Три словаря b1, b2 и b3 затем сохраняются в списке в переменной boxes. Игровой цикл Игровой цикл управляет анимацией движущихся блоков. Эффект ани- мации создается за счет того, что мы показываем друг за другом изображе- ния с небольшими различиями. В нашей анимации изображениями высту- пят движущиеся блоки, а небольшие различия будут состоять в положениях каждого блока. Каждый блок на изображении будет перемещаться на 4 пик- селя. Изображения будут чередоваться так быстро, что создастся впечатле- ние плавного перемещения по экрану. Если блок столкнется с границей окна, тогда игровой цикл заставит его отскочить, изменив направление. Теперь, когда мы немного знаем о том, как будет работать игровой цикл, давайте же его напишем! Обработка решения игрока завершить игру Когда игрок закрывает окно и завершает работу, мы должны прекратить выполнение программы так же, как мы делали это с pygame-программой «При- вет, мир!». Нам нужно осуществить это в игровом цикле, так что наша про- Анимированная графика 347 грамма постоянно проверяет, произошло ли событие QUIT. Код в строке 34 за- пускает цикл, а код в строках 36–39 руководит процессом выхода. 33. # Запуск игрового цикла. 34. while True: 35. # Проверка наличия события QUIT. 36. for event in pygame.event.get(): 37. if event.type == QUIT: 38. pygame.quit() 39. sys.exit() После этого следует убедиться, что windowSurface уже можно отображать. Позже мы отобразим каждый блок на windowSurface с помощью метода rect(). При каждой итерации игрового цикла код «перерисовывает» все содержимое окна, заменяя на нем блоки на новые на расстоянии в несколько пикселей от старых. При этом мы не отображаем заново весь объект Surface; взамен мы просто добавляем отображение объекта Rect на windowSurface. Но когда игро- вой цикл повторяется, «перерисовывая» каждый объект Rect, он не стирает старую версию Rect. Если мы просто позволим циклу отображать на экране объекты Rect, то получим их след вместо гладкой анимации. Чтобы этого из- бежать, необходимо очищать окно при каждой итерации игрового цикла. Для этого код в строке 42 заполняет Surface белым цветом, чтобы все, что было отображено там, стерлось. 41. # Создание на поверхности белого фона. 42. windowSurface.fill(WHITE) Без вызова windowSurface.fill(WHITE) для заливки белым всего окна перед отображением прямоугольников в новых положениях, вы увидите след объ- ектов Rect. Если хотите испробовать это и посмотреть, что произойдет, може- те закомментировать строку 42, указав символ # в ее начале. Как только windowSurface заполняется, можно приступать к отображению всех наших объектов Rect. Перемещение каждого блока Чтобы обновлять положение каждого блока, мы будем перебирать список boxes внутри игрового цикла. 44. for b in boxes: 348 Глава 18 Чтобы упростить код для ввода, внутри цикла for вы будете ссылаться на текущий блок как на b. Нам нужно изменить каждый блок в зависимости от направления, в котором он уже перемещается, поэтому мы будем использо- вать инструкции if, чтобы выяснить направление движения блока, проверяя ключ dir внутри структуры данных блока. Затем мы изменим положение бло- ка в зависимости от этого направления. 45. # Перемещение структуры данных блока. 46. if b['dir'] == DOWNLEFT: 47. b['rect'].left -= MOVESPEED 48. b['rect'].top += MOVESPEED 49. if b['dir'] == DOWNRIGHT: 50. b['rect'].left += MOVESPEED 51. b['rect'].top += MOVESPEED 52. if b['dir'] == UPLEFT: 53. b['rect'].left -= MOVESPEED 54. b['rect'].top -= MOVESPEED 55. if b['dir'] == UPRIGHT: 56. b['rect'].left += MOVESPEED 57. b['rect'].top -= MOVESPEED Новое значение атрибутов left и top каждого блока зависит от направле- ния его движения. Если это направление DOWNLEFT или DOWNRIGHT, нужно увели- чить значение атрибута top. Если это направление UPLEFT или UPRIGHT, нужно уменьшить значение атрибута top. Если направление блока — DOWNRIGHT или UPRIGHT, нужно увеличить значе- ние атрибута left. А если DOWNLEFT или UPLEFT — нужно уменьшить значение атрибута left. Значения этих атрибутов будут увеличиваться или уменьшаться на ве- личину целого числа, хранящегося в переменной MOVESPEED, то есть там, где хранится количество пикселей, на которые перемещаются блоки при каждой итерации игрового цикла. Мы присваиваем переменной MOVESPEED значение в строке 19. Например, если значение b['dir'] установлено равным 'downleft', b['rect'].left — равным 40 и b['rect'].top — 100, тогда условие в строке 46 будет истинным. Если переменной MOVESPEED присвоено значение 4, тог- да код в строках 47 и 48 изменит объект Rect так, что b['rect'].left станет 36 , а b['rect'].top — 104. Затем изменение значения Rect приводит к тому, что код в строке 86 «опускает» прямоугольник немного вниз и влево от его предыдущего положения. |