I have a QGraphicsScene with a bunch of QRects. I'm using the QRects as an overlay over a QPixmap to show detected object. I need to clear all the rectangles from the scene, but can't figure out how.
What is the best way to clear all of the QRects from the QGraphicsScene without redrawing the image?
Right now I'm keeping a list of QRects as I add them to the scene and then to clear I try to loop through all the rects and use QGraphicsScene.removeItem(rect) to remove the rectangle, but that doesn't work. I get the following error:
TypeError: removeItem(self, QGraphicsItem): argument 1 has unexpected type 'QRectF'
I guess a QRect isn't a graphics item. But how should I remove it from the scene. Below is the code where I add or (try to) remove the Rects:
if self.is_viewing_tracks:
for track_id, positions in self.tracks.items():
for frameno, pos in positions.items():
if int(frameno) == self.frameno:
x, y = pos
rect = QRectF(x-5, y-5, 10, 10)
self.scene.addRect(rect)
text = self.scene.addText(str(track_id))
text.setPos(x, y)
self.rects.append(rect)
else:
for rect in self.rects:
self.scene.removeItem(rect)
What is the right way to remove QRects from a QGraphicsScene?
QRectFs are not added to the QGraphicsScene but are used to create QGraphicsRectItem through the addRect() method, so you must remove the QGraphicsRectItem and not the QRectF. The solution is to store the QGraphicsRectItems in self.rects:
if self.is_viewing_tracks:
for track_id, positions in self.tracks.items():
for frameno, pos in positions.items():
if int(frameno) == self.frameno:
x, y = pos
rect = QRectF(x - 5, y - 5, 10, 10)
rect_item = self.scene.addRect(rect)
text = self.scene.addText(str(track_id))
text.setPos(x, y)
self.rects.append(rect_item)
else:
for rect in self.rects:
self.scene.removeItem(rect)
self.rects = []
Related
I'm a newbie in python so please bear with me. I was making a program that draws a turtle shape using OOP and I'm running into some issues. The first issue I had is in my drawShape method I am required to draw a shape with the sides and length of my argument. It can be any regular shape so I chose square but I used t.forward inserting d as the length of the shape how can I add s as the side of my shape to draw it as required. The second issue I had is in my __str__ method I wanted to return the current state of my turtle position I used turtle.pos to return the position but how do I also include the current heading of the turtle. I added comments on what I'm trying to do all over my code to make it easier to understand my code. Any help is appreciated. Thanks in advance.
import turtle
import random
class Terrapin:
#list of 5 color names
colors = ["green", "yello", "red", "blue", "black"]
def __init__(self, win, tur, x, y, dir, color, width):
#win: The turtle screen/window. In __init__, create the window
self.win = win
#tur: The turtle object itself. In __init__,
#create a turtle and store it in this attribute
self.tur = tur
#current x location of the Turtle. Initialize to 0
self.x = x
#Current y location of the Turtle. Initialize to 0
self.y = y
#Current heading of the Turtle (angle). Initialize to 0
self.dir = dir
#Current color of the Turtle. Pick a random value from colors,
#and set the Turtle to that color
self.color = color
#Current width of the Turtle. Pick a random value from 1 to 5,
#and set the Turtle's width to that value
self.width = width
x = 0
y = 0
dir = 0
color = random.choice(colors)
width = randint(1, 5)
def updateLoc(self, newX, newY, newDir):
#Without drawing a line, move the Turtle to newX,
#newY,facing newDir.
#Update self.x, self.y, and self.dir to the new values
turtle.penup()
turtle.goto(newX, newY)
setheading(newDir)
self.x = newX
self.y = newY
self.dir = newDir
def whereAmI(self):
#Return a tuple of x, y, and dir.
return ("x", "y", "dir")
def drawShape(self, s, d):
#Draw a regular shape with "s" sides of length "d", starting at x, y, dir.
#Leave the Turtle pointing in the direction it started
#(i.e. self.dir at the end must equal self.dir at that start).
#Regular means equal length sides, like a triangle, square, hexagon, etc.
startPos = turtle.setworldcoordinates(x, y, dir)
turtle.setworldcoordinates(x, y, dir)
turtle.forward(d)
t.left(90)
turtle.forward(d)
t.left(90)
turtle.forward(d)
t.left(90)
turtle.forward(d)
t.left(90)
setheading(startPos)
def setWidth(self, w):
#Set the Turtle's width to w, and save in self.width.
self.width = w
def newColor(self):
#Set the Turtle's color to a new random value, and save in self.color.
newColor = turtle.pencolor(random.choice(colors))
turtle.pencolor(random.choice(colors))
self.color = newColor
def __str__(self):
#Returns the current turtle state as "x, y # dir"
#If the Turtle is at 10, 100 with a heading of 270, this will print "10, 100 # 270"
turtle.pos()
doug = Terrapin()
doug.drawShape(4, 200) # Draws a rectangle
doug.newColor()
doug.setWidth(4)
doug.drawShape(5, 100) # Draws a pentagram
doug.updateLoc(100, 50, 0)
doug.drawShape(3, 50)
print(doug)
You need something like this
doug = Terrapin(1,2,3,4,5,6,7,8)
But more broadly...
If you aren't sure what values you want to use, you probably need to think about what you program is going to do before you make it.
You look like you're using Turtle graphics, i would try to play around with that library before you make any object oriented stuff
UPDATED
"The first issue I had is in my drawShape method I am required to draw a shape with the sides and length of my argument. It can be any regular shape so I chose square but I used t.forward inserting d as the length of the shape how can I add s as the side of my shape to draw it as required."
This sounds like homework lol
I think you are talking about Equilateral polygons.
https://i.stack.imgur.com/Hy39S.png
So the sum of interior angles is (n-2)*180
So a single interior angle is (n-2)*180/n
So and then the angle to move for a d sided polygon is
180 - (d-2)*180/d
EI 4 sided square
180-(4-2)*180/4 = 90
"The second issue I had is in my str method I wanted to return the current state of my turtle position I used turtle.pos to return the position but how do I also include the current heading of the turtle."
So decide if you want to print or return this.
I think you probably want to print.
print(x,", ",y," # ",direction)
Note that dir is highlighted differently because it means something else and you cannot use it as a variable
Whenever i try to move a rectangle in a scene, the origin point of the rectangle appears to change to the location where the rectangle was before updating the location.
So if i create the rectangle at (0,0) and move it using rect.setRect(x, y) then returning the position would yield (0,0) instead of (x, y).
If you would move it in the QGraphicsScene using your mouse it would return the correct (x, y).
The code i use to create the rectangle is as following:
class placeableObject:
def __init__(self, index, scene, QPen, QBrush, width=100, height=100):
"""Parent class for placeable objects"""
self.width = float(width)
self.height = float(height)
self.index = index
self.rect = scene.addRect(0, 0, int(width), int(height), QPen, QBrush)
self.rect.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
To move this rectangle i have the following embedded function and a function to return the position:
def getPos(self):
"""Returns a list with the x,y position of the object in the scene"""
return [self.rect.scenePos().x(), self.rect.scenePos().y()]
def move(self, x, y):
"""Moves the object in the editor view to coordinatex x,y"""
self.rect.setRect(x, y, self.width, self.height)
You are forgetting an important aspect of graphics items: their [scene] position is not always the top left corner of the object that is actually shown to the user.
This is clear when adding items using scene.add*() (this was already explained on this question).
As the documentation explains:
Note that the item's geometry is provided in item coordinates, and its position is initialized to (0, 0). For example, if a QRect(50, 50, 100, 100) is added, its top-left corner will be at (50, 50) relative to the origin in the item's coordinate system.
The item position is not the rectangle position, so when you use setRect you are not moving the item, but setting a new rectangle at the specified position, while leaving the item at (0, 0) coordinates of its system; note that this also means that if the item has no parent, scenePos() will be the same as pos(), otherwise it's relative to the parent.
If you want to know the actual position of the top left corner of the rectangle, you could use one of the following:
item.sceneBoundingRect().topLeft()
item.scenePos() + item.rect().topLeft()
If you are always adding the rectangle at (0, 0), then you can just use setPos(), but if you need to compute the position based on the current, actual rectangle position, you have to use one of the functions above.
Note that rectangles can also have negative sizes, so if you need the top left corner of the visible rectangle, you need to normalize it:
item.scenePos() + item.rect().normalized().topLeft()
It seems that i have figured it out.
I changed the move function to the following:
def move(self, x, y):
"""Moves the object in the editor view to coordinatex x,y"""
self.rect.setPos(x, y)
That returns the correct positions within the scene for me!
Thanks anyway :)
We would like to create a rectangle with width w and height h, on a surface whose width is a and whose height is b.
What would be the values of left, top, width, height that we have to pass to the pygame.Rect function, if the rectangle needs to be at a distance x_offset from the left edge of the surface and centered vertically on the surface?
I got this question and I know that left would be equal to x_offset but I have no idea how to figure out any of the other ones I've tried drawing it out.
Just create your Rect object first with the values you know:
rect = pygame.Rect(x_offset, 0, w, h)
and center the rect by using its centery attribute:
rect.centery = the_other_surface.get_rect().centery
It can help if you rename the variables into something readable.
For example:
rect_width = w
rect_height = h
surface_width = a
surface_height = b
rect_width and rect_height will be the Rect's width and height.
The x_offset will be the left.
rect_left = x_offset
To center it vertically, find the vertical center:
surface_center_v = surface_height / 2
Then place the rectangle there.
rect_top = surface_center_v
However this only places the rectangle's top edge, and we want the vertical center.
So adjust the rectangle's position upwards by half of the rectangle's height, to make the rectangle's vertical center align with the surface's vertical center.
rect_top -= rect_height / 2
Now you have all of rect_left, rect_top, rect_width and rect_height.
To use all of the specified variables (rather than hard-coding absolute sizes into the program), I would make use of pygame's Rectangle object for both the surface and the rectangle we want to draw.
import pygame
from time import sleep
pygame.init()
# Declare our variables and initialize
a,b = 0,0 # Declare screen dimensions.
w,h = 0,0 # Declare rectangle dimensions.
x,y = 0,0 # Declare placement locations on screen surface.
# Now set specific values to our variables
a = 500 # Screen Width
b = 500 # Screen Height
# We could use any surface here (such as for a sprite), but let's use the screen.
screen = pygame.display.set_mode((a,b),32) # Create a screen surface of size a X b
screen.fill(pygame.Color("DARKBLUE")) # Fill it with Dark Blue.
screen_rect = screen.get_rect() # Create a rectangle object for the screen.
w = 200 # Make the rectangle 200 wide.
h = 100 # Make the rectangle 100 high.
rec = pygame.Rect((x,y,w,h)) # Create a rectangle object for our drawn rectangle.
# Rec holds all of the placement and size values for the rectangle we want to draw.
rec_color = (255,0,0) # Let's draw a red rectangle.
distance_x = 50 # Place the rectangle 50 pixels over from the left side of the screen.
rec.x = distance_x # Set rec's x value equal to distance_x.
rec.centery = screen_rect.centery # Set rec's y value to center of the screen y-axis
# Pygame's Rectangle Object handles the calculations for us. NICE!
pygame.draw.rect(screen,rec_color,rec,2) # Draw a rectangle on the screen using a line
# that is 2-pixels wide. (Use 0 to fill.)
pygame.display.flip() # Show the screen.
print("\nrec =",rec,end="\n\n") # Let's see rec's final values.
sleep(3) # Sleep for 3 seconds.
exit() # Quit
Currently, My next project is going to be a platformer and when I look around stackoverflow for research on several mechanics, I see many people doing the same thing: They save a layout with some variable, then go and unload it somewhere and it just renders in the game. I was interested, so I looked further and I found nothing on how to load/unload states like that, or maybe I'm just not wording my search correctly.
Either way, How do I do this?
ex: I would save a level layout as either an array or a single multi-line string and then somehow generate a single tile sprite for each letter, like T.
import pygame
# Storage method A
level = '''
X X X X X
X X X X X
T T X X X
X X X T T
T T T T T
'''
# Storage Method B
level2 = [
'XXXXX',
'XXXXX',
'TTXXX',
'XXXTT',
'TTTTT'
]
# X is blank space, T is tiles
# Then what? Thats what I need to know.
# If someone already answered this and I'm just not using the right keywords let me know.
You will need to calculate the pixel-positions for each tile. To draw any tile, you need to know
the size of the canvas
the size of your grid
the position of the tile in your grid
1: Finding the size of your canvas should be trivial.
2: For the second storage method you can do
height = len(level2)
width = len(level2[0]) #Assuming all rows are of equal length and there's at least one row
3: We're going to iterate through the rows and characters which will keep track of our position in the grid on the side.
def draw_tiles(canvas_width, canvas_height, width, height, level2):
for row in range(height):
for column in range(width):
if list(level2[row])[column] == 'T':
pixel_x = int(canvas_width/width)*column
pixel_y = int(canvas_height/height)*row
draw_tile(pixel_x, pixel_y)
Now all you need to do is define the draw_tile(x, y) function to draw a tile on the canvas with its top-left corner being on the pixel co-ordinates (x, y). I'm sure pygame has something for that.
Make sure you set the grid width/height so that canvas_width/width and canvas_height/height are both integers. Otherwise your tiles will be slightly offset due to rounding.
You could iterate over the enumerated rows and characters in the layout, create the tile instances and add them to a sprite group.
In the example I just give the tiles different colors depending on the character in the layout (X=blue, T=green) before I add them to the group, but you could also create completely different Tile types or subclasses if the character is a 'T' or an 'X'.
import pygame
class Tile(pygame.sprite.Sprite):
def __init__(self, pos, color):
super().__init__()
self.image = pygame.Surface((50, 50))
self.image.fill(color)
self.rect = self.image.get_rect(topleft=pos)
def create_tile_group(layout):
"""Turn the layout into a sprite group with Tile instances."""
group = pygame.sprite.Group()
for y, row in enumerate(layout):
for x, tile in enumerate(row):
if tile == 'T':
color = (50, 150, 50)
else:
color = (0, 0, 200)
group.add(Tile((x*tile_size, y*tile_size), color))
return group
pygame.init()
screen = pygame.display.set_mode((250, 250))
clock = pygame.time.Clock()
layout1 = [
'XXXXX',
'XTXXX',
'XXXXT',
'XXXXX',
'TTTTT',
]
tile_size = 50
tile_group = create_tile_group(layout1)
loop = True
while loop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
loop = False
tile_group.update()
screen.fill((30, 30, 30))
tile_group.draw(screen)
pygame.display.flip()
clock.tick(30)
If you get performance problems because you blit too many small surfaces, you could blit them onto a big background surface before the while loop starts and then just blit the background once each frame.
There is no magic here: "it just renders in the game" is not accurate. There's more software behind the rendering call, a module that defines the tile sprites, scans the level character by character, and places the sprites accordingly. The developer (e.g. you) decides on the level representation, sprite form, sprite placement, etc.
You have several bits of code to write. The good news is that you decide the format; you just have to stay consistent when you write those modules.
I'm looking for the easiest way to implement this. I'm trying to implement platforms (with full collision detection) that you can draw in via mouse. Right now I have a line drawing function that actually draws small circles, but they're so close together that they more or less look like a line. Would the best solution be to create little pygame.Rect objects at each circle? That's going to be a lot of rect objects. It's not an image so pixel perfect doesn't seem like an option?
def drawGradientLine(screen, index, start, end, width, color_mode):
#color values change based on index
cvar1 = max(0, min(255, 9 * index-256))
cvar2 = max(0, min(255, 9 * index))
#green(0,255,0), blue(0,0,255), red(255,0,0), yellow(255,255,0)
if color_mode == 'green':
color = (cvar1, cvar2, cvar1)
elif color_mode == 'blue':
color = (cvar1, cvar1, cvar2)
elif color_mode == 'red':
color = (cvar2, cvar1, cvar1)
elif color_mode == 'yellow':
color = (cvar2, cvar2, cvar1)
dx = end[0] - start[0]
dy = end[1] - start[1]
dist = max(abs(dx), abs(dy))
for i in xrange(dist):
x = int(start[0]+float(i)/dist*dx)
y = int(start[1]+float(i)/dist*dy)
pygame.draw.circle(screen, color, (x, y), width)
That's my drawing function. And here's my loop that I have put in my main game event loop.
i = 0
while (i < len(pointList)-1):
drawGradientLine(screen, i, pointList[i], pointList[i + 1], r, mode)
i += 1
Thanks for any help, collision detection is giving me a huge headache right now (still can't get it right for my tiles either..).
Any reason you want to stick with circles?
Rectangles will make the line/rectangle a lot more smooth and will make collision detecting a lot easier unless you want to look into pixel perfect collision.
You also don't seem to save your drawn objects anywhere (like in a list or spritegroup), so how are you going to check for collision?
Here's a leveleditor I did for game awhile back, it's not perfect, but it works:
https://gist.github.com/marcusmoller/bae9ea310999db8d8d95
How it works:
The whole game level is divided up into 10x10px grid for easier drawing
The leveleditor check if the mouse is being clicked and then saves that mouse position
The player now moves the mouse to another position and releases the mouse button, the leveleditor now saves that new position.
You now have two different coordinates and can easily make a rectangle out of them.
Instead of creating a whole bunch of rect objects to test collision against, I'm going to recommend creating something called a mask of the drawn-in collideable object, and test for collision against that. Basically, a mask is a map of which pixels are being used and which are not in an image. You can almost think of it as a shadow or silhouette of a surface.
When you call pygame.draw.circle, you are already passing in a surface. Right now you are drawing directly to the screen, which might not be as useful for what I'm suggesting. I would recommend creating a rect which covers the entire area of the line being drawn, and then creating a surface of that size, and then draw the line to this surface. My code will assume you already know the bounds of the line's points.
line_rect = pygame.Rect(leftmost, topmost, rightmost - leftmost, bottommost - topmost)
line_surf = pygame.Surface((line_rect.width, line_rect.height))
In your drawGradientLine function, you'll have to translate the point coordinates to the object space of the line_surf.
while (i < len(pointList)-1):
drawGradientLine(line_surf, (line_rect.x, line_rect.y), i, pointList[i], pointList[i+1], r, mode)
i += 1
def drawGradientLine(surf, offset, index, start, end, width, color_mode):
# the code leading up to where you draw the circle...
for i in xrange(dist):
x = int(start[0]+float(i)/dist*dx) - offset[0]
y = int(start[1]+float(i)/dist*dy) - offset[1]
pygame.draw.circle(surf, color, (x, y), width)
Now you'll have a surface with the drawn object blitted to it. Note that you might have to add some padding to the surface when you create it if the width of the lines you are drawing is greater than 1.
Now that you have the surface, you will want to create the mask of it.
surf_mask = pygame.mask.from_surface(line_surf)
Hopefully this isn't getting too complicated for you! Now you can either check each "active" point in the mask for collision within a rect from your player (or whatever other objects you want to collide withe drawn-in platforms), or you can create a mask from the surface of such a player object and use the pygame.Mask.overlap_area function to check for pixel-perfect collision.
# player_surf is a surface object I am imagining exists
# player_rect is a rect object I am imagining exists
overlap_count = surf_mask.overlap_area(player_surf, (line_rect.x - player_rect.x, line_rect.y - player_rect.y))
overlap_count should be a count of the number of pixels that are overlapping between the masks. If this is greater than zero, then you know there has been a collision.
Here is the documentation for pygame.Mask.overlap_area: http://www.pygame.org/docs/ref/mask.html#pygame.mask.Mask.overlap_area