Шуман Х. - Python для детей - 2019. # Startwerte festlegen Red (255,0,0)
Скачать 5.95 Mb.
|
Глава Создание анимации 12 226 Метод update гарантирует, что содержимое окна обновляет- ся после прохождения каждого цикла. Но какой смысл в ме- тоде after()? Если ты не укажешь эту строку, то сразу уви- дишь эффект: круг будет двигаться слева направо с молние- носной скоростью, а если метод используется, движение выполняется несколько миллисекунд. Почему бы нам не использовать модуль time, как ранее? По идее, тот же результат будет достигнут, например, с помощью time. sleep(0,01) вместо after(10). Прием также работает, в прин- ципе. Однако не рекомендуется использовать внешние функции времени, потому что tkinter – это полноценная система (пользо- вательский интерфейс). В программах, особенно крупных, внеш- ний таймер может конфликтовать с внутренним управлением событиями посредством mainloop(). Вот почему мы используем метод tkinter after(). Нам еще не хватает функции, с помощью которой круг бу- дет исчезать (⇒ movei1.py): def hideImage() : global Circle Graphic.delete(Circle) Метод delete() удаляет круг из окна, затем его можно и нуж- но создать заново с помощью метода create_oval(). ¾ В приведенную выше программу добавь исходный код трех методов для кнопок SHOW, MOVE и DISAPPEAR. Затем за- пусти программу, отобрази круг, передвинь и удали его (рис. 12.2). Загружаем на холст изображение 227 Рис. 12.2.Готовое приложение Попробуй поэкспериментировать с разными значениями в качестве параметров методов move() и after(). (При зна- чении 0 эффект торможения равен нулю, но если значения слишком велики, движение может стать бесконечным!) Загружаем на холст изображение Да, круг неплох, однако лучше сделать реальную картин- ку, например с фигуркой, которая сможет даже двигаться сама. Но для этого нам нужен метод, который способен об- рабатывать внешние фотографии. Он доступен в Canvas под именем create_Image(), но сначала должен быть загружен файл изображения. Это можно сделать с помощью класса, отличного от Canvas: Picture = PhotoImage(file="Bilder/Figure01.gif") Опять же, Picture – это лишь идентификационный номер, который PhotoImage() назначает изображению после его за- грузки, для чего параметру file передается имя файла. В нашем случае файл figur01.gif находится в папке Bilder. В случае использования другой папки необходимо указать правильный путь. Глава Создание анимации 12 228 Почему именно изображения с расширением GIF, как насчет JPG или PNG? В настоящее время tkinter позволяет использовать только изображения в формате GIF. Если ты хочешь применять другие форматы, необходим дополнительный модуль под назва- нием Python Imaging Library (сокращенно PIL). Его можно загру- зить с сайта pythonware.com/products/pil/ К сожалению, этот модуль разработан только под версию Python 2, а в этой книге мы имеем дело с Python 3, но для внедрения под- держки есть модуль под названием Pillow, который доступен на сайте pypi.python.org/pypi/Pillow/ Модуль должен быть установлен в папку Python. Затем он может быть интегрирован с PIL путем ввода инструкции import Image. На мой взгляд, такой прием сложнее, чем класс PhotoImage. По- этому если есть возможность использовать изображения в фор- мате GIF, не нужно маяться с модулями PIL и Pillow. Как только изображение будет доступно, его можно создать и отобразить в объекте Canvas: Figure = Graphic.create_image(x, y, image=Picture) Следует отметить, что центр изображения указан здесь с помощью переменных x и y. Поскольку переменная Circle была заменена фигурой, дру- гие функции события также претерпевают (небольшие) из- менения. Ниже представлен полный листинг новой версии программы (⇒ movie2.py): # Анимация from tkinter import * # Режим и текст Width, Height = 600, 400 x, y = 120, 160 Mode = ["Появление", "Движение", "Сокрытие"] # Функция события def showImage() : global Figure global Picture Picture = PhotoImage(file="Bilder/Figure01.gif") Figure = Graphic.create_image(x, y, image=Picture) def moveImage() : global Figure for pos in range(20,Width-200,2) : Graphic.move(Figure, 2, 0) Загружаем на холст изображение 229 Graphic.update() Graphic.after(10) def hideImage() : global Figure Graphic.delete(Figure) # Основная программа Window = Tk() Window.title("Анимация") Window.config(width=Width, height=Height) Graphic = Canvas(Window, width=Width, height=Height) Graphic.pack() Knob = [] for Nr in range(0,3) : Knob.append(Button(Window, text=Mode[Nr])) Knob[Nr].place(x=80+Nr*150, y=Height-50, width=140, height=30) Knob[0].config(command=showImage) Knob[1].config(command=moveImage) Knob[2].config(command=hideImage) Window.mainloop() Как видишь, обе используемые переменные (Picture и Figu re ) должны быть глобальными, иначе все это не сработает. ¾ Измени исходный код и убедись, что у тебя есть соот- ветствующее изображение. (Если хочешь, ты можешь использовать изображения из папки с примерами для этой книги.) ¾ Теперь запусти программу; на этот раз рисунок будет перемещаться в пределах окна (рис. 12.3). Рис. 12.3.Вместо круга – клоун Глава Создание анимации 12 230 Коллекция изображений Изображение перемещается, но без эффекта движения – в смысле ходьбы или бега. Это было не так трагично в слу- чае с кругом, а здесь проблема очень заметна. Для анимации прогулки в двух направлениях нам нужна коллекция из как минимум восьми изображений. Итак, сначала создадим список: Picture = [0] Первый элемент уже есть. Для удобства я начинаю отсчет с 1, поэтому использую в общей сложности 9 элементов (и оставлю нулевой неиспользованным) (рис. 12.4). Рис. 12.4.Подборка изображений для проекта Теперь нам нужно достаточное количество рисунков. Я про- нумеровал свои файлы изображений с figure01.gif по fig u re08.gif. Ты можешь, конечно, создать свою собственную коллекцию изображений. Все изображения должны иметь одинаковые размеры и должны быть сохранены в соответствующем по- рядке с числом от 1 до 8. Правила, которые нужно соблю- дать, приведены в табл. 12.1. Коллекция изображений 231 Таблица 12.1. Изображения для проекта Файл изображения Позиция Файл изображения Позиция figure01.gif спереди figure05.gif спереди figure02.gif справа figure06.gif справа figure03.gif сзади figure07.gif сзади figure04.gif слева figure08.gif слева Каждая фигура сохранена в двух разных положениях. Если показывать поочередно, например figure02 и figure06, то по- хоже, что персонаж шагает вправо. Загрузка коллекции изображений должна выполняться с помощью дополнительной функции, отличной от showIm age() . Вместо одной инструкции нам нужен цикл, который должен выглядеть так: for Nr in range(1,9) : Name = "Figure0"+str(Nr)+".gif" self.Picture.append(PhotoImage(file="Bilder/"+Name)) Во-первых, составляется имя файла, что можно сделать, ис- пользуя его номер (который, конечно же, должен быть пре- образован в строку). Затем элемент добавляется в список. Но прежде чем мы определим другую функцию, мы долж- ны сделать небольшую паузу. Мы уже говорили про классы в прошлых главах, но есть вероятность, что ты мог все за- быть. Что мешает использовать класс в нашем анимацион- ном проекте? С моей точки зрения, ничего. Но как должен выглядеть но- вый класс? В любом случае в него входят три метода собы- тий. Затем мы попытаемся суммировать все это и помес- тить туда инициализацию (⇒ movie3.py): # Класс Player class Player : Picture = [0] def __init__(self, Graphic) : for Nr in range(1,9) : Name = "Figure0"+str(Nr)+".gif" self.Picture.append(PhotoImage(file="Bilder/"+Name)) def showImage(self) : self.Figure = Graphic.create_image(x, y, image=self.Pic ture[1]) def moveImage(self) : for pos in range(20,Width-200,2) : Graphic.move(self.Figure, 2, 0) Глава Создание анимации 12 232 Graphic.update() Graphic.after(10) def hideImage(self) : Graphic.delete(self.Figure) Новым является метод __init__(). Важно, чтобы каждый метод имел параметр self. Только так можно получить до- ступ к методам переменных класса. Поскольку графический объект Canvas не относится к классу Player , он наследуется, когда экземпляр инициализируется или создается. После того как все изображения были собраны в __init__(), функция showImage() отображает первый рисунок: self.Figure = Graphic.create_image(x, y, image=self.Picture[1]) Разумеется, изменения коснутся и основной программы. Там изображение должно быть согласовано как объект: Gameer = Player(Graphic) Ее код также нуждается в сопоставлении методов событий, которые теперь относятся к классу Player: Knob[0].config(command=Gameer.showImage) Knob[1].config(command=Gameer.moveImage) Knob[2].config(command=Gameer.hideImage) Весь остальной код остается неизменным. Но, даже отре- дактировав исходный код, ты не заметишь видимых изме- нений после запуска программы. Изображения все еще не анимированы. Класс Player Прежде чем мы оживим персонажа, мы должны сделать класс более универсальным. Для этого мы сохраним его в новом файле, который я назову mplayer.py. Класс Player 233 Первой строкой обязательно должна быть инструкция im port : from tkinter import * Ниже вставляем скопированное полное определение клас- са Player (рис. 12.5). В файле с основной программой ты, разумеется, можешь удалить соответствующий код. В верхней части файла с ос- новной программой введи дополнительную инструкцию import : from mplayer import * Рис. 12.5.Содержимое файла mplayer.py Теперь программа стала намного лучше (рис. 12.6). Но ра- ботает ли она? Глава Создание анимации 12 234 Рис. 12.6.Содержимое файла movie4.py Достаточно ли просто создать внешний класс и связать его с основной программой с помощью инструкции импорта? В принципе, да. Но первый запуск приводит к ошибке, показанной на рис. 12.7, сразу после нажатия кнопки Появление. Рис. 12.7.Ошибка после запуска программы Объект Graphic не определен? Но этот объект был передан как параметр при создании экземпляра Player! Очевидно, что ошибка заключается в определении класса Player. Класс Player 235 Первое, о чем я подумал, – это указать слово self перед Graphic . Но это приводит к новой ошибке: теперь Python жа- луется, что Graphic не является атрибутом Player. Это можно изменить следующим образом: def __init__(self, graphic) : self.Graphic = graphic Я чуть изменил имя второго параметра метода __init__(), при котором во время назначения будет создана новая пе- ременная, которая теперь является атрибутом класса. Сейчас нужно многое изменить. Все это можно найти в сле- дующем листинге в определении класса Player: from tkinter import * # Класс Player class Player : Picture = [0] def __init__(self, graphic) : self.Graphic = graphic for Nr in range(1,9) : Name = "Figure0"+str(Nr)+".gif" self.Picture.append(PhotoImage(file="Bilder/"+Name)) def showImage(self, x, y, Nr) : self.Figure = self.Graphic.create_image(x, y, image=self.Pic ture[Nr]) def moveImage(self, von, bis) : for pos in range(von, bis, 2) : self.Graphic.move(self.Figure, 2, 0) self.Graphic.update() self.Graphic.after(100) def hideImage(self) : self.Graphic.delete(self.Figure) ¾ Напиши исходный код определения класса в новом файле. В основной программе также есть улучшения. Это касается вызова двух методов – showImage() и moveImage(). Поскольку у них теперь больше параметров, а не только self, это необ- ходимо учитывать и в соответствующих кнопках: Глава Создание анимации 12 236 Knob[0].config(command=lambda : Gameer.showImage(x,y,1)) Knob[1].config(command=lambda : Gameer.moveImage(20,Width-200)) Еще раз, ключевое слово lambda должно использоваться как вспомогательное средство, чтобы вызываемые методы могли иметь параметры. Метод showImage() принимает по- зицию фигуры и номер изображения, а метод moveImage() – начальное и конечное значения по оси x (т. е. для переме- щения по горизонтали). Все работает? Все работает потому, что мы наконец создали нормальную программу, в которой может быть использован внешний класс? Да, но это еще не все. Теперь нам надо позаботиться о том, чтобы этот класс смог анимировать персонажа. Очевидно, что изменения в основном касаются метода moveImage() . Но есть еще несколько иных корректив. В пер- вую очередь нам нужна другая переменная в качестве атри- бута класса: PictureNr = 0 Она нужна, потому что для анимации мы должны пере- ключаться между разными изображениями. В этом случае мы имеем дело с изображениями номер 2 и 6. Для метода moveImage() в цикле for это выглядит так: if self.PictureNr == 2 : self.PictureNr = 6 else : self.PictureNr = 2 Конструкция if гарантирует, что номер изображения всег- да сменяется с 2 на 6 и так далее, а затем изображение сбра- сывается: self.Graphic.itemconfig(self.Figure, \ image=self.Picture[self.PictureNr]) С помощью метода itemconfig() мы заменяем одно изобра- жение другим. Таким образом, два рисунка отображаются поочередно – либо figure02, либо figure06. Имитируется ани- мация. Кроме того, изображение перемещается: Все работает? 237 self.Graphic.move(self.Figure, 10, 0) self.Graphic.update() self.Graphic.after(100) Я изменил размер шага для move(), а также добавил паузу, поэтому скорость смены изображений соответствует обще- му движению. В конце клоун должен снова смотреть вперед: self.Graphic.itemconfig(self.Figure, image=self.Picture[1]) Ниже показан полный исходный код класса Player (⇒ mplay er.py): class Player : Picture = [0] PictureNr = 0 def __init__(self, graphic) : self.Graphic = graphic for Nr in range(1,9) : Name = "Figure0"+str(Nr)+".gif" self.Picture.append(PhotoImage(file="Bilder/"+Name)) def showImage(self, x, y, Nr) : self.Figure = self.Graphic.create_image(x, y, \ image=self.Picture[Nr]) def moveImage(self, von, bis) : for pos in range(von, bis, 10) : if self.PictureNr == 2 : self.PictureNr = 6 else : self.PictureNr = 2 self.Graphic.itemconfig(self.Figure, \ image=self.Picture[self.PictureNr]) self.Graphic.move(self.Figure, 10, 0) self.Graphic.update() self.Graphic.after(100) self.Graphic.itemconfig(self.Figure, image=self.Picture[1]) def hideImage(self) : self.Graphic.delete(self.Figure) ¾ Оформи исходный код в соответствии с моими замеча- ниями, а затем запусти последнюю программу и нажми по очереди кнопки Появление_и_Движение'>Появление и Движение. Посмот- ри, как фигура бродит в окне (рис. 12.8). Затем нажми кнопку Сокрытие. Глава Создание анимации 12 238 Рис. 12.8.Фигура клоуна бредет в окне Повороты В моем проекте есть место для другого метода, который я бы назвал turnImage(). Он заставляет фигуру поворачи- ваться вокруг своей оси. Для этого сначала нужно дополнить окно еще одной кнопкой. Измени исходный код так, как показано ниже (⇒ movie5.py): # Анимация from tkinter import * from mplayerX import * # Режим и текст Width, Height = 600, 400 x, y = 120, 160 Mode = ["Появление", "Движение", "Поворот", "Сокрытие"] # Основная программа Window = Tk() Window.title("Анимация") Window.config(width=Width, height=Height) Graphic = Canvas(Window, width=Width, height=Height) Graphic.pack() # Игровой персонаж Gameer = Player(Graphic) # Управление Knob = [] Повороты 239 for Nr in range(0,4) : Knob.append(Button(Window, text=Mode[Nr])) Knob[Nr].place(x=30+Nr*135, y=Height-50, width=130, height=30) Knob[0].config(command=lambda : Gameer.showImage(x,y,1)) Knob[1].config(command=lambda : Gameer.moveImage(20,Width-200)) Knob[2].config(command=Gameer.turnImage) Knob[3].config(command=Gameer.hideImage) Window.mainloop() Нумерация кнопок немного меняется, но кнопка Сокры- тие должна оставаться последней. Метод turnImage() не нуж- дается в параметрах, поэтому его можно задействовать без ключевого слова lambda. Давай посмотрим, как мы можем заставить фигуру повора- чиваться (⇒ mplayerx.py): def turnImage(self) : for Nr in range(1, 5) : self.Graphic.itemconfig(self.Figure, image=self.Picture[Nr]) self.Graphic.update() self.Graphic.after(200) self.Graphic.itemconfig(self.Figure, image=self.Picture[1]) В цикле изображения отображаются последовательно, одно за другим. Похоже, что персонаж крутится вокруг своей оси (рис. 12.9). Рис. 12.9.Клоун крутится вокруг своей оси Глава Создание анимации 12 240 Важно, чтобы фотографии были сохранены в порядке, указанном выше. Если ты используешь свои собственные изображения, обя- зательно обрати на этот момент внимание или измени код метода turnImage() Чтобы вращение не происходило очень быстро, после пере- рыва интервал увеличивается. В конце фигура возвращает- ся в положение, обращенное к пользователю. Исчезновение и появление Есть еще кое-что, что меня беспокоит. Если ты нажмешь кнопку Появление, а затем Движение, появится фигура и начнет двигаться. Однако при повторном нажатии кноп- ки Появление появляется вторая фигура (рис. 12.10)! Рис. 12.10.Клонирование клоуна Необходимо как-то предотвратить повторное генерирова- ние фигуры до ее удаления нажатием кнопки Сокрытие. Например, присвоив переменной Figure значение 0 в самом начале определения класса: Figure = 0 Исчезновение и появление 241 Теперь есть атрибут с именем Figure, но, разумеется, нет самого объекта фигуры. Тем не менее здесь также есть ме- тод hideImage(), поэтому я дополню функцию showImage() (⇒ mplayerx.py): def showImage(self, x, y, Nr) : self.hideImage() self.Figure = self.Graphic.create_image(x, y, \ image=self.Picture[Nr]) Сначала существующая фигура удаляется, прежде чем бу- дет создана снова. Это гарантирует, что никогда не появит- ся двух клоунов одновременно, независимо от того, сколь- ко раз вы нажмете APPEAR. Главное, что существующая цифра удаляется или удаляет- ся, а затем снова воссоздается. Это гарантирует, что никог- да не будет двух символов одновременно, независимо от того, сколько раз ты нажал кнопку Появление. А как быть, если функция showImage() вызывается в первый раз? В этом случае фигура еще не существует, чтобы она была удалена методом hildeImage(). Догадка сразу же подтвержается в сооб- щении об ошибке, показанном на рис. 12.11. Рис. 12.11. У объекта нет атрибута Чтобы этого сбоя не было, нам нужно создать переменную Figure в самом начале (см. рис. 12.12): Figure = 0 |