I am trying to create a game using pygame and I am attempting to add a background to it (I have used some code from a YouTube video but this is not working). I also to not understand what the code is on about. I mean the background and does move but it automatically adds a new version of the background in the middle of the screen when the older background has not gone off screen yet:
class Background:
def __init__(self, x, y, picture):
self.xpos = x
self.ypos = y
self.picture = picture
self.rect = self.picture.get_rect()
self.picture = pygame.transform.scale(self.picture, (1280, 720))
def paste(self, xpos, ypos):
screen.blit(self.picture, (xpos, ypos))
def draw(self):
screen.blit(self.picture, (self.xpos, self.ypos))
while True:
background=pygame.image.load("C:/images/mars.jpg").convert_alpha()
cliff = Background(0, 0, background)
rel_x = x % cliff.rect.width
cliff.paste(rel_x - cliff.rect.width, 0)
if rel_x < WIDTH:
cliff.paste(rel_x, 0)
x -= 1
This is what currently happens with my background
[![what my problem looks like][1]][1]
[![What I want the background to move like ][2]][2]
This is what I want my background to look like (please ignore the sign it was the only one I could find)
I have now discovered what the real problem is
If you want to have a continuously repeating background, then you've to draw the background twice:
You've to know the size of the screen. The size of the height background image should match the height of the screen. The width of the background can be different, but should be at least the with of the window (else the background has to be drawn more than 2 times).
bg_w, gb_h = size
bg = pygame.transform.smoothscale(pygame.image.load('background.image'), (bg_w, bg_h))
The background can be imagined as a endless row of tiles.
If you want to draw the background at an certain position pos_x, then you have to calculate the position of the tile relative to the screen by the modulo (%) operator. The position of the 2nd tile is shifted by the width of the background (bg_w):
x_rel = pos_x % bg_w
x_part2 = x_rel - bg_w if x_rel > 0 else x_rel + bg_w
Finally the background has to be blit twice, to fill the entire screen:
screen.blit(bg, (x_rel, 0))
screen.blit(bg, (x_part2, 0))
You can test the process by the following example program. The background can be moved by <- respectively ->
import pygame
pygame.init()
size = (800,600)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
bg_w, bg_h = size
bg = pygame.transform.smoothscale(pygame.image.load('background.image'), (bg_w, bg_h))
pos_x = 0
speed = 10
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
allKeys = pygame.key.get_pressed()
pos_x += speed if allKeys[pygame.K_LEFT] else -speed if allKeys[pygame.K_RIGHT] else 0
x_rel = pos_x % bg_w
x_part2 = x_rel - bg_w if x_rel > 0 else x_rel + bg_w
screen.blit(bg, (x_rel, 0))
screen.blit(bg, (x_part2, 0))
pygame.display.flip()
Also see How to make parallax scrolling work properly with a camera that stops at edges pygame
This SO answer should have what you need
This seems to provide maybe a smarter and more functional background class than what you're using. I'd say give a try.
I created a pong game where I noticed the paddles are not placed equally at the screen edges.
I created an 800 pixel wide screen, and placed paddles at xcor = 380 and xcor = -380 but on the screen left paddle shows some gap but right paddle doesn't. Is my screen unsymmetrical? How do I fix it?
screen.setup(width=800, height=600)
screen.bgcolor("black")
screen.title("PONG")
screen.tracer(0)
l_paddle = Paddle()
l_paddle.create_paddle((-380, 0))
r_paddle = Paddle()
r_paddle.create_paddle((380, 0))
screenshot of screen
When we specify a window size to setup(), we're talking about total pixels used on the screen. Since there is chrome around the window (edges, title bar, etc.) the actual area we have to work with is slightly smaller. Trying to place two turtles at exactly the left and right edge, ignoring chrome:
from turtle import Screen, Turtle
CURSOR_SIZE = 20
WIDTH, HEIGHT = 600, 400
screen = Screen()
screen.setup(WIDTH, HEIGHT)
l_paddle = Turtle('square')
l_paddle.fillcolor('white')
l_paddle.setx(CURSOR_SIZE/2 - WIDTH/2)
r_paddle = Turtle('square')
r_paddle.fillcolor('white')
r_paddle.setx(WIDTH/2 - CURSOR_SIZE/2)
screen.exitonclick()
We get a result similar to yours:
If we compensate for the internal and external chrome elements:
from turtle import Screen, Turtle
CURSOR_SIZE = 20
BORDER_SIZE = 2 # inside the window
CHROME_SIZE = 9 # around the window
WIDTH, HEIGHT = 600, 400
screen = Screen()
screen.setup(WIDTH, HEIGHT)
l_paddle = Turtle('square')
l_paddle.fillcolor('white')
l_paddle.setx(CURSOR_SIZE/2 - WIDTH/2 + BORDER_SIZE)
r_paddle = Turtle('square')
r_paddle.fillcolor('white')
r_paddle.setx(WIDTH/2 - CURSOR_SIZE/2 - BORDER_SIZE - CHROME_SIZE)
screen.exitonclick()
We can get a more precise result:
The problem here is that the amount of chrome is system dependent but turtle doesn't tell us how much to compensate. You might be able to find out from the underlying tkinter code.
My recommendation is you estimate the best you can, assume it's not accurate on all systems, and stay away from the edges so it's less of an issue. The error can be greater in the Y dimension when the title bar is part of the chrome.
I'm making an platformer game where the camera follows the player. I'm trying to implement this by having a large surface surface with the whole map and only blitting a zoomed in section. however im only getting 30 fps (minimized) and 8 fps (full screen).
So my attempt to optimize it was to to crop it before blitting but i get ValueError: subsurface rectangle outside surface area
code
class screen_handler:
def __init__(self, screen=False, mapSize=[3, 3]):
if not screen: # if screen isn't open
init() # initialize pygame
user32 = ctypes.windll.user32 # set user32
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (user32.GetSystemMetrics(0) / 4, user32.GetSystemMetrics(1) / 4) # center future screen
screen = display.set_mode((640, 512), RESIZABLE) # create screen
self.screen = screen # save screen
self.blit_surf = Surface((640 * mapSize[0], 512 * mapSize[1])) # create blit_surf
self.clock = time.Clock() # create clock
self.neutralizerZoom = min(self.blit_surf.get_width() / 640, self.blit_surf.get_height() / 512) # reset zoom
self.zoom = 2
self.mousePos = [0, 0]
self.cameraPos = [0, 0]
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 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)
display.flip() # update screen
self.clock.tick(60)
print(self.clock.get_fps())
note: please tell me if there is a better way/ quicker way of implementing a camera
Here is how i do my camera movement:
WINDOW_WIDTH, WINDOW_HEIGHT = ...
window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT), RESIZABLE)
screen = pygame.Surface(your_resolution)
...
scroll_x, scroll_y = player_position # get the scroll
...
screen.blit(image, (x_pos + scroll_x, y_pos + scroll_y))
...
for event in pygame.event.get():
if event.type == VIDEORESIZE:
WINDOW_WIDTH, WINDOW_HEIGHT = event.size
...
window.blit(pygame.transform.scale(screen, (WINDOW_WIDTH, WINDOW_HEIGHT)), (0, 0))
pygame.display.update()
every time you want to show something you need to blit it onto screen instead of window.
if you want to have the same scale i would recommend the follwing class:
class Window:
def __init__(self, surf, width, height):
self.screen = pygame.display.set_mode((width, height), RESIZABLE)
self.surf = surf
self.orig_w, self.orig_h = surf.get_size()
self.set_sizes(width, height)
def set_sizes(self, width, height):
self.rate = min(width / self.orig_w, height / self.orig_h)
self.width = int(self.orig_w * self.rate)
self.x_off = int((width - self.width) / 2)
self.height = int(self.orig_h * self.rate)
self.y_off = int((height - self.height) / 2)
def get_mouse_pos(self):
mouse_x, mouse_y = pygame.mouse.get_pos()
return int((mouse_x - self.x_off) / self.rate), int((mouse_y - self.y_off) / self.rate)
def show(self):
self.screen.fill((50, 50, 50))
self.screen.blit(pygame.transform.scale(self.surf, (self.width, self.height)), (self.x_off, self.y_off))
pygame.display.flip()
EDIT: OPTIMTZING
the following code will replace the line that caused you problems:
instead of
scaled = scaled.subsurface(...)
self.screen.blit(scaled, (0, 0))
do
self.screen.blit(scaled, (0, 0), self.fit_to_rect)
this is more efficient because it doesn't need to create the subsurface but blits is directly onto the screen.
optimizing tips:
avoid recreating surfaces every frame.
your large surface does only need to be created when the map is loaded and never again. if you are rotating images you can simply create a list or dict of rotated images at the start of the program and just need to call it. same goes for changes in scale.
use img = img.convert()
this is a pretty simple optimizing trick.
Referring to John Zelle's graphics.py, I want the GraphWin to close right after the Circle object has reached the edge of the window and is out of sight.
Following code creates a circle and moves it:
win = GraphWin("My Circle", 100, 100)
c = Circle(Point(50,50), 10)
c.draw(win)
for i in range(40):
c.move(30, 0) #speed=30
time.sleep(1)
#c should move until the end of the windows(100),
win.close() # then windows of title "My Circle" should close immediately
Is there any way to do this instead of using range and counting its exact number of 'steps'?
Compare the x position of the left side of the circle against the right side of the window:
from graphics import *
WIDTH, HEIGHT = 300, 300
RADIUS = 10
SPEED = 30
win = GraphWin("My Circle", WIDTH, HEIGHT)
c = Circle(Point(50, 50), RADIUS)
c.draw(win)
while c.getCenter().x - RADIUS < WIDTH:
c.move(SPEED, 0)
time.sleep(1)
win.close() # then windows of title "My Circle" should close immediately
In a speedier loop, we might move RADIUS to the other side of the equation and make a new constant of WIDTH + RADIUS.
If it was an Image object, how would you suggest to get the leftmost
position of the object to compare it to the width of the window?
An Image object would work similarly, using it's anchor, instead of center, and using its width instead of its radius:
from graphics import *
WIDTH, HEIGHT = 300, 300
SPEED = 30
win = GraphWin("My Image", WIDTH, HEIGHT)
image = Image(Point(50, 50), "file.gif")
image.draw(win)
image_half_width = image.getWidth() / 2
while image.getAnchor().x - image_half_width < WIDTH:
image.move(SPEED, 0)
time.sleep(1)
win.close() # the window of title "My Image" should close immediately
In pygame I use pygame.draw.rect(screen, color, rectangle) for all the rectangles in my program. I want to be able to rotate these rectangles to any angle. I have seen the following code to rotate IMAGES but my question is with RECTANGLES.
pygame.transform.rotate(image, angle)
But I am working with rectangles, I don't have an image or "surface" that I can rotate. When I try to rotate a rectangle with
rect = pygame.draw.rect(screen, self.color, self.get_rectang())
rotatedRect = pygame.transform.rotate(rect, self.rotation)
screen.blit(rotatedRect)
This gives TypeError: must be pygame.Surface, not pygame.Rect on the line with .rotate()
My question is, how can I rotate a and display a RECTANGLE(x,y,w,h), not an image, in pygame.
The linked post that this is a "potential duplicate" of is not a duplicate. One answer explains about the consequences of rotating a rectangle and the other uses code for rotating an image.
See the second answer here: Rotating a point about another point (2D)
I think rectangles can only be horiz or vertical in their oreintation. You need to define the corners and rotate them and then draw and fill between them.
The other way is to make a class
class myRect(pygame.Surface):
def __init__(self, parent, xpos, ypos, width, height):
super(myRect, self).__init__(width, height)
self.xpos = xpos
self.ypos = ypos
self.parent = parent
def update(self, parent):
parent.blit(self, (self.xpos, self.ypos))
def rotate(self, angle):
#(your rotation code goes here)
and use that instead, as then you will be able to rotate it using the transform function.
import pygame as py
# define constants
WIDTH = 500
HEIGHT = 500
FPS = 30
# 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((100 , 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()
rect.center = (WIDTH // 2 , HEIGHT // 2)
# keep rotating the rectangle until running is set to False
running = True
while running:
# 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 = rect.center
# defining angle of the rotation
rot = (rot + rot_speed) % 360
# rotating the orignal image
new_image = py.transform.rotate(image_orig , rot)
rect = new_image.get_rect()
# set the rotated rectangle to the old center
rect.center = old_center
# drawing the rotated rectangle to the screen
screen.blit(new_image , rect)
# flipping the display after drawing everything
py.display.flip()
py.quit()
a more complex version of the quick replacement, in which you can define an arbitrary rotation center point for your rectangle - even outside of it (tested in python3):
def rectRotated( surface, color, pos, fill, border_radius, rotation_angle, rotation_offset_center = (0,0), nAntialiasingRatio = 1 ):
"""
- rotation_angle: in degree
- rotation_offset_center: moving the center of the rotation: (-100,0) will turn the rectangle around a point 100 above center of the rectangle,
if (0,0) the rotation is at the center of the rectangle
- nAntialiasingRatio: set 1 for no antialising, 2/4/8 for better aliasing
"""
nRenderRatio = nAntialiasingRatio
sw = pos[2]+abs(rotation_offset_center[0])*2
sh = pos[3]+abs(rotation_offset_center[1])*2
surfcenterx = sw//2
surfcentery = sh//2
s = pg.Surface( (sw*nRenderRatio,sh*nRenderRatio) )
s = s.convert_alpha()
s.fill((0,0,0,0))
rw2=pos[2]//2 # halfwidth of rectangle
rh2=pos[3]//2
pg.draw.rect( s, color, ((surfcenterx-rw2-rotation_offset_center[0])*nRenderRatio,(surfcentery-rh2-rotation_offset_center[1])*nRenderRatio,pos[2]*nRenderRatio,pos[3]*nRenderRatio), fill*nRenderRatio, border_radius=border_radius*nRenderRatio )
s = pygame.transform.rotate( s, rotation_angle )
if nRenderRatio != 1: s = pygame.transform.smoothscale(s,(s.get_width()//nRenderRatio,s.get_height()//nRenderRatio))
incfromrotw = (s.get_width()-sw)//2
incfromroth = (s.get_height()-sh)//2
surface.blit( s, (pos[0]-surfcenterx+rotation_offset_center[0]+rw2-incfromrotw,pos[1]-surfcentery+rotation_offset_center[1]+rh2-incfromroth) )
You cannot rotate a rectangle drawn by pygame.draw.rect. You have to create a transparent pygame.Surface and rotate the Surface:
rect_surf = pygame.Surface((widht, height), pygame.SRCLAPHA)
rect_surf.fill(color)
See How do I rotate an image around its center using PyGame?, to rotate the Surface.
I made a class which handles the rotation for you...
Extended from Ashish's design
from pygame import Surface, transform
from consts import screen
class BaseEntity:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
class Rectangle(BaseEntity):
def __init__(self, x: int, y: int, width: int, height: int, color: tuple):
super().__init__(x, y)
self.width = width
self.height = height
self.color = color
self.rotatation = 0
# the rectangle is a surface itself
self.surface = Surface((width, height))
self.surface.set_colorkey((0, 0, 0))
self.surface.fill(color)
self.rect = self.surface.get_rect()
def display(self, angle=None):
# updating values
self.surface.fill(
self.color
) # refill the surface color if you change it somewhere in the program
self.rect = self.surface.get_rect()
self.rect.center = (self.x, self.y)
# renderer
if angle is not None:
self.rotatation = angle
old_center = self.rect.center
new = transform.rotate(self.surface, self.rotatation)
self.rect = new.get_rect()
self.rect.center = old_center
screen.blit(new, self.rect)
Using a bit of trigonometry and the polygon function, I'm able to draw a rotated rectangle.
import math
import pygame.draw
def draw_rectangle(x, y, width, height, color, rotation=0):
"""Draw a rectangle, centered at x, y.
Arguments:
x (int/float):
The x coordinate of the center of the shape.
y (int/float):
The y coordinate of the center of the shape.
width (int/float):
The width of the rectangle.
height (int/float):
The height of the rectangle.
color (str):
Name of the fill color, in HTML format.
"""
points = []
# The distance from the center of the rectangle to
# one of the corners is the same for each corner.
radius = math.sqrt((height / 2)**2 + (width / 2)**2)
# Get the angle to one of the corners with respect
# to the x-axis.
angle = math.atan2(height / 2, width / 2)
# Transform that angle to reach each corner of the rectangle.
angles = [angle, -angle + math.pi, angle + math.pi, -angle]
# Convert rotation from degrees to radians.
rot_radians = (math.pi / 180) * rotation
# Calculate the coordinates of each point.
for angle in angles:
y_offset = -1 * radius * math.sin(angle + rot_radians)
x_offset = radius * math.cos(angle + rot_radians)
points.append((x + x_offset, y + y_offset))
pygame.draw.polygon(screen, color, points)
https://replit.com/#TimSwast1/RotateARectanlge?v=1
a quick replacement of the base pygame function adding rotation:
def rectRotated( surface, color, pos, fill, border_radius, angle ):
"""
- angle in degree
"""
max_area = max(pos[2],pos[3])
s = pg.Surface((max_area,max_area))
s = s.convert_alpha()
s.fill((0,0,0,0))
pg.draw.rect(s, color,(0,0,pos[2],pos[3]),fill, border_radius=border_radius)
s = pygame.transform.rotate(s,angle)
surface.blit( s, (pos[0],pos[1]) )
This code simulates rotating rectangles falling towards the ground. I used it in one of my games to make the background look awesome
import pygame
import random
class Square(pygame.sprite.Sprite):
def __init__(self, x, y):
super(Square, self).__init__()
self.win = win
self.color = (128, 128, 128)
self.speed = 3
self.angle = 0
self.side = random.randint(15, 40)
self.surface = pygame.Surface((self.side, self.side), pygame.SRCALPHA)
self.surface.set_colorkey((200,200,200))
self.rect = self.surface.get_rect(center=(x, y))
def update(self, win):
center = self.rect.center
self.angle = (self.angle + self.speed) % 360
image = pygame.transform.rotate(self.surface , self.angle)
self.rect = image.get_rect()
self.rect.center = center
self.rect.y += 1.5
if self.rect.top >= HEIGHT:
self.kill()
pygame.draw.rect(self.surface, self.color, (0,0, self.side, self.side), 4)
win.blit(image, self.rect)
if __name__ == '__main__':
pygame.init()
SCREEN = WIDTH, HEIGHT = 288, 512
win = pygame.display.set_mode(SCREEN, pygame.NOFRAME)
clock = pygame.time.Clock()
FPS = 60
count = 0
square_group = pygame.sprite.Group()
running = True
while running:
win.fill((200,200,200))
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
count += 1
if count % 100 == 0:
x = random.randint(40, WIDTH-40)
y = 0
square = Square(x, y)
square_group.add(square)
count = 0
square_group.update(win)
pygame.draw.rect(win, (30,30,30), (0, 0, WIDTH, HEIGHT), 8)
clock.tick(FPS)
pygame.display.update()
pygame.quit()
Here's the output, it's not an gif though
Now if you want color filled rectangle instead of bordered only, update this line on line 31
pygame.draw.rect(self.surface, self.color, (0,0, self.side, self.side))
and if you don't want the rectangle to fall down comment line 26
A concise and fast function to draw a rotated rectangle. Uses NumPy
def rectRotated(self, surface, rect, color, rotation):
"""
Draws a rotated Rect.
surface: pygame.Surface
rect: pygame.Rect
color: pygame.Color
rotation: float (degrees)
return: np.ndarray (vertices)
"""
# calculate the rotation in radians
rot_radians = -rotation * pi / 180
# calculate the points around the center of the rectangle, taking width and height into account
angle = atan2(rect.height / 2, rect.width / 2)
angles = [angle, -angle + pi, angle + pi, -angle]
radius = sqrt((rect.height / 2)**2 + (rect.width / 2)**2)
# create a numpy array of the points
points = np.array([
[rect.x + radius * cos(angle + rot_radians), rect.y + radius * sin(angle + rot_radians)]
for angle in angles
])
# draw the polygon
pygame.draw.polygon(surface, color, points)
# return the vertices of the rectangle
return points