I have different screens/menus set as instances of the Scene class. Whenever I want to switch screens, my gameloop checks for a button click, and if successful, it calls a function that defines the components of a screen and combines it into a Scene instance. However, when I switch screens a black screen is rendered for 1 frame. Through a series of wait commands, I've found that the line
combatscreen = Scene("combatscreen", settings["resolution"]["x"], settings["resolution"]["y"], [50, 50, 50], [atkbtn, backbtn])
is responsible, though I'm not sure how, as no display update is called.
This is the necessary code to run the window and trigger the bug:
import pygame
import sys
import types
import json
pygame.init()
with open("settings.json") as file:
settings = json.load(file)
class Scene:
all_scenes = []
def __init__(self, name, sizex, sizey, bgcolor, buttons=[], textboxes=[]):
self.name = name
self.sizex = sizex
self.sizey = sizey
self.bgcolor = bgcolor
self.buttons = buttons
self.textboxes = textboxes
self.active = False
Scene.all_scenes.append(self)
self.surface = pygame.display.set_mode(size=[sizex, sizey])
class Button:
def __init__(self, dest, destargs, posx, posy, width, height, text, color=[200,200,200], textcolor=[0,0,0], textsize=40):
self.dest = dest
self.destargs = destargs
self.posx = posx
self.posy = posy
self.width = width
self.height = height
self.text = text
self.color = color
self.textcolor = textcolor
self.textsize = textsize
def btnclick(self, prev_scene):
mpos = pygame.mouse.get_pos()
clicks = pygame.mouse.get_just_released()
btnbounds = pygame.draw.rect(self.surface, self.color, (self.posx, self.posy, self.width, self.height))
# Checks if LMB was clicked within the bounds of the button
if btnbounds.collidepoint(mpos):
if clicks[0]:
if isinstance(self.dest, types.FunctionType):
# Deactivates current scene and runs the function to define and activate the destination scene
prev_scene.active = False
self.dest()
else:
pass
def btnrender(self):
mpos = pygame.mouse.get_pos()
btnrect = pygame.draw.rect(self.surface, self.color, (self.posx, self.posy, self.width, self.height))
if btnrect.collidepoint(mpos):
# Makes the button lighter when hovered
self.color[0] += 20
self.color[1] += 20
self.color[2] += 20
pygame.draw.rect(self.surface, self.color, (self.posx, self.posy, self.width, self.height))
# Resets button color
self.color[0] -= 20
self.color[1] -= 20
self.color[2] -= 20
btnlabel = pygame.font.Font(filename="Assets\\Fonts\\Adhynatha.otf", size=self.textsize).render(self.text, antialias=True, color=self.textcolor)
labelrect = btnlabel.get_rect(center=(self.posx+(self.width/2), self.posy+(self.height/2)))
self.surface.blit(btnlabel, labelrect)
def gameloop():
scene = None
while True:
# Find which scene is active
for i in Scene.all_scenes:
if i.active:
scene = i
# Apply the active scene's surface to the buttons
for i in scene.buttons:
i.surface = scene.surface
# Set the background color and render buttons
scene.surface.fill(scene.bgcolor)
for i in scene.buttons:
i.btnrender()
# Update the screen and restart the loop
pygame.display.flip()
# Pump the event queue
for event in pygame.event.get():
# Game close check
if event.type == pygame.QUIT:
sys.exit()
pygame.quit()
# Check if a button has been clicked this frame
for i in scene.buttons:
i.btnclick(scene)
pygame.time.Clock().tick(60)
def startscreen():
startbtn = Button(combatscreen, None, 100, 100, 300, 100, "Start", [30, 30, 30], "white")
cnfgbtn = Button("settingsmenu", None, settings["resolution"]["x"]-400, 100, 300, 100, "Settings", [30, 30, 30], "white")
# PROBLEM LINE
startscreen = Scene("startscreen", settings["resolution"]["x"], settings["resolution"]["y"], [100, 100, 100], [startbtn, cnfgbtn])
startscreen.active = True
def combatscreen():
atkbtn = Button("attack_select", None, 100, 500, 300, 100, "Combat", [100, 100, 100], textcolor="red")
backbtn = Button(startscreen, None, settings["resolution"]["x"]-100, 0, 100, 50, "Back", textsize=20, textcolor="Black")
# PROBLEM LINE
combatscreen = Scene("combatscreen", settings["resolution"]["x"], settings["resolution"]["y"], [50, 50, 50], [atkbtn, backbtn])
combatscreen.active = True
startscreen()
gameloop()
I've tried reordering various events in the gameloop, but because the issue supposedly comes from an instance creation in a separate function, it hasn't changed anything.
i. For examplefor button in scene.buttons:instead offor i in scene.buttons:Button.renderinstead ofButton.btnrender(and laterOtherObject.renderorPlayer.renderand maybe evenScrene.render) - because it allows to put different objects on list and later runfor-loop to executerenderfor all object.for item in all_objects: item.render(). Frankly, I would remove prefixbtnfrom all variables in classButton. ie.rect = ...instead ofbtnrect = ...- to make similar code in different classes.Buttondoesn't change font size then you createFont()only once - in__init__- instead of creating it again and again in everyrenderpygamehas special classpygame.Rect()to keep position and size. self.rect = pygameRect(...)` and later you can use it inblit(..., self.rect)and when you check mouse clikcif self.rect.collidepoint(mpos):, and It has tupleself.rect.centerinstead of tuple(self.posx + self.width/2, self.posy + self.heigh/2)