Posts: 480
Threads: 86
Joined: Feb 2018
Oct-22-2020, 03:04 AM
(This post was last modified: Oct-22-2020, 03:04 AM by SheeppOSU.)
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
Posts: 5,150
Threads: 396
Joined: Sep 2016
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:
Posts: 480
Threads: 86
Joined: Feb 2018
Oct-22-2020, 05:28 PM
(This post was last modified: Oct-22-2020, 05:28 PM by SheeppOSU.)
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()
Posts: 5,150
Threads: 396
Joined: Sep 2016
Oct-22-2020, 06:17 PM
(This post was last modified: Oct-22-2020, 06:17 PM by metulburr.)
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:
Posts: 480
Threads: 86
Joined: Feb 2018
Oct-22-2020, 06:46 PM
(This post was last modified: Oct-22-2020, 06:46 PM by SheeppOSU.)
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.
Posts: 480
Threads: 86
Joined: Feb 2018
Oct-22-2020, 08:02 PM
(This post was last modified: Oct-22-2020, 08:03 PM by SheeppOSU.)
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.
Posts: 5,150
Threads: 396
Joined: Sep 2016
Oct-22-2020, 09:07 PM
(This post was last modified: Oct-22-2020, 09:07 PM by metulburr.)
(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:
Posts: 480
Threads: 86
Joined: Feb 2018
Oct-23-2020, 12:13 AM
(This post was last modified: Oct-23-2020, 12:13 AM by SheeppOSU.)
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.
Posts: 480
Threads: 86
Joined: Feb 2018
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?
Posts: 5,150
Threads: 396
Joined: Sep 2016
Oct-23-2020, 11:32 AM
(This post was last modified: Oct-23-2020, 11:33 AM by metulburr.)
(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:
|