0

I have already checked other questions here on Stack Overflow and the Internet but couldn't find something similar.

I have a "Settings Scene" where I want to toggle a specific audio clips in other scenes (for example I want to mute countdown timer sound only from settings scene (scene 2) in the game scene (scene 7)).

In the Game Scene with the countdown timer, I have an AudioClip attached and I want to access it from the Settings Scene and send timerHandler.ToggleMuteTimer(false)

Here is some code:

public class TimerSoundHandler : MonoBehaviour {

public GameObject audioOnIcon;
public GameObject audioOffIcon;
private TimerHandler timerHandler;

void Start()
{
    //timerHandler = GameObject.FindWithTag("TimerTAG").GetComponent<TimerHandler>();        
    //timerHandler = GameObject.FindGameObjectWithTag("TimerTAG");  
    timerHandler = FindObjectOfType<TimerHandler>();   
    SetSoundState();
}


public void ToggleSound()
{
    if (PlayerPrefs.GetInt("TimerMuted", 0) == 0)
    {
        PlayerPrefs.SetInt("TimerMuted", 1);
    }
    else
    {
        PlayerPrefs.SetInt("TimerMuted", 0);
    }

    SetSoundState();
}

public void SetSoundState()
{
    if (PlayerPrefs.GetInt("TimerMuted", 0) == 0)
    {
        UnmuteSound();
    }
    else
    {
        MuteSound();
    }
}

private void UnmuteSound()
{
    //timerHandler.ToggleMuteTimer(true);
    audioOnIcon.SetActive(true);
    audioOffIcon.SetActive(false);
}

private void MuteSound()
{
    //timerHandler.ToggleMuteTimer(false);
    audioOnIcon.SetActive(false);
    audioOffIcon.SetActive(true);
}

}

And this is the TimerHandler script:

    public class TimerHandler : MonoBehaviour {

    public Text timerText;
    public AudioClip timerClipSound;

    [Range(0,3600)]
    [SerializeField]
    private float totalTime = 300f;
    private bool timeUp = false;
    private AudioSource audioSource;


    private void Awake()
    {
        audioSource = GetComponent<AudioSource>();
    }

    private void Update()
    {
        if (timeUp)
            return;

        totalTime -= Time.deltaTime;

        string minutes = ((int)totalTime / 60).ToString("00");
        string seconds = (totalTime % 60).ToString("00");

        timerText.text = minutes + ":" + seconds;


        if (totalTime <= 0)
        {
            timeUp = true;
            audioSource.mute = true;
        }
    }

    public void ToggleMuteTimer(bool value)
    {
        audioSource.mute = value;
    }
}

Any ideas how to access and edit a gameobject that is not even instantiated yet?

Johny
  • 625
  • 2
  • 6
  • 26
  • i find it easier to save a seperate settings file. it needs to be loaded on initial creation and stocked with your vriables, but you can just set it all to default. then you can edit this file in the save scene, and load this file in the game scene – Technivorous Dec 14 '18 at 12:52

1 Answers1

1

You can't access objects that are not yet created (instantiated).

One of standard solutions is to include code in your spawned (TimerHandler) class that notifies its controller class (TimerSoundHandler) that the object is now in the scene.

I would typically use OnEnable/OnDisable pair of methods, and possibly a static method.

This can be useful to maintain an always up to date list of objects you want to keep track of


List of active listeners

TimeHandlerController.cs

static List<TimeHandler> activeHandlers; 

public static RegisterTimer(TimeHandler source)
{
  if (activeHandlers==null) activeHandlers = new List<TimeHandler>(); 
   activeHandlers.Add(source);
}

public static UnRegisterTimer(TimeHandler source)
{
   activeHandlers.Remove(source); //if (activeHandlers.Contains(source))  
}

void DoStuffWithTimeHandlers()
{
  foreach(TimeHandler th in activeHandlers)
    th.DoStuff();
}

TimerHandler.cs

void OnEnable()
{
    TimeHandlerController.RegisterTimer(this);
}

void OnDisable()
{
    TimeHandlerController.UnRegisterTimer(this);
}

public void DoStuff() 
{ 
 // Do your stuff 
}

This pattern lets you reliably handle multiple controlled objects so that the controller always has an up to date list of active objects it needs to control (without ever searching the scene).

An even simpler solution, in case you don't need to keep track of all the TimeHandlers, is to use events to which each object subscribes (aka the Observer pattern), its much less code, but you loose the list of active listeners, which is often useful. For example it is harder to debug exceptions using events, as the stack trace gets lost on event invocation.

Event subscribtion

TimeHandlerController.cs

public static System.Action tick; // event

void Tick()
{
  if (tick!=null) // exception if no handlers otherwise
    tick.Invoke();
}

TimeHandler.cs

void OnEnable()
{
    TimeHandlerController.tick+=DoStuff;
}

void OnDisable()
{
    TimeHandlerController.tick-=DoStuff;
}

public void DoStuff() 
{ 
 // Do your stuff 
}

In case of the event subscribtion the type of the subscriber is irrelevant (as long as method signature matches), in case of a list you need to either know a base class of the object to be able to call methods upon it, or (often preferably) to specify an interface (which goes beyond the scope of the question)

zambari
  • 4,797
  • 1
  • 12
  • 22
  • Thank you @Zambari. I don't really get this, could you please explain a bit or try to edit the code that you posted? Sorry I'm new to Unity. – Johny Dec 14 '18 at 10:00
  • @Johny This is an [Observer Pattern](https://en.wikipedia.org/wiki/Observer_pattern) in C#, not specific to Unity3D. You can look up how to use them on [DotNetPerls/Event](https://www.dotnetperls.com/event) – Eliasar Dec 14 '18 at 15:06
  • @Johy I've added to the code posted (showing the example invocation) and provided a simpler example. I hope this helps – zambari Dec 16 '18 at 01:16