Как создать кнопку в pygame
Перейти к содержимому

Как создать кнопку в pygame

Создание игр на Python 3 и Pygame: Часть 4

Это четвёртая из пяти частей туториала, посвящённого созданию игр с помощью Python 3 и Pygame. В третьей части мы углубились в сердце Breakout и узнали, как обрабатывать события, познакомились с основным классом Breakout и увидели, как перемещать разные игровые объекты.

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

Распознавание коллизий

В играх объекты сталкиваются друг с другом, и Breakout не является исключением. В основном с объектами сталкивается мяч. В методе handle_ball_collisions() есть встроенная функция под названием intersect() , которая используется для проверки того, ударился ли мяч об объект, и того, где он столкнулся с объектом. Она возвращает ‘left’, ‘right’, ‘top’, ‘bottom’ или None, если мяч не столкнулся с объектом.

Столкновение мяча с ракеткой

Когда мяч стукается об ракетку, он отскакивает. Если он ударяется о верхнюю часть ракетки, то отражается обратно вверх, но сохраняет тот же компонент горизонтальной скорости.

Но если он ударяется о боковую часть ракетки, то отскакивает в противоположную сторону (влево или вправо) и продолжает движение вниз, пока не столкнётся с полом. В коде используется функция intersect() .

Столкновение с полом

Когда ракетка пропускает мяч на пути вниз (или мяч ударяется об ракетку сбоку), то мяч продолжает падать и затем ударяется об пол. В этот момент игрок теряет жизнь и мяч создаётся заново, чтобы игра могла продолжаться. Игра завершается, когда у игрока заканчиваются жизни.

Столкновение с потолком и стенами

Когда мяч ударяется об стены или потолок, он просто отскакивает от них.

Столкновение с кирпичами

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

Чтобы определить, что мяч ударился об кирпич, код проверят, пересекается ли какой-нибудь из кирпичей с мячом:

Программирование игрового меню

В большинстве игр есть какой-нибудь UI. В Breakout есть простое меню с двумя кнопками, ‘PLAY’ и ‘QUIT’. Меню отображается в начале игры и пропадает, когда игрок нажимает на ‘PLAY’. Давайте посмотрим, как реализуются кнопки и меню, а также как они интегрируются в игру.

Создание кнопок

В Pygame нет встроенной библиотеки UI. Есть сторонние расширения, но для меню я решил создать свои кнопки. Кнопка — это игровой объект, имеющий три состояния: нормальное, выделенное и нажатое. Нормальное состояние — это когда мышь не находится над кнопкой, а выделенное состояние — когда мышь находится над кнопкой, но левая кнопка мыши ещё не нажата. Нажатое состояние — это когда мышь находится над кнопкой и игрок нажал на левую кнопку мыши.

Кнопка реализуется как прямоугольник с фоновым цветом и текст, отображаемый поверх него. Также кнопка получает функцию on_click (по умолчанию являющуюся пустой лямбда-функцией), которая вызывается при нажатии кнопки.

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

Свойство back_color , используемое для отрисовки фонового прямоугольника, всегда возвращает цвет, соответствующий текущему состоянию кнопки, чтобы игроку было ясно, что кнопка активна:

Создание меню

Функция create_menu() создаёт меню с двумя кнопками с текстом ‘PLAY’ и ‘QUIT’. Она имеет две встроенные функции, on_play() и on_quit() , которые она передаёт соответствующей кнопке. Каждая кнопка добавляется в список objects (для отрисовки), а также в поле menu_buttons .

При нажатии кнопки PLAY вызывается функция on_play() , удаляющая кнопки из списка objects , чтобы они больше не отрисовывались. Кроме того, значения булевых полей, которые запускают начало игры — is_game_running и start_level — становятся равными True.

При нажатии кнопки QUIT is_game_running принимает значение False (фактически ставя игру на паузу), а game_over присваивается значение True, что приводит к срабатыванию последовательности завершения игры.

Отображение и сокрытие игрового меню

Отображение и сокрытие меню выполняются неявным образом. Когда кнопки находятся в списке objects , меню видимо; когда они удаляются, оно скрывается. Всё очень просто.

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

Подводим итог

В этой части мы рассмотрели распознавание коллизий и то, что происходит, когда мяч сталкивается с разными объектами: ракеткой, кирпичами, стенами, полом и потолком. Также мы создали меню с собственными кнопками, которое можно скрывать и отображать по команде.

В последней части серии мы рассмотрим завершение игры, отслеживание очков и жизней, звуковые эффекты и музыку.

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

How to make buttons in python/pygame?

I’m making a game in pygame and on the first screen I want there to be buttons that you can press to (i) start the game, (ii) load a new screen with instructions, and (iii) exit the program.

I’ve found this code online for button making, but I don’t really understand it (I’m not that good at object oriented programming). If I could get some explanation as to what it’s doing that would be great. Also, when I use it and try to open a file on my computer using the file path, I get the error sh: filepath :Permission denied, which I don’t know how to solve.

Thank you to anyone who can help me.

6 Answers 6

I don’t have a code example for you, but how I would do it is to:

  1. Make a Button class, with the text to go on the button as a constructor argument
    1. Create a PyGame surface, either of an image or filled Rect
    2. Render text on it with the Font.Render stuff in Pygame

    That is similar to what your example is doing, although different still.

    Another good way to create buttons on pygame (in Python) is by installing the package called pygame_widgets ( pip3 install pygame_widgets ).

    The ‘code’ you have found online is not that good. All you need to make a button is this. Put this near the beginning of your code:

    Put the following in your game loop. Also somewhere in your game loop:

    Also put this in your game loop wherever you have done for event in pygame.event.get

    So, buttonify loads the image that will be on the button. This image must be a .jpg file or any other PICTURE file in the same directory as the code. Picture is its name. The name must have .jpg or anything else after it and the name must be in quotation marks. The coords parameter in Buttonify is the top-right coordinate on your screen or window that opens from pygame. The surface is this thing:

    So it the function makes something called ‘image’ which is a pygame surface, it puts a rectangle around it called ‘imagerect’ (to set it at a location and for the second parameter when blitting,) and then it sets it’s location, and blits it on the second to last last line.

    The next bit of code makes ‘Image’ a tuple of both ‘image’ and ‘imagerect.’

    Designing a Button UI Module for Pygame

    Pygame is a 2D graphics and gaming library for Python. It’s pretty nifty because it essentially gives you a blank window that you can draw any shapes or lines or images you want on it. But it doesn’t come with any UI elements like buttons, scrollbars, or check boxes. This post will go through not only creating a button class for Pygame, but also the reasoning behind why I’ve set up the code as it is. This is more of a «how to create a module other people can use» tutorial than a UI or Pygame tutorial.

    This tutorial assumes you know the basics of Pygame and Python programming. If you don’t, it’s probably easy enough to follow anyway.

    A button is a common user interface (UI) control that is used in many software applications. It seems simple enough: there’s a button on the window and you click on it and something happens. But there’s a lot of details we should plan out ahead of time. Remember, we want to make a generic button class so that other programmers can use this in their games and programs. Once you’ve read through the process here, you’ll be familiar with how to make your own modules for UI elements.

    Designing a UI button class is good a good programming practice exercise.

    Download the PygButton module from PyPI by running «pip install pygbutton». You can also look at just the pygbutton.py file itself.

    The Feature List (and the Non-Feature List)

    First, let’s create a list of design details for the buttons:

    1. Can have any width or height.
    2. The buttons can have text on them. The font and size can be customized.
    3. The background color of the button can be changed to any RGB value, as can the foreground (text) color.
    4. The button’s properties (bgcolor, bgcolor, text, font, size, etc.) can be dynamically changed.
    5. The button has three states: normal, down (when the mouse cursor has pressed down on the button), and highlight (when the mouse cursor is over the button but not pressing down).
    6. Pygame’s mouse events are passed to the button’s handleEvent() method, which update’s the button’s state and calls any event-handling code.
    7. The button recognizes 6 different types of events: mouse enter, mouse exit, mouse down, mouse up, mouse click, and mouse move. (These are explained later.)
    8. Instead of text, the user will be able to specify images for the three different states. We’ll call these image-based buttons.
    9. The button’s visibility can be toggled on and off.

    And it’s always a good idea to come up with a list of things we specifically won’t implement (to avoid feature creep each time we think, «Hey, it’d be cool if we could. «). These features could always be implemented later.

    1. Must be rectangular (i.e. can’t be oval).
    2. No transparency.
    3. No more than the three states.
    4. No hotkeys attached to them, or keyboard focus.
    5. No special «double click» event (it’ll just be two click events).
    6. For now, the highlight state looks identical to the normal state for text-based buttons.
    7. A button is either text-based or image-based, there’s no hybrid.
    8. No «disabled» state.
    9. Only one font & color at a time for text-based buttons.
    10. The text caption will always be centered, not left- or right-aligned.

    (But you can add these features to your own code if you want.)

    Design Details

    Whenever you’re designing something, always do a prior art search first. Looking at how buttons on a web page work is a good case to examine, for example.

    The buttons have three states and can have a different appearance for each state.

    • The «normal» state is what the button looks like when it has not been clicked and the mouse is not over it.
    • The «highlight» state is what the button looks like when the mouse is hovering over it, but not clicking it. We can use this to add some kind of highlighting behavior when the mouse glides over the button. For normal text-based buttons, this state will look identical to the normal state.
    • The «down» state is what the button looks like when it is being clicked down.

    There are also six different «button events» that the buttons can produce based on the Pygame mouse events that are passed to them:

    • Enter — When a MOUSEMOTION event has told the button that the mouse is over the button when previously it wasn’t.
    • Exit — When a MOUSEMOTION event has told the button that the mouse is no longer over the button when previously it was.
    • Move — When the button has received a MOUSEMOTION event.
    • Down — When the mouse is pressed down on the button.
    • Up — When the mouse is released on the button.
    • Click — When the mouse was pressed down on the button and released over the button. (Releasing the mouse off of the button does not trigger the click event.)

    (Note: The buttons won’t produce Pygame USEREVENTS. I didn’t see a significant need for them.)

    As to how the mouse button looks, I’ll be using the Windows look-and-feel of buttons. Here’s what they look like zoomed in:

    Notice that the 3D appearance is caused by drawing these black, gray, and white outlines. These lines don’t change if the background color of the button changes.

    What the API will Look Like

    Before diving into coding, we need a concrete plan for how other programmers will use this module. It doesn’t matter how sophisticated your library is, if it is opaque, difficult to learn, and inconsistent no one will want to learn it and it will not be used. It’s important to get these details right the first time, because making changes (like changing a function name or getting rid of a class) later on could break other people’s code that uses your library. This means they won’t adopt newer versions and new features (since the newer version breaks their code), which further limits the popularity of your module.

    The button’s API will have three main parts: the constructor function that creates it, the function that draws the button to a pygame.Surface object (to display it on the screen), and a handleEvent() method that we can pass pygame.Event objects to so it knows what is happening in the program. The code will roughly look like this:

    Before we start coding, we should write out the method names and parameters for the PygButton class first. This will help cement what we want to code before we start coding:

    • def __init__(self, rect=None, caption=», bgcolor=LIGHTGRAY, fgcolor=BLACK, font=None, normal=None, down=None, highlight=None) — The constructor. Note that pretty much everything has a default argument. If the user just wants asimple button, we shouldn’t have to make her write out tons of boilerplate code. Let’s just supply default values.
    • def handleEvent(self, eventObj) — Changes the button’s state if the Pygame event passed is relevant.
    • def draw(self, surfaceObj) — Draws the button (in its current state) to the surfaceObj surface.
    • def mouseClick(self, event) — Called when the button has a click event. (These methods don’t do anything in the PygButton class, but you can override this class to implement code in these methods.)
    • def mouseEnter(self, event) — Called when the button has a «mouse enter» event.
    • def mouseExit(self, event) — Called when the button has a «mouse exit» event.
    • def mouseMove(self, event) — Called when the button has a «mouse move» event.
    • def mouseDown(self, event) — Called when the button has a mouse button down event.
    • def mouseUp(self, event) — Called when the button has a mouse button up event.
    • def setSurfaces(self, normalSurface, downSurface=None, highlightSurface=None) — Let’s the user specify either image filenames or pygame.Surface objects to use for each of the states. (This sets the button to be an image-based button.)

    And here are some properties that we’d like to set for the PygButton class. Whenever you think you’ll need a get and set method for something (i.e. getCaption() and setCaption() instead of just a caption property), this is a strong indication that a property would be better instead.

    • caption — The string for the text caption in the center of the button.
    • rect — A pygame.Rect object which gives the position and size of the button.
    • visible — A boolean that sets the button to visible (True) or invisible (False).
    • fgcolor — An RGB tuple or pygame.Color object for the text (foreground) color.
    • bgcolor — An RGB tuple or pygame.Color object for the background color.
    • font — A pygame.font.Font object for the font (and size) to use for the text caption.

    Setting any of these properties (other than rect) will result in the button becoming a text-based button if it was previously an image-based button. Setting the rect property of an image-based button simply resizes the images.

    Note that we don’t have properties for setting the normal, down, and highlight Surfaces. This is because when we switch from a normal text-based button (which uses the caption, fgcolor, bgcolor, and font properties) to an image-based button, we want to set the images for all three Surfaces at the same time (even though we have defaults for the down and highlight surfaces.)

    The Preamble Code

    Here’s the code that goes at the top of the pygbutton.py file. It imports Pygame and calls the init() function for the fonts and creates a few constants that we’ll use in the module.

    The Constructor Function

    The constructor function is fairly straight forward. There are many different attributes that we can customize for a button, but we can always just create a standard default button.

    For image-based buttons, the setSurfaces() method is called, which handles the default images for the Down and Highlight state if they are unspecified. It also checks that the images are the same size. Note that the user can specify either pygame.Surface objects or string filename values.

    Note that the PygButton class also stores the original images in the origSurfaceNormal, origSurfaceDown, and origSurfaceHighlight member variables. This is so that when the code does a resize, we are resizing the original images. The button could be resized multiple times, and this would result in poor quality if we tried to resize and previously resized image. (The same way a photocopy of a photocopy of a photocopy reduces the image quality.)

    The draw() Method

    The draw() method is straightforward since it only copies the surfaceNormal, surfaceDown, and surfaceHighlight properties to the passed pygame.Surface object. The draw() method is called whenever the button’s current state needs to be drawn to a Surface object. Drawing the buttons themselves will be handled by the _update() method.

    The _update() method will be called whenever the appearance of the buttons has been modified. This happens when the text, background color, size, etc. of the button has changed. This is why the name of _update() begins with an underscore; it’s only called by the class’s code itself. It shouldn’t be called by the user.

    The _update() method is mostly drawing code for text-based buttons (or resizing the images for image-based buttons).

    The Event Callback Methods

    There are two ways that we can execute code in response to button-related events. The first is to have a method in the PygButton class (and its subclasses) get called that contains the code we want to run.

    We’ll just put stub functions for these methods. Any subclasses that inherit from PygButton can override these methods and use any code they want. But for now, they do nothing:

    The handleEvent() Method

    Whenever our program calls pygame.event.get_events() to retrieve all the events generated (for keyboard, mouse, etc. events) we should pass them to handleEvent() so the buttons can update their state. The second way to execute code in response to button events is with the return value of handleEvent().

    The handleEvent() method has been set up so that it returns a list of all button events that have happened due to the normal Pygame events passed to handleEvent(). So if a mouse move Pygame event has happened over the button (when previously the mouse cursor wasn’t over the button), the handleEvent() method will return the list [‘enter’, ‘move’].

    The caller of handleEvent() can perform any actions in response to these events.

    Here’s the code for handleEvent():

    PygButton Properties

    Instead of having simple member variables for the caption, rect, visible, fgcolor, bgcolor, and font, we can use Python properties instead. This is better, because each time these values get updated we need to run some code that updates the Surface objects that hold the button’s look. In other languages, this would require the use of bulky get and set methods. Python’s property() function lets us assign methods to be called whenever the member variables need to be get or set.

    Example Programs

    This is a very important step. We need to accompany the module with some example programs that show how simple it is to actually use the module in a working program. I would even say that including example programs is more important than having documentation (for smaller libraries, at least.)

    Learn to program with my books for beginners, free under a Creative Commons license:

    Take my Automate the Boring Stuff with Python online Udemy course. Use this link to apply a 60% discount.

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

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