-
-
Notifications
You must be signed in to change notification settings - Fork 8
Getting Started
This page will walk you through a common use of Zenject and Lapiz in Beat Saber mods: UI. Usually for using Zenject to handle UI, you will want a UIManager of some sort that gets installed to the menu location.
- Lapiz (obviously)
- Codegen
- Custom-Types
- BSML
Start off by creating a new custom type for our class:
// include/UI/UIManager.hpp
#pragma once
#include "custom-types/shared/macros.hpp"
#include "lapiz/shared/macros.hpp"
#include "Zenject/IInitializable.hpp"
#include "System/IDisposable.hpp"
#define INTERFACES { classof(::Zenject::IInitializable *), classof(::System::IDisposable *) }
__DECLARE_TYPE_WRAPPER_INHERITANCE(ExampleMod::Managers, UIManager,
Il2CppTypeEnum::IL2CPP_TYPE_CLASS, Il2CppObject, "ExampleMod::Managers", INTERFACES, 0, nullptr,
)
#undef INTERFACESNext, let's override the Initialize() method that we inherit from IInitializable. This is where we will run our code to handle the UI. We will also declare the fields that will store our dependencies. We will also override the Dispose method we inherit from IDisposable; it is very important to clean up our garbage once the manager is cleaned up.
// include/UI/UIManager.hpp
#include "custom-types/shared/macros.hpp"
#include "lapiz/shared/macros.hpp"
#include "Zenject/IInitializable.hpp"
#include "System/IDisposable.hpp"
#include "GlobalNamespace/MainFlowCoordinator.hpp"
#include "UI/UIFlowCoordinator.hpp"
#define INTERFACES { classof(::Zenject::IInitializable *), classof(::System::IDisposable *) }
#define GET_METHOD(method) \
il2cpp_utils::il2cpp_type_check::MetadataGetter<&method>::get()
__DECLARE_TYPE_WRAPPER_INHERITANCE(ExampleMod::Managers, UIManager,
Il2CppTypeEnum::IL2CPP_TYPE_CLASS, Il2CppObject, "ExampleMod::Managers", INTERFACES, 0, nullptr,
DECLARE_PRIVATE_FIELD(::GlobalNamespace::MainFlowCoordinator *, _mainFlowCoordinator);
DECLARE_PRIVATE_FIELD(::ExampleMod::UI::UIFlowCoordinator *, _uiFlowCoordinator);
DECLARE_OVERRIDE_METHOD(void, Initialize, GET_METHOD(::Zenject::Initializable::Initialize));
DECLARE_OVERRIDE_METHOD(void, Dispose, GET_METHOD(::Zenject::IDisposable::Dispose));
)
#undef INTERFACESNow, we need to create a way for Zenject to inject our dependencies into our UIManager. We have two (2) options to do this: Inject Constructor or Inject Method. In this example, we will use an Inject Constructor. To do this, we just define a constructor, with our dependencies as parameters:
// include/UI/UIManager.hpp
#include "custom-types/shared/macros.hpp"
#include "lapiz/shared/macros.hpp"
#include "Zenject/IInitializable.hpp"
#include "System/IDisposable.hpp"
#include "GlobalNamespace/MainFlowCoordinator.hpp"
#include "UI/UIFlowCoordinator.hpp"
#define INTERFACES { classof(::Zenject::IInitializable *), classof(::System::IDisposable *) }
#define GET_METHOD(method) \
il2cpp_utils::il2cpp_type_check::MetadataGetter<&method>::get()
__DECLARE_TYPE_WRAPPER_INHERITANCE(ExampleMod::Managers, UIManager,
Il2CppTypeEnum::IL2CPP_TYPE_CLASS, Il2CppObject, "ExampleMod::Managers", INTERFACES, 0, nullptr,
DECLARE_PRIVATE_FIELD(::GlobalNamespace::MainFlowCoordinator *, _mainFlowCoordinator);
DECLARE_PRIVATE_FIELD(::ExampleMod::UI::UIFlowCoordinator *, _uiFlowCoordinator);
DECLARE_CTOR(ctor, ::GlobalNamespace::MainFlowCoordinator *mainFlowCoordinator,
::ExampleMod::UI::UIFlowCoordinator *uiFlowCoordinator);
DECLARE_OVERRIDE_METHOD(void, Initialize, GET_METHOD(::Zenject::Initializable::Initialize));
DECLARE_OVERRIDE_METHOD(void, Dispose, GET_METHOD(::Zenject::IDisposable::Dispose));
)
#undef INTERFACESAnd finally we will add a field for our MenuButton that we will add and a method to show our FlowCoordinator called ShowFlow():
// include/UI/UIManager.hpp
#include "custom-types/shared/macros.hpp"
#include "lapiz/shared/macros.hpp"
#include "Zenject/IInitializable.hpp"
#include "System/IDisposable.hpp"
#include "GlobalNamespace/MainFlowCoordinator.hpp"
#include "UI/UIFlowCoordinator.hpp"
#include "bsml/shared/BSML/MenuButtons/MenuButton.hpp"
#define INTERFACES { classof(::Zenject::IInitializable *), classof(::System::IDisposable *) }
#define GET_METHOD(method) \
il2cpp_utils::il2cpp_type_check::MetadataGetter<&method>::get()
__DECLARE_TYPE_WRAPPER_INHERITANCE(ExampleMod::Managers, UIManager,
Il2CppTypeEnum::IL2CPP_TYPE_CLASS, Il2CppObject, "ExampleMod::Managers", INTERFACES, 0, nullptr,
DECLARE_PRIVATE_FIELD(::GlobalNamespace::MainFlowCoordinator *, _mainFlowCoordinator);
DECLARE_PRIVATE_FIELD(::ExampleMod::UI::UIFlowCoordinator *, _uiFlowCoordinator);
DECLARE_PRIVATE_FIELD(::BSML::MenuButton *, _menuButton);
DECLARE_CTOR(ctor, ::GlobalNamespace::MainFlowCoordinator *mainFlowCoordinator,
::ExampleMod::UI::UIFlowCoordinator *uiFlowCoordinator);
DECLARE_OVERRIDE_METHOD(void, Initialize, GET_METHOD(::Zenject::Initializable::Initialize));
DECLARE_OVERRIDE_METHOD(void, Dispose, GET_METHOD(::Zenject::IDisposable::Dispose));
DECLARE_INSTANCE_METHOD(void, ShowFlow);
)
#undef INTERFACESThat's all the declaration work done! Next we need to define our class:
// src/UI/UIManager.cpp
#include "UI/SettingsUI.hpp"
#include "HMUI/ViewController_AnimationDirection.hpp"
#include "bsml/shared/BSML.hpp"
DEFINE_TYPE(ExampleMod::UI, UIManager);
namespace ExampleMod::UI {
void UIManager::ctor(::GlobalNamespace::MainFlowCoordinator *mainFlowCoordinator,
::ExampleMod::UI::UIFlowCoordinator *uiFlowCoordinator) {
_mainFlowCoordinator = mainFlowCoordinator;
_uiFlowCoordinator = uiFlowCoordinator;
_menuButton = BSML::MenuButton::Make_new("ExampleMod", "Example", std::bind(&UIManager::ShowFlow, this));
}
void UIManager::ShowFlow() {
_mainFlowCoordinator->YoungestChildFlowCoordinatorOrSelf()
->PresentFlowCoordinator(_uiFlowCoordinator, nullptr,
::HMUI::ViewController::AnimationDirection::Horizontal,
false, false);
}
void ExampleMod::UI::Initialize {
::BSML::Register::RegisterMenuButton(_menuButton);
}
void ExampleMod::UI::Dispose {
::BSML::Register::UnRegisterMenuButton(_menuButton);
}
}This will assign our injected dependencies to our declared fields and create a menu button that will show our FlowCoordinator when clicked. But, in order for it do actually do anything, we need to install it. In order to install it, we need to create an installer.
For this UI, we will make an installer that will be installed in the Menu location. We will appropriately name it MenuInstaller:
// include/Installers/MenuInstaller.hpp
#pragma once
#include "custom-types/shared/macros.hpp"
#include "Zenject/Installer.hpp"
DECLARE_CLASS_CODEGEN(ExampleMod::Installers, MenuInstaller, ::Zenject::Installer,
)Next we need to override the InstallBindings method that we inherit from Zenject.Installer as well as defining a default constructor for Zenject to invoke when we install it into the menu location.
// include/Installers/MenuInstaller.cpp
#pragma once
#include "custom-types/shared/macros.hpp"
#include "Zenject/Installer.hpp"
#define GET_METHOD(method) \
il2cpp_utils::il2cpp_type_check::MetadataGetter<&method>::get()
DECLARE_CLASS_CODEGEN(ExampleMod::Installers, MenuInstaller, ::Zenject::Installer,
DECLARE_OVERRIDE_METHOD(void, InstallBindings, GET_METHOD(::Zenject::Installer::InstallBindings);
DECLARE_DEFAULT_CTOR();
)Now let's define our InstallBindings method in a source file.
// src/Installers/MenuInstaller.cpp
#include "Installers/MenuInstaller.hpp"
DEFINE_TYPE(ExampleMod::Installers, MenuInstaller)
namespace ExampleMod::Installers {
void MenuInstaller::InstallBindings() {
}
}In order to bind our UI classes to our container, we need to access the installer's DiContainer. We can use the Container property to do this:
// src/Installers/MenuInstaller.cpp
#include "Installers/MenuInstaller.hpp"
#include "Zenject/DiContainer.hpp"
DEFINE_TYPE(ExampleMod::Installers, MenuInstaller)
namespace ExampleMod::Installers {
void MenuInstaller::InstallBindings() {
auto *container = get_Container();
}
}Next we need to bind our UIFlowCoordinator and our UIManager to the container. For the UIManager we will use the BindInterfacesAndSelfTo<T>() method. We will also use the same method for the FlowCoordinator, however we will need to use the extension method FromNewComponentOnNewGameObject() with it. It can be found in lapiz/shared/utilities/ZenjectExtensions.hpp
// src/Installers/MenuInstaller.cpp
#include "Installers/MenuInstaller.hpp"
#include "lapiz/shared/utilities/ZenjectExtensions.hpp"
#include "Zenject/DiContainer.hpp"
#include "Zenject/ConcreteIdBinderGeneric_1.hpp"
#include "Zenject/FromBinderNonGeneric.hpp"
#include "UI/UIManager.hpp"
#include "UI/UIFlowCoordinator.hpp"
DEFINE_TYPE(ExampleMod::Installers, MenuInstaller)
using namespace Lapiz::Zenject::ZenjectExtensions;
namespace ExampleMod::Installers {
void MenuInstaller::InstallBindings() {
auto *container = get_Container();
FromNewComponentOnNewGameObject(
container->BindInterfacesAndSelfTo(::ExampleMod::UI::UIManager *>()
)->AsSingle();
container->BindInterfacesAndSelfTo<::ExampleMod::UI::UIFlowCoordinator *>()->AsSingle();
}
}You may also want to bind your ViewControllers to the container, to be injected into the FlowCoordinator. You can do this using the FromNewComponentAsViewController() extension method.
// src/Installers/MenuInstaller.cpp
#include "Installers/MenuInstaller.hpp"
#include "lapiz/shared/utilities/ZenjectExtensions.hpp"
#include "Zenject/DiContainer.hpp"
#include "Zenject/ConcreteIdBinderGeneric_1.hpp"
#include "Zenject/FromBinderNonGeneric.hpp"
#include "UI/UIManager.hpp"
#include "UI/UIFlowCoordinator.hpp"
#include "UI/UIViewController.hpp"
DEFINE_TYPE(ExampleMod::Installers, MenuInstaller)
using namespace Lapiz::Zenject::ZenjectExtensions;
namespace ExampleMod::Installers {
void MenuInstaller::InstallBindings() {
auto *container = get_Container();
FromNewComponentAsViewController(
container->Bind<::ExampleMod::UI::UIViewController *>()
)->AsSingle();
FromNewComponentOnNewGameObject(
container->BindInterfacesAndSelfTo(::ExampleMod::UI::UIManager *>()
)->AsSingle();
container->BindInterfacesAndSelfTo<::ExampleMod::UI::UIFlowCoordinator *>()->AsSingle();
}
}Once you are happy with your installer, we just need to install it.
In most cases, you will want to install your installer in the load() function in your main.cpp file. This can be done as shown:
#include "custom-types/shared/register.hpp"
#include "lapiz/shared/zenject/Zenjector.hpp"
#include "lapiz/shared/AttributeRegistration.hpp"
#include "bsml/shared/BSML.hpp"
#include "Installers/MenuInstaller.hpp"
...
extern "C" void load() {
...
::BSML::Init();
::custom_types::Register::AutoRegister();
::Lapiz::Attributes::AutoRegister();
auto *zenjector = ::Lapiz::Zenject::Zenjector::Get();
zenjector->Install<::ExampleMod::Installers::MenuInstaller *>(::Lapiz::Zenject::Location::Menu);
....
}And that's, more or less, all there is to it. You can find more in-depth documentation elsewhere on this wiki.