19. Продолжаем делать игру «Jumper». Объекты — это экземпляры классов.

Пока у нас прыгает один джампер, платформ нет.

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):
	global vy,vx
	vx = (event.x - x)/10
	vy = (event.y - y)/10
	 
canv.bind('<1>',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 += 1.5
	y += vy
	x += vx
	vx *= 0.99
	if y > 550:
		vy *= -0.7
		vx *= 0.8
		y = 550
	if x > 750:
		x = 750
		vx *= -0.7
	if x < 50:
		x = 50
		vx *= -0.7
	canv.coords(jumper,x-r,y-r,x+r,y+r)
	canv.update()
	time.sleep(0.03)
mainloop()

Джампер так и останется один, а вот платформ будет много. Причем заранее неизвестно сколько. Это означает, что мы не можем просто создать список переменных x1,y1,x2,y2 и т.д., чтобы хранить в них параметры платформ. Нужно какое-то сильное решение, которое позволит создавать неограниченное количество одинаковых объектов и легко управлять ими.

На помощь приходит ООП: объектно-ориентированное программирование. Что такое «объект» в жизни? Это какая-то штука, у которой есть свойства (ширина, высота, вес, может еще что-нибудь). Иногда эта штука лежит себе и лежит (или даже сидит, как памятник), а другая штука может действовать.
Удобнее всего представить себе «объект» как некую коробочку, у которой есть свойства (и они описаны в документации) и кнопки (чтобы эта коробочка могла что-то делать).

Соберем информацию о джампере в один объект: (это не надо повторять, просто посмотреть)

class = Jumper():
	x = 40
	y = 150
	r = 20
	id = canv.create_oval(x-r,y-r,x+r,y+r)
	vy = 0
	vx = 6

Теперь, чтобы изменить скорость джампера, надо писать не vx=10, а jumper.vx=10
На первый взгляд, никаких упрощений. На самом деле так и есть: мы не получим большой выгоды от создания класса «джампер», потому что не планируем создавать их много. Джампер будет один. Тем не менее, это будет полезная практика по созданию класса, которая очень пригодится при создании платформ.
Класс — это описание того, какими будут объекты. Объекты — это экземпляры класса.
Для пример: класс — «люди». Вася — экземпляр класса «люди».
Класс — это шаблон, по которому создаются объекты.

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

j = Jumper()

Оставшаяся часть кода будет выглядеть так: (это не надо повторять, просто посмотреть)

while 1:
	j.vy += 1.5
	j.y += j.vy
	j.x += j.vx
	j.vx *= 0.99
	if j.y > 550:
		j.vy *= -0.7
		j.vx *= 0.8
		j.y = 550
	if j.x > 750:
		j.x = 750
		j.vx *= -0.7
	if j.x < 50:
		j.x = 50
		j.vx *= -0.7
	canv.coords(j.id,j.x-j.r,j.y-j.r,j.x+j.r,j.y+j.r)
	canv.update()
	time.sleep(0.03)
 

Однако, нужно поправить и функцию click(): (это не нужно повторять, просто посмотреть)

def click(event):
	global j
	j.vx = (event.x - j.x)/10
	j.vy = (event.y - j.y)/10

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

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):
	global j
	j.vx = (event.x - j.x)/10
	j.vy = (event.y - j.y)/10
	 
canv.bind('<1>',click) 

class Jumper():
	x = 40
	y = 150
	r = 20
	id = canv.create_oval(x-r,y-r,x+r,y+r) 
	vy = 0
	vx = 6

	def move(self): # self - ссылка на себя
		self.vy += 1.5 # свою скорость увеличить на 1,5
		self.y += self.vy # к своей координате y прибавить свою скорость
		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)
		

j = Jumper()

while 1:
	j.move()
	canv.update()
	time.sleep(0.03)
 
 
mainloop()

При копировании иногда возникают проблемы с отступами. Дело в том, что для нас табуляция и 4 пробела выглядят одинаково, но с точки зрения питона — это разные отступы.
Некоторые редакторы автоматически заменяют знаки табуляции на 4 пробела, но geany не из них. Это нужно проделать вручную, через меню «Документ»-«Заменить пробелы табуляциями».

Теперь можно перейти к созданию платформ:

class Platform():
	x = 300
	y = 0
	vx = 0
	vy = 2
	w = 120
	h = 20
	id = canv.create_rectangle(x,y,x+w,y+h,fill='green',width=0) # это создаст проблему

j = Jumper()
p = Platform()

После запуска вы увидите, что одна платформа была создана и «висит» в верхней части экрана.
Научим ее двигаться:

	def move(self):
		self.y += self.vy
		canv.coords(self.id,self.x,self.y,self.x+self.w,self.y+self.h)

Не двигается?
Нужно вызвать метод move():

j = Jumper()
p = Platform()

while 1:
	j.move()
	p.move()
	canv.update()
	time.sleep(0.03)
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)
 
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():
	x = 40
	y = 150
	r = 20
	id = canv.create_oval(x-r,y-r,x+r,y+r) 
	vy = 0
	vx = 6

	def move(self): # self - ссылка на себя
		self.vy += 1.5 # свою скорость увеличить на 1,5
		self.y += self.vy # к своей координате y прибавить свою скорость
		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():
	x = 300
	y = 0
	vx = 0
	vy = 2
	w = 120
	h = 20
	id = canv.create_rectangle(x,y,x+w,y+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()
p = Platform()

while 1:
	j.move()
	p.move()
	canv.update()
	time.sleep(0.03)
mainloop()

Работает?

Теперь создам несколько платформ. Пусть будет три, для начала:

j = Jumper()
p1 = Platform()
p2 = Platform()
p3 = Platform()

while 1:
	j.move()
	p1.move()
	p2.move()
	p3.move()
	canv.update()
	time.sleep(0.03)
 
mainloop()

Ну как? Одну видите?
Какие предположения?
Может быть изображения наложились друг на друга, ведь координаты одинаковые?
Попробуем дать разные координаты:


j = Jumper()
p1 = Platform()
p1.x = 100
p2 = Platform()
p2.x = 200
p3 = Platform()
p3.x = 300

Не помогло. Убедимся, что платформы действительно созданы:

j = Jumper()
p1 = Platform()
p1.x = 100
p2 = Platform()
p2.x = 200
p3 = Platform()
p3.x = 300

print(p1)
print(p2)
print(p3)

В консоли должны быть что-то вроде этого:
Снимок экрана от 2016-01-09 22:16:19

Разные числа показывают, что p1, p2 и p3 — это разные объекты.

Посмотрим на x,y всех объектов:

j = Jumper()
p1 = Platform()
p1.x = 100
p2 = Platform()
p2.x = 200
p3 = Platform()
p3.x = 300

print(p1.x, p1.y)
print(p2.x, p2.y)
print(p3.x, p3.y)
print(j.x, j.y)

Снимок экрана от 2016-01-09 22:20:11

В этой части все в порядке. Но не случайно я обратил внимание на одну строку при создании платформы.
Посмотрим на id всех созданных объектов:

print(p1.id)
print(p2.id)
print(p3.id)
print(j.id)

Вот в этом и проблема.
На все три платформы — один рисунок!

Теперь разберемся, как это произошло:

class Platform():
	x = 300
	y = 0
	vx = 0
	vy = 2
	w = 120
	h = 20
	id = canv.create_rectangle(x,y,x+w,y+h,fill='green',width=0) # это создаст проблему
	print('new_platform')

Мы создали три платформы, по идее должны три раза увидеть надпись new_platform, но она будет одна!
Если нужно, чтобы выполнялись какие-либо действия для каждого экземпляра класса, то нужно создавать конструктор. Что-то можно создать без конструктора (это пройдет для обычных числовых переменных), но для чего-либо более сложного нужно создавать конструктор. К чему-то «более сложному» относится и создание изображения.
Сделать нужно так:

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) 

__init__(self) — это особое название конструктора. Именно этот метод будет вызван при создании экземпляра класса (объекта).
Теперь все в порядке, если не считать того, что платформы «слиплись». Я думаю, что вы справитесь с тем, чтобы их разделить.

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

Сверим код. Должно быть так:

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):
	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()
p1 = Platform()
p1.x = 50
p2 = Platform()
p2.x = 200
p3 = Platform()
p3.x = 350


while 1:
	j.move()
	p1.move()
	p2.move()
	p3.move()
	canv.update()
	time.sleep(0.03)
 
 
mainloop()

Чтобы создавать неограниченное количество платформ будем использовать список:

j = Jumper()
pls = [] # список для хранения всех платформ

def new_platform():
	p = Platform() # создали платформу
	p.x = rnd(50,700) # переместили в случайное место по горизонтали
	pls.append(p) # добавили в список

while 1:
	j.move()
	for pl in pls: # перебрать все платформы в списке
		pl.move() # каждой дать команду переместиться
	canv.update()
	time.sleep(0.03)
 
mainloop()

Первой строкой вставьте в программе:

from random import randrange as rnd 

Но так ни одной платформы не появится, потому что функция new_platform не будет вызвана. Никогда.
Исправим это:


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()

Осталась одна проблема: платформы сначала появляются в центре экрана, а потом переходят в случайную позицию.

1. Как сделать, чтобы платформы не мелькали при появлении?
2. Как сделать, чтобы платформы появлялись через разные промежутки времени?
3. Как сделать, чтобы некоторые платформы двигались влево или вправо?
4. Как сделать, чтобы платформы, двигающиеся влево-вправо, отражались от боковых стен и шли в противоположном направлении?
5. Тщательно разобрать весь код. Создавать джампера и платформы до тех пор, пока не сможете делать это без пауз.

На следующем уроке мы организуем взаимодействие платформ и джампера.

One thought on “19. Продолжаем делать игру «Jumper». Объекты — это экземпляры классов.

  1. Уведомление: Аноним

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

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