So, i am building a physics simulation. And whenever the left mouse button is pressed, the player can control the height the ball is at(by dragging). What i want to build is: whenever the player is dragging the ball to reposition it, a screen will appear at the side, and inside it i want a real time zoom of where the ball is(sorry if i didnt explain it too well, i think the picture explains it better).
Just to clarify, I want it all in one window only
Can anyone help me? :)
It's not that complicated. Just draw your stuff on a seperate Surface, then use subsurface and the transform module. Here's an example:
import pygame
pygame.init()
def main():
# we'll not draw an the display surface directly
screen = pygame.display.set_mode((800, 600))
# but we'll draw everything on this surface
main = screen.copy()
# this surface is the zoom window
zoom = pygame.Surface((400, 300))
# the ball and its movement vector
ball = pygame.Rect(550, 100, 40, 40)
ball_v = pygame.Vector2(0, 0)
dt = 0
clock = pygame.time.Clock()
while True:
pressed = pygame.mouse.get_pressed()
for e in pygame.event.get():
if e.type == pygame.QUIT:
return
if pressed[0]:
# dragging the ball if mouse button is pressed
ball_v = pygame.Vector2(0, 0)
ball.center = pygame.mouse.get_pos()
else:
# if not, we apply the gravity
ball_v += pygame.Vector2(0, 0.2)
# move the ball
ball.move_ip(ball_v)
# but keep it on the screen
ball.clamp_ip(main.get_rect())
# draw the main surface
main.fill(pygame.Color('white'))
w = 10
for y in range(main.get_rect().height):
pygame.draw.rect(main, pygame.Color('lightgray'), pygame.Rect(500, y, w, 1))
w = (w - 1) % 20
pygame.draw.circle(main, pygame.Color('orange'), ball.center, 20)
screen.blit(main, (0, 0))
# if the mouse button is pressed, draw the zoom window
if pressed[0]:
# the area of the main surface that should be drawn in the zoom window
rect = pygame.Rect(0, 0, 200, 150)
# center it on the ball
rect.center = ball.center
# ensure it's on the screen
rect.clamp_ip(main.get_rect())
# grab the part from the main surface
sub = main.subsurface(rect)
# scale it
zoom.blit(pygame.transform.scale2x(sub), (0, 0))
pygame.draw.rect(zoom, pygame.Color('black'), zoom.get_rect(), 4)
screen.blit(zoom, (25, 25))
pygame.display.flip()
clock.tick(120)
main()
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 want to move the image of a Rect object, is this possible?
examples:
1 - make a waterfall with the water animated (make the water image scroll)
2 - adjust location of the image not the rect
note: these are just examples not the code I am working on
You can shift the surface image in place with pygame.Surface.scroll. For instance, call
water_surf.scroll(0, 1)
However, this will not satisfy you. See pygame.Surface.scroll:
Move the image by dx pixels right and dy pixels down. dx and dy may be negative for left and up scrolls respectively. Areas of the surface that are not overwritten retain their original pixel values.
You may want to write a function that overwrites the areas wich are not overwritten, with the pixel that is scrolled out of the surface:
def scroll_y(surf, dy):
scroll_surf = surf.copy()
scroll_surf.scroll(0, dy)
if dy > 0:
scroll_surf.blit(surf, (0, dy-surf.get_height()))
else:
scroll_surf.blit(surf, (0, surf.get_height()+dy))
return scroll_surf
once per frame to create a water flow effect like a waterfall.
To center an image in a rectangular area, you need to get the bounding rectangle of the image and set the center of the rectnagle through the center of the area. Use the rectangle to blit the image:
area_rect = pygame.Rect(x, y, w, h)
image_rect = surf.get_rect()
image_rect.center = area_rect.center
screen.blit(surf, image_rect)
The same in one line:
screen.blit(surf, surf.get_rect(center = area_rect.center))
Minimal example:
repl.it/#Rabbid76/PyGame-SCroll
import pygame
def scroll_y(surf, dy):
scroll_surf = surf.copy()
scroll_surf.scroll(0, dy)
if dy > 0:
scroll_surf.blit(surf, (0, dy-surf.get_height()))
else:
scroll_surf.blit(surf, (0, surf.get_height()+dy))
return scroll_surf
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
rain_surf = pygame.image.load('rain.png')
dy = 0
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window_center = window.get_rect().center
scroll_surf = scroll_y(rain_surf, dy)
dy = (dy + 1) % rain_surf.get_height()
window.fill(0)
window.blit(scroll_surf, scroll_surf.get_rect(center = window_center))
pygame.display.flip()
pygame.quit()
exit()
I am making a program with a graph that scrolls and I just need to move a section of the screen.
If I do something like this:
import pygame
screen = pygame.display.set_mode((300, 300))
sub = screen.subsurface((0,0,20,20))
screen.blit(sub, (30,40))
pygame.display.update()
It gives the error message: pygame.error: Surfaces must not be locked during blit
I assume it means the child is locked to its parent surface or something but how else could I go about doing this?
screen.subsurface creates a surface, which reference to the original surface. From documentation:
Returns a new Surface that shares its pixels with its new parent.
To avoid undefined behaviour, the surfaces get locked. You've to .copy the surface, before you can .blit it to its source:
sub = screen.subsurface((0,0,20,20)).copy()
screen.blit(sub, (30,40))
Just don't draw to the screen surface directly. Create a Surface for each part of your game/UI, and blit each of those to the screen.
import pygame
def main():
pygame.init()
screen = pygame.display.set_mode((640, 480))
# create two parts: a left part and a right part
left_screen = pygame.Surface((400, 480))
left_screen.fill((100, 0, 0))
right_screen = pygame.Surface((240, 480))
right_screen.fill((200, 200, 0))
x = 100
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
# don't draw to the screen surface directly
# draw stuff either on the left_screen or right_screen
x += 1
left_screen.fill(((x / 10) % 255, 0, 0))
# then just blit both parts to the screen surface
screen.blit(left_screen, (0, 0))
screen.blit(right_screen, (400, 0))
pygame.display.flip()
if __name__ == '__main__':
main()
I am trying to make a virtual phone kind of program with pygame, just to experiment with it, but i ran into a problem. I have loaded an image and then blitted it to the bottom left of the screen. But when i do print(imagename.get_rect()) it prints out a location at 0, 0 of the screen.
also the mouse collides with it there. what am i not understanding?
def aloitus(): #goes back to the home screen
cls() #clears the screen
tausta = pygame.image.load("./pyhelin.png") #load background
tausta = pygame.transform.scale(tausta, (360, 640)) #scale it
screen.blit(tausta, (0, 0)) #blit it
alapalkki = pygame.Surface((600, 100)) #ignore
alapalkki.set_alpha(120)
alapalkki.fill(blonk)
screen.blit(alapalkki, (0, 560))
global messenger #this is the thing!
messenger = pygame.image.load("./mese.png").convert_alpha() #load image
print(messenger.get_rect()) #print its location
messenger = pygame.transform.scale(messenger, (60,65)) #scale it to the correct size
screen.blit(messenger, (10, 570)) # blit on the screen
update() #update screen
aloitus() # at the start of the program, go to the home screen
while loop: #main loop
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
# Set the x, y postions of the mouse click
x, y = event.pos
if messenger.get_rect().collidepoint(x, y): #messenger is the image defined earlier
#do things
print("hi")
Expected result would be, when clicking on the image "hi" would be printed.
actual result is that when topleft corner is clicked hi is printed.
get_rect returns a pygame.Rect with the default top left coordinates (0, 0). You need to set the coordinates afterwards or pass them as a keyword argument to get_rect.
I suggest assigning the rect to another variable and set the coords in one of these ways:
messenger_rect = messenger.get_rect()
messenger_rect.x = 100
messenger_rect.y = 200
# or
messenger_rect.topleft = (100, 200)
# or pass the coords as an argument to `get_rect`
messenger_rect = messenger.get_rect(topleft=(100, 200))
There are even more rect attributes to which you can assign the coordinates:
x,y
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
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