Rotating a rectangle (not image) in pygame - python

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

Related

ValueError: subsurface rectangle outside surface area

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.

How Can I Rotate My Sprite Towards The Player Even If Player Moves Position

so I have a sprite that shoots projectiles the projectiles shoot at the player I was wondering how I could make the sprite rotate to the player? VIDEO the player bullets attack the player what ever position he is at but how could I make the cannon sprite do the same?
my cannon class
shotsright = pygame.image.load("canss.png")
class enemyshoot:
def __init__(self,x,y,height,width,color):
self.x = x
self.y =y
self.height = height
self.width = width
self.color = color
self.shootsright = pygame.image.load("canss.png")
self.shootsright = pygame.transform.scale(self.shootsright,(self.shootsright.get_width()-150,self.shootsright.get_height()-150))
self.rect = pygame.Rect(x,y,height,width)
self.health = 10
self.hitbox = (self.x + -20, self.y + 30, 31, 57)
def draw(self):
self.rect.topleft = (self.x,self.y)
window.blit(self.shootsright,self.rect)
self.hits = (self.x + 20, self.y, 28,60)
pygame.draw.rect(window, (255,0,0), (self.hitbox[0], self.hitbox[1] - 60, 50, 10)) # NEW
pygame.draw.rect(window, (0,255,0), (self.hitbox[0], self.hitbox[1] - 60, 50 - (5 * (10 - self.health)), 10))
self.hitbox = (self.x + 100, self.y + 200, 81, 87)
black = (0,0,0)
enemyshoots1 = enemyshoot(1100,10,100,100,black)
enemyshooting = [enemyshoots1]
my full code: script
Basically you can rotate the image to point to some co-ordinate quite simply. You create a vector of the distance between your cannon and the player. This is then converted to an angle with the Vector.as_polar() function. The angle is used to rotate an original copy of the bitmap to the desired angle. Image rotation can be fairly CPU-consuming.
class enemyshoot:
def __init__(self, x, y, height, width, color):
[...]
# Make a Reference Copy of the bitmap for later rotation
self.original = pygame.image.load("canss.png")
self.image = self.original
self.rect = self.image.get_rect()
self.position = pygame.math.Vector2( ( x, y ) )
def lookAt( self, coordinate ):
# Rotate image to point in the new direction
delta_vector = coordinate - self.position
radius, angle = delta_vector.as_polar()
self.image = pygame.transform.rotozoom( self.original, -angle, 1 )
# Re-set the bounding rectangle and position since
# the dimensions and centroid will have (probably) changed.
current_pos = self.rect.center
self.rect = self.image.get_rect()
self.rect.center = current_pos
So the idea is you take the original bitmap, and rotate from that each time. If you keep rotating the same bitmap the slight differences will compound and the image will loose definition.
The other thing of note is that we're rotating around the bitmap's centre-point. But the rotation changes the bitmap's dimensions, also changing the centre-point. So this needs to be re-calculated and preserved.
You may find it useful to cache the rotated images once they have been rotated to save CPU. Maybe round the angle to the nearest 10 degrees, and then see if it's already been rotated. This would also allow you to pre-rotate all images and store them in a look-up table.
Edit: Calling lookAt() the mouse position:

pygame blitting - center

I am trying to make a script in python for pygame to draw a button with text centered, but when I blit onto the screen, it blits to the x and y I give it, not a proportionally centered location. I want to be able to center it to a set of (x,y,w,h). How would I do this? Here's my code:
# Imports
import pygame
class Text:
'Centered Text Class'
# Constructror
def __init__(self, text, (x,y,w,h), color = (0,0,0)):
self.x = x
self.y = y
self.w = w
self.h = h
# Start PyGame Font
pygame.font.init()
font = pygame.font.SysFont("sans", 20)
self.txt = font.render(text, True, color)
# Draw Method
def Draw(self, screen):
coords = (self.x, self.y)
screen.blit(self.txt, coords)
Edit: Comments, yes I know but I only used x and y as temporary variables because I have no idea what the centered x and y would be to center the text. (I want to know how to center its CENTER to a rect, not its top left corner)
You'll want to use the font.size() method to determine how large the rendered text will be.
Something like:
class Text:
"""Centered Text Class"""
# Constructror
def __init__(self, text, (x,y), color = (0,0,0)):
self.x = x #Horizontal center of box
self.y = y #Vertical center of box
# Start PyGame Font
pygame.font.init()
font = pygame.font.SysFont("sans", 20)
self.txt = font.render(text, True, color)
self.size = font.size(text) #(width, height)
# Draw Method
def Draw(self, screen):
drawX = self.x - (self.size[0] / 2.)
drawY = self.y - (self.size[1] / 2.)
coords = (drawX, drawY)
screen.blit(self.txt, coords)
I think something like the following does what you want. It uses pygame.font.Font.size() to determine the amount of space needed to render the text, and then centers that within rectangular region defined by CenteredText instance.
class CenteredText(object):
""" Centered Text Class
"""
def __init__(self, text, (x,y,w,h), color=(0,0,0)):
self.x, self.y, self.w, self.h = x,y,w,h
pygame.font.init()
font = pygame.font.SysFont("sans", 20)
width, height = font.size(text)
xoffset = (self.w-width) // 2
yoffset = (self.h-height) // 2
self.coords = self.x+xoffset, self.y+yoffset
self.txt = font.render(text, True, color)
def draw(self, screen):
screen.blit(self.txt, self.coords)
# for testing purposes, draw the rectangle too
rect = Rect(self.x, self.y, self.w, self.h)
pygame.draw.rect(screen, (0,0,0), rect, 1)
Given:
text = CenteredText('Hello world', (200,150,100,100))
Here's the results from calling text.draw(screen) in a 500x400 pixel window.
If you want to perfectly centre an object:
When you give Pygame coordinates for an object it takes them to be the coordinates for the upper left corner. Thus we have to halve the x and y coordinates.
coords = (self.x/2, self.y/2)
screen.blit(self.txt, coords)
Other than that your question is unclear.
Let pygame do the maths for you by using Rects and assigning the text's center to the destination center:
# Start PyGame Font
pygame.font.init()
font = pygame.font.SysFont("sans", 20)
class Text:
'Centered Text Class'
# Constructror
def __init__(self, text, (x,y,w,h), color = (0,0,0)):
self.rect = pygame.Rect(x, y, w, h)
self.txt = font.render(text, True, color)
# Draw Method
def Draw(self, screen):
coords = self.txt.get_rect()
coords.center = self.rect.center
screen.blit(self.txt, coords)

Pygame Scrolling Map

I'm making a rogue-like and I trying to make a camera follow the player as he moves by the map.
I was able to make the Draw function only happens when the tiles are inside the camera viewport[show in gray], but I can't make the camera stay in the corner of the window.
This is how it is:
And this is How it should be:
Is there a way to 'crop' the screen Surface, or perhaps copy only whats inside the camera viewport and blit it in the screen again.
Probably I'm doing this the hard way.
I'm iterating over the whole map, creating a rectangle for each tile, and checking if it's inside the Camera Viewport Rect using the '.contains()'.
EDIT:
This is how I'm drawing the map:
for x in xrange(mymap.width):
for y in xrange(mymap.height):
lit = field_of_view.lit(x, y)
visited = field_of_view.visited(x, y)
graphic = mymap.tileAt(x, y).graphic
if lit:
color = mymap.tileAt(x, y).color
elif visited:
color = GRAY
else:
color = BLACK
renderedgraphic = myfont.render(graphic, 1, color)
screen.blit(renderedgraphic, (x*TILESIZE, y*TILESIZE))
I do the same thing for the player, monsters, items and etc, but everything in it's own classmethod.
My camera is set like this:
class Camera(Object):
def __init__(self, x, y):
graphic = ''
Object.__init__(self, graphic, x, y)
self.rect = pygame.Rect(x, y, CAMERA_WIDTH * 2 + 5, CAMERA_HEIGHT * 2 + 5)
def update(self):
self.x = PLAYER.x
self.y = PLAYER.y
startx = ((self.x * TILESIZE) - CAMERA_WIDTH) + 5
starty = ((self.y * TILESIZE) - CAMERA_HEIGHT) + 5
self.rect = pygame.Rect(startx, starty, CAMERA_WIDTH * 2 + 5, CAMERA_HEIGHT * 2 + 5)
So I tried what user3762084 said.
in short:
for x in xrange(mymap.width):
for y in xrange(mymap.height):
... # do fov stuff
tile = Tile.At(x, y) # get tile instance
if tile:
tile.update() # update it's relative position
screen.blit(renderedgraphic, (tile.relX * TILESIZE, tile.relX * TILESIZE)) # blit the postion to it's relative position
This is what happens:
It's all squished in the side of the window. And if the player moves it all goes black.
What I have done before to make a scrolling environment was to give each object its coordinates in the world, and then give your camera a position. When you are drawing, you then calculate the position of each object on the screen based on the object's actual coordinates and the camera's coordinates.
class Tile:
def __init__(self, x, y, other_variables):
self.x = x
self.y = y
# relative vars for later
self.relX = None
self.relY = None
# then your camera should have a position as well as its width and height:
class Camera:
def __init__(self, x, y, width, height):
# assign those variables here
# your drawing function:
for tile in tiles:
tile.relX = tile.x - camera.x
tile.relY = tile.y - camera.y
# blit your tiles to the screen at the relative coordinates
In addition, you could also implement checks to see if the tile is completely outside of the camera space and not draw those tiles.
Ok I got what I was looking for, I did a litte hack tho. So if anyone have a better way to do this.
I changede the way my map was drawing
I took the Camera Rect positions [topleft and bottomright];
Converted it to World Position;
Iterate over it, with a enumerator too;
Did any lit/visited FOG calcunations with X and Y;
And Blited in the screen using the enumerators 'i' and 'j'.
Here's the code:
topleft = Map.toWorld(camera.rect.topleft)
bottomright = Map.toWorld(camera.rect.bottomright)
for i, x in enumerate(xrange(topleft[0], bottomright[0])):
for j, y in enumerate(xrange(topleft[1], bottomright[1])):
tile = mymap.tileAt(x, y)
object = [obj for obj in Object.OBJECTS if obj.pos == (x,y)]
if tile:
lit = field_of_view.lit(x, y)
visited = field_of_view.visited(x, y)
graphic = tile.graphic
if lit:
color = tile.color
elif visited:
color = GRAY
else:
color = BLACK
renderedgraphic = myfont.render(ch, 1, graphic)
screen.blit(renderedgraphic, Map.toScreen((i + 1, j)))
if object:
Draw.drawObject(object[0], Map.toScreen((i + 1, j)))
The only issue now is when the player is at the sides of the map it shows a black border.

Why pygame.draw.circle doesn't work in this code?

I have a ball object that waits one second in the middle of the screen before moving. This is the update method:
def update(self, dt):
now = pygame.time.get_ticks() / 1000
if now - self._spawn_time >= BALL_WAIT_TIME:
self.rect = self.calcnewpos(dt)
self.handle_collision()
else:
step = 255 / FPS
value = int(self._frame * step)
rgb = (value, value, value)
self._draw_ball(rgb)
self._frame += 1
That one second happens below the else clause. My goal is to have the ball image go from black (8, 8, 8) to white (255, 255, 255) in that time but as it is _draw_ball doesn't do anything.
def _draw_ball(self, rgb):
pygame.draw.circle(self.image, rgb, self.rect.center, BALL_RADIUS)
The funny things is, it works the first time when it's called in __init__. I've tried taking lines out of update and testing this code on its own in another module but can't figure out what's the problem. Why is pygame.draw.circle not drawing the the circles in the colors passed by the update method?
Here is the whole class:
#!python3
class Ball(pygame.sprite.Sprite):
def __init__(self, game, velocity):
super(Ball, self).__init__()
self.image = pygame.Surface((BALL_RADIUS*2, BALL_RADIUS*2))
self.image.fill(BLACK)
self.image.set_colorkey(BLACK, RLEACCEL)
self.rect = self.image.get_rect()
screen = pygame.display.get_surface()
self.area = screen.get_rect().inflate(-GAP*2, 0)
self.velocity = velocity
self.game = game
self.start_to_the = random.choice(['left', 'right'])
self._draw_ball(BALL_COLOR)
self.reinit()
def _draw_ball(self, rgb):
pygame.draw.circle(self.image, rgb, self.rect.center, BALL_RADIUS)
def _hit_topbottom(self):
return self.rect.top < self.area.top or self.rect.bottom > self.area.bottom
def _hit_leftright(self):
if self.rect.left < self.area.left: return 'left'
elif self.rect.right > self.area.right: return 'right'
else: return 0
def reinit(self):
self._spawn_time = pygame.time.get_ticks() / 1000
self._frame = 1
if self.start_to_the == 'left':
self.velocity = Vec2D(-BALL_SPEED, 0)
else:
self.velocity = Vec2D(BALL_SPEED, 0)
self.rect.center = self.area.center
def update(self, dt):
now = pygame.time.get_ticks() / 1000
if now - self._spawn_time >= BALL_WAIT_TIME:
self.rect = self.calcnewpos(dt)
self.handle_collision()
else:
step = 255 / FPS
value = int(self._frame * step)
rgb = (value, value, value)
self.image.fill(rgb)
self._frame += 1
def calcnewpos(self, dt):
(dx, dy) = self.velocity.x, self.velocity.y
return self.rect.move(dx, dy)
def handle_collision(self):
(dx, dy) = self.velocity.x, self.velocity.y
if self._hit_topbottom():
dy = -dy
elif self._hit_leftright():
side = self._hit_leftright()
self.game.enemy.update_hitpos()
self.game.increase_score(side)
if side == 'left': self.start_to_the = 'right'
elif side == 'right': self.start_to_the = 'left'
self.reinit()
return
else:
if self.hit_paddle():
paddle = self.hit_paddle()
paddle.handle_collision()
if paddle == self.game.paddles['left']:
self.rect.left = GAP + PADDLE_WIDTH
elif paddle == self.game.paddles['right']:
self.rect.right = SCREEN_WIDTH - (GAP + PADDLE_WIDTH)
dx = -dx
dy = (self.rect.centery - paddle.rect.centery)
dy = (math.copysign(min(abs(dy) // 16 * 16, 32), dy)) / 4
paddle.handle_collision()
self.velocity = Vec2D(dx, dy)
def hit_paddle(self):
paddles = self.game.paddles.values()
for paddle in paddles:
if self.rect.colliderect(paddle.rect): return paddle
I don't see any calls to pygame.display.flip. This is the function responsible for updating the screen with the current state of your display surface. It also doesn't look like you are redrawing your ball on the display surface. Somewhere, probably in update or _draw_ball there should be calls like the following:
self.screen.draw(self.image, self.rect)
pygame.display.flip()
The first line draws the image of the ball to the surface representing the screen, and the second call updates the screen to reflect the new surface.
My second theory is that you are drawing new frames of the ball outside of the bounds of self.image. This theory comes from seeing that are moving the ball's rect according to velocity, but always drawing a circle on self.image at self.rect's center. The size of self.image is only BALL_RADIUS*2, which makes it easy to draw outside of it if self.rect's topleft becomes something that's not (0,0). Even if this isn't your problem now, it will be later.
in pygame the draw circle statement is :
pygame.draw.circle (SURFACE, COLOUR, (X, Y), SIZE, 0)
if you put your screen.fill statement after the circle statement then it will draw the circle and immediately cover it up with the colour of the screen, making your circle disappear a 10000th of a second after its drawn.

Categories