0

I am a new user of PyGame and python in general. That being said, I have created some sprites and put them in a group. For example,

gems = pygame.sprite.Group()

for i in range (0,4):
    gem = Gem()
    all_sprite_list.add(gem)
    gems.add(gem)

Now in my main I would like to manipulate one of the gem's in the group by hitting a specific key. I am currently accomplishing this by setting an id attribute to each gem and iterating through the group until I match the id with the one I want to manipulate. i.e.:

if event.type == pygame.KEYDOWN and event.key == pygame.K_F1:
    for gem in gems:
        if gem.id == 1:
           gem.state = normal
elif event.type == pygame.KEYDOWN and event.key == pygame.K_F2:
    for gem in gems:
        if gem.id == 2:
           gem.state = normal

I know this is a dirty dirty trick and I'm sure there is a better way instead of iterating through the group. I looked at the references and tried doing something like

gems.sprites().index(0) 

with no avail. Any suggestions?

ekad
  • 14,436
  • 26
  • 44
  • 46
imjojo42
  • 15
  • 3

3 Answers3

0

It seems like you know which keys correspond to which ID's, so you can put that information in a dict and then only do one iteration through the gems:

gem_keys = {
    1: pygame.K_F1,
    2: pygame.K_F2,
}

if event.type == pygame.KEYDOWN:
    for gem in gems:
        # Check if key pressed corresponds to current gem
        if event.key == gem_keys[gem.id]:
           gem.state = normal

Were you having any specific problems beyond the code feeling clunky?

Marius
  • 58,213
  • 16
  • 107
  • 105
0

in PyGame's Sprite class, you have a function update(*args) (documentation).

(As an aside, *args means you can pass in as many arguments as you want:

>>> def f(*args):
...     print args
...
>>> f(1,2)
(1, 2)
>>> f(3)
(3,)
>>> f(3,5,6)
(3, 5, 6)
>>> def g(*args):
...     print args[0]
...
>>> g(1,2)
1

By default update does nothing, but you could overwrite it for your own purposes, for example by creating a class that overwrites the base Sprite class:

class MySprite(Sprite):
    def update(self,*args):
        key = args[0]
        if key == pygame.K_F1 and self.id == 1 :
            self.state = normal
        elif key == pygame.K_F2 and self.id == 2 :
            self.state = normal

Now, Sprite Groups also have an update function (with the same signature update(*args))

Official documentation.

What this function does is it calls update with the passed in parameters on each sprite

# group has sprite1 and sprite2
group.update(1,2,3) # same as calling sprite1.update(1,2,3) 
                    #  and sprite2.update(1,2,3)

so if gems is your group, you can do this (assuming you're using something like the MySprite class above)

if event.type == pygame.KEYDOWN:
    gems.update(event.key) 
    # this will call the MySprite.update function for all the gems
rtpg
  • 2,419
  • 1
  • 18
  • 31
  • this certainly looks cleaner and is pretty much what I was looking for... but is it as efficient as possible? Do we really have to call update on each of the items in the group? Is there no way to only manipulate the sprite I want? – imjojo42 Jul 22 '14 at 02:40
  • This is the "best" solution when you have a more general update cycle (where you'll likely be checking for a lot of different properties on each gem), but I'm writing an answer for your more specific case – rtpg Jul 22 '14 at 02:51
0

I wrote another answer that seems like the canonical answer for updating PyGame sprites (especially if you're updating a lot of sprites per update). But here's an alternative solution for the case of updating few sprites among many.


The objective is to find a small set of objects in a large collection. Here my proposed solution is to have a parallel dictionary with your sprite group. This dictionary will be keyed to the key that triggers it.

Here we'll use defaultdict (check out this question (second answer especially) to get an idea on how to use these). Basically it's a dictionary with default values (here since it's list it's filled with empty lists).

key_dict = defaultdict(list)
# add the sprite triggered by F1
key_dict[pygame.K_F1].append(gem1)
# you can add multiple sprites as well (these are lists)
key_dict[pygame.K_F2].append(gem2)
key_dict[pygame.K_F2].append(gem3)

#...  in your update loop
if event.type == pygame.KEYDOWN:
    for gem in key_dict[event.key] : #this gets all the gems for this key event 
                                     # (empty if not defined because defaultdict)
        gem.state = normal

This solution is good if you have a lot of gems and only really need to touch a couple small ones.

But there are some problems :

  • you need to mantain two collections (your sprite group and this)

  • if you use this for a lot of gems, then the speed advantage will be less

  • This will make your code messier

I think this is worth doing only if you're in a situation where the number of gems to update is vastly smaller than the total number of gems. If you're updating a lot of other properties too I'd suggest my other answer, as its cleaner.

Community
  • 1
  • 1
rtpg
  • 2,419
  • 1
  • 18
  • 31
  • "you need to mantain two collections (your sprite group and this)" This was exactly the reason I wanted to try to use the sprite groups if I could. I didn't want to maintain 2 lists if I didn't have to. – imjojo42 Jul 22 '14 at 03:10
  • I looked through the sprite group code and there doesn't seem to be an easy way to query it so this is probably your best bet. – rtpg Jul 23 '14 at 07:45