Python Bindings using pybind11 for EntityX (α Alpha)
This system adds the ability to extend entity logic with Python scripts. The goal is to allow ad-hoc behaviour to be assigned to entities through scripts, in contrast to the more strictly pure entity-component system approach.
from entityx import Entity, Component, emit
from mygame import Position, Health, Dead
class Player(Entity):
position = Component(Position, 0, 0)
health = Component(Health, 100)
def on_collision(self, event):
self.health.health -= 10
if self.health.health <= 0:
self.dead = Component(Dead, self)
EntityX Pybind11 has the following build and runtime requirements:
ENTITYX_PYTHON_BUILD_TESTING
: Enable building of testsENTITYX_ROOT
: Set path to EntityX root if CMake did not find itPYTHON_ROOT
: Set path to Python root if CMake did not find it
Check out the source to entityx_pybind11, and run:
mkdir build && cd build
cmake ..
make
make install
- Python scripts are attached to entities via
PythonScript
. - Systems and components can not be created from Python, primarily for performance reasons.
Events are proxied directly to Python entities via(Removed for now to keep it simple)PythonEventProxy
objects.PythonSystem
manages scripted entity lifecycle and event delivery.
To add scripting support to your system, something like the following steps should be followed:
- Expose C++
Component
andEvent
classes to Python withPYBIND_PLUGIN
. - Initialize the module with
pybind11_init()
. - Create a Python package.
- Add classes to the package, inheriting from
entityx.Entity
and using theentityx.Component
descriptor to assign components. - Create a
PythonSystem
, passing in the list of paths to add to Python's import search path. - Optionally attach any event proxies.
- Create an entity and associate it with a Python script by assigning
PythonScript
, passing it the package name, class name, and any constructor arguments. - When finished, call
EntityManager::destroy_all()
.
entityx::python
primarily uses standard pybind11
to interface with Python, with some helper classes and functions.
In most cases, this should be pretty simple. Given a component, provide a pybind11
class definition with two extra methods defined with EntityX::Python helper functions assign_to<Component>
and get_component<Component>
. These are used from Python to assign Python-created components to an entity and to retrieve existing components from an entity, respectively.
Here's an example:
namespace py = pybind11;
using namespace pybind11::literals;
struct Position : public entityx::Component<Position> {
Position(float x = 0.0, float y = 0.0) : x(x), y(y) {}
float x, y;
};
void export_position_to_python() {
py::class_<Position>("Position")
//Defines the initalizer to have default values for x and y
.def(py::init<py::optional<float, float>>(), "x"_a = 0.f, "y"_a = 0.f)
// Allows this component to be assigned to an entity
.def("assign_to", &entityx::python::assign_to<Position>)
// Allows this component to be retrieved from an entity.
// Set return_value_policy to reference raw component pointer
.def_static("get_component", &entityx::python::get_component<Position>,
py::return_value_policy::reference)
.def_readwrite("x", &Position::x)
.def_readwrite("y", &Position::y);
}
// Namespace to avoid collision other pybind11_init();
namespace python {
PYBIND11_PLUGIN(mygame) {
export_position_to_python();
}
}
Use the entityx.Component
class descriptor to associate components and provide default constructor arguments:
import entityx
from mygame import Position # C++ Component
class MyEntity(entityx.Entity):
# Ensures MyEntity has an associated Position component,
# constructed with the given arguments.
position = entityx.Component(Position, 1, 2)
def __init__(self, entity = None):
# Overiding __init__ you must call super()'s init
# passing in entity
super(MyEntity, self).__init__(entity=entity)
assert self.position.x == 1
assert self.position.y == 2
Finally, initialize the mygame
module once, before using PythonSystem
, with something like this:
// This should only be performed once, at application initialization time.
python::pybind11_init();
Then create a PythonSystem
as necessary:
// Initialize the PythonSystem.
vector<string> paths;
// Ensure that MYGAME_PYTHON_PATH includes entityx.py from this distribution.
paths.push_back(MYGAME_PYTHON_PATH);
// +any other Python paths...
entityx::python::PythonSystem python(paths);