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.
1 Answer 1
Problem is that you have .set_mode()
in Scene.__init__
which deletes and creates window again and this deletes all elements in window and you get black screen for short time.
Games usually don't need to change window's size and .set_mode()
is needed only once - after pygame.init()
.
You can assign it to variable and send it to Scene as parameter.
class Scene:
def __init__(self, name, surface, ...):
self.surface = surface
# ... code
# ---
pygame.init()
screen = pygame.display.set_mode(size=settings["resolution"]["x"], settings["resolution"]["y"])
# ... code
combatscreen = Scene("combatscreen", screen, ...)
i
. For examplefor button in scene.buttons:
instead offor i in scene.buttons:
Button.render
instead ofButton.btnrender
(and laterOtherObject.render
orPlayer.render
and maybe evenScrene.render
) - because it allows to put different objects on list and later runfor
-loop to executerender
for all object.for item in all_objects: item.render()
. Frankly, I would remove prefixbtn
from all variables in classButton
. ie.rect = ...
instead ofbtnrect = ...
- to make similar code in different classes.Button
doesn't change font size then you createFont()
only once - in__init__
- instead of creating it again and again in everyrender
pygame
has 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.center
instead of tuple(self.posx + self.width/2, self.posy + self.heigh/2)