Jump to content
Unity Insider Forum

Musik bei Scenewechsel


Kojote

Recommended Posts

Grüß!

Ich habe gerade ein kleiens Problem. In Scene 1 habe ich eine Hintergrundmusik laufen. Dafür ist GameObjakt "Musik" mit der Componente Audio Source zuständig, steuerung über mein Hauptmenü Script. Wenn ich das Theme wechsel, soll die Musik weiter gespiel werden. In Scene 2 habe ich ebenfalls ein GameObjekt "Musik" mit der Componente Audio Source. Hier soll nun die Musik Natlot in einander übergehen, beim Wechsel wird die Musik aber neu gestartet.

Was kann ich dagegen unternehmen?^^

Grüße von Kojote

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja, das ist klar. Dein Musikabspieler wird beim entladen der ersten Szene zerstört und das neue Objekt fängt halt gerade erst an.

Erstelle einfach ein Musikobjekt, welches per Code eben nicht zerstört wird, also durch alle Szenen mitgeschleift wird. Wenn es dann doch mal zerstört werden soll, musst du das manuell, wieder per Code machen.
DontDestroyOnLoad ist dein Schlüssel zum Ganzen. :)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja, das würde es machen.

Du kann das Object enteder zum singleton machen. Oder aber du erzeugst es einmalig in einer Szene, die wirklich nur einmal geladen wird. Meist hat man ja ne Startszene, die vor dem Menü und allen Spielszenen geladen wird und dann nicht mehr.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habs mal so versucht:

	if(GameObject.FindGameObjectsWithTag("Musik").Length > 1) {
            GameObjekt zerstoeren = GameObject.Find("Musik");
            Destroy(zerstoeren);
        }
        DontDestroyOnLoad(musikObjekt);
        musikObjekt = GameObject.Find("Musik");
        audioSourceSound = musikObjekt.GetComponent<AudioSource>();

Jedoch landet das musikObjekt nicht in DontDestroyOnLoad, wenn ich das Spiel starte.

EDIT: Ja ne, is klar, erst das Objekt mit DontDestroyOnLoad sichern und danach finden. Umgekehrt klappts vielleicht deutlich besser! :D Vielen Dank @malzbie

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Lass mal das mit den GameObject.Find-Funktionen, die sind böse.

Lieber ein Unity-Singleton benutzen:

public class MusicPlayer : MonoBehaviour
{
  private static MusicPlayer singleton;
  
  void Awake()
  {
    if(singleton)
    {
      Destroy(gameObject);
    }
    else
    {
      singleton = this;
      DontDestroyOnLoad(this);
    }
  }
}

Bei Fragen gerne fragen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Zitat

Lass mal das mit den GameObject.Find-Funktionen, die sind böse.

Merk ich glaube ich gerade, dass steht in meiner Hauptmenüsteuerung, in Awake:

	if (GameObject.FindGameObjectsWithTag("Musik").Length > 1) {
            GameObject zerstoeren = GameObject.Find("Musik");
            Destroy(zerstoeren);
        }
        musikObjekt = GameObject.Find("Musik");
        DontDestroyOnLoad(musikObjekt);
        audioSourceMusik = musikObjekt.GetComponent<AudioSource>();

Das in meiner Spielesteuerung, in Awake:

	if (GameObject.FindGameObjectsWithTag("Musik").Length > 1) {
            GameObject zerstoeren = GameObject.Find("Musik");
            Destroy(zerstoeren);
        }
        musikObjekt = GameObject.Find("Musik");
        DontDestroyOnLoad(musikObjekt);
        audioSourceMusik = musikObjekt.GetComponent<AudioSource>();

Am Anfang macht er alles richtig, das Objekt "musikObjekt" wird in DontDestroyOnLoad geladen. Das Spiel wird geladen und übernommen. Wenn ich nun aber zurück ins Hauptmenü wechsel, wird das Musikobjekt nicht mehr in DontDestroyOnLoad geschoben und wenn ich das nächste mal ein Level lade, findet er kein musikObjekt -> ERROR!

Warum übernimmt er das nicht?

 

Jetzt probier ich mal den Script von @Sascha aus. :)

Gleich mal ne Frage, wie greife ich nun auf die Audio Komponente zu, die in diesem Gameobjekt ist? Des weiteren, wie funktioniert der Spaß? Anscheinend löst das zumindest das Problem, was ich oben schrieb.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich hab Sascha's Klasse mal erweitert:

[RequireComponent(typeof(AudioSource))]
public class MusicPlayer : MonoBehaviour
{
  private static MusicPlayer singleton;
  private AudioSource audioSource; //Store a reference to our AudioSource.
  
  void Awake()
  {
    if(singleton)
    {
      Destroy(gameObject);
    }
    else
    {
      singleton = this;
      DontDestroyOnLoad(this);
      // Get a component reference to the attached AudioSource
      audioSource = GetComponent<AudioSource>();
    }
  }
}

Deine Audiosource hängst du nun an das gleiche Objekt, wo auch das Skript "MusicPlayer" hängt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

So richtig verstehe ich das jetzt nicht. Ich muss ja trotzdem von einem anderen Script auf AudioSource zugreifen. Wie würde da der Zugriff darauf aussehen?

Derzeit mache ich das so:

AudioSource audioSourceMusik = GameObject.Find("Musik").GetComponent<AudioSource>();

Man könnte es auch so machen:

Aufruf:

audioSourceMusik = HEX_Musik_Steuerung.audioSourceMusik;

Klasse:

[RequireComponent(typeof(AudioSource))]
public class Musik_Steuerung : MonoBehaviour {

    private static Musik_Steuerung singleton;
    public static AudioSource audioSourceMusik;

    void Awake() {
        if (singleton) {
            Destroy(gameObject);
        } else {
            singleton = this;
            DontDestroyOnLoad(this);
            audioSourceMusik = GetComponent<AudioSource>();
        }
    }
}

Nur weiß ich nicht, ob das immer klappt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Da ist schon wieder dieses GameObject.Find...!

vor 21 Minuten schrieb Kojote:

Ich muss ja trotzdem von einem anderen Script auf AudioSource zugreifen.

Warum solltest du das müssen? Es gibt dafür Möglichkeiten, aber da ich den Verdacht habem dass du das nur zu müssen glaubst, weil du das mit dem Singleton noch nicht verstanden hast, verrate ich erstmal nix ;)

Zur Funktionsweise:

Das Keyword "static" ist eine der größten Verursacher von Missverständnissen bei Programmieranfängern. So sehr, dass ich schon viele Beiträge geschrieben habe, die static erklären. Und ein Tutorial. Das ist zwar mit JS und nicht C#, aber das Konzept ist dasselbe. Die Kurzfassung: Eine statische Variable existiert exakt einmal, und nicht einmal pro Objekt wie eine normale Variable. Die Variable "singleton" aus meinem Code speichert also global und für alle Objekte gleichermaßen gültig die Referenz auf exakt ein Objekt vom Typ MusicPlayer. Das MusicPlayer-Objekt, das ganz zu Anfang als erstes Existiert, schaut, ob es bereits "den" MusicPlayer gibt:

if(singleton)
// was dasselbe wäre wie
if(singleton != null)

Wenn noch kein vorheriger MusicPlayer den Platz als Singleton-Objekt für sich beansprucht hat, dann krallt sich das Ding den Platz und referenziert sich selbst:

singleton = this;

und macht sich mit DontDestroyOnLoad resistent gegenüber Szenenwechseln.

Wenn dann irgendwann später ein neues MusicPlayer-Objekt auftaucht (weil du zurück ins Hauptmenü gehst, wo das Ding in der Hierarchie steckt), hast du zwei. Das neue führt Awake aus, das alte nicht. Dabei stellt das neue fest, dass "singleton" nicht gleich null ist, und zerstört sich und sein gesamtes GameObject selbst.

Destroy(gameObject);

Durch die statische Variable kannst also tracken, ob es "das eine Objekt" bereits gibt. Und wenn ja, zerstören sich alle Nachmacher selbst. Und da das alles Code stattfindet, brauchst du in Unity keine Namen oder Tags mehr zu vergeben.

Du brauchst damit keine Kommunikation zwischen den verschiedenen Objekten mehr.

Falls du deine Aussage, dass du da von außen drauf zugreifen musst, wegen etwas anderem meintest, dann sag bescheid.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ist schon klar, dass Static nur einmal existiert, es ist recht unschön ausgedrückt, eine Globale Variable, die nur einmal existiert.

Jedoch, wie greife ich nun bei AudioSource auf Volume zu, um die Lautstärke zu ändern, ohne einen Zugriff zu schaffen?

Ohne Objekt Find, ist das schlecht möglich.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wenn ich nicht irgendwo noch einen Syntaxfehler drin hab:

[RequireComponent(typeof(AudioSource))]
public class MusicPlayer : MonoBehaviour
{
  private static MusicPlayer singleton;
  private AudioSource audioSource; //Store a reference to our AudioSource.
  
  void Awake()
  {
    if(singleton)
    {
      Destroy(gameObject);
    }
    else
    {
      singleton = this;
      DontDestroyOnLoad(this);
      // Get a component reference to the attached AudioSource
      audioSource = GetComponent<AudioSource>();
    }
  }
        
  public static void setVolume(float vol) {
        audioSource.volume = vol;
  }
}

Aufruf:

MusicPlayer.setVolume(0.5f);

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 2 Stunden schrieb Kojote:

es ist recht unschön eine Globale Variable, die nur einmal existiert.

Dasja quatsch. Das ist wunderschön! :P

Aber @Zer0Cools Code hat einen Kleinen Fehler. Es muss heißen

public static void SetVolume(float vol)
{
  singleton.audioSource.volume = vol;
}

audioSource ist ja nicht static, daher kann darauf nicht direkt von SetVolume zugegriffen werden. Eine Alternative ist das Folgende: Aus

private static MusicPlayer singleton;

mach

public static MusicPlayer singleton { private set; get; }

Ist dasselbe bis auf den Vorteil, dass man public lesend drauf zugreifen kann. Dann könnte man von außen schon

MusicPlayer.singleton.audioSource.volume = 0.5f;

Anstatt für alles, was man so mit ner AudioSource machen kann, eine neue Methode in MusicPlayer zu schreiben.

Ob du das jetzt besser findest oder schlechter als zer0s Vorschlag, ist absolut Geschmackssache.

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 3 weeks later...

EDIT: Oh man jetzt sehe ich erst, dass dieser Thread ja schon fast ein Monat her ist ^^.

Ich habe so fast wie in den letzen Post gemacht. Also Volume und so verändere ich auch nur durch audioSource selber ohne extra Methoden.

Bei Singleton würde ich aufpassen. Ich persönlich würde nicht mit bei dem Beispiel mit GetComponent arbeiten. Aber ist Geschmacksache. 
Wieso?
Ich lasse diese Sachen "vorladen" also schon bereits in der erste Scene (was ganz leer sein kann, quasi Ladebildschirm/Splashscreen mäßig beim Spielstart). Dort ist nun meine AudioSource Einstellung bereits gemacht. Ich lasse daher bei Singleton eher eine Error ausgeben, dass es nicht existiert. Theoretisch müsste ja dieser immer vorhanden sein.

Wie gesagt ist Geschmacksache bzw. kommt drauf an. Bei vielen anderen Dingen braucht man wie bei mir nicht vorgehen.

Folgendes existiert bei mir noch extra.

Die Musikdateien. Eigentlich reicht auch private und [SerializeField], aber wollte damals Public haben

public AudioClip[] clips;

Paar Methoden um mein Leben leichter zu machen

    public void PlayMusicID(int id)
    {
        if (clips == null || id >= clips.Length)
            return;

        if (source.clip == clips[id])
            return;

        source.clip = clips[id];
        source.Play();
    }

    public void PlayMusicID(int id, float volume)
    {
        if (clips == null || source.clip == clips[id])
            return;

        source.volume = volume;
        PlayMusicID(id);
    }

 

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...