# Implementing custom load screens

<figure><img src="https://1613353771-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fygjewtla1W7kzTTIpGWq%2Fuploads%2F7t6i6DAJbCnKAS3htk39%2Fimage.png?alt=media&#x26;token=d37a1791-6ba4-4aee-9f0d-74e0bef5011d" alt=""><figcaption><p>Path where base abstract LoadScreen class located</p></figcaption></figure>

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:**

<details>

<summary>1 - Create file class, inherit it from LoadScreen</summary>

You will get smth like this:

{% code lineNumbers="true" %}

```csharp
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() { }
    }
}
```

{% endcode %}

</details>

{% hint style="warning" %}
*You should use* <mark style="color:green;">**Time.unscaledDeltaTime**</mark> in load transitions to avoid load screen fade freeze if game logic needs to stop Time.
{% endhint %}

<details>

<summary>2 - Implement Show / Hide Screen methods</summary>

<pre class="language-csharp" data-line-numbers><code class="lang-csharp">using System.Collections; // add to use IEnumerator for coroutines
<strong>using UnityEngine;
</strong>
// 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;
        }
        
<strong>        // this method to change runtime manualy
</strong>        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 &#x3C; 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();
        }
    }
}
</code></pre>

</details>

<details>

<summary>3 - Add new enum value in LoadScreenManager.cs</summary>

```csharp
public enum LoadScreens {
    None = -1,
    BlackInOut,
    
    // our new custom load screen type:
    ImageFillInOut
}
```

</details>

<details>

<summary>4 - Create load screen object in LoaderScreens prefab</summary>

Before:

![](https://1613353771-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fygjewtla1W7kzTTIpGWq%2Fuploads%2F5hcRQOv9U4ivhNtZCVNU%2Fimage.png?alt=media\&token=f6d30178-d1a5-42a5-8a14-c46664e131be)

After:

![](https://1613353771-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fygjewtla1W7kzTTIpGWq%2Fuploads%2FFaWnuutdDETSeAy4O4UW%2Fimage.png?alt=media\&token=0fa9908c-bf72-48cd-920f-776c70e4dabf)

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):

![](https://1613353771-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fygjewtla1W7kzTTIpGWq%2Fuploads%2F5uce59E73rhASteIbWV9%2Fimage.png?alt=media\&token=002fa0ff-93d7-4275-a55e-e635e78518cb)

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

<img src="https://1613353771-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fygjewtla1W7kzTTIpGWq%2Fuploads%2Facj9qH6D5V1feQeC8Bq2%2Fimage.png?alt=media&#x26;token=852ab737-c115-4d18-9778-6a2c6f708e3c" alt="" data-size="original">

</details>

<details>

<summary>5 - Call your new loadscreen</summary>

```csharp
// example how to test your screen
// call it in any test script that should trigger your custom loadscreen type
LoadScreenManager.ShowLoadScreen(LoadScreens.ImageFillInOut, 
    callback: () => {
        LoadScreenManager.HideLoadScreen(callback: null);
});
```

</details>
