Jump to content
Unity Insider Forum

Sascha

Administrators
  • Posts

    13,017
  • Joined

  • Last visited

  • Days Won

    712

Sascha last won the day on January 11

Sascha had the most liked content!

7 Followers

About Sascha

  • Birthday 08/13/1990

Contact Methods

  • Website URL
    http://13pixels.de

Profile Information

  • Gender
    Male
  • Location
    Hamburg
  • Interests
    Programmierung

Recent Profile Visitors

79,266 profile views

Sascha's Achievements

Advanced Member

Advanced Member (3/3)

2.6k

Reputation

  1. Nein, ich denke nicht, dass dieser Code-Ausschnitt etwas damit zu tun hat. Der sieht wie gesagt gut aus. Wenn man im Edit Mode, also im Editor ist, während der Play Mode aus ist, dann muss man Dinge mit DestroyImmediate zerstören statt mit Destroy. Und genau diese Unterscheidung passiert hier. Ich sehe in diesem Code auch nichts, was mit Animation oder Bewegung zu tun hätte.
  2. Es handelt sich hierbei um Preprozessor-Direktiven. Damit sagst du dem Compiler, dass er auf eine bestimmte Art mit deinem Code umgehen soll, während er kompiliert wird. #if schmeißt den folgenden Code (bis zum #else oder #endif) einfach komplett raus, wenn die nachfolgende Bedingung nicht zutrifft. Die UNITY_BLUB-Flags werden von Unity je nach Plattform gesetzt. So landen in deinem Beispiel die ersten beiden Zeilen (DestroyImmediate) im kompilierten Code, solange du im Editor bist - sobald du aber einen Build machst, also den Editor verlässt, landen die unteren beiden Zeilen im Ergebnis. Der Verdacht, dass Code wie dieser Schuld daran sein kann, dass im Build etwas anders ist als im Editor, ist also nicht schlecht. Allerdings sieht dieser Code ziemlich narrensicher aus.
  3. So völlig ohne Code oder einen Hinweis darauf, was du benutzt, ist es immer schwierig, irgendetwas zu sagen. Was für eine Animation? Nur die Bewegung? Oder redest du von einem Animator? Ist das dein Code mit dem [ExecuteInEditMode]-Attribut oder von jemand anderem?
  4. Moin, Schau mal hier: https://docs.unity3d.com/ScriptReference/Light-lightmapBakeType.html Den Lightmapper gibt's nur im Editor, der wird bei Builds nicht mit eingebaut. Diese Eigenschaft gibt es entsprechend nicht in Builds. Du kannst den Fehler beheben, indem du entweder den Code ausbaust, der den Lightmapper benutzt, oder ihn auf den Editor beschränkst, sodass er ebenfalls nicht mit im Build landet. Letzteres ist sinnvoll, wenn dein Code im Editor seinen Zweck erfüllt und du ihn im Build nicht mehr brauchst. Dabei helfen Preprozessor-Direktiven: #if UNITY_EDITOR // code, der nur im Editor laufen soll #endif
  5. Persistent sind die Listener, die im Editor eingestellt werden. Mit AddListener, RemoveListener und RemoveAllListeners arbeitest du nur mit Listenern, die im Code behandelt werden. Du hast quasi zwei Listen: Die im Editor und die, die du im Code erzeugst.
  6. Der Lambda-Ausdruck ist ein Statement, das zu einem Wert evaluiert. Dieser Wert ist eine Referenz auf die Methode, die du erzeugst. Mit diesem Wert musst du bei Lambda-Ausdrücken etwas machen. Genauso wie du nicht 5 + 5; in deinen Code schreiben kannst, sondern das Ergebnis irgendwie benutzen musst var number = 5 + 5; musst du das auch bei Lambda-Ausdrücken machen. () => Debug.Log(number); // Geht nicht Action foo = () => Debug.Log(number); // Geht Damit ist hoffentlich auch die Frage geklärt: Die Methode hat keinen Namen, genau wie Objekte keinen Namen haben. Aber die Variable, die die Methode referenziert, hat einen Namen (hier foo), über den du die anonyme Methode aufrufen kannst. Sie zu erzeugen passiert genau da, wo sie gebraucht werden. Man kann halt tatsächlich auch eine ganz normale Methode definieren und diese in einer Action-Variable referenzieren: private void Start() { Action foo = MyMethod; foo(); } private void MyMethod() { Debug.Log("hi"); } Das kann auch oft die bessere Variante sein. Beim Button geht das genauso: btn.onClick.AddListener(MyMethod); und es spricht erstmal wirklich nicht viel dagegen. Wenn deine Methode einen guten Namen hat, ist das 1a. Knifflig wird es dann aber, wenn du z.B. sechs verschiedene Verhaltensweisen haben willst, und wenn diese sich auch nur durch eine einzige Zahl unterscheiden. So wie bei dir Die vordefinierte Methode hat nämlich im Gegensatz zur anonymen keine Closure. Naja, abgesehen vom Objekt, in dem sie steckt. Oder anders gesagt: Du kannst keine Parameterwerte an MyMethod binden, wenn diese Methode Parameter hätte. Nehmen wir dein Beispiel: private void Start() { btn.onClick.AddListener(MyMethod); } private void MyMethod(int index) { menuManager.ButtonAction(index); } hier würde das Programm erwarten, dass der Button deiner Methode beim Aufruf den Index übergibt. Tut er ja aber nicht. Und du kannst auch nicht schreiben btn.onClick.AddListener(MyMethod(index)); weil du dann nicht MyMethod übergibst, sondern MyMethod aufrufst und dann das übergibst, was dabei zurückkommt. Mit () => menuManager.ButtonAction(index) erzeugst du eine neue, parameterlose Methode, in die dein Index fest integriert ist. Wenn deine Schleife also sechsmal durchläuft, hast du sechs neue Methoden generiert, die wegen des sich ändernden Index tatsächlich auch alle unterschiedlich sind. Das Äquivalent dazu wäre MyMethod1, MyMethod2, MyMethod3 usw. zu definieren... merkste Du erzeugst also neue Methoden zur Laufzeit, damit z.B. so ein Unity-Button einfach stumpf eine Methode zum ausführen in die Hand kriegt. Diese Methode ist dann einzigartig und braucht beim Aufrufen keinen Parameter mehr, um anders zu sein als die der anderen Buttons. Und zuallerletzt: "Action" ist in System definiert. Du musst also System.Action schreiben oder, was fast immer besser ist: using System; an den Anfang.
  7. Moin, ist wirklich nur ein Schuss ins Dunkle, aber kannst du das Script nicht auf die Masken anwenden statt auf die maskierten Bilder?
  8. Moin! 1. Die üblichere Schreibweise ist die Lambda-Schreibweise. Die gibt's in mehreren Sprachen und wird, wenn man sie erstmal kennt, meistens als angenehmer empfunden als da "delegate" hinzuschreiben. Ein Lambda-Ausdruck (genau wie mit "delegate") ist einfach eine Schreibweise für eine Methode ohne Namen. Man kann also als Beispiele Methoden nehmen und dann die entsprechenden Lambda-Ausdrücke zeigen: a) private void Foo() { Debug.Log(5); } als Lambda-Ausdruck wäre () => Debug.Log(5) b) private void Foo() { Debug.Log(5); Debug.Log(10); } als Lambda-Ausdruck wäre () => { Debug.Log(5); Debug.Log(10); } Hier braucht man geschweifte Klammern, weil mehr als eine Anweisung drinsteht. c) Jetzt mal mit Parametern: private void Foo(int number) { Debug.Log(number); } wäre number => Debug.Log(number) und private void Foo(int number, string text) { Debug.Log(number + text); } wäre (number, text) => Debug.Log(number + text) Man bemerke hier, dass keine Parametertypen angegeben werden - da steht also nix davon, dass "text" ein string ist. Das liegt daran, dass Lambda-Ausdrücke zu Methodenreferenzen evaluieren. Diese werden als normale Werte behandelt - man kann sie also in eine Variable speichern oder eben als Parameter übergeben. Und in diesen Fällen ist vom Typ der Variable bzw. des Parameters her schon vorgegeben, dass da eine Referenz auf eine Methode kommen muss, die ein int- und einen string-Parameter hat. Action<int, string> foo = (number, text) => Debug.Log(number + text); // und jetzt kann man die Methode aufrufen foo(5, " Stück"); Mit Lambda-Ausdrücken kann man einem Objekt mehr als nur simple Werte in die Hand drücken, sondern ganze Verhaltensweisen. Deshalb kannst du die Dinger benutzen, um Buttons zu sagen, was sie tun sollen. btn.onClick.AddListener(() => GetComponent<MenuManager>().ButtonAction(i)); Soviel erstmal dazu. 2. Jetzt musst du (leider) das Konzept von Closures kennenlernen. Eine Closure ist wie ein Objekt, also ein Ding, das bestimmte Werte hat. Und jede anonyme Methode (also die, die du mit einem Lambda-Ausdruck erzeugst), hat so eine. Sie enthält die für die Ausführung der Methode relevanten Variablen aus der Umgebung, in der der Lambda-Ausdruck steht. var number = 5; Action foo = () => Debug.Log(number); foo(); Hier wird eine int-Variable mit dem Wert 5 definiert, eine Methode die den Wert der Variable ausgibt, und dann wird diese Methode aufgerufen. Es sollte also recht klar sein, dass die Zahl 5 ausgegeben wird. Die Variable "number" ist Teil der Closure der Methode, die von der Variable "foo" referenziert wird. Jetzt kommt der Knackpunkt: Closures machen pass-by-reference. Das heißt, dass hier nicht die 5 in der Closure gespeichert wird, sondern eine Referenz auf die Variable "number". Wenn du folgendes tust: var number = 5; Action foo = () => Debug.Log(number); number = 10; foo(); dann wird tatsächlich 10 ausgegeben und nicht 5, weil beim Aufruf der Methode der aktuelle Wert der Variable angeschaut wird. Wenn du also eine Schleife hast und in dieser Schleife Methoden erzeugst, die irgendetwas mit der Zählvariable i tun, dann wird am Ende jede dieser anonymen Methoden den aktuellen Wert von i nehmen - und das wird nun einmal der Wert am Ende des Schleifendurchlaufs sein, also in deinem Fall 1 bei einem Button bzw. 6 bei sechs Buttons. Die Lösung des Problems ist jetzt ein bisschen stumpf: Du machst in der Schleife eine neue Variable und kopierst da den Wert rein; dann benutzt du diese Variable in deinem Lambda-Ausdruck statt i: for(int i = 0; i < list.count; i++) { var btn = list[i]; var index = i; btn.onClick.AddListener(() => GetComponent<MenuManager>().ButtonAction(index)); } Dann wird die Variable "index" angeschaut, wenn man auf einen Button klickt - und der Wert dieser Variable ändert sich nicht mehr, weil sie am Ende des jeweiligen Durchlaufs (eigentlich) out of scope geht. Übrigens kannst du es dir (so als Schmakerl) sparen, jedes Mal GetComponent aufzurufen, indem du das vorher einmal machst: var menuManager = GetComponent<MenuManager>(); for(int i = 0; i < list.count; i++) { var btn = list[i]; var index = i; btn.onClick.AddListener(() => menuManager.ButtonAction(index)); }
  9. Moin, es gibt keine Grenzen in einer Szene, wenn du keine baust. Aufgrund mathematisch bedingter Einschränkungen von Fließkommazahlen schmeißt Unity dir ab 10.000m Abstand vom Nullpunkt Warnungen, und Positionen können ungenau werden. Aber abprallende Physikobjekte gibt's nicht, wenn du die Begrenzung nicht selber eingebaut hast.
  10. Kannst ja in der gleichen Methode isKinematic wieder auf true stellen. Aber zwei beliebige Spiele sind in der Regel nicht gleich, sodass in dem einen Spiel diese Lösung besser ist und im anderen Spiel eine andere. Events werden sehr häufig verwendet und sind auch hier sehr sinnvoll. Sie sind nur eben kein reines Anfängermaterial mehr.
  11. Wenn du einen NavMeshAgent nutzt, solltest du niemals selber Transform.Translate nutzen. Lass den Agent die Bewegung übernehmen.
  12. Joa, ich auch, aber das ist so ein kurzer Code dass es mir egal war. Sobald es minimal komplizierter wird, kommt man mit der Herangehensweise nicht mehr weit
  13. Das ist mir schon klar - ich wollte wissen, ob da auch "1234" ankommt Das ist schon einmal nicht schlecht, aber TextMeshProUGUI ist die falsche Komponente. Das ist die Komponente, die den Text anzeigt, aber nicht die Komponente, die den Input regelt. Wenn du etwas in das Textfeld eingibst, werden bestimmte Events (wie OnValueChanged) ausgelöst, bevor der Text in der Textkomponente aktualisiert wird. Du willst also stattdessen als Typ TMP_InputField nehmen und die Input Field-Komponente reinziehen.
  14. Und wieder einen Beitrag verschoben. @notstrom bitte bleibe hier, okay? Also, hab's mir mal angeschaut. Du hast dieses Script: public class Singleton : MonoBehaviour { private static Singleton singletonInstance; private void Awake() { if(singletonInstance == null) { singletonInstance = this; } else if(singletonInstance != this) { Destroy(gameObject); } DontDestroyOnLoad(singletonInstance); } } Und darüber hinaus ein weiteres Script, das tatsächlich eine AudioSource referenziert (MusicManager). Deine Konfiguration teilt sich dabei in zwei GameObjects auf: MusicManager AudioSource + Singleton Das Singleton-Script funktioniert in dem Sinne, dass es die AudioSource "unverwundbar macht" und darüber hinaus bei der Rückkehr in Szene A die frisch geladene AudioSource zerstört, sodass nur die alte bleibt. Der MusicManager allerdings ist weiterhin der frisch geladene, der entsprechend auch die frisch geladene AudioSource referenziert anstatt der alten. Und die frische wird ja gelöscht, deshalb wird die Referenz vom MusicManager ungültig. Kannst du auch direkt im Inspektor sehen: Die Idee hinter dem Singleton-Pattern ist, dass es keine eigenständige Klasse ist, sondern eben ein Muster (Pattern), das du in beliebigen Klassen anwenden kannst. Du machst also aus deinem MusicManager eine Singleton-Klasse: [RequireComponent(typeof(AudioSource))] public class MusicManager : MonoBehaviour { private static MusicManager singletonInstance; private AudioSource audioSource; private void Awake() { if(singletonInstance == null) { singletonInstance = this; DontDestroyOnLoad(singletonInstance); } else if(singletonInstance != this) { Destroy(gameObject); } audioSource = GetComponent<AudioSource>(); } // hier kommt noch was hin } Du hast also einen MusicManager, der sich und seine AudioSource unverwundbar macht. Dieser MusicManager kann aber nicht einfach referenziert werden - aus genau dem Grund, den ich eben erklärt habe. Sobald du die Szene zum zweiten Mal lädst, referenzieren die anderen Objekte ggf. den neuen MusicManager, der ja sofort gelöscht wird, und nicht den alten, den sie aber benutzen müssten. Deshalb nutzt du die statische Variable "singletonInstance", um den existierenden MusicManager zu finden. Deshalb kommt noch z.B. diese Methode dazu: public static void Play() { singletonInstance.audioSource.Play(); } Sie ist statisch wie die Variable. "Statisch" bedeutet, dass die Methode bzw. die Variable nicht an ein Objekt gebunden ist. Wenn du z.B. zwei Light-Objekte hast, hat das eine vielleicht die Farbe Rot, das andere die Farbe Blau. Jedes Objekt hat seinen eigenen Wert für diese Variable, denn sie ist nicht statisch. Eine statische Variable gibt's pro Script nur einmal, egal wie oft du es auf ein GameObject ziehst. In der Variable singletonInstance haben wir ja aber zum Glück eine Referenz auf den korrekten, aktiven (alten) MusicManager hinterlegt. Wir können also diese Variable nutzen, um mit genau diesem MusicManager und keinem anderen zu arbeiten. Wir schnappen uns dessen AudioSource und rufen Play() auf. Das ganze können wir von einem anderen Script aus so benutzen: MusicManager.Play(); Da die Methode statisch ist, können wir einfach [Klassenname].[Methodenname]() aufrufen und fertig. Falls du dich noch über meine Änderungen im Singleton- (jetzt MusicManager-)Script wunderst... Mit RequireComponent und GetComponent in Awake wird sichergestellt, dass die AudioSource auf demselben Objekt wie der MusicManager ist und nicht auf einem anderen, das dann beim Szenenwechsel gelöscht werden würde. Man spart sich dann auch das reinziehen.
  15. private void Update() { if (Input.GetButtonDown(KeyCode.Space)) { transform.Rotate(90, 0, 0); } } Sowas wird aber in so ungefähr jedem Grundlagentutorial abgedeckt, einfach mal was anschauen
×
×
  • Create New...