0

I'm sorry if this question is a duplicate, but I've followed numerous pages on this site trying to find an answer, and I haven't been able to get one to work yet. Some of the pages I've used are:

Pygame mouse clicking detection

self.rect not found?

how does collide.rect work in pygame

It may be my lack of coding comprehension, and I apologize if that's the case. However, I found the most "helpful" page to be the first link. Using Sloth's first answer, it seemed very simple and something I would be able to do. Once his method did not work, I tried searching around even more for on YouTube and other sites. I also couldn't find an answer.

All I want to do is simply print "clicked" when a sprite is clicked. Here is my code.

import pygame
size = (700,500)
screen = pygame.display.set_mode(size)
done = False
clock = pygame.time.Clock()
itunes = pygame.image.load('itunes.png')
screen.blit(itunes,(200,200))
sprites = [itunes]
while not done:
    for event in pygame.event.get():
        if event.type == pygame.MOUSEBUTTONDOWN:
            pos = pygame.mouse.get_pos()
            clicked_sprites = [s for s in sprites if s.rect.collidepoint(pos)]
            for i in clicked_sprites:
                print('clicked sprite')
    pygame.display.flip()
    clock.tick(60)

I've learned very little of the Python language, but I found myself getting bored making the same text-based programs and would like to move on.

EDIT: I'm sorry, I should have stated what error I was getting. I'm getting: AttributeError: 'pygame.Surface' object has no attribute 'rect'

EDIT: Having trouble drawing the image on the screen

import pygame
size = (700,500)
screen = pygame.display.set_mode(size)
done = False
clock = pygame.time.Clock()
white = (0,0,0)
image = 'itunes.png'
class Thing(pygame.sprite.Sprite):
    def __init__(self, image, x, y):
        self.image = pygame.image.load('itunes.png')
        self.x = x
        self.y = y

    @property
    def rect(self):
        return self.image.get_rect(topleft=(self.x, self.y))

itunes = Thing(image,200,200)

SpriteGroup = pygame.sprite.Group()    
SpriteGroup.add(itunes)                

while not done:
    for event in pygame.event.get():
        if event.type == pygame.MOUSEBUTTONDOWN:
            pos = pygame.mouse.get_pos()
            clicked_sprites = [s for s in SpriteGroup if s.rect.collidepoint(pos)]
            for i in clicked_sprites:
                print('clicked sprite')
    screen.fill((0, 0, 0))             
    SpriteGroup.draw(screen)           
    pygame.display.flip()
    clock.tick(60)
Community
  • 1
  • 1

2 Answers2

1

Images are pygame.Surface objects, and they don't have a rect as an attribute. You can get one for that Surface object, however, by calling SurfaceObject.get_rect(). This will create a pygame.Rect object that represents the dimensions of the Surface, if not its position.

Depending on how often your object would create a new rect or modify it based on its position in the window, you might subclass pygame.sprite.Sprite and use a property to retrieve the Rect at its correct position. For example:

class Thing(pygame.sprite.Sprite):
    def __init__(self, image, x, y):
        super(Thing, self).__init__()    #don't forget to do this!! (like I did :D, haha)
        self.image = pygame.image.load(image)
        self.x = x
        self.y = y

    @property
    def rect(self):
        return self.image.get_rect(topleft=(self.x, self.y))

The surface.get_rect() call takes keyword arguments that allow you to set attributes of the object as it's being created. In this way, it's basically a one-liner for:

new_rect = self.image.get_rect()
new_rect.x = self.x
new_rect.y = self.y
return new_rect

This whole process returns a rect which is in the correct position in the window, such that it can be used for collision detection or just to see if it's the object that was clicked as in your example.

Another upshot of this approach (using a property for the rect) is that it makes the object work well with the pygame.sprite.Group.draw() method, which draws all Sprite objects in that Group object by blitting their obj.image and obj.rect attributes.

So you take advantage of this by turning sprites from a list to a sprite.Group object, and making the following changes to your code:

SpriteGroup = pygame.sprite.Group()    #use these for working with Sprite objects
SpriteGroup.add(itunes)                #add itunes to sprites Group
while not done:
    for event in pygame.event.get():
        if event.type == pygame.MOUSEBUTTONDOWN:
            pos = pygame.mouse.get_pos()
            clicked_sprites = [s for s in sprites if s.rect.collidepoint(pos)]
            for i in clicked_sprites:
                print('clicked sprite')
    screen.fill((0, 0, 0))             #wipes the screen
    SpriteGroup.draw(screen)           #draws every Sprite object in this Group
    pygame.display.flip()
    clock.tick(60)
  • Thank you for the help. I'm now able to click on the screen and it will print out "clicked sprite" whenever I click in it's collision points. however, now I just don't know how to draw the image onto the screen... Before I was using `screen.blit(itunes,(200,200))` which gives me the error "TypeError: argument 1 must be pygame.Surface, not Thing" and I'm not really sure what to do from here. – user3780831 Aug 15 '14 at 20:15
  • Before, `itunes` was a `Surface`. The `screen.blit` method is a `Surface` method (your `pygame.display.set_mode()` call returns the window as a Surface). `Surface.blit()` takes two arguments: a Surface and a Rect. The `Thing` Sprite has obj.image and obj.rect, and these should suffice for `screen.blit`. Alternatively, I would suggest making your `sprites` list a `pygame.sprite.Group()` object, and then calling `sprites.draw(screen)` to automatically take care of the blitting. This will automatically iterate over any sprites in the Group and blit their .image and .rect attributes to `screen`. –  Aug 15 '14 at 20:55
  • Also -- I don't know what kind of app you're creating, but 60 FPS is a lot. If this is a game, then I suppose it makes sense, but if it's some kind of app, you can probably bring that way down –  Aug 15 '14 at 21:18
  • Thanks again :D. I'm so close to getting this to work... I'm still having a bit of trouble though. So you said I should change the sprites list to a SpriteGroup. So I changed that, but I noticed that the list `clicked_sprites` is still iterating through sprites in the code. I tried changing it to `SpriteGroup` but now I'm getting another error, "AttributeError: 'Thing' object has no attribute '_Sprite__g'" I updated my post with the code I'm using now – Kyle Me Aug 15 '14 at 21:18
  • 1
    bah, that's my bad -- you have to call super() during the init –  Aug 15 '14 at 21:20
  • Also -- your collidepoint bit is fine, you don't necessarily need that to *not* be a list. –  Aug 15 '14 at 21:27
  • Everything works perfectly. Thank you for your help. Turns out I REALLY have A LOT more to learn about Python... Never even heard of a super before. – Kyle Me Aug 15 '14 at 21:48
0

Let's take a look at what you code does.

  • You load a new image into the itunes variable.
  • You blit this image onto the screen at 200,200
  • You add the image into the sprite list.
  • When you click the mouse you loop the sprite list to check if the mouse point lies in the rect of the objects in the sprite list.

Now how do we know the position of the objects that are in the sprite list? The answer is that we don't. There is no information about the position of the object in the surface itself.

To solve this you can make your own Sprite class that will include a rect and a surface, or more preferably use pygame.sprite.Sprite which already includes rect.

Bartlomiej Lewandowski
  • 10,771
  • 14
  • 44
  • 75