A simple decorator-based event handler and key bind system for use with pygame
Project description
Simple Events
A simple, decorator-based event system for Pygame.
Explore the docs »
Report Bug
·
Request Feature
Table of Contents
About The Project
Simple Events is a simple system that uses decorator syntax to register functions to Pygame events, allowing those function to be fired whenever the assigned event occurs. It also features a keybind manager, which similarly can assign functions to remappable keybinds and controller binds.
Getting Started
Simple events is written in pure python, with no system dependencies, and should be OS-agnostic.
Installation
Simple events can be installed from the PyPI using pip:
pip install pygame_simple_events
and can be imported for use with:
import simple_events
Simple events also require Pygame Community edition to be installed.
Usage
EventManagers and KeyListeners are instantiated like loggers from the built-in python logging library.
Event Manager
import simple_events
LOCAL_MANAGER = simple_events.getEventManager("Example")
This will generate an instance with the handle "Example", which will be stored by the manager system. If another module calls for that same handle, both modules will share the same event manager. Modules can even have multiple event managers to allow for control over execution context. Really, you can use as many or as few managers as desired.
The variable to which the event manager is assigned does not need to be written as a constant, though it is recommended for noticeability and avoiding accidental reassignment. The variable name has no special meaning to the event manager system.
Functions are registered using the register decorator along with the Pygame event type it wants to respond to. For example, we will use pygame.QUIT
@LOCAL_MANAGER.register(pygame.QUIT)
def quit_function(event: pygame.Event) -> None:
# Do
# Things
# Here
# When
# Quitting
The function can have any syntactically valid name, and can even be used elsewhere as a normal function.
The event manager will pass on the event to the function, so the function must be able to accept an event being passed to it as its first parameter, even if it has no use for event-specific data. This can mean using either an underscore or the *args syntax to ignore the incoming event data. Decorated functions cannot accept any additional positional arguments, unless using *args. The event manager will not provide any arguments beyond the event, so additional arguments must be optional, and are generally not recommended.
Additionally, a function can be assigned to multiple events.
Alternatively, a function does not need to use decorator syntax for registration.
LOCAL_MANAGER.register(pygame.USEREVENT)(quit_function)
This method is useful for late binding a function.
Event managers can also be used on objects!
@LOCAL_MANAGER.register_class
class TestClass:
@LOCAL_MANAGER.register_method(pygame.QUIT)
def sample_method(self, event: pygame.Event) -> None:
# Do
# The
# Things
With this, the event manager will track all instances of TestClass, and whenever the assigned event is called, it will call the registered methods on all instances. As with regular registered callables, it must be able to accept the event as an argument, but also uses self to allow access to the instance within the function.
Every manager that registers a method in a class should also register that class. If a manager registers a method but not the class, the method cannot be called, and will have a dangling attribute left over from the registration process.
@LOCAL_MANAGER.register_class
class TestClass:
@LOCAL_MANAGER.register_method(pygame.QUIT)
def sample_method(self, event: pygame.Event) -> None:
# Do
# The
# Things
@OTHER_MANAGER.register_method(pygame.QUIT) # This will not work as expected
def other_sample_method(self, event: pygame.Event) -> None:
# Do
# Other
# Things
Methods cannot be late registered, unlike regular callables. Classes can be late registered, but the event manager will not pick up on existing instances.
For more information on Pygame events, including a list of event type with descriptions, see here
Key Listener
import simple_events
KEYBINDS = simple_events.getKeyListener("Example")
Key Listeners are seperate from event managers and can share handles without conflict.
Key binds are slightly more involved. They require a bind name, and can accept an optional default key as well as mod keys. They have the same function signature requirements as regular event binds.
@KEYBINDS.bind("example_name", pygame.K_p, pygame.KMOD_SHIFT)
def some_function(_):
# Does
# Something
# When
# Shift+P
# Is pressed
Default key specifies the initial key needed to activate the bind, and can be left blank, but this will make the bind "unbound" and unable to be called. With a default key set, the mod key specifies what additional mod keys (such as Alt, Control, or Shift) need to be pressed to activate the bind. If none is set, the bind will be called regardless of mod keys. If pygame.KMOD_NONE is used, the bind will fire only if no mod keys are pressed.
Optionally, an event may be specified. By default, it uses key down for key events, and for controller inputs it will attempt to intuit the desired event. The callable will be called only when the required function is called.
If the event type does not match the input type, the bind will never be called.
@KEYBINDS.bind("example_name", pygame.K_p, pygame.KMOD_SHIFT, pygame.KEYUP)
def some_function(_):
# Does
# Something
# When
# Shift+P
# Is pressed
If a bind is used for multiple functions, the first processed call is used to establish the default keys.
@KEYBINDS.bind("example2", pygame.K_o)
def func1(_):
...
@KEYBINDS.bind("example2", pygame.K_z, pygame.KMOD_CTRL)
def func2(_):
...
In this example, pressing the "o" key will activate both functions, even though func2 asks for Ctrl+Z.
Key Listeners also work with classes and methods, following similar syntax as the Event Manager.
For more information on pygame and key handling, including a list of key names, see here
Key Maps
Key Listeners rely on the Key Map to determine which binds to call when a key is pressed. Every Key Listener uses the same Key Map.
Key Maps can be modified via a Key Listener.
import simple_events
KEYBINDS = simple_events.getKeyListener("Remapper")
@KEYBINDS.bind("example_name", pygame.K_p, pygame.KMOD_SHIFT)
def some_function(_):
...
KEYBINDS.rebind("example_name", pygame.K_m, pygame.KMOD_ALT)
This changes the key for all functions bound to "example_name", which now get called when Alt+M is pressed instead of Shift+P.
Key Maps and Joy Maps can also be saved and loaded from file. This requires a path to the desired file location, including the file name and extension. If the file type is supported, it will be intuited from the file extension. Unsupported file types can have a File Parser class passed to force a specific encoding.
import simple_events
KEYBINDS = simple_events.getKeyListener("Saveloader")
KEYBINDS.save_to_file("path/to/the/file.json")
In this case, the current KeyMap will be saved to file.json, and will use the JSON format.
A Key Map can be loaded similarly.
import simple_events
KEYBINDS = simple_events.getKeyListener("Saveloader")
KEYBINDS.load_from_file("path/to/source/key_binds.json")
This will try to load key_binds.json and merge the key binds into the current Key Map. The loaded key binds take precedence over existing ones, but if a key bind exists in the current map but not the loaded one, it is carried over without modification.
Controller Maps
Also known as JoyMaps.
JoyMaps are to controller event what KeyMaps are to keyboard events. They have many of the same properties to KeyMaps, but use a dictionary of a key and value for the binding.
import simple_events
CONTROLLER = simple_events.getKeyListener("controller_stuff")
@CONTROLLER.bind("example", {"button": 0})
def test_func(_):
pass
In this case, it will look for the "button" attribute of a Joystick event, and will be called on button 0. It should be noted, different controller types label their buttons differently.
Instance-specific attributes like "instance_id" and "value" are disregarded by the Joy Map.
For more information on pygame and joystick/controller behavior, including examples of button maps, see here
Passing Events to the Managers
With functions registered to the managers, you now need to tie the managers into the event queue.
There are two options:
- Notify All
import simple_events
import pygame
# pygame setup and initialization
while game_is_running:
# Frame rate handling
for event in pygame.event.get():
simple_events.notifyEventManagers(event)
simple_events.notifyKeyListeners(event)
# Game Loop stuff
This ensures that every manager is being fed events as they happen.
- Direct Notification
import simple_events
import pygame
# pygame setup and initialization
MANAGER = simple_events.getEventManager("Example") # Remember, the handle needs to be the same as wherever events are assigned
MANAGER2 = simple_events.getEventManager("Example2")
KEYBINDS = simple_events.getKeyListener("Example")
while game_is_running:
# Frame rate handling
for event in pygame.event.get():
MANAGER.notify(event)
MANAGER2.notify(event)
KEYBINDS.notify(event)
# Game Loop stuff
The developer must track the managers and is responsible for feeding them the events. This allows greater control over if and when a given manager is activated. For example, it may be desirable to have a manager that handles menu functions, and another gameplay functions. This way, the game loop can test for game state, and run only the menu functions when in menu, and only gameplay functions while playing.
Additionally, event managers and key listeners support calling only sequential and only concurrent functions and methods, if desired. This may be done using the [manager].notify_sequential(event) and [manager].notify_concurrent(event) methods, respectively. It should be noted that calling both the general and specific notifies on the same frame will call those functions twice.
Concurrency
By default, functions are called using Python's threading library. This means that the called functions can be blocked, such as by using time.sleep, without blocking the rest of the program.
However, this comes at the cost of thread safety. These functions may be able to change state at unpredictable times, and generate race conditions. Always use caution when dealing with concurrency, and investigate Python's threading library for more info on best practices regarding concurrency.
Optionally, you can use
@KEYLISTENER.sequential
to mark a function as sequential. Sequential functions and methods will called after the concurrent functions and methods, and will run one after the other. They lose out on being easily blockable, but reduce the risk of forming race conditions, especially if not sharing resources with any concurrent functions.
Roadmap
- Add support for additional formats for saving and loading keybinds.
- Add a utility for simplifying the default input for controller binds.
See the open issues for a full list of proposed features (and known issues).
License
Distributed under the MIT License. See LICENSE.txt
for more information.
Contact
Better Built Fool - betterbuiltfool@gmail.com
Project Link: https://github.com/BetterBuiltFool/simple_events
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file pygame_simple_events-1.0.0.tar.gz
.
File metadata
- Download URL: pygame_simple_events-1.0.0.tar.gz
- Upload date:
- Size: 25.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.4
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | c15450a833768ecc1e20b870a9abea700f73b6ddb655df2024fdc82be5feb687 |
|
MD5 | 155838e3c8a4e030c21ab622a7eec190 |
|
BLAKE2b-256 | 95ff14b65867e64f919200eef6fb94a6cdf138db9e0d7a8a05d5ffb891ccc1b6 |
File details
Details for the file pygame_simple_events-1.0.0-py3-none-any.whl
.
File metadata
- Download URL: pygame_simple_events-1.0.0-py3-none-any.whl
- Upload date:
- Size: 24.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.4
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | c7a507fbb00e0ffb2f5c216b6a4f68e344f45177a1de434839669777ab7d8ef6 |
|
MD5 | 3c533697594feec6c820c5f7d144a2ce |
|
BLAKE2b-256 | 6eb2c89951adab271ba4700389e7b04889c723ea05f4a3ae2c41cb66a3ee2caf |