Skip to content

Creating your first mod

You’ll need the latest BmSDK version and a development environment set up per the README.

Every mod lives in its own folder under %GameDir%\BmGame\Mods:

BmGame\Mods\
MyFirstMod\
mod.toml
scripts\
MyFirstModScript.cs

mod.toml marks the folder as a mod. BmSDK skips any folder without one. To start, it only needs a name:

name = "MyFirstMod"

Sub-folders under scripts are picked up too. Each mod compiles and hot-reloads on its own, so a broken mod won’t take yours down with it.

A script is a class marked with [Script] that inherits from Script:

using BmSDK;
namespace Example; // optional but recommended
[Script]
public class ExampleScript : Script
{
// Main() is optional
public override void Main()
{
Debug.Log("Hello world!");
}
}

Main() runs once when the engine first becomes ready. Script.cs lists the other entry points with inline docs.

// This is inside the class
public override void OnKeyDown(Keys key)
{
switch (key)
{
case Keys.J:
// Get the pawn that the player is possesing (Batman)
var rpp = Game.GetPlayerPawn();
// Load package we need for RCharacter_Joker
Game.LoadPackage("FunFair");
// Spawn in a pawn
var enemy = Game.SpawnCharacter<RPawnVillainThug, RCharacter_Joker>(rpp.Location, rpp.Rotation);
// Now we can call methods on the created Pawn
enemy.bArmoured = true;
break;
}
}

Game.GetPlayerPawn() returns Batman himself (RPawnPlayer), which is what you want for anything physical like health or position. Game.GetPlayerController() returns the player (RPlayerController), which drives movement and sits at the camera rather than at Batman.

Game.LoadPackage() pulls a package into memory so you can use what’s inside it. The game only keeps what the current level needs, so any time you reference an asset or class from elsewhere, load its package first. Here that’s FunFair, which owns RCharacter_Joker. Packages live in %GameDir%\BmGame\CookedPCConsole and can be browsed with UPK Explorer to find which one holds what you need. Pass just the name, no .upk and no path.

Game.LoadPackage() loads on the spot. If your mod always needs the same packages, declare them in mod.toml and BmSDK loads them once at startup, before any level:

name = "MyFirstMod"
[preload]
packages = ["FunFair"]
keep_alive = ["FunFair.SomeObjectName"]

keep_alive takes full object paths (Package.ObjectName, the same ones Game.FindObject() takes) and roots those objects so the garbage collector won’t drop them between levels. Loading a package doesn’t by itself keep its contents alive.

Press F5 in Visual Studio to launch the game. “Hello world!” should show up in the Output window, and pressing J in story mode spawns an armoured Joker at Batman’s location.

Entry points only get you so far. Two guides cover the APIs you’ll reach for next:

Beyond that, the Game utility class is the best overview of what BmSDK exposes, with worked examples in DemoScript.cs and IceMage’s Chaos Mod.

Expect some trial and error, since plenty of the game’s internal methods don’t work or have side effects. UE Explorer helps make sense of the quirks, and the Arkham Workshop Discord is the place to ask for help.