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
Related
I should point out that I'm a beginner with PyGame. I have made a program that displays some simple graphics on the screen using PyGame. It blits every graphic on a dummy surface and the dummy surface gets scaled and blit to a 'real' surface that gets displayed on the screen in the end. This allows the program to have a resizable window without messing the graphics and UI.
I have also made my own 'Button' class that allows me to draw clickable buttons on the screen. Here it is:
import pygame
pygame.font.init()
dfont = pygame.font.Font('font/mfdfont.ttf', 64)
#button class button(x, y, image, scale, rot, text_in, color, xoff, yoff)
class Button():
def __init__(self, x, y, image, scale = 1, rot = 0, text_in = '', color = 'WHITE', xoff = 0, yoff = 0):
self.xoff = xoff
self.yof = yoff
self.x = x
self.y = y
self.scale = scale
width = image.get_width()
height = image.get_height()
self.image = pygame.transform.rotozoom(image, rot, scale)
self.text_in = text_in
self.text = dfont.render(self.text_in, True, color)
self.text_rect = self.text.get_rect(center=(self.x +width/(2/scale) + xoff, self.y + height/(2/scale) + yoff))
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
self.clicked = False
def draw(self, surface):
action = False
#get mouse position
pos = pygame.mouse.get_pos()
#check mouseover and clicked conditions
if self.rect.collidepoint(pos):
if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False:
self.clicked = True
action = True
if pygame.mouse.get_pressed()[0] == 0:
self.clicked = False
#draw button on screen
surface.blit(self.image, (self.rect.x, self.rect.y))
surface.blit(self.text, self.text_rect)
return action
When I need to draw one of these buttons on the screen I firstly define it like this:
uparrow = button.Button(128, 1128, arrow_img, 0.5, 0, "SLEW", WHITE, 0, 128)
Then I call it's draw function like this:
if uparrow.draw(screen):
print('UP')
It works reasonably well when drawing it to a surface that doesn't get scaled. This is the problem. When I scale the dummy surface that it gets drawn to, the button's image and text scale just fine but it's collider does not. So when I click on it nothing happens, but if I click on the location of the screen the button would have been on the unscaled dummy surface it works.
Just for context, the dummy surface is 2048x1024 and the 'real' surface is much smaller, starting at 1024x512 and going up and down however the user resizes the window. The game maintains a 2:1 aspect ratio though, so any excess pixels in the game window are black. You can see this in the screenshot below:
Above is a screenshot of the game window. You can see the 'NORM' button at the top of the game screen, and the red box that roughly represents the same 'NORM' button's actual collider. It's basically where it would be on the dummy surface.
(I have previously posted a question on somewhat the same problem as this one, but at that time I didn't know the colliders actually worked and I thought my clicks just didn't register on the buttons, which is not the case).
I'd like to know what part of my button class causes this and how it should be refactored to fix this issue. Alternatively, if you think it's caused by my double surface rendering technique or anything else really, please do point me in the right direction.
In your setup you draw the buttons on an surface, scale the surface and blit that surface on the display. So you do something like the following:
dummy_surface = pygame.Surface((dummy_width, dummy_height)
while True:
# [...]
scaled_surface = pygame.transform.scale(dummy_surface, (scaled_width, scaled_height))
screen.blit(scaled_surface, (offset_x, offset_y))
For click detection to work on the original buttons, you must scale the position of the mouse pointer by the reciprocal scale and shift the mouse position by the inverse offset:
def draw(self, surface):
action = False
# get mouse position
pos = pygame.mouse.get_pos()
scale_x = scaled_width / dummy_surface.get_width()
scale_y = scaled_height / dummy_surface.get_height()
mx = int((pos[0] - offset_x) / scale_x)
my = int((pos[1] - offset_y) / scale_y)
pos = (mx, my)
# [...]
I am trying to make a tic-tac-toe game with pygame. An important thing I want is being able to make my images (eg. X and O) slightly translucent for when my user is only hovering over a grid tile. I also use opacity to visually show whose turn it is.
This is what I have tried:
x_tile = pygame.image.load('x_tile').convert()
x_tile.set_alpha(100)
This works fine when I'm blitting x_tile directly onto the display like this:
# This is for simplicity's sake. The actual blit process is all being done in an infinite loop
screen = pygame.display.set_mode((300, 300))
screen.blit(x_file, x_file.get_rect())
But my game is using another image that represents the grid, and that is what I'm blitting onto. So I'm blitting this board onto the display, then blitting the actual X and O tiles on the board.
screen = pygame.display.set_mode((300, 300))
screen.blit(board, board_rect)
board.blit(x_tile, x_tile.get_rect(center=grid[0].center)) # I have a list of Rects that make a grid on the board image. grid[0] is the top left
When I do it that way, x_tile.set_alpha(100) seems to have no effect and I don't know what to do.
Edit: I am using pygame 2.0.1. I'm on Windows 10.
Here is the entire code
import os
import pygame
from pygame.locals import *
# Game constants
WIN_SIZE = WIN_WIDTH, WIN_HEIGHT = 800, 600
BLACK = 0, 0, 0
WHITE = 255, 255, 255
RED = 255, 0, 0
BLUE = 0, 0, 255
# Game functions
class NoneSound:
"""dummy class for when pygame.mixer did not init
and there is no sound available"""
def play(self): pass
def load_sound(file):
"""loads a sound file, prepares it for play"""
if not pygame.mixer:
return NoneSound()
music_to_load = os.path.join('sounds', file)
try:
sound = pygame.mixer.Sound(music_to_load)
except pygame.error as message:
print('Cannot load following sound:', music_to_load)
raise SystemExit(message)
return sound
def load_image(file, colorkey=None, size=None):
"""loads image into game"""
image_to_load = os.path.join('images', file)
try:
image = pygame.image.load(image_to_load).convert()
except pygame.error as message:
print('Cannot load following image:', image_to_load)
raise SystemExit(message)
if colorkey is not None:
if colorkey == -1:
colorkey = image.get_at((0, 0))
image.set_colorkey(colorkey, RLEACCEL)
if size is not None:
image = pygame.transform.scale(image, size)
return image
# Game class
class TTTVisual:
"""Controls game visuals"""
def __init__(self, win: pygame.Surface):
self.win = win
# Load in game images
self.board = load_image('board.png', size=(600, 450), colorkey=WHITE)
self.x_tile = load_image('X_tile.png', size=(100, 100), colorkey=BLACK)
self.o_tile = load_image('O_tile.png', size=(100, 100), colorkey=BLACK)
# Translucent for disabled looking tile
self.x_tile_trans = self.x_tile.copy()
self.o_tile_trans = self.o_tile.copy()
self.x_tile_trans.set_alpha(100)
self.o_tile_trans.set_alpha(100)
# Used to let user know whose turn it is
self.x_turn = pygame.transform.scale(self.x_tile, (50, 50))
self.o_turn = pygame.transform.scale(self.o_tile, (50, 50))
self.x_turn_trans = pygame.transform.scale(self.x_tile_trans, (50, 50))
self.o_turn_trans = pygame.transform.scale(self.o_tile_trans, (50, 50))
self.get_rects()
self.grid = self.setup_grid()
def get_rects(self):
"""Creates coords for some visual game assets"""
self.board_rect = self.board.get_rect(
center=self.win.get_rect().center)
self.x_turn_rect = self.x_turn.get_rect(top=10, left=10)
self.o_turn_rect = self.o_turn.get_rect(top=10, left=WIN_WIDTH-60)
def setup_grid(self):
grid = []
left = 0
top = 150
row = 0
for i in range(9):
if (i != 0) and (i % 3 == 0):
row += 1
left = 0
grid.append(pygame.Rect(left, row*top, 200, 150))
left += 200
return grid
def update_turn_status(self):
"""Updates the X and O tiles on the top left and right to
let user know whose turn it is"""
self.win.blits((
(self.x_turn_trans, self.x_turn_rect),
(self.o_turn, self.o_turn_rect)
))
def update_grid(self):
"""Updates board"""
self.win.blit(self.board, self.board_rect)
# Here is where you could change board to win and see that the tile changes in opacity
self.board.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=self.grid[0].center))
def update(self):
self.win.fill(WHITE)
self.update_turn_status()
self.update_grid()
pygame.display.flip()
def main():
pygame.init()
win = pygame.display.set_mode(WIN_SIZE)
tttvisual = TTTVisual(win)
tttfunc = TTTFunc(tttvisual)
clock = pygame.time.Clock()
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == QUIT:
running = False
tttvisual.update()
pygame.quit()
if __name__ == "__main__":
main()
The issue is caused by the line:
self.board.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=self.grid[0].center))
You don't blit the image on the display Surface, but on the self.board Surface. When a Surface is blit, it is blended with the target. When you draw on a Surface, it changes permanently. Since you do that over and over again, in every frame, the source Surface appears to by opaque. When you decrease the alpha value (e.g. self.x_tile_trans.set_alpha(5)), a fade in effect will appear.
Never draw on an image Surface. Always draw on the display Surface. Cleat the display at begin of a frame. Draw the entire scene in each frame and update the display once at the end of the frame.
class TTTVisual:
# [...]
def update_grid(self):
"""Updates board"""
self.win.blit(self.board, self.board_rect)
# Here is where you could change board to win and see that the tile changes in opacity
x, y = self.grid[0].center
x += self.board_rect.x
y += self.board_rect.y
self.win.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=(x, y)))
The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()
How can I get the exact location of a collision between two sprites?
And if there are multiple pixels overlapping, maybe getting a list of the pixels location.
I want to spawn particles at that point and would need it for other functions too.
Pseudo code:
obj1.rect..x += obj1.velocity
collision = collide(obj1, obj2):
if collision:
?
return collision_location
My code
def _bullet_collision(self):
for bullet in self.bullets:
hits = pygame.sprite.spritecollide(bullet, self.aliens, False, pygame.sprite.collide_mask)
if hits:
hit_str = 11
for alien in hits:
x, y = alien.x, alien.y
spawn_pos = (x, y)
self._hit_enemy(alien, x, y, hit_str)
bullet.bullet_hit(spawn_pos, hit_str)
Based on the answer: https://stackoverflow.com/a/57023183/1730895
It's fairly easy to calculate the intersection of the rectangles:
def rectIntersection( rect1, rect2 ):
""" Given two pygame.Rect intersecting rectangles, return the overlap rect """
left = max( rect1.left, rect2.left )
width = min( rect1.right, rect2.right ) - left
top = max( rect1.top, rect2.top )
height= min( rect1.bottom, rect2.bottom ) - top
return pygame.Rect( left, top, width, height )
So use pygame.rect.colliderect() (doco) first to determine if there's actually any overlap. Obviously this gives you an overlapping region, so to answer your question, it could be every pixel within this rectangular area. But perhaps you could just take the centroid for use as a "happening at" point.
import pygame
# Window size
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
WINDOW_SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
WINDOW_MAXFPS = 60
# Colours
DARK_BLUE = ( 3, 5, 54)
RED = (255, 0, 0)
YELLOW = (200, 200, 20)
GREEN = ( 20, 200, 20)
def rectIntersection( rect1, rect2 ):
""" Given two pygame.Rect intersecting rectangles, calculate the rectangle of intersection """
left = max( rect1.left, rect2.left )
width = min( rect1.right, rect2.right ) - left
top = max( rect1.top, rect2.top )
height= min( rect1.bottom, rect2.bottom ) - top
return pygame.Rect( left, top, width, height )
### Initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption("Rect-Rect Intersection")
central_rect = pygame.Rect( 175, 175, 50, 50 )
player_rect = pygame.Rect( 0, 0, 30, 30 )
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.MOUSEMOTION ): # move the player's rectangle with the mouse
mouse_pos = pygame.mouse.get_pos()
player_rect.center = mouse_pos
# Update the window
window.fill( DARK_BLUE )
pygame.draw.rect( window, RED, central_rect, 1 ) # centre rect
pygame.draw.rect( window, YELLOW, player_rect, 1 ) # player's mouse rect
# IFF the rectangles overlap, paint the overlap
if ( player_rect.colliderect( central_rect ) ):
overlap_rect = rectIntersection( player_rect, central_rect )
pygame.draw.rect( window, GREEN, overlap_rect )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop( WINDOW_MAXFPS )
pygame.quit()
If pixel-perfect collision is desired, use Surface.subsurface() with the overlap rectangle's co-ordinates on the bitmap mask of both sprites. This gives you two bit-masks (maskA and maskB) which you know overlap somehow.
I can't think of a fast way to find the exact pixels of overlap between these two sub-sections. However it's quite easy to iterate through each of the mask-pixels, finding maskA & maskB pairs that are both "on". Obviously you need to convert from screen co-ordinates to sprite co-ordinates during this step, but this is just a subtraction of the sprite's current screen x and y.
What you really want is the "edge" of the bitmap-intersection, but that's another question.
I´m trying to draw a ship on the bottom-right of the screen, but it´s not appearing on the window! Coordinates seem to be off on X, Y by approximately 50 points. No matter what kind of resolution is set through pygame.display.set_mode(), the window is always smaller than the defined dimensions ( by 50 ).
External FULL HD screen is connected to the laptop through HDMI, but disconnecting it had no effect. Using Windows 10, Python 3.6.2 and Pygame 1.9.3.
Using "centerx", "bottom" to display the ship
Same as above, but substracting both "centerx" and "bottom" by 50.
import sys
import pygame
def main():
#Initialize the screen.
pygame.init()
screen = pygame.display.set_mode( ( 1024, 768 ) )
screen_rect = screen.get_rect()
bg_color = ( 235, 235, 235 )
# Load the ship surface, get its rect.
ship_image = pygame.image.load( "images/ship.bmp" )
ship_rect = ship_image.get_rect()
# TRYING TO POSITION THE SHIP TO THE BOTTOM-RIGHT OF THE SCREEN.
screen_bottom_right = screen_rect.centerx, screen_rect.bottom
while True:
for event in pygame.event.get():
if ( event == "QUIT" ):
sys.exit()
# Redraw the screen.
screen.fill( bg_color )
# Blit the ship´s image.
screen.blit( ship_image, ( screen_bottom_right ) )
pygame.display.flip()
main()
Tried searching for answers, but none of them had worked / explicitly mentioned this issue. Tutorials, which used the code didn´t substract the X/Y coordinates to obtain exactly positioned image. "0, 0" as rect drawing position works flawlessly. The bottom-right suffers from the above-mentioned issue.
Pygame blits the image so that its top left corner is positioned at the coordinates that you passed. So by passing the screen bottom as the y-coord you're telling pygame to draw the ship below the screen. To fix this you can assign the bottomright attribute of the screen_rect to the bottomright of the ship_rect and then just blit the image at the ship_rect.
import sys
import pygame
def main():
pygame.init()
screen = pygame.display.set_mode((1024, 768))
screen_rect = screen.get_rect()
bg_color = (235, 235, 235)
ship_image = pygame.Surface((40, 50))
ship_image.fill((20, 10, 100))
ship_rect = ship_image.get_rect()
ship_rect.bottomright = screen_rect.bottomright
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill(bg_color)
screen.blit(ship_image, ship_rect)
pygame.display.flip()
main()
pygame.Rects have a lot of other attributes that you can use as well, but keep in mind that only the topleft coords are used as the blit position:
x,y
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height
w,h
I was wondering how I would go about scaling the size of images in pygame projects to the resolution of the screen. For example, envisage the following scenario assuming windowed display mode for the time being; I assume full screen will be the same:
I have a 1600x900 background image which of course displays natively in a 1600x900 window
In a 1280x720 window I can obviously just scale this images' rect to 1280x720
What happens, however if I need to add, say a 300x300 px image at x,y 1440,860 (example sizes) that is sized to fit with the original 1600x900 background? Of course for the 1600x900 I can of course use the image natively but what about the smaller/larger window sizes?
How do I scale images to the window size and then position them accordingly? I guess there must be a REALLY easy automated method but right now I can't figure it out.
You can scale the image with pygame.transform.scale:
import pygame
picture = pygame.image.load(filename)
picture = pygame.transform.scale(picture, (1280, 720))
You can then get the bounding rectangle of picture with
rect = picture.get_rect()
and move the picture with
rect = rect.move((x, y))
screen.blit(picture, rect)
where screen was set with something like
screen = pygame.display.set_mode((1600, 900))
To allow your widgets to adjust to various screen sizes,
you could make the display
resizable:
import os
import pygame
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((500, 500), HWSURFACE | DOUBLEBUF | RESIZABLE)
pic = pygame.image.load("image.png")
screen.blit(pygame.transform.scale(pic, (500, 500)), (0, 0))
pygame.display.flip()
while True:
pygame.event.pump()
event = pygame.event.wait()
if event.type == QUIT:
pygame.display.quit()
elif event.type == VIDEORESIZE:
screen = pygame.display.set_mode(
event.dict['size'], HWSURFACE | DOUBLEBUF | RESIZABLE)
screen.blit(pygame.transform.scale(pic, event.dict['size']), (0, 0))
pygame.display.flip()
If you scale 1600x900 to 1280x720 you have
scale_x = 1280.0/1600
scale_y = 720.0/900
Then you can use it to find button size, and button position
button_width = 300 * scale_x
button_height = 300 * scale_y
button_x = 1440 * scale_x
button_y = 860 * scale_y
If you scale 1280x720 to 1600x900 you have
scale_x = 1600.0/1280
scale_y = 900.0/720
and rest is the same.
I add .0 to value to make float - otherwise scale_x, scale_y will be rounded to integer - in this example to 0 (zero) (Python 2.x)
Scaling the background to the size of the window can easily be done with pygame.transform.scale() lor smoothscale. e.g.:
import pygame
pygame.init()
window = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
background = pygame.image.load('sky.png').convert()
background = pygame.transform.smoothscale(background, window.get_size())
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
pygame.display.flip()
pygame.quit()
exit()
However, this does not take into account the aspect ratio of the background. To fit the window into the background, you need to compare the width and height ratio and scale the image by the minimum ratio.
The following function scales an image to the desired size, but retains the aspect ratio. The function returns the scaled image and a rectangle indicating the position of the scaled image in the center of the area:
def transformScaleKeepRatio(image, size):
iwidth, iheight = image.get_size()
scale = min(size[0] / iwidth, size[1] / iheight)
new_size = (round(iwidth * scale), round(iheight * scale))
scaled_image = pygame.transform.smoothscale(image, new_size)
image_rect = scaled_image.get_rect(center = (size[0] // 2, size[1] // 2))
return scaled_image, image_rect
If you want to fill the entire window with the background, keeping the aspect ratio but cropping the sides of the background, just replace min with max.
scale = min(size[0] / iwidth, size[1] / iheight)
scale = max(size[0] / iwidth, size[1] / iheight)
Minimal example
import pygame
def transformScaleKeepRatio(image, size):
iwidth, iheight = image.get_size()
scale = min(size[0] / iwidth, size[1] / iheight)
#scale = max(size[0] / iwidth, size[1] / iheight)
new_size = (round(iwidth * scale), round(iheight * scale))
scaled_image = pygame.transform.smoothscale(image, new_size)
image_rect = scaled_image.get_rect(center = (size[0] // 2, size[1] // 2))
return scaled_image, image_rect
pygame.init()
window = pygame.display.set_mode((300, 300), pygame.RESIZABLE)
clock = pygame.time.Clock()
background = pygame.image.load('parrot.png').convert_alpha()
scaled_bg, bg_rect = transformScaleKeepRatio(background, window.get_size())
run = True
while run == True:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.VIDEORESIZE:
window = pygame.display.set_mode(event.size, pygame.RESIZABLE)
scaled_bg, bg_rect = transformScaleKeepRatio(background, window.get_size())
window.fill((127, 127, 127))
window.blit(scaled_bg, bg_rect)
pygame.display.flip()
pygame.quit()
exit()
i do not know if you meant this, but this is how to scale to the size of the screen an image at the max that is possible, without losing the aspect ratio of the image among width and height
row = pygame.image.load(f"{image}")
x, y = row.get_size()
rx = 1000 / x
ry = 600 / y
print(rx)
print(ry)
ratio = rx if rx < ry else ry
row = pygame.transform.scale(row, (int(x*rx), int(y*rx)))
Here's a recipe that allows scaling image to screen so that it maintains aspect ratio and never extends outside the screen.
screen_resolution = (1920, 1080)
image_path = '/path/to/image.png'
center_image = True
image = pygame.image.load(image_path)
screen_w, screen_h = screen_resolution
image_w, image_h = image.get_size()
screen_aspect_ratio = screen_w / screen_h
photo_aspect_ratio = image_w / image_h
if screen_aspect_ratio < photo_aspect_ratio: # Width is binding
new_image_w = screen_w
new_image_h = int(new_image_w / photo_aspect_ratio)
image = pygame.transform.scale(image, (new_image_w, new_image_h))
image_x = 0
image_y = (screen_h - new_image_h) // 2 if center_image else 0
elif screen_aspect_ratio > photo_aspect_ratio: # Height is binding
new_image_h = screen_h
new_image_w = int(new_image_h * photo_aspect_ratio)
image = pygame.transform.scale(image, (new_image_w, new_image_h))
image_x = (screen_w - new_image_w) // 2 if center_image else 0
image_y = 0
else: # Images have the same aspect ratio
image = pygame.transform.scale(image, (screen_w, screen_h))
image_x = 0
image_y = 0
display.blit(image, (image_x, image_y))