-
Posts
13,503 -
Joined
-
Last visited
-
Days Won
766
Content Type
Profiles
Forums
Calendar
Blogs
Everything posted by Sascha
-
Parameter generischer Klassen dynamisch erstellen
Sascha replied to Kurumi-chan's topic in Scripting
Korrekt! Aber du kannst Typen eben nicht nur über generische Parameter kommunizieren, sondern auch über einen normalen System.Type-Parameter. Statt public void Foo<T>() machst du public void Foo(System.Type type) Du kannst generische Parameter auch problemlos mit typeof() zu einem System.Type-Objekt umwandeln. So als Beispiel: public bool Foo<T>(System.Type type) { return typeof(t) == type; } Das gibt dann true zurück, wenn man das so aufruft: Foo<Light>(typeof(Light)) // oder Foo<Light>(GetComponent<Light>().GetType()) Umgekehrt braucht man halt leider Reflection und sollte das vermeiden. Aber man kann oft von der anderen Seite ankommen, z.B. so: class MyGenericClass<T> { public System.Type GenericType => typeof(T); } Objekte dieser Klasse geben die mit der Property "GenericType" den Typ zurück, den du beim Erstellen für T übergeben hast: var thing = new MyGenericClass<Light>(); Debug.Log(thing.GenericType.Name); // "Light" Damit geht eine ganze Menge. Du könntest zum Beispiel beim Start alle deine Objekte laden und sie in ein Dictionary packen. Das Dictionary kann so aussehen: private Dictionary<System.Type, List<MyGenericClass<>> genericClassObjectsByGenericType = new Dictionary<...>(); Und dann lädst du da alle geladenen Objekte rein und sortierst sie nach ihrem generischen Typ: foreach (var loadedObject in allLoadedObjects) { var type = loadedObject.GenericType; List<MyGenericClass<>> list; if (genericClassObjectByType.TryGetValue(type, out list)) { list.Add(loadedObject); } else { list = new List<MyGenericClass<>>(); list.Add(loadedObject); genericClassObjectByType.Add(type, list); } } Und wenn du dann alle Objekte eines bestimmten generischen Type haben willst, holst du sie dir einfach aus dem Dictionary: public List<MyGenericClass<>> GetAllObjectsForType(System.Type type) { return genericClassObjectsByType[type]; } Da fehlt noch die Ausnahmebehandlung, falls der Typ nicht gefunden wird. Und ich hab das jetzt so runtergetippt, kann sein dass die Syntax mit <> nicht ganz richtig verwendet wird. Aber ich hoffe, die Idee ist einigermaßen klar. Ja schon, aber eine Datenstruktur wachsen zu lassen ist besserer Stil als ein switch-case, das ständig erweitert werden muss. Vor allem, weil du bei einem Dictionary einfach mit der Schleife über alle Typen gehen kannst (siehe oben), anstatt deinen switch-case-Code jedes Mal anzufassen. -
Parameter generischer Klassen dynamisch erstellen
Sascha replied to Kurumi-chan's topic in Scripting
Hmm... müssen tut man das nie. Ich bin mir gerade nicht ganz sicher, aber mir fällt gerade kein Fall ein, wo man etwas mit einer generischen Methode machen kann, was man nicht auch mit einem System-Type-Parameter machen könnte. Außer Boxing bei Structs vermeiden... Was machst du denn am Ende mit deinem generischen Typparameter, dass du nicht auch ein System.Type-Objekt dafür nehmen kannst? Und so nebenbei: switch-case kannst du üblicherweise mit einem Dictionary vermeiden. Sollte man machen, wenn das switch-case potentiell immer weiter wachsen würde. -
Parameter generischer Klassen dynamisch erstellen
Sascha replied to Kurumi-chan's topic in Scripting
Moin! Ich bin gerade nicht so ganz fit, daher verstehe ich die Frage vielleicht nur so halb. Aber wenn ich die Hälfte immerhin richtig verstehe, dann geht es darum, ein System.Type zu haben und diesen als generischen Parameter zu nehmen. Das ist nicht vorgesehen, da Generics ein "statisches" Konzept ist, das zur Compile Time feststeht und dadurch mehr Sicherheit gibt als dynamisches Umherwerfen von Typen zur Laufzeit. Der korrekte Weg ist daher eine Überladung der Methode mit einem normalen Parameter vom Typ System.Type. Macht GetComponent auch so (wobei "Unity macht das auch so" natürlich kein starkes Argument ist ). Alternativ geht das theoretisch auch mit Reflection. Man kann da aber den generischen Typ (typeof(DatabaseCodeWindow)) nehmen und den mit MakeGenericType() inflaten (also generische Parameter setzen). Diesen Typ kannst du dann in den Activator stecken, um eine Instanz zu erzeugen. Reflection ist aber nur in einigen wenigen Sonderfällen eine gute Idee, z.B. wenn dein Code mit anderem Code funktionieren soll, den du jetzt noch nicht kennst, z.B. weil du ein Paket schreibst, das andere dann weiter verwenden. Wenn das nicht der Fall ist, solltest du Reflection eher meiden. -
An dieser Aussage hat sich für mich jetzt leider nicht wirklich was geändert...
-
Moin! Doch, das kann es schon sein. Objekte sind nicht von sich aus undurchlässig. Auch Collidern ist es egal, wenn sie ineinander stecken. Es muss immer irgendein Stück Code, z.B. der der Rigidbody-Komponente, ankommen und Kollisionschecks machen, damit Objekte nicht ineinander landen. Wenn du in deinem anderen Projekt einen Rigidbody benutzt, diesen mit der Tastatur bewegst und er in eine Wand gerät, dann stellt er eine Kollision fest und bewegt sich wieder heraus. Hältst du die Taste in Richtung der Wand weiter gedrückt, dann passiert im nächsten Frame noch einmal dasselbe: Geht in die Wand hinein, stellt Kollision fest, geht wieder heraus. Diesen Zwischenstand in der Wand drin sieht man auch nicht, weil das alles zwischen zweimal rendern passiert. Ich vermute jetzt: In deinem neuen Projekt bewegst du dich aber nicht jeden Frame ein Stück weiter (und dann im Kollisionsfall wieder ein bisschen zurück), sondern "teleportierst" dein Objekt in jedem Frame zur Mausposition. Vielleicht ist das nicht der Fall, oder vielleicht doch aber es ist nicht das Problem - aber der Punkt ist, dass in diesem Fall das Verhalten ganz anders sein kann, weil dein Rigidbody vielleicht tief in der Wand drinsteckt und nicht weiß, wo er hin soll. Rigidbodys haben die Angewohnheit, dann irgendwo™ hinzugehen, und deshalb gibt es so etwas. Jetzt, wo das gesagt ist... ich weiß überhaupt nicht, was bei dir genau schief läuft. Gibt ja leider keine Info außer des implizierten "Geht nicht". Vielleicht ist der Absatz mit dem Rigidbody aber ja schon genug.
-
Moin! Dafür, dass das gar nicht so einfach passieren dürfte, habe ich dieses Problem, dass beim zweiten Mal Laden etwas anders ist als beim ersten Mal, schon echt oft gesehen. Hast du irgendwo DontDestroyOnLoad drin, z.B. in deinem Spawn- und Endpunkt-Scripts oder so?
-
Moin! Dein Ansatz ist gut, dass du dich drehst und geradeaus fliegst. Wenn du jetzt noch Trägheit einbaust, würde das heißen, dass du geradeaus beschleunigst (aber die bestehende Geschwindigkeit nicht sofort überschrieben wird. Wenn du einen Rigidbody benutzt, kriegst du das gratis, da Beschleunigung und Trägheit dessen täglich Brot sind. Da nutzt du AddForce statt Translate und fertig. Wenn du das aber nicht willst (da gibt's manchmal gute Gründe für), dann musst du dir ein Vector3-Feld in deine Klasse definieren, das die aktuelle Geschwindigkeit speichert. Diese Geschwindigkeit packst du statt Vector3.forward bei Translate rein. Und davor machst du, dass die Geschwindigkeit mehr in Richtung "vorne" geht. Wie du das genau machst, ist dir überlassen. Sowas hier wäre eine Möglichkeit: velocity = Vector3.Lerp(velocity, transform.forward, someValue * Time.deltaTime); Hier müssen wir transform.forward statt Vector3.forward nehmen, weil wir im global space sind. transform.Translate müssen wir auch global machen: transform.Translate(velocity * Time.deltaTime, Space.World); Bei Fragen gerne fragen
-
Moin! Also erstmal... Kommazahlen musst du auf Englisch schreiben, also mit nem Punkt statt eines Kommas. Dahinter kommt ein f (aus Gründen, die gerade erst mal nicht wichtig sind): 1.5f Dann zur Pause: Es gibt da mehrere Wege, aber am Besten geht's mit einer Coroutine. Das ist eine Methode, die nicht alles sofort macht, sondern zwischen zwei Anweisungen warten kann... einen Frame, mehrere Frames, bis irgendetwas passiert ist oder eben eine bestimmte Anzahl von Sekunden. Das funktioniert so: Erst mal baust du eine Methode, die IEnumerator als Rückgabetyp hat: private IEnumerator TuDinge() { } In diese Methode kannst du ganz normalen Code reinschreiben, aber wenn du eine Zeile schreibst, die mit yield return anfängt, dann ist das eine Anweisung zum Warten. X Sekunden warten kannst du mit einem WaitForSeconds: private IEnumerator TuDinge() { // tu irgendetwas ... // warte zwei Sekunden yield return new WaitForSeconds(2); // tu etwas nach dem Warten ... } Aufrufen musst du diese Methode so, damit es funktioniert: StartCoroutine(TuDinge()); Also als Beispiel: private void OnCollisionEnter2D() { StartCoroutine(TuDinge()); } private IEnumerator TuDinge() { rigidbody.velocity = Vector2.zero; yield return new WaitForSeconds(2); rigidbody.velocity = new Vector2(1.5f, 0.5f); }
-
Öh... also... das war auch überhaupt nicht meine Idee? Ist vielleicht nicht so gut zu erkennen, aber in meinem Bild sind drei eigenständige Tiles zu sehen, die sich überlappen. Die blauen Kästchen sind das Grid, nicht die Tiles. Die Tiles sollen durch den Vertex Shader vergrößert werden, sodass sie überlappen.
-
Also... wenn du den Rand einfach nur etwas "wild" abschneiden willst, wie im zweiten Bild, dann könntest du eine globale Graustufen-Perlin-Noise-Textur haben, und dazu noch eine "lokale". Die lokale müsste etwa so aussehen: Die Idee ist jetzt die folgende: Deine Tiles müssten etwas größer sein als ihr Grid. Wenn du ein 1x1-Grid hast, dann sollten die Tiles alle so 1.2x1.2 sein, sodass sie, wenn man sie mit dieser Textur nebeneinander packt, so aussehen: Das ließe sich vermutlich am elegantesten mit einem Vertex Shader machen. Der "Glow" außenrum wird dann die "kaputte" Kante, wenn man so will. Du hast jetzt die Perlin-Noise-Textur und nimmst wieder die World Space-Koordinaten dafür. Dann vereinst du sie mit der lokalen Textur, die eben lokale Objektkoordinaten benutzt. Zum Vereinen musst du ein bisschen rumprobieren - die richtige Formel fällt mir dazu gerade nicht direkt ein. Aber die Idee wäre + = Vermutlich irgendeine Kombination aus max() und Multiplikation oder so. Das benutzt du dann als Alpha-Wert, oder für Alpha Clipping.
-
Moin! Ich bin mir nicht ganz sicher mit "teilen sich dasselbe Material" meinst. Anhand der folgenden Fragen vermute ich, du meinst, dass da eine fortlaufende Textur zu sehen ist, die sich über mehrere Tiles erstreckt? Der Ansatz mit dem Mesh könnte theoretisch funktionieren, aber ich stelle mir das schwierig vor, das performant zu halten. Ich denke, der bessere Weg sind World Space-Texturkoordinaten. Shader haben ja Koordinaten, mit denen sie das Pixel der Textur finden, das sie an eine gegebene Stelle rendern wollen. Da das für die meisten Fälle Sinn ergibt, nehmen die meisten Shader dafür die UV-Map des 3D-Modells. Ein 2D-Objekt ist hierbei auch nur ein flaches 3D-Modell. Jedenfalls muss man dabei aber nicht bleiben - man kann den Shader beliebige Zahlen verwenden, nachschauen oder ausrechnen lassen, um diese als Texturkoordinaten zu verwenden. So kannst du z.B. auch einfach die World Space-Koordinate eines Pixels nehmen und diese als Textur-Koordinate nutzen. Ist zwar 3D, aber hier kannst du dir das mal ansehen: Wie du siehst, ist die Textur immer an derselben Stelle der Welt und nicht, wie sonst, des Objekts. Wenn er hier den Würfel kopiert und die Kopie neben das Original packt, dann passen die beiden Würfel direkt aneinander. Mit dieser Technik kannst du deine Tiles völlig beliebig in die Welt klatschen, und die Textur ist immer gleich. Wenn du dir mal deinen zweiten Screenshot genauer ansiehst, siehst du auch, dass das so gemacht sein dürfte. Denn die Textur wiederholt sich nach 16-order-so Tiles: Also, lange Rede, kurzer Sinn: Baue dir oder finde einen Sprite Shader mit World Space-Texturkoordinaten.
-
Also erst mal sei gesagt, dass dein Problem kein triviales ist. Stell dir vor, da ist ein Collider auf der Linie zwischen 1 und 2. Einfach weil da eine kleine Wand auf dem Boden steht. Wie willst du der Katze sagen, dass sie das Ding ignorieren soll? Ich würde an deiner Stelle dazu tendieren, die Physik und die normale Bewegung für die Dauer eines Sprungs komplett abzuschalten. Du definierst ja sowieso per Hand (und anders geht es auch kaum) die jeweiligen Höhen der Bodenflächen in deiner Welt. Du hast also auch bereits überhaupt erst mal ein System, mit dem du diese Flächen definieren kannst. Ich denke, es wäre einfacher, von hier noch einen Schritt weiter zu gehen und an Stellen, an denen ein Sprung erlaubt ist, eine prozedurale Animation (in diesem Fall mit errechnetem Start- und Zielpunkt und einer Parabel dazwischen) abzuspielen. Während diese läuft, wird der Rigidbody deaktiviert und erst dann wieder aktiviert, wenn die Katze am Ziel des Sprungs angekommen ist. Wenn das etwas kompliziert klingt: Ist es leider auch. Machbar, aber wirklich kein Anfängerkram mehr. Dennoch halte ich das aktuell für überschaubarer als einen Rigidbody zu zähmen.
-
Abhängigkeiten zwischen Gameobjects sauber lösen
Sascha replied to Damon93's topic in Allgemeine Hilfe
Nein Dein Projekt ist auch nur ein ganz normales C#-Programm, das von der darunter liegenden C++-Engine gestartet wird. Du hast keinen Zugriff auf die übliche Main-Methode und einen ganzen Haufen Engine-interner Dinge, aber abgesehen davon hast du da nicht so wirklich nennenswerte Einschränkungen. Wenn du diesen Satz also so meinst, dass der Code zum Erstellen von Objekten in einem MonoBehaviour stecken muss, dann: Nein. Objekte können Objekte können Objekte erstellen. Gar kein Problem. Wenn du den Satz aber so meinst, dass der initiale Impuls, dass irgendetwas passiert, aus Unity heraus kommen muss, dann hast du damit sogar Recht. Wenn du daraus aber wiederum ableitest, dass das in einem MonoBehaviour passieren muss, dann stimmt die Behauptung auch wieder nicht. Unity hat einen solchen Impuls, der einfach nur zum Programmstart irgendeinen Code ausführt - ähnlich einer Main-Methode. Dafür brauchst du dieses Attribut: [RuntimeInitializeOnLoadMethod] private static void InitializeStuff() { Debug.Log("Hello, World!"); } Das kannst du über eine parameterlose, statische Methode in einer beliebigen Klasse packen und ab dafür. Das Attribut hat auch noch einen optionalen Parameter, mit dem du bestimmen kannst, ob das vor dem Laden der ersten Szene passieren soll oder danach... oder noch woanders. In dieser Methode kannst du dann auch wunderbar auch verschiedene Events subscriben, z.B. auf das Wechseln zu einer anderen Szene. Du kannst auch mit dem PlayerLoopSystem deine eigene Update-Methode schreiben, die kein MonoBehaviour braucht, wobei es für simple Fälle meiner Meinung nach entspannter ist, einfach ein Gameobject zu bauen. Nachdem all das gesagt ist, finde ich es aber wichtig, zu erwähnen, dass das nicht heißt, dass du ab jetzt MonoBehaviour meiden solltest, solange etwas nicht sichtbar ist oder so. Ein GameObject definiert ein Stück weit, wie eine Szene funktioniert. Und das darfst du auch genau so nutzen. So als Beispiel: Ich habe ein Beispiel-Projekt für Soda (ein Asset-Store-Paket von mir) gebaut. Als Hinweis: Gerne anschauen, ich benutze es auch als Best Practices-Showcase, aber ohne Soda wird es nicht kompilieren. Dort habe ich jedenfalls ein Musik-System. In diesem System benutze ich das [RuntimeInitializeOnLoadMethod]-Attribut, um direkt bei Spielstart ein GameObject zu erzeugen. Dieses mache ich mit HideFlags unsichtbar, damit es im Editor nicht weiter auffällt, und mit DontDestroyOnLoad mache ich es "unverwundbar" gegen Szenenwechsel. Damit habe ich ein (wenn man nicht danach sucht...) unsichtbares GameObject, das von Anfang bis Ende der Laufzeit existiert. Diesem GameObject gebe ich eine AudioSource, das die Hintergrundmusik abspielt. Nun habe ich aber auch eine Komponente, die einen AudioClip referenzieren kann und die sich, solange sie enabled ist, in eine Liste einträgt. Wann immer sich eine solche Komponente einträgt, wird der AudioClip des letzten Elements der Liste von dieser AudioSource abgespielt. Wenn ich also eine neue Szene mache und da ist nichts drin, dann habe ich da Musik laufen, sofern der Player da etwas brauchbares in der Liste hat. Und damit komme ich endlich zu meinem Punkt: Wenn ich eine Musik-Komponente in meine leere Szene packe, dann habe ich eine leere Szene, die ihre eigene Musik vom globalen Musik-Player abspielen lässt. Diese Komponente ist für den Spieler unsichtbar, sie stellt keine unsichtbare Wand dar, gar nichts. Und trotzdem hat sie, in der Szene und als MonoBehaviour, ihre Daseinsberechtigung, weil sie dazu beiträgt, zu definieren, was genau in dieser Szene passiert. Es ist also völlig okay, eine MonoBehaviour-Klasse zu schreiben, um irgendwelche Dinge zu initialisieren oder um Daten einzutragen, die in der Szene relevant sein können. Es ist aber trotzdem sehr empfehlenswert, sich von der Idee zu lösen, die Unity einem initial leider vermittelt: Dass alles irgendwie in MonoBehaviours passieren muss. -
Moin! Ein bisschen mehr Info wäre noch gut. Hast du da normale Collider2D, durch die sich dein Character mit einem Rigidbody2D bewegt? Oder wie funktioniert das bei dir? Ansonsten ist das mit der Distanz schon schlau. Entweder, du bewegst an irgendeiner Stelle deines Codes dein Objekt, oder du setzt die Geschwindigkeit und es bewegt sich selbst. Deshalb auch die Nachfrage danach, wie du das implementiert hast. Bei ersterem Fall kannst du deinen Bewegungsvektor genau dafür benutzen. Beim anderen Fall, z.B. bei einem Rigidbody, musst du dir immer die letzte Position speichern und dann nach der Bewegung von der neuen Position abziehen, um den Bewegungsvektor zu erhalten.
-
Meinst du jetzt TMP? Das ist halt meiner Erfahrung nach keine Geschmackssache, sondern der Unterschied zwischen "funktioniert" und "funktioniert nicht", je nach Situation.
-
Ohne jetzt viel darüber nachzudenken, direkt einmal der Hinweis: Bei TextMeshPro immer als Komponententyp TMP_Blub nehmen. Wenn's nicht mit "TMP_" anfängt, kann es immer Probleme geben. Hier also statt TextMeshProUGUI bitte "TMP_Text" nehmen. @gombolo Nö, oben steht ja "using TMPro;", damit geht's auch.
-
Abhängigkeiten zwischen Gameobjects sauber lösen
Sascha replied to Damon93's topic in Allgemeine Hilfe
Naja, "sichtbar" ist eine der Arten, in der man in einer Szene eine Rolle erfüllen kann. Eine unsichtbare Wand, um den Spieler daran zu hindern irgendwo lang zu hüpfen, oder eine AudioSource, aus der Windgeräusche kommen, dürfen genauso in die Szene. Aber davon abgesehen: Genau. Nö. Ein Objekt gehört niemals einem anderen Objekt. Wir lassen Structs da gerade mal außen vor, aber Objekte sind auf dem "Heap", einer Datenstruktur, die dein Spiel im Arbeitsspeicher anlegt. Und die ist genau so organisiert, wie der Name suggeriert: Es ist ein "Haufen". Da sind einfach alle Objekte draufgeschmissen und fertig. Jeder kann dein Objekt referenzieren, jeder kann damit arbeiten. Das einzige, was hier einschränkend wirkt, ist die Kontrolle über die Referenz. Wenn irgendein Code nicht weiß, an welcher Speicheradresse dein Objekt ist, dann kann er es auch nicht finden. Sobald du die Referenz aber hast, kann dein Code alles mit dem Objekt tun, was man eben so damit tun kann. Auch, wenn das Objekt, dass dein Einwohner-Objekt erzeugt hat, aufhört zu existieren, ist das egal. So eine Art Verbindung zwischen einem Objekt und wo es herkommt, oder überall schonmal benutzt wurde, gibt es einfach nicht. -
Abhängigkeiten zwischen Gameobjects sauber lösen
Sascha replied to Damon93's topic in Allgemeine Hilfe
Nö. GameObjects erlauben dir, Dinge in deiner Szene darzustellen. Ein GameObject brauchst du daher nur, wenn du einen Akteur in deiner Szene haben willst, der Einfluss auf Rendering (also "ist sichtbar" oder Lichtquelle oder so), Physik (Collider oder sogar etwas das sich bewegt) oder Sound haben soll. Deine Beschreibung klingt sehr stark danach, als wäre das nicht der Fall. Deine Einwohner sind nur Zahlen auf dem Papier, und können dem entsprechend auch abstrakt in deinen Daten dargestellt werden. Wie ich oben meinte, vermute ich, dass du nicht nur kein GameObject, sondern vermutlich nicht einmal ein (normales) Objekt pro Einwohner brauchst. Denk an Cookie Clicker: Da hast du sicherlich auch nicht Milliarden von Keks-Objekten. Und wäre es in Unity implementiert, hättest du schon gar nicht Milliarden GameObjects. Falls du ungefähr weißt, wie Borderlands funktioniert: Wäre das in Unity implementiert, wären Waffen, die auf dem Boden liegen, GameObjects. Dein Inventar allerdings, in dem du ein paar dutzend von den Dingern hast, wären normale Objekte, in denen die Daten der jeweiligen Waffe stehen. Ich nehme an, dass @chrische5 dasselbe sagen wollte: "Nur, wenn es eine Repräsentation in der Spielwelt gibt, ...". Ich denke, die List<Worker> kann funktionieren, aber du könntest es meiner Meinung nach noch viel sparsamer mit int workerCount implementieren. Jein. Die Dinger sind unheimlich wichtig, um etwas sauberes zu bauen, aber deshalb nicht überall sinnvoll. Wie gesagt: Sie sind dafür da, Daten für das Spiel im Editor zu definieren. Aber halt ohne das in einer Szene oder einem Prefab zu tun. -
Abhängigkeiten zwischen Gameobjects sauber lösen
Sascha replied to Damon93's topic in Allgemeine Hilfe
ScriptableObjects sind dafür da, um etwas im Editor zu definieren, das dann im Spiel landet. Wenn du irgendetwas hast, das nur zur Laufzeit existiert, dann bringt es dir keine Vorteile, dieses Ding von einem SO repräsentieren zu lassen. ScriptableObjects zur Laufzeit zu erzeugen, anstatt sie im Editor anzulegen, ist ein recht klares Zeichen dafür, dass was nicht ganz rund läuft. Nimm stattdessen einfach ganz normale Objekte. ScriptableObjects nehmen einfach nur mehr Arbeitsspeicher weg. Wobei du auch nicht unbedingt für jeden Einwohner gleich ein ganzes Objekt brauchst. Wenn du einfach pro Firma x Angestellte hast, dann kann es auch reichen, ein int in das Firma-Objekt zu packen. Dann wird die Berechnung irgendwelcher Kosten auch viel harmloser. Individuelle Sachen wie Namen kannst du dann immer noch prozedural generieren, ohne sie zu speichern. Davon abgesehen... Ist jeder angestellte ein GameObject? Da hast du mit 10.000 Stück sowieso eigentlich direkt verloren. Für so eine Menge von Akteuren brauchst du ECS. Wenn es aber nur um simple Datenobjekte geht, dann kannst du durchaus ein paar Tausend Stück haben... und die auch in eine Liste tun. Aber wie gesagt, das muss vielleicht gar nicht unbedingt sein. Zum Thema Events: Events sind dazu da, dass der Auslöser und der Reagierende sich nicht kennen. Wenn der Spieler stirbt löst er ein Event aus, und als Reaktion erscheint ein Game-Over-Bildschirm. Dass das Spieler-Leben-System und das Game-Over-Bildschirm-System sich nicht kennen, ist eine gute Idee. In deinem Fall aber ist es durchaus logisch, dass die Firma ihre angestellten kennt. Wie @chrische5 also schon sagt: Da kann die Firma einfach über die Liste iterieren, die sie sowieso schon rumliegen hat. Und eine Liste von Firmen, über die man iteriert, um über die Gehaltskürzung zu informieren, klingt auch erst mal nicht falsch. -
Moin, ein "Parser" ist ein komplett abstraktes Konzept. Es ist einfach nur ein Ding, dass Text anschaut und versucht, diesen so umzuwandeln, dass das Programm damit etwas anfangen kann. Das kannst du implementieren, wie du willst. Ein simpler Ansatz wäre z.B. so: public void Parse(string input) { if (IsCommand(input, "go to", out var place)) { GoTo(place); return; } if (IsCommand(input, "go", out var direction)) { GoTo(direction); return; } } private void GoTo(string place) { // ... } private void Go(string direction) { // ... } private bool IsCommand(string input, string command, out string parameter) { parameter = null; if (input == command) { return true; } if (input.StartsWith(command + " ")) { parameter = input.Substring(command.Length + 1); return true; } return false; } IsCommand schaut, ob der gegebene Input gleich dem gegebenen Kommando ist. Wenn nicht, wird noch geschaut, ob der Input mit dem Kommando anfängt, danach aber noch etwas kommt. In beiden Fällen wird true zurück gegeben, und in letzterem noch alles, was nach dem Kommando im Input steht. Parse geht jetzt mithilfe dieser Methode durch eine Reihe von Kommandos und der erste, der erkannt wird, kriegt seine entsprechende Methode ausgeführt. Jetzt brauchst du noch irgendwelche Datenstrukturen für deine Welt. Schließlich macht dasselbe Kommando unterschiedliche Dinge, wenn man woanders ist oder irgendwelche anderen Bedingungen sich geändert haben. Die Methoden da oben müssen also je nach Situation andere Ergebnisse haben. Es gehört also noch ein bisschen mehr dazu als das Verstehen von Eingaben, aber da es ja um einen Parser ging, entmystifiziert das Beispiel da oben das Thema hoffentlich. Man kann das bestimmt noch viel schöner machen, z.B. mit einem Dictionary - aber funktionieren würde es auch so schon.
-
Moin! Die "fakeAnswers"-Variable wird vermutlich null als Wert haben, also keine Liste referenzieren. Du musst irgendwo mit so etwas wie fakeAnswers = new List<int>(); ein neues List-Objekt erstellen und die Referenz der Variable zuweisen. P.S.: Um's anderen weniger schwer zu machen, bitte statt "eine Fehlermeldung" immer die Fehlermeldung posten. Der Inhalt ist ja nicht gerade unwichtig
-
Moin, du packst sie einfach als Child-Objekte in ein GameObject mit einer VerticalLayoutGroup.
-
Was du suchst, ist das Kreuzprodukt von Vector3.up und dem Vektor vom CC zum Kreismittelpunkt. Ein Kreuzprodukt zweier Vektoren ist der Vektor, der senkrecht zu beiden Vektoren ist. Das wäre dann (ausgehend davon, dass dein Kreis horizontal ist) die Tangente des Kreises. var lookDirection = Vector3.Cross(Vector3.up, directionToCenter); characterController.transform.rotation = Quaternion.LookDirection(lookDirection); Wenn der Vektor in die falsche Richtung zeigen sollte, Parameter-Reihenfolge vertauschen oder bei einem von beiden ein Minus davor klatschen
-
Öh joa, kann man so machen. Du kannst aber auch einfach direkt den CC drehen, ohne den Leader zu involvieren. Aber wenn du den ganzen mehrstufigen Drehkram in eine Methode auslagerst, dann ist das schon okay so
-
Wozu braucht denn der Leader eine bestimmte Rotation? Aber davon abgesehen ist das ne gute Lösung, wenn in die Mitte gucken okay für dich ist.