18. Начинаем создавать игру «Jumper» (почти как Doodle). Анимация.

Суть игры простая: прыгать с платформы на платформу и продержаться как можно дольше.

Первое, с чего нужно начать — это анимация. Изображать джампера будем простым кругом. Перед тем, как мы заставим его прыгать, научим его двигаться.

# -*- coding: utf-8 -*-
# строка выше нужна для того, чтобы можно было использовать русские буквы в комментариях

from tkinter import *
root = Tk()
root.geometry('800x600')
canv = Canvas(root, bg = 'white')
canv.pack(fill=BOTH,expand=1)
x = 40
y = 450
r = 20
jumper = canv.create_oval(x-r,y-r,x+r,y+r) # просто нарисовали круг
mainloop()

Попробуем заставить его двигаться:

# -*- coding: utf-8 -*-
from tkinter import *
root = Tk()
root.geometry('800x600')
canv = Canvas(root, bg = 'white')
canv.pack(fill=BOTH,expand=1)
x = 40
y = 450
r = 20
jumper = canv.create_oval(x-r,y-r,x+r,y+r) 
x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r) # изменили координаты 
x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r)
x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r)
x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r)
mainloop()

Мяч двигается, но мы этого не видим. Дело в том, что все происходит слишком быстро. Замедлим процесс

from tkinter import *
import time # модуль для работы со временем
root = Tk()
fr = Frame(root)
root.geometry('800x600')
canv = Canvas(root, bg = 'white')
canv.pack(fill=BOTH,expand=1)
x = 40
y = 450
r = 20
jumper = canv.create_oval(x-r,y-r,x+r,y+r)
x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r)
time.sleep(0.3)

x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r)
time.sleep(0.3)

x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r)
time.sleep(0.3)

mainloop()

Оказалось, дело было не только в том, что изображение менялось слишком быстро. Оно вообще не менялось! И появилось только после того, как сработала команда mainloop()
Чтобы показать изображение раньше будем использовать canv.update()

from tkinter import *
import time # модуль для работы со временем
root = Tk()
fr = Frame(root)
root.geometry('800x600')
canv = Canvas(root, bg = 'white')
canv.pack(fill=BOTH,expand=1)
x = 40
y = 450
r = 20
jumper = canv.create_oval(x-r,y-r,x+r,y+r)
x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r)
canv.update()
time.sleep(0.3)

x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r)
canv.update()
time.sleep(0.3)

x += 10
canv.coords(jumper,x-r,y-r,x+r,y+r)
canv.update()
time.sleep(0.3)

mainloop()

Вот теперь видно, что круг немного двигается. Добавим цикл:

from tkinter import *
import time # подключаем модулть
root = Tk()
fr = Frame(root)
root.geometry('800x600')
canv = Canvas(root, bg = 'white')
canv.pack(fill=BOTH,expand=1)
x = 40
y = 450
r = 20
jumper = canv.create_oval(x-r,y-r,x+r,y+r)
for z in range(100):
    x += 5
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.3)

mainloop()

Теперь надо увеличить скорость смены кадров, т.е. уменьшить задержку. time.sleep(0.03) будет в самый раз.

Если вы заметили мерцание, то сразу сообщу, что с мерцанием мы бороться не будем, просто смиримся с ним. Для создания игр больше подходят другие пакеты (pygame,kivy,..) но на данный момент наша задача — это обучение, а не создание красочных игры. Поэтому мы ограничимся tkinter’ом и не будем обращать внимание на мерцание. Kivy будет, но немного позже.

А теперь сделаем так, чтобы шарик менял скорость:

v = 10
for z in range(100):
    x += v
    v -= 0.15
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)

Это первый способ замедления. Обратите внимание, что скорость становится отрицательной и круг начинает двигаться в обратном направлении. Это очень скоро нам пригодится для вертикальной составляющей скорости. А для горизонтальной больше подойдет другой способ:

v = 20
for z in range(100):
    x += v
    v *= 0.9 # скорость уменьшается, пока не превратиться в 0
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)

Теперь отработаем составляющую часть движения:

x = 40
y = 150
r = 20
jumper = canv.create_oval(x-r,y-r,x+r,y+r)
v = 0
for z in range(100):
    y += v
    v += 0.3 # скорость увеличивается, чтобы круг двигался вниз все быстрее и быстрее (координата y растет вниз)
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)

Как сделать так, чтобы шарик остановился в нижней части экрана? А лучше отскочил от нее и подпрыгнул вверх? Подсказка: используйте цикл while
**** тут должен быть спойлер *****

x = 40
y = 150
r = 20
jumper = canv.create_oval(x-r,y-r,x+r,y+r)
v = 0
while 1: # вечный цикл
    y += v
    v += 0.3 # скорость увеличивается, чтобы круг двигался вниз все быстрее и быстрее (координата y растет вниз)
    if y > 550:
        v *= -1
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)

Теперь объединим движение по вертикали и по горизонтали:

x = 40
y = 150
r = 20
jumper = canv.create_oval(x-r,y-r,x+r,y+r)
vy = 0
vx = 10 # чтобы было видно движение по горизонтали
while 1: # вечный цикл
    y += vy
    x += vx
    vy += 0.3 # скорость увеличивается, чтобы круг двигался вниз все быстрее и быстрее (координата y растет вниз)
    vx *= 0.99 # при меньших значениях скорость уменьшается слишком быстро
    if y > 550:
        vy *= -1
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)

Отскоки должны быть затухающими, чтобы после удара круг отскакивал вверх не на такую же высоту, а примерно на 70% от первоначальной высоты:

vy = 0
vx = 10
while 1: # вечный цикл
    y += vy
    x += vx
    vy += 0.3 # скорость увеличивается, чтобы круг двигался вниз все быстрее и быстрее (координата y растет вниз)
    vx *= 0.99
    if y > 550:
        vy *= -0.7
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)

Не очень-то получилось, правда?
Дело в том, что когда круг уходит ниже, чем 550, то он не всегда может вернуться обратно из-за того, что его обратная скорость стала меньше, чем была.
Например, при скорости 20 круг может пройти по таким координатам: 548, 568. В этот момент мы разворачиваем его, но скорость уменьшаем -20*-0.7 = 14 и он из 568 переходит в точку с y = 568-14 = 554, что ниже, чем 550. Все, круг попался: опять сработает условие y > 550 и опять мы развернем его. Результат вы уже видели.
Решение простое:

vy = 0
vx = 1
while 1: # вечный цикл
    y += vy
    x += vx
    vy += 0.5 # немного увеличим скорость, а то падает слишком медленно для Земли
    vx *= 0.99
    if y > 550:
        vy *= -0.7
        y = 550
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)

Ну вот, теперь вылезла другая проблема: он прыгает и не успокаивается. Математическую сторону этой проблемы я предлагаю вам увидеть самостоятельно, я же дам самое простое решение:

vy = 0
vx = 6 # скорость снова увеличил, чтобы поймать еще одну проблему
while 1: # вечный цикл
    vy += 0.5 # скорость изменяем до изменения координаты 
    y += vy
    x += vx
    vx *= 0.99
    if y > 550:
        vy *= -0.7
        y = 550
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)

Jumper падает с ускорением, как положено, отскакивает, замедляется и … продолжает скользить по горизонтали. Смотрится не очень хорошо, не так ли?

vy = 0
vx = 6
while 1: # вечный цикл
    vy += 0.5  
    y += vy
    x += vx
    vx *= 0.99
    if y > 550:
        vy *= -0.7
        vx *= 0.8
        y = 550
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)

Ну вот теперь просто замечательно! В момент касания земли джампер будет замедлять горизонтальную скорость, чтобы не скользить.

Теперь заставим его прыгать по щелчку мыши. Вы помните, как назначить обработку события? Как определить щелчок мыши на холсте?
***** тут должен быть спойлер *****

from tkinter import *
import time # подключаем модулть
root = Tk()
fr = Frame(root)
root.geometry('800x600')
canv = Canvas(root, bg = 'white')
canv.pack(fill=BOTH,expand=1)

def click(event):
        print('!!!')
        
canv.bind('<1>',click) # создать обработчик события "Щелчок левой кнопкой мыши" для холста и связать его с вызовом функции click
# обратите внимание, что эти действия нужно выполнять до цикла, т.к. все, что после цикла не выполняется (цикл-то вечный!)
x = 40
y = 150
r = 20
jumper = canv.create_oval(x-r,y-r,x+r,y+r)
vy = 0
vx = 6
while 1: # вечный цикл
    vy += 0.5 
    y += vy
    x += vx
    vx *= 0.99
    if y > 550:
        vy *= -0.7
        vx *= 0.8
        y = 550
    canv.coords(jumper,x-r,y-r,x+r,y+r)
    canv.update()
    time.sleep(0.03)


mainloop()

После того, как мы убедились, что щелчок мыши обрабатывается, можно начинать «пинать» джампера:

def click(event):
        global vy
        vy = -10

Остановим движение по горизонтали:

def click(event):
    global vy,vx
    vy = -10
    vx = 0

Добавим немного понимания геометрии, чтобы джампер прыгал в сторону мыши (это проще, поэтому начнем с такого варианта).

Снимок экрана от 2016-01-09 21:19:15

Снимок экрана от 2016-01-09 21:21:10

Чтобы рассчитать значение скорости мы найдем расстояние между точками и возьмем скорость как 1/10 этого расстояния.
На самом деле немного проще, так как составляющие скорости у нас две: vx и vy (скорость по горизонтали и скорость по вертикали). Находим проекцию расстояния между точками на оси, берем 1/10 часть от них — это и будут соответствующие значения скоростей.

def click(event):
    global vy,vx
    vx = (event.x - x)/10
    vy = (event.y - y)/10
1. Повторить все столько раз, сколько потребуется для понимания кода.
2. Повторить все столько раз, сколько потребуется, чтобы вы могли сами написать этот код.
Следующий урок будет сложнее: мы будем создавать движущиеся платформы. Если вы до конца не понимаете то, что сделано в этом уроке или не можете сами это повторить, то следующий урок будет просто наслоением непонимания. Повторите этот код, даже если для этого потребуется переписывать его 10 раз. (когда-то давно я запомнил с 15-го, несмотря на то, что придумал все это сам)
3. Добавьте цвет джамперу и фону.
4. Поиграйте со значениями скорости и ускорением падения. Сейчас он падает слишком медленно, на мой взгляд.
5. Сделайте отскок от стен слева и справа.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *