17. Делаем первую игру на Python: Поймай шарик

Для создания игр разумнее использовать специальные библиотеки (движки), хотя бы Pygame, которые уже содержат такие вещи, как двойная буферизация, работа с спрайтами и т.д. Но первые «игры» мы будем создать с помощью tkinter, потому что цель — научится программировать (в Python), а не создать готовый продукт (игру). Для этой цели простой и понятный tkinter подходит больше, чем сложные, хотя и богатые по возможностям игровые движки. Дойдет очередь и до них, но не все сразу. На текущем этапе стоит задача разобраться с основными конструкциями и возможностями языка Python (операторы цикла, операторы ветвления, работа со списками, словарями и т.д.), т.е. все то, что дают на первых этапах всех курсов, о чем пишут в книгах по программированию, а игры — это не более, чем примеры.

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

Вначале создадим появляющиеся шарики:

from tkinter import *
from random import randrange as rnd, choice 
import time
root = Tk()
root.geometry('800x600')

canv = Canvas(root,bg='white')
canv.pack(fill=BOTH,expand=1)

colors = ['red','orange','yellow','green','blue']
def new_ball():
    canv.delete(ALL)
    x = rnd(100,700)
    y = rnd(100,500)
    r = rnd(30,50)
    canv.create_oval(x-r,y-r,x+r,y+r,fill = choice(colors), width=0)
    root.after(1000,new_ball) 
    
new_ball()
mainloop()

Теперь добавим обработку щелчка мыши. Для начала выведем что-нибудь в консоль:

from tkinter import *
from random import randrange as rnd, choice 
import time
root = Tk()
root.geometry('800x600')

canv = Canvas(root,bg='white')
canv.pack(fill=BOTH,expand=1)

colors = ['red','orange','yellow','green','blue']
def new_ball():
    canv.delete(ALL)
    x = rnd(100,700)
    y = rnd(100,500)
    r = rnd(30,50)
    canv.create_oval(x-r,y-r,x+r,y+r,fill = choice(colors), width=0)
    root.after(1000,new_ball) 
    
    
def click(event):
	print('click')    
    
new_ball()
canv.bind('<Button-1>', click)
mainloop()

При каждом щелчке в консоли будет появляться надпись «click».

Чтобы определить, попали ли мы в круг, нужно знать его координаты, радиус круга и координаты мыши в момент щелчка. Координаты мыши легко получить через event.x, event.y. Попробуем получить координаты круга:

def click(event):
	print(x,y,r)   

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

Исправим ситуацию:

def new_ball():
    global x,y,r
    canv.delete(ALL)
    x = rnd(100,700)
    y = rnd(100,500)
    r = rnd(30,50)
    canv.create_oval(x-r,y-r,x+r,y+r,fill = choice(colors), width=0)
    root.after(1000,new_ball) 
    
    
def click(event):
	print(x,y,r)    

Использование global – это не самое лучшее решение. Для данной задачи больше подходит использование ООП (объектно-ориентированного подхода), но об этом мы поговорим в следующей части. А пока – продолжим и будем использовать global.

global означает, что переменные будут считаться глобальными (а не локальными), т.е. их значение сохранится и после завершения работы функции, а не будет уничтожено, как это произойдет со всеми локальными переменными.

Осталось проверить, не лежит ли точка event.x,event.y дальше, чем r от точки x,y. Для этого, с помощью теоремы Пифагора мы найдем расстояние между двумя точками и сравним с радиусом круга.

Снимок экрана от 2016-01-05 19:32:16

Если расстояние (гипотенуза) больше радиуса, то щелчок произошел вне круга.
Снимок экрана от 2016-01-05 19:34:21

Если же расстояние меньше радиуса, значит щелкнули внутри круга.
Снимок экрана от 2016-01-05 19:35:05

def click(event):
	if (event.y - y)**2 + (event.x - x)**2 <= r**2:
		print('+')
	else:
		print('-')

Для подсчета очков потребуется еще одна переменная.

def click(event):
	global points
	if (event.y - y)**2 + (event.x - x)**2 <= r**2:
		points += 1

Теперь нужно подумать, как выводить значения. Можно в консоль, через print, но удобнее будет прямо на экран, через canv.create_text:

from tkinter import *
from random import randrange as rnd, choice 
import time
root = Tk()
root.geometry('800x600')

canv = Canvas(root,bg='white')
canv.pack(fill=BOTH,expand=1)

colors = ['red','orange','yellow','green','blue']
def new_ball():
    global x,y,r
    canv.delete(ALL)
    x = rnd(100,700)
    y = rnd(100,500)
    r = rnd(30,50)
    canv.create_text(20,20,text=str(points), font = 'Arial 20')
   
    canv.create_oval(x-r,y-r,x+r,y+r,fill = choice(colors), width=0)
    root.after(1000,new_ball) 
    
    
def click(event):
	global points
	if (event.y - y)**2 + (event.x - x)**2 <= r**2:
		points += 1

points = 0
new_ball()
canv.bind('<Button-1>', click)

mainloop()

Чтобы нельзя было «накручивать» очки, щелкая много раз по кругу, пока он не исчез,

def click(event):
	global points, x
	if (event.y - y)**2 + (event.x - x)**2 <= r**2:
		points += 1
		x = -1000 

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

def click(event):
	global points, x
	if (event.y - y)**2 + (event.x - x)**2 <= r**2:
		points += 1
		x = -1000
		canv.delete(ALL)

Лучше дать имена всем графическим примитивам (тексту и шарику) и удалять их отдельно друг от друга:

def new_ball():
    global x,y,r,ball
    canv.delete(ball)
    x = rnd(100,700)
    y = rnd(100,500)
    r = rnd(30,50)
    ball = canv.create_oval(x-r,y-r,x+r,y+r,fill = choice(colors), width=0)
    root.after(1000,new_ball) 
    
def click(event):
	global points, x
	if (event.y - y)**2 + (event.x - x)**2 <= r**2:
		points += 1
		x = -1000
		canv.delete(text)
		text = canv.create_text(20,20,text=str(points), font = 'Arial 20')

Все хорошо, только не работает. Говорит, что не может удалить ball, потому что при первом вызове ball на экране еще нет.
Это легко поправить:


def new_ball():
    global x,y,r,ball
    canv.delete(ball)
    x = rnd(100,700)
    y = rnd(100,500)
    r = rnd(30,50)
    ball = canv.create_oval(x-r,y-r,x+r,y+r,fill = choice(colors), width=0)
    root.after(1000,new_ball) 
    
def click(event):
	global points, x, text
	if (event.y - y)**2 + (event.x - x)**2 <= r**2:
		points += 1
		x = -1000
		canv.delete(text)
		text = canv.create_text(20,20,text=str(points), font = 'Arial 20')


ball = canv.create_oval(-100,0,0,0)
text = canv.create_text(20,20,text=0, font = 'Arial 20')
points = 0
new_ball()
canv.bind('<Button-1>', click)

Однако, круг все еще не удаляется при щелчке. Исправим, удалим круг после попадания:

def new_ball():
    global x,y,r,ball
    canv.delete(ball)
    x = rnd(100,700)
    y = rnd(100,500)
    r = rnd(30,50)
    ball = canv.create_oval(x-r,y-r,x+r,y+r,fill = choice(colors), width=0)
    root.after(1000,new_ball) 
    
def click(event):
	global points, x, text
	if (event.y - y)**2 + (event.x - x)**2 <= r**2:
		points += 1
		x = -1000
		canv.delete(text)
		canv.delete(ball)
		text = canv.create_text(20,20,text=str(points), font = 'Arial 20')


ball = canv.create_oval(-100,0,0,0)
text = canv.create_text(20,20,text=0, font = 'Arial 20')
points = 0
new_ball()
canv.bind('<Button-1>', click)

Есть иной, более удобный способ определять по какому графическому объекту щелкнули. Советую посмотреть справочник по Tkinter. Мы рассмотрим некоторые из этих возможностей во второй части книги. В целом, игра закончена, но можно и еще кое-что сделать:
1. Добавить подсчет количества промахов
2. Изменить подсчет очков, в зависимости от цвета или радиуса круга
3. Изменить подсчет очков, в зависимости от точности попадания. Ближе к центру круга – больше очков
4. Добавить случайности во время появления круга
5. Добавить подсчет упущенных кругов
6. Заканчивать игру по времени, по очкам или по количеству выстрелов
7. Хранить таблицу лучших результатов
8. Сопровождать звуков попадание и промах
9. Усложнить управление, добавив инерцию в прицел
10. Заменить курсор мыши на изображение прицела
11. Менять изображение прицела при наведении на цель

Я предлагаю поработать на этими заданиями самостоятельно. Жду ваших вопросов и результатов в комментариях к этой записи или в группе ВК . Чтобы добавить код в комментарии, оборачивайте его в теги
[pythоn]
ваш код
[/pythоn]

А лучше через http://pastebin.com

One thought on “17. Делаем первую игру на Python: Поймай шарик

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

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