Программирование аркадных игр и обучение информатике

Программирование аркадных игр
и обучение информатике



Chapter 10: Устройства ввода и Графика

Как, используя клавиатуру, мышку или игровой джойстик, мы можем заставить объекты двигаться?

10.1 Введение

So far, we've shown how to animate items on the screen, but not how to interact with them. How do we read and use the mouse, a keyboard, or a game controller? Thanksfully this is pretty easy.

10.2 Код рисования в функции

Видео: Рисование с помощью функции

Для начала нужно, чтобы у нас был объект, который мог бы двигаться по экрану. Лучший способ, как это сделать - создать функцию, берущую координаты x и y, а затем рисующую объект в данной точке.

Мы передаёт функции x и y как параметры. Например, следующий код рисует снеговика.

def draw_snowman(screen, x, y):
    pygame.draw.ellipse(screen, WHITE,[35+x, 0+y, 25, 25])
    pygame.draw.ellipse(screen, WHITE,[23+x, 20+y, 50, 50])
    pygame.draw.ellipse(screen, WHITE,[0+x, 65+y, 100, 100])

Затем, в главном цикле программы, мы можем нарисовать несольких снеговиков:
# Снеговик в верхней левой части
draw_snowman(screen,10,10)

# Снеговик в верхней правой части
draw_snowman(screen,300,10)

# Снеговик в нижней левой части
draw_snowman(screen,10,300)
fig.snowmen
Figure 10.1: Снеговики, которых нарисовала функция

Видео: переделка существующего кода в функцию

Нажмите здесь для кода работающего примера.

Скорее всего, у вас остался код, рисующий что-нибудь классное, с предыдущих лабораторных работ. Но как поместить его в функцию? Например, вот код, рисующий фигурку из линий:
# Голова
pygame.draw.ellipse(screen,black,[96,83,10,10],0)

# Ноги
pygame.draw.line(screen,black,[100,100],[105,110],2)
pygame.draw.line(screen,black,[100,100],[95,110],2)

# Тело
pygame.draw.line(screen,red,[100,100],[100,90],2)

# Руки
pygame.draw.line(screen,red,[100,90],[104,100],2)
pygame.draw.line(screen,red,[100,90],[96,100],2)
fig.stick_figure
Figure 10.2: Фигурка из линий

Этот код может быть легко переведён в функцию:

def draw_stick_figure(screen,x,y):
    # Голова
    pygame.draw.ellipse(screen,black,[96,83,10,10],0)

    # Ноги
    pygame.draw.line(screen,black,[100,100],[105,110],2)
    pygame.draw.line(screen,black,[100,100],[95,110],2)

    # Тело
    pygame.draw.line(screen,red,[100,100],[100,90],2)

    # Руки
    pygame.draw.line(screen,red,[100,90],[104,100],2)
    pygame.draw.line(screen,red,[100,90],[96,100],2)

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

fig.making_a_function
Figure 10.3: Создание функции

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

def draw_stick_figure(screen,x,y):
    # Голова
    pygame.draw.ellipse(screen,black,[96+x,83+y,10,10],0)

    # Ноги
    pygame.draw.line(screen,black,[100+x,100+y],[105+x,110+y],2)
    pygame.draw.line(screen,black,[100+x,100+y],[95+x,110+y],2)

    # Тело
    pygame.draw.line(screen,red,[100+x,100+y],[100+x,90+y],2)

    # Руки
    pygame.draw.line(screen,red,[100+x,90+y],[104+x,100+y],2)
    pygame.draw.line(screen,red,[100+x,90+y],[96+x,100+y],2)

Но проблема в том, что фигура рисуется с определённым смещением от исходной точки. Функция берёт поданные координаты как нулевую точку, а затем рисует фигуру примерно на 100 пикселей ниже и правее. Посмотрите:

fig.stick_figure_2
Figure 10.4: Фигурка

Добавив x и y в функцию, мы переместили начало координат фигуры на столько единиц. Например, если мы вызываем:

draw_stick_figure(screen,50,50)

Код не ставит фигурку в точке (50, 50). Он переносит начало вниз и вправо на 50 пикселей. Потому, что наша фигурка уже рисовалась примерно в (100, 100), вместе со смещением координат теперь она будет примерно в (150, 150). Как нам это починить, чтобы фигурка на самом деле рисовалась там, куда указывает вызов функции?

fig.code_example
Figure 10.5: Пример кода

Найти самое маленькое значение x, а затем - самое маленькое значение y. Отнять это значение от x и y в этой функции. Не менять значения высоты и ширины. Вот пример, где мы отняли минимальное значение x и y:

def draw_stick_figure(screen,x,y):
    # Голова
    pygame.draw.ellipse(screen,black,[96-95+x,83-83+y,10,10],0)

    # Ноги
    pygame.draw.line(screen,black,[100-95+x,100-83+y],[105-95+x,110-83+y],2)
    pygame.draw.line(screen,black,[100-95+x,100-83+y],[95-95+x,110-83+y],2)

    # Тело
    pygame.draw.line(screen,red,[100-95+x,100-83+y],[100-95+x,90-83+y],2)

    # Руки
    pygame.draw.line(screen,red,[100-95+x,90-83+y],[104-95+x,100-83+y],2)
    pygame.draw.line(screen,red,[100-95+x,90-83+y],[96-95+x,100-83+y],2)

Or, to make a program simpler, do the subtraction yourself:

def draw_stick_figure(screen,x,y):
    # Голова
    pygame.draw.ellipse(screen,black,[1+x,y,10,10],0)

    # Ноги
    pygame.draw.line(screen,black,[5+x,17+y],[10+x,27+y],2)
    pygame.draw.line(screen,black,[5+x,17+y],[x,27+y],2)

    # Тело
    pygame.draw.line(screen,red,[5+x,17+y],[5+x,7+y],2)

    # Руки
    pygame.draw.line(screen,red,[5+x,7+y],[9+x,17+y],2)
    pygame.draw.line(screen,red,[5+x,7+y],[1+x,17+y],2)

10.3 Мышь

Видео: передвижение с мышкой

Отлично, теперь мы знаем, как написать функцию, рисующую объект в определённой точке. Как нам получить координаты этой точки? Самое простое - с помощью мышки. Нужна всего лишь одна строка кода, чтобы заполучить координаты:

pos = pygame.mouse.get_pos()

Координаты возвращаются как список, или, если быть точным, как немодифицируемый кортеж. Как x, так и y, храняся в одной переменной. Так что, если мы напишем print( pos ), мы получим:

fig.coordinates
Figure 10.6: Координаты

Переменная pos - кортеж с двумя числами. Координата x хранится в позиции 0, а координата y - в позиции 1. Они могут лишь быть взяты и переданы функции, рисующей объект:

# Игровая логика
pos = pygame.mouse.get_pos()
x=pos[0]
y=pos[1]

# Рисование
draw_stick_figure(screen,x,y)

Получение координаты курсора мыши должно идти в часть с игровой логикой ("game logic") главного цикла программы. Вызов функции рисование должно идти в часть с рисованием ("drawing") главного цикла программы.

Единственная проблема в том, что курсор мышки рисуется поверх только что нарисованный фигуры, тем самым усложняя её рассмотрение.

Курсор можно спрятать, используя следующий код прямо перед главным циклом программы:

# Спрятать курсор мыши
pygame.mouse.set_visible(0)

Полностью работающий пример можно найти здесь: move_mouse.py

10.4 Клавиатура

Видео: передвижение с помощью клавиатуры

Управление с помощью клавиатуры ненмого более сложное. Мы не можем просто взять x и y от мышки. Нам нужно:

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

Для начала, перед главным циклом задаются начальные координаты и скоростной вектор:

# Скорость, пикселей за кадр
x_speed=0
y_speed=0

# Текущая позиция
x_coord=10
y_coord=10

Внутри главного цикла программы, обработка событий должна быть изменена. В дополнению к событию выхода, программа должна проверять ввод с клавиатуры. Событие создаётся каждый раз, когда пользователь нажимает на клавишу. Другое событие создаётся, когда пользователь отпускает клавишу. Когда пользователь нажимает на клавишу, вектор скорости устанавливается на 3 или -3. Когда пользователь отпускает клавишу, вектор скорости возвращается к нулю.

И, наконец, координаты объекта меняются в зависимости от вектора, после чего следует рисование объекта. Ознакомьтесь с нижеприведённым кодом:

for event in pygame.event.get():
	if event.type == pygame.QUIT:
		done=True

	# Пользоцатель нажимает на клавишу
	if event.type == pygame.KEYDOWN:
		# Figure out if it was an arrow key. If so
		# adjust speed.
		if event.key == pygame.K_LEFT:
			x_speed=-3
		if event.key == pygame.K_RIGHT:
			x_speed=3
		if event.key == pygame.K_UP:
			y_speed=-3
		if event.key == pygame.K_DOWN:
			y_speed=3

	# Пользователь отпускает клавишу
	if event.type == pygame.KEYUP:
		# If it is an arrow key, reset vector back to zero
		if event.key == pygame.K_LEFT:
			x_speed=0
		if event.key == pygame.K_RIGHT:
			x_speed=0
		if event.key == pygame.K_UP:
			y_speed=0
		if event.key == pygame.K_DOWN:
			y_speed=0

# Передвинуть объект в зависимости от вектора скорости.
x_coord=x_coord+x_speed
y_coord=y_coord+y_speed

# Нарисовать объект.
draw_stick_figure(screen,x_coord,y_coord)

Для полного примера используйте: move_keyboard.py. Заметьте, что этот пример не запрещает персонажу уходить за пределы экрана. Чтобы это сделать, в отделе с игровой логикой потребуется набор условий if, проверяющий значения x_coord и y_coord. Если они находятся за пределами экрана, то нужно вернуть координаты в предыдущую позицию, находившуюся на экране. Написать код для этого будет заданием самого читателя.

10.5 Джойстик

Джойстики требуют другой набор кода, но идея всё ещё достаточно проста.

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

# Текущая позиция
x_coord=10
y_coord=10

# Посчитать, сколько джойстиком подключено к компьютеру
joystick_count=pygame.joystick.get_count()
if joystick_count == 0:
	# Джойстики не подключены!
	print ("Error, I didn't find any joysticks.")
else:
	# Использовать джойстик #0, затем инициализировать его
	my_joystick = pygame.joystick.Joystick(0)
	my_joystick.init()

Джойстик вернёт два числа с плавающей точкой. Если джойстик идеально отцентрован - он вернёт (0,0). Если джойстик полностью в верхней и левой позиции, он вернёт (-1, -1), если же джойстик полностью в нижней и правой позиции, он вернёт (1,1). Если джойстик не сдвинут до конца, значения будут возвращены соответственно изменёнными.
Центр(0,0) Верх, лево(-1,-1)
Верх(0,-1) Верх, право(1,-1)
Право(1,0) Низ, право(1,1)
Низ(0,1) Лево(-1,0)

Внутри главного цикла программы, значения, возвращённые из джойстика, могут быть умножены в соответствии с тем, как далеко нужно подвинуть объект. В случае с нижеприведённым кодом, полное передвижение джойстика в одном из направлений задаст скорость в 10 пикселей/кадр (значения, возвращённые джойстиком, умножаются на 10).

# Пока есть джойстик
if joystick_count != 0:

	# Получить позицию джойстика
	# Будут возвращены числа в диапазоне от -1.0 до +1.0
	horiz_axis_pos= my_joystick.get_axis(0)
	vert_axis_pos= my_joystick.get_axis(1)

	# Подвинуть x в соответствии с положением джойстика. Умножаем на 10 для увеличения скорости.
	x_coord=int(x_coord+horiz_axis_pos*10)
	y_coord=int(y_coord+vert_axis_pos*10)

# Нарисовать предмет в соответствии с координатами
draw_stick_figure(screen,x_coord,y_coord)

Для полного примера, ознакомьтесь с move_game_controller.py.

У джойстиков есть много кнопок или рычажков. Внизу предоставлен пример программы и скриншот, выводящий на экран все события, вызванные с помощью игрового джойстика. Учтите, что джойстики должны быть подключены к компьютеру, иначе программа не будет работать.
joystick_calls.png

"""
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/

Show everything we can pull off the joystick
"""
import pygame

# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)


class TextPrint(object):
    """
    This is a simple class that will help us print to the screen
    It has nothing to do with the joysticks, just outputting the
    information.
    """
    def __init__(self):
        """ Constructor """
        self.reset()
        self.x_pos = 10
        self.y_pos = 10
        self.font = pygame.font.Font(None, 20)

    def print(self, my_screen, text_string):
        """ Draw text onto the screen. """
        text_bitmap = self.font.render(text_string, True, BLACK)
        my_screen.blit(text_bitmap, [self.x_pos, self.y_pos])
        self.y_pos += self.line_height

    def reset(self):
        """ Reset text to the top of the screen. """
        self.x_pos = 10
        self.y_pos = 10
        self.line_height = 15

    def indent(self):
        """ Indent the next line of text """
        self.x_pos += 10

    def unindent(self):
        """ Unindent the next line of text """
        self.x_pos -= 10


pygame.init()

# Set the width and height of the screen [width,height]
size = [500, 700]
screen = pygame.display.set_mode(size)

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.
done = False

# Used to manage how fast the screen updates
clock = pygame.time.Clock()

# Initialize the joysticks
pygame.joystick.init()

# Get ready to print
textPrint = TextPrint()

# -------- Main Program Loop -----------
while not done:
    # EVENT PROCESSING STEP
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

        # Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN
        # JOYBUTTONUP JOYHATMOTION
        if event.type == pygame.JOYBUTTONDOWN:
            print("Joystick button pressed.")
        if event.type == pygame.JOYBUTTONUP:
            print("Joystick button released.")

    # DRAWING STEP
    # First, clear the screen to white. Don't put other drawing commands
    # above this, or they will be erased with this command.
    screen.fill(WHITE)
    textPrint.reset()

    # Get count of joysticks
    joystick_count = pygame.joystick.get_count()

    textPrint.print(screen, "Number of joysticks: {}".format(joystick_count))
    textPrint.indent()

    # For each joystick:
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)
        joystick.init()

        textPrint.print(screen, "Joystick {}".format(i))
        textPrint.indent()

        # Get the name from the OS for the controller/joystick
        name = joystick.get_name()
        textPrint.print(screen, "Joystick name: {}".format(name))

        # Usually axis run in pairs, up/down for one, and left/right for
        # the other.
        axes = joystick.get_numaxes()
        textPrint.print(screen, "Number of axes: {}".format(axes))
        textPrint.indent()

        for i in range(axes):
            axis = joystick.get_axis(i)
            textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis))
        textPrint.unindent()

        buttons = joystick.get_numbuttons()
        textPrint.print(screen, "Number of buttons: {}".format(buttons))
        textPrint.indent()

        for i in range(buttons):
            button = joystick.get_button(i)
            textPrint.print(screen, "Button {:>2} value: {}".format(i, button))
        textPrint.unindent()

        # Hat switch. All or nothing for direction, not like joysticks.
        # Value comes back in an array.
        hats = joystick.get_numhats()
        textPrint.print(screen, "Number of hats: {}".format(hats))
        textPrint.indent()

        for i in range(hats):
            hat = joystick.get_hat(i)
            textPrint.print(screen, "Hat {} value: {}".format(i, str(hat)))
        textPrint.unindent()

        textPrint.unindent()

    # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

    # Go ahead and update the screen with what we've drawn.
    pygame.display.flip()

    # Limit to 60 frames per second
    clock.tick(60)

# Close the window and quit.
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit()

10.6 Повторение пройденного

Пройдите тест с несколькими вариантами ответов (на англ. яз.).


You are not logged in. Log in here and track your progress.