Search Unity

Resolved Objects getting destroyed with singleton pattern when using timeline?

Discussion in 'Timeline' started by MidniteOil, Nov 17, 2020.

  1. MidniteOil

    MidniteOil

    Joined:
    Sep 25, 2019
    Posts:
    345
    I hope this is the right group to post this in. I'm new to timeline so forgive me if this is a stupid question.

    I am writing a 2D game with some hand-drawn art and I want to have "cut scenes" where I animate "paper puppets" and play an audio clip.

    I created a PencilPowerPuppet object and added a sprite render, particle trail and audio source and I added a TimelineDirector to it. I animated the position and rotation and dragged an audio clip into the playable asset.
    upload_2020-11-16_21-41-50.png

    upload_2020-11-16_21-42-6.png

    upload_2020-11-16_21-42-21.png

    When I play the animation or play the game it works perfect.

    But I want to have a collection of "puppets" and my intention is to play a random one from the collection when a level (scene) starts.

    So I created a PuppetManager component.
    upload_2020-11-16_21-44-11.png

    I am using a singleton pattern for this PuppetManager because I will always have the same collection of puppets to choose from for every scene.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Random = UnityEngine.Random;
    5.  
    6. public class PuppetManager : MonoBehaviour
    7. {
    8.     [SerializeField] private List<GameObject> puppets = null;
    9.     [SerializeField] private float chanceToPlayPuppet = 25f;
    10.  
    11.     private bool _puppetPlaying;
    12.  
    13.     public static PuppetManager Instance;
    14.  
    15.     private void Awake()
    16.     {
    17.         if (Instance != null && Instance != this)
    18.         {
    19.             Destroy(gameObject);
    20.         }
    21.         else
    22.         {
    23.             Instance = this;
    24.             DontDestroyOnLoad(gameObject);
    25.             _puppetPlaying = false;
    26.         }
    27.     }
    28.  
    29.     public void PlayRandomPuppet()
    30.     {
    31.         if (_puppetPlaying || Random.Range(0, 100) > chanceToPlayPuppet) return;
    32.         int index = Random.Range(0, puppets.Count - 1);
    33.         var puppet = puppets[index];
    34.         if (puppet == null)
    35.         {
    36.             Debug.Log($"Puppet[{index}] is null.");
    37.             return;
    38.         }
    39.         Debug.Log($"Playing puppet {puppet.name}");
    40.         _puppetPlaying = true;
    41.         puppet.SetActive(true);
    42.         StartCoroutine(StopPuppet(puppet));
    43.     }
    44.  
    45.     private IEnumerator StopPuppet(GameObject puppet)
    46.     {
    47.         yield return new WaitForSeconds(5f);
    48.         if (puppet != null) puppet.SetActive(false);
    49.         _puppetPlaying = false;
    50.     }
    51. }
    52.  
    I have a LevelController class that calls PuppetManager.Instance.PlayRandomPuppet() in OnEnable().
    This plays a puppet when starting a level:

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.SceneManagement;
    6.  
    7. [RequireComponent(typeof(AudioSource))]
    8. public class LevelController : MonoBehaviour
    9. {
    10.     [SerializeField] private string _nextLevelName = null;
    11.     [SerializeField] private AudioClip _nextLevelSound = null;
    12.    
    13.     private Monster[] _monsters;
    14.     private AudioSource _audioSource;
    15.     private bool _levelComplete;
    16.  
    17.     private void Start()
    18.     {
    19.         _audioSource = GetComponent<AudioSource>();
    20.     }
    21.  
    22.     private void OnEnable()
    23.     {
    24.         _levelComplete = false;
    25.         _monsters = FindObjectsOfType<Monster>();
    26.         MusicManager.Instance.PlayNextSong();
    27.         PuppetManager.Instance.PlayRandomPuppet();
    28.     }
    29.  
    30.     // Update is called once per frame
    31.     void Update()
    32.     {
    33.         if (MonstersAreAllDead())
    34.         {
    35.             _audioSource.PlayOneShot(_nextLevelSound);
    36.             StartCoroutine(GoToNextLevel());
    37.         }
    38.     }
    39.  
    40.     private bool MonstersAreAllDead()
    41.     {
    42.         if (_levelComplete) return false;
    43.         foreach (var monster in _monsters)
    44.         {
    45.             if (monster.gameObject.activeSelf)
    46.                 return false;
    47.         }
    48.  
    49.         _levelComplete = true;
    50.         return true;
    51.     }
    52.    
    53.     private IEnumerator GoToNextLevel()
    54.     {
    55.         yield return new WaitForSeconds(1);
    56.         SceneManager.LoadScene(_nextLevelName);
    57.     }
    58. }
    59.  
    Whenever the scene changes, the puppet I get from the collection is null.
    Puppet[0] is null.
    UnityEngine.Debug:Log (object)
    PuppetManager:playRandomPuppet () (at Assets/Scripts/PuppetManager.cs:36)
    LevelController:OnEnable () (at Assets/Scripts/LevelController.cs:34)

    I only ever change the active state of the puppet game object so how is it getting destroyed? Shouldn't it get preserved since it's contained by the Singleton DontDestroyOnLoad PuppetManager?

    I don't have this issue with my MusicManager which uses the same pattern so the Timeline is the only difference I can think of.
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. [RequireComponent(typeof(AudioSource))]
    5. public class MusicManager : MonoBehaviour
    6. {
    7.     [SerializeField] private List<AudioClip> musicClips = null;
    8.    
    9.     public static MusicManager Instance;
    10.  
    11.     private AudioSource _audioSource;
    12.     private int _currentSong = -1;
    13.  
    14.     private void Awake()
    15.     {
    16.         if (Instance != null && Instance != this)
    17.         {
    18.             Destroy(gameObject);
    19.         }
    20.         else
    21.         {
    22.             Instance = this;
    23.             DontDestroyOnLoad(gameObject);
    24.             _audioSource = GetComponent<AudioSource>();
    25.             _currentSong = 0;
    26.         }
    27.     }
    28.  
    29.     private void PlaySong(int clipIndex)
    30.     {
    31.         _audioSource.clip = musicClips[clipIndex];
    32.         _audioSource.Play();
    33.     }
    34.  
    35.     public void PlayNextSong()
    36.     {
    37.         if (++_currentSong >= musicClips.Count)
    38.             _currentSong = 0;
    39.         PlaySong(_currentSong);
    40.     }
    41.    
    42. }
    43.  
    Could the timeline somehow be destroying the puppet after animating it?

    Thanks in advance!!!
     

    Attached Files:

  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    It doesn't look like timeline is doing it. The manager is surviving, it looks more like PencilPowerPuppet is getting deleted when the level unloads. You likely need to call DontDestroyOnLoad to the puppet gameObjects (contained in the Puppets List<>) as well as the manager.

    It works on the music manager because AudioClips are assets, and only unload when they are no longer referenced. GameObjects are deleted when their scene is unloaded.
     
    MidniteOil likes this.
  3. MidniteOil

    MidniteOil

    Joined:
    Sep 25, 2019
    Posts:
    345
    Thanks. I refactored the PuppetManager to take a list of prefabs and I instantiate each one in Awake and store them in a "pool" but they are still getting destroyed when the scene changes. So based on your comment I need to all DontDestroyOnLoad on each of them which makes sense. If that works I'll make this post as Solved.
     
  4. MidniteOil

    MidniteOil

    Joined:
    Sep 25, 2019
    Posts:
    345
    Okay, that worked. Thanks so much.