You need to choose a standardized ABI (application binary interface) that all the languages you want to support can themselves support. Typically, this will be C (not C++) because C has a standardized ABI and many, many languages choose it as their method of inter-operating with other languages as a result.
You will need to define a expected interface, one you expect extensions to your game to expose (such a PluginLoaded
entry point).
You'll then want to load your game's extensions from dynamically linked libraries (.dll
s on Windows, .so
s on non-Windows platforms). Once loaded into memory you locate and call your expected entry point (PluginLoaded
, et cetera) at the appropriate time.
Another option you can use instead support extension via interprocess communication. In this approach you still define an agreed-upon interface, except this time it's a networking protocol (or similar form of IPC) instead of a binary interface, and instead of loading DLLs you simply wait for connections from other programs that are running to "extend" your game. You don't have to deal with DLLS or a mandated binary format in this approach, but your users instead have to write and run their own processes to leverage extensions, which is somewhat awkward.
Finally you could combine the approaches, so you load DLLs to avoid the annoyance of having to run "extension" processes, but once loaded you have only a minimal entry point into the plugin that is called. This minimizes the amount of boilerplate work you have to do to expose a C API to the various bits of your game you'd want to extend. Instead, invoking the single entry point tells the plugin to start communicating with your game via IPC, even though they are technically in the same process.