I'm facing a problem with pygame 1.9.2 based on SDL 1.2.15: the only mean of resizing window programmatically I see is pygame.display.set_mode(...). But when I call this method my keyboard state is reset, so all currently pressed keys and modifiers send KEYUP event, repeated keys are stopped too.
When the window is resized manually by user keyboard state is perfectly normal. The event queue is paused for the time of resizing, then VIDEORESIZE and VIDEOEXPOSE events come up and keys are still pressed, if they were so.
There are few questions:
Is there a way to keep normal keyboard state while resizing window?
If not, I would like to at least keep modifier keys but I can not just use key.set_mods() because I am unable to get the moment when user releases key.
At last is there a way to invoke resizing of SDL window directly?
Example program demonstrates undesired behaviour. Left and right arrows resize window to same size, yet if they are pressed, there is only one keypress. Other keys are repeated normally if held. Output is event log for everything that happens.
import pygame
import sys
# same size all the time
size = (200, 200)
pygame.init()
pygame.display.set_mode(size, pygame.RESIZABLE)
# if left or right arrow key is held nothing will happen despite this line
pygame.key.set_repeat(500, 200)
run = True
while run:
for event in pygame.event.get():
out = '%s\t%s'%(event.type, str(event.dict))
if event.type == pygame.QUIT:
pygame.display.quit()
run = False
if event.type == pygame.KEYDOWN:
# mods are also reset on set_mode() call
out += ', mods = %i' % (pygame.key.get_mods())
if event.key == pygame.K_LEFT:
pygame.display.set_mode(size, pygame.RESIZABLE)
if event.key == pygame.K_RIGHT:
pygame.display.set_mode(size, pygame.RESIZABLE)
print(out)
pygame.time.Clock().tick(60)
Tested on Win machine but soon will report if linux fails too.
UPD: I've been digging into source of pygame and can not see why, but the documentation bundled with sources of pygame states, that Pygame can only have a single display active at any time. Creating a new one with pygame.display.set_mode() will close the previous display which implies that there is no way to resize window, only to recreate it. Seems a little strange to me.
UPD: I have changed my progam behaviour so that resize does not actually happen until user releases keyboard completely. It works well and does not break usability.
Nevertheless, it would be great to know if there exists a solution to stated problem.
One solution is, like self. has mentioned, to store the states of the keys so that instead of using only the events, you could ignore this particular key up event. This could, however, have one problem. If you are trying to create a key control to resize the screen, which you probably are doing, then you will also be ignoring the key up event that is real. Because this kind of control is rarely implemented, there does not seem to be any kind of clean solution, like a function to just change the screen, or one to suppress the false key events. However, you could find a way around the key event problem. There are really two viable solutions. The first one (and probably the worse of the two) is to create a way to identify the fake events, and selectively ignore the key up events. Probably the easiest way to do this is to resize the screen periodically during a fixed amount of time, so that you can expect a false keyboard event at this time, and ignore it. Here is an example of the code to block a resize every second (specifically, it assumes a screen update occurs every time the system time has an exact second value, no fractions of a second) for the a key:
import pygame
import datetime
a_keydown = False
for event in pygame.event.get():
if item.type == pygame.KEYDOWN and item.key == pygame.K_a:
a_keydown = True
print "keydown event"
if item.type == pygame.KEYUP and item.key == pygame.K_a:
if datetime.datetime.now().time()[4] > 15: #leaves a window of fifteen microseconds for the screen resize, this may need to be adjusted
a_keydown = False
print "key up event"
The better of the two solutions is not this, however. Assuming the keyup event created by set_mode exists soley in python, you could get key events from elsewhere, like pywin32 for example. Try using the win32api module, and something like the GetKeyState() function for what you need. This will give you the last known state of a key. Not quite as convenient as pythons event stream, but it may be your best solution, as pygame doesn't supply an elegant solution to this.
Related
When running I am blit'ing some text onto my surface (WIN) and I want this to be shown in the game so I call pygame.display.update to update the display, this works perfectly fine until the loop has iterated around 75 times and after this the display stops updating. Code below
def dialogue(x,y,text):
global clock
global run
typing=True
tempstring=""
counter=0
switcher=0
for z in range(0,(len(text))):
clock.tick(15)
tempstring=text[0:z+1]
counter+=1
print(counter)
print("temp is "+tempstring)
writing=TITLE_FONT.render(tempstring, 1, (255,255,255),(0,0,0))
if x==-1 and y>-1:
pygame.display.update(WIN.blit(writing, (640-(writing.get_width()//2),y-(writing.get_height()//2))))
elif x>-1 and y==-1:
pygame.display.update(WIN.blit(writing, (x-(writing.get_width()//2),360-(writing.get_height()//2))))
elif x==-1 and y==-1:
pygame.display.update(WIN.blit(writing, (640-(writing.get_width()//2),360-(writing.get_height()//2))))
else:
pygame.display.update(WIN.blit(writing, (x-(writing.get_width()//2),y-(writing.get_height()//2))))
print(typing)
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
run=False
pygame.quit()
break
if event.type == pygame.KEYDOWN:
break
I have tried just using pygame.display.update() at the end of the line of if statements too but this also fails.
On some systems PyGame doesn't work correctly if you don't get pygame.event from system - system may think that program hangs and it may even close it.
You may need to use pygame.even.get() (or similar functions) inside your loop.
In doc pygame.event you can find (but it is hidden in long description)
To prevent lost events, especially input events which signal a quit command, your program must handle events every frame (with pygame.event.get(), pygame.event.pump(), pygame.event.wait(), pygame.event.peek() or pygame.event.clear()) and process them.
Not handling events may cause your system to decide your program has locked up.
To keep pygame in sync with the system, you will need to call pygame.event.pump() internally process pygame event handlers to keep everything current.
I've been trying to develop a "text box" class for pygame as a little personal project, and I've come across an issue that's really stumped me. I'm trying to expand on the pygame text input class found here, wrapping it in a text box class that supports multiple lines and, hopefully, scrolling capabilities.
My problem comes when trying to move the blinker up and down between the lines of text. Basically, hitting the "up" arrow once moves the blinker all the way to the top, and then it stops being responsive to move it down.
Here's the code for how I give the pygame_textbox class the events:
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
test_box.update(events)
test_box_2.update(events)
test_box.draw(screen, (100, 100))
test_box_2.draw(screen, (200, 250))
pygame.display.update()
Here's the code for the text box class (event comes from the above code):
if event.type == pl.KEYDOWN:
if self.is_active:
print("reading in text box: {}".format(event))
if event.key == pl.K_UP and self.cursor_line > 0:
self.cursor_line -= 1
print("cursor going UP to {}".format(self.cursor_line))
if event.key == pl.K_DOWN and self.cursor_line < len(self.text_input)-1:
self.cursor_line += 1
print("cursor going DOWN to {}".format(self.cursor_line))
if event.key == pl.K_RETURN:
self.text_input.insert(self.cursor_line+1, pygame_textinput.TextInput())
self.cursor_line += 1
for line in self.text_input:
print(line.input_string)
Trying to debug it seems to show that the pygame.event.get() queue is taking in way more KEYDOWN events than it's supposed to; one press of the button sends multiple (and sometimes ongoing) events. I'm new to pygame, but I'm pretty sure that's not supposed to happen with KEYDOWN events, right? There's only supposed to be one event triggered every time a key is pressed. What am I doing wrong here? Is this a bug with pygame itself?
Thanks for any help you can provide. I'm fairly new at this and I hope I formatted the question right.
Just take a look at the pygame_textinput module you linked in your question:
# Update key counters:
for key in self.keyrepeat_counters:
self.keyrepeat_counters[key][0] += self.clock.get_time() # Update clock
# Generate new key events if enough time has passed:
if self.keyrepeat_counters[key][0] >= self.keyrepeat_intial_interval_ms:
self.keyrepeat_counters[key][0] = (
self.keyrepeat_intial_interval_ms
- self.keyrepeat_interval_ms
)
event_key, event_unicode = key, self.keyrepeat_counters[key][1]
pygame.event.post(pygame.event.Event(pl.KEYDOWN, key=event_key, unicode=event_unicode))
As you can see, it's the update method of the TextInput that repeats the key events by posting them again to pygame's event queue.
I'm making a little game and I want to make another window separately from my main one.
I have the the main game in a main window, and I want to open a new window and do a little animation when the user does something.
In my example code below, when the user presses "a" I want it to open a new window and blit to there.
Here I set up the two windows: (I know this doesnt work, its what I'm asking how to do)
SCREEN_X = 400
SCREEN_Y = 400
BSCREEN_X = 240
BSCREEN_Y = 160
BATTLE_SCENE = pygame.display.set_mode((BSCREEN_X, BSCREEN_Y))
SCREEN = pygame.display.set_mode((SCREEN_X, SCREEN_Y))
and then the program:
def run_ani ():
#Do animation, blitting to BATTLE_SCENE
return
def main_game():
ending=False
while ending==False:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT: ending=True
if event.type == KEYDOWN: # key down or up?
if event.key == K_ESCAPE:
ending=True # Time to leave
print("Stopped Early by user")
elif event.key == K_a:
run_ani()
#Normal screen motion, blitting to SCREEN
if ending: pygame.quit()
return
So far what this does is draws the main screen, then when A is pressed, it stops drawing the main screen animations, but still draws the other animations on the main screen and draws in the top left corner.
I'm pretty sure it does this because I am setting BATTLE_SCENE to be smaller than the main screen, thus when blitting to BATTLE_SCENE it blits to the area I created (240x160) in the top corner of the main screen.
However I want BATTLE_SCENE to be a seperate window, so that when I press 'a' it will pop up, do its thing, then close or at least go behind the main screen.
How to do this? Is it even possible?
Do you really need multiple windows? I mean, do you really need them?
If yes, then you should probably use pyglet/cocos2d instead.
To have multiple windows in pygame, you need multiple processes (one for each window). While this is doable, it's not worth the efford. You'll need IPC to exchange data between the windows, and I guess your code will become error-prone and ugly.
Go with pyglet when you need more than one window.
The better solution is probably to divide your game into scenes. Create multiple scenes so that each one represent one stage of the game, something like MenuScene, MainScene, BattleScene, GameOverScene, OptionScene etc.
Then let each of those scenes handle input/drawing of that very part of the game.
MenuScene handles drawing and input etc. of the game's menu
MainScene handles drawing and input etc. of the running game
BattleScene handles drawing and input etc. of whatever you do in run_ani
In your mainloop, just pass control over to the current scene by implementing the methods draw(), handle_event(), and update().
Some example code to get the idea:
scenes = {'Main': MainScene(),
'Battle': BattleScene()} #etc
scene = scenes['Main']
class MainScene():
...
def handle_event(self, event):
if event.type == KEYUP:
if event.key == K_a:
scene = scenes['Battle']
...
class BattleScene():
...
def draw(self):
# draw your animation
def update(self):
# if animation is over:
scene = scenes['Main']
...
def main_game():
ending=False
While Not ending:
clock.tick(30)
for event in pygame.event.get():
scene.handle_event(event)
scene.update()
scene.draw()
This is an easy way to cleanly seperate the game logic and allow context switching.
======================================Edit=========================================
Actually it won't work. Apperantly pygame only supports one display screen, and when you initialize another, it will close the first. You will stay with two varibles, which in fact are the same surface. You can have instead the game increasing the window size and playing the battle scene on the side of it, to do this, you can call the pygame.display.set_mode() again with different values. The varible which references the display screen will still be usable, as it change its reference to the new one. After the scene is over you can decrease the window back the same way.
==================================================================================
What basically happens is you run a loop, and each iteration of it is rendering and displaying a new frame.
When you call a function inside a loop, it doesn't continue to run until you finish running the function.
One way to solve this problen is just keep calling the function that updates the battle scene in the main loop.
Another way is by using threading. Threading is basically running multiple scripts ("Threads") in the same time.
Luckily, python already implemented this for us with the threading module.
It's too long for me to explain the module here, but you can learn it here. It might be a little complex if you haven't use threads before, but after some time it will be easier.
And If you want to learn more about threading you can go here.
Specificly here, you can have two threads, one for each loop/window, and run them in the same time.
Hope I helped you!
Yes, that is possible. SDL2 is able to open multiple windows. In the example folder you can take a look at "video.py".
https://github.com/pygame/pygame/blob/main/examples/video.py
"This example requires pygame 2 and SDL2. _sdl2 is experimental and will change."
I'm making a simple game using Pygame where you have to get (by moving the cursor over them) all the circles that will appear on the screen (more will appear every second). The code is quite long so I made an example code. This code works fine, the Pygame window doesn't become unresponsive at all:
import pygame, random, sys
pygame.init()
window=pygame.display.set_mode((480,360))
end_program=False
while not end_program:
for event in pygame.event.get():
if event.type==pygame.QUIT or pygame.key.get_pressed()[pygame.K_ESCAPE]: #If the user either click the "x", or pressed the "esc" key
end_program=True
pass
pygame.quit()
sys.exit()
However, in my game, in order to give the user the choice to play again, I need to wrap everything inside end_program in another loop. In the example shown, this is break_from_second_loop:
import pygame, random, sys
pygame.init()
window=pygame.display.set_mode((480,360))
end_program=False
while not end_program:
for event in pygame.event.get():
if event.type==pygame.QUIT or pygame.key.get_pressed()[pygame.K_ESCAPE]: #If the user either click the "x", or pressed the "esc" key
end_program=True
break_from_second_loop=False
while not break_from_second_loop:
pass
pygame.quit()
sys.exit()
Now if this is run, the window becomes unresponsive! Anyone know why something as simple as wrapping the code in another loop (without altering the code at all) does this?
The problem is that the game can't respond, or do anything at all, if you're not running the event loop. And in the other loop, you're not running the event loop.
This is a general problem with event-loop-based programming. You can't do anything that takes a long time, and you can't do anything that has to run across multiple events.
So, you have to break your loop apart into steps, and do just one step (or a few of them) each time through the event loop.
In this particular case, it's actually pretty simple: just change that while to an if (and move the has_got_all_circles=False outside the main loop), and your logic now runs exactly once each time through the event loop.
Alternatively, change it to an if and also move it inside the for, so now it runs exactly once per event, instead of once per event loop iteration.
A third alternative is to factor out the whole thing into a function and set it as an idle or timer function, that runs whenever the event loop is idle, or once every frame, or once every 20ms, or whatever.
It's hard to know which of the three is appropriate in your case, but the basic idea is the same in all of them, so I'll just show the second one:
end_program=False
break_from_second_loop=False
while not end_program:
for event in pygame.event.get():
if event.type==pygame.QUIT or pygame.key.get_pressed()[pygame.K_ESCAPE]: #If the user either click the "x", or pressed the "esc" key
end_program=True
if not break_from_second_loop:
pass
This blog post explains the general problem in more detail—although most of it isn't really appropriate to this specific problem.
The issue you're having is that you're not nesting your event loop code within the while loop that does your game logic. Here's the general structure of what you want:
while not end_program:
while not end_game:
handle_events()
do_one_frame_of_game_logic()
offer_another_game()
It's possible that offer_another_game will also need to be run in its own loop, with its own event handling code.
Indeed, you might want to encapsulate the logic you want to use into a state machine system. You'd have states like PlayingGame, GameOver, and DoYouWantToPlayAgain, each of which would run for a while, then hand off to another state. Your main loop would then be something like:
state = StartState()
while state:
state.handle_events()
state.update()
state.draw()
state = state.next_state() # most of the time, the state will return itself
I know that this is a vague question, but I was hoping to get some help. I know VBA pretty well, and have been able to accomplish some simple tasks in python as well as the statistical programming language in R.
What I am looking to do is create a simple application that lets me capture data, some of which is captured from the keyboard. Every time there is a keystroke, I wanted to create a new record in my dataset.
For some context, think about creating a simple interface that lets me track the location (and duration) of the puck in an NHL hockey game.
I am not really a programmer, but know just enough to get in trouble and am not really sure where to get started. I simply am looking for some thoughts on a very basic (non-commercial) solution.
Many thanks in advance.
EDIT: I want to capture how long the puck is each zone. I plan on using the directional keys left/right to "follow" the puck from zone to each. Each time the puck changes to a zone, I want to "close" the active record and start a new one. The start and end times will let me calculate how long the puck was in the zone. I also need a way to stop the creation of a new record for things like faceoffs, tv time outs, and end of period. I was planning on using the spacebar. My thought is that if I do this correctly, when I follow along, the times recorded should match up with what is posted on the game clock found on tv. Yes, this is a crazy idea.
If you choose to program in Python:
You could use the pygame package to easily capture keyboard events. The library was built to write games, but would probably give you the functionality that you are looking for with keydown/keyup events. It also handles mouse events and (since it is intended for games) has the ability to do graphics/text. The documentation is really good and it is cross platform. A possible downside is that you have to have a "screen" and it has to have focus. Here is a small example:
import pygame
def main():
"""
Pygame Example
"""
pygame.init()
screen = pygame.display.set_mode((200, 200))
app_running = True
while app_running:
# Get all key/mouse events from system.
events = pygame.event.get()
# Loop thru each event...
for e in events:
# Handle when the program is killed.
if e.type == pygame.QUIT:
app_running = False
break
# Handle key events.
elif e.type == pygame.KEYDOWN:
# Exit if escape is pressed.
if e.key == pygame.K_ESCAPE:
app_running = False
# Do something when the right arrow
# is pressed.
elif e.key == pygame.K_RIGHT:
print "right arrow pressed"
# Do something when the left arrow
# is pressed.
elif e.key == pygame.K_LEFT:
print "left arrow pressed"
# and so on ...
# Fill the screen to blank it.
#screen.fill(mycolor)
# Write someting to the screen to display.
#screen.blit(some_image, some_position)
# Flip to display.
#screen.flip()
pygame.quit()
if __name__ == '__main__':
main()
If you are using a version of Windows you could use the msvcrt library but the event handling is not as nice as pygame: instead of events, you have to deal with raw keyboard output and it is a little less intuitive. Here is a small code snippet from Robert Gillies on ActiveState:
import msvcrt
def funkeypress():
"""
Waits for the user to press any key including function keys. Returns
the ascii code for the key or the scancode for the function key.
"""
while 1:
if msvcrt.kbhit(): # Key pressed?
a = ord(msvcrt.getch()) # get first byte of keyscan code
if a == 0 or a == 224: # is it a function key?
b = ord(msvcrt.getch()) # get next byte of key scan code
x = a + (b*256) # cook it.
return x # return cooked scancode
else:
return a # else return ascii code
Look at scan() for keyboard input in R. And you didn't ask about mouse input, but consider locator() for that.
Put it a loop if you want the output immediately.
Is it necessary that you program it yourself? There is a free program called jwatcher Designed for scoring animal behavior in ethological studies. It seems like that would be well-suited to your task.