/UKMF

Ultrakill Modding Framework

Primary LanguageC#

UKMF

The Ultrakill Modding Framework (UKMF) is a BepInEx plugin that contains various APIs and utilities to aid in creating custom content for the early access title ULTRAKILL.

Functionality

UKMF currently has the following features:

  • Ability to reserve custom enum values without fear of mod conflict
  • API for defining custom arm variations
  • Easy methods for mods to store their own custom save data
  • A simple interface for mods to read static files on disk
  • Interface for adding custom content to the spawn menu
  • Helper classes for handling save data and weapon configuration

Difference from ULTRAKIT

ULTRAKIT is another project with a very similar goal as UKMF. However, there are quite a few differences between this project and ULTRAKIT, which are:

ULTRAKIT UKMF
Plugins written in Lua Plugins written in C#
Can access wrappers for some engine/base game APIs Can access any part of the game directly
Unity is required to make plugins Unity is only required for custom assets
Easy learning curve Intermediate C# knowledge required

Compatability

There are no plans to make UKMF compatible with any other modding framework, including ad-hoc ones like Ultrakill-Custom-Arms-Proof-Of-Concept.

Feature Showcase

Custom Enums

Using the CustomEnums static class you can reserve IDs for existing enumerations in the game:

BlockType Lava = (BlockType)CustomEnums.CurrentSlotData.GetValue<BlockType>("your.guid.here", "Lava");

When that code is executed, a new ID is generated and saved as belonging to your mod and being identified by whatever name you gave it; in this case, Lava.

Custom Arms

To create a custom arm, first you will need to define how it is created:

public class MyArmFactory : AbstractPrefabFactory {
	// The return value of this method is instantiated and used as the object for this arm.
	// This example just returns the object for the Knuckleblaster; you can return any GameObject you want through this method.
	public override GameObject CreateObject() {
		return FistControl.Instance.redArm;
	}
}

Then, all you have to do is define your arm in the API:

CustomArms.AddArm(new CustomArmInfo() { 
	ID = (FistType)CustomEnums.CurrentSlotData.GetValue<FistType>("your.guid.here", "MyArm"),
	DisplayName = "My Arm",
	PrefabFactory = new MyArmFactory(),
	IconColor = Color.magenta,
	Holdable = true,
});

Custom Save Data

Saving your own custom data is easy with the ModdedSaveFile class:

// CreateSaveFile is an extension method of BaseUnityPlugin, and requires a reference to your main plugin instance to use
// This particular snippet should be placed somewhere in the main plugin class, if you want to use it in other contexts you should use the Singleton pattern
ModdedSaveFile MyFile = this.CreateSaveFile("MyData");

You can modify the stored data through the Data property:

MyFile.Data.SetString("message", "Hello, World!");
MyFile.Save(); // Custom save files do not automatically write to disk when modifying them

Later, you can then retrieve that data:

string message = MyFile.Data.GetString("message");
Logger.LogMessage(message);

You can also store arbitrary binary data using GetBytes and SetBytes:

MyFile.SetBytes("someData", new byte[] { 0x0C, 0x25 });

foreach(var b in MyFile.GetBytes("someData")) {
	Logger.LogMessage(b.ToString());
}

Asset Manager

The AssetManager class aids in reading files from disk. To create one, simply use the AssetManager.Create method:

// In this context, "Info" is the PluginInfo object associated with your main plugin class.
var assets = AssetManager.Create(Info);

Then you can load text, JSON, image, and binary files:

// The path passed to these methods is relative to (Plugin Path)/assets
string text = assets.ReadText("myText.txt");
Vector3 position = assets.ReadJSON<Vector3>("position.json");
Texture2D art = assets.ReadImage("art.jpg");
byte[] rawData = assets.ReadBytes("data.bytes");

You can also load custom Unity assets through asset bundles:

// For the best compatibility, asset bundles should be built using Unity v2019.4.
AssetBundle bundle = assets.ReadBundle("bundle");
foreach(string name in bundle.GetAllAssetNames()) {
	Logger.LogMessage(name);
}

Custom Sandbox Tools

Creating a custom tool is easy:

CustomSandboxTools.AddNewTool(new SpawnableObject() { 
	identifier = "your.guid.here:box",
	objectName = "box",
	gameObject = myObj, // "myObj" should be an object created elsewhere, usually loaded from an asset bundle
	gridIcon = myIcon // "myIcon" should be a sprite, usually loaded from disk/an asset bundle
});