Understanding Entities
Overview
An IdleKit game is made up of many different Entities
of different concepts that make up the core foundation of the game. Examples of IdleKit Entities include Stages
, Generators
, Modifiers
, and Currencies
.
Note
For more on extending or overriding entities, see Customizing Entities.
Components
Entities
within IdleKit follow the MVC pattern. In practice, this means that each Entity has a Controller (within the IEntity
class), Static Data
, Data Asset
, and optional Saved Data
. A full IEntity
will implement the following classes:
- [EntityName].cs — I[EntityName], IEntity
- The controller containing the logic performed by the Entity
- [EntityName]Data.cs — I[EntityName]Data, IEntityData
- The static data for the
IEntity
which is loaded at runtime. - Responsible for the creation of the
IEntity Controller
andSavedData
via theCreateEntity
method.
- The static data for the
- [EntityName]DataAsset.cs — I[EntityName]DataAsset, ScriptableObject
- The ScriptableObject that contains the Data. This is required for implementation in Unity. It also works with
IGuidReferenceableAsset
to connect Entities by id.
- The ScriptableObject that contains the Data. This is required for implementation in Unity. It also works with
- [EntityName]SavedData.cs — ISavedData
- The serialized user data for the Entity. This is the dynamic data used by the
IEntity
controller which changes during gameplay.
- The serialized user data for the Entity. This is the dynamic data used by the
Note
The View component of an IdleKit Entity would be implemented by the UI layer of a game. In order to be as flexible as possible, IdleKit does not provide any UI components. However, sample implementations can be found within the idlekit-tools
section of IdleKit, and samples folder in the idlekit-showcase
repository, or within the idlekit-layoutui-showcase
repository.
Entity Life Cycle
IdleKit uses the Dependency Container
to manage the way Entities
are loaded and dependencies are handled. The IEntity
life cycle contains multiple phases:
Creation
An IEntity
is created by its owner object using the IEntityLoaderService
. The constructor of the IEntity
(string id) will be invoked, creating the object and caching the id
. The id
is then used in the Injection phase.
e.g: Content.cs
-> ctor(string,string)
public Content(string id)
{
_id = id;
}
Injection
After the IEntity
is created, it is injected into the Dependency Container
. In this phase, all of the Entity
dependencies, such as required Services
or the dependent Entities
, are loaded. In turn, each of these Entities
goes through the creation and injection process, making the loading process recursive. Before exiting this phase, the first IEntity
created has been resolved, as well as all of its dependencies.
Note
For more information on how the injection works, refer to the Dependency Container.
e.g: Content.cs
-> void Inject(IResolver)
public virtual void Inject(IResolver resolver)
{
_entityLoaderService = resolver.Resolve<IEntityLoaderService>();
_actionService = resolver.Resolve<IActionService>();
_dataService = resolver.Resolve<IDataService>();
_contentData = _dataService.GetData<IContentData>(Id);
_contentSavedData = resolver.Resolve<IContentSavedData>(Id);
}
Initialization
Initialize
is immediately called after the injection phase. In this phase, setup logic would be performed on the IEntity
, which includes logic such as registration with the ITimerService
, error validation, or subscribing to Actions
. After this phase, the IEntity
has been fully loaded and will persist until the Cleanup
method within an IEntity
is invoked.
e.g: Content.cs
-> void Initialize()
public virtual void Initialize()
{
BeginStage();
if (!ContentSavedData.HasStarted)
{
NewContentStateAction newContentStateAction = _actionService.Get<NewContentStateAction>();
newContentStateAction.Initialize(this);
_actionService.Dispatch(newContentStateAction, this);
}
ContentInitializedAction contentInitializedAction = _actionService.Get<ContentInitializedAction>();
contentInitializedAction.Content = this;
_actionService.Dispatch(contentInitializedAction);
}
protected virtual void BeginStage()
{
_currentStage = _entityLoaderService.LoadEntity<IStage, IStageData>(CurrentStageId);
SetAvailableCurrencies();
}
Cleanup
Cleanup is called on the IEntity
by the owner when it needs to be unloaded. This step works similarly to the Injection phase, but is unloading dependencies instead of loading. It is important to call Cleanup when references are no longer used, for example when switching Contents
.
e.g: Content.cs
-> void Cleanup(IEntityLoaderService)
public virtual void Cleanup(IEntityLoaderService entityLoaderService)
{
EndStage();
_contentData = null;
_contentSavedData = null;
_entityLoaderService = null;
_actionService = null;
_dataService = null;
}
protected virtual void EndStage()
{
_entityLoaderService.UnloadEntity(_currentStage);
_currentStage = null;
}
Entity Initialization Flow
The different phases of an IEntity
initialization is triggered by the IEntityLoaderService
. It encompasses the Entity Life Cycle and uses various components in IdleKit to create a new IEntity
.
-
The
IEntity
is initialized when it is required by anotherIEntity
or when the owningIEntity
is initialized (e.g.:IStage
is initialized when the ownerIContent
is initialized). -
Initialization starts with a check to see if an
IEntity
with the givenId
already exists in theIContainer
. If theIEntity
already exists, it has previously been initialized by another owner. Instead of being reloaded, theIEntity
is retrieved from theContainer
and the Entity's initialization process is complete. -
If the
IEntity
does not exist in theIContainer
, theIEntityLoaderService
determines how to create theIEntity
by getting its associatedIEntityData
. -
If the
IEntityData
is not found in theIContainer
, aBindingException
is thrown. Only theIEntityData
contains the information (Type, method, ids) necessary to create theIEntity
, so it is important to make sure theIEntityData
is registered in theIContainer
via theIDataService
. -
If the
IEntityData
is found, theIEntityLoaderService
invokes the constructor through theCreateEntity
method. TheCreation
phase of theEntity
life cycle is then invoked. -
To prevent duplication of
IEntities
with the sameid
and to enable dependency injection, as soon as theIEntity
is created, it is bound in theIContainer
based on itsid
. Then theInjection
phase ofIEntity
is invoked, where each dependency is created recursively. All childEntities
go through the same Entity Initialization Flow until all dependencies are fulfilled. -
After the
Injection
phase is completed and all dependencies have been resolved,Initialize
is called to perform setup logic. -
Since this is the first instance of this
IEntity
, the reference of theIEntity
loaded is tracked and anEntityAddedAction
is dispatched to notify any listeners that a newIEntity
has been initialized.
Entity Cleanup Flow
Since an IEntity
is created and initialized by an owner, it also has to be cleaned up by its owner. Below shows the cleanup flow of an IEntity
.
-
The cleanup process begins when the
IEntity
is no longer required (e.g. when the previousIStage
is not required after ascending to a newIStage
within anIContent
) or when the ownerIEntity
is cleaned up (eg. theIContent
that ownsIStage
is cleaned up when switching to a differentIContent
). -
Once the process begins, the
IEntity
is passed into theIEntityLoaderService
to be unloaded. -
If the current
IEntity
is not referenced by another owner, the associatedIEntityData
andISavedData
is unbound from theIContainer
. AnEntityRemovedAction
is then dispatched on theIEntity
. If theIEntity
is referenced by another Entity that is not cleaned up, the cleanup process is aborted. -
The
Cleanup
method on theIEntity
is invoked, where all of theEntity
dependencies are unloaded and cleaned up by setting them to null and initiating the Cleanup Flow on any childEntities
. This starts the same process recursively on the child Entities until all Entities are cleaned up.