Jump to content
Unity Insider Forum

Sascha

Administrators
  • Posts

    13,503
  • Joined

  • Last visited

  • Days Won

    766

Sascha last won the day on March 30

Sascha had the most liked content!

About Sascha

  • Birthday 08/13/1990

Contact Methods

  • Website URL
    http://13pixels.de

Profile Information

  • Gender
    Male
  • Location
    Hamburg
  • Interests
    Programmierung

Recent Profile Visitors

103,674 profile views

Sascha's Achievements

Advanced Member

Advanced Member (3/3)

2.7k

Reputation

  1. 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.
  2. 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.
  3. 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.
  4. An dieser Aussage hat sich für mich jetzt leider nicht wirklich was geändert...
  5. 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.
  6. 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?
  7. 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
  8. 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); }
  9. Ö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.
  10. 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.
  11. 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.
  12. 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.
  13. 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.
  14. 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.
  15. Meinst du jetzt TMP? Das ist halt meiner Erfahrung nach keine Geschmackssache, sondern der Unterschied zwischen "funktioniert" und "funktioniert nicht", je nach Situation.
×
×
  • Create New...