Jump to content
Unity Insider Forum

Sascha

Administrators
  • Posts

    13,025
  • Joined

  • Last visited

  • Days Won

    714

Everything posted by Sascha

  1. Das klassische Problem: Wie lernen sich meine Objekte gegenseitig kennen? Hier hast du etwas zum Lesen: http://blog.13pixels.de/2020/communicating-objects/
  2. Moin! Du brauchst eine Art Datenbank, in der alle Gebäude-Sorten (Prefabs) eingetragen sind und einen Identifier, mit dem man ein bestimmtes Gebäude in dieser Datenbank finden kann. Das klingt vielleicht heftiger, als es ist. Es reicht prinzipiell z.B. eine Liste, und der Identifier wäre der Index des Gebäudes in dieser Liste. Um etwas robuster zu sein, wäre ein String besser, damit die Reihenfolge der Gebäude in der Liste egal wird. Die Liste kannst du z.B. in einem ScriptableObject haben, damit sie überall verfügbar ist. Deine JSON-Datei sieht dann etwa so aus: "buildings": [ { type: "<identifier in der liste>", position: "10,2", health: 100 }, { type: "<noch ein identifier>", position: "0,5", health: 20 } ] Jedes Gebäude muss wissen, welchen Identifier sein Gebäudetyp in der Liste hat, damit du durch alle gebauten Gebäude gehen kannst und ihre jeweiligen Identifier in die Datei zusammen mit den anderen Daten speichern kannst. Beim Laden musst du wieder die Liste in die Hand nehmen und anhand des Identifiers eines Gebäudes aus der Datei das richtige Prefab besorgen und instantiieren.
  3. Nimmste halt den Teil mit dem "Controlpoint" aus deinem Code raus? var Controlpoint1GameObject = new GameObject($"Controlpoint 1");
  4. Im Editor (also im Play Mode) ja. Im Build nicht. Builds wissen nicht, was Assets sind. Da musst du stattdessen eine Information (wie z.B. die 3 Indizes der Teil-GameObjects) speichern und dann beim Laden diese Information benutzen, um den Körper identisch wieder aufzubauen.
  5. Nein, eine statische Variable kann auch in einer nicht-statischen Klasse sein und dort funktioniert exakt genau so. Um sie zu verwenden, ist keine Instanz der Klasse nötig, wenn diese nicht statisch sein sollte. In diesem Fall ist es allerdings in der Tat recht sinnlos, eine (nicht-statische) MonoBehaviour-Klasse zu haben, da diese auf ein oder mehrere GameObjects gezogen werden kann und dort exakt gar nichts macht. Ich muss da ein bisschen klugscheißen, und wenn ich schonmal dabei bin... Eine Klasse ist sowieso immer nur einmal im Programm. Es kann aber bei nicht-statischen Klassen keine, eine oder mehrere Instanzen einer Klasse geben. @grinseengel wenn du ein Script auf ein GameObject ziehst, entsteht eine Komponente - diese Komponente ist eine Instanz deiner Klasse, die du im Script geschrieben hast. Vermutlich meinst du das, @malzbie, aber ich glaube, man kann das etwas falsch verstehen: Da es sich um eine nicht-statische Klasse handelt, kann man "GlobalValues" beliebig oft auf irgendwelche GameObjects ziehen. Allerdings gibt es die Variable "leben" unabhängig davon immer nur genau ein Mal im Programm. @grinseengel Wenn du also "GlobalValues" zweimal auf irgendein GameObject ziehst (was, wie gesagt, sowieso wenig Sinn ergibt), dann arbeiten alle diese Komponenten mit derselben Zahl. @grinseengel: malzbie hat mit den Nachteilen Recht. Grundsätzlich sieht man es immer wieder, dass Leute statische/globale Variablen als erstbesten Weg finden, damit ein Objekt an Werte herankommt, die woanders herkommen. Mit globalen Variablen holt man sich aber immer einen Haufen Restriktionen ins Haus und wird sie auch nicht mehr los. Das heißt nicht, dass man sie nicht nutzen sollte - aber man muss sich genau anschauen, was "static" bedeutet und was es mit sich bringt. Da du gerade an dem Thema zum Lernen dran bist, würde ich sagen: Arbeite damit, dann fallen dir die Probleme früher oder später selbst auf. Das hilft immer am meisten. Wundere dich nur nicht, wenn es nervig wird
  6. Prinzipiell ist alles, was (public) static ist, global. Da es in C# (fast) keinen Code außerhalb von Klassen geben kann, muss eine statische Variable in einer Klasse drin sein. Ob du sie in eine auch anderweitig verwendete Klasse packst (muss ja nicht statisch sein) oder dir eine statische Klasse dafür machst, kommt ganz auf deine Architektur an. Du musst also nicht unbedingt eine statische Klasse machen, um statische Variablen darin zu deponieren. ScriptableObjects haben den Vorteil, dass du davon mehr erzeugen kannst ohne Code anzufassen. Aber vor allem auch, dass du sie injecten ("wo reinziehen") kannst.
  7. Ein Event ist einfach nur ein Ding, das eine Liste von Reaktionen hat und ausgelöst werden kann. Wird ein Event ausgelöst, werden dadurch alle dessen Reaktionen ausgelöst. Das ist alles. Events können auf verschiedenste Arten implementiert werden. Was du dadurch gewinnst ist, dass du nicht jeden Frame immer wieder dieselben Checks ausführst, obwohl sich die allermeiste Zeit gar nichts ändert, sodass sowieso immer dasselbe herauskommt.
  8. ExecuteInEditMode hat eher weniger damit zu tun, wenn etwas im Build anders läuft als im Play Mode im Editor.
  9. 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.
  10. 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.
  11. 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?
  12. 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
  13. 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.
  14. 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.
  15. Moin, ist wirklich nur ein Schuss ins Dunkle, aber kannst du das Script nicht auf die Masken anwenden statt auf die maskierten Bilder?
  16. 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)); }
  17. 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.
  18. 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.
  19. Wenn du einen NavMeshAgent nutzt, solltest du niemals selber Transform.Translate nutzen. Lass den Agent die Bewegung übernehmen.
  20. 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
  21. 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.
  22. 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.
  23. 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
  24. Was hat die Variable "keycode" denn für einen Typ? UnityEngine.UI.Text? Was zeigt denn das erste print an?
  25. Sehr merkwürdiger Workaround. Und auch nicht ganz eindeutig. Aber ein Blick in die Scripting Reference zeigt eine Eigenschaft namens targetCameraAlpha. Damit würde ich das mal versuchen.
×
×
  • Create New...