FNAF 10 - Bugs & The End

In this final installment, we bring the FNAF 2D series to its conclusion by adding the essential "boring" work that makes a game actually feel complete. We tackle a massive spreadsheet of bug fixes, from managing audio playheads so background music stays seamless between rooms, to fixing the power-down logic to ensure you can't sneak out a win during a blackout. We also dive into the technical overhaul of the Night Manager using Scriptable Objects—creating a modular system that allows for unique animatronic behavior and custom configurations for every single night.

To wrap everything up, we walk through the creation of the end-game win screen in Photoshop, including a hidden secret message for the eagle-eyed viewers. Whether you’re here for the technical Unity breakdown or to see if I can actually survive the "impossible" Night 5 playtest, this finale has it all.

You can copy and paste the complete code directly from the tutorial below, or…

Just want the full Unity package with everything pre-built? The entire Unity project package, including all scripts, modular systems, and final assets seen in this video, is available for a one-time purchase of $20 on my Patreon account: https://www.patreon.com/glecakes/shop

More From this Series

EndScreenManager.cs


using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class EndScreenManager : MonoBehaviour
{
    [SerializeField]
    UnityEngine.UI.Image blackFade;
    [SerializeField]
    CanvasGroup winGraphics;

    [SerializeField]
    float fadeInTime = 1.0f;
    [SerializeField]
    float holdOnEndTime = 8.0f;
    [SerializeField]
    float fadeOutTime = 1.0f;

    //Debugging
    float debugTimer = 3.0f;

    Coroutine endSequence = null;

    //Singals when the end sequence has finished playing.
    public event Action OnWinScreenFinished;

    // Start is called before the first frame update
    void Start()
    {
        blackFade.color = new Color(0f, 0f, 0f, 0f);
        winGraphics.alpha = 0.0f;
    }

    public void PlayEndSequence()
    {
        endSequence = StartCoroutine(CR_EndSequence());
    }

    IEnumerator CR_EndSequence()
    {
        yield return CR_BlackFadeIn();
        yield return CR_WinGraphicsFadeIn();
        yield return new WaitForSeconds(holdOnEndTime);
        yield return CR_WinGraphicsFadeOut();
        OnWinScreenFinished?.Invoke();
    }

    IEnumerator CR_BlackFadeIn()
    {
        float alpha = 0.0f;
        float fadeRate = 1.0f / fadeInTime;
        while (alpha < fadeInTime)
        {
            alpha += Time.deltaTime * fadeRate;
            blackFade.color = new Color(0f, 0f, 0f, alpha);
            yield return null;
        }
        alpha = 1.0f;
        blackFade.color = new Color(0f, 0f, 0f, alpha);
    }

    IEnumerator CR_BlackFadeOut()
    {
        float alpha = 0.0f;
        float fadeRate = 1.0f / fadeOutTime;
        while (alpha < fadeOutTime)
        {
            alpha += Time.deltaTime * fadeRate;
            blackFade.color = new Color(0f, 0f, 0f, 1.0f - alpha);
            yield return null;
        }
        alpha = 0.0f;
        blackFade.color = new Color(0f, 0f, 0f, alpha);
    }

    IEnumerator CR_WinGraphicsFadeIn()
    {
        float alpha = 0.0f;
        float fadeRate = 1.0f / fadeInTime;
        while (alpha < fadeInTime)
        {
            alpha += Time.deltaTime * fadeRate;
            winGraphics.alpha = alpha;
            yield return null;
        }
        alpha = 1.0f;
        winGraphics.alpha = alpha;
    }

    IEnumerator CR_WinGraphicsFadeOut()
    {
        float alpha = 0.0f;
        float fadeRate = 1.0f / fadeOutTime;
        while (alpha < fadeOutTime)
        {
            alpha += Time.deltaTime * fadeRate;
            winGraphics.alpha = 1.0f - alpha;
            yield return null;
        }
        alpha = 0.0f;
        winGraphics.alpha = alpha;
    }

    // Update is called once per frame
    void Update()
    {
        if (debugTimer > 0.0f)
        {
            debugTimer -= Time.deltaTime;
            if (debugTimer <= 0.0f && endSequence == null)
            {
                PlayEndSequence();
            }
        }
    }
}

AnimatronicNightConfig.cs


using System;
using UnityEngine;

[Serializable]
public class AnimatronicNightConfig
{
    public AnimatronicSystem.Animatronics animatronic;
    [Header("Starting Point")]
    public RoomConfig.RoomID start;
    [Header("Movement")]
    public int startingHour;
    public float timerVariability;
    public float transitionDuration;
    public float timeToRoomChange;
    [Header("Core Rooms")]
    public float prob_stage;
    public float prob_hall;
    public float prob_supply;
    public float prob_westDoor;
    public float prob_eastDoor;
    public float prob_office;
}

NightConfig.cs


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "FNAF/Night Config")]
public class NightConfig : ScriptableObject
{
    [Header("Night Number")]
    public int nightNumber = 1;
    [Header("Intro Audio")]
    public AudioClip introClip;
    [Header("Starting Power")]
    public float startingPower = 100f;
    [Header("Animatronics")]
    public List animatronics = new();
}

NightConfigTable.cs


using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "FNAF/Night Config Table")]
public class NightConfigTable : ScriptableObject
{
    [Tooltip("Index 0 = Night 1, index 1 = Night 2, etc...")]
    public List nights = new();

    public NightConfig GetNight(int night)
    {
        if(night > nights.Count)
        {
            return null;
        }
        return nights[night-1];
    }
}