Так что я думаю, что здесь есть две проблемы.Во-первых, как сгруппировать кнопки в некоторый логический и функциональный набор.Другой проблемой является отслеживание состояния пользователя и использование его для определения того, что он видит на экране.Мое понимание наборов кнопок состоит в том, что для данного состояния игры показан другой набор кнопок.
Поэтому, когда вы думаете «состояние» в программировании, вы, как правило, также думаете « enum»».В приведенном ниже коде я создал два набора перечислимых типов, один для состояния игры, другой для событий кнопок.В основном перечисляемые типы - просто числа с красивыми именами.Их использование делает код более читабельным - но это немного больше работы.
Например:
if ( game_state == 6 ):
гораздо менее понятен, чем:
if ( game_state == GameState.GAME_OVER ):
Любая пользовательская программа имеет подразделы, где управление не обязательно связано с«основной бизнес» программы.Это может быть открытие файла или выбор сложности - по сути, управляющий ввод (движения мыши, щелчки и события клавиатуры и т. Д.) Должен обрабатываться по-разному.Итак, мы отслеживаем game_state
, чтобы знать, с какой частью мы сейчас работаем.Для этой конкретной программы она позволяет нам контролировать, какое меню ButtonSet
выводить на экран.
Чтобы сгруппировать набор из Button
объектов в какую-то группу, я создал не-образное имя ButtonSet
.По сути, это объект-обертка вокруг списка кнопок, с вспомогательными функциями для одновременной работы со всем набором.
Пока я писал для них демо-код, мне пришло в голову, что для обработки событий онбыло много времени, чтобы написать (и для python, чтобы интерпретировать) много if button_name == "blah"
повсюду.Поэтому я создал набор уникальных событий кнопок с перечислением ButtonEvent
.Когда кнопка нажата, она теперь публикует уникальный номер события, а не одно событие щелчка для всех кнопок.Затем я понял, что поля ширины, высоты и т. Д. Могут быть сохранены в PyGame rect , и щелчок проверяется с помощью функции столкновения точек класса rect.Это немного упростило код.
#Stealth Assassin
import pygame #Imports the pygame module inclulding many in built functions that aids in game design
import time #Imports the time module for which I can implement delays into my program
import enum
pygame.init() #Runs pygame
clock = pygame.time.Clock() #Intialises the variable to control the game clock (FPS)
#gameDisplay = pygame.display.set_mode((1920,1080),pygame.FULLSCREEN) #Variable which will set the resolution of the game window and put the window into fullscreen mode
gameDisplay = pygame.display.set_mode((800,800)) #Variable which will set the resolution of the game window and put the window into fullscreen mode
pygame.display.set_caption("Stealth Assassin") #Sets the title of the pygame window for the game
### All states the game-screen can be in
class GameState( enum.Enum ):
MENU_PLAYQUIT = 1,
MENU_DIFFICULTY = 2,
MENU_LEVELSELECT = 3,
GAME_PLAYING = 4,
GAME_OVER = 5
### All the event-codes the buttons send back
class ButtonEvent( enum.IntEnum ): # IntEnum so we can convert back to an int for Event poting
QUIT = pygame.USEREVENT + 1
PLAY = pygame.USEREVENT + 2
EASY = pygame.USEREVENT + 3
MEDIUM = pygame.USEREVENT + 4
HARD = pygame.USEREVENT + 5
VETERAN = pygame.USEREVENT + 6
LEVEL1 = pygame.USEREVENT + 7
LEVEL2 = pygame.USEREVENT + 8
LEVEL3 = pygame.USEREVENT + 9
LEVEL4 = pygame.USEREVENT +10
class Button: #This class contains methods for buttons including display and functionality
def __init__(self, buttonname, event_code, buttonx, buttony, buttonwidth, buttonheight, textfile, textx, texty): #Methods used to allow classes to intialise attributes
self.buttonname = buttonname # Name of the button
self.rect = pygame.Rect( buttonx, buttony, buttonwidth, buttonheight )
self.text_image = pygame.image.load( textfile+".png" ) # Button Label
self.textx = textx # X-axis positioning of the text
self.texty = texty # Y-axis positioning of the text
self.event_code = event_code
def drawButton(self, screen): #Method which creates a button for the menu
pygame.draw.rect(screen, (0,0,0), self.rect ) #Draws a rectangular button which is black and given the size and coordinates which were attributes
screen.blit(self.text_image, (self.textx,self.texty)) #Displays the text given coordinates
def checkClick( self, mouse_position ):
""" Check if the given point is inside our button-rectangle.
If the click was, post a BUTTON_CLICK_EVENT to the PyGame Event queue and return True
return False otherwise """
result = False
if ( self.rect.collidepoint( mouse_position ) ):
#If the mouse-click is inside our rectangle, post a message to the queue
pygame.event.post( pygame.event.Event( int( self.event_code), { "button_name" : self.buttonname } ) )
result = True
return result
###
### A container class for a bunch of buttons
###
class ButtonSet:
def __init__( self, *buttons ):
self.buttons = list( buttons )
def addButton( self, b ):
""" Add a new button to our set, but not if we have it already """
if ( b not in self.buttons ):
self.buttons.append( b )
def anyClicked( self, click_location ):
""" For every button in the group, check to see if the mouse click was inside it. """
result = False
for b in self.buttons:
if ( b.checkClick( click_location ) == True ):
result = True
return result
def draw( self, screen ):
""" Paint the entire button set to the screen """
for b in self.buttons:
b.drawButton( screen )
PlayButton = Button('playbutton',ButtonEvent.PLAY,133,477,756,223,'button_text',387,545) or ButtonAction(1) #Creates play button
QuitButton = Button('quitbutton',ButtonEvent.QUIT,133,731,756,223,'button_text',387,806) #Creates quit button
play_quit_buttons = ButtonSet( PlayButton, QuitButton )
EasyButton = Button('easybutton', ButtonEvent.EASY, 133,477,362,223,'button_text',214,548) #Creates easy button
MediumButton = Button('mediumbutton', ButtonEvent.MEDIUM, 533,477,362,223,'button_text',560,548) #Creates medium button
HardButton = Button('hardbutton', ButtonEvent.HARD, 133,731,362,223,'button_text',214,806) #Creates hard button
VeteranButton = Button('veteranbutton', ButtonEvent.VETERAN, 533,731,362,223,'button_text',537,806) #Creates veteran button
difficulty_buttons = ButtonSet( EasyButton, MediumButton, HardButton, VeteranButton )
OneButton = Button('onebutton', ButtonEvent.LEVEL1, 133,477,362,223,'button_text',287,550) #Creates the level 1 button
TwoButton = Button('twobutton', ButtonEvent.LEVEL2, 533,477,362,223,'button_text',693,550) #Creates the level 2 button
ThreeButton = Button('threebutton', ButtonEvent.LEVEL3, 133,731,362,223,'button_text',285,810) #Creates the level 3 button
FourButton = Button('fourbutton', ButtonEvent.LEVEL4, 533,731,362,223,'button_text',685,810) #Creates the level 4 button
level_buttons = ButtonSet( OneButton, TwoButton, ThreeButton, FourButton )
### What game-state is displayed to the user
game_state = GameState.MENU_PLAYQUIT
game_difficulty = 1
game_level = 1
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
print("Quit Event")
done = True
elif event.type == pygame.MOUSEBUTTONUP:
click_location = pygame.mouse.get_pos()
#print("Mouse-Up Event -> (%3d, %3d)" % ( click_location[0], click_location[1] ) )
# send the mouse-click location to the correct button-set depending on the state
if ( game_state == GameState.MENU_PLAYQUIT ):
play_quit_buttons.anyClicked( click_location )
elif ( game_state == GameState.MENU_DIFFICULTY ):
difficulty_buttons.anyClicked( click_location )
elif ( game_state == GameState.MENU_LEVELSELECT ):
level_buttons.anyClicked( click_location )
elif ( game_state == GameState.GAME_PLAYING ):
# TODO
pass
elif ( game_state == GameState.GAME_OVER ):
# TODO
pass
###
### Handle all the mouse-click button events
###
elif event.type == ButtonEvent.QUIT:
done = True
elif event.type == ButtonEvent.PLAY:
# user clicked "play", trainsition to next state
game_state = GameState.MENU_DIFFICULTY
elif event.type in [ ButtonEvent.EASY, ButtonEvent.MEDIUM, ButtonEvent.HARD, ButtonEvent.VETERAN ]:
game_state = GameState.MENU_LEVELSELECT
# NOTE: This could be simpler with a dictionary of { event : difficulty-level }
if event.type == ButtonEvent.EASY:
game_difficulty = 1
elif event.type == ButtonEvent.MEDIUM:
game_difficulty = 2
elif event.type == ButtonEvent.HARD:
game_difficulty = 3
elif event.type == ButtonEvent.VETERAN:
game_difficulty = 4
elif event.type in [ ButtonEvent.LEVEL1, ButtonEvent.LEVEL2, ButtonEvent.LEVEL3, ButtonEvent.LEVEL4 ]:
game_state = GameState.GAME_PLAYING
if event.type == ButtonEvent.LEVEL1:
game_level = 1
### etc
#ButtonBox.LoadImage()
###
### Depending on the Game State, render the screen
###
if ( game_state == GameState.MENU_PLAYQUIT ):
gameDisplay.fill( ( 128, 128, 128 ) ) # Temorarily fill with grey to see button locations better
play_quit_buttons.draw( gameDisplay )
elif ( game_state == GameState.MENU_DIFFICULTY ):
gameDisplay.fill( ( 188, 188, 188 ) ) # Temorarily fill with grey to see button locations better
difficulty_buttons.draw( gameDisplay )
elif ( game_state == GameState.MENU_LEVELSELECT ):
gameDisplay.fill( ( 240, 240, 240 ) ) # Temorarily fill with grey to see button locations better
level_buttons.draw( gameDisplay )
elif ( game_state == GameState.GAME_PLAYING ):
gameDisplay.fill( ( 0, 0, 0 ) ) # Temorarily fill with grey to see button locations better
# TODO paint game sprites
elif ( game_state == GameState.GAME_OVER ):
gameDisplay.fill( ( 200, 0, 0 ) ) # Temorarily fill with grey to see button locations better
# TODO paint game-over screen
# TODO play wah-wah-wahhh sound
pygame.display.flip()
clock.tick_busy_loop( 60 ) # Limit FPS
pygame.quit()
#quit()
Честно говоря, Button
и ButtonSet
внимательно следят за внешним видом и использованием классов PyGame Sprite и SpriteGroup .Вероятно, было бы лучше для кода, если бы класс Button
унаследовал pygame.sprite.Sprite
, но я не думаю, что это действительно необходимо для кнопки, и ваша реализация с текстовым растровым изображением на цветном фоне тоже немного отличается.
РЕДАКТИРОВАТЬ:
Если в вашем коде заканчиваются коды событий в пространстве пользователя, рассмотрите возможность использования события группового типа с дополнительным параметром события.Например, событие pygame.KEYDOWN
включает в себя набор параметров для события, таких как .scancode
и .unicode
.
Таким же образом для событий уровня можно было бы иметь одно событие NEW_LEVEL
, и при публикации события добавить числовой индикатор к параметрам события, например:
pygame.event.post( pygame.event.Event( int( ButtonEvent.NEW_LEVEL ), { "level_num" : 1 } ) )
...
# Handle user-input
for event in pygame.event.get():
if ( event.type == ButtonEvent.NEW_LEVEL ):
if ( event.level_num == 1 ):
pass # TODO
elif ( event.level_num == 2 ):
pass # TODO
elif ...