Обработка спрайтов по классам - PullRequest
0 голосов
/ 22 сентября 2019

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

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

class Controller():
    if getattr(sys, 'frozen', False):
        path = os.path.dirname(sys.executable)
    elif __file__:
        path = os.path.dirname(__file__)
    else:
        print('No path has been defined')

    windowWidth = 800
    windowHeight = 600

    def __init__(self):
        self._running = True
        self.sprites = pygame.sprite.Group()
        self.callback = None
    def on_init(self):
        pygame.init()
        pygame.display.set_caption('Ourstory')
        self.clock = pygame.time.Clock()
        self.screen = pygame.display.set_mode((self.windowWidth, self.windowHeight))
        self.menu('MainMenu')
        self._running = True
    def on_event(self, event):
        if event.type == QUIT:
            self._running = False
        elif event.type == TURN:
            self.turn += 1
    def on_loop(self):
        pass
    def on_cleanup(self):
        pygame.quit()
    def on_execute(self):
        if self.on_init() == False:
            self._running = False
        while (self._running):
            self.clock.tick(60)
            pygame.event.pump()
            #self.update_button()
            #self.sprites._update_image(events)
            self.screen.fill(pygame.Color('white'))
            self.sprites.draw(self.screen)
            pygame.display.update()
            keys = pygame.key.get_pressed()
            if (keys[K_ESCAPE]):
                self._running = False
            self.on_loop()
        self.on_cleanup()
    def menu(self, name:str):
            if name == 'MainMenu':
                self.sprites.add(Controller.load_image(self, 'TOC_EarthDawn800600.png', 0, 0))
                #Controller.load_button(self, 'SelectCivilisation', 'Culture3232.png', 'Espionage3232.png', 32, 32, 32, 32, -1, 'Select Civilisation', 'freesansbold.ttf', 8, (0,0,0))
                self.sprites.add(Button('TestButton', (128,128,128), (160,160,160), (0,0,0), pygame.Rect(150, 200, 90, 100), QUIT))#, 'text', 'freesansbold.ttf', 8, (0,0,0)))
            elif name == 'NewGame':
                Controller.load_image(self, 'TOC_EarthDusk800600.png', 0, 0)
            else:
                print('Cannot find menu: ', name)
    def load_image(self, name:str, x:int, y:int, colorkey=None):
        fullname = os.path.join('Images', name)
        try:
            image = pygame.image.load(fullname)
        except:
            pygame.error
            print('Cannot find image: ', name)
        image = image.convert()
        if colorkey is not None:
            if colorkey is -1:
                colorkey = image.get_at((0, 0)) #i.e. top-left pixel
            image.set_colorkey(colorkey, RLEACCEL)
        self.screen.blit(image, (x, y))
        pygame.display.flip()

class Button(pygame.sprite.Sprite):
    def __init__(self, name:str, colourinactive:str, colouractive:str, colouroutline:str, rect, callback, text=None, font='freesansbold.ttf', fontsize=8, textcolour=(0,0,0), imageinactive=None, imageactive=None, colorkey=None):
        super().__init__()
        self.text = text
        temprect = pygame.Rect(0, 0, *rect.size)

        if not imageinactive:
            self.inactive = self._create_image(colourinactive, colouroutline, temprect, text)
            self.active = self._create_image(colouractive, colouroutline, temprect, text)
            self.image = self.inactive
            self.rect = rect
        self.callback = callback
    def _create_image(self, colourinactive:str, colouroutline:str, rect, text=None, font='freesansbold.ttf', fontsize=8, textcolour=(0,0,0)):
        imagesurface = pygame.Surface(rect.size)
        if colouroutline:
            imagesurface.fill(colouroutline)
            imagesurface.fill(colourinactive, rect.inflate(-4, -4))
        else:
            imagesurface.fill(colourinactive)
        if text:
            font = pygame.font.Font(font, fontsize)
            print(type(textcolour))
            textsurface = font.render(text, True, pygame.Color(textcolour))
            textrect = textsurface.get_rect(center=rect.center)
            img.blit(textsurface, textrect)
        return imagesurface
    def _update_image(self, events):
        mouse = pygame.mouse.get_pos()
        active = self.rect.collidepoint(mouse)
        self.image = self.active if active else self.inactive
        for event in events:
            if event.type == pygame.MOUSEBUTTONDOWN and hit:
                self.callback(self)

1) В def menu(self, name:str): я создаю свои меню и даю им фоновое изображение с функцией load_image, которую я объявляю ниже.В настоящее время у меня self.sprites.add перед фоном 'MainMenu', что заставляет его работать на мгновение (я вижу изображение), а затем сразу вылетает:

AttributeError: 'NoneType' object has no attribute 'add_internal'

Если я не поставлю self.sprites.add там, как в случае с меню 'NewGame', код работает, но этот фрагмент кода создает полностью белый фон, так что я не вижу свое фоновое изображение:

self.screen.fill(pygame.Color('white'))
            self.sprites.draw(self.screen)
            pygame.display.update()

Поэтому мне нужно найти способ добавить фоновое изображение, которое я обрабатываю с помощью функции load_image, в группу спрайтов.Я не уверен, как это сделать, что является проблемой 1).

2) Помимо универсальной функции для быстрого создания изображений, я хочу универсальную функцию для быстрого создания кнопок.Это я делаю с class Button(pygame.sprite.Sprite):.Однако, хотя текущий код показывает кнопку, _update_image, похоже, не работает.У меня проблема в том, что я обрабатываю свои события в while (self._running): из class Controller():, и что моя группа спрайтов является частью def __init__(self):, также из class Controller():.Я следовал примеру ленивца, здесь , но для своих целей я не могу (не думаю, что это разумно?) Определять все как часть class Button или просто вне какого-либо класса,Как бы вы решили это?

Я надеюсь, что это понятно.Спасибо большое за помощь!:)

РЕДАКТИРОВАТЬ в ответ на Rabbid76:

Спасибо за подробный ответ!К сожалению, ваш код не работал (кроме дополнительного) в img = self.load_image(self, 'TOC_EarthDawn800600.png', 0, 0))), он выдает мне ошибку:

Traceback (most recent call last):
  line 243, in <module>
    Game.on_execute()
  line 59, in on_execute
    if self.on_init() == False:
  line 47, in on_init
    self.menu('MainMenu')
  line 76, in menu
    background = self.load_image(self, 'TOC_EarthDawn800600.png', 0, 0)
  line 88, in load_image
    fullname = os.path.join('Images', name)
  line 114, in join
    genericpath._check_arg_types('join', path, *paths)
  line 149, in _check_arg_types
    (funcname, s.__class__.__name__)) from None
TypeError: join() argument must be str or bytes, not 'Controller'

Я немного расширил свой код, такой как ниже,но это возвращает меня к этой ошибке: AttributeError: 'pygame.Surface' object has no attribute 'add_internal'

class Controller():
    if getattr(sys, 'frozen', False):
        path = os.path.dirname(sys.executable)
    elif __file__:
        path = os.path.dirname(__file__)
    else:
        print('No path has been defined')

    windowWidth = 800
    windowHeight = 600

    def __init__(self):
        self._running = True
        self.sprites = pygame.sprite.Group()
        self.callback = None
    def on_init(self):
        pygame.init()
        pygame.display.set_caption('Ourstory')
        self.clock = pygame.time.Clock()
        self.screen = pygame.display.set_mode((self.windowWidth, self.windowHeight))
        self.menu('MainMenu')
        self._running = True
    def on_event(self, event):
        if event.type == QUIT:
            self._running = False
        elif event.type == TURN:
            self.turn += 1
    def on_loop(self):
        pass
    def on_cleanup(self):
        pygame.quit()
    def on_execute(self):
        if self.on_init() == False:
            self._running = False
        while (self._running):
            self.clock.tick(60)
            pygame.event.pump()
            #self.update_button()
            #self.sprites._update_image(events)
            self.screen.fill(pygame.Color('white'))
            self.sprites.draw(self.screen)
            pygame.display.update()
            keys = pygame.key.get_pressed()
            if (keys[K_ESCAPE]):
                self._running = False
            self.on_loop()
        self.on_cleanup()
    def menu(self, name:str):
            if name == 'MainMenu':
                background = Image._create_image(self, 'TOC_EarthDawn800600.png', 0, 0)
                self.sprites.add(background)

                #self.sprites.add(Controller.load_image(self, 'TOC_EarthDawn800600.png', 0, 0))
                ##Controller.load_button(self, 'SelectCivilisation', 'Culture3232.png', 'Espionage3232.png', 32, 32, 32, 32, -1, 'Select Civilisation', 'freesansbold.ttf', 8, (0,0,0))
                #self.sprites.add(Button('TestButton', (128,128,128), (160,160,160), (0,0,0), pygame.Rect(150, 200, 90, 100), QUIT))#, 'text', 'freesansbold.ttf', 8, (0,0,0)))
            elif name == 'NewGame':
                Controller.load_image(self, 'TOC_EarthDusk800600.png', 0, 0)
            else:
                print('Cannot find menu: ', name)

class Image(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
    def _create_image(self, name, x, y, colorkey=None):
        self.name = name
        fullname = os.path.join('Images', name)
        try:
            image = pygame.image.load(fullname)
        except:
            pygame.error
            print('Cannot find image: ', name)
        image = image.convert()
        if colorkey is not None:
            if colorkey is -1:
                colorkey = image.get_at((0, 0))  # i.e. top-left pixel
            image.set_colorkey(colorkey, RLEACCEL)
        return image

class Button(pygame.sprite.Sprite):
    def __init__(self, name:str, colourinactive:str, colouractive:str, colouroutline:str, rect, callback, text=None, font='freesansbold.ttf', fontsize=8, textcolour=(0,0,0), imageinactive=None, imageactive=None, colorkey=None):
        super().__init__()
        self.text = text
        temprect = pygame.Rect(0, 0, *rect.size)

        if not imageinactive:
            self.inactive = self._create_image(colourinactive, colouroutline, temprect, text)
            self.active = self._create_image(colouractive, colouroutline, temprect, text)
            self.image = self.inactive
            self.rect = rect
        self.callback = callback
    def _create_image(self, colourinactive:str, colouroutline:str, rect, text=None, font='freesansbold.ttf', fontsize=8, textcolour=(0,0,0)):
        imagesurface = pygame.Surface(rect.size)
        if colouroutline:
            imagesurface.fill(colouroutline)
            imagesurface.fill(colourinactive, rect.inflate(-4, -4))
        else:
            imagesurface.fill(colourinactive)
        if text:
            font = pygame.font.Font(font, fontsize)
            print(type(textcolour))
            textsurface = font.render(text, True, pygame.Color(textcolour))
            textrect = textsurface.get_rect(center=rect.center)
            img.blit(textsurface, textrect)
        return imagesurface
    def _update_image(self, events):
        mouse = pygame.mouse.get_pos()
        active = self.rect.collidepoint(mouse)
        self.image = self.active if active else self.inactive
        for event in events:
            if event.type == pygame.MOUSEBUTTONDOWN and hit:
                self.callback(self)
...