Create a Game Service
This guide provides an introduction on creating game service components, which are manager components that must be placed on a child object of the object containing the Game Manager component. A game service is initialized and registered on the game manager and can be fetched by other services or entity components.
In this guide, we will go through the creation of a very simple Unit Wave Spawner component that allows to create unit instances every period of time and move the created unit instances towards a position after they spawn.
1. Define the Game Service
In the project tab, create a new C# script under the name of UnitWaveSpawner and open the new script in your editor.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UnitWaveSpawner : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
To define this component as a service component then we need to implement either the IPreRunGameService interface or the IPostRunGameService interface. We will opt for implementing the latter interface since we want this game service to be initialized after the game in the map scene is completely built.
Now we can either implement the interface in our new EntitySeller component or we can simply make the EntitySeller derive from EntityComponentBase, an abstract class that implements all the properties and methods of the IEntityComponent interface and exposes overridable properties and methods for the class it extending.
Go ahead and implement the IPostRunGameService interface's only method OnInit() which provides a reference to the IGameManager which we can use to fetch other game services.
using UnityEngine;
using RTSEngine.Game;
public class UnitWaveSpawner : MonoBehaviour, IPostRunGameService
{
public void Init(IGameManager gameMgr)
{
// Initialize here
}
}
At this stage, you can add the UnitWaveSpawner component to a child object of the GameManager object in the scene so that it properly registered as a game service when the game starts.
2. Spawn Timer Handling
We will be spawning a unit entity every period of time that we can choose from the inspector. In the RTS Engine, the simulation can be sped up or slowed down. Therefore, when considering time, there are fields that you can use that guarantee a sync to the game's simulation time modifier.
Go ahead and start by adding the following two fields:
[SerializeField]
private float spawnPeriod = 5.0f;
private TimeModifiedTimer timer;
- The spawnPeriod is a simple float that represents the amount of time between each consecutive spawn time.
- The timer field of type TimeModifiedTimer is the timer that will take the spawnReload as input and run a timer that is synchronous with the simulation's modifiable time scale.
In the Init() method, we need to initialize the timer field with the spawnPeriod value as follows:
public void Init(IGameManager gameMgr)
{
timer = new TimeModifiedTimer(spawnPeriod);
}
Then add Unity's Update() method with the following logic that handles running the timer and reloading it. The ModifiedDecrease() method is what allows the timer to run and it only starts returning true as soon as the timer's value hits 0. That is when we call the Reload() and where we will add the unit spawning logic later.
private void Update()
{
if(timer.ModifiedDecrease())
{
timer.Reload();
// Spawn Unit Here
}
}
With the latest additions, the UnitWaveSpawner class looks like this:
using UnityEngine;
using RTSEngine.Game;
using RTSEngine.Determinism;
public class UnitWaveSpawner : MonoBehaviour, IPostRunGameService
{
[SerializeField]
private float spawnPeriod = 5.0f;
private TimeModifiedTimer timer;
public void Init(IGameManager gameMgr)
{
timer = new TimeModifiedTimer(spawnPeriod);
}
private void Update()
{
if(timer.ModifiedDecrease())
{
timer.Reload();
// Spawn Unit Here
}
}
}
3. Unit Spawn Handling
Moving on to the logic that actually handles creating the unit instances each spawn period. First start by adding the following fields:
[SerializeField, EnforceType(typeof(IUnit), prefabOnly: true)]
private GameObject unitPrefabObj = null;
private IUnit unitPrefab;
[SerializeField]
private int spawnAmount = 3;
[SerializeField]
private Transform spawnTransform = null;
[SerializeField]
private Transform gotoTransform = null;
protected IUnitManager unitMgr { private set; get; }
- unitPrefabObj field is of type GameObject but has the EnforceType(typeof(IUnit), prefabOnly: true) allows this field to only accept game objects that are prefabs and that have the IUnit component attached to them. The reason behind this is that interface types are not serializable by the Unity editor. You can use Unit as the type of this field instead but it is always recommended to use interface types than actual implementations.
- The next unitPrefab field has type IUnit and will be used to store the IUnit component attached to the unitPrefabObj game object in the Init() method.
- spawnAmount is an integer that defines the amount of instances to spawn each period.
- spawnTransform and gotoTransform are two fields of type Transform which allow to define the position the created units will spawn at and then the position they would move towards.
- unitMgr of type IUnitManager, the interface of the Unit Manager service that handles spawning units. It includes a method that we will use to create new instances of the above unit prefab.
In the Init() method, add the following logic:
public void Init(IGameManager gameMgr)
{
timer = new TimeModifiedTimer(spawnPeriod);
if(!spawnTransform.IsValid() || !gotoTransform.IsValid() || !unitPrefabObj.IsValid())
{
Debug.LogError("[UnitWaveSpawner] All inspector fields must be assigned!");
return;
}
unitPrefab = unitPrefabObj.GetComponent<IUnit>();
unitMgr = gameMgr.GetService<IUnitManager>();
}
- We first want to make sure that the inspector fields spawnTransform, gotoTransform and unitPrefabObj are all assigned by calling the IsValid() helper method on them.
- We then assign the value of the unitPrefab value by fetching the IUnit component off the the assigned unitPrefabObj field.
- And finally, we grab the unitMgr service using the GetService() method of the gameMgr parameter provided by the Init() method.
In the Update() method, add the following unit creation logic:
private void Update()
{
if(timer.ModifiedDecrease())
{
timer.Reload();
for (int i = 0; i < spawnAmount; i++)
{
unitMgr.CreateUnit(unitPrefab, spawnTransform.position, Quaternion.identity, new InitUnitParameters
{
free = true,
useGotoPosition = true,
gotoPosition = gotoTransform.position,
});
}
}
}
- We have a simple for-loop to make sure we are spawning the specified amount as assigned in the spawnAmount inspector field.
- We call the CreateUnit() method and provide it with the following parameters:
- unitPrefab: Prefab of the unit to create that we assigned in the Init() method from the inspector field.
- spawnTransform.position: Vector3 spawn position of the unit.
- Quaternion.identity: As a default initial rotation for the spawned unit.
- Unit Initialization Parameters: a new instance of InitEntityParameters where we mark the unit as free (it does not belong to any faction slot) and where we enable useGotoPosition and assign the gotoPosition to the gotoTransform.position field assigned in the inspector so that as soon as the unit is created, it would move towards that goto position.
The final code in the UnitWaveSpawner should look like this with the latest addition of the unit spawning logic:
using RTSEngine;
using RTSEngine.Determinism;
using RTSEngine.Entities;
using RTSEngine.Game;
using RTSEngine.UnitExtension;
using UnityEngine;
public class UnitWaveSpawner : MonoBehaviour, IPostRunGameService
{
[SerializeField]
private float spawnPeriod = 5.0f;
private TimeModifiedTimer timer;
[SerializeField, EnforceType(typeof(IUnit), prefabOnly: true)]
private GameObject unitPrefabObj = null;
private IUnit unitPrefab;
[SerializeField]
private int spawnAmount = 3;
[SerializeField]
private Transform spawnTransform = null;
[SerializeField]
private Transform gotoTransform = null;
protected IUnitManager unitMgr { private set; get; }
public void Init(IGameManager gameMgr)
{
timer = new TimeModifiedTimer(spawnPeriod);
if(!spawnTransform.IsValid() || !gotoTransform.IsValid() || !unitPrefabObj.IsValid())
{
Debug.LogError("[UnitWaveSpawner] All inspector fields must be assigned!");
return;
}
unitPrefab = unitPrefabObj.GetComponent<IUnit>();
unitMgr = gameMgr.GetService<IUnitManager>();
}
private void Update()
{
if(timer.ModifiedDecrease())
{
timer.Reload();
for (int i = 0; i < spawnAmount; i++)
{
unitMgr.CreateUnit(unitPrefab, spawnTransform.position, Quaternion.identity, new InitUnitParameters
{
free = true,
useGotoPosition = true,
gotoPosition = gotoTransform.position,
});
}
}
}
}
4. Testing
Back in the Unity editor and in the inspector of the UnitWaveSpawner, assign the following fields:
Run the game and notice that for every Spawn Period and free units will spawn from the assigned Spawn Transform and then move towards Goto Transform.
Download Files
You can download the results of this tutorial here.