Enables the Mission Pinball Framework to drive VPE
Documentation at https://docs.visualpinball.org/plugins/mpf/
MPF is an open-source framework written in Python to drive real pinball machines. It has a "configuration over code" approach, meaning that 90% of what you'd do in a pinball game can be achieved through configuration (YAML files) rather than implementing it in code.
When you read MPF's Getting Started page, you'll notice a banner stating that "MPF is not a simulator." Well, you've found the simulator. ;)
This project lets you use MPF to drive game logic in VPE, a pinball simulator based on Unity. It does this by spawning a Python process running MPF and communicating with VPE through gRPC.
This project is available as a Unity package at registry.visualpinball.org
. To
install it, make sure the scoped registry is added in your Package Manager
settings. Then open the Package Manager in Unity and add
org.visualpinball.engine.missionpinball
under Install package by name.
The Unity package is built and published to our registry on every merge to master.
- Add the
MpfGamelogicEngine
component to the root object of your table - Click on 'Get Machine Desciption' in its inspector
- Click on 'Populate Hardware' to bring the coils switches and lamps from MPFs machine description into VPEs respective manager windows
- Assign the coils, switches and lamps to items on your playfield using the coil, switch and lamp manager windows
In order to import the package locally instead from our registry, clone and compile it. This will copy the necessary binaries into the Unity folder. Only then, import the project into Unity.
Since the Unity folder contains .meta
files of the binaries, but not the
actual binaries, .meta
files of uncompiled platforms are cleaned up by Unity.
In order to not accidentally commit those files, we recommend to ignore them:
git update-index --assume-unchanged Plugins/Google.Protobuf.dll.meta
git update-index --assume-unchanged Plugins/Grpc.Core.Api.dll.meta
git update-index --assume-unchanged Plugins/Grpc.Net.Client.dll.meta
git update-index --assume-unchanged Plugins/Grpc.Net.Common.dll.meta
git update-index --assume-unchanged Plugins/Microsoft.Extensions.Logging.Abstractions.dll.meta
git update-index --assume-unchanged Plugins/System.Buffers.dll.meta
git update-index --assume-unchanged Plugins/System.Diagnostics.DiagnosticSource.dll.meta
git update-index --assume-unchanged Plugins/System.IO.Pipelines.dll.meta
git update-index --assume-unchanged Plugins/System.Memory.dll.meta
git update-index --assume-unchanged Plugins/System.Numerics.Vectors.dll.meta
git update-index --assume-unchanged Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta
git update-index --assume-unchanged Plugins/System.Threading.Tasks.Extensions.dll.meta
git update-index --assume-unchanged Plugins/VisualPinball.Engine.Mpf.deps.json.meta
git update-index --assume-unchanged Plugins/VisualPinball.Engine.Mpf.dll.meta
git update-index --assume-unchanged Plugins/VisualPinball.Engine.Mpf.pdb.meta
MPF is a standalone Python application and not a library. Despite this, we want
to make its use with VPE as seamless as possible, so we include precompiled MPF
binaries and start them together with VPE automatically. The process of starting
up and communicating with MPF via gRPC while respecting the many configuration
options that are available is quite complex and managed in a single class:
MpfWrangler
. It could be worthwhile to split it up into one class that manages
the MPF process and another that manages the gRPC connection.
MPF has two APIs: One to control hardware on the playfield, and one to play
media in the backbox. The former is the main, essential part of this MPF
integration and the latter was added later. The hardware controller is
responsible for sending switch changes to MPF and applying its commands to
correct items on the playfield. This is done by the class MpfGamelogicEngine
.
It also retrieves and stores the machine description from MPF at design time,
which allows the table author to create the mapping between MPF and VPE coils,
lamps and switches that is used at runtime to control the playfield.
The precompiled binaries included here are slightly different from the official
version of MPF. They are based on 0.80.0.dev6
, with the addition of a Ping
RPC that allows VPE to check whether MPF is ready without any side effects (like
starting the game). This way, the game can start as soon as MPF is ready,
regardless of how long MPF takes to start up. I have
proposed this change to the
MPF developers, but as of February 2025 they have not yet gotten around to
including it in the official version. The binaries were made using
PyInstaller. I'm not completely sure anymore what
parameters I used, but this should be pretty close:
pyinstaller --collect-all mpf --exclude-module mpf.tests --name mpf --noconfirm mpf/**main**.py
.
This repository includes a basic media controller implementation that is fully compliant with the BCP spec and can parse all documented message types. Without writing code, table authors can trigger sounds, toggle game objects and display variables in text fields, but its capabilities are not comparable to those of the official Kivvy and Godot based media controllers made by the MPF developers.
When the included media controller is selected in the game logic engine
inspector, the MpfWrangler
creates the BcpInterface
in its constructor,
which in turn creates the BcpServer
that ultimately starts a TcpListener
to
send and receive messages from MPF via TCP. Messages are sent and received
through a message queue, but the server does not run on a separate thread.
To communicate with the various available media controller implementations, the MPF developers invented their own little message format called 'Backbox Control Protocol,' or BCP for short. The downside compared to something like gRPC is that there is no parser. All we get is strings. Much of the code that comprises VPE's media controller is dedicated to parsing those message strings into strongly typed C# objects and back. For each message type, there is a class that parses and represents instances of it.
There is also a message handler class for each type of message that fires an
event every time a message of the associated type is received. Some message
types must be requested by the media controller using the
monitor_start
command. By the number of listeners to the events of each message handler, we
can tell when a message type is actually of interest and monitor only those
message types.
Many BCP message types are sent when some variable in MPF changes. For easy
access to these values in VPE, there are monitoring classes for these types of
messages that share a common base class and provide the most recently sent value
and an event that is triggered when a new value is received. For example, using
the CurrentPlayerPlayerMonitor
class, other code can easily keep track of
whose turn it is without having to go through the
PlayerTurnStartMessageHandler
.
To the table author, who is not really allowed to write C# code because it cannot be shipped with the table file, we offer a few Unity components that can make sounds and toggle objects based on events and modes from MPF and display player and machine variables in text fields. The sad part is that this severely limits what would otherwise be possible with this media controller implementation. Many message types are not accessible and some fairly basic functionality like displaying player variables of a player other than the currently active one is not supported. To fix this, the selection of components and their feature-set should grow and there should be a visual scripting interface for less common use-cases.
The most important missing feature is support for slides or more generally support for displaying stuff on the backbox display. The first obstacle here is that the way slides work in MPF is not officially defined or documented.