I made my pygame window resizeable by using pygame.RESIZEABLE
in pygame.display.set_mode()
but now the content (buttons) I put on the screen earlier haven't shifted with the resizing which is expected but I cannot figure out a way to successfully implement this. I don't get any error messages thankfully. The only problem is that I am not getting the output I want. If the start button is at 100, 100 out of a screen of 600,600 I want it to be at 200,200 out of the resized screen which for this example I'm taking 1200,1200 for the sake of simplicity. I tried using percentages of the total screen size but that completely messed up the positioning.
import pygame
#Initializing pygane
pygame.init()
#Setting Screen Size
SCREENSIZE = (1000,700)
SCREEN = pygame.display.set_mode(SCREENSIZE, pygame.RESIZABLE)
class Button():
def __init__(self, x, y, image, scale):
...
def draw(self, surface):
...
#create button instances
start_button = Button(100,100,start_img,0.8)
exit_button = Button(250,250,exit_img,0.8)
setting_button = Button(450,75,setting_img, 0.35)
As suggested, it's pretty easy to do this with percentage size and position.
When the Button is first initialised, there is a given position and size. But there is also a window size and position at startup too. So instead of storing an exact x
co-ordinate, instead we also store the relative x
co-ordinate as a factor of the window width.
So say the window is 1000
pixels wide. If your button is positioned in the exact middle, at pixel 500
- the relative position is:
initial_x (500) / initial_width (1000) => 0.5
So we're keeping that relative_x
of 0.5
. Any time we need to re-position the button, the new x
will always be relative_x * window_width
for any window size. Bigger or smaller, it still works.
So say now the window is stretched to 1500
pixels wide. The new x
co-ordinate would be computer as:
relative_x (0.5) * window_width (1500) => 750
Which is obviously still right in the middle. Say the window then shrunk to 300
pixels ~
relative_x (0.5) * window_width (300) => 150
Obviously you need to extend this further for the y
, width
and height
. But that is just more of the exact same thing.
Working example:
Code:
import pygame
WINDOW_WIDTH = 600 # initial size only
WINDOW_HEIGHT= 300
MAX_FPS = 60
WHITE = (255,255,255)
BLACK = ( 0, 0, 0)
RED = (255, 0, 0)
class StretchyButton( pygame.sprite.Sprite ):
""" A Button Sprite that maintains its relative position and size
despite size changes to the underlying window.
(Based on a pyGame Sprite, just to make drawing simpler) """
def __init__( self, window, x, y, image ):
super().__init__()
self.original_image = image.convert_alpha() # re-scaling the same image gets messed-up
self.image = image
self.rect = self.image.get_rect()
self.rect.topleft = ( x, y )
# Calculate the relative size and position, so we can maintain our
# position & proportions when the window size changes
# So "x_percent" is the relative position of the original x co-ord at the original size
window_rect = window.get_rect()
image_rect = image.get_rect()
# The given (x,y) of the button is relative to the initial window size.
# Keep the ratios of the position against window size
self.x_percent = x / window_rect.width
self.y_percent = y / window_rect.height
self.width_percent = image_rect.width / window_rect.width
self.height_percent = image_rect.height / window_rect.height
def reScale( self, window ):
""" When the window size changes, call this to re-scale the button to match """
window_rect = window.get_rect()
# re-calculate the button's position and size to match the new window size
# we calcualted the percentage (relative) positions at startup, so now
# they can just be used to re-position & re-size
self.rect.x = self.x_percent * window_rect.width
self.rect.width = self.width_percent * window_rect.width
self.rect.y = self.y_percent * window_rect.height
self.rect.height = self.height_percent * window_rect.height
# re-scale the button's image too
# always re-scale off an original image, otherwise the image degrades
self.image = pygame.transform.smoothscale( self.original_image, ( self.rect.width, self.rect.height ) )
### I don't like the look of text without some surrounding space
def renderWithBorder( font, text, border=3, foreground=WHITE, background=BLACK ):
""" Render the given text with the given font, but surrounded by a border of pixels """
text = font.render( text, True, foreground, background )
width,height = text.get_rect().size
bigger_surf = pygame.Surface( ( width + border + border, height + border + border ), pygame.SRCALPHA )
bigger_surf.fill( background )
bigger_surf.blit( text, ( border, border ) )
return bigger_surf
###
### MAIN
###
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.RESIZABLE )
pygame.display.set_caption( "Stretchy Buttons" )
font = pygame.font.Font( None, 30 )
# Just a re-square image
red_box = pygame.Surface( ( 64, 64 ), pygame.SRCALPHA )
red_box.fill( RED )
# create a set of buttons
butt0 = StretchyButton( window, 10, 10, red_box )
butt1 = StretchyButton( window, 200, 100, renderWithBorder( font, "The Owl" ) )
butt2 = StretchyButton( window, 200, 150, renderWithBorder( font, "And The Pussycat" ) )
butt3 = StretchyButton( window, 200, 200, renderWithBorder( font, "Went to Sea" ) )
# use a sprite group to hold the buttons, because it's quicker
buttons = pygame.sprite.Group()
buttons.add( [ butt0, butt1, butt2, butt3 ] )
running = True
while running:
clock = pygame.time.Clock() # used to govern the max MAX_FPS
# Handle events
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
running = False
elif ( event.type == pygame.VIDEORESIZE ):
# The window size has changed, re-scale our buttons
#new_width, new_height = event.size
for butt in buttons:
butt.reScale( window )
# paint the window
window.fill( WHITE )
buttons.draw( window )
pygame.display.flip()
clock.tick( MAX_FPS ) # keep the frame-rate sensible, don't waste electricity