Creating custom Scenes with Unity¶
Introduction¶
The process behind creating levels is equally diverse with the simplest methodology requiring you to create and position everything manually with code. This approach will have you create your levels in the game engine Unity, and import your new level back into Hollow Knight. By the end of this tutorial, you should be able to accomplish all of these tasks with limited extra help needed. With all of that said, this tutorial will be geared towards those who have previously modded Hollow Knight and furthermore, it will assume you know how to assetbundle new scenes into Hollow Knight.
Background¶
You cannot create scenes without having a good understanding of the various aspects that run Hollow Knight and its mods. Therefore, this guide recommends and expects that you have already worked with the following topics:
- Understanding the structure of a basic mod.
- Having basic Unity knowledge.
Note
You will also need the ability to choose between reimplementing certain behaviors/object yourself or copying them using preloading.
Note
With the exception of basic Unity knowledge, all other topics listed are explained, either through video or text, on this site. If you still need assistance, don’t be afraid to ask on Discord.
What you will need¶
- Unity version 2017.4.10f
- Modding API
- The latest release of SFCoreUnity.dll
- C# IDE (Visual Studio or JetBrain Rider, this guide is written with Visual Studio as an IDE)
And now, here are the steps to create custom Hollow Knight scenes.
Mod project setup¶
- Create a new C# class library project. Use the one labelled “Class library (.NET Framework)”.
- Give it a name and select .NET Framework 3.5
- Add the modding api and the SFCore library mod as a reference.
Note
SFCore is needed because of the MonoBehaviours we will use in the custom scene.
- Add
GetPlayerBoolHook
,LanguageGetHook
andUnityEngine.SceneManagement.SceneManager.activeSceneChanged
. - The
GetPlayerBoolHook
will only listen to a bool that we will callCustomSceneTutorial_VisitedArea
and will always return false.
private bool OnGetPlayerBoolHook(string target)
{
if (target == "CustomSceneTutorial_VisitedArea") return false;
return PlayerData.instance.GetBoolInternal(target);
}
- The
LanguageGetHook
will only listen to 3 strings that start with"CustomSceneTutorial_AreaTitle_"
.
private string OnLanguageGetHook(string key, string sheet)
{
if (sheet == "Titles" && key == "CustomSceneTutorial_AreaTitle_SUPER") return "Testing";
else if (sheet == "Titles" && key == "CustomSceneTutorial_AreaTitle_MAIN") return "area";
else if (sheet == "Titles" && key == "CustomSceneTutorial_AreaTitle_SUB") return "of doom";
return Language.Language.GetInternal(key, sheet);
}
- The
UnityEngine.SceneManagement.SceneManager.activeSceneChanged
will do nothing for now.
private void OnSceneChanged(Scene from, Scene to)
{
}
- For the sake of simplicity add the preloads “Area Title Controller” and “_SceneManager” from “White_Palace_18”.
public override List<ValueTuple<string, string>> GetPreloadNames()
{
return new List<ValueTuple<string, string>>
{
new ValueTuple<string, string>("White_Palace_18", "Area Title Controller"),
new ValueTuple<string, string>("White_Palace_18", "_SceneManager"),
new ValueTuple<string, string>("White_Palace_18", "_Managers/PlayMaker Unity 2D")
};
}
- This also means storing these preloaded GOs in your mod class.
10) For later use in Unity, also add a script for inserting an AreaTitleController, a script to insert a SceneManager, and a script to insert a PlayMaker Manager.
- Also for later, add a script for setting the correct width and height of the scene.
class PatchTilemapSize : MonoBehaviour
{
public int width = 30;
public int height = 17;
public void Awake()
{
On.GameManager.RefreshTilemapInfo += OnGameManagerRefreshTilemapInfo;
}
public void OnDestroy()
{
On.GameManager.RefreshTilemapInfo -= OnGameManagerRefreshTilemapInfo;
}
private void OnGameManagerRefreshTilemapInfo(On.GameManager.orig_RefreshTilemapInfo orig, GameManager self, string targetScene)
{
orig(self, targetScene);
if (targetScene == gameObject.scene.name)
{
self.tilemap.width = width;
self.tilemap.height = height;
self.sceneWidth = width;
self.sceneHeight = height;
FindObjectOfType<GameMap>().SetManualTilemap(0, 0, width, height);
}
}
}
Preparation for Unity¶
- Create a new C# class library project using Unity. Use the one labelled “Class library (.NET Framework)”.
- Give it a name (I suggest the same from before, but with
"Scripts"
behind it) and select .NET Framework 3.5 - Add ONLY the required Unity assemblies as references.
- Copy only the MonoBehaviour classes from before into this new project.
- You can remove all functions from these classes, only the member variables are important.
Note
For member variables that are of enum types, you can use other enums that have the same ranges covered as seen in the PatchSceneManager class.
- Build this MonoBehaviour (only project) and copy the DLL.
Unity project setup¶
- Create a new project using Unity. As a template choose the 3D template. The name is irrelevant.
- Make a few folders for organizing assets.
- Paste the copied DLL from before into the
Assemblies
folder, also add theSFCoreUnity.dll
assembly.
Note
Don’t forget to rename SFCoreUnity.dll
to SFCore.dll
- Create a scene in Unity.
- Change the lighting of the scene.
- Add your terrain meshes, put colliders on them (either
EdgeCollider2D
orPolygonCollider2D
) and put them on layer 8 (aka the terrain layer).
Note
This can utilize custom made meshes in programs like Blender.
- On these mesh GameObjects, add the
SceneMapPatcher
component from SFCore and give it a black texture to use. - Behind everything (with a global Z position of around 7), it is good to add a BlurPlane with a
MeshFilter
,MeshRenderer
, andBluePlanePatcher
component. - Add decorations, sprites should have the
SpritePatcher
component on them or above them in the hierarchy. - Add a GameObject called
__Initializer
with aPatchAreaTitleController
,PatchSceneManager
,PatchPlayMakerManager
, andPatchTilemapSize
component. - The
PatchAreaTitleController
will be set as a sub area with the area eventCustomSceneTutorial_AreaTitle
and the visited boolCustomSceneTutorial_VisitedArea
. - The
PatchSceneManager
can be adjusted to one’s liking. - The
PatchPlayMakerManager
should be given a transform of a GameObject called_Managers
. - The
PatchTilemapSize
should be given the width & height of the custom scene. - Add an entry & exit by adding a GameObject with a Collder set as a trigger and a
TransitionPoint
component, which can be added as a simple.cs
file in the_MonoBehaviours
folder in the assets. - Build the assetbundles and include them in the first C# project as embedded resources.
- In the mod, load the assetbundles in either the constructor or the
Initialize
function.
Accessing the custom scene¶
- In
UnityEngine.SceneManagement.SceneManager.activeSceneChanged
that did nothing until now, add code to create a GameObject with aTransitionPoint
wherever you want to access your scene from. - Build the mod.
- Enjoy your first empty custom scene!