Python Forum
[PyGame] Screen is all black and nothing is updating, but I can't figure out why.
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] Screen is all black and nothing is updating, but I can't figure out why.
#1
I laid down the base work for this game, and was doing some tests, however I realized that nothing is updating on the pygame screen. I used some print functions to check and it looks like the lines are getting run to update the window. I'd appreciate any help in pointing out any error that I've made in my code causing this problem.

Main Code:
import pygame
import sys
from PygameFuncs import Button, message_display
from functools import partial

settings = {
    'fps': 60,
    'caption': "PyLine",
}

H = 800
W = 600


class StartScreen:
    def __init__(self, controller):
        self.colors = {
            'background': (255, 255, 255),
        }
        self.objects = {
            Button(rect=[W*0.5-100, H*0.5-150, 200, 300], color=[0, 0, 255], hoverColor=[0, 0, 200],
                   textsize=15, text="Start", textcolor=[0, 0, 0]):
            {
                'scales': [0.5, 0.5, 1/3, 3/8],
                'offsets': [-100, -150, 0, 0],
                'finish_funcs': [],
            },
        }

    def resize(self, screen):
        for obj, info in self.objects.items():
            w = screen.get_width()
            h = screen.get_height()
            scales = info['scales']
            offsets = info['offsets']
            obj.resize([w*scales[0]+offsets[0], h*scales[1]+offsets[1],
                        w*scales[2]+offsets[2], h*scales[3]+offsets[3]])

    def draw(self, screen):
        screen.fill(self.colors['background'])
        for obj in self.objects.keys():
            obj.draw(screen)

    def run(self, controller):
        for obj, info in self.objects.items():
            obj.run(controller.screen, *info['finish_funcs'])


class Controller:
    def __init__(self, settings, startingWindow):
        pygame.init()
        self.screen = pygame.display.set_mode((H, W), pygame.RESIZABLE)
        pygame.display.set_caption(settings['caption'])

        self.currentWindow = startingWindow
        self.running = False
        self.clock = pygame.time.Clock()

        self.settings = settings

    def new_window(self, window):
        self.running = False
        self.currentWindow = window
        self.run()

    def run(self):
        self.running = True
        self.currentWindow = self.currentWindow(self)
        while self.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False
                    break
                if event.type == pygame.VIDEORESIZE:
                    self.screen = pygame.transform.scale(self.screen, (event.w, event.h))
                    self.currentWindow.resize(self.screen)
            self.currentWindow.draw(self.screen)
            self.currentWindow.run(self)
            pygame.display.update()
            self.clock.tick(self.settings['fps'])


if __name__ == '__main__':
    app = Controller(settings, StartScreen)
    try:
        app.run()
    except KeyboardInterrupt:
        print("KeyboardInterrupt")
    pygame.quit()
    sys.exit()
else:
    print("This file must be run directly for the game to display and work correctly.")
Button Class:
class Button:
    def __init__(self, **kwargs):
        self.rect = pygame.Rect(kwargs.get('rect', [0, 0, 100, 100]))
        self.font = pygame.font.SysFont(kwargs.get('textfont', 'arial'), kwargs.get('textsize', 10))
        self.color = kwargs.get('color', [255, 255, 255])
        self.hoverColor = kwargs.get('hoverColor', [200, 200, 200])
        self.textSurf, self.textRect = text_objects(kwargs.get("text", "Hello World"),
                                                    self.font, kwargs.get('textcolor', [0, 0, 0]))
        self.textRect.center = ((self.rect[0] + self.rect[2]/2, self.rect[1] + self.rect[3]/2),)

    def resize(self, rect):
        self.rect = pygame.Rect(rect)
        
    def draw(self, screen, hover=False):
        color = self.color
        if hover:
            color = self.hoverColor
        pygame.draw.rect(screen, color, self.rect)
        screen.blit(self.textSurf, self.textRect)

    def run(self, screen, *args):
        mouse = pygame.mouse.get_pressed()
        if self.rect.collidepoint(pygame.mouse.get_pos()) and mouse[0]:
            self.draw(screen, True)
            for command in args:
                command()
            return True
        return False
Reply
#2
can you make your code fully runnable?
Error:
Traceback (most recent call last): File "test2.py", line 117, in <module> app.run() File "test2.py", line 97, in run self.currentWindow = self.currentWindow(self) File "test2.py", line 50, in __init__ Button(rect=[W*0.5-100, H*0.5-150, 200, 300], color=[0, 0, 255], hoverColor=[0, 0, 200], File "test2.py", line 20, in __init__ self.textSurf, self.textRect = text_objects(kwargs.get("text", "Hello World"), NameError: name 'text_objects' i
Recommended Tutorials:
Reply
#3
Sorry I forgot that the button uses the text_objects function, here it is:
def text_objects(text, font, color):
    textSurface = font.render(text, True, color)
    return textSurface, textSurface.get_rect()
Reply
#4
What is your goal here?

In your button call
    def run(self, controller):
        for obj, info in self.objects.items():
            obj.run(controller.screen, *info['finish_funcs'])
You are only sending the screen and info back, the buttons run does no callback to anything.

                'finish_funcs': [],
Like this will print out as your button is held down
                'finish_funcs': [lambda:print('test')],
Recommended Tutorials:
Reply
#5
I first wanted to see the button on the screen before I added any functionality to the button. My plan was to do something like this
'finish_funcs': [partial(controller.new_window, args=(ClassOfTheNextWindow,))] # I don't quite remember if this is the right way to use the partial function, I just typed this as an example.
I've never used the lambda functionality before, nor do I know how to, I'll consider looking into it soon.
Reply
#6
I decided to bring all my code and test it here to find that it ran just fine, so then the code's not the problem, but I still don't know what is the problem. The first thing that came to mind was uninstalling and reinstalling pygame, but that did nothing to help. I also tried restarting my computer but it changed nothing.
Reply
#7
(Oct-22-2020, 03:04 AM)SheeppOSU Wrote: it looks like the lines are getting run to update the window
(Oct-22-2020, 06:46 PM)SheeppOSU Wrote: I first wanted to see the button on the screen before I added any functionality to the button

What exactly are you trying to run with the button? What issues are you having with the button alone? Your button class is a little odd because it allows a callback function to be ran over and over based on how long you have a mouse button held for example. Normally you want a single occurrence with a button.

I also have a button class tutorial/example here. Notice my get_event method in mine where it only allows the callback function to be ran once.

Here is an example.
import pygame as pg
 
class Button(object):
    def __init__(self,rect,command,**kwargs):
        self.rect = pg.Rect(rect)
        self.command = command
        self.clicked = False
        self.hovered = False
        self.hover_text = None
        self.clicked_text = None
        self.process_kwargs(kwargs)
        self.render_text()
 
    def process_kwargs(self,kwargs):
        settings = {
            "color"             : pg.Color('red'),
            "text"              : None,
            "font"              : None, #pg.font.Font(None,16),
            "call_on_release"   : True,
            "hover_color"       : None,
            "clicked_color"     : None,
            "font_color"        : pg.Color("white"),
            "hover_font_color"  : None,
            "clicked_font_color": None,
            "click_sound"       : None,
            "hover_sound"       : None,
            'border_color'      : pg.Color('black'),
            'border_hover_color': pg.Color('yellow'),
            'disabled'          : False,
            'disabled_color'     : pg.Color('grey'),
            'radius'            : 3,
        }
        for kwarg in kwargs:
            if kwarg in settings:
                settings[kwarg] = kwargs[kwarg]
            else:
                raise AttributeError("{} has no keyword: {}".format(self.__class__.__name__, kwarg))
        self.__dict__.update(settings)
 
    def render_text(self):
        if self.text:
            if self.hover_font_color:
                color = self.hover_font_color
                self.hover_text = self.font.render(self.text,True,color)
            if self.clicked_font_color:
                color = self.clicked_font_color
                self.clicked_text = self.font.render(self.text,True,color)
            self.text = self.font.render(self.text,True,self.font_color)
 
    def get_event(self,event):
        if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
            self.on_click(event)
        elif event.type == pg.MOUSEBUTTONUP and event.button == 1:
            self.on_release(event)
 
    def on_click(self,event):
        if self.rect.collidepoint(event.pos):
            self.clicked = True
            if not self.call_on_release:
                self.function()
 
    def on_release(self,event):
        if self.clicked and self.call_on_release:
            #if user is still within button rect upon mouse release
            if self.rect.collidepoint(pg.mouse.get_pos()):
                self.command()
        self.clicked = False
 
    def check_hover(self):
        if self.rect.collidepoint(pg.mouse.get_pos()):
            if not self.hovered:
                self.hovered = True
                if self.hover_sound:
                    self.hover_sound.play()
        else:
            self.hovered = False
 
    def draw(self,surface):
        color = self.color
        text = self.text
        border = self.border_color
        self.check_hover()
        if not self.disabled:
            if self.clicked and self.clicked_color:
                color = self.clicked_color
                if self.clicked_font_color:
                    text = self.clicked_text
            elif self.hovered and self.hover_color:
                color = self.hover_color
                if self.hover_font_color:
                    text = self.hover_text
            if self.hovered and not self.clicked:
                border = self.border_hover_color
        else:
            color = self.disabled_color
         
        #if not self.rounded:
        #    surface.fill(border,self.rect)
        #    surface.fill(color,self.rect.inflate(-4,-4))
        #else:
        if self.radius:
            rad = self.radius
        else:
            rad = 0
        self.round_rect(surface, self.rect , border, rad, 1, color)
        if self.text:
            text_rect = text.get_rect(center=self.rect.center)
            surface.blit(text,text_rect)
             
             
    def round_rect(self, surface, rect, color, rad=20, border=0, inside=(0,0,0,0)):
        rect = pg.Rect(rect)
        zeroed_rect = rect.copy()
        zeroed_rect.topleft = 0,0
        image = pg.Surface(rect.size).convert_alpha()
        image.fill((0,0,0,0))
        self._render_region(image, zeroed_rect, color, rad)
        if border:
            zeroed_rect.inflate_ip(-2*border, -2*border)
            self._render_region(image, zeroed_rect, inside, rad)
        surface.blit(image, rect)
 
 
    def _render_region(self, image, rect, color, rad):
        corners = rect.inflate(-2*rad, -2*rad)
        for attribute in ("topleft", "topright", "bottomleft", "bottomright"):
            pg.draw.circle(image, color, getattr(corners,attribute), rad)
        image.fill(color, rect.inflate(-2*rad,0))
        image.fill(color, rect.inflate(0,-2*rad))
         
    def update(self):
        #for completeness
        pass
         
if __name__ == '__main__':
    pg.init()
    screen = pg.display.set_mode((600,400))
    screen_rect = screen.get_rect()
    done = False
 
    def print_on_press():
        print('button pressed')
         
    settings = {
        "clicked_font_color" : (0,0,0),
        "hover_font_color"   : (205,195, 100),
        'font'               : pg.font.Font(None,16),
        'font_color'         : (255,255,255),
        'border_color'       : (0,0,0),
    }
 
    btn = Button(rect=(10,10,105,25), command=print_on_press, text='Press Me', **settings)
 
    while not done:
        mouse = pg.mouse.get_pos()
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            btn.get_event(event)
        btn.draw(screen)
        pg.display.update()
Recommended Tutorials:
Reply
#8
Quote:What exactly are you trying to run with the button? What issues are you having with the button alone? Your button class is a little odd because it allows a callback function to be ran over and over based on how long you have a mouse button held for example. Normally you want a single occurrence with a button.
I realized this while testing it earlier and that's not an intended design. I just now edited the code so the run function looks like this:
    def run(self, screen, *args):
        mouse = pygame.mouse.get_pressed()
        over = self.rect.collidepoint(pygame.mouse.get_pos())
        if over and mouse[0]:
            if not self.press:
                self.press = True
            self.draw(screen, True)
        elif over and not mouse[0] and self.press:
            self.press = False
            self.draw(screen)
            for command in args:
                command()
            return True
        elif not over and not mouse[0] and self.press:
            self.press = False
        return False
and during the __init__ self.press is defined.
I also noticed quite a few mistakes in my code involving the rects, so here's the full updated code:
import pygame
import sys
from PygameFuncs import Button, message_display
from functools import partial

settings = {
    'fps': 60,
    'caption': "PyLine",
}

H = 600
W = 800


class StartScreen:
    def __init__(self, controller):
        self.colors = {
            'background': (255, 255, 255),
        }
        self.objects = {
            Button(rect=[W*0.5-(W*3/16), H*0.5-(H/6), W*3/8, H/3], color=[0, 0, 255], hoverColor=[0, 0, 200],
                   textsize=15, text="Start", textcolor=[0, 0, 0]):
            {
                'scales': [0.5, 0.5, 3 / 8, 1 / 3],
                'offsets': [-W*3/16, -H/6, 0, 0],
                'finish_funcs': [],
            },
        }

    def resize(self, screen):
        for obj, info in self.objects.items():
            w = screen.get_width()
            h = screen.get_height()
            scales = info['scales']
            offsets = info['offsets']
            obj.resize([w*scales[0]+offsets[0], h*scales[1]+offsets[1],
                        w*scales[2]+offsets[2], h*scales[3]+offsets[3]])

    def draw(self, screen):
        screen.fill(self.colors['background'])
        for obj in self.objects.keys():
            obj.draw(screen)

    def run(self, controller):
        for obj, info in self.objects.items():
            obj.run(controller.screen, *info['finish_funcs'])


class Controller:
    def __init__(self, settings, startingWindow):
        pygame.init()
        self.screen = pygame.display.set_mode((W, H), pygame.RESIZABLE)
        pygame.display.set_caption(settings['caption'])

        self.currentWindow = startingWindow
        self.running = False
        self.clock = pygame.time.Clock()

        self.settings = settings

    def new_window(self, window):
        self.running = False
        self.currentWindow = window
        self.run()

    def run(self):
        self.running = True
        self.currentWindow = self.currentWindow(self)
        while self.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False
                    break
                if event.type == pygame.VIDEORESIZE:
                    self.screen = pygame.transform.scale(self.screen, (event.w, event.h))
                    self.currentWindow.resize(self.screen)
            self.currentWindow.draw(self.screen)
            self.currentWindow.run(self)
            pygame.display.update()
            self.clock.tick(self.settings['fps'])


if __name__ == '__main__':
    app = Controller(settings, StartScreen)
    try:
        app.run()
    except KeyboardInterrupt:
        print("KeyboardInterrupt")
    pygame.quit()
    sys.exit()
else:
    print("This file must be run directly for the game to display and work correctly.")
I planned on running the new_window function to start up the next window with the button. With the button alone I am not having any issues, except that maybe there are some bad habits in my code which is making an inefficient button. The main problem I'm having is my pygame window being completely black when I run it, but running just fine when I use an online source for running pygame.
Reply
#9
I figured out that this line was causing it:
if event.type == pygame.VIDEORESIZE:
    self.screen = pygame.transform.scale(self.screen, (event.w, event.h))
    self.currentWindow.resize()
When the window is resized, the screen object still has the same width and height. So what is the best way to change it's height and width variables without messing up the screen like so?
Reply
#10
(Oct-23-2020, 12:13 AM)SheeppOSU Wrote: The main problem I'm having is my pygame window being completely black when I run it, but running just fine when I use an online source for running pygame.
Im not sure what you mean. When i run your code i see the button. I do see that when you resize the screen you have not accounted for aspect ratio.
I would look at @Mekire repos for an example of retaining aspect ratio. You have to constantly update the screen's dimensions. The best way to do this is the create a screen pygame rect that is updated with the screens dimensions. Then apply all objects within that rect. Its been a long time since i did pygame so i could not make a specific example anymore.

Also can you please make a runnable example next time so people can copy and paste in one shot to run your code instead of piecing it together. Ill do that here.

import pygame
import sys
#from PygameFuncs import Button, message_display
from functools import partial
 
settings = {
    'fps': 60,
    'caption': "PyLine",
}
 
H = 600
W = 800

def text_objects(text, font, color):
    textSurface = font.render(text, True, color)
    return textSurface, textSurface.get_rect()

class Button:
    def __init__(self, **kwargs):
        self.rect = pygame.Rect(kwargs.get('rect', [0, 0, 100, 100]))
        self.font = pygame.font.SysFont(kwargs.get('textfont', 'arial'), kwargs.get('textsize', 10))
        self.color = kwargs.get('color', [255, 255, 255])
        self.hoverColor = kwargs.get('hoverColor', [200, 200, 200])
        self.textSurf, self.textRect = text_objects(kwargs.get("text", "Hello World"),
                                                    self.font, kwargs.get('textcolor', [0, 0, 0]))
        self.textRect.center = ((self.rect[0] + self.rect[2]/2, self.rect[1] + self.rect[3]/2),)
 
    def resize(self, rect):
        self.rect = pygame.Rect(rect)
         
    def draw(self, screen, hover=False):
        color = self.color
        if hover:
            color = self.hoverColor
        pygame.draw.rect(screen, color, self.rect)
        screen.blit(self.textSurf, self.textRect)
 
    def run(self, screen, *args):
        mouse = pygame.mouse.get_pressed()
        if self.rect.collidepoint(pygame.mouse.get_pos()) and mouse[0]:
            self.draw(screen, True)
            for command in args:
                command()
            return True
        return False
 
 
class StartScreen:
    def __init__(self, controller):
        self.colors = {
            'background': (255, 255, 255),
        }
        self.objects = {
            Button(rect=[W*0.5-(W*3/16), H*0.5-(H/6), W*3/8, H/3], color=[0, 0, 255], hoverColor=[0, 0, 200],
                   textsize=15, text="Start", textcolor=[0, 0, 0]):
            {
                'scales': [0.5, 0.5, 3 / 8, 1 / 3],
                'offsets': [-W*3/16, -H/6, 0, 0],
                'finish_funcs': [],
            },
        }
 
    def resize(self, screen):
        for obj, info in self.objects.items():
            w = screen.get_width()
            h = screen.get_height()
            scales = info['scales']
            offsets = info['offsets']
            obj.resize([w*scales[0]+offsets[0], h*scales[1]+offsets[1],
                        w*scales[2]+offsets[2], h*scales[3]+offsets[3]])
 
    def draw(self, screen):
        screen.fill(self.colors['background'])
        for obj in self.objects.keys():
            obj.draw(screen)
 
    def run(self, controller):
        for obj, info in self.objects.items():
            obj.run(controller.screen, *info['finish_funcs'])
 
 
class Controller:
    def __init__(self, settings, startingWindow):
        pygame.init()
        self.screen = pygame.display.set_mode((W, H), pygame.RESIZABLE)
        pygame.display.set_caption(settings['caption'])
 
        self.currentWindow = startingWindow
        self.running = False
        self.clock = pygame.time.Clock()
 
        self.settings = settings
 
    def new_window(self, window):
        self.running = False
        self.currentWindow = window
        self.run()
 
    def run(self):
        self.running = True
        self.currentWindow = self.currentWindow(self)
        while self.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False
                    break
                if event.type == pygame.VIDEORESIZE:
                    self.screen = pygame.transform.scale(self.screen, (event.w, event.h))
                    self.currentWindow.resize(self.screen)
            self.currentWindow.draw(self.screen)
            self.currentWindow.run(self)
            pygame.display.update()
            self.clock.tick(self.settings['fps'])
 
 
if __name__ == '__main__':
    app = Controller(settings, StartScreen)
    try:
        app.run()
    except KeyboardInterrupt:
        print("KeyboardInterrupt")
    pygame.quit()
    sys.exit()
else:
    print("This file must be run directly for the game to display and work correctly.")
Recommended Tutorials:
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  what's not working? black window codeweak 3 721 Dec-07-2023, 02:32 AM
Last Post: Benixon
  [PyGame] When I hit the space bar from the home screen, it sends me to the game over screen JesusisKing 1 953 Apr-30-2023, 10:11 PM
Last Post: deanhystad

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020