The Godot BCP Server is a plugin for connecting Godot to the Mission Pinball Framework (MPF) using the Backbox Control Protocol (BCP).
With this plugin, pinball creators using MPF can choose Godot as their display engine and drive all screen slides, videos, animations, and sounds via a Godot project.
- Direct socket connection between MPF and Godot for high-speed synchronization
- Fully extensible base classes allow customization and overrides
- Direct access to player variables, machine variables, and settings
- Built-in signals for core events, extensible to add any signals you need
- Easily subscribe to MPF events and send events to MPF
- Support for keyboard input bindings to aid in development
Create a new Godot project and find Godot BCP Server in the Asset Library. Add this plugin to your project and save.
Note: Godot BCP Server uses custom class names for extending support to your project. It is recommended to exit and restart Godot after adding the plugin, to ensure that the class names are recognized and available.
Godot BCP Server provides base classes for two Autoload instances: BCPServer for the socket connection to MPF, and MPFGame for game, player, machine, and setting values.
This class manages the connection to MPF and all data coming in and out. For optimal performance, it runs in a separate thread from the main Godot instance.
Create a new autoload script (e.g. /autoloads/server.gd) and add extends BCPServer
to the top of the file. You can then set class variables and override
class methods. In your Project Settings > AutoLoad, add your script as an autoload with the name "Server".
Note: The Game autoload script (see MPFGame below) must be listed before the Server autoload script, because the Server is dependent on the Game.
The init method allows you to configure the base setup of the server. There are two properties you can set here.
The auto_signals
property is a list of MPF events that have Godot signals of the
same name. This is a very fast and easy way to setup signals if you want to
re-broadcast an MPF event to listeners in Godot.
extends BCPServer
signal bonus(payload)
signal hurryup_started(payload)
signal options(payload)
func _init() -> void:
self.auto_signals = ['options', 'bonus', 'hurryup_started']
The registered_events
property is a list of MPF events that Godot should
subscribe to. By default only the basic player, mode, and game events are sent
via BCP. With registered
events you can specify any events you want to receive.
func _init() -> void:
self.registered_events = [
"jackpot_lit",
"jackpot_collected",
"mystery_lit",
"skillshot_hit",
"timer_boss_mode_tick",
]
At some point in your game's startup sequence you need to call Server.listen()
on your server to open a port and begin listening for an MPF connection. The name
of the server instance will be based on your autoload's name.
listen()
should be called from your active scene when the Godot MC is ready to
connect to MPF, or in your main scene's _ready()
method if you want to connect
immediately.
The on_method event allows you to define custom callback methods for events received by MPF. This list is evaluated before the default event handlers, and if this method returns a falsey value the default behavior will be skipped. Return the original message to proceed to the default handlers.
Note: The BCP Server runs on a separate thread, so all callbacks should be
wrapped in call_deferred(method_name, *args)
to ensure that the callback
is thread-safe (i.e. handled on the main thread).
func on_message(message):
match message.cmd:
"high_score_enter_initials":
call_deferred("deferred_scene", "res://modes/HighScore.tscn")
"quest_shot_jackpot":
call_deferred("emit_signal", "popup", message)
_:
return message
The on_mode_start method is called whenever a mode starts in MPF. This is the place where you can load scenes, play music, or do any other mode-specific behavior.
func on_mode_start(mode_name: String) -> void:
if mode_name.substr(0,6) == "quest_":
self._deferred_scene_top(self._next_quest)
return
if mode_name.substr(0,7) == "threat_":
call_deferred("start_threat", mode_name)
return
match mode_name:
"attract":
self.deferred_scene("res://modes/AttractMode.tscn")
"bonus":
self.deferred_scene("res://modes/BonusMode.tscn")
"game":
MusicPlayer.play_sfx("game_started.wav")
There are other methods that can be defined in server for custom behavior.
func on_ball_start() -> void:
# Called when a player's ball starts
pass
func on_ball_end() -> void:
# Called when a player's ball ends
pass
func on_connect() -> void:
# Called when a BCP connection is established with MPF
pass
func on_disconnect() -> void:
# Called when the BCP connection to MPF is ended
pass
func on_input(event_payload: PoolStringArray) -> void:
# Called when an input event key is pressed
pass
There are some built-in methods that are convenient.
You can call self.send_event
to send an event to MPF!
You can call self.deferred_game
to schedule a method to be called in the Game
autoload singleton. This is a thread-safe way to trigger game callbacks from events.
You can call self.deferred_game_player
to schedule an update to a player variable
using the properties result.name
and result.value
.
You can call self.deferred_scene
to change the main scene tree to the resource
path specified (i.e. call get_tree().change_scene(scene_res)
on the main thread).
You can call self.deferred_scene_to
to change the main scene tree to the resource
pack specified (i.e. call get_tree().change_scene_to(scene_pck)
on the main thread).
The Game class provides access to player variables, machine variables, settings, and active modes.
Create a new autoload script (e.g. /autoloads/game.gd) and add tool
and
extends MPFGame
to the top of the file. You can customize the class variables
and override class methods as described below. In your Project Settings > AutoLoad add your script with the name "Game".
Note: The Game autoload script must be listed before the Server autoload script (see BCPServer above), because the Server is dependent on the Game.
You can initialize anything you need here.
The MPFGame class will automatically create player objects to mirror MPF, but one helpful approach is to mock out a player object so that you can run scenes in Godot without starting an MPF game. This can save time when you need to quickly debug things.
You can specify certain player variables to have corresponding signal
events so
that whenever the player variable changes, a signal with the same name is broadcast
in Godot.
tool
extends MPFGame
signal atomic_blaster(payload)
signal extra_balls
func _init()
self.auto_signal_vars = ['atomic_blaster', 'extra_balls']
The Game autoload has a self.player
property that is a dictionary with all the
values of the current player. The self.players
property is an array with all
the players of the current game.
Because the Game is an autoload, you can access the player variables anywhere in
your codebase, e.g. Game.player.score
.
The Game autoload has self.machine_vars
and self.settings
properties which are
dictionaries with all the values of the MPF machine and settings values.
The Game autoload has self.active_modes
which is a list of the names of all of
the currently active modes that MPF is running.
The Game has a signal player_update
that will emit whenever a player variable
changes value. You can subscribe to player changes anywhere in your Godot project
using this signal.
# Any scene in your game, e.g. ShipBattle
Game.connect("player_update", self, "_on_player_update")
func _on_player_update(variable_name: String, value):
if variable_name == "ships_destroyed":
self.destroy_ship(value)
The Game has a signal player_added
that will emit whenever a new player is
added to the game.
The Game has a signal credits
that will emit whenever the machine's credits
change.
The Game class has a static method to format integers as strings with comma-separation at every thousand. Just pass in a number and get a string back!
Game.comma_sep(5432198)
# returns "5,432,198"
The Game class has a static method to return a string with an optional "s"
character
added based on whether a value is not equal to the integer 1
.
Game.pluralize("New Reward%s Available", 2)
# returns "New Rewards Available"
Game.pluralize("New Mission%s Unlocked", 1)
# returns "New Mission Unlocked"
Note that you can include both the value and the pluralization in one call, but
you must escape the pluralization template string by using %%s
.
Game.pluralize("%s Hit%%s Remaining", 5)
# returns "5 Hits Remaining"
Game.pluralize("Hit %s Target%%s to Complete", 1)
# returns "Hit 1 Target to Complete"
The Log autoload scripts provides a convenient, MPF-style logging interface with matching levels and output formatting.
You can choose not to incorporate the included log script in your project. The BCP Server module requires logging, so it will instantiate its own logger instance.
In your Godot project settings, you can include addons/godot_bcp_server/log.gd to gain access to a global Log instance with methods for logging to the console and files.
The default levels are VERBOSE (0), DEBUG (10), INFO (20), and WARN (30). Messages
at the error and fail level will always be logged. You can specify the level
at any time by calling the setLevel
method. By default, the log logs at level INFO.
Log.setLevel(10)
Like MPF, the logger uses levels to determine the output. It includes methods
verbose
, debug
, info
, warn
, error
, and fail
, each accepting parameters for
the message and values. It is recommended to pass the message string and values
separately rather than combining the string when you call it—this way, calls
below the output level won't waste time on the string formatting.
Log.info("Player var %s updated, new value %s", [var_name, value])
If you prefer to customize your logger, you can implement your own script with
extends Logger
. Any of the above methods can be overridden, and you can also
change the output of the log messages by overriding the log output method.
func log(level_name: String, message: String, args=null) -> void:
print(...) # Output whatever string you like
To enable global access to your custom logger, save your script and add it to
your project as an autoload. If your autoload has the global variable name Log
,
the BCP Server will detect and use your logger instead of the built-in one.
If you do not wish to name your autoload Log
, you can still instruct the BCP
Server to use it by assigning your logger as self.logger
in your custom Server
class _init()
method.
User contributions are welcome!
https://github.com/missionpinball/godot-bcp-server
For questions and support, please open an issue in the above GitHub repository or visit the MPF forums at https://groups.google.com/g/mpf-users