• Announcements

    • Lars

      Allgemeine Forenregeln   03/13/2017

      Forenregeln Nimm dir bitte einen Moment um die nachfolgenden Regeln durchzulesen. Wenn du diese Regeln akzeptierst und die Registration fortsetzen willst, klick einfach auf den "Mit der Registrierung fortfahren"-Button. Um diese Registration abzubrechen, klick bitte einfach auf den "Zurück" Button deines Browsers. Wir garantieren nicht für die Richtigkeit, Vollständigkeit und Brauchbarkeit der Nachrichten und sind auch nicht dafür verantwortlich. Die Beiträge drücken die Meinung des Autors des Beitrags aus, nicht zwangsläufig das, wofür die Forensoftware steht. Jeder Nutzer, der denkt, dass ein veröffentlichter Beitrag unzulässig bzw. störend ist, ist aufgefordert uns unverzüglich per E-Mail zu kontaktieren. Wir haben das Recht störende Beiträge zu löschen und bemühen uns, das in einem realistischem Zeitraum zu erledigen (sofern wir beschlossen haben, dass die Löschung notwendig ist). Du akzeptierst, durchgehend während der Nutzung dieses Services, dass du dieses Forum nicht dazu missbrauchen wirst, Inhalte zu veröffentlichen, welche bewusst falsch und/oder verleumderisch, ungenau, beleidigend, vulgär, hasserfüllt, belästigend, obszön, sexuell belästigend, bedrohlich, die Privatsphäre einer Person verletzend oder in irgend einer Art und Weise das Gesetz verletzen. Des Weiteren akzeptierst du, dass du keine urheberrechtlich geschützte Inhalte ohne Erlaubnis des Besitzers in diesem Forum veröffentlichst. Mit dem Klick auf den "Mit der Registrierung fortfahren"-Button, akzeptierst du zudem unsere Datenschutzerklärung und stimmst der Speicherung deiner IP-Adresse und personenbezogenen Daten zu, die dafür benötigt werden, um dich im Falle einer rechtswidrigen Tat zurückverfolgen zu können bzw. permanent oder temporär aus dem Forum ausschließen zu können. Es besteht keine Pflicht zur Abgabe der Einwilligung, dies erfolgt alles auf freiwilliger Basis.   Zusatzinformationen Der Forenbetreiber hat das Recht, Nutzer ohne Angabe von Gründen permanent aus dem Forum auszuschließen. Des Weiteren hat er das Recht, Beiträge, Dateianhänge, Umfrage, Blogeinträge, Galleriebilder oder Signaturen ohne Angabe von Gründen zu entfernen. Mit der Registrierung verzichtest du auf alle Rechte an den von dir erstellten Inhalten, bzw. treten diese an das Unity-Insider.de und Unity-Community.de ab. Dies bedeutet im Klartext, dass das Unity-Insider.de und Unity-Community.de frei über deine Texte verfügen kann, sofern diese nicht wiederum die Rechte anderer verletzen. Es besteht weiterhin kein Anspruch von registrierten Nutzern bzw. ehemaligen registrierten Nutzern darauf, dass erstellte Inhalte und/oder die Mitgliedschaft (User) wieder gelöscht werden (Erhaltung der Konsistenz dieses Forums).   Einwilligungserklärung Wenn du mit der Speicherung deiner personenbezogenen Daten sowie den vorstehenden Regeln und Bestimmungen einverstanden bist, kannst du mit einem Klick auf den Mit der Registrierung fortfahren-Button unten fortfahren. Ansonsten drücke bitte Zurück. Stand: 07.03.2011

Leaderboard


Popular Content

Showing most liked content since 03/28/2017 in all areas

  1. 5 likes
    Sodele. Jetzt endlich kann ich mal wieder was zeigen. Mein neuer Flipper ist gut im werden und dieses Mal geht es um das Thema Carnival. Ist nicht viel mehr als ein Ausblick auf das was kommen wird. Schaut:
  2. 4 likes
    Hallo miteinander, die Tage überkam mich die Idee doch mal ein kleines Game für zwischendurch über meine Hauskatzen "Paul" und "Romy" zu machen. So entstand die Idee zu "Pauls Adventure" - Auf der Suche nach seiner Schwester "Romy" :-) Hier mal ein erstes kleines Konzeptlevel um verschiedene Gemaplaymechaniken auszuprobieren. Steuerung: ========= Moving: WASD or GamePad Run: Shift (left) Shoot: Strg (left) Jump: Space Geplant sind insg. 4 Welten (Wald, Höhle, Wasser, Himmel) a 4 kleiner netter "Jumpnrun" Levels. (frühe Planung). Freue mich über konstruktive Ideen/Kritik. Gruß Download: http://www.filedropper.com/paulsadventure003
  3. 4 likes
    C# ist schon seit langer Zeit das Mittel der Wahl für Programmieren für Unity. Der Support für Boo wurde gänzlich abgeschafft, und Unitys JavaScript-Variante beschert einem mehr Probleme als Vorteile. Leider stellt C# eine etwas größere Einstiegshürde dar als "UnityScript", aber nach eventuellen anfänglichen Scherereien wird es immer einfacher zu verwenden. Dieses Tutorial richtet sich an reine Programmieranfänger, aber auch Erfahrenere, die neu in Unity sind, können etwas davon haben. Dieses Tutorial ist weniger ein "Wie mache ich XY", als mehr eine generelle Einführung, die alle folgenden Schritte in der Welt von C# in Unity (besser) verstehbar machen soll. Am Ende wird also kein großartiges Ergebnis zum Zeigen vorliegen, aber dafür solltest du ein gutes Verständnis dafür haben, was in Unity überhaupt passiert. Mein Code als Komponente Wer dieses Tutorial liest, hat vermutlich den grundlegenden Workflow des Unity-Editors bereits vor Augen gehabt. Objekte in einer Szene (GameObjects) bestehen aus einer Reihe von Komponenten, die das GameObject ausmachen. So ist ein GameObject mit einem Collider etwas, wogegen andere Objekte prallen können, und eines mit MeshRenderer und MeshFilter ist ein sichtbares 3D-Modell. Folglich ist ein GameObject mit MeshRenderer, MeshFilter und Collider ein sichtbares 3D-Objekt, mit dem man kollidieren kann. Ein GameObject ist also die Summe aller seiner Komponenten. Für jedes Spiel muss das, was passieren soll, programmiert werden. Im Unity-Kontext kommt der Code, den man dabei schreibt, in Form von Komponenten zum Einsatz. Man schreibt also etwas Code in eine Datei und fügt diesen als Komponente zu einem GameObject hinzu. Diese Scripts sind größtenteils dazu da, das GameObject, dem sie hinzugefügt werden, zu beeinflussen. Ein Beispiel wäre ein Script, das das Objekt rotieren lässt. Man nehme also einen MeshRenderer, einen MeshFilter, einen Collider und dazu dieses Script als Komponente. Das ergebnis ist ein sichtbares 3D-Objekt, mit dem man kollidieren kann und das sich dreht. Diese Grundidee, in welcher Form ein Script das Spiel, das man baut, formt und beeinflusst, ist immer im Hinterkopf zu behalten. Mein erstes Script Um ein neues Script zu erstellen, einfach mit Rechts in die Assets (oder oben links aus "Assets") klicken, dann auf "Create", dann "C# Script". Die entstandene Script-Datei kann und sollte man direkt vernünftig benennen. Dabei ist es nicht unsinnvoll, sich an Unitys Namensgebung zu orientieren, denn der Dateiname wird gleichzeitig der Name der Komponente. Neben "MeshRenderer", "Collider" und "Light" würde also "SimpleRotation" oder "SimpleRotator" passen. Öffnet man das Script, wird der eingestellte Script-Editor gestartet. Zu empfehlen ist Visual Studio, aber hier kann man nach eigenen Präferenzen gehen. Was man dann sieht, ist in etwa folgendes: using UnityEngine; using System.Collections; public class ScriptName : MonoBehaviour { // Use this for initialisation void Start() { } // Update is called once per frame void Update() { } } Sehen wir uns die Einzelteile davon einmal an, damit wir anfangen können, darin zu arbeiten. Am Anfang stehen diese beiden Zeilen: using UnityEngine; using System.Collections; Diese Zeilen heißen "Imports" oder "Using-Statements". Sie geben für den Rest des Codes an, was alles an bereits existierendem Code bekannt sein soll. Unity ist ein riesiger Haufen Code, den unser Code kennen muss, damit man damit arbeiten kann. Die Light-Komponente z.B. ist irgendwo in "UnityEngine" vorhanden, und wenn man von einer solchen Komponente die Farbe ändern will, dann muss bekannt sein, was ein Licht überhaupt ist. Es ist also etwa so, als würde ein Schullehrer ein neues Thema anfangen und zum Beginn der Stunde einige vorherige Themen in's Gedächtnis rufen, auf denen der neue Stoff aufbaut. System.Collections ist standardmäßig mitimportiert, wird aber erst einmal gar nicht gebraucht. Diese Zeile kann man sogar problemlos löschen, bevor man weiter macht. Als nächstes folget ein Haufen Begriffe, gefolgt von einem Rumpf. Einen Rumpf erkennt man immer an den geschweiften Klammern { } . public class ScriptName : MonoBehaviour { // Das hier ist im Rumpf } Der C#-Standard ist eigentlich, die geschweifte Klammer in die nächste Zeile zu machen. Ist absolut Geschmackssache - bei mir sieht das jedenfalls so aus: public class ScriptName : MonoBehaviour { // Das hier ist im Rumpf } Wir sehen hier also etwas Text, gefolgt von einem Rumpf. In den allermeisten Fällen ist das, was vor dem Rumpf steht, mit dem Rumpf verbunden, oder besser: Der Rumpf gehört zu diesem Text. Was also sagt dieser Text aus? public class ScriptName : MonoBehaviour Zuerst einmal steht da "public class". Das public können wir jetzt erstmal ignorieren - es steht da und macht irgendetwas. "class" ist da schon wichtiger - es sagt aus, dass im folgenden Rumpf eine so genannte Klasse deklariert wird. Eine Klasse ist ein Begriff aus der Objektorientierten Programmierung und ist eine Art Bauplan für Objekte. Der Bildschirm vor dir ist sozusagen ein Objekt (oder "Instanz" oder "Exemplar") der Klasse "Bildschirm". Oder vielleicht der Klasse "Flachbildschirm", je nach dem, wie spezifisch da deklariert wurde. In unserem aktuellen Spezialfall ist ein Objekt eine Komponente. Man kann mehreren GameObjects eine Light-Komponente geben, und sie sind alle mehr oder weniger gleich. Sie alle haben eine Farbe, eine Intensität und mehr Eigenschaften, und sie alle machen auf irgendeine Weise Licht in der Szene. Und doch können sie alle unterschiedlich sein: Das eine Licht ist rot, das andere blau. Es handelt sich hierbei um verschiedene Objekte, aber der grundlegende Aufbaum, also die Klasse, ist immer dieselbe: Light. Wann immer wir in einer objektorienterten Sprache wie C# Code schreiben, existiert dieser in Form von Klassen. Jedes Script ist eine Klasse und jedes Mal, wenn wir das Script zu einem GameObject hinzufügen, wird ein neues Objekt, also eine neue Komponente dieser Klasse erstellt, die dann auf diesem GameObject existiert, bis sie oder das GameObject wieder gelöscht werden. Der nächste Begriff kommt uns bekannt vor: Es ist der Dateiname und damit der Name der Klasse, die wir hier schreiben wollen. Anschließend steht da noch etwas kryptisches: : MonoBehaviour Hier steckt eine ganze Menge drin, aber zu diesem Zeitpunkt kann man diesen Teil vereinfachen und sagen: Dieser Ausdruck sorgt dafür, dass Objekte unserer Klasse auch wirklich Komponenten sind. Löscht man diesen Teil, kann man das Script anschließend nicht mehr zu einem GameObject hinzufügen. Damit haben wir jetzt die Klassendefinition gesehen. Der darauffolgende Rumpf wird alle möglichen Informationen über unsere Klasse enthalten, so wie der Rumpf der Light-Klasse irgendwo in UnityEngine den Code enthält, der Objekte anleuchtet und der angibt, dass eine Lichtkomponente eine Farbeigenschaft hat. Im Rumpf stehen bereits irgendwelche Dinge, die wir uns gleich ansehen werden. Zuerst einmal gehen wir aber zurück in den Editor und sehen uns eine wichtige Kleinigkeit an - und zwar, wie man ein Script einem GameObject hinzufügt. Das ist denkbar einfach: Per Drag & Drop kann man die Datei auf den leeren Platz eines GameObjects im Inspektor ziehen; unter die anderen Komponenten. Man kann sie auch auf das GameObject in der Hierarchie ziehen oder den "Add Component"-Button im Inspektor benutzen. Scripts sind standardmäßig in der Kategorie "Scripts". Methoden Zwei Absätze weiter oben habe ich zwei Beispiele dafür gegeben, was in einer Klasse drinsteht: Die Light-Komponente leuchtet Objekte an, sie tut etwas, und sie hat eine Fabreigenschaft, sie ist irgendwie. Diese beiden Dinge, "wie es ist" und "was es tut" sind genau die beiden Bausteine, aus denen eine Klasse aufgebaut ist. Fangen wir an mit "was es tut". Die Abläufe dessen, was ein Objekt tut, sind in so genannten Methoden deklariert. Von denen hat unser neues Script bereits zwei: // Use this for initialisation void Start() { } // Update is called once per frame void Update() { } Eine leere Methode ist nicht ganz unähnlich einer leeren Klasse aufgebaut. Wieder haben wir einnige Worte, gefolgt von einem Rumpf. Diese beiden Methoden heißen Start und Update. Bei beiden steht vorher "void" und beide haben danach runde Klammern ohne etwas darin. Beides wird für uns erst später relevant und kann daher ignoriert werden. Interessant sind für uns die Namen der Methoden, aber dazu gleich mehr. Eine Methode ist grundätzlich eine Reihe von Anweisungen, die nacheinander ausgeführt werden, wenn die Methode aufgerufen wird. Eine Methode aufzurufen ist verlgleichbar damit, einen Knopf bei einem Gerät zu drücken. Plötzlich passieren im Gerät irgendwelche Dinge, und vielleicht kommt sogar ein sichtbares Ergebnis heraus, zum Beispiel eine Flasche Wasser. Allem voran steht aber das Drücken des Knopfes, oder eben das Aufrufen der Methode. Um eine Methode aufzurufen, ist es sozusagen erst einmal nur nötig, ihren Namen zu schreiben. Das testen wir gleich, aber vorher sehen wir uns die beiden Methoden an, die wir schon haben. Über den Methoden stehen so genannte Kommentare. Alles, was rechts von "//" steht, ist ein Kommentar und wird vom Computer komplett ignoriert. Man kann dort also alles hinschreiben, was den Code erklärt, wenn gewünscht auch auf deutsch. Die beiden Kommentare über unseren Methoden beschreiben eine Besonderheit, wenn man mit Unity entwickelt. Anhand ihrer Namen nimmt sich Unity das Recht, diese Methoden geradezu magisch im richtigen Moment aufzurufen. Eine Methode mit dem Namen "Start" wird einmalig aufgerufen, wenn das Objekt neu entsteht - das versteht sich inklusive aller Objekte zum Start des Spiels. Innerhalb eines Methodenrumpfs können jetzt unter anderem weitere Methoden aufgerufen werden. So entsteht eine sinnvolle Kette von Abläufen. Die verschiedenen Objekte werden hierbei wie eine Firmenhierarchie: Ein Kunde ruft in einer Firma an und bestellt eine Dienstleistung. Die Sekretärin informiert die Chefin. Die Chefin schickt einen Mitarbeiter los. Jede dieser Interaktionen ist eine mögliche Metapher für jeweils einen Methodenaufruf, und die verscheidenen Leute sind die Objekte. Wir bauen jetzt eine ganz simple Kette und fügen folgenden Code in die Start-Methode ein: // Use this for initialisation void Start() { Debug.Log("Hallo!"); } Debug.Log ist eine bestimmte Methode. Warum sie so heißt, wie sie heißt und wieso da ein Punkt drinsteckt, ist erst einmal egal. Die runden Klammern deuten den Methodenaufruf an. In den Klammern steht dieses Mal allerdings etwas. In den Anführungszeichen steht ein String, eine Zeichenkette, also etwas, das Worte, Sätze oder ganze Texte beinhalten kann. Debug.Log nimmt diesen String und schreibt ihn in Unitys Konsole. Am Ende jeder Anweisung wie dieser steht ein Semikolon ; , das die Anweisung beendet. Speichere das Script und ab zurück zu Unity. Stelle sicher, dass irgendein Objekt in der Szene das Script als Komponente hat und starte das Spiel. Du müsstest in der Konsole (Strg + Shift + C) und ganz unten links im Editor das "Hallo!" sehen können. Hier hat also Unity Start() aufgerufen und in Start wurde dann wieder Debug.Log aufgerufen. Der String in den Klammern nennt sich dabei Parameter. Ein Parameter spezifiziert genauer, was eine Methode tun soll. Während bei einigen Tätigkeiten keine Parameter nötig sind (z.B. "starte das Auto"), benötigen einige andere genauere Angaben, wie sie im jeweiligen Moment zu funktionieren haben (z.B. "Tritt das Gaspedal" - aber wie doll?). Bei Debug.Log muss angegeben werden, was genau in die Konsole geschrieben werden soll. Würde man der Methode nichts geben, also die runden Klammern leer lassen, würde der ganze Aufruf keinen Sinn mehr ergeben und der Code wird als fehlerhaft erkannt. Jetzt machen wir aber mal etwas Unity-bezogenes. Das Drehskript Wir bauen jetzt ein Skript, das das Objekt dreht, auf dem es als Komponente liegt. Zuerst machen wir das in der Start-Methode. Unser Objekt wird also einmalig zum Spielstart auf eine bestimmte Drehung gesetzt und bleibt dann so. Wenn es um Position oder Rotation geht, ist immer die Transform-Komponente wichtig. Sie ist immer ganz oben in der Komponentenliste und beim Verschieben oder Drehen eines Objekts geht es immer um ihre Felder. Sie hat außerdem die Besonderheit, dass jedes GameObject genau eine davon hat. Die Transform-Komponente kann man ansprechen, indem man einfach transform schreibt: void Start() { transform.Rotate(20, 20, 20); } Woah, jetzt aber mal langsam! Wir sehen hier wieder einen Methodenaufruf, zu erkennen an den Klammern nach einem Methodennamen (Rotate). Doch vor dem Methodennamen steht jetzt transform und dazwischen ein Punkt! Der Punkt ist extrem wichtig. Er erlaubt es, Methoden von anderen Objekten aufzurufen. Die Methode Rotate ist Teil der Klasse "Transform", also der Klasse, die als Bauplan für die Transform-Komponenten dient, die auf den GameObjects liegen. Um nun ein bestimmtes Objekt zu drehen, muss man angeben, welches Objekt das überhaupt ist. Beachte hierbei, dass ich nicht einmal von GameObjects rede, sondern von Objekten im programmiertechnischen Sinne. Die Transform-Komponente unseres GameObjects ist ein solches Objekt. Mit dem Punkt vereint man nun zwei Dinge: Zur linken steht das Objekt, von dem man etwas will, und rechts steht der Name der Methode, also das, was man vom Objekt will. In diesem Fall also: Ich will, dass sich unsere Transform-Komponente dreht. In den Klammern sind dieses Mal gleich drei Parameter. Diese entsprechen den drei Achsen, um die ein Objekt gedreht werden kann. Der Reihenfolge nach X, Y, und Z. Dieses Skript kannst du jetzt ausprobieren und nach Belieben die Zahlen ändern. Als nächstes wollen wir, dass sich das Objekt durchgehend immer weiter dreht, womit wir dem Ende dieses ersten Einstiegs nahe kommen. Wie in den Kommentaren im Code angedeutet, wird Update immer und immer wieder aufgerufen. Genauer: Es werden alle Update-Methoden von allen Komponenten auf allen GameObjects in der Szene aufgerufen, und danach wird das Bild neu gemalt. Der Ablauf ist eigentlich etwas komplexer, aber das ist das grundlegende Prinzip. Update findet auf jedem Objekt also genau so oft statt wie das Bild neu gezeichnet wird, also abhängig von den FPS des Spiels. Verschiebe nun einfach die Zeile mit transform.Rotate von Start() nach Update(), also in dessen Rumpf, speichere das Script und teste das Spiel. Dein Objekt dreht sich nun immer weiter, weil es in jedem Frame, also in jedem Update ein bisschen weiter gedreht wird. Aber warte mal... wenn das Spiel jetzt doppelt so viele FPS hast, dann wird Update doppelt so oft aufgerufen! Und wenn es sich doppelt so oft gleich weit dreht, dann dreht es sich doppelt so schnell! Hierfür gibt es Abhilfe: Time.deltaTime. Wie an den nicht vorhandenen Klammern zu erkennen, handelt es sich hier nicht um einen Methodenaufruf, sondern um eine so genannte Eigenschaft. Eine Eigenschaft stellt einen einzelnen Wert dar. Ein Beispiel dafür wäre wieder die Farbe einer Light-Komponente. Time.deltaTime allerdings gehört zu keinem Objekt - wie das kommt, ist allerdings etwas für ein anderes Kapitel. Time.deltaTime hat als Wert nicht etwa rot oder blau, sondern einen Zahlenwert; genauer: Die Zeit seit dem letzten Frame in Sekunden, inklusive Nachkommastellen. Diese Zahl ist also halb so groß, wenn wir doppelt so viele FPS haben, und umgekehrt. Wenn wir Time.deltaTime mit einer beliebigen Zahl multiplizieren, so bedeuetet das quasi "pro Sekunde". Wenn wir wegen hoher FPS doppelt so oft drehen, aber dafür jedes Mal nur halb so weit, dann haben wir eine konstante Rotation unabhängig von der Framerate. Multiplikation funktioniert, wie zu erwarten, mit dem *-Operator: void Update() { transform.Rotate(0, 20 * Time.deltaTime, 0); } Von hier aus gibt es noch jede Menge zu erzählen, bevor man so richtig loslegen kann. Ich hoffe allerdings, dass dieser Text hilft, zu verstehen, was man da genau schreibt, wenn man C# codet, und wie die Begriffe richtig heißen. Mal sehen, ob ich die Zeit finde, von hier aus kleinere Tutorials zu schreiben, die auf diesem aufbauen. Bis dahin kannst du hoffentlich andere Tutorials mit diesem hier kombinieren, um weiter zu kommen. Auf lange Sicht soll dieser Text und die darauf folgenden aber unsere JS-Tutorials ersetzen. Bis dahin!
  4. 4 likes
    und weiter gehts .. hier sieht man den Startbereich in dem man seine Waffen und Rüstungen einkaufen kann und alles mögliche ..Glücksspiel .. Karten legen lassen Freischalten von allerlei Stuff. damit man gerüstet ist für den Kampf
  5. 3 likes
    Und weiter geht's! Bin momentan simultan an allen Dingen gleichzeitig dran. Aber es macht Spaß und wird immer besser. Hier mal ein kleiner Ausblick auf den neuen Flipper:
  6. 3 likes
    Hallo zusammen heute möchte ich euch das gerade in Entwicklung befindliche Tool Lidskialf vorstellen. Was hat der Name zu bedeuten? Lidskialf ist der Thron Odins, von dem er aus die 9 Welten überblickt, genau das soll einen das Tool auch ermöglichen. Man soll damit alle Spiel Welten die man „regiert“ kontrollieren können :-). Wie kam es zu der Idee? Die Idee entsprang dem Projekt an dem ich gerade Mitwirke (hier geht es zur Projekt Vorstellung). Da es wie für ein RPG üblich Quests und Unterhaltungen beinhalten sollte. Der Ablauf dieser beiden Teile sollte von dem Game Designer und Story Writtern erstellt werden. Damit diese nicht mit Kilometer langen Text Dateien oder gar kryptischen Coding Zeitverschwenden. Wollte ich es ihnen ermöglichen Quests und Unterhaltungen über einen Graphischen Editor zu erstellen. Der Quest Editor Was ist, das Ziel das ich damit versuche zu erreichen? Nachdem ich eine gewisse Zeit an dem Design des Tools gesessen hatte, keimte in mir die ehrgeizige Idee auf, dass das Tool nicht nur für die Quests und Unterhaltungen da ist, sondern für den Kern für den gesamten Ablauf im Spiel darstellt. Nachdem ich einige Zeit darüber nachgedacht hatte, kam ich zu dem Schluss dies auch anzugehen. Wie habe ich vor das umzusetzen? Das Grundprinzip ist relativ einfach jede Aktion, die in der Welt geschieht ist ein Knoten in einen Graphen. Am Anfang gibt es einen Welt-Graph in dem die verschiedenen Szenarien (hat an sich nichts mit Unity Scenes zu tun) enthalten sind. Jedes Szenario wiederum ist ein Graph. In diesem Graph kann man nun verschiedene Ereignis Knoten und Aktions knoten einfügen. So ist es möglich zu sagen das der Player automatisch beim Betreten eines Gebietes eine Quest zugewiesen bekommt. Dies ist jedoch nur ein Beispiel von vielen möglichen Anwendungen. Ob ein Knoten eine Aktion oder ein weiterer Graph mit unter Abläufen wird dabei dem Entwickler des Knotens überlassen. So ist eine Quest natürlich ein Knoten mit einen Untergraphen der verschiedene Ziel- und Belohnungsknoten enthält. Während der Knoten zum spawnen eines Items nur ein Aktions Knoten ohne eigenen Graphen ist. Ich denke das, damit das Prinzip wie das Tool funktioniert deutlich geworden ist. Der Unterhaltungs-Editor Nun kommen wir zum Technischen. Da ich ein großer Fan von Modularisierung bin, ist das Gesamte Tool durch Plugins erweiterbar. Das eigentliche Tool enthält damit nur die Graphen Logik sowie die Welt und die Szenarien Knoten. Der Rest wird über DLLs die sich in einen Plugin Ordner befinden dazu geladen. Einstiegspunkt für jedes Plugin sind dabei die Klassen die das Interface IGFTFeature implementieren. Ein Plugin besteht im Wesentlichen aus drei Teilen: einen Teil für das Tool (in .net 4.5) der die IGFTFeature Implementierung enthält einen Teil, der die Datenstruktur enthält (in .net 3.5) einen Teil der auf der Seite von Unity(oder eine andere Engine) für die Auswertung der Daten zuständig ist. Die Knoten werden als JSON und XML abgespeichert. Durch diese Aufgliederung soll das Tool nur auf die Komponenten zugeschnitten werden die der User benötigt. Auf Seiten von Unity können verschiedene MonoBehaviours sich auf das Starten und Abschließen der Knoten Typen registrieren und die Anweisung, die ein Knoten darstellt ausführen. So ist es möglich nach Abschluss eines Szenarios. Dem Spieler z. B. ein kleines Start Geld zu geben. Zum Zeitplan Da das gesamte Tool wird, Projekt getrieben entwickelt deswegen rechne ich nicht mit einer allzu frühen Veröffentlichung für die Breite Masse. Als Zeitpunkt für die Fertigstellung des Tools sowie Plugins die das Spiel benötigt peile ich Q4 2017 an. Heißt vor 2018, wird es vermutlich keine Veröffentlichung hier im Forum oder im Asset Store geben. Wie ihr schon früher mitmachen könnt? (Beta-Tester) Falls ihr jedoch von dem Konzept überzeugt seit und gerade ein Spiel entwickelt können wir uns gerne Unterhalten, ob es möglich ist, das ihr das Tool schon früher einsetzen bzw. Ideen für Erweiterungen beisteuern könnt. Ich hoffe, ich konnte euch meine Idee verständlich näher bringen. Falls noch Fragen bestehen freue ich mich diese mit euch hier im Forum oder per PN zu erörtern. Danke für lesen und viele Grüße Mabenan
  7. 3 likes
    Hier mal ein kleines Update: 25*25 KM terrain WIP. Die map besteht aus 20 einzelnen terrains die zusammen gefügt wurden- Außerdem habe ich die grafik ein wenig verbesssert. Liks: Vorher Rechts: Nachher Jabbas Palast WIP ! und noch ein Bild von der map (ungefähr in der mitte)
  8. 3 likes
    Mathrix ist ein Casual-Game, das während eines 24-stündigen Game Jams entstanden ist. Die Regeln sind einfach: Überlebe solange wie möglich! Um einen Gegner auszuschalten musst du die Rechenaufgabe über seinem Kopf lösen. Mit Hilfe deiner "Pistole" kannst du dem Gegner Schaden zufügen, aber nur das richtige Ergebnis erledigt ihn. Im Lauf befindet sich immer eine Kugel die 1, 2, 3, 5 oder 7 Schaden macht. Dieser Schaden wird entweder addiert (linke Maustaste) oder subtrahiert (rechte Maustaste). Man sei gewarnt: das Spiel startet direkt, es gibt keinen Willkommensbildschirm. Dafür war einfach keine Zeit mehr Link zum Spiel (Win-64bit): https://www.dropbox.com/s/k75c5558x94ui76/Mathrix Prototyp.zip?dl=0
  9. 3 likes
    Hi, mein erster Beitrag ist direkt mal ein Tutorial. Da ich keine Beispiele zu “Schießen mit Raycasts, mit beachtung von Projektilgeschwindigkeit in Unity”, also einer verzögerung von abfeuern bis Einschlag, gefunden habe, möchte ich heute ein kleines Tutorial hierzu vorstellen. Es richtet sich eher an Anfänger die schon ein wenig Erfahrung haben. Ich hoffe ich habe keine zu groben Fehler gemacht und bitte um Feedback. Vorwort Da ich mich momentan mit Shooter-Mechaniken beschäftige habe ich mich mit diesem Thema mal befasst. Um das Rad nicht neu zu erfinden, hab ich erst mal Google angeworfen um zu gucken wie “Schießen in Spielen” generell gemacht wird, bzw. obs da eventuell schon was fertiges gibt. FPS-Baukästen habe ich außen vor gelassen da ich dabei nichts über die Mechaniken lerne. Da für richtige Shooter stets von Prefab-Shooting abgeraten wurde, sollte es nun mit Raycasts gemacht werden. Leider gab es dazu keine oder nur schlechte Ratschläge im Netz. Die “beste” Idee die ich hierfür gefunden habe war: Abfeuern - die Distanz messen - anhand der Distanz die Flugzeit berechnen - nach ablauf der Zeit den Raycast machen. Von Code Samples keine Spur. Hätte ich aber sowieso nicht benutzt. Andere haben empty Objects mit einem Collider durch die gegend geschossen und meinten das is anders als Prefab-Shooting. Welche Probleme bei sowas auftreten können, sieht man in diversen Prefab-Raycast-Vergleichsvideos. Also habe ich mir selbst ein System überlegt. Vermutlich kann man das noch besser/anders machen. Aber es funktioniert. In diesem Beispiel verwende ich eine fixe Projektilgeschwindigkeit, folgend “bSpeed” genannt, und lasse physikalische Einwirkungen wie Schwerkraft erstmal außen vor. Eventuell kann ich das noch ergänzen wenn Bedarf besteht. Die Theorie In meinen Überlegungen sah das so aus: Ich habe eine Waffen in der Hand, welche Projektile abfeuert (welch Überraschung). Diese Projektile fliegen dann von der Position des Abfeuerns mit der Geschwindigkeit bSpeed/Sekunde in eine bestimmte Richtung. und zwar so lange bis sie etwas treffen oder sie für das Spiel unwichtig werden. Soweit so gut. Klingt gar nicht schwer. Wie Rechnet man jetzt das Ganze aus? Es folgt ein kleiner Ausflug in die Mathematik Da wir uns in einem dreidimensionalen Koordinatensystem befinden geht das am einfachsten mit Vektoren. Falls jemand nicht weiß was Vektoren sind und wie man mit ihnen rechnet, würde ich empfehlen an dieser Stelle dieses Tutorial zu pausieren und sich schnell Vektoren anzuschauen. Vektoren sind wirklich kein Hexenwerk. Ich werde aber die einzelnen Rechnungen schrittweise ausführen damit man gut folgen kann. Also rechnen wir am besten mal ein beispiel. Ich werde die Vektoren folgend ausschreiben: (x | y | z) Gegeben sind: Postion des Abfeuerns “pos”: (6 | 2 | 1) Richtung beim abfeuern “dir”: ( 1 | 2 | 1,5) bSpeed: 3 (Strecke die das Projektil pro Sekunde zurücklegt) Man könnte die Vektoren jetzt einfach addieren um das Projektil “nach vorne” zu bringen: neue Position = “pos” + “dir” neue Position = (6 | 2 | 1) + ( 1 | 2 | 1,5) neue Position = (6 + 1 | 2 + 2 | 1 + 1,5) neue Position = (7 | 4 | 2,5) Das Problem hierbei ist, dass der Betrag eines Vektors unabhängig von der Richtung ist. deshalb kann es in diesem Kontext zu falschen Ergebnissen kommen. Der Betrag eines Vektors ist im Prinzip nichts anderes als die Hypotenuse eines Dreiecks aus dem Satz des Pythagoras. Deshalb wird er genauso berechnet. Der Betrag ist in unserem Beispiel außerdem die Strecke die dieser Vektor, also auch unser Projektil pro Sekunde in die Richtung “dir” zurücklegen würde. Zur kontrolle berechnen wir den Betrag von “dir”, um zu sehen ob der Betrag unserer definierten Strecke entspricht: betrag = wurzel(1^2 + 2^2 + 1,5^2) betrag = wurzel(1 + 4 + 2,25) betrag = 2,693 Das Projektil würde nur 2,693, statt unseren definierten 3 Längeneinheiten pro Sekunde zurücklegen. Somit wäre das Ergebnis falsch. Der Vektor (3 | 6 | 4,5) hätte zum Beispiel die gleiche Richtung wie der Vektor “dir” aber den dreifachen Betrag, was heißt, dass das Projektil auch das dreifache an Strecke pro Sekunde zurücklegen würde. Dieses Ergebnis wäre ebenso falsch. Deshalb kann man das auf diese weise nicht berechnen. Welchen Vektor müssen wir denn nun zum Vektor “pos” addieren um das Projektil 3 Längeneinheiten (bSpeed) nach vorne zu bringen? Und wie bekommen wir diesen Vektor? Gesucht ist also: Vektor “posDelta” (Dessen Betrag genau bSpeed beträgt) Dazu schauen wir, um welchen wert wir den Vektor “dir” kürzen oder verlängern müssen, dass wir genau diesen Betrag erhalten. Wie müssen nun den Betrag von “dir” in einer Gleichung auf den Wert von bSpeed bekommen, und dann anhand des Teilers den Vektor ändern dass dessen Betrag bSpeed entspricht. Alles in einzelnen Schritten: Konkrete Frage: Durch welche Zahl müssen wir den Betrag des Vektors teilen um 3 (bSpeed) zu erhalten? Also bilden wir folgende Formel: Der Betrag von “dir” beträgt wie oben errechnet 2,693: 2,693 / x = bSpeed 2,693 / x = 3 nach x auflösen 2,693 / 3 = x 0,898 = x Da für Vektoren kein Divisionsverfahren definiert ist, müssen wir diesen Teiler durch die Kehrwert-Aktion in einen Multiplikator umwandeln: 1 / 0,898 = 1,1136 diesen Wert nennen wir “deltaFactor”. Den gekürzten/verlängerten Vektor erhalten wir indem wir den Vektor mit “deltaFactor” multiplizieren. dir * deltaFactor = posDelta (1 | 2 | 1,5) * 1,1136 = posDelta (1 * 1,1136 | 2 * 1,1136 | 1,5 * 1,1136) = posDelta (1,1136 | 2,2272 | 1,6704) = posDelta Um zu kontrollieren ob wir mit diesem Vektor auch wirklich 3 Längeneinheiten pro Sekunde (bSpeed) vorankommen, berechnen wir erneut den Betrag: Betrag = wurzel(1,1136^2 + 2,2272^2 + 1,6704^2) Betrag = wurzel(1,2401 + 4,96042 + 2,79023) Betrag = wurzel(8,99075) Betrag = 2,99845 = 3 Laut Kontrolle ist unser gesuchter Vektor also “posDelta” = (1,1136 | 2,2272 | 1,6704) Wenn wir diesen Vektor “posDelta” nun zum Vektor “pos” addieren, Wandert unser Projektil 3 Längeneinheiten (bSpeed) nach vorne. neue Position = “pos” + “posDelta” neue Position = (6 | 2 | 1) + (1,1136 | 2,2272 | 1,6704) neue Position = (6 + 1,1136 | 2 + 2,2272 | 1 + 1,6704) neue Position = (7,1136 | 4,2272 | 2,6704) Jetzt haben wir die neue Position berechnet, die das Projektil nach einer komplette Sekunde Flugzeit hat. Wir müssen aber noch den Faktor Zeit einrechnen. Momentan wird immer die Strecke von einer Sekunde addiert, egal was passiert. Das heißt, dass das Projektil 3 Längeneinheiten fliegt, egal wie viel Zeit in wirklichkeit vergangen ist. Pro berechnung werden 3 Längeneinheiten addiert. Aber das Projektil soll in einer in Sekunde 3 Längeneinheiten fliegen, in einer halben aber nur 1,5 LE, in einer 0,1 Sekunden nur 0,3 LE, usw.. Deshalb brauchen wir noch den Faktor Zeit. Dieser wird einfach mit dem Vektor “posDelta” multipliziert: “posDelta” * zeit (1,1136 | 2,2272 | 1,6704) * zeit (1,1136 * time | 2,2272 * time | 1,6704 * time) Diese Rechnung muss durchgeführt werden BEVOR man “posDelta” zu “pos” addiert. Sonst hat man keine funktionierende Flugbahn, aber ein Klasse “Spieler-Abspace-Beam-System” weil sich der Character höchstwahrscheinlich wild durch die Gegend Portet (Habs noch nicht ausprobiert). Den Faktor “time” haben wir oben nicht definiert. Brauchen wir auch nicht. Denn “time” wird später dann zu Unitys Time.deltaTime. Deshalb können wir ab hier nicht mehr weiterrechnen bzw. sind fertig mit Rechnen. Den Algorithmus nochmal zusammengefasst: Gegeben: Vektor pos Vektor dir Zahl bSpeed Gesucht: Zahl deltaFactor Vektor posDelta Nicht definiert: Zahl time (in sekunden) Eigentlicher Vorgang: deltaFactor = 1/(Betrag dir / bSpeed) posDelta = dir * deltaFactor neue Position = pos + posDelta * time Zu guter Letzt müssen wir noch definieren was ein unwichtig gewordenes Projektil ist. In unserem Fall ganz einfach: Wenn man vorbei geschossen hat und die Kugel jetzt in den unendlichen Weiten des PCs rumschwirrt. Soviel zur Theorie. Wie baue ich das jetzt in meinen Code ein? Hier werde ich erstmal die Elementaren Klassen und Methoden erklären und danach alles zusammenführen. Wir brauchen eine Klasse FlyingShot: using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; class FlyingShot { private Vector3 position; public Vector3 Postion { get { return position; } set { position = value; } } private Vector3 direction; public Vector3 Direction { get { return direction; } set { direction = value; } } private float speed; public float Speed { get { return speed; } set { speed = value; } } private bool finished = false; public bool Finished { get { return finished; } private set { } } public Vector3 lengthPerSecond; private float maxLifeInSec = 5.0f; private float actualLivedSec = 0.0f; public FlyingShot(Vector3 position, Vector3 direction, float speed) { this.position = position; this.direction = direction; this.speed = speed; CalcPositionDelta(); } } Vektor3 positon ist die Position des Abfeuerns und entspricht unserem Vektor “pos”. Vektor3 direction -> unser “dir” von oben. Float Speed -> “bSpeed” von oben. Die Methode CalcPostionDelta berechnet unser “posDelta” von oben. Diese wird beim Erzeugen des Schusses einmal aufgerufen und der Vektor gespeichert. Wir müssen ihn nur einmal berechnen, da dieser Schuss ja pro frame die gleiche Geschwindigkeit hat und in die gleiche Richtung fliegt. Den Satz des Pythagoras berechnet Unity für uns mit Vector3.magnitude. private void CalcPositionDelta() { float div = Direction.magnitude / speed; positionDelta = new Vector3(Direction.x / div, Direction.y / div, Direction.z / div); } und mit CalcNextPosition berechnen wir pro frame die neue Position des Projektils. Wie ihr seht wird hier das “time” von oben durch Time.DeltaTime ersetzt. public void CalcNextPosition() { position = new Vector3(position.x + positionDelta.x * Time.deltaTime, position.y + positionDelta.y * Time.deltaTime, position.z + positionDelta.z * Time.deltaTime); //Ich glaub es Würde auch so gehen: // postition += postionDelta * Time.deltaTime // müsste ich mal probieren //Hier wird noch die “Lebenszeit” des Projektils berechnet CalcLifeTime(); } Die Lebenszeit des Projektils wird so berechnet: private void CalcLifeTime() { actualLivedSec += Time.deltaTime; if(actualLivedSec >= maxLifeInSec) { FinishShot(); } } Dann haben wir noch die Methode finishShot: public void FinishShot() { finished = true; } diese brauchen wir später um eingeschlagene oder unwichtig gewordene Schüsse wegzuräumen. Und dann brauchen wir noch das MonoBehavior Shooting: public class Shooting : MonoBehaviour { public Camera camera; public double fireRate; public float bSpeed; private double cooldown = 0; private bool shooting = false; private List<FlyingShot> activeShots; private List<RaycastHit> hitShots; void Awake() { activeShots = new List<FlyingShot>(); hitShots = new List<RaycastHit>(); } // Use this for initialization void Start() { } // Update is called once per frame void Update() { Shoot(); } } Die obersten drei Variablen werden im Editor befüllt. Die 2 Listen sind zum Speichern der noch nicht weg geräumten Schüsse. Die Methode InitShot Erzeugt nur einen neuen fliegenden Schuss und speichert ihn in der Liste. private void InitShot() { activeShots.Add(new FlyingShot(camera.transform.position, camera.transform.forward, bSpeed)); } CheckTarget führt den eigentlichen Raycast aus. Der erzeugte FlyingShot wird direkt bei der Kamera erstellt. Normalerweise würde ich dafür extra nochmal ein Objekt vorne an der Waffe anbringen. Und dann mit einem zweiten Raycast dann noch die Richtung angleichen (Siehe das Raycast-Shooting Tutorial von der Unity Website). Aber das würde den Code für dieses Beispiel nur aufblähen. Jedenfalls wird dann von diesem FlyingShot der Raycast ausgeführt. Falls dieser nichts trifft, wird eine neue Position berechnet und im nächsten Frame wieder Geprüft. Um zu sehen wie der Schuss durch die Gegend fliegt, kann man noch die DrawRay-Zeile entkommentieren. Dann sieht man in der Sceneview einen grünen Laserstrahl. Bei einem Treffer wird dann der RayCastHit in die Hit-Liste gelegt. private Transform CheckTarget(FlyingShot shot, out RaycastHit hit) { if (Physics.Raycast(shot.Postion, shot.Direction, out hit, shot.Speed * Time.deltaTime)) { Debug.Log("HIT"); shot.FinishShot(); return hit.transform; } else { shot.calcNextPosition(); //Debug.DrawRay(shot.Postion, shot.Direction * Time.deltaTime, Color.green, 1.0f); } return null; } CalcShotPostions führt pro Frame CheckTarget aus. private void CalcShotPostions() { activeShots.ForEach(delegate (FlyingShot shot) { RaycastHit hit; Transform trans = CheckTarget(shot, out hit); if (hit.transform != null) { Debug.Log("Adding hit"); hitShots.Add(hit); } }); } Und CheckForHits verarbeitet dann alle Hits die in der Liste liegen und schmeißt sie hinterher raus. Dies ist dann auch der Punkt an dem ihr eure Events einbauen müsst (z.B. Damage, Destroyen, etc.). Ich habe zum Beispiel eine weitere Klasse WLCharacter der ich Schaden zuweisen kann. private void CheckForHits() { hitShots.ForEach(delegate (RaycastHit hit) { Transform target = hit.transform; print("Target" + target.ToString()); WLCharacter enemy = CheckIfEnemy(target); if (enemy != null) { Hit(enemy); }else { } //spawnBulletHole(hit); hitShots.Remove(hit); }); } Alles zusammen dann sieht so aus: using UnityEngine; using System.Collections; using System.Collections.Generic; [RequireComponent(typeof(AudioSource))] public class Shooting : MonoBehaviour { public Camera camera; public double fireRate; public float bSpeed; private double cooldown = 0; public AudioClip impact; private AudioSource audio; private Animation anim; private bool shooting = false; public Transform muzzleFlash; public Transform muzzleSpawn; public GameObject holePrefab; private List<FlyingShot> activeShots; private List<RaycastHit> hitShots; // Use this for initialization void Start() { audio = GetComponent<AudioSource>(); anim = GetComponent<Animation>(); activeShots = new List<FlyingShot>(); hitShots = new List<RaycastHit>(); } // Update is called once per frame void Update() { Shoot(); } private void Shoot() { cooldown = calcCooldown(); CheckForHits(); CalcShotPostions(); CleanShotList(); if (AllowedToShoot()) { if (Input.GetAxisRaw("Fire1") == 1) { playVisualEffekts(); cooldown = 1000 / fireRate; InitShot(); } else { anim.Stop(); shooting = false; } } } private WLCharacter CheckIfEnemy(Transform target) { WLCharacter wlchar = target.GetComponent<WLCharacter>(); if (wlchar != null) { string frac = wlchar.Fraction; if (fac.Equals("bandits")) { Debug.Log("faction is " + frac); return wlchar; } } return null; } private void Hit(WLCharacter enemy) { double damage = enemy.getDamage(10d); Debug.Log(damage + " points of damage are dealed"); } private bool AllowedToShoot() { return cooldown <= 0; } private double calcCooldown() { if (cooldown <= 0) { return cooldown; } else { double tmpCooldown = cooldown - Time.deltaTime * 1000; return tmpCooldown; } } private void playVisualEffekts() { audio.PlayOneShot(impact, 0.7F); Object tempMuzzle = Instantiate(muzzleFlash, muzzleSpawn.position, muzzleSpawn.rotation); ((Transform)tempMuzzle).parent = muzzleSpawn; if (!shooting) { anim.Play("GunShaking"); shooting = true; } } private void spawnBulletHole(RaycastHit hit) { GameObject hole = (GameObject)Instantiate(holePrefab, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal)); hole.transform.SetParent(hit.transform); } private void InitShot() { activeShots.Add(new FlyingShot(camera.transform.position, camera.transform.forward, bSpeed)); } private void CalcShotPostions() { activeShots.ForEach(delegate (FlyingShot shot) { RaycastHit hit; Transform trans = CheckTarget(shot, out hit); if (hit.transform != null) { Debug.Log("Adding hit"); hitShots.Add(hit); } }); } private Transform CheckTarget(FlyingShot shot, out RaycastHit hit) { if (Physics.Raycast(shot.Postion, shot.Direction, out hit, shot.Speed)) { Debug.Log("HIT"); shot.FinishShot(); return hit.transform; } else { shot.calcNextPosition(); //Debug.DrawRay(shot.Postion, shot.Direction, Color.green, 1.0f); } return null; } private void CleanShotList() { activeShots.ForEach(delegate (FlyingShot shot) { if (shot.Finished) { activeShots.Remove(shot); } }); } private void CheckForHits() { hitShots.ForEach(delegate (RaycastHit hit) { Transform target = hit.transform; print("Target" + target.ToString()); WLCharacter enemy = CheckIfEnemy(target); if (enemy != null) { Hit(enemy); }else { } spawnBulletHole(hit); hitShots.Remove(hit); }); } } Ich prüfe noch ab ob der Spieler in diesem Frame überhaupt schießen darf. Dafür habe ich einen simplen Timer eingebaut. private bool AllowedToShoot() { return cooldown <= 0; } private double CalcCooldown() { if (cooldown <= 0) { return cooldown; } else { double tmpCooldown = cooldown - Time.deltaTime * 1000; return tmpCooldown; } } Außerdem habe ich noch eine Methode “PlayVisualEffects” die mir eben “Visuelle Effekte” abspielt. Zum Beispiel das feuer aus dem Lauf und so. Für den Sound habe ich auch noch eine Methode. Aber auf diese Dinge will ich jetzt nicht eingehen. Da gibts andere Tutorials. Fazit Ich hoffe das Tutorial ist verständlich und hilft euch weiter. Ein Tutorials zu machen ist sehr viel Arbeit und da es mein erstes ist hoffe ich dass es nicht allzu wirr ist. Bei Coding- oder inhaltlichen Fehlern einfach kommentieren. Viel Spaß beim Basteln!
  10. 2 likes
    Hallo Unity fans, hier sind wieder 2 neue Video tutorials über den Timeline Editor von Unity 2017 und die Cinemachine Integration, viel Spaß.
  11. 2 likes
    Ja, der Code oben war in Endeffekt nur dafür gedacht, wenn der Gegner bereits direkt am Spieler steht. So, dann hier aber mal die ultimative "Flieh"-Methode, dafür komm ich dann aber in die Credits Du kannst nun bestimmen, ob der Gegner auf direktestem Wege vom Gegner weglaufen soll (randomPath = false), oder ob er eine zufällige Richtung abgewand vom Spieler einschlagen soll. Ebenfalls kannst du nun eine minimale und eine maximale Wegstrecke einstellen, die er sich Wegbewegen soll. Die Transforms für den Spieler und den Gegner sind hier noch als Public Variablen der Klasse geführt zum besseren Testen, aber du solltest sie noch als Parameter in die "Flee"-Methode legen. Wenn er einen Zufallspfad bestimmen muss, dann testet er einige Zufallsrichtungen durch, bis die Richtung stimmt, könnte man ggf. noch etwas performanter machen. Nach meinen Messungen ruft er bei einer Zufallspfadberechnung die "Random.insideUnitCircle" im Durchschnitt 2x Mal auf, also das sollte zu verkraften sein (ich hatte mal 10000x Durchläufe gemacht). Du könnest die Methode noch verbessern, indem du nun noch "NavMesh.SamplePosition" einbaust. Diese Methode gibt dir einen gültigen (erreichbaren) Pfad im Navmesh zurück, wenn du ihr die Position von der "Flee"-Methode übergibst. bool foundAgentPosition = false; Vector3 newAgentPosition; NavMeshHit hit; Vector3 randomPoint = Flee(25f, 50f, true); if (NavMesh.SamplePosition(randomPoint, out hit, 5.0f, NavMesh.AllAreas)) { newAgentPosition = hit.position; foundAgentPosition = true; } if (foundAgentPosition) { agent.SetDestination(newAgentPosition); } else { // Wenn er hier reinläuft wurde kein Pfad im Navmesh gefunden in einem Bereich // 5 Meter um die berechnete Position der Flee-Methode // In diesem Fall z.b. die Flee-Methode nochmals aufrufen ... } using UnityEngine; using System.Collections; public class SimpleAI : MonoBehaviour { public Transform player; public Transform enemy; void Start() { Vector3 newAgentPosition = Flee(25f,50f, true); Debug.DrawLine(enemy.position, newAgentPosition); Debug.Log(enemy.position + " --- " + newAgentPosition); Debug.Log((enemy.position - newAgentPosition).magnitude); Debug.Break(); } Vector3 Flee(float minfleeDistance, float maxfleeDistance, bool randomPath) { Vector2 randomDirection; Vector2 fleeDirection; float direction; Vector2 directionFromPlayer = new Vector2(enemy.position.x, enemy.position.z) - new Vector2(player.position.x, player.position.z); directionFromPlayer.Normalize(); if (randomPath) { // Determine a direction that is pointing away from the player do { randomDirection = Random.insideUnitCircle; direction = Vector2.Dot(directionFromPlayer, randomDirection); } while (direction < 0); randomDirection = randomDirection * maxfleeDistance; // Add the minfleeDistance float currentDistance = randomDirection.magnitude; if (currentDistance < minfleeDistance) { float minFleeFactor = minfleeDistance / currentDistance; randomDirection = randomDirection * minFleeFactor; } fleeDirection = randomDirection; } else { fleeDirection = directionFromPlayer * Random.Range(minfleeDistance, maxfleeDistance); } return (new Vector3(enemy.position.x + fleeDirection.x, enemy.position.y, enemy.position.z + fleeDirection.y)); } }
  12. 2 likes
    Ich würde dafür 2 Wheelcollider verwenden. Es gibt dafür bereits etliche Skripte und Tutorials, wie man damit eine Fahrzeugphysik (mit 4 Rädern) implementiert: https://docs.unity3d.com/Manual/WheelColliderTutorial.html Ich würde hier "einfach" mal 2 Räder herausnehmen und diese Räder mittig unter dem Fahrzeugcollider zentrieren. Alles andere wäre dann ein Feintuning dieser 3 Komponenten (2 Wheel Collider und einen Box Collider für das Fahrzeuggestell). Hier ist noch ein Projekt, welches vermutlich bereits ein Bike realisiert hat: https://github.com/RandomationGames/Randomation-Vehicle-Physics
  13. 2 likes
    Hallo liebe Unity Insider Community, ich habe die Seite grade erst beim suchen nach Unity Tutorials gefunden und bin begeistert ! Dann habe ich den Vorstellungsbereich hier gefunden und war noch mehr erfreut: Richtig schöne Indie-Spiele-Perlen gibt es hier, genug um eine schöne Beschäftigung für das nächste verregnete Wochenende zu haben Was mich auch direkt zu meiner Projektvorstellung bringt: Ich habe grade erst ein Webprojekt veröffentlicht bei dem es darum geht das Hobby-/Indiespieleprogrammierer ihre Spieleprojekte dort vorstellen können, egal ob diese schon abgeschlossen sind oder sich noch in einer früheren Entwicklungsphase befinden. Das Ziel, bzw. mein Wunschtraum ist es das so irgendwann eine schöne Übersicht über die deutsche Hobby-/Indiespieleprogrammierer Szene entsteht und woran grade so gearbeitet wird. Die Entwickler bekommen eine weitere Möglichkeit ihr Spiel vorzustellen und Feedback einzusammeln und interessierte Spieler können so leicht und strukturiert nach frischer Indie-Spielekost aus dem deutschsprachigen Raum suchen. Die Seite läuft erst seit kurzem und sie befindet sich noch im Beta-Stadium, also habt bitte etwas Nachsicht wenn noch Fehler auftreten. Aber ich arbeite fleißig daran sie durch regelmäßige Updates zu verbessern und zu erweitern. Ein wenig zur Technik: Die Seite wird mit dem üblichen JavaEE Stack entwickelt: JPA, EJB, CDI, JSF, JAAS, JCA Fürs Logging nutze ich Logback mit der SLF4j API und als komponentenbibliothek nutze ich Primefaces. PrettyFaces für hübsche URLs und Caching mit EHCache. Rome zum erstellen von RSS/Atom Feeds und Atmosphere für Websockets mit Fallback. Das ist zwar mehr arbeit als einfach ein fertiges CMS zu nutzen, gibt aber deutlich mehr Flexibilität, denn ich möchte auf lange Sicht gesehen so etwas wie indiedb/gamejolt/moddb für den deutschsprachigen Raum machen. Und da aller Anfang schwer und die Seite grade erst am entstehen ist: Falls ich ein wenig Interesse wecken konnte und jemand mal reinschauen oder gar sein Projekt eintragen möchte: Ich würde mich sehr darüber freuen. Auch jegliche Kritik ist sehr willkommen. Ich hoffe es ist in Ordnung die Seite hier vorzustellen. Falls das als ungewollte Werbung gilt bitte ich vielmals um Entschuldigung und bitte darum diesen Thread einfach zu löschen. Die Adresse lautet: http://pewn.de grüße, krazun
  14. 2 likes
    Ich denke, dieses qualitativ hochwertige Bild sollte die Funktion verdeutlichen:
  15. 2 likes
    Hier was tolles umsonst im Asset Store http://u3d.as/bmx
  16. 2 likes
    So und hier mal mein momentaner Stand mit Erklärungen, was da so möglich ist.
  17. 2 likes
    die Tage wieder etwas Zeit gehabt....kleines Update: * Added: GamePad Support * Added: World1-2 * Added: Player can now shoot "hairballs" when eating mouse * Added: new Enemie * Added: Running-Particle FX * Added: moving platforms * Added: GameOver screen * Added: Enemie itemdrop on death * Added: DEV-Cheat start/stop timer (Strg+F1) * Added: DEV-Cheat 999 hairballs (Strg+F2) * Fixed: World1-1 gamplay/time tuning * Fixed: World1-1 places where player shouldn't go * Fixed: antialising issues * Fixed: enemie KI * Fixed: other small stuff http://www.file-upload.net/download-12082119/PaulsAdventure_002.rar.html
  18. 1 like
    Mhh, ich dachte die Routine wäre besser. Als Notlösung vorher alle leeren Plätze im Array mit einem vordefinierten String füllen. Zum Beispiel: int index = 0; foreach (string freund in freunde) { if (freund == null) freunde[index] = "leer"; index += 1; }
  19. 1 like
    Du solltest dir vielleicht zuerst Überlegen was du mit den Singleton zu erreichen versuchst. Geht es darum Methoden anderen Klassen zu Verfügung zustellen oder soll die Klasse irgendwas verwalten. Bei ersterem ist ein Singleton seltenst sinnvoll. (kurze Denk Pause) So wenn du dir sicher bist dass du immer noch ein Singleton verwenden willst würde ich das mit folgendem Code machen. public class SingletonScript : MonoBehaviour { public static SingletonScript Instance { get { if(_instance == null) { GameObject go = GameObject.Find("Singletons"); if(go == null) go = new GameObject("Singletons"); _instance = go.AddComponent<SingletonScript>(); } return _instance; } } private static SingletonScript _instance; } Damit stellst du sicher das es das Singleton nur einmal gibt (Und das es nie null ist).
  20. 1 like
    Wenn dein Anwendungsfeld das nicht verhindert, würde man dafür eine Queue nehmen. Man kann sich das aber auch selber bauen. class FixedQueue<T> { private T[] arr; private int index = 0; public int count { private set; get } public FixedQueue(int capacity) { arr = new T[capacity]; } public void Insert(T t) { arr[index] = t; index = (index + 1) % arr.length; if(count < arr.length) { count++; } } public T GetAtPosition(int i) { if(i < 0 || i => arr.length) { return new IndexOutOfBoundsException(); } return arr[(index + i) % arr.length]; } } Nur so runtergeschrieben, nicht getestet.
  21. 1 like
    Aaaaah!!1eins Wenn Distance == 10 ist, kommt man in einen Zustand, aus dem man nicht mehr raus kommt Außerdem: Warum nicht einfach nextUpdate = distance * 0.1f;
  22. 1 like
    Vielleicht nochmal ein kleiner Nachtrag, für alle die sowas nutzen: Vielleicht die Logik so ausrichten, das der Path mit Abhängigkeit nach anderen Größen berechnet wird (z.B Aktuelle Distanz usw.) Z.B. kann man wunderbar Die "Distance-Check" und den "Path-Check" zusammen kombinieren. Allerdings funktioniert das nur z.B. mit Spieltypen wo die Player sich nicht soo schnell bewegen. if(NextUpdate>0) NextUpdate-=Time.deltaTime; else UpdateAgent(); void UpdateAgent() { Distance = Vector3.Distance (transform.position, Target.position); agent.SetDestination (Target.position); if (Distance < 10) { NextUpdate = 0.5f; } if (Distance > 10) { NextUpdate = 1.0f; } if (Distance > 30) { NextUpdate = 3.0f; } if (Distance > 50) { NextUpdate = 6.0f; } }
  23. 1 like
    Ewig kannst du dich aber um's Selbercoden nicht drücken
  24. 1 like
    Sollte eigentlich gehen, 100 Meter war tatsächlich etwas zu weit ... der Code funktioniert allerdings nur zu 100% sicher, wenn Spieler und Gegner auf ebener Fläche stehen, ansonsten kann es sein, das der Agent unter den Navmesh oder in die Luft geschickt wird. Mit dem Debug.DrawLine kannst du nun auch die Position sehen, wo der Agent hinlaufen sollt, wenn er das nicht tut, liegt das Problem woanders (Einstellungen im Navmesh / Hinderniss etc). Transform playerTarget = transform; float wert = 10f; Vector3 newRandomPosition = Random.insideUnitSphere * wert + new Vector3(playerTarget.position.x, playerTarget.position.z); Vector3 newAgentPosition = new Vector3(newRandomPosition.x, playerTarget.position.y, newRandomPosition.y); Debug.DrawLine(transform.position, newAgentPosition); Agent.SetDestination(newAgentPosition);
  25. 1 like
    Hier nochmal der aktuelle Code, falls den Thread noch jemand verfolgt: using UnityEngine; using System.Collections; using System.Collections.Generic; /// <summary> /// Simple Bike Controller by zer0f0rce (skype) for the Unity Insider Forum. /// "Center of Mass" should be a child gameObject with a centered position below the vehicle on the ground. /// To completely prevent the swinging of the vehicle add a constraint "freeze rotation Z" to the rigidbody. /// Missing Feature: Move the center of mass to the opposite direction of the steering process. /// Relating to: /// https://docs.unity3d.com/Manual/WheelColliderTutorial.html /// </summary> [System.Serializable] public class AxleInfoSingle { public WheelCollider wheel; public bool motor; public bool steering; } public class SimpleBikeController : MonoBehaviour { public List<AxleInfoSingle> axleInfos; public float maxSpeed = 20.0f; public float maxMotorTorque; public float maxSteeringAngle; public Transform centerOfMass; public float steerDamping = 10.0f; public float timeToZAlignment = 0.75f; private Rigidbody bikeRigidbody; private AudioSource bikeAudio; private string consoleLog = ""; private float lastSteer = 0; private float lastSteerDelta = 0; void Start() { bikeRigidbody = GetComponent<Rigidbody>(); bikeRigidbody.centerOfMass = centerOfMass.localPosition; bikeAudio = GetComponent<AudioSource>(); } // finds the corresponding visual wheel // correctly applies the transform public void ApplyLocalPositionToVisuals(WheelCollider collider) { if (collider.transform.childCount == 0) { return; } Transform visualWheel = collider.transform.GetChild(0); Vector3 position; Quaternion rotation; collider.GetWorldPose(out position, out rotation); visualWheel.transform.position = position; visualWheel.transform.rotation = rotation; } public void FixedUpdate() { lastSteerDelta = Time.time - lastSteer; if ((Mathf.Abs(Input.GetAxis("Horizontal")) > 0.00001f)) { lastSteer = Time.time; } Vector3 localAngularVelocity = bikeRigidbody.transform.InverseTransformDirection(bikeRigidbody.angularVelocity); Vector3 localRotation = bikeRigidbody.transform.localRotation.eulerAngles; if ((lastSteerDelta > timeToZAlignment) || (localRotation.z > 0 && localRotation.z < 60) && (Input.GetAxis("Horizontal") > 0) || (localRotation.z > 300 && localRotation.z < 360) && (Input.GetAxis("Horizontal") < 0)) { consoleLog = "Aufrichten aktiv"; float step = steerDamping * Time.deltaTime * bikeRigidbody.velocity.magnitude * 0.25f; //geschwindigkeit von aufrichten Quaternion targetRotation = Quaternion.Euler(bikeRigidbody.rotation.eulerAngles.x, bikeRigidbody.rotation.eulerAngles.y, 0); Quaternion currentRotation = Quaternion.RotateTowards(bikeRigidbody.rotation, targetRotation, step); bikeRigidbody.MoveRotation(currentRotation); } else { consoleLog = ""; } // Some Infos about values: // Rigidbody localAngularVelocity: if z - then the bike is rolling to the right ... if z + then to the left // Input.GetAxis("Horizontal"): if player directs to the left then value is - ... if to the right then + // Transform localRotation: if z is between 0-60 then the bike is on the left side ... if between 300-360 then on the right if (bikeAudio != null) { bikeAudio.volume = bikeRigidbody.velocity.magnitude * 0.1f; bikeAudio.pitch = bikeRigidbody.velocity.magnitude * 0.1f; } float motor; if (bikeRigidbody.velocity.magnitude < maxSpeed) { motor = maxMotorTorque * Input.GetAxis("Vertical"); } else { motor = 0; } float steering = maxSteeringAngle * Input.GetAxis("Horizontal"); foreach (AxleInfoSingle axleInfo in axleInfos) { if (axleInfo.steering) { axleInfo.wheel.steerAngle = steering; } if (axleInfo.motor) { axleInfo.wheel.motorTorque = motor; } ApplyLocalPositionToVisuals(axleInfo.wheel); } } void OnGUI() { GUI.TextArea(new Rect(10, Screen.height - 70, Screen.width - 10, Screen.height - 10), consoleLog); } } So sieht das Ganze dann aus:
  26. 1 like
    Hallo liebe Community... Über die Ostertage habe ich mein nächstes kleines Projekt fertiggestellt. Asteroid Defender ist ein Game für Android inspiriert von Missile Command. Aufgabe ist eis seine Gebäude auf fremden Planeten vor heranstürzenden Asteroiden zu verteidigen. Man kann seine Waffen erweitern in Punkto Explosionsdauer/größe oder Raketengeschwindigkeit/munition Die Bedienung ist intuitiv, muss ich wohl nichts zu sagen Über Kritik oder Bewertungen würde ich mich freuen Asteroid Defender - Google Playstore MFG GoldBaercheN
  27. 1 like
    Hm, das ist seltsam.. bei mir macht das Skript was es soll. Du hast nicht zufällig beim Rigidbody die 'Constraints' verstellt? Du kannst ja mal "Debug.Log("Hallo Welt");" in der "FixedUpdate"-Methode aufrufen. Dann müsstest du in der Console sehen, ob das Skript läuft.
  28. 1 like
    Hey und Willkommen Yoro, dass das ausgegraut ist, gehört so. So ist zumindest bei mir auch immer. Das heißt nicht, dass das Skript nicht aktiv ist. ( Wenn es inaktiv ist, fehlt der Haken oben links in der Komponente. ) Wenn nichts passiert, liegt das an deinem Skript. In deinem Fall würde ich versuchen den 'Speed'-Wert zu erhöhen, dann bewegt sich vtl. ehr was
  29. 1 like
    Am einfachsten den Winkel des Transforms prüfen: https://docs.unity3d.com/ScriptReference/Transform-eulerAngles.html und wenn die 180 Grad überschritten werden könnten, nicht weiter drehen. Ich habe allerdings den lokalen Winkel verwendet: using UnityEngine; public class ExampleClass : MonoBehaviour { void Update() { float speed = Time.deltaTime * 10; if (transform.localEulerAngles.x + 10) > 180 return; // Rotate the object around local X axis at 10 degree per second transform.Rotate(Vector3.right * speed); } }
  30. 1 like
    PlayerPrefs sind schon sehr einfach zu benutzen. So eine eigene Textdatei klingt immer ganz nett, weil man da nichts extra für lernen muss - aber man darf den Aufwand nicht unterschätzen, den es macht, eigene Speicher- und Ladefunktionen für ein eigenes Speicherformat zu schreiben.
  31. 1 like
    Das ist es, was ich meine. Solange du nicht zwei korrekte GameManager-Objekte haben willst, passt das Pattern. Es kann halt nur eines zur Zeit in der Variable referenziert werden, nicht mehrere. Im Moment wird das Objekt noch ganz normal zerstört, wenn die Szene gewechselt wird. Ich weiß, das ist jetzt vielleicht etwas verwirrend, weil ich oben geschrieben habe, dass eine statische Variable dagegen hilft. Ich erklär das mal. Unity hat eine Funktion, die vor finsterer Magie nur so tropft: Destroy. Wenn man ein Unity-Objekt (z.B. ein GameObject oder eine Komponente) mit Destroy zerstören lässt, dann wird sie am Ende des Frames gelöscht. Wenn man dann im nächsten Frame den Wert einer Variablen abfragt, die dieses Objekt referenziert, ist der Wert "null", also "nichts referenziert". var light = GetComponent<Light>(); // Existiert, gibt also nicht null zurück Destroy(light); // Warte einen Frame if(light == null) { Debug.Log("Jap, ist weg"); } Tatasache ist, dass das in C# eigentlich schlicht unmöglich ist. Also, dass der Wert der Variablen sich geändert hat. Wie die bei Unity das gemacht haben, ist erst einmal aber nicht wichtig. Wichtig ist hier, dass eine Variable, die ein Objekt referenziert, das mit Destroy gelöscht wird, plötzlich den Wert "null" bekommt, sobald die Zerstörung fertig ist. So ist es auch bei deiner statischen Variable im Pattern. Alles, was ein Szenenwechsel macht, ist alle Objekte der aktuellen Szene mit Destroy zu löschen und alle Objekte der neuen Szene zu instanziieren. Dabei werden alle Komponenten auf den GameObjects natürlich ebenso gelöscht wie die GameObjects selbst, und damit gehen alle Werte von Variablen flöten, die die Komponenten hatten. Der Kern hinter statischen Variablen ist aber, dass sie nicht an die Objekte (hier: Komponenten) gebunden sind, sondern genau wie die Klasse selbst "übergreifend" existieren. Deshalb kannst du auch sagen "Klassenname.variable", weil es diese Variable nicht einmal pro Objekt gibt (light1 hat eine andere Farbe als light2), sondern einmal pro Klasse, und damit auch genau wie die Klasse unsterblich ist. Naja, bis das Programm selbst beendet wird. Deshalb kannst du in statischen Variablen Werte unterbringen, die einen Szenenwechsel überleben - sie sind nicht an die Komponenten gebunden, die da massenweise gelöscht werden. public static int score; Allerdings haben wir hier den Fall, dass das Objekt, das deine statische Variable referenziert, mit Destroy gelöscht wird. Obwohl der Szenenwechsel also die Variable nicht direkt beeinflusst, so wird Destroy dennoch für den Wert "null" sorgen, sobald es das referenzierte Objekt löscht. Der GameManager ist also weg und die statische Variable hat dann "null" als Wert. Lange Rede (die hoffentlich beim Verständnis hilft), kurzer Sinn: Du musst deinen GameManager immer noch vor der Zerstörung beim Szenenwechsel bewahren, und das geht mit DontDestroyOnLoad. Es passt sehr gut in's bereits vorhandene Pattern: public class ClassName : MonoBehaviour { public static ClassName singleton { private set; get; } void Awake() { if(singleton == null) { singleton = this; DontDestroyOnLoad(this); } else { Destroy(gameObject); } } } Beachte, dass hier eine Abfrage dazu gekommen ist. Wenn du deinen GameManager z.B. im Hauptmenü hast und zum zweiten Mal im Hauptmenü vorbei schaust, dann ist es vielleicht egal, dass singleton den neuen GameManager referenziert. Aber du willst nicht, dass zwei GameManager-Objekte existieren, die beide durch DontDestroyOnLoad vor Szenenwechsel geschützt sind. Dann werden das nämlich immer mehr Daher setzt sich das Ding nur dann als "das" Ding, wenn es noch keines gibt. also singleton den Wert "null" hat. Ansonsten zerstört es sein eigenes GameObject sofort selbst. Damit sollte sich auch deine letzte Frage geklärt haben.
  32. 1 like
    Also ich versuche es mal ^^: - im Projektfenster das Gameobjekt suchen, welches die Animationen enthält. Meistens ist das ein FBX file (ist so ein kleiner blauer Würfel mit nem kleinen weißen Zettel) - im Inspektor sollten oben nun 3 Buttons zu sehen sein: "Model" "Rig" und "Animations" - gehe auf Animations - dann auf den entsprechenden Clip, den du einstellen möchtest - hier solltest du dann unten die entsprechenden Haken sehen Wenn du es immer noch nicht findest, so ist es vielleicht eine "*.anim"-Datei. Diese wurden vermutlich über Unity selbst erstellt und es kann sein, daß du diese Animationen nicht weiter "einstellen" kannst (das was ich beschrieben habe sind nämlich Importsettings für die Animationen eines FBX). Da kann ich aber leider nicht weiterhelfen, ich weiß nur, daß man diese Animationen sehr gut über die Unity-eigene Animationskomponente bearbeiten / editieren kann. Also man beispielsweise eine Transformverschiebung selbst hinzufügen könnte (ist aber schon ein etwas fortgeschrittenes Thema). Wenn ich das mit der Rotation richig verstehe, ja kannst du mit "Time.deltaTime". Damit wird deine Rotation unabhängig von deiner Framerate. Möchtest du die Rotation noch weiter verlangsamen, dann verkleinere den Faktor 10, welchen ich unten eingeführt habe: https://docs.unity3d.com/ScriptReference/Transform.Rotate.html In diesem Beispiel rotiert er 1x um die lokale X-Achse und dann noch einmal um die globale Y-Achse. using UnityEngine; public class ExampleClass : MonoBehaviour { void Update() { float speed = Time.deltaTime * 10; // Rotate the object around local X axis at 10 degree per second transform.Rotate(Vector3.right * speed); } } Hier nur um die lokale X-Achse (10x so schnell).
  33. 1 like
    Du kannst auf zwei Arten Daten vor der Zerstörung durch einen Szenenwechsel retten. Entweder, du baust eine oder mehrere statische Variablen, oder du benutzt DontDestroyOnLoad . Je nach Situation ist mal das eine besser, mal das andere. P.S. Nächstes Mal gerne nur einen Thread für dasselbe Thema
  34. 1 like
    Du musst auch das Mesh als Asset speichern, da es ansonsten bloß in der Szene lebt und somit, logischerweise, auch nicht über die Szene hinaus verwendbar ist.
  35. 1 like
    Erstens solltest du prüfen, ob bei deiner Animation beim Importer Rootmotion aktiviert ist. Die Rootmotion sorgt für die Bewegung in Y-Richtung (oder in XZ). "Animations" => "Root Transform Postion (Y)" => "Bake into Pose" (kein Haken) Zusätzlich muss die Verwendung dieser Rootmotion noch in der Animatorkomponente gesetzt werden: "Apply Root Motion" (Haken gesetzt) Sollte das auch nicht helfen, könnte es auch sein, daß deine Animation keine Rootmotions besitzt. Dann kann man die Y-Bewegung tatsächlich nur per Code ergänzen.
  36. 1 like
    Zuerst vorweg: Ich kann dir nicht all deine Fragen beantworten. Versuche dir aber trotzdem etwas zu helfen, was das Thema Cardboard angeht. Ich fange mal zum Einstieg hier unten an zu beantworten. Auf die Frage kann ich leider nicht direkt eingehen, da ich die native VR Funktion von Unity nie benutzt habe. Im Endeffekt ist das aber auch nur die GoogleVR SDK, nur fest in Unity integriert. Und somit unter Umständen veraltet und dir fehlen die Prefabs. Von daher würde ich empfehlen das komplett in den Player Settings auszuschalten und die gedownloadete GoogleVR SDK zu benutzen. Das ist auf jeden Fall praktischer für den Einstieg. Cardboard ist das System mit der Pappschachtel als Logo. Sinn und Zweck dahinter ist es, Virtual Reality für jeden zur Verfügung zu stellen. Die Apps dafür werden auch ganz normal über den Play Store vertrieben und sind für jeden (mit einem Gyroscope Sensor im Smarthphone) einseh- und runterladbar. Ebenso kann auch jeder dafür Brillen auf den Markt bringen. Daydream ist ein geschlossenes System und nur für ne Hand voll (Hochleistungs-)Smartphones verfügbar. (Ähnlich wie bei der GearVR, welche nur mit leistungsstarken Samsung Handys funktioniert). Die Auswahl der Daydream Apps ist dadurch natürlich im Durchschnitt qualitativ deutlich hochwertiger. Dazu kommt, dass es zu der Daydream einen Controller gibt, welcher in der Funktionalität den der Oculus Rift ähnelt (Das wird auch der Unterschied zu der Cardboard SDK sein) Wenn du die GoogleVR SDK benutzt, kannst du einfach in den NonVR Scenen auf die VR Prefabs verzichten. Da du was von einen optionalen VR Modus geschrieben hast: Schreibe momentan auch ein Spiel von mir auf einen optionalen VR Modus um und habe dort das so geregelt, dass ich im Player Prefab zwei Camera drin habe (VR / NonVR) und dann per Script die passende aktiviere. Warum das aber auf deinem Smartphone nicht wie gewollt funktioniert, kann ich dir leider nicht beantworten. Funktionert es denn auf deinem Smartphone, wenn du eine Sample Scene aus dem SDK Ordner auf dein Handy spielst? Da es aber mit der nativen SDK schonmal bei dir funktioniert hat, aber mit der gedownloadeten SDK nicht, könnte es auch sein, dass das ein Fehler in der gedownloadeten SDK Version liegt. Vllt mal ne ältere Version in ein leeres Projekt zum testen importieren. Bevor du dir aber deswegen einen Wolf suchst, gucke lieber erstmal die Github Issues durch. Gibt da häufiger Probleme. Z.B auf dem Galaxy S7 funktioniert der Cardboard Modus seit der GoogleVR SDK 0.85 nicht mehr vernünftig. Das Problem ist seit Juli 2016 auf Github bekannt und immer noch nicht gefixt. Kenne mich deswegen auch nicht mit den neueren SDKs so gut aus. Ansonsten check mal den Youtuber NurFACEGAMES ab. Hab den leider erst zu spät entdeckt, sprich nachdem ich das erste mal alles zum laufen bekommen habe
  37. 1 like
    Es sind immernoch 4 Gutscheine zu haben. SimpleScript hat ein Video über das Tool aufgeschaltet - ein grosses Dankeschön an der Stelle auch an ihn! https://www.youtube.com/watch?v=ePcudVIp4b0 Michael
  38. 1 like
    In Unity kann man das allermeiste in beliebiger Reihenfolge machen. Ein Singleplayer-Spiel Multiplayer-fähig zu machen ist da so die größte Ausnahme. Du kannst also problemlos erst einmal drauflos arbeiten. Zu beachten gibt es bei Android nicht wirklich etwas, an das man nicht sowieso denkt. Niedrigere Performance und anderer Input.
  39. 1 like
    Jap, genau so hab ich es bisher immer gemacht Man muss das halt nur durchgehend durchziehen. Z.B. haben dann Energiebälle die über den Boden fliegen auch keinen Collider um das Sprite herum, sondern auch einen flachen Collider unter dem Sprite usw. Stell dir vor ein Licht von genau oben scheint auf dein Objekt, der "Schatten" der dabei entsteht auf dem Boden, ist dann der Collider.
  40. 1 like
    Unity hat seit nicht allzu langer Zeit einige praktische "YieldInstructions", die das ganze etwas hübscher machen können. public void DoStuff() { StartCoroutine(DoStuffCoroutine()); } private IEnumerator DoStuffCoroutine() { StartAnimation(); yield return new WaitWhile(() => AnimationIsPlaying()); SwitchLevel(); } Mit WaitWhile und WaitUntil kann man sich wunderbar die Schleifen sparen. StartAnimation(), AnimationIsPlaying() und SwitchLevel() sind hier natürlich Platzhalter.
  41. 1 like
    Naja nen bissl Transferleistung ist schon erforderlich. Hätte mich überrascht wenn mein Code bei Dir läuft ... Grundsätzlich sollte die Coroutine gehen mit leichten Anpassungen: 1. Animator ist die Referenz auf den Mechanim Animator den ich im Start Teil des Skriplets mit GetComponent abfrage und als Member sicher. 2. Du musst schon den Namen Deines AnimationState oder Trees abfragen. Der 2te Fehler sollte sich mit der Animator Referenz erledigen. 3.Den letzten Fehler kannst Du streichen da Du den Member nicht hast und nicht benötigst. Schau Dir bitte in der API an wie Du die Coroutine aufrufst. Habe grad den Code nicht zur Hand ...
  42. 1 like
    Ich weiß ja nicht, wie es euch geht. Eine interessante Geschichte oder interessante Technik kann ich hier aber leider nicht erkennen... Der Thread ist auch schon ein halbes Jahr alt.
  43. 1 like
    Das liegt daran, dass du da eine Methode übergibst. Der relevante Teil ist void Foo() { Blub(() => print("eins")); print("zwei"); } () => print("eins") ist eine Methode, die du als Parameter in Blub reinschiebst. Einige Methoden nehmen ints, andere strings, und Blub, wie auch Authenticate nehmen eine Methode. Diese Methode können sie dann aufrufen, wann immer sie wollen. Wenn Blub() eine leere Methode wäre, dann würde die übergebene Methode nie ausgeführt werden, die "eins" landet also nie in der Konsole. Authenticate führt die Methode früher oder später aus - und zwar, wenn das Authentifizieren abgeschlossen ist. Das dauert gerne mal etwas und deshalb läft der Rest des Programms weiter. In diesem Fall wird also print("zwei") ausgeführt, bevor Blub sich dazu entscheidet, die übergebene Methode auszuführen. "eins" landet so nach "zwei" in der Konsole. Und genau das ist dein Problem - du führst SetInt aus, bevor deine int-Variable gesetzt wird.
  44. 1 like
    Ja, das kannst du so machen, daß du deine Optionenszene additiv lädst mit der Methode: https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html und dem Parameter: https://docs.unity3d.com/ScriptReference/SceneManagement.LoadSceneMode.Additive.html Was passiert dabei genau? Die vorherige Szene wird nicht gelöscht (oder zerstört) und alle Objekte der zu ladenden Szene werden der vorherigen Szene hinzugefügt. Man kann die Optionenmenu-Objekte sogar wieder entladen mit: https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.UnloadSceneAsync.html Allerdings halte ich das Entladen für eher überflüssig, du solltest programmatisch erfassen, ob das Optionenmenü bereits geladen wurde (damit du es nicht mehrfach lädst) und ansonsten kannst du das einmal geladene Menü einfach geladen lassen und es kontextbezogen (d.h. das Menu bemerkt selbst ob es aus dem Hauptmenu oder dem Spiel heraus angezeigt wird) Anzeigen wenn es benötigt wird, daher ist ein Entladen nicht notwendig.
  45. 1 like
    Der Collider muss ja nicht mit allem kollidieren. Dafür gibt es in Unity die Layer und die Layermatrix, wo man einstellen kann, welcher Layer mit welchem Layer alles machen kann oder eben auch nicht.
  46. 1 like
    Ein Button hat mehr als nur 2 Zustände. GetButtonDown ist nur dieser eine Moment, wenn der Button runter gedrückt wird. ButtonUp ist der eine Moment, wenn der Button wieder losgelassen wird und Button ohne Up oder Down ist eine art Dauerfeuer. Nur hier wird dauernd true gegeben, solange der Button gedrückt ist. Du kannst jetzt also die Momente der Zustandswechsel abfragen und immer dabein eine Variable verändern, da musst du aber up UND down abfragen. Oder mit if - else den Button alleine abfragen. If(Input.GetButton){ sprinting=true; } else{ sprinting=false; }
  47. 1 like
    Total egal! Das Spawnen kostet Ressourcen, nicht die Art der Zeitpunktfindung. Teste es doch selber mal und schau dir im Profiler an, was da passiert.
  48. 1 like
    Ich stehe total auf UnityEvents: https://docs.unity3d.com/Manual/UnityEvents.html Vielleicht wäre das ja auch was für dich. Einfach Klicky-Bunti im Editor die Verbindung zwischen deinen Objekten bauen. Oder halt im Script dynamisch mit Events arbeiten.
  49. 1 like
    Ich würde einfach die Augen nicht der Kugel unterordnen und mit dem Standard-Follow-Script die Position der Augen an die Kugel heften: public Transform target; void LateUpdate() { transform.position = target.position; } Die Blickrichtung wird etwas kniffliger. Deine Kugel wird sich nach ein paar mal um die Kurve rollen völlig krumm gedreht haben. Da ist dann nichts mehr mit Blickrichtung auslesen. Du musst die Rotation des Augen-Objekts entsprechend von etwas anderem auslesen. Möglich wäre z.B. Rigidbody.velocity der Kugel, und dann Transform.forward der Augen darauf setzen, bzw. auf eine Kopie des Vektors mit y = 0.
  50. 1 like
    Sie nehmen sich gegenseitig war (man kann hier auch noch einiges mit Prioritäten steuern, ist aber ist auch begrenzt), ABER sie haben wohl nichts dagegen, wenn sie auch mal leicht kollidieren oder sie sich überscheiden (wenn es eng wird). Ich habe auch schon unzählige Tests damit durchgeführt. Unity verwendet Reciprocal Velocity Obstacles (RVO). Die normalen Navmesh-Agenten verwenden lokale Obstacle Avoidance, was vermutlich die Probleme erklärt, wenn sich die Agenten an einem bestimmten Punkt versammeln müssen und es eng wird, denn hier wird diese Methode wohl immer schlechter. Wie gesagt gibt es wohl vor allem dann vermehrt Probleme, wenn sie "gezwungen" werden sich im selben Bereich aufzuhalten, d.h. wenn sie beispielsweise alle den Spieler als Target haben und sie sich um diesen versammeln müssen, je mehr es sind desto grösser werden die Probleme. Nicht nur das sie sich gegenseitig überlagern, es kommt dann auch zu einem recht heftigen Flimmern, da sich die Positionen der Agenten wohl zu schnell anpassen. Im Gegensatz dazu verwenden die Navmesh Obstacles auch die globale Obstacle Avoidance. Diese zwingen einen Agenten nun mehr oder weniger zu einem Ausweichen. Man kann nun die Vor- und Nachteile beider Systeme verbinden, indem man den Navmesh Agenten zusätzlich Navmesh Obstacles verpasst und diese aktiv werden lässt, sobald die Navmesh Agenten quasi stationär werden (sich nicht mehr bewegen). Damit weichen die Agenten sich nun wieder "fast" vollständig aus. Verzögert man zudem noch ein wenig die Positionen der Transforms von den Positionen der Agenten, so kann man auch das Flimmern der Agenten in den Griff bekommen. Wenn es dich interessiert, ich habe dazu ein Beispielprojekt, welches diese beiden Sachen kombiniert. Somit versammeln sich die Agenten nun wieder "ordentlich" um einen Spieler und klumpen nicht an einer Seite, überscheiden sich oder fangen an zu flimmern. PS: Das seitliche Drehen der Agenten ist kein Problem. Man kann die Rotation der Agenten in Pfadrichtung ausschalten und dann die Rotation selbst bestimmen.