user890167
user890167

Reputation:

Python + PyGame - handling simultaneous mouse and keyboard events

I'm totally willing to submit to the idea that my hardware is the cause of the problem here, however I don't honestly think so because I've definitely seen the computer handle both kinds of input simultaneously using other software/games/etc, so I'm guessing that the fault here is my approach to the PyGame event handler.

I'm casually mucking about with Python and PyGame, and have just being trying to build my knowledge a piece at a time and express that knowledge by building a 'game' as I'm learning. This is very much a work-in-progress, there's no implementation of anything like collision detection or scorekeeping, I figure that can come later.

The relevant conundrum here is that the game will execute MOUSEMOTION events, and KEYDOWN events, it just doesn't seem to want to handle them at the same time. While the "player" object is moving, it cannot shoot, and while it is shooting, it cannot move. Since most gamers enjoy the luxury of moving while shooting, I see this as a something of a snag.

import pygame, random, sys
from pygame.locals import *

pygame.init()

width = 640
height = 480


DISPLAYSURF = pygame.display.set_mode((width, height))
pygame.display.set_caption('It moves!')
pygame.mouse.set_visible(0)



class Player(pygame.sprite.Sprite):

    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)

        self.x = x
        self.y = y
        self.width = 50
        self.height = 25
        self.playerRect = None



    def update(self, event):
        if event.type == MOUSEMOTION:
            self.x, self.y = event.pos


        #get a new playerRect and draw it
        self.playerRect = pygame.Rect(self.x, self.y, self.width, self.height)
        pygame.draw.ellipse(DISPLAYSURF, RED, (self.playerRect), 3)


    def shotcheck(self, event):
        if event.type == KEYDOWN:
            if event.key == K_KP8:
                return (True, 'up')
            elif event.key == K_KP2:
                return (True, 'down')
            elif event.key == K_KP4:
                return (True, 'left')
            elif event.key == K_KP6:
                return (True, 'right')
            elif event.key == K_KP7:
                return (True, 'upleft')
            elif event.key == K_KP1:
                return (True, 'downleft')
            elif event.key == K_KP9:
                return (True, 'upright')
            elif event.key == K_KP3:
                return (True, 'downright')
            else:
                return (0, 0)



class Enemy(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        #self.body = pygame.rect.Rect(self.x, self.y, 15, 15)
        self.speed = 5
        self.xmove = 0
        self.ymove = 0



    def update(self, event):
        self.x += self.speed
        if self.x > 350:
            self.speed *= -1
        elif self.x < 25:
            self.speed *= -1

        pygame.draw.rect(DISPLAYSURF, BLUE, (self.x, self.y, 15, 15), 4)



#pass it a directional value when fired based on the key
#may have to divide speed / 2 if moving diagonally
class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y, direction):
        pygame.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        self.direction = direction
        self.width = 4
        self.height = 4
        self.bulletRect = None
        self.speed = 8



    def update(self, event):

        if self.direction == 'up':
            self.y -= self.speed

        elif self.direction == 'down':
            self.y += self.speed

        elif self.direction == 'left':
            self.x -= self.speed

        elif self.direction == 'right':
            self.x += self.speed

        elif self.direction == 'upleft':
            self.x -= (self.speed/2)
            self.y -= (self.speed/2)

        elif self.direction == 'downleft':
            self.x -= (self.speed/2)
            self.y += (self.speed/2)

        elif self.direction == 'upright':
            self.x += (self.speed/2)
                self.y -= (self.speed/2)

        elif self.direction == 'downright':
            self.x += (self.speed/2)
            self.y += (self.speed/2)


        self.bulletRect = pygame.Rect(self.x, self.y, 4, 4)
        pygame.draw.ellipse(DISPLAYSURF, GREEN, (self.bulletRect), 2)





FPS = 30
fpsClock = pygame.time.Clock()


RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)



ship = Player(width / 2, height / 2)
bads = Enemy(width / 2, height / 2)



queue = pygame.sprite.Group()
queue.add(ship)
queue.add(bads)


while True:
    DISPLAYSURF.fill(BLACK)
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()


        #passes 'event' to everything in the queue and calls
        #their obj.update().  in this way the gameloop 
        #is a bit more readable
    for thing in queue:
        thing.update(event)

    try: #i'm not married to this bit of code :/
        checkForShot, shotDirection = ship.shotcheck(event)
        if checkForShot:
            shotx, shoty = ship.playerRect.center
            shot = Bullet(shotx, shoty, shotDirection)
            queue.add(shot)
    except TypeError:
        pass

    pygame.display.flip()
    fpsClock.tick(FPS)

I'm aware that this essentially produces a really uninspired Robotron clone, but like I said, this is a toddler project of mine that I'm putting together while running through tutorials online. Yes, there's a needless "import random" for now, it's going to matter later.

I'm guessing that there's a couple of hangups; for starters I don't like the way that the creation of bullets is being handled (the player object should, in my mind, add them to the game queue itself instead of returning a True/False tuple, but it seemed sort of unintuitive to have the player object mention the queue directly. and handling it with a try/except feels lazy, but maybe I'm being critical). However I also sense that this problem is tantamount to figuring out how to deal with getting the event handler to properly thing.update() for simultaneous moving (MOUSEMOTION) and shooting (KEYDOWN).

And I am also guessing that in order to get this to behave more "as one would expect" that I need to tell it to handle KEYUP events as well. However, I'm still just stymied as to why exactly the event handler seems to pick one event.type and ignore the other (in my experience it's been whichever one came first).

Upvotes: 2

Views: 3248

Answers (1)

przemo_li
przemo_li

Reputation: 4053

Check this out!

import pygame, random, sys
from pygame.locals import *

pygame.init()

width = 640
height = 480


DISPLAYSURF = pygame.display.set_mode((width, height))
pygame.display.set_caption('It moves!')
pygame.mouse.set_visible(0)



class Player(pygame.sprite.Sprite):

    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)

        self.x = x
        self.y = y
        self.width = 50
        self.height = 25
        self.playerRect = None



    def update(self, event):
        if event.type == MOUSEMOTION:
            self.x, self.y = event.pos


        #get a new playerRect and draw it
        self.playerRect = pygame.Rect(self.x, self.y, self.width, self.height)
        pygame.draw.ellipse(DISPLAYSURF, RED, (self.playerRect), 3)


    def shotcheck(self, event):
        if event.type == KEYDOWN:
            if event.key == K_KP8:
                return (True, 'up')
            elif event.key == K_KP2:
                return (True, 'down')
            elif event.key == K_KP4:
                return (True, 'left')
            elif event.key == K_KP6:
                return (True, 'right')
            elif event.key == K_KP7:
                return (True, 'upleft')
            elif event.key == K_KP1:
                return (True, 'downleft')
            elif event.key == K_KP9:
                return (True, 'upright')
            elif event.key == K_KP3:
                return (True, 'downright')
            else:
                return (0, 0)



class Enemy(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        #self.body = pygame.rect.Rect(self.x, self.y, 15, 15)
        self.speed = 5
        self.xmove = 0
        self.ymove = 0



    def update(self, event):
        self.x += self.speed
        if self.x > 350:
            self.speed *= -1
        elif self.x < 25:
            self.speed *= -1

        pygame.draw.rect(DISPLAYSURF, BLUE, (self.x, self.y, 15, 15), 4)



#pass it a directional value when fired based on the key
#may have to divide speed / 2 if moving diagonally
class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y, direction):
        pygame.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        self.direction = direction
        self.width = 4
        self.height = 4
        self.bulletRect = None
        self.speed = 8



    def update(self, event):

        if self.direction == 'up':
            self.y -= self.speed

        elif self.direction == 'down':
            self.y += self.speed

        elif self.direction == 'left':
            self.x -= self.speed

        elif self.direction == 'right':
            self.x += self.speed

        elif self.direction == 'upleft':
            self.x -= (self.speed/2)
            self.y -= (self.speed/2)

        elif self.direction == 'downleft':
            self.x -= (self.speed/2)
            self.y += (self.speed/2)

        elif self.direction == 'upright':
            self.x += (self.speed/2)
                self.y -= (self.speed/2)

        elif self.direction == 'downright':
            self.x += (self.speed/2)
            self.y += (self.speed/2)


        self.bulletRect = pygame.Rect(self.x, self.y, 4, 4)
        pygame.draw.ellipse(DISPLAYSURF, GREEN, (self.bulletRect), 2)





FPS = 30
fpsClock = pygame.time.Clock()


RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)



ship = Player(width / 2, height / 2)
bads = Enemy(width / 2, height / 2)



queue = pygame.sprite.Group()
queue.add(ship)
queue.add(bads)


while True:
    DISPLAYSURF.fill(BLACK)
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()


                #passes 'event' to everything in the queue and calls
                #their obj.update().  in this way the gameloop 
                #is a bit more readable
            for thing in queue:
                thing.update(event)

    try: #i'm not married to this bit of code :/
        checkForShot, shotDirection = ship.shotcheck(event)
        if checkForShot:
            shotx, shoty = ship.playerRect.center
            shot = Bullet(shotx, shoty, shotDirection)
            queue.add(shot)
    except TypeError:
        pass

    pygame.display.flip()
    fpsClock.tick(FPS)

The problem was in the code that handled pulling (retrieving them from pygame). You made loop that get every event. But then you "updated" your game state not with every one, but with last one! I think that correcting indentation will be enough.

Upvotes: 1

Related Questions