Keyboard layout aware combinations in pygame - python

Pygame has a constant for example for the exclamation mark, !. The constant is called pygame.K_EXCLAIM. However, in my standard US keyboard layout, the exclamation mark is produced by pressing shift and the key 1. Consider the following two snippets:
# snippet 1
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_EXCLAIM:
print('!')
elif event.key == pygame.K_1:
print('1')
# snippet 2
pressed = pygame.key.get_pressed()
if pressed[pygame.K_EXCLAIM]:
print('!')
if pressed[pygame.K_1]:
print('1')
In both cases, when I press Shift+1, which would produce a exclamation mark, pygame does not recognize the combination and simply says the shift key and the 1 key are pressed, printing the value "1". This concerns me a little bit because while I can hardcode such combinations, they are dependent of the keyboard's layout.
Can I make pygame produce combinations constants (such as K_EXCLAIM or K_AMPERSAND) in a keyboard layout-aware manner?

What you want is the event.unicode attribute:
if event.unicode == "!":
print("!")

Related

PyGame doesn´t detect Button 6 press? [duplicate]

I've seen other solutions to this problem say that you either need to call the pygame.event.pump() or initialize the joystick outside of the while loop. However, even with these solutions, I'm getting 0's for the joystick axes values.
If I uncomment just the pygame.display.set_mode((1, 1)), then the code works as expected, and the values are output to the console.
Is there a way to still get the axes values without having to create the extra window?
Also, I am running python 3.6 on Windows 10.
import pygame
FRAMES_PER_SECOND = 20
pygame.init()
pygame.joystick.init()
# pygame.display.set_mode((1,1))
# Used to manage how fast the screen updates.
clock = pygame.time.Clock()
xboxController = pygame.joystick.Joystick(0)
xboxController.init()
# Loop until the user presses menu button
done = False
print('Found controller\nStarting loop...')
while not done:
pygame.event.pump()
for event in pygame.event.get():
if event.type == pygame.JOYBUTTONDOWN and event.button == 7:
print(f'Exiting controller loop')
done = True
for i in range(xboxController.get_numaxes()):
print(f'Axis {i}: {xboxController.get_axis(i)}')
# pygame.display.flip()
clock.tick(FRAMES_PER_SECOND)
Output:
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
Found controller
Starting loop...
Axis 0: 0.0
Axis 1: 0.0
Axis 2: 0.0
Axis 3: 0.0
Axis 4: 0.0
.
.
.
I would probably move away from Pygame unless you need the whole underlaying GL features, as the library is meant for 2D/3D game development. Although it might be possible to use it for these purposes, issues down the line is more or less unavoidable. A perhaps simpler approach would be to go with python's input library, which can handle gamepads (joysticks).
from inputs import get_gamepad
while True:
events = get_gamepad()
for event in events:
if event.ev_type == 'Absolute':
if event.code == 'ABS_X':
print(f'Left joystick x: {event.state}')
elif event.code == 'ABS_Y':
print(f'Left joystick y: {event.state}')
elif event.code == 'ABS_RX':
print(f'Right joystick x: {event.state}')
elif event.code == 'ABS_RY':
print(f'Right joystick y: {event.state}')
Alright found the answer 5 minutes after I posted this. The problem was that I was using pygame 1.9.6 instead of 2.0.0.dev8. After updating, I'm getting the console output without the display window.

Playing sound using pygame (python)

As of right now I have a program that plays individual sounds based on a certain key presses. Each key press successfully plays the correct sound based on the key pressed, but the sound is cut off when the next sound from a different key begins to play. I want to know how to have my program play the sounds from each key in their entirety, even when another key is pressed and a new sound begins to play (I want the sounds to play simultaneously). I am using the pygame and keyboard libraries.
Here is the function I am using to play sounds for the keys:
# 'keys' refers to a dictionary that has the key press strings and sound file names stored as key-value pairs.
key = keyboard.read_key()
def sound(key):
play = keys.get(key, 'sounds/c2.mp3')
pygame.mixer.init()
pygame.mixer.music.load(play)
pygame.mixer.music.play()
If you need more context, please tell me and I will update my question.
In pygame, you can play multiple sound tracks simultaneously using channels. Note that channels only support wav or ogg files.
Here is some sample code. Press keys 1-3 to start a track. You can also start the same track multiple times.
import pygame
lst = [
'Before The Brave - Free.wav', # key 1
'Imagine Dragons - Demons.wav', # key 2
'Shinedown-How Did You Love.wav' # key 3
]
pygame.init()
size = (250, 250)
screen = pygame.display.set_mode(size)
pygame.mixer.init()
print("channel cnt",pygame.mixer.get_num_channels()) # max track count (8 on my machine)
while True:
pygame.time.Clock().tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
idx = 0
keys = pygame.key.get_pressed()
if keys[pygame.K_1]: idx = 1
if keys[pygame.K_2]: idx = 2
if keys[pygame.K_3]: idx = 3
if (idx):
ch = pygame.mixer.find_channel() # find open channel, returns None if all channels used
snd = pygame.mixer.Sound(lst[idx-1]) # create sound object, must be wav or ogg
if (ch): ch.play(snd) # play on channel if available

Handling None as position in pygame

The built in pygame function: pygame.mouse.get_pos() returns the X and Y coordinates in a tuple when I am moving the mouse, but if I stop moving the mouse, the function returns None.
But, I want the computer to keep returning the current coordinates over and over again.
How do I do that?
What I am trying to do is write a function that makes it easier to get the mouse coordinates.
I want to be able to do following:
Xpos, Ypos = mouse_pos()
What I have right now is:
def mouse_pos():
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if pos != None:
x=a[0]
y=a[1]
return x, y
else:
#here I want code that says: if pos is NoneType, x=(latest x-value) and y=(latest y-value)
So, how can i get pygame to not return None (and return current coordinates) even if mouse is stationary?
Thanks in advance!
You are using the API wrong, the fact that there are events doesn't mean any of them is a mouse event, and pygame fills the mouse position on mouse events.
The function get_pos shouldn't be calle to find out where the mouse is but rather to update your knowledge when it is changed.
The correct way to keep track of mouse position will be to have the following in you main game loop:
mouse_position = (0, 0) # initial value
while game_is_running: # your main game loop
for event in pygame.event.get():
if event.type == pygame.MOUSEMOTION:
mouse_position = pygame.mouse.get_pos()
Remove your function. Just use pygame.mouse.get_pos().
It's not pygame.mouse.get_pos() that returns None, it's your own function.
It will only set pos if there's an event in the event queue. If not, pos will be None.
But even worse is that every time you call your mouse_pos function, the event queue will be cleared. That will lead to events getting lost.

pygame - Getting name of pressed key [duplicate]

Game I'm working on currently needs to let people time in their name for highscore board. I'm slightly familiar with how to deal with key presses, but I've only dealt with looking for specific ones. Is there an easy way to get the letter of any key pressed without having to do something like this:
for event in pygame.event.get():
if event.type == KEYUP:
if event.key == K_a:
newLetter = 'a'
elif event.key == K_b:
newLetter = 'b'
...
elif event.key == K_z:
newLetter = 'z'
While that would work, I have a feeling there is a more efficient way to go about it. I just can't figure it out or find any guides on it.
There a basically two ways:
Option 1: use pygame.key.name().
It's as simple as
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
print(pygame.key.name(event.key))
The advantage over using chr is that chr works only if the value of event.key is between 0 and 255 (inclusive).
If you press menu, Alt Gr, Tab or LShift, pygame.key.name will happily return menu, alt gr, tab and left shift, while chr will crash, crash, return whitespace, and crash.
Option 2: use the unicode attribute of the pygame.KEYDOWN event
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
print(event.unicode)
It will get you the letter/number or an empty string when using a function key, and it will also take modifiers into account, e.g. if you hold Shift while pressing a it will return A instead of just a.
The pygame.KEYDOWN event has additional attributes unicode and
scancode. unicode represents a single character string that is the
fully translated character entered. This takes into account the shift
and composition keys. scancode represents the platform-specific key
code.
if you look K_a is just 97 thats the ascii code for 'a' , I will extrapolate to K_? is the ascii code for whatever the question mark represents
if event.type == KEYUP:
print chr(event.key) #convert ascii code to a character
maybe?
as pointed out this will not work with ascii over 255 (no longer ascii really)
you can use the method outlined below or just use unichr(event.key) instead of chr

How to append items to a list one time each in python

Lets say I have a list called my_list and a function called my_function and my_function appends items to my_list based on which portion of the surface gameDisplay was clicked on. However whenever you hold down the mouse for more than one frame, it appends more than one of that item to my_list. This is not the result I am going for. I was wondering if there was a way you could do this without appending more than one of each item to my_list
Thanks for your help
You didn't show code but I guess you use pygame.mouse.get_pressed() which gives True all the time when you keep button pressed. And this can be your problem.
You can do one of two things:
use event.MOUSEBUTTONDOWN which is created only once - when button change state from not-pressed into pressed.
Or:
use extra variable which will remeber pygame.mouse.get_pressed() from previous frame. And then compare if now button is pressed but in previous frame was not-pressed then add element to list.
EDIT: old code from different question which use event.MOUSEBUTTONDOWN to change color.
#!/usr/bin/env python
# http://stackoverflow.com/questions/33856739/how-to-cycle-3-images-on-a-rect-button
import pygame
# - init -
pygame.init()
screen = pygame.display.set_mode((300,200))
# - objects -
# create three images with different colors
images = [
pygame.Surface((100,100)),
pygame.Surface((100,100)),
pygame.Surface((100,100)),
]
images[0].fill((255,0,0))
images[1].fill((0,255,0))
images[2].fill((0,0,255))
images_rect = images[0].get_rect()
# choose first image
index = 0
# - mainloop -
running = True
while running:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1 and images_rect.collidepoint(event.pos):
# cycle index
index = (index+1) % 3
# - draws -
screen.blit(images[index], images_rect)
pygame.display.flip()
# - end -
pygame.quit()
GitHub: furas/python-examples/pygame/button-click-cycle-color

Categories