resize window without border [duplicate] - python

This question already has an answer here:
ValueError: subsurface rectangle outside surface area
(1 answer)
Closed 1 year ago.
I'm making a platformer, and I want to let the user resize the screen but without borders (so the game isn't fixed ratio). however I don't know the best way to implement this, all my versions are very slow (around 8 fps).
this i was my attempt
def video_resize(self):
self.fit_to_rect = self.blit_surf.get_rect().fit(self.screen.get_rect()) # fit the surface to the screen
self.fit_to_rect.size = self.fit_to_rect.width * self.neutralizerZoom * self.zoom, self.fit_to_rect.height * self.neutralizerZoom * self.zoom # add zoom
def update(self):
scaled = transform.scale(self.blit_surf, (self.fit_to_rect.width, self.fit_to_rect.height)) # scale surface to screen
self.fit_to_rect.topleft = self.screen.get_rect().top + self.cameraPos[0], self.screen.get_rect().left + self.cameraPos[1] # center surface & camera pos
self.mousePos[0] = (mouse.get_pos()[0] / (scaled.get_width() / self.blit_surf.get_width())) - (self.cameraPos[0] / (scaled.get_width() / self.blit_surf.get_width())) # scale x axis mouse pos
self.mousePos[1] = (mouse.get_pos()[1] / (scaled.get_height() / self.blit_surf.get_height())) # scale y axis mouse pos
#scaled = scaled.subsurface(self.fit_to_rect.x, self.fit_to_rect.y, self.fit_to_rect.x + self.fit_to_rect.width, self.fit_to_rect.y + self.fit_to_rect.height)
#self.screen.blit(scaled ,(0, 0)) # blit surface to screen
self.screen.blit(scaled, (self.fit_to_rect.x, self.fit_to_rect.y, self.fit_to_rect.width, self.fit_to_rect.height))
display.flip() # update screen
self.clock.tick(60)
what is the most efficient way to resize the screen like this?

Do not scale the Surface in every frame. Scale the surface once when resizing:
class ...:
def __init__(...):
# [...]
self.scaled_surf = self.blit_surf
def video_resize(self):
self.fit_to_rect = self.blit_surf.get_rect().fit(self.screen.get_rect()) # fit the surface to the screen
self.fit_to_rect.size = self.fit_to_rect.width * self.neutralizerZoom * self.zoom, self.fit_to_rect.height * self.neutralizerZoom * self.zoom # add zoom
self.scaled_surf = transform.scale(self.blit_surf, (self.fit_to_rect.width, self.fit_to_rect.height)) # scale surface to screen
def update(self):
self.fit_to_rect.topleft = self.screen.get_rect().top + self.cameraPos[0], self.screen.get_rect().left + self.cameraPos[1] # center surface & camera pos
self.mousePos[0] = (mouse.get_pos()[0] / (scaled.get_width() / self.blit_surf.get_width())) - (self.cameraPos[0] / (scaled.get_width() / self.blit_surf.get_width())) # scale x axis mouse pos
self.mousePos[1] = (mouse.get_pos()[1] / (scaled.get_height() / self.blit_surf.get_height())) # scale y axis mouse pos
self.screen.blit(self.scaled_surf, (self.fit_to_rect.x, self.fit_to_rect.y, self.fit_to_rect.width, self.fit_to_rect.height))
display.flip() # update screen
self.clock.tick(60)

Related

cant properly make the player move on mobile joystick project [duplicate]

This question already has answers here:
how to make sprite move upwards and downwards with joystick in pygame
(1 answer)
How do I get xbox controls outside event loop in pygame?
(1 answer)
problems with pygame controller support
(1 answer)
Closed 2 days ago.
Trying to make mobile joystick, went well until i stumbled on a roadblock, that is trying to make a player move.
the problem is, the player position isn't really moving, its only set on the position of the given x,y mouse pos, thus giving the player position on the center.
from pygame import *;
import pygame;
import math;
import os;
pygame.init();
os.chdir("/storage/emulated/0/Insanity");
ingame = True;
source_dir = os.path.dirname(os.path.abspath(__file__));
screen = pygame.display.set_mode((1280,720));
def get_velo(x,y, dist):
vec1,vect2 = 0,0;
vec1 = y / dist;
vec2 = x / dist;
return vec1,vec2;
class sprite(pygame.sprite.Sprite):
def __init__(self,img, size, pos):
self.img = pygame.image.load(img[1]);
self.size = pygame.transform.scale(self.img, (size[0],size[1])).convert_alpha();
# HITBOX
self.rect = self.size.get_rect(center=pos);
self.pos = self.rect.move(pos);
def display(self):
screen.blit(self.size, self.pos);
HW, HH = 1280/2, 720/2;
AREA = 1280*720;
joystick_body = sprite(["", "circle.png"], [100,100], (0,0));
joystick_control = sprite(["", "circle.png"], [30,30], (0,0));
joystick_body.pos = [0,630];
radius = joystick_body.size.get_rect().center[0];
my_velo = 0;
## PLAYER
player = sprite(["", "player.png"], [100,100], (0,0));
movespeed = 10;
while ingame:
screen.fill((255,255,255));
## JOYSTICK
joystick_body.display();
x, y = pygame.mouse.get_pos();
# CIRCLE COLLISION
v1 = pygame.math.Vector2(0, 630);
v2 = pygame.math.Vector2(x,y);
if v1.distance_to(v2) < radius + radius:
joystick_control.pos = (x,y);
joystick_control.rect.clamp_ip(joystick_body.rect);
vec1,vec2 = x / radius, y / radius# x / v1.distance_to(v2), y / v1.distance_to(v2)
#vec1,vec2 = (x,y, v1.distance_to(v2));
player.pos = (vec1*movespeed,vec2*movespeed);
#joystick_control.pos = joystick_body.pos + (joystick_control.pos - joystick_body.pos).clamped(radius);
else:
joystick_control.pos = (33,664);
player.display();
joystick_control.display();
pygame.display.update();
the video:
https://www.veed.io/view/442d6abd-c072-41bb-98be-be5b1fb1ba42/223fbe52-0cf9-4724-9393-d7a85f9beb68?sharingWidget=true&panel=share
I tried pygame.vector2, rect.move() and the same happened, i felt like i need to use the player.pos x and y but i dont know how i would implement it, if there is a pygame version of godot function (move_and_slide()). I expect to make a working mobile joystick

PyGame colliders don't scale with window

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)
# [...]

Pygame: How to rotate a rectangle clockwise/counterclockwise depending on input [duplicate]

This question already has answers here:
How do I rotate an image around its center using Pygame?
(6 answers)
Closed 2 years ago.
I have made one rectangle so far that follows the user's mouse and rotates counterclockwise with the a key and clockwise with the d key.
Currently the rectangle follows the mouse but once the user starts to rotate the rectangle, the rectangle becomes very laggy and disfigured. I need help with keeping the rectangle the same and a constant FPS. Thank you for your help!
Here is the code:
import pygame as py
# define constants
WIDTH = 500
HEIGHT = 500
FPS = 200
# define colors
BLACK = (0 , 0 , 0)
GREEN = (0 , 255 , 0)
# initialize pygame and create screen
py.init()
screen = py.display.set_mode((WIDTH , HEIGHT))
# for setting FPS
clock = py.time.Clock()
rot = 0
rot_speed = 2
# define a surface (RECTANGLE)
image_orig = py.Surface((1 , 100))
# for making transparent background while rotating an image
image_orig.set_colorkey(BLACK)
# fill the rectangle / surface with green color
image_orig.fill(GREEN)
# creating a copy of orignal image for smooth rotation
image = image_orig.copy()
image.set_colorkey(BLACK)
# define rect for placing the rectangle at the desired position
rect = image.get_rect()
x, y = py.mouse.get_pos()
rect.center = (x, y)
# keep rotating the rectangle until running is set to False
running = True
while running:
x, y = py.mouse.get_pos()
# set FPS
clock.tick(FPS)
# clear the screen every time before drawing new objects
screen.fill(BLACK)
# check for the exit
for event in py.event.get():
if event.type == py.QUIT:
running = False
# making a copy of the old center of the rectangle
old_center =(x, y)
# defining angle of the rotation
rot = (rot + rot_speed) % 360
# rotating the orignal image
keys = py.key.get_pressed()
rot_speed = .2
image_orig = py.transform.rotate(image_orig , 0)
rect = image_orig.get_rect()
# set the rotated rectangle to the old center
rect.center = (x, y)
# drawing the rotated rectangle to the screen
screen.blit(image_orig , rect)
# flipping the display after drawing everything
py.display.flip()
if(keys[py.K_a]):
rot_speed = .2
image_orig = py.transform.rotate(image_orig , rot)
rect = image_orig.get_rect()
# set the rotated rectangle to the old center
rect.center = (x, y)
# drawing the rotated rectangle to the screen
screen.blit(image_orig , rect)
# flipping the display after drawing everything
py.display.flip()
if(keys[py.K_d]):
rot_speed = -.2
image_orig = py.transform.rotate(image_orig , rot)
rect = image_orig.get_rect()
# set the rotated rectangle to the old center
rect.center = (x, y)
# drawing the rotated rectangle to the screen
screen.blit(image_orig , rect)
# flipping the display after drawing everything
py.display.flip()
rect.center = (x, y)
py.quit()
The mayor issue is that you continuously rotate and manipulate the original image. That causes that the image gets distorted. See How do I rotate an image around its center using Pygame?
Compute the new angle of the image:
rot = (rot + rot_speed) % 360
Create a new image which is rotated around its center:
image = py.transform.rotate(image_orig, rot)
rect = image.get_rect(center = (x, y))
And blit the rotated image:
screen.blit(image, rect)
When A or D is pressed then the new direction is set by rot_speed = .2 respectively rot_speed = -.2.
Full example code:
import pygame as py
# define constants
WIDTH = 500
HEIGHT = 500
FPS = 200
# define colors
BLACK = (0 , 0 , 0)
GREEN = (0 , 255 , 0)
# initialize pygame and create screen
py.init()
screen = py.display.set_mode((WIDTH , HEIGHT))
# for setting FPS
clock = py.time.Clock()
rot = 0
rot_speed = .2
# define a surface (RECTANGLE)
image_orig = py.Surface((1 , 100))
# for making transparent background while rotating an image
image_orig.set_colorkey(BLACK)
# fill the rectangle / surface with green color
image_orig.fill(GREEN)
# creating a copy of orignal image for smooth rotation
image = image_orig.copy()
image.set_colorkey(BLACK)
# define rect for placing the rectangle at the desired position
rect = image.get_rect()
x, y = py.mouse.get_pos()
rect.center = (x, y)
# keep rotating the rectangle until running is set to False
running = True
while running:
x, y = py.mouse.get_pos()
# set FPS
clock.tick(FPS)
# clear the screen every time before drawing new objects
screen.fill(BLACK)
# check for the exit
for event in py.event.get():
if event.type == py.QUIT:
running = False
# rotating the orignal image
keys = py.key.get_pressed()
if keys[py.K_a]:
rot_speed = .2
if keys[py.K_d]:
rot_speed = -.2
# defining angle of the rotation
rot = (rot + rot_speed) % 360
# rotating the orignal image
image = py.transform.rotate(image_orig, rot)
rect = image.get_rect(center = (x, y))
# drawing the rotated rectangle to the screen
screen.blit(image, rect)
# flipping the display after drawing everything
py.display.flip()
py.quit()

Detecting if an arc has been clicked in pygame

I am currently trying to digitalize an boardgame I invented (repo: https://github.com/zutn/King_of_the_Hill). To make it work I need to check if one of the tiles (the arcs) on this board have been clicked. So far I have not been able to figure a way without giving up the pygame.arc function for drawing. If I use the x,y position of the position clicked, I can't figure a way out to determine the exact outline of the arc to compare to. I thought about using a color check, but this would only tell me if any of the tiles have been clicked. So is there a convenient way to test if an arc has been clicked in pygame or do I have to use sprites or something completely different? Additionally in a later step units will be included, that are located on the tiles. This would make the solution with the angle calculation postet below much more diffcult.
This is a simple arc class that will detect if a point is contained in the arc, but it will only work with circular arcs.
import pygame
from pygame.locals import *
import sys
from math import atan2, pi
class CircularArc:
def __init__(self, color, center, radius, start_angle, stop_angle, width=1):
self.color = color
self.x = center[0] # center x position
self.y = center[1] # center y position
self.rect = [self.x - radius, self.y - radius, radius*2, radius*2]
self.radius = radius
self.start_angle = start_angle
self.stop_angle = stop_angle
self.width = width
def draw(self, canvas):
pygame.draw.arc(canvas, self.color, self.rect, self.start_angle, self.stop_angle, self.width)
def contains(self, x, y):
dx = x - self.x # x distance
dy = y - self.y # y distance
greater_than_outside_radius = dx*dx + dy*dy >= self.radius*self.radius
less_than_inside_radius = dx*dx + dy*dy <= (self.radius- self.width)*(self.radius- self.width)
# Quickly check if the distance is within the right range
if greater_than_outside_radius or less_than_inside_radius:
return False
rads = atan2(-dy, dx) # Grab the angle
# convert the angle to match up with pygame format. Negative angles don't work with pygame.draw.arc
if rads < 0:
rads = 2 * pi + rads
# Check if the angle is within the arc start and stop angles
return self.start_angle <= rads <= self.stop_angle
Here's some example usage of the class. Using it requires a center point and radius instead of a rectangle for creating the arc.
pygame.init()
black = ( 0, 0, 0)
width = 800
height = 800
screen = pygame.display.set_mode((width, height))
distance = 100
tile_num = 4
ring_width = 20
arc = CircularArc((255, 255, 255), [width/2, height/2], 100, tile_num*(2*pi/7), (tile_num*(2*pi/7))+2*pi/7, int(ring_width*0.5))
while True:
fill_color = black
for event in pygame.event.get():
# quit if the quit button was pressed
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
x, y = pygame.mouse.get_pos()
# Change color when the mouse touches
if arc.contains(x, y):
fill_color = (200, 0, 0)
screen.fill(fill_color)
arc.draw(screen)
# screen.blit(debug, (0, 0))
pygame.display.update()

How can I scale everything on a screen depending on the size of monitor? [duplicate]

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))

Categories

Resources