Продолжаем делать игру «Jumper».
В прошлом уроке мы создали прыгающий мячик и платформы. Теперь пришло время организовать взаимодействие мячика и платформ.
from tkinter import * from random import randrange as rnd import time root = Tk() fr = Frame(root) root.geometry('800x600') canv = Canvas(root, bg = 'white') canv.pack(fill=BOTH,expand=1) def click(event): global j j.vx = (event.x - j.x)/10 j.vy = (event.y - j.y)/10 canv.bind('<1>',click) class Jumper(): def __init__(self): self.x = 40 self.y = 150 self.r = 20 self.id = canv.create_oval(self.x-self.r,self.y-self.r,self.x+self.r,self.y+self.r,fill='orange') self.vy = 0 self.vx = 6 def move(self): self.vy += 1.5 self.y += self.vy self.x += self.vx self.vx *= 0.99 if self.y > 550: self.vy *= -0.7 self.vx *= 0.8 self.y = 550 if self.x > 750: self.x = 750 self.vx *= -0.7 if self.x < 50: self.x = 50 self.vx *= -0.7 canv.coords(self.id,self.x-self.r,self.y-self.r,self.x+self.r,self.y+self.r) class Platform(): def __init__(self): self.x = 300 self.y = 0 self.vx = 0 self.vy = 2 self.w = 120 self.h = 20 self.id = canv.create_rectangle(self.x,self.y,self.x+self.w,self.y+self.h,fill='green',width=0) def move(self): self.y += self.vy canv.coords(self.id,self.x,self.y,self.x+self.w,self.y+self.h) j = Jumper() pls = [] # список для хранения всех платформ def new_platform(): p = Platform() # создали платформу p.x = rnd(50,700) # переместили в случайное место по горизонтали pls.append(p) # добавили в список root.after(2000,new_platform) # через 2 сек появится новая платформа new_platform() # создаем первую платформу и запускаем процесс непрерывного их создания while 1: j.move() for pl in pls: # перебрать все платформы в списке pl.move() # каждой дать команду переместиться canv.update() time.sleep(0.03) mainloop()
Для того, чтобы определить, касается ли мячик платформы, есть два способа. Можно определять по координатам (как мы и сделаем), а можно через методы Tkinter. Во всех библиотеках (движках) для создания игр есть способы для определения столкновений. Tkinter не является библиотекой для создания игр, но его Canvas обладает интересными возможностями, среди которых есть и проверка столкновений, но пользоваться мы ею не будем.
Первая мысль, которая приходит в голову — это проверить на равенство координаты платформы и мяча. Но, во-первых, вероятность того, что центр круга совпадет с левым верхним углом прямоугольника не очень высока, а во-вторых, нам это и не нужно.
На данном изображении хорошо видно, что центр круга не совпадает с левым верхним углом прямоугольника, но мы должны засчитать такое положение как касание круга и прямоугольника.
Еще варианты попаданий:
Для начала попробуем считать попаданием ситуацию, в которой центр круга находится внутри прямоугольника. Это самое простое:
Изменим основной цикл так:
while 1: j.move() for pl in pls: # перебрать все платформы в списке pl.move() # каждой дать команду переместиться if pl.x <= j.x <= pl.x+pl.w and pl.y <= j.y <= pl.y+pl.h: # если центр круга внутри платформы j.vx = 0 j.vy = 0
Немного странно, не так ли?
Мячик застревает внутри платформы. Попробуем его усадить повыше:
j.vx = 0 j.vy = 0 j.y = pl.y-j.r
Сможете объяснить то, что произошло? Почему мячик дергается?
Измените проверку попадания мячика на платформу:
if pl.x <= j.x <= pl.x+pl.w and pl.y-j.r <= j.y <= pl.y+pl.h: # если центр круга внутри платформы
Мячик продолжает дергаться, но уже не так сильно.
Почему?
if pl.x <= j.x <= pl.x+pl.w and pl.y-j.r <= j.y <= pl.y+pl.h: # если центр круга внутри платформы j.vx = 0 j.vy = pl.vy j.y = pl.y-j.r
Почему перестал дергаться?
Продолжаем улучшать код: сделаем так, чтобы при прыжке вверх мячик не цеплялся за платформу.
Для этого добавим проверку направление полета шарика:
if j.vy >= 0 and pl.x <= j.x <= pl.x+pl.w and pl.y-j.r <= j.y <= pl.y+pl.h: # если центр круга внутри платформы j.vx = 0 j.vy = pl.vy j.y = pl.y-j.r
Другую проблему попробуйте исправить сами: при появлении платформа на мгновение появляется и только потом перемещается в «своё» место и это мелькание заметно. Сделайте так, чтобы мелькания не было, чтобы платформа сразу появлялась там, где нужно. Это не очень сложно.
А пока мы добавим разнообразие в поведение платформ: заставим некоторые из перемещаться по горизонтали.
class Platform(): def __init__(self): self.x = 300 self.y = 0 if rnd(2) == 1: # решаем, сделать ли платформу движущейся self.vx = 1 color = 'orange' else: self.vx = 0 color = 'green' self.vy = 2 self.w = 120 self.h = 20 self.id = canv.create_rectangle(self.x,self.y,self.x+self.w,self.y+self.h,fill=color,width=0) def move(self): self.y += self.vy self.x += self.# двигаем платформу canv.coords(self.id,self.x,self.y,self.x+self.w,self.y+self.h)
Появилось три проблемы: шарик соскальзывает с платформы; а платформы уезжают за экран; платформы едут только направо.
def move(self): self.y += self.vy self.x += self.vx if self.x > 790 - self.w or self.x < 10: self.vx *= -1 canv.coords(self.id,self.x,self.y,self.x+self.w,self.y+self.h)
Платформы стали отражаться от вертикальных стен.
Теперь сделаем так, чтобы платформы двигались с разной скоростью и направление выбирали случайно. Для упрощения я не стал оставлять неподвижные платформы и вернул всем зеленый цвет. Неподвижные платформы могут появится, но вероятность этого не очень высока.
class Platform(): def __init__(self): self.x = 300 self.y = 0 self.vx = random()*6-3 color = 'green' self.vy = 2 self.w = 120 self.h = 20 self.id = canv.create_rectangle(self.x,self.y,self.x+self.w,self.y+self.h,fill=color,width=0)
Появилась новая функция: random()
. Ее нужно импортировать из модуля random:
from tkinter import * from random import randrange as rnd, random import time
Дело в том, что randrange возвращает только целое число, а это даст всего несколько вариантов скорости: -2,-1,0,1,2,3. Если же мы хотим, чтобы скорость могла быть любой, в том числе и дробной: -2.1, -1.7, и т.д. то нужно использовать функцию random(). Она возвращает случайное число [0;1). Т.е. не включая единицу. Чтобы получить любое число мы умножим на расстояние между крайними точками (которые мы хотим получить) и сдвинем влево на половину этого расстояния. Если random() даст 0, то по той формуле получится 0*6-3=-3. Это самое маленькое значение, которое можно получить (той формулой). Если же random() даст 1 (точнее 0.999999),т.е. самое большое значение, которое может дать, то по формуле получится 1(условно)*6 -3 = 6-3 = 3. Т.е. эта формула даст случайные значения в [-3;3), что нам и требовалось.
Еще увеличим разнообразие: добавим падающие платформы и выделим их цветом:
class Platform(): def __init__(self): self.x = 300 self.y = 0 self.vx = random()*6-3 color = 'green' self.vy = 2 self.w = 120 self.h = 20 self.mode = rnd(2) # сделать ли падающей? self.ay = 0 # ускорение падения. Пока не падает if self.mode == 0: # если падающая, то - оранжевым color = 'orange' self.id = canv.create_rectangle(self.x,self.y,self.x+self.w,self.y+self.h,fill=color,width=0) def move(self): self.y += self.vy self.vy += self.ay # пока платформа не стала падать, в ay будет 0 self.x += self.vx if self.x > 790 - self.w or self.x < 10: self.vx *= -1 canv.coords(self.id,self.x,self.y,self.x+self.w,self.y+self.h)
А теперь при посадке на платформу «прилепим» шарик, чтобы он не соскальзывал.
while 1: j.move() for pl in pls: # перебрать все платформы в списке pl.move() # каждой дать команду переместиться if j.vy >= 0 and pl.x <= j.x <= pl.x+pl.w and pl.y-j.r <= j.y <= pl.y+pl.h: # если центр круга внутри платформы j.vx = pl.vx # взять горизонтальную скорость платформы j.vy = pl.vy # взять вертикальную скорость платформы j.y = pl.y-j.r if pl.mode == 0: # если мяч прикоснулся к падающей платформе, то она начинает падать: pl.ay = 1
Прежде, чем закончить, сделаем еще одну важную вещь: чтобы мячик не прыгал в воздухе, а мог прыгать только после того, как прикоснулся к платформе:
class Jumper(): def __init__(self): self.x = 40 self.y = 150 self.r = 20 self.id = canv.create_oval(self.x-self.r,self.y-self.r,self.x+self.r,self.y+self.r,fill='orange') self.vy = 0 self.vx = 6 self.can_jump = 1 # может ли прыгать
while 1: j.move() for pl in pls: # перебрать все платформы в списке pl.move() # каждой дать команду переместиться if j.vy >= 0 and pl.x <= j.x <= pl.x+pl.w and pl.y-j.r <= j.y <= pl.y+pl.h: # если центр круга внутри платформы j.vx = pl.vx # взять горизонтальную скорость платформы j.vy = pl.vy # взять вертикальную скорость платформы j.y = pl.y-j.r if pl.mode == 0: # если мяч прикоснулся к падающей платформе, то она начинает падать: pl.ay = 1 j.can_jump = 1 # снова можно прыгать canv.update() time.sleep(0.03)
def click(event): global j if j.can_jump != 0: j.vx = (event.x - j.x)/10 j.vy = (event.y - j.y)/10 j.can_jump = 0 # если прыгнул, то еще раз прыгать нельзя, пока не коснулся платформы
Все вместе:
from tkinter import * from random import randrange as rnd, random import time root = Tk() fr = Frame(root) root.geometry('800x600') canv = Canvas(root, bg = 'white') canv.pack(fill=BOTH,expand=1) def click(event): global j if j.can_jump != 0: j.vx = (event.x - j.x)/10 j.vy = (event.y - j.y)/10 j.can_jump = 0 # если прыгнул, то еще раз прыгать нельзя, пока не коснулся платформы canv.bind('<1>',click) class Jumper(): def __init__(self): self.x = 40 self.y = 150 self.r = 20 self.id = canv.create_oval(self.x-self.r,self.y-self.r,self.x+self.r,self.y+self.r,fill='orange') self.vy = 0 self.vx = 6 self.can_jump = 1 # может ли прыгать def move(self): self.vy += 1.5 self.y += self.vy self.x += self.vx self.vx *= 0.99 if self.y > 550: self.vy *= -0.7 self.vx *= 0.8 self.y = 550 if self.x > 750: self.x = 750 self.vx *= -0.7 if self.x < 50: self.x = 50 self.vx *= -0.7 canv.coords(self.id,self.x-self.r,self.y-self.r,self.x+self.r,self.y+self.r) class Platform(): def __init__(self): self.x = 300 self.y = -50 self.vx = random()*6-3 color = 'green' self.vy = 2 self.w = 120 self.h = 20 self.mode = rnd(2) # сделать ли падающей? self.ay = 0 # ускорение падения. Пока не падает if self.mode == 0: # если падающая, то - оранжевым color = 'orange' self.id = canv.create_rectangle(self.x,self.y,self.x+self.w,self.y+self.h,fill=color,width=0) def move(self): self.y += self.vy self.vy += self.ay # пока платформа не стала падать, в ay будет 0 self.x += self.vx if self.x > 790 - self.w or self.x < 10: self.vx *= -1 canv.coords(self.id,self.x,self.y,self.x+self.w,self.y+self.h) j = Jumper() pls = [] # список для хранения всех платформ def new_platform(): p = Platform() # создали платформу p.x = rnd(50,700) # переместили в случайное место по горизонтали pls.append(p) # добавили в список root.after(2000,new_platform) # через 2 сек появится новая платформа new_platform() # создаем первую платформу и запускаем процесс непрерывного их создания while 1: j.move() for pl in pls: # перебрать все платформы в списке pl.move() # каждой дать команду переместиться if j.vy >= 0 and pl.x <= j.x <= pl.x+pl.w and pl.y-j.r <= j.y <= pl.y+pl.h: # если центр круга внутри платформы j.vx = pl.vx # взять горизонтальную скорость платформы j.vy = pl.vy # взять вертикальную скорость платформы j.y = pl.y-j.r if pl.mode == 0: # если мяч прикоснулся к падающей платформе, то она начинает падать: pl.ay = 1 j.can_jump = 1 # снова можно прыгать canv.update() time.sleep(0.03) mainloop()
Что можно сделать еще?
Можно добавить счетчик прыжков, пройденных платформ, секунд.
Можно сделать меню и падение мяча вниз без остановки на нижней границе. Можно изменить управление: сделать управление с клавиатуры.
Это все можно сделать.
Но ключевой момент данного урока это классы.
Классы позволяют объединить свойства и методы в один контейнер. Это удобно. Еще классы могут быть унаследованы от других, но это тема совсем другого урока.
One thought on “20. Заканчиваем игру Jumper (почти Doodle)”