ZenOfPython
ZenOfPython

Reputation: 901

How do I make two keys respond at the same time?

I am currently making a pong game in python:

enter image description here

However, say the ball is coming at pink, and the yellow guy decides to spam his w and s keys. Then his paddle will start moving (fine), but then the pink one will stop (not fine).

Is it possible for python to listen to two event keys simultaneously?

Here is the code:

import pygame, sys
from pygame.locals import *
pygame.init()

DISPLAYSURF = pygame.display.set_mode((1439, 790))
YELLOW = (255, 255, 0)
PINK = (255, 0, 255)
BLUE = (120, 214, 208)
y1, y2 = (0, 0)
circx, circy = (1439//2, 790//2)
diffx, diffy = (15, 15)
pygame.key.set_repeat(1, 10)
while True:
        if (0 <= circy <= 780) == False:
                diffy*=-1
        if circx <= 60 and y1-10 <= circy <= y1+75:
                diffx*=-1
        if circx >= 1439-60 and y2-10 <= circy <= y2+75:
                diffx*=-1
        if (0 <= circx <= 1439) == False:
                circx, circy = (720, 395)
        DISPLAYSURF.fill((0, 0, 0))
        pygame.draw.rect(DISPLAYSURF, YELLOW, (50, y1, 10, 75))
        pygame.draw.rect(DISPLAYSURF, PINK, (1439-60, y2, 10, 75))
        pygame.draw.circle(DISPLAYSURF, BLUE, (circx, circy), 10)
        circx+=diffx
        circy+=diffy
        try:
                for event in pygame.event.get():
                        if event.type == QUIT or event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
                                pygame.quit()
                                sys.exit()
                        if event.key == pygame.K_w:
                                y1-=15
                        if event.key == pygame.K_s:
                                y1+=15
                        if event.key == pygame.K_UP:
                                y2-=15
                        if event.key == pygame.K_DOWN:
                                y2+=15
        except AttributeError:
                pass
        pygame.display.flip()

How do I make it independently handle each key press if there are 2 simultaneous ones?

Upvotes: 2

Views: 4084

Answers (5)

Lighform
Lighform

Reputation: 13

In a game that I'm making, where the character can move in all directions, I made it so that when you press a key, rather than directly changing the character's x and y, it simply makes one of the variables moveLeft, moveRight, moveUp, or moveDown (depending on which key is pressed) True. Then you have to make a seperate thing that turns it off when the key is lifted. Later in the loop it checks whether each variable is True, and moves the character based on which keys are pressed. Plus, I don't understand why the Try loop is there.

You could change the main loop to this:

p1moveup = False
p1movedown = False
p2moveup = False
p2movedown = False
while True:
    if (0 <= circy <= 780) == False:
            diffy*=-1
    if circx <= 60 and y1-10 <= circy <= y1+75:
            diffx*=-1
    if circx >= 1439-60 and y2-10 <= circy <= y2+75:
            diffx*=-1
    if (0 <= circx <= 1439) == False:
            circx, circy = (720, 395)
    DISPLAYSURF.fill((0, 0, 0))
    pygame.draw.rect(DISPLAYSURF, YELLOW, (50, y1, 10, 75))
    pygame.draw.rect(DISPLAYSURF, PINK, (1439-60, y2, 10, 75))
    pygame.draw.circle(DISPLAYSURF, BLUE, (circx, circy), 10)
    circx+=diffx
    circy+=diffy
    for event in pygame.event.get():
        if event.type == QUIT or event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_w:
                p1moveup = True
            if event.key == pygame.K_s:
                p1movedown = True
            if event.key == pygame.K_UP:
                p2moveup = True
            if event.key == pygame.K_DOWN:
                p2movedown = True
        if event.type === pygame.KEYUP:
            if event.key == pygame.K_w:
                p1moveup = False
            if event.key == pygame.K_s:
                p1movedown = False
            if event.key == pygame.K_UP:
                p2moveup = False
            if event.key == pygame.K_DOWN:
                p2movedown = False
    if p1moveup == True:
        y1-=15
    if p1movedown == True:
        y1+=15
    if p2moveup == True:
        y2-=15
    if p2movedown == True:
        y2+=15
    pygame.display.flip()

Upvotes: 0

ZenOfPython
ZenOfPython

Reputation: 901

As I said above, I used pygame.key.set_repeat(1, 10) only so that even while the key was held down the event would occur.

I discovered an alternate way to do the same, using pygame.key.get_pressed()[pygame.DESIRED_KEY].

pygame.key.get_pressed() returns a tuple such as the following:

(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

When a specific key is pressed down, then the 0 turns into a 1, enabling us to use if pygame.key.get_pressed()[pygame.DESIRED_KEY].

Here is your edited code:

import pygame, sys
from pygame.locals import *
pygame.init()

DISPLAYSURF = pygame.display.set_mode((1439, 790))
YELLOW = (255, 255, 0)
PINK = (255, 0, 255)
BLUE = (120, 214, 208)
y1, y2 = (0, 0)
circx, circy = (1439//2, 790//2)
diffx, diffy = (15, 15)
pygame.key.set_repeat(1, 10)
while True:
        if pygame.key.get_pressed()[pygame.K_UP]:
                y1-=15
        if pygame.key.get_pressed()[pygame.K_DOWN]:
                y1+=15
        if pygame.key.get_pressed()[pygame.K_w]:
                y2-=15
        if pygame.key.get_pressed()[pygame.K_s]:
                y2+=15
        if (0 <= circy <= 780) == False:
                diffy*=-1
        if circx <= 60 and y1-10 <= circy <= y1+75:
                diffx*=-1
        if circx >= 1439-60 and y2-10 <= circy <= y2+75:
                diffx*=-1
        if (0 <= circx <= 1439) == False:
                circx, circy = (720, 395)
        DISPLAYSURF.fill((0, 0, 0))
        pygame.draw.rect(DISPLAYSURF, YELLOW, (50, y1, 10, 75))
        pygame.draw.rect(DISPLAYSURF, PINK, (1439-60, y2, 10, 75))
        pygame.draw.circle(DISPLAYSURF, BLUE, (circx, circy), 10)
        circx+=diffx
        circy+=diffy
        try:
                for event in pygame.event.get():
                        if event.type == QUIT or event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
                                pygame.quit()
                                sys.exit()
        except AttributeError:
                pass
        pygame.display.flip()

Upvotes: 0

Silas Ray
Silas Ray

Reputation: 26160

After some research, I think this is actually an SDL event queue overflow problem. SDL is a C event library that PyGame's event system is built on top of. From what I've read, the event queue has a 128 event limit, after which new events being added are simply dropped until the queue is flushed.

Your rendering logic isn't very efficient, and you've bound your input poll rate to your rendering logic (you re-render the entire screen, paddles, and ball every game loop, and then you poll the event queue, so you can't empty the event queue any faster than you can render an entire frame from scratch). I think what you need to do here is improve your render logic (look at how you can erase/redraw just portions of a sprite instead of flushing the entire buffer) and unbind event polling from rendering by putting rendering in a separate thread.

Upvotes: 1

Adam Smith
Adam Smith

Reputation: 54273

I agree with SilasRay that this is almost certainly NOT an issue with thread blocking, but if it is: this should fix it (therefore if it doesn't, it's an issue with your keyboard or the SDL layer that Silas mentioned). It's kind of important to be making objects out of your Paddles anyway, so think about doing that part anyhow. The thread section is in paddlelisten().

import threading

class Paddle(object):
    def __init__(self, posx, color):
        self.posx = posx
        self.posy = 0
        self.color = color
    @property
    def pos():
        return (self.posx, self.posy, 10, 75)

def main():
    # the logic for the program
    p1_paddle = Paddle(50, YELLOW)
    p2_paddle = Paddle(1439-60, PINK)
    def paddlelisten(paddle, upkey, downkey):
        if event.key == upkey:
            paddle.posy -= 15
        if event.key == pygame.downkey:
            paddle.posy += 15
    t1 = threading.Thread(target=lambda: paddlelisten(p1_paddle, pygame.K_w, pygame.K_s))
    t2 = threading.Thread(target=lambda: paddlelisten(p2_paddle, pygame.K_UP, pygame.K_DOWN))
    for t in [t1, t2]:
        t.daemon = True
        t.start()
    while True:
        # game loop
        pygame.draw.rect(DISPLAYSURF, p1_paddle.color, p1_paddle.pos)
        pygame.draw.rect(DISPLAYSURF, p2_paddle.color, p2_paddle.pos)

Upvotes: 1

Pimenta
Pimenta

Reputation: 1109

I think you need to use threads otherwise the program is always sequencial.

Check this example:

Make 2 functions run at the same time

Try to put the while True in each thread's function so that both responde to commands.

Upvotes: 0

Related Questions