Skip to main content

A simple decorator-based event handler and key bind system for use with pygame

Project description

Contributors Forks Stargazers Issues MIT License


Simple Events

A simple, decorator-based event system for Pygame.
Explore the docs »

Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Getting Started
  3. Usage
  4. Roadmap
  5. License
  6. Contact
  7. Acknowledgments

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.

(back to top)

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.

(back to top)

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

(back to top)

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

(back to top)

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.

(back to top)

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

(back to top)

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:

  1. 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.

  1. 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.

(back to top)

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.

(back to top)

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).

(back to top)

License

Distributed under the MIT License. See LICENSE.txt for more information.

(back to top)

Contact

Better Built Fool - betterbuiltfool@gmail.com

Project Link: https://github.com/BetterBuiltFool/simple_events

(back to top)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pygame_simple_events-1.0.0.tar.gz (25.6 kB view details)

Uploaded Source

Built Distribution

pygame_simple_events-1.0.0-py3-none-any.whl (24.5 kB view details)

Uploaded Python 3

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

Hashes for pygame_simple_events-1.0.0.tar.gz
Algorithm Hash digest
SHA256 c15450a833768ecc1e20b870a9abea700f73b6ddb655df2024fdc82be5feb687
MD5 155838e3c8a4e030c21ab622a7eec190
BLAKE2b-256 95ff14b65867e64f919200eef6fb94a6cdf138db9e0d7a8a05d5ffb891ccc1b6

See more details on using hashes here.

File details

Details for the file pygame_simple_events-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pygame_simple_events-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c7a507fbb00e0ffb2f5c216b6a4f68e344f45177a1de434839669777ab7d8ef6
MD5 3c533697594feec6c820c5f7d144a2ce
BLAKE2b-256 6eb2c89951adab271ba4700389e7b04889c723ea05f4a3ae2c41cb66a3ee2caf

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page