Clicking a sprite

Clicking a sprite

Postby ac.smith » Mon Jul 28, 2014 11:52 am

Hi, Have created a basic game where stars bounce around the screen and the user has to shoot the stars with the mouse. But no matter how hard I try to search google for an answer to my problem I cannot seem to a. understand the solution and b. get it to work.
My problem is with getting the game to know when a star is clicked. I would be very greatful if some one could help me with the code to register clicks on sprites and explain how or why it is done that way. I will post my code below :

Code: Select all
#Shooting Star main
import math
import random
import pygame
import sys
import os
from star import
from game import
from pygame.locals import *




##TOP LEVEL CONSTANTS
FPS=30
WINDOWWIDTH = 800
WINDOWHEIGHT = 600
GAMETITLE = "Shooting Star"
WHITE = [255,255,255]; BLACK = [0,0,0]; GREEN=[0,255,0]
os.environ['SDL_VIDEO_WINDOW_POS'] = 'center'
background = pygame.image.load("background.png")
game = Game()

def main():
   
   
   #set up initial display
   pygame.init()
   clock=pygame.time.Clock()
   surface=pygame.display.set_mode([WINDOWWIDTH,WINDOWHEIGHT])
   pygame.display.set_caption(GAMETITLE)
   surface.blit(background, (0,0))
   displayFont=pygame.font.Font("256BYTES.TTF",28)
   
   #Main game loop
   game_over = False
   live_star_sprites=pygame.sprite.Group()
   ticktock=1
   while game_over == False:
      for event in pygame.event.get():
         if event.type==pygame.KEYDOWN:
            if event.key==pygame.K_ESCAPE:
               game_over=True
         if event.type == pygame.QUIT:
            game_over=True

      if ticktock % (FPS/0.8)==1:
         if len(live_star_sprites)<10:
            live_star_sprites.add((Star(WINDOWWIDTH,WINDOWHEIGHT)))
      
      surface.blit(background, (0,0))
      #print pygame.time.get_ticks()
      
      for sprite in live_star_sprites:
         sprite.update_position(WINDOWHEIGHT,WINDOWWIDTH,game)
      
      live_star_sprites.draw(surface)
      
      scoreText = pygame.font.Font(None,20)
      scoreText=displayFont.render('Score: '+str(game.get_score()),True,GREEN)
      surface.blit(scoreText,(10,10))   
            
      pygame.display.update()
      
      ticktock+=1
      
      clock.tick(FPS)
      
   surface.fill(BLACK)
   scoreText=displayFont.render('Game over! Your Score: '+str(game.get_score()),True,GREEN)
   surface.blit(scoreText,(110,200))
   pygame.display.update()
   
   while game_over==True:
         for event in pygame.event.get():
            if event.type==pygame.KEYDOWN:
               if event.key==pygame.K_ESCAPE:
                  pygame.quit()
                  sys.exit()
            if event.type == QUIT:
               pygame.quit()
               sys.exit()
      
if __name__ == '__main__':
   main()

#shooting star
import pygame, random

class Star(pygame.sprite.Sprite):
   
   def __init__(self, WINDOWWIDTH,WINDOWHEIGHT):
      pygame.sprite.Sprite.__init__(self)
      self.image=pygame.image.load("star.png")
      self.rect=self.image.get_rect()
      self.rect.y=random.randint(10,790)
      self.rect.x=random.randint(10,590)
      self.speed = [1,1]
      self.life_length=0
         
   def update_position(self,WINDOWHEIGHT,WINDOWWIDTH,game):
      
      #move the star
      self.rect.left+=self.speed[0]
      self.rect.top+=self.speed[1]
      
      if self.rect.left < 0 or self.rect.right > WINDOWWIDTH:
         self.speed[0] = -self.speed[0]
      if self.rect.top < 0 or self.rect.bottom > WINDOWHEIGHT:
         self.speed[1] = -self.speed[1]
      
      self.life_length+=1
      
      if self.life_length >= 500:
         game.update_score(-10)
         self.kill()
         
      
   def shot(self,game, pos):
      game.update_score(50)      
      self.kill()

#Game
class Game():
   def __init__(self):
      self._score=0
      self._stars_shot=0
      
   def update_score(self,amount):
      self._score+=amount
      
   def get_score(self):
      return self._score

   def update_stars_shot(self,amount):
      self._stars_shot+=amount
      
   def get_stars_shot(self):
      return self._stars_shot
      
ac.smith
 
Posts: 7
Joined: Fri Jul 11, 2014 10:34 am

Re: Clicking a sprite

Postby DrakeMagi » Mon Jul 28, 2014 12:51 pm

Code: Select all
if event.type == pygame.MOUSEBUTTONDOWN:
    # Left mouse button.
    if event.button == 1:
        # loop through a starlist.
        for star in starlist:
            # is mouse over this star.
            if star.rect.collidepoint(event.pos):
                # this star been click.
                star.click()
Linux: won't find windows here.
Linux: the choice of a GNU generation.
https://github.com/DrakeMagi
DrakeMagi
 
Posts: 113
Joined: Sun May 12, 2013 8:36 pm

Re: Clicking a sprite

Postby freddyhard » Mon Jul 28, 2014 1:07 pm

Code: Select all
    btn1Released = False
    btn2Released = False
    btn3Released = False
   
    while not game_over:
       
        # test to see if any mouse button was pressed
        # this returns True for each mouse button that may be pressed
        (button1, button2, button3) = pygame.mouse.get_pressed()
       
       
        if (button1 and btn1Released) or (button2 and btn2Released) or (button3 and btn3Released):
            # get the mouse position
            mouse_pos = pygame.mouse.get_pos()
           
           
            # measure distance mouse is from star. if distance is < 5 pixels from centre consider it a hit. the radius 5 can be tweaked to better
            # suite the star sprite.
            for sprite in live_star_sprites:
                if math.sqrt(math.pow((mouse_pos[0] - sprite.rect.centerx), 2) +
                             math.pow((mouse_pos[1] - sprite.rect.centery), 2)) < 5:
                    sprite.shot()
                    break
       
        # these booleans mean the player has to release the mouse button in order to get a hit on
        # each star. otherwise all the player has to do is hold down any mouse button and pass
        # over a star to get a hit
        btn1Released = not button1
        btn2Released = not button2
        btn3Released = not button3
Last edited by Mekire on Tue Jul 29, 2014 4:49 pm, edited 5 times in total.
Reason: Lock.
freddyhard
 
Posts: 36
Joined: Wed Jul 16, 2014 9:32 pm

Re: Clicking a sprite

Postby Mekire » Mon Jul 28, 2014 3:23 pm

freddyhard wrote:
Code: Select all
#measure distance mouse is from star. if distance is <5 pixels from centre consider it a hit
for sprite in live_star_sprites:
    if math.sqrt(math.pow((mouse_pos[0] - sprite.rect.centerx), 2) +
                 math.pow((mouse_pos[1] - sprite.rect.centery), 2)) < 5:
        sprite.shot()
        break

No, no, no.

As Drake has already shown, he should be using pygame.Rect.collidepoint.

Also I would like to bring to your attention that there is a very useful, and oft ignored, function in the math module.
Check out math.hypot(). Calculating the hypotenuse is such a common need that they included a function for specifically that.

-Mek
User avatar
Mekire
 
Posts: 1018
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: Clicking a sprite

Postby freddyhard » Tue Jul 29, 2014 1:26 pm

yes yes yes!
there are loads of different ways to accomplish a task.
rect.collidepoint only tests to see if the mouse point is inside the rectangle that represents the star. this can look false unless a pixel overlap is tested for true collision
Last edited by Mekire on Tue Jul 29, 2014 4:49 pm, edited 1 time in total.
Reason: Lock.
freddyhard
 
Posts: 36
Joined: Wed Jul 16, 2014 9:32 pm

Re: Clicking a sprite

Postby Mekire » Tue Jul 29, 2014 4:47 pm

this can look false unless a pixel overlap is tested for true collision

Indeed, in which case you would want to follow it up with a mask check for pixel perfect accuracy.

There is also a pygame collision method for checking circular collision where each sprite is assigned a radius element if this is really what you want to check for.

Collision is slow enough. You should be using the pygame methods that already exist. They will generally, in the worst case, be written in an efficient manner, and in the best, get down to the C library code faster.

Also as I said previously, if we are doing it manually, using the best tool available helps too:
Code: Select all
math.hypot(mouse_pos[0]-sprite.rect.centerx, mouse_pos[1]-sprite.rect.centery)

-Mek
User avatar
Mekire
 
Posts: 1018
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: Clicking a sprite

Postby DrakeMagi » Tue Jul 29, 2014 10:06 pm

freddyhard wrote:yes yes yes!
there are loads of different ways to accomplish a task.
rect.collidepoint only tests to see if the mouse point is inside the rectangle that represents the star. this can look false unless a pixel overlap is tested for true collision

Yes there is many ways to accomplish a task.
Some are so much slower then others.
python math is the slowest thing you can do.

I never been a fan pygame.sprite.Sprite.
And it written in python. So I just use classes and list.

Here an example of a simple falling star with pixel perfect collision.
Code: Select all
import pygame
import random

pygame.init()

class Star(object):
    # only need it once
    image = pygame.Surface((40,40))
    image.set_colorkey((0,0,0))
    pygame.draw.polygon(image, (200,200,0), ((20,0), (30,15), (40,15), (30,25), (40,40), (20,30), (0,40), (10,25), (0, 15), (10,15)) )
    mask = pygame.mask.from_surface(image)
    fall_tick = 30
   
    def __init__(self, position):
        self.rect = Star.image.get_rect()
        self.rect.topleft = position
        self.shot = False
        self.tick = 0
        self.fall_speed = 1.0
       
    def blit(self, surface):
        surface.blit(Star.image, self.rect.topleft)
       
    def update(self, tick):
        if self.shot:
            if tick > self.tick:
                self.tick = tick + Star.fall_tick
                if self.fall_speed < 20:
                    self.fall_speed += 0.1
           
                self.rect.move_ip(0, int(self.fall_speed))
               
    def mask_collide(self, position):
        p = position[0] - self.rect.x, position[1] - self.rect.y
        if Star.mask.get_at(p) == 1:
            return True
        return False
       
    def collidepoint(self, position):
        if not self.shot:
            # mask_collide might be fast enough by it self
            if self.rect.collidepoint(position):
                if self.mask_collide(position):
                    self.shot = True
           
def main():
    pygame.display.set_caption('Falling Stars')
    screen = pygame.display.set_mode((800, 600))
    clock = pygame.time.Clock()
    fps = 60
   
   
    starlist = [Star((random.randint(0, 760), random.randint(0, 400))) for x in xrange(12)]
    running = True
   
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    for star in starlist:
                        star.collidepoint(event.pos)
                               
        screen.fill((0,0,0))
        for star in starlist:
            star.blit(screen)
       
        # I do this because drawing can to awhile.
        tick = pygame.time.get_ticks()
        for star in starlist:
            star.update(tick)
           
        pygame.display.flip()
        clock.tick(fps)
       
        new_starlist = []
        for star in starlist:
            if 0 < star.rect.y < 600:
                new_starlist.append(star)
               
        starlist = new_starlist
        if len(starlist) < 10:
            starlist.append(Star((random.randint(0, 760), random.randint(0, 400))))

main()
Linux: won't find windows here.
Linux: the choice of a GNU generation.
https://github.com/DrakeMagi
DrakeMagi
 
Posts: 113
Joined: Sun May 12, 2013 8:36 pm

Re: Clicking a sprite

Postby metulburr » Tue Jul 29, 2014 11:53 pm

Now i have to ask. What is the reason you do not like using pygame.sprite.Sprite?
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1508
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: Clicking a sprite

Postby DrakeMagi » Wed Jul 30, 2014 1:05 am

I don't remember all the reasons. Override methods i had trouble with.
using class and list where just so much easier then dealing with it framework.
When i looked at source code and it was written in python. I saw no reasons to use it.

Haven't tested yet. But always wonder how much more memory it uses storing the same images over and over again.
Does loading the same image waste cpu cycles ? Does it reference the same image after loading ?

Here i clean up my simple falling star program to store it own group.
Code: Select all
import pygame
import random

pygame.init()

class Star(object):
    # only need it once
    image = pygame.Surface((40,40))
    image.set_colorkey((0,0,0))
    pygame.draw.polygon(image, (200,200,0), ((20,0), (30,15), (40,15), (30,25), (40,40), (20,30), (0,40), (10,25), (0, 15), (10,15)) )
    mask = pygame.mask.from_surface(image)
    fall_tick = 30
    group = []
   
    def __init__(self, position):
        self.rect = Star.image.get_rect()
        self.rect.topleft = position
        self.shot = False
        self.tick = 0
        self.fall_speed = 1.0
        Star.group.append(self)
       
    def blit(self, surface):
        surface.blit(Star.image, self.rect.topleft)
       
    def update(self, tick):
        if self.shot:
            if tick > self.tick:
                self.tick = tick + Star.fall_tick
                if self.fall_speed < 20:
                    self.fall_speed += 0.1
           
                self.rect.move_ip(0, int(self.fall_speed))
                if 0 > self.rect.y or self.rect.y > 600:
                    Star.group.remove(self)
                    if len(Star.group) < 10:
                        Star((random.randint(0, 760), random.randint(0, 400)))
               
    def mask_collide(self, position):
        p = position[0] - self.rect.x, position[1] - self.rect.y
        if Star.mask.get_at(p) == 1:
            return True
        return False
       
    def collidepoint(self, position):
        if not self.shot:
            # mask_collide might be fast enough by it self
            # but it does do box boundery
            if self.rect.collidepoint(position):
                if self.mask_collide(position):
                    self.shot = True
           
def main():
    pygame.display.set_caption('Falling Stars')
    screen = pygame.display.set_mode((800, 600))
    clock = pygame.time.Clock()
    fps = 60
   
    for x in xrange(12):
        Star((random.randint(0, 760), random.randint(0, 400)))

    running = True
   
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    for star in Star.group:
                        star.collidepoint(event.pos)
                               
        screen.fill((0,0,0))
        for star in Star.group:
            star.blit(screen)
       
        # I do this because drawing can to awhile.
        tick = pygame.time.get_ticks()
        for star in Star.group:
            star.update(tick)
           
        pygame.display.flip()
        clock.tick(fps)

main()
Linux: won't find windows here.
Linux: the choice of a GNU generation.
https://github.com/DrakeMagi
DrakeMagi
 
Posts: 113
Joined: Sun May 12, 2013 8:36 pm

Re: Clicking a sprite

Postby freddyhard » Wed Jul 30, 2014 9:50 am

DrakeMagi wrote:Yes there is many ways to accomplish a task.
Some are so much slower then others.
python math is the slowest thing you can do...

i never knew that python was slow with maths. the lecturers i have really dislike python, so the only information i got on python was by myself looking online.
are the standard mathematical operators +-*/ slow as well or is it just the math.* library?
freddyhard
 
Posts: 36
Joined: Wed Jul 16, 2014 9:32 pm

Re: Clicking a sprite

Postby Mekire » Wed Jul 30, 2014 11:03 am

The math module is written in C and is not particularly slow compared to anything else. Function calls can get expensive though so if you are calling 3 math functions (sqrt, pow, pow) as opposed to just calling hypot it can be noticeable (if you are calling those functions many many times). Good libraries implement anything potentially intensive in C. Pygame for instance is an SDL wrapper, and SDL is written in C.

Python is, to its disadvantage, quite slow compared to programming in compiled languages. This may be the reason that you have met people who don't like it; or they might just prefer statically typed languages over dynamic.

-Mek
User avatar
Mekire
 
Posts: 1018
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan


Return to Game Development

Who is online

Users browsing this forum: No registered users and 1 guest