Программирование аркадных игр
и обучение информатике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) |
Нажмите здесь для кода работающего примера.
Скорее всего, у вас остался код, рисующий что-нибудь классное, с предыдущих лабораторных работ. Но как поместить его в функцию? Например, вот код, рисующий фигурку из линий:
# Голова 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) |
Этот код может быть легко переведён в функцию:
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)
Положить функцию сразу после определений цвета в коде. Затем вызвать функцию в месте, где был до этого расположен оригинальный код. Наверное, лучшим способом объяснить это, будет следующая картинка:
Этот код не включает в себя координаты 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 пикселей ниже и правее. Посмотрите:
Добавив x и y в функцию, мы переместили начало координат фигуры на столько единиц. Например, если мы вызываем:
draw_stick_figure(screen,50,50)
Код не ставит фигурку в точке (50, 50). Он переносит начало вниз и вправо на 50 пикселей. Потому, что наша фигурка уже рисовалась примерно в (100, 100), вместе со смещением координат теперь она будет примерно в (150, 150). Как нам это починить, чтобы фигурка на самом деле рисовалась там, куда указывает вызов функции?
Найти самое маленькое значение 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 ), мы получим:
Переменная 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 и y.
- Установить "скорость", когда нажата клавиша со стрелкой(keydown).
- Снизить "скорость", когда клавиша со стрелкой опущена (keyup)
- Приспособить 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.
У джойстиков есть много кнопок или рычажков. Внизу предоставлен пример программы и скриншот,
выводящий на экран все события, вызванные с помощью игрового джойстика. Учтите, что джойстики должны быть
подключены к компьютеру, иначе программа не будет работать.
""" 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.
English version by Paul Vincent Craven
Spanish version by Antonio Rodríguez Verdugo
Russian version by Vladimir Slav
Turkish version by Güray Yildirim
Portuguese version by Armando Marques Sobrinho and Tati Carvalho
Dutch version by Frank Waegeman
Hungarian version by Nagy Attila
Finnish version by Jouko Järvenpää
French version by Franco Rossi
Korean version by Kim Zeung-Il
Chinese version by Kai Lin