π οΈImplementing custom load screens
guide how to implement custom load screens

Let's implement custom load screen (It will be with fill image). You can dublicate one of LoadScreen implementations and change it (might be easy to make your new custom loadscreen).
Implementation from scratch:
1 - Create file class, inherit it from LoadScreen
You will get smth like this:
using UnityEngine;
// write it in Game.Core namespace
namespace Game.Core {
// inherit from LoadScreen
public class ImageFillOutIn : LoadScreen {
// LoadScreen implement (recomended to use auto-properties like this)
public override LoadScreenState CurrentState {
get;
protected set;
}
public override float TransitionInTime { get; set; }
public override float TransitionOutTime { get; set; }
public override void OnUpdateLoadProgress(float loadingProgressValue) { }
// LoadScreen implement
internal override void ShowScreen() { }
internal override void ShowScreenImmediately() { }
internal override void HideScreen() { }
internal override void HideScreenImmediately() { }
internal override void ForceStop() { }
}
}You should use Time.unscaledDeltaTime in load transitions to avoid load screen fade freeze if game logic needs to stop Time.
2 - Implement Show / Hide Screen methods
using System.Collections; // add to use IEnumerator for coroutines
using UnityEngine;
// write it in Game.Core namespace
namespace Game.Core {
public class ImageFillOutIn : LoadScreen {
public override LoadScreenState CurrentState { get; protected set; }
public override float TransitionInTime { get => _transitionInTime; set => _transitionInTime = value; }
public override float TransitionOutTime { get => _transitionOutTime; set => _transitionOutTime = value; }
// add reference to Image component that we will change to
// implement hide and show logic;
[SerializeField] private Image fillImage;
// image fill specifil origins to implement show/hide logic;
[SerializeField] private Image.OriginHorizontal fillInOrigin = Image.OriginHorizontal.Left;
[SerializeField] private Image.OriginHorizontal fillOutOrigin = Image.OriginHorizontal.Right;
// transition in/out time in seconds;
[SerializeField, Range(0.1f, 5f)] private float transitionInTime = 1.5f;
[SerializeField, Range(0.1f, 5f)] private float transitionOutTime = 1.5f;
// image fill change curve in/out;
[SerializeField] private AnimationCurve transitionIn;
[SerializeField] private AnimationCurve transitionOut;
private Coroutine _transition;
// this method to change runtime manualy
public void SetTransitionColor(Color color) {
_fillImage.color = color;
}
// this method to change runtime manualy
public void SetFillInOrigin(int fillOriginType) {
_fillInOrigin = fillOriginType;
if (CurrentState == LoadScreenState.Showing) _fillImage.fillOrigin = _fillInOrigin;
}
// this method to change runtime manualy
public void SetFillOutOrigin(int fillOriginType) {
_fillOutOrigin = fillOriginType;
if (CurrentState == LoadScreenState.Hiding) _fillImage.fillOrigin = _fillOutOrigin;
}
// this method to change runtime manualy
public void SetFillMethod(Image.FillMethod method) => _fillImage.fillMethod = method;
// I left body empty because I don't want to display or use loading progress
// by default SceneSystem will send it on scene change after load screen appeared
public override void OnUpdateLoadProgress(float loadingProgressValue) { }
internal override void ShowScreen(UnityAction callback = null) {
if (gameObject.activeSelf == false) gameObject.SetActive(true);
CurrentState = LoadScreenState.Showing;
OnLoaderShown.AddListener(() => {
CurrentState = LoadScreenState.Active;
});
if (callback != null) OnLoaderShown.AddListener(callback);
_transition = StartCoroutine(DoTransitionIn());
}
internal override void ShowScreenImmediately() {
CurrentState = LoadScreenState.Active;
_fillImage.fillAmount = 1f;
if (gameObject.activeSelf == false) gameObject.SetActive(true);
CoroutineHelper.Stop(this, ref _transition);
if (CurrentState == LoadScreenState.Hiding) {
OnLoaderHiden.RemoveAllListeners();
}
else if (CurrentState == LoadScreenState.Showing) {
OnLoaderShown?.Invoke();
OnLoaderShown.RemoveAllListeners();
}
}
internal override void HideScreen(UnityAction callback = null) {
CurrentState = LoadScreenState.Hiding;
OnLoaderHiden.AddListener(() => {
gameObject.SetActive(false);
CurrentState = LoadScreenState.Inactive;
});
if (callback != null) OnLoaderHiden.AddListener(callback);
_transition = StartCoroutine(DoTransitionOut());
}
internal override void HideScreenImmediately() {
CurrentState = LoadScreenState.Inactive;
_fillImage.fillAmount = 0f;
CoroutineHelper.Stop(this, ref _transition);
if (CurrentState == LoadScreenState.Hiding) {
OnLoaderHiden?.Invoke();
OnLoaderHiden.RemoveAllListeners();
}
else if (CurrentState == LoadScreenState.Showing) {
OnLoaderShown.RemoveAllListeners();
}
if (gameObject.activeSelf) gameObject.SetActive(false);
}
internal override void ForceStop() {
CoroutineHelper.Stop(this, ref _transition);
if (CurrentState == LoadScreenState.Hiding) {
OnLoaderHiden.RemoveAllListeners();
}
else if (CurrentState == LoadScreenState.Showing) {
OnLoaderShown.RemoveAllListeners();
}
}
private IEnumerator DoTransitionIn() {
_fillImage.fillOrigin = (int)_fillInOrigin;
float transitionTime = (1f - _fillImage.fillAmount) * _transitionInTime;
if (transitionTime > 0.001f) {
for (float i = _fillImage.fillAmount; i < 1f; i += Time.unscaledDeltaTime / transitionTime) {
_fillImage.fillAmount = _transitionIn.Evaluate(i);
yield return null;
}
}
_fillImage.fillAmount = 1f;
OnLoaderShown?.Invoke();
OnLoaderShown.RemoveAllListeners();
}
private IEnumerator DoTransitionOut() {
_fillImage.fillOrigin = (int)_fillOutOrigin;
float transitionTime = _fillImage.fillAmount * _transitionOutTime;
if (transitionTime > 0.001f) {
for (float i = _fillImage.fillAmount; i > 0f; i -= Time.unscaledDeltaTime / transitionTime) {
_fillImage.fillAmount = _transitionOut.Evaluate(i);
yield return null;
}
}
_fillImage.fillAmount = 0f;
OnLoaderHiden?.Invoke();
OnLoaderHiden.RemoveAllListeners();
}
}
}3 - Add new enum value in LoadScreenManager.cs
public enum LoadScreens {
None = -1,
BlackInOut,
// our new custom load screen type:
ImageFillInOut
}4 - Create load screen object in LoaderScreens prefab
Before:

After:

ImageFillInOut object will be with our new ImageFillOutIn.cs script and Image reference is FillImage. Setup ImageFillOutIn script in the inspector (set created Type the same as we added to enum 'ImageFillInOut', other fields):

Now save ImageFillInOut object as prefab and remove it from LoaderScreens prefab, Reference created prefab in LoaderScreenManager and you will end up with:

Last updated