1

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.

Rabbid76
212k30 gold badges161 silver badges204 bronze badges
asked Jun 27 at 17:11
4
  • 2
    in for-loops you could use more readable variable instead of i. For example for button in scene.buttons: instead of for i in scene.buttons: Commented Jun 28 at 10:38
  • 1
    often it is useful to have the same names in similar classes - Button.render instead of Button.btnrender (and later OtherObject.render or Player.render and maybe even Screne.render) - because it allows to put different objects on list and later run for-loop to execute render for all object. for item in all_objects: item.render(). Frankly, I would remove prefix btn from all variables in class Button. ie. rect = ... instead of btnrect = ... - to make similar code in different classes. Commented Jun 28 at 10:46
  • 1
    if Button doesn't change font size then you create Font() only once - in __init__ - instead of creating it again and again in every render Commented Jun 28 at 10:51
  • 1
    pygame has special class pygame.Rect() to keep position and size. self.rect = pygameRect(...)` and later you can use it in blit(..., self.rect) and when you check mouse clikc if self.rect.collidepoint(mpos):, and It has tuple self.rect.center instead of tuple (self.posx + self.width/2, self.posy + self.heigh/2) Commented Jun 28 at 10:54

1 Answer 1

3

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, ...)
answered Jun 28 at 10:26
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.