Jump to content
Unity Insider Forum

Leaderboard


Popular Content

Showing content with the highest reputation since 06/06/2010 in all areas

  1. 34 points
    Es gibt eigentlich keine richtitge Rubrik für dieses Thema, deswegen poste ich es in den Texttutorials, da es am naheliegendsten ist. Weil doch einige Leute nicht alle Begrifflichkeiten aus dem Grafikbereich kennen, weil sie z.B. Programmierer sind und einfach nichts damit zu tun haben, will ich hier mal etwas Licht ins Dunkel bringen es geht hier um das 3D Objekt und was dazu gehört. Jedes 3D Spiel braucht 3D Objekte. Da in Unity nur einige Grundkörper zur Verfügung stehen, muss man ein anderes Programm nutzen um 3D Objekte wie z.B. ein Männchen, Monster, Gegenstände, Häuser oder Fahrzeuge zu erstellen. Alle diese Programme arbeiten ähnlich und wenn das Objekt erst einmal erstellt ist muss man es nach Unity rüber bringen. Hier und da gibt es Schwierigkeiten beim im/export aber darauf will ich nicht eingehen. Ich möchte hier mal mit recht einfachen Worten erklären, was ein 3D Objekt ist, wie es sein sollte, warum man auf gewisse Sachen achten muss und wie sich das mit den Texturen und Animationen verhält. Das 3D Objekt: Jedes 3D Objekt wird aus Flächen gebildet. Auch wenn das Objekt nur aus einer einzigen Fläche besteht, ist es ein 3D Objekt denn diese Fläche wird aus Eckpunkten gebildet und diese Punkte haben Informationen wo sie sich im Raum befinden. Eine Fläche muss mindestens 3 Eckpunkte haben, was ja klar ist, denn nur mit 2 Punkten wäre es eine Linie und keine Flächen. Der Fachausdruck für eine Fläche ist das "Polygon". In den gängigen 3D Programmen wird mit viereckigen Flächen gearbeitet. Es können aber auch mehr Ecken sein. Eine dreieckige Fläche nennt man "Tris". Eine viereckige Fläche nennt man "Quad". Flächen die mehr als 4 Punkte haben nennt man "N-Gons" (das "n" steht für irgendeine Zahl über 4), denn sobald diese Flächen über 4 Eckpunkte besitzen ist das 3D Programm gefordert und es macht keinen Unterschied mehr, wieviele Punkte es nun wirklich sind. Ganz wichtig zu wissen ist die Tatsache, dass fast in jedem Programm aus den Quads und n-Gons intern Dreiecke bildet. Unity arbeitet auch nur mit Dreiecken und die Objekte werden schon beim Import gewandelt. Es gibt da aber Schwierigkeiten bei den n-Gons! Ist bei es bei einem Quad einfach die Fläche zu unterteilen um 2 Tris zu bekommen (kann ja nur von Punkt 1 nach Punkt 3 oder aber von Punkt 2 nach Punkt 4 geschnitten werden) so gibt es bei n-Gons viele Möglichkeiten des Schneidens. Je nachdem können da gleichmäßige Dreiecke entstehen oder aber sehr große und ganz spitze. Das wirkt sich später auf das Aussehen des Objektes aus und kann ungewünschte Ergebnisse erzielen. Deswegen auf n-Gons verzichten und einfach selber den Schitt setzen um maximal Quads zu haben. Wie viele Polygone ein Objekt hat oder haben sollte hängt davon ab was ich in meinem Spiel zeigen will, welche Shader ich nutzen will, wie beleuchtet wird, wieviel Performance mir zur Verfügung steht und wieviele Polygone insgesamt in der Szene zu sehen sind. Eine ganz normale Schachtel, die keine abgerundeten Kanten hat, braucht wirklich nur 12 Polygone (6 Seiten a 2 tris). Jede größere Unterteilung wäre Verschwendung. Würde diese Schatel sich nicht bewegen lassen, bliebe also immer am selben Fleck liegen, dann kann man sogar die 2 Polygone den Bodens weg lassen. Habe ich eine Spielfigur, welches viele Details haben soll, da es nah zu sehen ist, brauche ich auch ein Mindestmaß an Polygonen. Wieviele das werden ist schwer zu sagen, da man vieles auch mit Texturen lösen kann. So braucht man leichte Unebenheiten der Kleidung oder Rüstung nicht ausmodellieren sondern kann das über Texturen vorgaukeln. Aber wie auch immer, solch ein Modell wird schon mehr als 1000 Polygone haben. Je realistischer das Ganze werden soll umso mehr muss das Polygon bringen. Bei modernen Spielen, die sich nicht mehr auf einfachen Grafikkarten spielen lassen, haben die Figuren auch schon mal mehr als 10.000 Polygone. Aber auch da wird geschaut, wo man bei anderen Objekten die Polygone einsparen kann, sodass die Summe an Polygonen in der Szene nicht zu hoch wird. Die Animation mit Bones: Um eine Spielfigur laufen zu lassen, muss man dem 3D Objekt ein Skelett verpassen. Man legt fest, welche Punkte des 3D Objektes mit welchen bones (Knochen) interagieren. An einem Bein will ich das mal verdeutlichen. Dieses Bein hat jetzt nur einen Oberschenkelknochen und einen Unterschenkelknochen. Wenn ich den Oberschenkelknochen an seinem Gelenk drehe, dreht sich das ganze Bein. Drehe ich den Unterschenkelknochen, bleibt der Oberschenkel wie er ist, aber der Unterschenkel dreht sich über das Kniegelenk. Damit das so ist, muss ich die Knochen an das Mesh binden. Ich lege also fest, welche Punkte sich in welcher Stärke mit dem zugehörigen Knochen bewegen sollen. Somit hat der Oberschenkelknochen die Punkte des Oberschenkels und der Unterschenkelknochen die Punkte des Unterschenkels. Am Knie teilen sich beide Knochen die Punkte. WIe stark welcher Knochen einen Punkt bewegen darf, nennt man Wichtung. Jeder Punkt muss am Schluß zu 100% gewichtet sein. Egal ob nur ein Knochen auf den Punkt einwirkt oder mehrere sich die Einwirkung teilen. Damit das Knie schön rund bleibt, wenn es einknickt, reicht es nicht nur direkt am Knie Punkte zu haben. Man braucht auch noch Loops (Unterteilungen im Mesh) etwas oberhalb und etwas unterhalb vom Knie. Der Oberschenkelknochen übergibt den Punkten unterhalb nur wenig Wichtung und beim Unterschenkel ist es genau umgekehrt. Dadurch kann man das Knicken etwas weicher gestalten lassen und es kommt nicht zu unschönen Deformationen. Ist jeder Punkt zu 100% gewichtet, kann man das Objekt animieren. Dafür macht man quasi Fotos der Position der Punkte in der Zeitleiste. Am Schluss hat man eine gewisse Animationsdauer und während dieser Dauer mehrere Fotos von den Positionen der Punkte gemacht. Dieses wird auch nach Unity importiert und kann dort abgespielt werden. Dieses 3D Objekt hat jetzt ein Skinned Mesh! Unity weiss also, dass die Punkte nicht starr sind sondern sich innerhalb der Animation bewegen. In Unity kann eingestellt werden, wieviele bones jetzt wirklich auf so einen Gelenkbereich wirken sollen. Voreingestellt ist glaube ich 2. Je mehr Bones auf das Mesh wirken, umso mehr muss Unity die Sache berechnen. Sowieso braucht jeder Bone Performance, weswegen es auch gut ist so wenig Bones wie möglich zu nutzen. Es gibt noch weitere Einstellungsmöglichkeiten, die etwas Performance zurück bringen, denn wenn z.B. ein Skinned Mesh nicht im Bild ist, muss es auch nicht unbedingt animiert werden. Texturen, Shader und die UV Map: 3D Objekte sollen ja auch nach etwas aussehen und somit brauchen die ein Material. Das Material ist erstmal nur ein Container in dem ein Shader eingestellt ist. Dieser Shader kann unterschiedliche Kanäle haben um einen Farbanteil, eine Transparenz, eine Textur und einen Glanz/Spiegelung zu haben. Habe ich jetzt wieder mal eine Schachtel, die z.B. wie Holz aussehen soll und zusätzlich einen Aufdruck auf einer der Seiten hat, reicht mir die Farbe braun nicht aus. Ich muss einen Shader nutzen, in dem ich eine Textur einladen kann. Diese Textur ist erstmal eine ganz normale Grafik. Hätte ich z.B. ein Foto von einer Holzmaserung und würde das jetzt dem Shader zuweisen, so würden alle Seiten der Kiste mit dieser Holzmaserung bestückt werden. Da nichts weiter an Informationen vorliegt, wird Unity von sich aus auf jede Seite die Maserung drauflegen und skalieren. Man sieht das, denn die Ausrichtung der Maserung ist nicht immer richtig. Mehrere Seiten sind verzerrt. Um das zu umgehen, brauche ich eine UV Map. Man stelle sich ein Blatt Papier vor, auf das ich die Kiste lege. Die Unterseite der Kiste belegt jetzt einen gewissen Bereich des Papieres. Würde ich an den Eckpunkten der Kiste jetzt echte Punkte auf das Papier malen, hätte ich einen Bereich definiert, der später der untenliegenden Seite der Kiste zugewiesen würde. Die Kiste könnte ich jetzt über eine Kante abrollen und wieder Punkte auf das Papier bringen, wo die Ecken der neuen Seite der Kiste sind. Ihr würdet sehen, dass ich nur 2 neue Punkte setzen muss, denn die ersten 2 Punkte sind die gleichen 2 Punkte der Unterseite der Kiste. Das würde ich mit allen Seiten der Kiste so machen und hätte am Schluss 6 Bereiche auf meinem Blatt Paier definiert. Ich hätte eine UV Map erzeugt. So ist das auch bei den 3D Objekten. Ist eine UV Map erzeugt worden hat das Objekt eine Information darüber, welches Polygon welchen Bereich meiner Grafik zugewiesen bekommt. Da es ja weiß welche Punkte wo auf dem Blatt liegen. Mit einem Grafikprogramm könnte ich jetzt eine Holztextur in die richtigen Bereiche der Grafik einfügen und dann auch zusätzlich den Aufdruck, den die Kiste später auf einer Seite haben soll, dazu malen. In Unity würde das 3D Objekt jetzt die Textur mit Hilfe der Informationen genau richtig platzieren. Ich hätte eine Kiste, die von allen Seiten anders aus sehen kann, ganz so, wie ich es wollte. Das erzeugen einer UV Map geht leider nur bedingt automatisch und braucht einiges an Handarbeit. Viele 3D Programme haben diese Funktion zum Erzeugen schon dabei. Es gibt aber auch unabhängige Tools. In Unity braucht jedes neues Material in der Szene Performance. Deswegen ist es gut, wenn sich viele Objekte ein Material (Textur) teilen. So eine Grafik, wo z.B. viele Button Grafiken nebeneinander liegen würden, nennt man Texturatlas. Jeder Button würde wissen, welchen Teil des Textruenatlas er zeigen soll. Man kann es direkt in Unity einstellen, wenns nur 2D ist, oder aber die Objekte haben alle unterschiedliche UV Maps, die immer nur einen Bereich der Großen Texture benötigen. Um Unebenheiten auf Oberflächen zu erzeugen, muss man sie nicht modellieren, nein man kann auch eine NormalMap oder einen Bumpmap (Höhenkarte) dafür nutzen. Das ist wieder so eine UV Textur, die jetzt aber nicht die Farbe des Objektes beinhaltet, sondern Höheninformationen. Bei der Bumpmap wird das mit Graustufen gelöst, wobei weiß hoch bedeutet und schwarz tief. Die Normalmap ist 3 Farbig und kann dadurch auch seitliche Erhebungen darstellen. Es ist viel performanter Unebenheiten über die Textur zu erzeugen anstatt über die echte Geometrie zu gehen. Auch wenn so ein Shader wiederum etwas performance braucht. Natürlich wird dadurch alles nur vorgegaukelt und es scheint, als hätte die Oberfläche eine Struktur. Sie ist aber noch glatt und wenn die Struktur auch Auswirkung auf andere Objekte haben soll, kann das nicht mit der Textur lösen.
  2. 17 points
    Über unsere Scripting-Tutorials Dies ist die Liste unserer Scripting-Tutorials. Sie ist gedacht für alle, die noch absolut keine Programmiererfahrung haben, aber auch für jene geeignet, die bereits, wenn auch nicht in Unity, programmieren können. Es werden überhaupt keine Vorkenntnisse voraus gesetzt. Eigentlich sind diese Texte auch weniger Tutorials als vielmehr Lehrtexte Scripten in Unity für Einsteiger Erste Grundlagen für das Scripten im Allgemeinen Scripten in Unity für Nicht-mehr-Einsteiger Der zweite Teil, passend zum ersten Nach diesen beiden beherrscht man die Grundlagen und kann sich an seine ersten eigenen Scripts setzen. Wer jetzt auf C# umsteigen möchte: C#-Tutorial für Um- und Einsteiger Umstieg von JavaScript auf C# Scripten in Unity mit Unity Die Schnittstelle der Unity-Engine, deren Klassen beim Scripten benutzt werden, vorgestellt Scripten in Unity für Scripterfahrene (an Objekte kommen) Der vierte Teil, wichtig für sauberes Programmieren und um das Prinzip hinter Unity zu verstehen Die Scripting Reference lesen Damit man eigenständig neue Dinge über Unity lernen kann Noch mehr Scripten in Unity (Wissen für Fortgeschrittene) Noch mehr Scripten in Unity - Teil 1: Arrays und Existenzoperator Erste leicht fortgeschrittene Techniken, aber einfach erklärt Noch mehr Scripten in Unity - Teil 2: Klassen und Überladungen Objektorientierte Programmierung in Unitys JavaScript Für den Anfang nicht benötigt, für Fortgeschrittene interessant Noch mehr Scripten in Unity - Teil 3: Statische Variablen und Funktionen Für die richtige Anwendung statischer Deklarationen und damit für sauberes Programmieren Noch mehr Scripten in Unity - Teil 4: Böse Funktionen beseitigen Die typischen inperformanten Funktionen durch äquivalenten, besseren Code ersetzen Scripten in der Praxis (Komponenten- und Praxisorientiertes Sctipting) Fehler in Scripts finden Konsole benutzen und Fehlermeldungen lesen können Scripten in der Praxis - Der CharacterController Menschen und andere Lebewesen in Unity Scripten in der Praxis - Raycasts Wozu sie gut sind und wie man sie benutzt
  3. 14 points
    In dieser Tutorial reihe wollen wir uns mithilfe von Unity und der Programmiersprache C# einen Space Shooter basteln. Dabei werde ich euch sanft mit Unity und C# vertraut machen. Deshalb ist diese Reihe sowohl für absolute Anfänger in Sachen Unity als auch im Programmieren geeignet. Im ersten Teil wollen wir unseren Grundstein legen, wir wollen unser Spieler Raumschiff mithilfe der Tastatur steuern. Das Spielerraumschiff stellen wir uns zuerst als einfachen Würfel zur Verfügung. In einem späteren Tutorial werden wir diesen Würfel durch komplexere Gebilde austauschen. Folgendes soll unser Raumschiff in der ersten Version können: - Beschleunigen wenn wir die Vorwärtstaste drücken - Negativ Beschleunigen wenn wir die Rückwärtstaste drücken - Nach links drehen wenn wir die Linkstaste drücken - Und nach rechts drehen wenn wir die Rechtstaste drücken Wir beginnen das ganze indem wir uns ein neues Projekt in Unity erstellen, komplett leer ohne Schnickschnack. Wählt dazu, innerhalb von Unity den Menüpunkt "File"->"New Project.." aus und wählt im darauf folgenden Dialog einen geeigneten Namen und Speicherort für euer Projekt aus. Selektiert zusätzlich die Option "3D" unter dem Punkt "Setup defaults to" im unteren Bereich des Dialoges. Klickt danach auf OK. Wir erstellen nun unser Raumschiff. Dazu wählen wir im Menü "GameObject"->"Create Other"->"Cube". Dies erzeugt uns unser erstes GameObject, einen Würfel mit dem Namen "Cube". Da der Name "Cube" wenig aussagt benennen wir ihn kurzerhand in "Spieler Raumschiff" um: Wählt dazu den Würfel in der Ansicht die "Hierarchy" genannt wird (ganz links) aus. Dann könnt ihr im Fenster welches "Inspector" genannt wird (ganz rechts) den Namen ändern indem ihr in das Textfeld mit dem Inhalt "Cube" klickt und dort den Text "Spieler Raumschiff" eintippt. Die Anführungszeichen bitte weglassen. Ihr könnt zum Umbenennen eines GameObjectes auch, nachdem ihr es in der "Hierarchy" Ansicht ausgewählt habt, die F2 Taste drücken und dort den Namen eintippen. Das Spieler Raumschiff ist nun noch etwas ungünstig positioniert und um das zu ändern wählen wir es wie bereits getan aus und ändern in der "Inspector" Ansicht die Position. Dies kann gemacht werden indem ihr unter "Position" alle 3 Werte (X, Y und Z) auf 0 ändert. Um auch die Kamera etwas passender zu positionieren und zu drehen wählen wir nun auch das "Main Camera" GameObject aus und ändern im Inspektor die Position auf: X = 0, Y = 6 und Z = -2 Sowie die Rotation auf X = 70 Dadurch schaut unsere Kamera auf unser Raumschiff. Speichert nun die Scene ab indem ihr Strg+S drückt. Unity wird euch darauf hin fragen wie die Scene heißen soll. Ich habe meine Scene "Scene0" genannt. Wärend der Entwicklung solltet ihr darauf achten immer wieder Strg+S zu drücken um eure Zwischenstände beim verändern der Scenen auch zu speichern. Nichts ist ärgerlicher als zuvor gemachte Änderungen wiederholen zu müssen. Weil Unity zB abgestürzt ist. Damit sind die Vorbereitungen abgeschlossen und wir können uns nun ein Script erstellen welches unser Raumschiff steuert. Dazu klicken wir mit der rechten Maustaste in den "Assets" Bereich (ganz unten) und wählen den Kontextmenüpunkt "Create"->"C# Script" Anschließend wählen wir als Namen für dieses Script "PlayerControler". "PlayerControler" deshalb weil dies unser Script ist welches das Spieler Schiff kontrolliert. Wir werden in dieser TutorialReihe englische Bezeichner verwenden sofern es sich um das Programmieren handelt. In Unity ist der Name des Scriptes gleichbedeutend mit dem Klassennamen. Dazu aber gleich mehr. Um nun dieses Script zu editieren könnt ihr es doppelt anklicken. Darauf hin sollte sich ein Editor öffnen und ihr seht folgendes: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } } Die ersten beiden Zeilen (die die mit using beginnen) helfen dabei uns einige Grundfunktionen zu geben die wir in unserem Script verwenden können. using UnityEngine gibt uns alle Funktionen die direkt mit Unity zu tun haben. using System.Collections gibt uns einige Hilfsmittel um Ansammlungen von Daten zu verwalten, zB eine Reihe von Werten die wachsen kann und aus der Elemente entfernt werden können. Ausserdem stellt es uns etwas bereit was wir benötigen sobald es um Coroutinen geht. Die nächste Zeile definiert den Namen unseres Scriptes (PlayerController) und gibt an was genau es sein soll (class, eine Klasse) und von wem es einige Grundfunktionen bekommen soll (MonoBehaviour). Jedes Script welches wir auf GameObjects legen können leitet von MonoBehaviour ab. "leitet" bedeutet hier dass unser Script zugleich selbst ein MonoBehaviour ist und damit einige Grundfunktionen beherbergt die dieses MonoBehaviour selbst bereit stellt. Stellt euch einfach ein Auto vor. Ein Auto leitet zB von Fahrzeug ab und kann dadurch gefahren werden. Die Zeile mit void Start() bedeutet dass wir hier eine Methode definieren die Start heißt. Im Bezug auf Unity bedeutet dies dass diese Methode automatisch von Unity aufgerufen wird wenn das Script auf ein GameObject liegt und die Scene betritt. Innerhalb des Bereiches der durch { und } eingegrenzt ist können wir schreiben was unser Script machen soll wenn es startet. Der durch { und } eingegrenzte Bereich wird Scope genannt. Die Zeile Update ist ähnlich wie Start, nur wird diese Update Methode nicht aufgerufen wenn unser Script die Welt betritt sondern in jedem Framedurchgang wenn unser GameObject und Script aktiv sind. Frame bedeutet der Moment in dem ein Bild fertig gezeichnet wurde. Je schneller ein Computer desto mehr Frames in einer Sekunde können erreicht werden. In der Update Methode können wir überprüfen ob der Spieler eine Taste gedrückt hat und dies werden wir nun auch tun. Mithilfe der Input Klasse von Unity können wir abfragen ob eine Taste gedrückt wurde, wir erweitern deshalb die Update Methode um folgendes: if (Input.GetKey(KeyCode.UpArrow)) { print("Power!"); } Denkt daran: Schreibt das ganze zwischen { und } der Update Methode. Das if ist ein Schlüsselwort von C# welches besagt dass hier eine Abfrage statt finden soll und das innerhalb der Klammern ist die Bedingung die dazu erfüllt sein muss damit der darauf folgende Bereich (Scope) innerhalb der { und } Zeichen ausgeführt wird. Input gibt eine Klasse an welche die GetKey Methode bereit stellt. Die GetKey Methode benötigt einen Parameter der besagt welche Taste geprüft werden soll und dies definieren wir mit KeyCode.UpArrow. Input.GetKey liefert einen bool Wert zurück. bool bedeutet dass es 2 verschiedene Werte gibt, entweder true, was wahr oder ja bedeutet, oder false, welches unwahr, nein oder falsch bedeutet. Bedingungen erwarten immer ein bool welches sie auswerten können. Nur wenn dieses bool true ist wird die Ausführung des Scopes vom if gestartet. Die print Methode gibt den String aus der ihr als Parameter übergeben wurde. Ein String ist eine Zeichenkette, ein Text. Wenn wir direkt einen Text angeben wollen so müssen wir ihn innerhalb der Anführungszeichen schreiben. Dann speichern wir das ganze und wechseln zurück zu Unity. Unity wird nun dieses von uns gespeicherte Script kompilieren (in Maschinencode umwandeln) und uns auf eventuelle Fehler hinweisen. Wenn alles erfolgreich kompiliert wurde können wir unser Script auf unser Spieler Raumschiff zuweisen. Dazu wählt das Spieler Raumschiff aus und klickt im unteren Inspektor Bereich (möglicherweise müsst ihr nach unten scrollen) auf den Knopf "Add Component" wählt dann "Scripts"->"Player Controller" aus. Unser Script sollte nun unserem Raumschiff zugewiesen worden sein. Das zuweisen ist auch möglich indem man das Script aus dem Asset Bereich auf das Raumschiff schiebt. Jetzt können wir auf den Play Knopf drücken (das Dreieck im oberen Bereich von Unity). Wenn wir nun die nach Oben Taste drücken sollte im unteren linken Bereich von Unity der Text "Power!" stehen. Glückwunsch du hast soeben dein erstes Script geschrieben und es einem GameObjekt zugewiesen und siehst nun noch wie es agiert. Das Raumschiff bewegt sich aber dennoch nicht, was ziemlich öde ist und schleunigst geändert werden sollte. Unser Raumschiff benötigt dazu die Möglichkeit physikalisch bewegt zu werden, dies geht indem wir dem Raumschiff die Rigidbody Komponente (Komponenten sind Scripte!) zuweisen. Dazu klicken wir wieder im Inspektor den Knopf "Add Component" aus und benutzen "Physics"->"Rigidbody". Im Inspektor erscheint nun ein zusätzlicher Bereich mit dem Titel "Rigidbody". Hier müssen wir auch noch einige Änderungen vornehmen, zB müssen wir dafür sorgen dass unser Raumschiff nicht nach unten fällt. Dazu deaktivieren wir die Checkbox "Use Gravity". Nun können wir mithilfe des Rigidbodies unser Raumschiff steuern. Dazu gehen wir wieder in unser PlayerController Script und entfernen die Zeile mit dem print("Power!") und ersetzen die Zeile durch die Anweisung welche unser Raumschiff beschleunigt: rigidbody.AddForce(transform.forward); rigidbody ist eine Abkürzung die es uns erlaubt ohne extra Code direkt auf unser Rigidbody zuzugreifen, diese Abkürzung gibt es in ähnlicher Form auch für andere Komponenten, zB der Transform Komponente (transform). AddForce ist eine Methode der Rigidbody Komponente die wir aufrufen können um etwas Kraft auf das rigidbody auszuüben. Da wir es vorwärts, entlang der Blickrichtung des Objektes beschleunigen wollen, benutzen wir transform.forward als Parameter. transform ist die Abkürzung auf die Transform Komponente welche Position, Richtung, Skalierung des GameObjektes beinhaltet. Und forward ist ein Richtungsvector der dorthin zeigt wo das GameObjekt "hin blickt". Wenn wir nun das Script speichern und uns das Resultat in Unity ansehen (Play drücken) dann können wir sehen wie sich unser Raumschiff bewegt wenn wir die nach Oben Taste drücken. Leider ist diese Beschleunigung stark von der Anzahl der Frames pro Sekunde abhängig. Was bedeutet das auf schnelleren Computer die Beschleunigung höher ist als auf langsameren Computern. Ausserdem können wir die Beschleunigung nicht genauer regeln und entspricht immer der Länge des Richtungsvectors. Um das zu ändern erweitern wir unser Script wieder ein wenig. Als aller erstes machen wir die Beschleunigung unabhängig von der Anzahl der Frames pro Sekunde: aus rigidbody.AddForce(transform.forward); wird deswegen: rigidbody.AddForce(transform.forward * Time.deltaTime); Time ist die Klasse von Unity die einiges an Operationen und Variablen bereit stellt die etwas mit der Zeit zu tun haben. deltaTime zB ist die Zeit in Sekunden die der letzte Frame benötigte. Indem wir transform.forward mit diesem Wert multiplizieren bleibt unsere Beschleunigung auch dann konstant wenn wir einen sehr langsamen oder sehr schnellen Computer verwenden. Wenn wir nun das ganze in Unity ausprobieren werden wir allerdings ein unangenehmes Wunder erleben. Unser Würfel bewegt nur sehr langsam. Deswegen werden wir nur die Kraft erhöhen. Dazu erweitern wir unser Script indem wir innerhalb der Klassendefinition über void Start folgendes schreiben: public float Acceleration = 10.0f; public besagt dass die float Variable öffentlich erreichbar ist, also jeder der unser Script zu fassen bekommt diesen Wert lesen und/oder verändern kann. Dies besagt zB auch dass dieser Wert im Inspektor von Unity angepasst werden kann. Acceleration ist der Name unserer Variable und float der Datentyp. float gibt an dass wir einen Zahlentyp wollen der Kommawerte darstellen kann. Ein Zahlentyp der nur ganze Zahlen darstellen kann wäre zB int. Wir verwenden unsere Acceleration Variable nun indem wir unser Zeile in der wir Kraft auf das Rigidbody wirken lassen erweitern und zwar: rigidbody.AddForce(transform.forward * Time.deltaTime * Acceleration); Wir multiplizieren also unsere Frame unabhängige Kraft mit unserer Variable. Dies ermöglicht es uns nun anzugeben wie Stark die Beschleunigung ist. Wenn wir nun wieder das ganze innerhalb von Unity testen sehen wir dass unser Würfel sich schneller bewegt als zuvor. Wir können diesen Wert anpassen indem wir im Inspektor nach unsererm Script Player Controller suchen und dort den Eintrag mit dem Namen "Acceleration" ändern. Unser Script sollte aktuell wie folgt aussehen: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { public float Acceleration = 10.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (Input.GetKey(KeyCode.UpArrow)) { rigidbody.AddForce(transform.forward * Time.deltaTime * Acceleration); } } } Da wir nun schon das Beschleunigen haben werden wir nun noch das Abbremsen hinzufügen. Das ganze ist ziemlich identisch zum Beschleunigen und ist eine Ideale Übung zum selber probieren des bereits erlernten. Wenn ihr euch ausgetobt habt oder einfach nur weiter machen wollt, hier ist der Code welcher auch die negative Beschleunigung beinhaltet: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { public float Acceleration = 10.0f; public float Deacceleration = 10.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (Input.GetKey(KeyCode.UpArrow)) { rigidbody.AddForce(transform.forward * Time.deltaTime * Acceleration); } if (Input.GetKey(KeyCode.DownArrow)) { rigidbody.AddForce(-transform.forward * Time.deltaTime * Deacceleration); } } } Was nun noch fehlt ist das Drehen unseres Raumschiffes. Dies funktioniert auf leicht ähnliche Weise wie das Beschleunigen und Abbremsen. Deswegen legen wir uns wieder eine Variable an welche angibt wie schnell die Drehung statt finden soll. public float RotationAcceleration = 30.0f; Als Nächstes müssen wir nun noch auf den Tastendruck reagieren der das Drehen veranlassen soll, ich werde mit dem nach rechts drehen anfangen was relativ einfach auf eine links Drehung erweitert werden kann: if (Input.GetKey(KeyCode.RightArrow)) { rigidbody.AddTorque(0, Time.deltaTime * RotationAcceleration, 0); } So wie AddForce fügt AddTorque etwas an unser Rigidbody. und zwar ein Drehmoment. Wir beschleunigen das Drehmoment unseres Rigidbdies um die Y Achse. Wenn wir das Ganze nun in Unity testen können wir unseren Cube schon vor der Kamera bewegen. Nur fühlt es sich momentan so an als würden wir auf dem Eis laufen. Dies liegt daran dass wir direkt Kräfte auf unseren Würfel anwenden, was ansich realistisch ist, aber leider das ganze schwieriger Steuerbar macht. Die Lösung dazu? Wir steuern durch das Beschleunigen oder Abbremsen die Kraft die unser Antrieb pro Frame automatisch auf den Würfel anwenden soll. Dazu fügen wir eine Reihe weiterer Variablen ein die dafür zuständig sind die maximale und minimale Kraft zu regeln als auch die momentane Kraft die unser Antrieb generieren soll zu speichern: public float MaxForce = 1000.0f; public float MinForce = -200.0f; public float CurrentForce = 0.0f; Nachdem wir das nun haben müssen wir natürlich auch die Handhabung der nach Oben und Unten Tasten ändern. Wir ändern hierbei jeweils die AddForce Zeilen indem wir den Frame unabhängigen Acceleration Wert zu CurrentForce hinzu addieren und den Deacceleration Wert abziehen. if (Input.GetKey(KeyCode.UpArrow)) { CurrentForce += Time.deltaTime * Acceleration; } if (Input.GetKey(KeyCode.DownArrow)) { CurrentForce -= Time.deltaTime * Deacceleration; } += bedeutet hierbei dass der Wert rechts von += zu den Wert links vom += addiert werden soll. Das ganze ist gleich zu a = a + b -= bedeutet dass der Wert abgezogen anstatt hinzugefügt wird. Jetzt haben wir die CurrentForce zwar angepasst aber noch kann sie sowohl zu hoch als zu niedrig werden und verwenden tun wir diesen Wert ebenso wenig. Daher grenzen wir CurrentForce zuerst ein. Dies geht indem wir die Clamp Methode der Mathf Klasse von Unity verwenden. Schreibe folgendes ans Ende der Update Methode: CurrentForce = Mathf.Clamp(CurrentForce, MinForce, MaxForce); Mathf.Clamp begrenzt den Wert im ersten Parameter innerhalb von den Wert des zweiten und dritten Parameters und gibt das Resultat wieder zurück. Damit ist sicher gestellt dass die CurrentForce niemals größer oder kleiner unserer Grenzwerte wird. Nun müssen wir CurrentForce nur noch irgendwie auf unser Raumschiff angewendet bekommen. Hierfür benutzen wir einfach unser rigidbody.AddForce wieder. Wir multiplizieren CurrentForce mit der Zeit die der letzte Frame zum ausführen brauchte und der Richtung unseres Raumschiffes: rigidbody.AddForce(CurrentForce * Time.deltaTime * transform.forward); Der aktuelle Code des PlayerController Scriptes sollte nun so aussehen: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { public float Acceleration = 100.0f; public float Deacceleration = 100.0f; public float RotationAcceleration = 30.0f; public float MaxForce = 1000.0f; public float MinForce = -200.0f; public float CurrentForce = 0.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (Input.GetKey(KeyCode.UpArrow)) { CurrentForce += Time.deltaTime * Acceleration; } if (Input.GetKey(KeyCode.DownArrow)) { CurrentForce -= Time.deltaTime * Deacceleration; } if (Input.GetKey(KeyCode.RightArrow)) { rigidbody.AddTorque(0, Time.deltaTime * RotationAcceleration, 0); } if (Input.GetKey(KeyCode.LeftArrow)) { rigidbody.AddTorque(0, -Time.deltaTime * RotationAcceleration, 0); } CurrentForce = Mathf.Clamp(CurrentForce, MinForce, MaxForce); rigidbody.AddForce(CurrentForce * Time.deltaTime * transform.forward); } } Das Raumschiff beschleunigt nun immer mit der aktuellen Antriebskraft in die aktuelle Schiffsrichtung. Das fühlt sich nun schon alles viel besser an als vorher, die Steuerung ist leichter zu beherrschen, aber etwas fehlt noch.. Spielt ein wenig mit den Werten Im Inspektor der Rigidbody und PlayerController Komponenten herum um ein leichtes Gefühl dafür zu bekommen wie sich jeder Wert auswirkt. Gute Werte sind zB folgende: PlayerController: Acceleration 100 Deacceleration 50 Rotation Acceleration 100 Max Force 100 Min Force -20 Rigidbody: Mass 1 Drag 0 Angular Drag 10 Das war es für den ersten Teil der Tutorial Reihe. Im nächsten Teil werden wir die Kamera so umbauen dass diese immer auf das Spieler Raumschiff gerichtet ist. Feedback ist gern gesehen. Teil2: http://forum.unity-c...__fromsearch__1
  4. 13 points
    Titel sagt alles sobald das spiel geöffnet ist soll ein Text erscheinen. der text soll aber nur für 10s, also nicht dauerhaft da sein weiß jemand wie das funktioniert? danke im vorraus
  5. 12 points
    Hallo Community, Ich schreibe diesen Thread um mich einfach mal bei euch für die großartige Unterstützung und Hilfe zu bedanken. Ich habe selten so eine freundliche und hilfsbereite Community mit so vielen fähigen, netten Menschen erlebt. Ich weiß nicht ob es einen ähnlichen Thread bereits gibt, aber ich möchte an dieser Stelle einfach mal Danke sagen. Ich habe mich als totaler Anfänger vor 1 1/2 Jahren hier angemeldet und extrem viel dazu gelernt. Ich denke ein Forum wie dieses kann beim Lernprozess sehr helfen, da es einfach Dinge gibt, die man mit den Jahren als Entwickler erst lernt. Und solche Dinge sollten meiner Meinung nach weitergegeben werden, wie es hier geschieht. Mit Unity habe ich es gelernt Spiele zu programmieren und erste Projekte fertiggestellt. Auch wenn ich derzeit mehr mit Goo Create und der Goo Engine arbeite, hat mir Unity viel auf meinem Weg mitgeben können. Dadurch habe ich beispielsweise im Januar diesen Jahres bei der Mozilla and Goo Technologies Game Creator Challenge 1000$ gewonnen. Und ich denke, daran habt ihr alle ebenfalls einen Anteil, da Unity Insider mein Interesse in Programmierung, und Spielentwicklung geweckt hat und wie bereits erwähnt sowohl Motivation als auch technisches Wissen gespendet hat. Besonders möchte ich hier auch offiziell Sascha und Stephan Rauh (damuddamc) danken, die mich bei meiner Belegarbeit dieses Jahr unterstütz haben. Sascha stand mir als Außenbetreuer zur Seite und Stephans A* Tutorials haben mir ebenfalls sehr geholfen. Abschließend bleibt mir nur zu sagen, dass ich auf weitere gute Unterstützung, gegenseitige Hilfe und natürlich viel Spaß hoffe! Ich denke, ich kann im Namen vieler anderer Mitglieder hier sagen: DANKE!
  6. 11 points
    Hallo miteinander, wird mal wieder Zeit für ein neues Tutorial! Das Thema heute: Wie deklariere ich meine Variable? Wer noch nicht so viel programmiert hat, hört manchmal in einem Tutorial von einer Lösung für sein Problem, versteht diese nicht ganz, probiert es aus und: Tadaa! Es funktioniert. Ähnlich wie bei php gibt es aber auch für Unity eine Menge motivierter Menschen, die freundlicherweise nach ihren ersten eigenen Erfolgen gleich mit anderen Teilen, wie sie es geschafft haben. Entsprechend wird das "warum" oft nicht weitergegeben, oder noch schlimmer: Es ist nur gefährliches Halbwissen. Dann bekommt man das nächste Problem, findet keine Lösung und besucht z.B. dieses Forum. Und da kommt's: Wie, deine Variable ist static?? Das ist ja großer Humbug! Heute will ich möglichst knapp ein paar wichtige Modifikatoren auflisten, die Variablen in C# haben können. Kleine Anmerkung: Dieses Tutorial wird alles andere als umfassend, da es für (Fast-noch-)Anfänger gedacht ist. Außerdem: Es geht um Objekt- und Klassenvariablen, also nicht die Variablen, die man in einer Methode definiert. Zuerst einmal werde ich auf allgemeines Programmieren eingehen. Die Bedeutung dieser Begriffe ist in C# immer so, wird aber in Unity erweitert. C# allgemein Variablentyp und Name Darf bei keiner Variable fehlen: Der Variablentyp und der Name. Dahinter ein Semikolon. typ name; Der Typ gibt an, welche Werte die Variable haben kann: Gibt man z.B. int (also Ganzzahl) an, dann sind mögliche Werte 0, -4, 20 usw. Der Name wird benötigt, um die Variable woanders benutzen zu können. Beispiel: int lives; Initialisierung Eine Variable kann gleich bei der Definition initialisiert werden, also einen Wert kriegen. Einfach ein "= wert" zwischen Name und Semikolon quetschen. Je nach Kontext wird das mal mehr, mal weniger als guter Programmierstil angesehen; für Unity ist das meist sehr gebräuchlich. Der Wert kann sich später natürlich wieder ändern. Beispiel: int lives = 5; Zugriffsmodifikatoren Jetzt wird's interessant. Ein Zugriffsmodifikator gibt an, wer alles diese Variable benutzen darf. Für den Anfang relevant sind erst einmal nur public und private. public gibt dabei an, dass alles und jeder den Wert der Variable herausfinden und ihn auch ändern kann. private ist quasi das Gegenteil: Nur die Klasse, in der die Variable definiert ist, weiß überhaupt, dass es sie gibt. Gibt man keinen Zugriffsmodifikator an, ist das in C# (!) gleichbedeutend mit private. Ob man private also hinschreibt oder nicht, ist also Geschmackssache. Beispiel: public Person bestFriend; public ist immer mit etwas Vorsicht zu genießen. Grundsätzlich gilt: Je weniger eine Klasse von sich öffentlich macht, desto unwahrscheinlicher wird es, das eine andere Klasse etwas von außen vermurkst. Klassen sollen als Einheiten funktionieren und nicht eng verwoben mit drölf anderen Klassen sein. Wann immer es allerdings völlig okay ist, wenn eine andere Klasse völlig uneingeschränkt den Wert einer Variablen ändert, benutzt in Unity ruhig public. public hat allerdings in Unity einen Nebeneffekt, auf den ich später eingehen werde. Static Das Keyword static hat eine besondere Bedeutung, die eine Variable von ihrer Bedeutung her komplett woanders hin verschiebt. Ich erklär's nochmal so kurz wie möglich: Wenn man ein Klasse hat, und diese hat eine Variable: class Person { int alter; } ...dann kann man mehrere Objekte dieser Klasse erstellen: jemand = new Person(); jemandAnderes = new Person(); ...und diese Objekte können unterschiedliche Werte für diese Variable haben. jemand könnte als Alter 18 haben und jemandAnderes 42. static hebt diesen Umstand auf und sagt: Diese Variable hat nicht pro Objekt einen eigenen Wert, sondern nur einen einzigen, der global gilt. Anwendungsbeispiel: class Checkpoint { static int checkpointsLeft = 0; public void Initialise() { ++checkpointsLeft; } public void CheckpointTouched() { --checkpointsLeft; if(checkpointsLeft == 0) { WinTheGame(); } } } Die Variable checkpointsLeft zählt hier, wie viele Checkpoints es (noch) gibt, und wann immer einer berührt wird, wird 1 abgezogen. Hat man alle Checkpoints berührt, gewinnt man. Wäre die Variable nicht statisch (also mit static markiert), dann würde jeder Checkpoint seinen eigenen Wert für checkpointsLeft haben. So aber gibt es global nur eine Variable checkpointsLeft. Alles, was man mit static machen kann, kann man irgendwie auch ohne machen. Auch static sollte nur dann eingesetzt werden, wenn es wirklich sinnvoll ist - da es aber einige Vorteile gegenüber den Alternativen bietet, kommt es immer mal wieder vor. Zugriffsmodifikatoren und static lassen sich kombinieren: public static int amount; ...aber gerade die Kombination public static ist fast immer ein schlechtes Zeichen C# für Unity Jetzt kommt der Teil, der den C#-Standard erweitert, wann immer man Unity benutzt. public Wie erwähnt, hat public in Unity eine Sonderbedeutung: Öffentliche Variablen werden serialisiert und im Editor exponiert. Das heißt, dass eine Variable mit public davor im Editor angezeigt wird (exponiert), man den Wert im Editor einstellen kann und dieser dann in der Szenendatei mitgespeichert wird (serialisiert). Nnach dem Motto: "Dieses" GameObject hat "diese" Komponente (die ihr programmiert habt) und "diese" Variable davon hat "diesen" Wert. So ziemlich jedes Unity-Scripting-Anfängertutorial zeigt einem das, aber der allgemeine Effekt von public (s.o.) wird gerne erst einmal unterschlagen. Was macht man denn jetzt, wenn eine Variable vor Zugriff von anderen Klassen geschützt sein soll, aber trotzdem im Editor exponiert sein soll? Oder umgekehrt: Eine Variable, die public sein soll, aber immer denselben Startwert hat und damit im Editor nichts zu suchen hat? Zum Glück gibt es dafür Lösungen. Serialisierungs-Attribute C# hat so genannte "Attribute", die man über Variablen, aber auch, je nach Attribut, vor Methoden und Klassen setzen kann. Sie sehen so aus: [Attribut] int zahl; Für uns jetzt relevant sind [serializeField] und [system.NonSerialized]. Sie machen das, was man vermutet: [serializeField] sorgt für das, was public macht, nur eben auch bei privaten Variablen. Beispiel: [serializeField] private int lives = 5; Der umgekehrte Fall, dass eine Variable public sein soll, aber nicht im Editor auftauchen soll, kommt eher selten vor, aber falls doch, hilt [system.NonSerialized] auf gleiche Art. Kleiner Zusatz für Fortgeschrittene: Propertys Propertys sind "Dinger", die von außen so aussehen wie Variablen, aber wesentlich mehr können. Die einfache Anwendungsform erlaubt es, Lesen uns Schreiben eines Variablenwertes mit unterschiedlichen Zugriffsmodifikatoren zu versehen: public int lives { private set; get; } Mit diesem Code ist lives von allen anderen Klassen auslesbar; den Wert ändern kann man allerdings nur von innerhalb der Klasse. get nimmt hierbei den Zugriffmodifikator der Property selbst an. Benutzt werden Propertys, als wären es Variablen: if(lives == 0) // oder lives = 5; Propertys können noch mehr, aber zuerst zu den Einschränkungen: 1. Propertys können von sich aus nicht direkt initialisiert werden, da muss man tricksen 2. Propertys kann Unity nicht serialisieren, weder mit public, noch mit [serializeField]. Kommen wir direkt dazu, was Propertys noch können. Und zwar: Seiteneffekte. Oft will man, dass etwas passiert, wenn sich ein Wert ändert; z.B. Tod, wenn die HP 0 erreichen. Propertys können Code ausführen, wann immer der Wert ausgelesen oder geändert wird. Tatsächlich kann der Wert beim Auslesen überhaupt erst generiert werden! Um so etwas zu erreichen, einfach geschweifte Klammern hinter get und/oder set schreiben. Beispiel "Direkte Weitergabe": public Color color { get { return renderer.material.color; } set { renderer.material.color = value; } } Man bemerke hier die Verwendung von return und dem speziellen Keyword value. Beispiel "Daten generieren": private Konto[] konten; public int gesamtVermoegen { get { var result = 0; foreach(var konto in konten) { result += konto.saldo; } return result; } } Auffällig hier: Es gibt kein set. Das bedeutet, man kann die Variable nur auslesen, aber nicht einen Wert setzen. Natürlich kann man auf die Konten einzahlen, um den Wert zu ändern. Beispiel "Gemischte Zugirffsmodifikatoren mit Serialisierung": [serializeField] private int _lives = 5; public int lives { get { return _lives; } private set { _lives = value; } } Dieser Code ist besonders nützlich: lives ist öffentlich auslesbar, aber nur privat änderbar. _lives wird serialisiert. Der Unterstrich ist eine verbreitete Schreibweise für private Variablen (die ich in Unity allerdings nur in Verbindung mit Code wie diesem verwende) und wird beim Anzeigen im Editor entsprechend weg gelassen. Von aussen sieht es damit aus, als gäbe es nur eine Variable lives, die im Editor zu sehen ist, deren Wert aber auf magische Weise nicht von anderen Klassen geändert werden kann. Das war's dann für heute Hoffentlich schafft diese kleine Auflistung etwas Klarheit darüber, wann man wie Variablen deklariert. Frohes Schaffen!
  7. 9 points
    Multiplayer mit Unity - Teil 2 Masterserver und Statesynchronisation Inhalt: Kommentar Etwas über Unity und Multiplayer Den Masterserver benutzen Spieler Spawnen Einfache Statesynchronisation Dead Reckoning 1. Kommentar Guten Abend zusammen Ich entschuldige mich dafür dass dieses Tutorial so lange hat auf sich warten lassen. Ich arbeite aktuell nicht mit dem eingebauten Netzwerksystem von Unity sondern mit einer eigenen Lösung (basierend auf Sockets) und einem C++ Server, die Gründe dafür werde ich in 2 genauer erläutern. Generell gibt es zu diesem Tutorial zu sagen dass es auf dem Code und dem Wissen vom 1. Tutorial basiert und ich werde demzufolge kein komplettes Programm sondern nur die speziellen Ausschnitte (zum Master Server verbinden, Statesynchronisation...) hier erläutern, da es sonst auch zu lang würde. Das komplette Projekt ist im Anhang zu finden, und eine Webplayer Demo werde ich auch veröffentlichen. 2. Etwas über Unity und Multiplayer "Unity unterstützt Multiplayer, juhuu, jetzt kann ich endlich ein MMORPG schreiben!" Das wäre zu schön um wahr zu sein. Abzusehen davon dass ein MMORPG immernoch sehr viel Arbeit ist, ist Unity für diesen (oder andere Spieltypen) nicht ausgelegt. Hier mal etwas genauer: Unity's Multiplayer ist durchaus einfach zu bedienen und funktionstüchtig, das möchte ich ihm nicht absprechen, aber es ist nur für Spiele geeignet bei dem ein Spieler selber der Server ist, d.h. z.B. FPS oder Strategiespiele. Es ist grundsätzlich bis zu einem gewissen Grad möglich auch Spiele mit Unity zu realisieren bei denen ein eigenständiger Server benutzt wird (z.B. bei einem MMORPG), aber dabei sollte man auf andere Server zurückgreifen, z.B. Photon. Für den Hobby Entwickler liegt das einzige Problem da evtl. beim Preis Bei dieser Lösung wird Unity dann praktisch nur noch benutzt um beim Client die Grafik/Sound usw. zu verwalten. Von der Idee den Server auch in Unity zu realisieren sollte man sich fernhalten. Abgesehen davon dass Unity nicht dafür strukturiert ist wird man spätestens beim 2. Level dass der Server parallel verwalten soll auf Probleme treffen. [beispiel: Im Server hat der NetworkView eine andere ID als im Client weil der Server mehrere Szenen auf einmal hat (Wenn die 1. Szene 3 Network Views hat dann hat der 1. Network View der 2. Szene beim Server die ID 3 und im Client die ID 0...)] Die andere Alternative ist es auf Unitys Netzwerksystem zu verzichten und es selber (z.B. mit Sockets) zu erledigen. 3. Den Masterserver benutzen Ok, let's go. (Ich gehe davon aus dass der Quellcode aus dem 1. Tutorial benutzt wird) Um statt zu einer direkten IP über den Master Server zu verbinden sind einige wenige Änderungen nötig. Beim Server ist lediglich ein weiterer Funktionsaufruf nötig: if (GUILayout.Button ("Start Server")) { // Status auf "Starte Server...." setzen Status = "Starte Server..."; Debug.Log(Status); // zu bestimmter IP und bestimmtem Port verbinden Network.InitializeServer(32, parseInt(Port), !Network.HavePublicAddress()); // Den Server beim Master Server "anmelden" MasterServer.RegisterHost("Multiplayer_Tutorial_2-Test", "Testgame", "free4all"); } Der Aufruf von "RegisterHost" tut die ganze Magie auf der Seite des Servers. Dadurch wird der Server in die Serverliste vom Unity Masterserver aufgenommen. Der Erste Parameter gibt den Typ des Spiels an. Nur Wenn der Server den selben Typ wie der Client hat erscheint der Server auch in der Serverliste des Clients. Der 2. Parameter gibt den Spielnamen und der 3. ein Kommentar an. Hier muss ich noch auf NAT hinweisen (durch Network.useNat benutzbar). Falls es Probleme beim Verbinden geben sollte ist es ratsam Nat an/aus zu schalten. Es gibt viele Computer die nur zu Nat Servern verbinden können oder nur zu nicht Nat Servern. Fortgeschritten ist es möglich die NAT-Möglichkeiten durch Unity herausfinden zu lassen. Link: Test Connection Auf der Seite des Clients wird das ganze schon etwas komplizierter. Die Serverliste kann man mit MasterServer.RequestHostList("Multiplayer_Tutorial_2-Test"); abrufen. Dann muss man sie nur noch auswerten. //in OnGUI // Serverliste abfragen var serverList : HostData[] = MasterServer.PollHostList(); // Alle Server durchgehen for (var server in serverList) { GUILayout.BeginHorizontal(); // Name, Spielerzahl/Spielerlimit und Kommentar anzeigen GUILayout.Label(server.gameName + " (" + server.connectedPlayers + " / " + server.playerLimit + ") | " + server.comment); GUILayout.FlexibleSpace(); // Für jeden Server einen Button erstellen if (GUILayout.Button("Verbinden")) { // Bei Nat richten wir uns nach dem Server Network.useNat = server.useNat; // verbinden Network.Connect(server.ip, server.port); } GUILayout.EndHorizontal(); } Dazu muss man noch sagen dass man RequestHostList öfters aufrufen sollte (über einen "Aktualisieren" Button oder zeitlich alle paar Sekunden) um die Serverliste zu aktualisieren. Das wars schon um den Master Server zu benutzen. 4. Spieler Spawnen Als nächstes kommen wir zu einem spannenderem Punkt: Wie kann man Spieler in der Welt instanziieren/Steuern usw. Um etwas instanziieren zu können brauchen wir erstmal ein Prefab. Dabei habe ich eine einfache Capsule mit Capsule Collider und Rigidbody (Use gravity und Freeze Rotation) genommen, aber das bleibt jedem selbst überlassen. Dem ganzen kann bzw. sollte man dann noch ein Spielerskript und einen NetworkView (mit Standardeinstellungen) hinzufügen. Damit sich auch was bewegt kann man in das Spielerskript ein provisorisches Steuerungssystem einbauen: function Update() { // Wenn es mein NetworkView ist if(networkView.isMine) { // den Spieler bewegen transform.Translate(Vector3(Input.GetAxis("Horizontal"), 0,Input.GetAxis("Vertical")).normalized *Time.deltaTime * 7); } } Dann brauchen wir noch einen Spawner, das ist die Stelle an der die Spieler erstellt werden sollen. Dazu ein leeres GameObject erstellen und an die Position verschieben an der die Spieler gespawnt werden sollen. Dann ein neues Skript (z.B. "Spawner.js") erstellen. // Das Prefab, muss im Inspektor gesetzt werden var playerPrefab : Transform; // Wenn wir zum Server verbunden wurden function OnConnectedToServer() { // das Spielerprefab instanziieren (an der Position des Spawners und mit der Ausrichtung des Spawners) Network.Instantiate(playerPrefab, transform.position, transform.rotation,0); } // Wenn der Server gestartet wurde auch für ihn einen Spieler erstellen function OnServerInitialized() { // das Spielerprefab instanziieren (an der Position des Spawners und mit der Ausrichtung des Spawners) Network.Instantiate(playerPrefab, transform.position, transform.rotation,0); } // Ein Spieler hat sich ausgeloggt (wird nur auf dem Server aufgerufen) function OnPlayerDisconnected(player: NetworkPlayer) { // aufräumen Debug.Log("Der Spieler " + player + " hat sich ausgeloggt, Entferne seine RPCs und Objekte"); // alle von ihm gebufferten RPCs löschen Network.RemoveRPCs(player); // Seine Spielerobjekte zerstören Network.DestroyPlayerObjects(player); } In diesem Codeschnipsel wird zuerst das Prefab für den Spieler definiert (muss ihm Inspektor zugewiesen werden (!) ) und dann werden 3 verschiedene Events behandelt. Bei einer Neuverbindung (Serverside/Clientside) wird jeweils ein neuer Spieler erstellt und wenn eine Verbindung beendet wird wird auch der dazugehörende Spieler gelöscht. [Das Event "OnDisconnectedFromServer" sollte auch behandelt werden ist hier aber unwichtiges. Genaueres dazu ist in der Datei Spawner.js als Kommentar zu finden ] Network.Instantiate erstellt eine Instanz von dem Prefab an der angegebenen Position mit der angegebenen Ausrichtung auf jedem Client/Server. Dieser Aufruf ist gebuffert, d.h. auch Spieler die sich später verbinden werden diesen Aufruf empfangen. Wenn man das Ganze jetzt testet sollte Unity die Spielerpositionen standardmäßig synchronisieren und soweit sollte alles funktionieren. 5. Einfache Statesynchronisation Auch wenn Unity im Falle von Position, Skalierung und Rotation selber synchronisiert ist es doch wünschenswert es auch manuell durchführen zu können. Das kann man z.B. gut gebrauchen wenn auch andere Variablen ausgetauscht werden sollten, z.B. Health oder Mana. Hier ein kleines Beispiel: // Health und Mana als Integer Variablem var health : int = 100; var mana : int = 100; // Bei jeder Synchronization function OnSerializeNetworkView(stream : BitStream, info : NetworkMessageInfo) { // temporäre Kopien anlegen var healthCopy : int = 0; var manaCopy : int = 0; // Es ist unser NetworkView (wir können schreiben) if (stream.isWriting) { // den temporären Kopien die lokalen Werte zuweisen healthCopy = health; manaCopy = mana; // Beides zu dem gleichen NetworkView auf den anderen Clients/Server weiterleiten. stream.Serialize(healthCopy); stream.Serialize(manaCopy); } else { // Die beiden temporären Kopien mit den Werten von der Synchronisation füllen stream.Serialize(healthCopy); stream.Serialize(manaCopy); // Die lokalen Variablen aktualisieren health = healthCopy; mana = manaCopy; } } Der Code ist schnell erklärt. OnSerializeNetworkView wird von Unity immer dann aufgerufen, wenn standardmäßig Daten "synchronisiert" werden. Dabei wird überprüft ob wir Schreibzugriff auf den Stream haben (wenn ja sind wir der Eigentümer, ansonsten nicht) und handeln dann dementsprechend. Zum Schreiben/Lesen wird die Funktion stream.Serialize benutzt, die abhängig davon wem der NetworkView gehört entweder schreibt oder liest. Beim Schreiben wird der Wert des ersten Parameters an alle Clients "verteilt", beim Lesen wird der erste Parameter mit dem empfangenen Wert gefüllt. 6. Dead Reckoning Unitys Statesynchronisation ist relativ simpel, die Position wird alle ~15 Frames synchronisiert. Bei einer langsamen Verbindung kann das allerdings zu dem altenbekannten Lag führen. (Wenn Positionsupdates verspätet oder gar nicht ankommen.) Und die pure Positionssynchronisation ist auch oft recht "ruckelig". Deswegen gibt es eine Technik die sich Dead Reckoning nennt. Der Client bekommt statt der Position die Geschwindigkeit/Richtung des Spielers übertragen und berechnet die neue Position selber um sie nur gegebenenfalls zu berichtigen (und das am besten auch über mehrere Frames verteilt, damit es nicht zu "Sprüngen" kommt). Das wird Thema dieses Kapitels sein. Es haut bei mir gerade zeitlich nicht hin, ich werde das Tutorial (Kapitel 5 und 6) bald fortsetzen, heute geht leider nicht mehr Hier noch die Webplayer Version vom fertigen Projekt (Anhang) [server muss zuerst laufen]. Gute Nacht. Hier die Dateien: MPT2.html MPT2.unity3d
  8. 8 points
    Moin Leute. Ich hatte endlich mal richtig gut Zeit etwas an meinem Flipper zu basteln und ich will euch den aktuellen Stand hier mal zeigen. Ich bin total zufrieden mit dem Dingen und es macht mir richtig Spaß, weil es eben nicht nur programmieren ist, sondern eben auch gestalten. Ich kann ja alles irgendwie so ein bißchen. Und da ist dieser Flipper ein gutes Übungsprojekt, welches mich total erfüllt. Die Elemente erstelle ich in Cinema4D und nur wenige Dinge, sind modular nutzbar. Das macht zwar etwas Arbeit, wenn man merkt, dass dieses oder jenes nicht so funktioniert, aber man lernt halt auch worauf es ankommt. Bis jetzt habe ich gerade mal 11 kleine Scripts, die hauptsächlich für den Ton zuständig sind. Beleuchtung und Materialien/Texturen sind alle noch nicht in Arbeit. Wie die Spiellogik bzw. die Ziele sein sollen, das weiß ich schon zu 90%. Da ist noch einiges an Arbeit drin. Ist aber alles machbar. Denn bis jetzt habe ich gerade Mal so 50 Stunden investiert. (inklusive der Recherche im Inernet) Und wenn es auf 200 Std. hinaus läuft, dann ist das ein super Projekt für eine One-Man-Show. Bald kommt Neues!
  9. 8 points
    Um Variablen auch dann noch benutzen zu können wenn man eine Szene wechselt müssen diese speziell behandelt werden, hier sind einige Möglichkeiten: Statische Variablen: Mithilfe des static Keywords werden variablen statisch und behalten somit ihren Wert solange bis entweder die AppDomain entladen wurde (Spiel wird beendet, Player/Editor Modus Wechsel in Unity) oder der Thread in dem sie erstellt wurden beendet wurde (ThreadStorage static). Statische Variablen sind an keine Objektinstanz gebunden und überleben damit auch wenn es keine Instanz einer Klasse mehr gibt. Kurz und Knapp: Statische Variablen sind Globale Variablen die nur innerhalb einer Klasse definiert werden um einen Zugriffspunkt auf sie zu bekommen. Die Klasse selbst spielt dabei nur eine Rolle wenn es darum geht Zugriffsbeschränkungen zu haben (public, private, internal, protected) und ist damit nur eine Art namespace. public static float Score = 0; Man verwendet statische Variablen indem man den Klassennamen anstatt des Klasseninstanznamens verwendet, in dem sich die statische Variable befindet. Klasse.Variable = ...; Für Fortgeschrittene Benutzer: Möchte man statische Variablen benutzen und dennoch in verschiedenen Threads einen anderen Wert haben so benötigt man das ThreadStaticAttribute: [ThreadStatic] public static float Score = 0; Damit hat der Score Wert in jedem Thread einen anderen Wert. Statische Variablen haben den Vorteil dass sie sehr einfach zu verwenden sind. Der Nachteil allerdings ist dass sie überverwendet werden und damit einen eher schlechten Programmierstil fördern. Auch ist das managen der Lebenszeit dieser Variablen nicht möglich, zB müssen alle statischen Variablen explizit neu gesetzt werden wenn man sie resetten möchte, was bei vielen statischen variablen viel Schreibarbeit ist. Statische Variablen sind auch nicht persistent und verschwinden damit unwiederruflich nach Programmende. DontDestroyOnLoad: http://docs.unity3d....troyOnLoad.html Mithilfe dieser Methode kann man ein ganzes Unity.Object davor bewahren bei einem Szenenwechsel zerstört zu werden. Komponenten sind zB solche Unity.Objects. void Awake() { DontDestroyOnLoad(gameObject); } Wenn man ein solches Objekt dennoch löschen möchte muss man dies manuell erledigen, indem entweder das Containerobjekt (ein GameObjekt im Falle eines MonoBehaviours) zerstört wird oder das vor dem Löschen bewahrte Objekt selbst. Dazu kann man Destroy und/order DestroyImmediate benutzen. Destroy(gameObject); Auch hier verschwinden die Variablen natürlich wenn das Programm beendet wird wie bei den statischen Variablen. PlayerPrefs: Um Daten persistent zu speichern und wieder zu laden ist es nötig das speichern und laden selbst zu implementieren. Es gibt zum Glück im AssetStore genügent Tools die einen dies erleichtern. Aber auch Unity selbst liefert eine kleine Hilfestellung, die PlayerPrefs: http://docs.unity3d....layerPrefs.html PlayerPrefs.SetInt("Score", Score); Score = PlayerPrefs.GetInt("Score"); Damit werden die Daten lokal gespeichert und können auch wieder geladen werden. WWW Klasse: Um die daten global zu laden und zu speichern benötigt man wiederum etwas mehr als das was Unity bietet. zB benötigt man entweder einen eigenen Webserver oder ein über Netzwerkmethoden erreichbares Interface. Mithilfe von NodeJS oder anderen Frameworks lässt sich relativ einfach ein solcher Webserver aufbauen. Hat man also einen eigenen Webserver und entsprechende Schnittstellen kann man zB die WWW Klasse von Unity verwenden um Daten auf den Server zu oder herabzu laden. http://docs.unity3d....erence/WWW.html var scoreByteData = System.BitConverter.GetBytes(Score); // POST Request var www = new WWW("http://www.mydomain.de/api/score", scoreByteData); // GET Request var www = new WWW("http://www.mydomain.de/api/score"); ... Score = System.BitConverter.ToSingle(www.bytes, 0);
  10. 7 points
    ... und ich möchte euch als Moderator und vorallem als Privatperson einen guten Rutsch ins neue Jahr wünschen. Dieses Jahr gab es (meiner Meinung nach) nur wenig zu moderieren. Klar, hier und da musste mal ein doppelter Thread gelöscht oder ein falsch platzierter Thread verschoben werden. Es gab aber eigentlich keinen Streit oder rumgetrolle und somit war alles sehr harmonisch. Das finde ich gut und so soll's auch bleiben! Irgendwie kam ich dieses Jahr auch garnicht so recht dazu anderen Leuten hier zu helfen, denn immer wenn ich ins Forum geschaut habe, hatte @Zer0Cool schon die passende Antwort gegeben. Entweder ist er omnipresent oder ich bin nicht oft genug da. Jedenfalls gefällt es mir sehr, wie er sich hier für's Forum aufopfert. (Mir kommt es so vor, als baut er sogar alles nach um helfen zu können) @Sascha ist der Mann der Nacht. Alles was spät noch rein kommt, ist am Morgen beantwortet. Ich finde es sowieso interessant, wieviele Nasen um 3Uhr oder 4Uhr noch mit Unity kämpfen und dann Fragen im Forum stellen...die Jugend von heute ist schon toll! Natürlich sind nicht nur diese 2 Jungs da oben wichtig, sondern ihr alle, die ihr Fragen stellt, Fragen beantwortet oder eure Projekte vorstellt. Schön, dass ihr da seid und das Forum belebt. Ich wünsche euch für das neue Jahr, dass eure Projekte fertig werden! Spiele sind toll und was wäre die Welt ohne Spiele?! Ach so: Ich fände es schön, wenn wir im nächsten Frühjahr endlich mal wieder eine "Make a Game" Challenge machen würden. Denn das ist in der letzten Zeit leider viel zu kurz gekommen. Kommt gut ins neue Jahr!
  11. 7 points
  12. 7 points
    Hab am Freitag mein Erstdruck-Exemplar bekommen Wenn Ihr wissen wollt wie aufgeregt ich war (und immer noch bin), dann seht hier: Hier ist der im Video erwähnte Link: http://www.hummelwalker.de/2014/05/25/deutsches-unity-buch/
  13. 6 points
    Um dir bei dem gravierendsten Problem zu helfen: http://www.alfa-telefon.de/ Um dir bei dem Threadspezifischen Problem zu helfen: Dein ColliderScript reagiert nur auf GameObjekte mit dem Tag "Player", das ist korrekt ja? Du kannst schauen ob er überhaupt zu dem SendMessage kommt indem du ein print("test"); vor das SendMessage schreibst. Du kannst auch schauen ob er die Nachricht bekommt indem du was ähnliches in ApplyDamage vom anderen Script schreibst.
  14. 6 points
    Hi, ich wollte euch mal zeigen, woran ich die letzte halbe Stunde so gebastelt habe: (die Stützen waren nur fürs Foto.. lässt sich auch rumtragen) Das gibt einem zwar kein VR-Feeling wie Oculus Rift oder Google CardBoard (habe ich noch nie getestet, aber nehme ich mal an ), aber es reicht für ein paar Einblicke
  15. 6 points
    Hab mich mal für einen Prototypen dran gesetzt einen Maze Generator zu schreiben der einem immer wieder ein zufälliges Labyrinth erzeugt. Das ganze basiert auf dem Depth First Search Algorithmus.. da ich mir den Code gestern schnell selbst erstellt habe ist er noch nicht optimiert.. Informationen über einzelne Tiles im Labyrinth liegen momentan als Daten innerhalb einer Klasse vor.. über kurz oder lang werde ich aber alle Information in einen Integer speichern und über die einzelnen Bits auslesen. Dann werde ich bei Interesse auch den Code posten. Hier mal eine Webplayer Vorschau die nur den Algorithmus demonstriert. http://dl.dropbox.co...03837/Maze.html Gruß Stephan
  16. 5 points
    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:
  17. 5 points
    Bitte hört auf, von "einem void" zu reden. Gemeint ist eine Methode ohne Rückgabewert. Ich sage ja auch nicht "ein GameObject", wenn ich eine Methode mit GameObject als Return-Wert habe.
  18. 5 points
    Ihr glaubt's bestimmt nicht, aber ich sitze schon dran... der Forenverwaltungs-Wechsel hat irgendwie Narben hinterlassen
  19. 5 points
    Okay, sorry, aber den Stress, den du hier veranstaltest, muss sich hier keiner geben. Tschüss!
  20. 5 points
    High Quality Urban Texture Pack Das aktuelle Update (14 Materials mit Diffuse, Normal und Gloss) ist derzeit in Prüfung des Asset Stores und wird in Kürze released. Asset Store: http://u3d.as/7Pw Screenshots:
  21. 5 points
    Mal ein ein paar Stunden was anderes machen als Codezeilen zusammenhacken... Zum warm werden - WIP
  22. 5 points
    Der letzte Post ist über ein halbes Jahr alt? Das gibts doch nicht! Nach längerer Zeit habe ich mich wieder an Roboticos gesetzt, um dieses Projekt doch irgendwann einmal zu Ende zu bringen. Es geht seit ein paar Wochen wieder langsam voran - wenn man den ganzen Tag in einer Spielefirma Praktikum macht, ist am Abend eben oft die Luft raus für eigene Projekte. Aber das soll mich nicht aufhalten! . Hier könnt ihr den Meister der Stadt sehen, der den Spielerroboter gebaut hat und ihn zum Beginn des Spiels in seine Aufgabe einweist. Und eine aktuelle Spieleszene (Work in Progress)- die Brücke ist gesperrt und so muss ein alternativer Weg auf die andere Seite der Schlucht gefunden werden. Wenn der Zahnradriemen nur nicht so schnell durchlaufen würde.... Schönen Abend! Dominik
  23. 5 points
    hey leute Manche kennen vl mein anderes Projekt mit dem typen am Frosch. Leider hab ich gestern wegen einem fileverlust 2tage arbeit verloren Aus wut auf dieses projekt musste ich was anderes machen...
  24. 5 points
    Dieses kurze Tutorial ist die Fortsetzung von Scripten in Unity für Einsteiger und Scripten in Unity für Nicht-mehr-Einsteiger. Es behandelt Arrays, einen wichtigen Variablentyp beim Programmieren. Noch mehr Scripten in Unity - Teil 1: Arrays und Existenzoperator Hat man sich jetzt etwas mit Programmieren auseinander gesetzt, und bastelt man vielleicht schon an einem eigenen Spiel mit eigenen Scripts, so ist man vielleicht schon einmal über den Begriff "Array" gestolpert. Array ist ein absolut unverzichtbarer Datentyp, der mehrere Variablen beinhaltet, wie ein Regal Bücher. In Unity gibt es für Javascript zwei Sorten von Arrays. 1. Statische Arrays (built-in arrays / native arrays) Dieser Arraytyp hat eine bestimmte Anzahl von "Fächern", also Variablen, für deren Werte darin Platz ist. Eine Variable in einem Array wird als "Element" bezeichnet. Der Typ der darin vorhandenen Elemente ist determiniert (festgelegt). Deklariert wird er wie folgt: var zahlen : int[]; Einfach zwei eckige Klammern ( [ und ] ) hinter einen beliebigen Datentypen setzen, schon hat man nicht mehr eine einzelne Variable dieses Typs, sondern gleich ein ganzes Array. Bevor man nun Werte zuweisen kann, muss das Array initialisiert werden, damit klar ist, was für eine Größe (length) es hat, sprich: Wie viele Elemete hinein passen. Das geschieht dann so: zahlen = int[10]; Oder gleich so: var zahlen : int[] = int[10]; Jetzt passen 10 int-Variablen hinein. Hinweis: Ein normal deklariertes Array lässt sich über den Inspektor im Unity Editor einstellen, und zwar inklusive Größe. Daher ist die Größe in diesem Fall nicht unbedingt sicher. Will man nun auf die einzelnen Elemente im Array zugreifen, helfen wieder die eckigen Klammern: zahlen[3] = 5; print("Die Zahl im Array mit index 3 hat den Wert "+zahlen[3]); Die Größe eines Arrays, also die Anzahl der Elemente, kann mit "length" abgefragt (nicht aber geändert) werden. print("Wir haben "+zahlen.length+" Zahlen im Array."); Will man die Größe eines statischen Arrays zur Laufzeit (also: Während das Spiel läuft) ändern, muss man es neu initialisieren, und dabei gehen alle darin enthaltenen Werte verloren. Will man diese sichern, muss man sich eine Funktion dafür zusammenbauen, die die Werte in einem anderen Array zwischenspeichert, das alte Array initialisiert und die Werte zurück kopiert. Das ist keine sehr schöne Sache, und obwohl statische Arrays sehr schnell arbeiten, sollte man in solchen Fällen zu dynamischen Arrays greifen. 2. Dynamische Arrays (js-arrays) Ein Dynamisches Array kann jederzeit vergrößert oder verkleinert werden, einfach, indem man ein neues Element anhängt oder eines löscht. Zudem kann dieser Arraytyp Variablen verschiedener Art beinhalten. Wer schon einmal etwas von objektorientierter Programmierung gehört hat: Das geht, weil es Werte des Typs "Object" verwendet, von dem alle Datentypen erben. Ist aber nicht weiter wichtig Ein dynamisches Array wird wie folgt deklariert und initialisiert: var sachen : Array; sachen = Array(); Oder gleich: var sachen : Array = Array(); Wie man sieht, muss keine Länge angegeben werden, ein dynamisches Array hat am Anfang die Größe Null. Ein Element anhängen kann man mit Push(): sachen.Push("Hallo, dies ist ein Satz."); sachen.Push(17+4); var geht : boolean = true; sachen.Push(geht); Jetzt steht in dem Array: {"Hallo, dies ist ein Satz.", 21, true} Schick. Trotz der Tatsache, dass es anders geht, empfehle ich dringend, nur Variablen eines Typs in einem Array unter zu bringen. Ansonsten kann man beim Auslesen der Werte ernsthafte Probleme bekommen. Auslesen kann man das Array auf verschiedene Arten, die erste kennst Du nun schon: print(sachen[0]); Eine weitere ist Pop(). Ja, Pop. Diese Funktion heißt so, seit es dynamische Arrays gibt, also keine Witze. Pop() gibt das als letzte Element des Arrays zurück und entfernt dieses gleichzeitig aus dem Array. Pop() ist daher eher dafür geeignet, ein Array "abzuarbeiten", wenn also die Werte jeweils nur ein Mal ausgelesen müssen. var dingens = sachen.Pop(); Beachtet, dass ich "dingens" keinen Datentyp zugewiesen habe, da sachen völlig unsaubererweise alle möglichen Datentypen gemischt hat. Mit dingens kann man jetzt weiter arbeiten. Kommt bloß nicht auf die Idee, mehrere Aktionen hintereinander mit Pop() auszuführen! VÖLLIG FALSCH: var zahlen : Array = Array(); zahlen.Add(6); zahlen.Add(9); if(zahlen.Pop() > 7) print(zahlen.Pop()+" ist größer als 7."); Wer sich nämlich alles gut durchgelesen hat, sieht: die beiden Aufrufe von Pop() geben unterschiedliche Werte zurück, da nach dem ersten Aufruf Pop() die 9 aus dem Array entfernt hat. Weist also immer einer Variable den Rückgabewert von Pop() zu und arbeitet damit weiter, anstatt den Wert direkt zu benutzen. Alles klar? Weiter im Text. Will man auf den letzen Wert eines Arrays zugreifen, ohne ihn zu löschen, muss man die eckigen Klammern und "length" benutzen: zahlen[zahlen.length-1] Warum -1? Erinnere: Der Index fängt bei Null an, length gibt aber 1:1 die Anzahl der Elemente zurück. Das letzte Element hat also den Index length-1. Das gilt natürlich auch für statische Arrays. Will man nun Elemente entfernen, die nicht ganz am Ende sind (ansonsten hilft Pop(), einfach ohne den Rückgabewert zu beachten), benutzt man RemoveAt(): sachen.RemoveAt(1); Diese Anweisung entfernt das Element mit Index 1 aus "sachen". Alle nachfolgenden Elemente rücken nach! Was also vorher den Index 2 hatte, hat nun 1, was 3 hatte, hat nun 2 usw. Die Größe des Arrays (length) verkürzt sich dadurch, wie auch bei Pop(), um 1. Manchmal möchte man Elemente an den Anfang anhängen oder vom Anfang "poppen" (hör auf zu lachen...). In diesem Fall gibt es netterweise Shift() und Unshift(). Sie funktionieren wie Push() und Pop(), nur eben am Anfang anstatt am Ende. Shift() fügt dabei hinzu, Unshift() entfernt ein Element. Beachte immer die dadurch entstehenden Indexänderungen, weil alle anderen Elemente Auf- oder Nachrücken. Ansonsten kann man noch andere Funktionen von dynamischen Arrays benutzen: Clear() - Löscht alle Elemente des Arrays Reverse() - Dreht das Array einmal um, das letzte Element ist dann vorne, usw. Sort() - Sortiert alle Elemente des Arrays. Hat man z.B. Zahlen darin, so wird die Reihenfolge der Elemente so geändert, dass die kleinste Zahl das erste Element wird, usw. Einen vollständigen Überblick über die Array-Funktionen gibt es hier: http://unity3d.com/support/documentation/ScriptReference/Array.html Arrays traversieren (mehrere Elemente auslesen) Alles folgende gilt für beide Arraytypen! Ein Element auslesen, schön und gut. In den meisten Fällen aber will man mehrere Elemente hintereinander auslesen, und da hilft in der Regel die For-Schleife: for(var i : int = 0; i < zahlen.length; i++) print("Die Zahl mit dem Index "+i+" hat den Wert "+zahlen[i]); Beachte: bei zahlen.length kein -1, da ich < (kleiner als) und nicht <= (kleiner gleich) benutze. Das -1 erübrigt sich so. Die For-Schleife wird von vielen ständig unterschätzt, mit ihr kann man noch auf andere weise arbeiten, die aus anderen Programmiersprachen vielleicht als "foreach" bekannt ist: for(var value in zahlen) { print(value); } "zahlen" ist wieder ein Array und "value" eine geschwind deklarierte Variable, ähnlich wie "i" in der Schleife davor. Der Code in den geschweiften Klammern ( { und } ) wird dann für jedes Element ein Mal ausgeführt und "value" übernimmt bei jedem Durchlauf den jeweiligen Wert. Null und der Existenzoperator Wir machen gleich mal etwas richtig cooles, aber dafür muss noch ein bisschen Wissen her. Und zwar: Alle Variablen, die keinen primitiven Datentyp haben (int, boolean, float, ...), sondern einen nicht-primitiven (z.B. GameObject, Vector oder auch Arrays), haben den Wert null, wenn sie nicht initialisiert sind. Versucht man mit einer nicht initialisierten, nicht-primitiven Variable zu arbieten, gibt es meist einen Fehler: var richtung : Vector3; function Start() { richtung.x = 5; //FEHLER!!! "richtung" ist nicht initialisiert! } Der Fehler heißt dann "NullReferenceException". Man kann den Wert null für Abfragen benutzen: if(richtung != null) richtung.x = 5; In diesem Fall gibt es keinen Fehler (aber natürlich auch kein x = 5). Unity macht uns das noch ein Stück einfacher und lässt uns alle nicht-primitiven Variablen wahlweise wie booleans behandeln: if(richtung) oder if(!richtung) Erstere Abfrage wird in unserem Fall false ergeben, die zweite somit true. Diese Möglichkeit nenne ich gerne "Existenzoperator". Ein dynamisches Array abarbeiten Hier gibt's jetzt mal einen Code für Fortgeschrittene! Und zwar eine Zuweisung in einer Abfrage. var sachen : Array = Array(); //Array füllen... var ding; while(ding = sachen.Pop()) { print(ding); } In diesem Code wird bei jedem Aufruf der While-Schleife der Variable "ding" der Wert des jeweils letzten Elements von "sachen" zugewiesen, anschließend kann innerhalb des Funktioneskörpers mit diesem Wert gearbeitet werden, indem man "ding" benutzt. Die Zuweisung wird vor der While-Abfrage ausgeführt, und die Abfrage schaut am Ende nur nach "ding" und beachtet alles ab dem Gleichzeichen nicht mehr. Da Unity uns den Existenzoperator bereitstellt, hört die while-Schleife auch auf, sobald das Array leergepoppt wurde (ok, jetzt darfst Du lachen). Arrays in Unity Abgesehen von den überall vorhandenen Nutzungsmöglichkeiten von Arrays, ist es gut, wichtige Arrays in Unity zu kennen. Eines ist zum Beispiel der Rückgabewert von GameObject.FindGameObjectsWithTag(tag), in dem alle GameObjects des aktuellen Levels mit dem Tag "tag" stehen. Forscht man ein wenig in der Scriptreferenz herum, begegnet man immer mehr Arrays. Abschließende Worte Wow, jetzt hast Du echt schon einiges gelernt. Wenn Du das, was Du bis jetzt in den drei Tutorials von mir nicht nur gelesen, sondern auch verstanden hast, musst Du dich offiziell nicht mehr Anfänger nennen, denn Du hast die wichtigsten Aspekte des Programmierens erlernt. Glückwunsch!
  25. 5 points
    Howdi, wie wäre es damit das wir uns mal ein wenig über den AssetStore und den damit einhergehenden Uploadprozess unterhalten. Einige von hier haben das ganze ja schon hinter sich und damit auch einhergehende Probleme. Aktuell warte ich nun schon fast 20 Tage darauf dass das FXLab Asset freigegeben wird. Was ich schon sehr böse finde. Dabei und durch vorherige Assets habe ich folgende Punkte gelernt die dabei wichtig sind damit das Asset angenommen wird, ob jeder Punkt getestet wird hängt wohl davon ab wer das Asset bei Unity grade testet: Unity Technologies möchte das Unity Unity und Unity Pro Unity Pro genannt wird, auch nur das und nichts wie Unity3D, Unity Indie, Basic oder Free. Kontaktinformationen müssen in der Dokumentation stehen, damit sich Kunden auch melden können. Am besten nicht versteckt in einem Unterpunkt, so einfach wie möglich so klar ersichtlich wie möglich. Wenn eine Schritt für Schritt Anleitung für die Benutzung angeführt wird dann sollte das Ergebnis davon gut aussehen (!). Die AssetStore Leute achten wohl darauf und führen die Schritte selbst aus. Die Dokumentation muss als pdf Datei im Asset beiliegen, eine html Datei oder ähnliches reicht wohl nicht. Keine Demofeatures ins Asset einbauen die in einer Pro version des Assets aufgehoben werden. Assets müssen ohne Einschränkungen funktionieren, also Demo Features ausbauen. Auch dauert das bearbeiten eines Assets aktuell keine max 3 Buissnesstage, meine Erfahrung sagt das es aktuell wohl eher eine ganze Woche dauert. AUCH wenn es nur kleine Änderungen gibt und das Asset schon released oder abgelehnt wurde. Allgemeine Tipps zur Präsentation und dem Verkauf: Die Beschreibung des Assets sollte einfach und mit wenigen Füllwörtern aufgeschmückt werden, sagt was Sache ist und redet nicht um den heißen Brei herum. Screenshots sind ein Muss, selbst wenn das Asset nur aus Code besteht, irgendwas kann man immer Zeigen, auch wenns nurn Screenshot des Quellcodes ist. Eine Dokumentation zum ansehen bevor man sich das Asset kauft ist Gold wert, insbesondere bei Code Assets. Videos oder Webplayer ermöglichen es den potentiellen Käufern sich vom Asset zu überzeugen, Blindkäufe mag keiner. Wenn Screenshots oder Beispielszenen nicht im Asset vorhanden sind, dann erwähnt das, ansonsten kann es negative Kommentare geben. Wenn möglich angeben ob es überall oder wo genau es funktioniert, das schließt ein welche Unity Variante man benötigt (Pro oder nicht Pro) sowie iOS, Android, etc. dies lässt sich schwer testen, aber dazu gibt es Betatests oder Freunde. Support für das Produkt, kein Support, sehr schlechte Reviews. Haltet eure Asset Logos/Bilder Stilvoll und Professionell, dies ermöglicht es einem bei einem guten Asset geshowcased zu werden, was dann wiederum massiven Verkäufen entspricht. Macht Werbung in den Offiziellen Unity Foren, WIP für laufende Eeiter/Entwicklungen. Showcase wenn ihr Projekte habt die euer Asset benutzen/zeigen und Assets&AssetStore um eure fertigen Assets direkt zu promoten. Präsenz bedeutet Verkäufe, selbst wenn es nur Support ist. Ist der Thread auf der ersten Seite sichtbar ist schon die halbe Miete gewonnen. (dennoch nicht spammen) Achtet auf eine gute ausdrucksweise, kein Slang oder ähnliches, bleibt professionell, auch wenn es böse/falsche Kritik gibt, oft springen einen andere User zur Seite.
  26. 4 points
    Hi, ich habe in den letzten zwei Jahren immer wieder an einem Flüssigkeit Simulations Shader gearbeitet und seit Mitte letzten Monats ist er jetzt im AssetStore erhältlich. Mit Fluid Flow kann man in Echtzeit simulieren wie beispielsweise Blut oder Farbe an einer Oberfläche herunter fließt. Es ist keine 100% physikalisch korrekte Simulation, sondern das Asset versucht mit möglichst geringen Performance Einbußen überzeugend Flüssigkeit zu simulieren. Dazu werden die Berechnungen mithilfe eines Compute Shaders auf die Grafikkarte ausgelagert. Compute Shader benötigen allerdings mindestens DirectX 11 oder OpenGL 4.3 support! Zudem sind Android und iOS momentan nicht unterstützt! Fluid Flow Echtzeit Flüssigkeit Simulation dynamisches Anpassen der Flussrichtung unterstützt animierte Objekte Tropfen* * werden asynchron von der GPU zur CPU gelesen. Dieses Feature gibt es erst seit Unity 2018.2, und mit OpenGL wird es nicht unterstützt! Demo (win): https://drive.google.com/drive/folders/1ewcJn2Cc56Pcg3IVXPgvDDYucfRrU2mg Asset Store: https://assetstore.unity.com/packages/vfx/shaders/fluid-flow-143786
  27. 4 points
    Huhu Leute, ich bin gerade dabei eine Wood Texture Hand Painted zu entwerfen und würde einfach mal gern eure Meinung dazu hören, wie sie denn bisher aussieht und wie ihr sie generell findet. Hab sowas noch nie gemacht deshalb ist mir die Meinung von anderen sehr wichtig Also hier ist sie, habe bisher nur das oberste Eck komplett gemalt. (ca. 2 Stunden Arbeit) Und hier eine neue Version etwas mehr ist nun bemalt und ich habe versucht auf das bisherige Feedback einzugehen. Hoffe das ist mir wenigstens etwas gelungen! (ca. 4 Stunden Arbeit) Feedback ist wieder gern gesehn! Habe das ganze mal in Blender auf nen einfachen Cube gepackt und gerendert. Image qualitiy ist nicht sooo gut wie in Blender aber finde es kann sich durchaus sehn lassen und für ein Game wäre er meiner Meinung nach durchaus schon geeignet. (ohne mich selbst zu hoch feiern zu wollen :D )
  28. 4 points
    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:
  29. 4 points
    Da sind jede Menge Antworten möglich, da da jede Menge Fragen drinstecken. Wie stellst du fest, dass der Spieler berührt wurde? > OnCollisionEnter? Distanz ausrechnen? Raycast? Wie ermittelst du, dass er nicht nur eine Wand berührt hat, sondern dass es das Monster war? > GetComponent? CompareTag? Wie lädst du das Hauptmenü? > SceneManager.LoadSettings... da gibt's nicht viele andere Möglichkeiten Das sind nun aber alles Themen, die in den ersten paar Tutorials angesprochen werden. Daher würde ich empfehlen, ein paar davon anzusehen
  30. 4 points
    Hi Nico und willkommen im Forum zu deiner Frage: Gerade bei Anfänger werden Fragen oft nach dem Motto gestellt ist dies mit Unity möglich.... Die Frage sollte aber eher lauten ob du das kannst ? Die Engine nimmt dir zwar viel Arbeit ab aber ein Zauber Programm ist es dennoch nicht. All diese Sachen die du gerne umsetzen willst musst du entwickeln und nicht Unity. Auch macht man ein Spiel nicht nur in Unity sondern in vielen unterschiedlichen Programme wie zb 3D Software, 2D Software, Texturierungs Software usw. Eine Ampel zb könnte man durch eine wechselde Textur (Rot, Geld, Grün) umsetzen so eine Textur würdest du dann vielleicht in einem 2D Programm bauen. Das Ganze Kombinierst du mit einer Emessiv das ist eine Textur die leuchtet. Dann brauchst du vielleicht noch ein Licht das du dann in Unity erstellst. das ganze steuerst du dann über ein Script. Weichen sind auch möglich die würdest du dann zb in einem 3D Programm modellieren und Animieren. Die weiche würdest du stellen in dem du eine Animation abspielst. Man kann auf jeden Fall all deine Fragen mit JA beantworten ,Unity kann das die frage ist wie gesagt ob du das kannst ? Grüße.
  31. 4 points
    Nach dem die Poststelle an meiner Hochschule den Virtualizer für gut eine Woche dem Mathelabor vorenthalten hat, konnte ich nun endlich das Konstrukt montieren. In diesem Thread möchte ich dazu einfach mal meine Erfahrungen teilen, da es im WWW sicherlich bisher kaum praxisnahe Infos zum Gerät gibt. Ich möchte hier aber auch kein ausfürliches Review verfassen. Die gelieferten Teile des Virtualizers waren gut und kompakt verpackt. Man merkt, dass einige kleinere Teile aus einem 3D-Drucker stammen, da es ein paar "unsaubere Oberflächen" gibt. Das ist an für sich nicht tragisch, da man diese Teilchen auch nicht wirklich sieht. Ansonsten machen die meisten Teile einen guten Eindruck. Hier und da sind diese etwas schmutzig, was wohl damit zu tun hat, dass das Gerät in der hauseignen Werkstatt gebaut wurde. Eine Massenproduktion gibt es noch nicht. Als kleiner Mangel ist nur der "Arm" aufgefallen. Die Bohrlöcher zum Fixieren stimmen leider nicht überein. Das Einstecken an der Säule bedurfte einer kleinen Vergrößerung des Montageloches im Plastikdeckel der Säule. Für den Arm werde ich letztlich wohl neue Löcher bohren. Das ist am Ende aber nur eine Kleinigkeit. Absolut eindeutig sind die Anleitungen nicht, aber insgesamt war die Montage nicht so schwer. Die Inbetriebnahme des Virtualizers liest sich wie folgt: Anschließen der Kabel (Strom & USB) Danach Kallibrieren (Ring min. einmal drehen und tiefsten und höchsten Punkt erfassen) Dann soll das Test GUI Tool genutzt werden um zu prüfen ob alles geht Leider konnte ich den Virtualizer noch nicht ans Laufen bekommen. Das Test GUI Tool erkennt keine bestehende Verbindung zum Virtualizer. Selbiges gilt für die Demos, welche keine Feedback über einen Fehler geben. Im Rahmen der Montageanleitung gab es quasi keine Schritte die irgendwie in Verbindung zur Verkabelung gebracht werden können, daher muss ich den Hersteller in das Troubleshooting einbinden. Hoffentlich bekomme ich eine Rückmeldung bis zum Wochenende, damit das Gerät auch mal richtig ausprobiert werden kann. Den Gurt habe ich aber trotzdem schon mal angelegt. Die Bodenplatte bietet sehr wenig Reibung. Vor allemder Schuhüberzug vermindert die Reibnung noch einmal mehr, im Gegensatz zur Nutzung mit Socken. Pauschal bleibe ich lieber bei den Socken, da man dann ein wenig mehr Griff an der Sohle hat. Kleine Schritte sind wohl das Weg zum Ziel. Wenn man richtig Gas geben möchte, dann kann man auch mal den Halt verlieren. Der Gurt und der Ring halten aber an für sich einen ordentlich im Spiel. Der Ring ist deutlich enger als ich ihn mir vorgestellt habe. Ich befürchte, dass Menschen mit breiter Hüfte den Virtualizer nicht nutzen können. Ich bin schon schlank und habe vielleicht 2-3 cm Luft zum Rand des Ringes. Das ist so ziemlich alles was ich euch bisher berichten kann. Ich hoffe, dass ich zeitnah mit der Entwicklung loslegen kann. Wenn jemand etwas Genaueres wissen möchte, dann gehe ich darauf gerne ein. Beispielsweise ist das die Version des Virtualizers mit Force Feedback. Sprich die Bodenplatte kann vibrieren. edit: Das Developer Center von Cyberith beschreibt das TestGUITool aks kompatibel zu Win7 und Win8. Die Unity und UE4 SDKs unterstützen Win10. Die Acan's Call Demo für die Oculus Rift DK2 kommt ohne Betriebssystemspezifikation daher. Also könnte es vielleicht bisher an Win10 liegen. Sobald ich wieder im Labor bin teste ich die SDKs. Ansonsten könnte ich auch auf die Schnelle ein Win8 auf eine Platte hauen.
  32. 4 points
    Grüße aus China ^^ Wer ist noch mit ans Bort um seine Apps in China zu verkaufen?
  33. 4 points
    Ich verstehe kein Wort.
  34. 4 points
    Hallo liebes Forum, ich weiß ehrlich gesagt gar nicht, wie ich diese Vorstellung aufziehen soll, deswegen schreibe ich einfach mal drauf los. Es geht um mein Übungsprojekt im 2D Platformer Stil, für welches ich leider noch keinen Namen habe. Ich lerne jetzt seit knapp einer Woche den Umgang mit Unity und C#. Ich arbeite lediglich in meiner Freizeit an diesem Projekt, weswegen der Fortschritt auf sich warten lässt. Am meisten Spaß macht es mir, mit bereits erlangten Wissen neue Dinge zu erstellen. Für mich ist es immer so, als würde ich Vokabeln lernen und plötzlich kann ich daraus Sätze bilden. Am meisten stolz bin ich auf mein "keySystem", denn dieses habe ich ohne Tutorials erstellt. Alles ist noch ein wenig buggy, aber ich hoffe mal, dass hier kein super Spiel erwartet wird!
  35. 4 points
    Meine Meinung zu Croudfunding (egal ob Kickstarter oder eine andere Plattform): Es lohnt nicht! (Oder nur ganz selten) Damit man überhaupt etwas zusammen bekommt, muss man schon ein besonderes Projekt haben. Etwas, was die Leute anspricht, worauf sie gewartet haben oder was sie einfach haben wollen, weil's hip oder nerdig ist. Kleine Projekte, wo vielleicht nur 1000€ zusammen kommen müssen, gehen ganz gut durch. Größere Projekte so gut wie gar nicht. Da gibt es nur ganz wenige Ausnahmen. Oculus Rift oder aber Elte:Dangerous sind solche Ausnahmen, weil sie eine riesen Fangemeinde ansprechen oder aber eine Technik bringen, die es in Grundzügen schon 1995 gab, aber einfach damals zu schlecht war und jetzt endlich das bringt, was die Leute haben wollen. Ein normales Spiel, in deinem Fall ein Jump & Run, wird es schwer haben. Wenn überhaupt, dann wird es nur laufen, wenn du schon viel Content hast, den du zeigen kannst. Wenn du eine super Story ins Spiel rein bringst oder einen tollen neuen Weg gehst. Die Leute müssen quasi begeistert werden. Und du musst dafür werben, denn bei Kickstarter alleine geht das Ganze unter. Kaum einer wird dann aber einfach nur so spenden. Fast alle wollen etwas haben und deswegen musst du auch einiges anbieten. Das Spiel alleine wird nicht ausreichen, wenn es um hohe Summen geht. Was man sich damit antun kann, zeigt das Beispiel "Castle Story" ! Alle, die mehr als 50$ gegeben haben, bekommen außerdem zum Spiel auch Ware in Form von Postern oder aber T-Shirts. Das waren bei diesen Jungs rund 1500 Sendungen! Das war eine mächtige Aufgabe. Die wurde wohl gelöst, aber am Spiel geht es einfach nicht vorran. Die Stimmung in der Community ist gekippt, denn 2 Jahre später ist immer noch kein fertiges Spiel entstanden. Diese Jungs werden wahrscheinlich nicht nocheinmal so eine Sache durchkriegen. Vielleicht ist es denen auch egal, sie haben ja über 700.000 $ alleine bei Kickstarter zusammen bekommen.
  36. 4 points
    Moin moin miteinander! Wer die letzten Tage im oberen Bereich des Forums mitgelesen hat, hat vielleicht bemerkt, dass die Benennung eines neuen Moderators im Gespräch war. Wir haben und entschieden, malzbie zu fragen (wer nicht erst seit gestern im Forum ist, muss nicht erklärt kriegen, warum ) und er hat zugestimmt. Ich habe daher eben ein bisschen Knöpchen gedreht und jetzt kann malzbie Themen verschieben, Leute verwarnen und Stellenmarkt-Threads freischalten. Was den Stellenmarkt angeht, da werden wir wohl demnächst Kommentare wieder an schalten. Da wir dafür aber Richtlinien brauchen, auf die man sich berufen kann, wenn man Posts wegzappen will, ohne dass sich jemand (u.U. zurecht) angegriffen fühlt, wird das nicht mehr heute abend geschehen Von daher: Willkommen im Team, malzbie!
  37. 4 points
    wenn ich mir mal selbst noch kurz auf die schulter klopfen darf.. http://www.travelindustryclub.de/go/award_winners/gallery=besttraveltechnologysolution das ding hat den 1. platz beim travel industry club in der kategorie best travel technology solution gemacht ick freu mir
  38. 4 points
    Bäääm da isser wieder der Harlyk hehe. Ich hatte ja bereits erwähnt das für mein Robohorse noch nen Spacecowboy erstellt werden soll. Nun bin ich damit angefangen und ich dachte ich zeig euch meinen Stand des WIPs nach 2,5 Stunden sculpting. Denke ich hab meine Technik gegenüber dem Pferd verbessert und komme nun viel schneller voran. Stört euch nicht an dem Glitzer das kommt weil ich mal nen Metallic Lack Shader testen wollte. Ja ich weiß, sieht scheisse aus nächstes mal nehme ich Altbewertes.
  39. 4 points
    Nicht böse verstehen, aber das Problem mit solchen Communityprojekte ist dass es meist von Leuten gestartet und betrieben wird die besser erst mal noch ein paar Einzelprojekte machen sollten. Ein Projekt bei dem du mehr als eine Person brauchst ist nichts um da das Entwickeln zu lernen. Da solltest du idealerweise das Gelernte nur noch anwenden. Und dann wäre da noch der Faktor Mensch. Ich persönlich habe die Erfahrung gemacht dass solche Communityprojekte ein gutes Mittel sind auch noch die dickste Freundschaft zu beenden ...
  40. 4 points
    So, melde mich mal wieder zu diesem Thema. Ich hatte ja beim letzten Post mitgeteilt, dass sich eine kleine Wendung ergeben hatte. Langsam sollte ich auch mal sagen was für eine das war - Kurz nachdem ich das hier gepostet hatte, kam unverhofft von einem Buchverlag die Anfrage, ob ich nicht ein Buch für sie schreiben wolle. Und genau das mache ich aktuell. Das gute Stück wird im Sommer/Spätsommer erscheinen und wird um die 400 Seiten umfassen.
  41. 4 points
    Moin, jo, wieder eine NGUI-Frage ^^ Ich habe vor, mein Menü von eden mittels NGUI zu realisieren. Klappt auch gut. Nur würde ich aber gerne wieder ein Hauptscript haben, was alle Abläufe erledigt. So wie es bei meinem vorherigen UnityGUI-Menü der Fall war (Source) Klicke ich z.B auf den Exit-Button, soll sich das Programm schließen. Nun könnte ich natürlich ein kleines Script a lá OnMouseDown = Application.Exit(); schreiben und es dem Button anhängen (und das bei allen Buttons so machen), aber das würde wohl wenig Sinn machen. Wie spreche ich also einzelne NGUI-Komponente (in JS) richtig an? Desweiteren hake ich bei der Möglichkeit fest, beim Klick auf den Buttons das jeweilige Menü (sei es Optionen- oder Level-Menü) ein und auszublenden. Das UIButton-Script lässt mich ja nur einmalig ein anderes GameObject (ein Panel z. aktivieren. Aber erneutes drücken des Buttons bewirkt danach nichts mehr. -Mauri
  42. 4 points
    Ich möchte ein kleines Rennspiel machen. Als Modellierungssoftware benutze ich Blender. Bin vor kurzem auf Unity gestoßen und lerne mich gerade mit Büchern und Tutorials ein, was die Engine kann. Vorher habe ich mit Gamestudio 3D gearbeitet. Aber leider lässt da der Android-Exporter auf sich warten. Zumal Unity mehrere Exporter beinhaltet. Der Webplayer ist noch interessant. Momentan bin ich dabei noch Lowpoly-Modelle für das Spiel zu erstellen. Aber das Fahrzeug fährt schon mal mit Joystick(Mobile assets) und Tastatur. Später sollen noch mehr Fahrzeuge und Strecken enstehen. http://www.youtube.com/watch?v=iPnIov3dBzE
  43. 4 points
    ------------------------------------------------------------------------- Du programmierts in JS?--> Teil 3:A* Pathfinder JS ------------------------------------------------------------------------- Willkommen zu Teil 3 des A* Pathfinding- Tutorials in Unity. In diesem Teil werde ich den A* Algorithmus in Unity umsetzten. Dieses Tutorial baut direkt auf Teil 2 auf und eine Testszene sollte bereits bestehen. Auch das Grundverständnis für den A* sollte da sein, wenn nicht vielleicht Teil 1 noch mal lesen. Anfangen tu ich indem ich ein neues C# Dokument anlege, ich nenne es PathNode, in dieser wird die Klasse PathNode deklariert, sie erbt von ScriptableObject und nicht von MonBehaviour da wir es nicht als Componente einem GameObject zuweisen möchten! Die Klasse PathNode soll zu jedem Wegpunkte alle Werte speichern die zur Pfadberechnung nötig sind. Dazu gehören der G-Wert, H-Wert, F-Wert, der dazu gehörende Wegpunkt und der parentPathNode der den PathNode speichert von dem aus dieser berechnet wurde. So sollte die Klasse aussehen: using UnityEngine; using System.Collections; public class PathNode:ScriptableObject { public float fValue = 0f; public float gValue = 0f; public float hValue = 0f; public PathNode parentPathNode; public Waypoint waypoint; } Damit hätten wir Schritt 1 schon erledigt! Jetzt erstellen wir die Klasse "PathFinder" einem gleichnamige C# File. using UnityEngine; using System.Collections; public class PathFinder:MonoBehaviour { } Die static Funktion die wir jetzt erstellen und später von überall aus aufrufen können um einen Pfad zurück zu bekommen nenne ich „GetPath“, sie bekommt 2 Parameter vom Typ Waypoint, den startWaypoint und den targetWaypoint. Als Rückgabe bekommen wir von der Funktion ein Array. In der Funktion deklarieren wir zunächst einmal alle wichtigen Variablen. Dazu gehören die openList und die closedList vom Typ ArrayList. In die OpenList kommen alle schon berechneten Wegpunkte bzw. die PathNodes die diese repräsentieren. In die closedList kommen die Wegpunkten von denen aus die Nachbarwegpunkte schon berechnet wurden. Die Variable currentPathNode beinhaltet den PathNode von dem aus die Nachbarn momentan berechnet werden. Das startField, ebenfalls vom Typ PathNode wird direkt mit dem startWaypoint instanziert, das targetField, nach dem ja gesucht wird bleibt zu Beginn noch null. Als letzte Variable haben wir noch das pathArray in dem der errechnete Pfad zurückgegeben wird. Zu Beginn werfen wir das startField auch gleich in die openList damit diese nicht leer ist, denn wenn die openList zu irgend einem Zeitpunkt der Berechnung einmal leer sein sollte heißt dass, das es keinen Weg zum Ziel gibt, In diesem Fall muss die Berechnung abgebrochen werden, aber dazu später. Damit ist der erste Schritt eledigt, die Funktion sieht momentan so aus: public static ArrayList GetPath(Waypoint startWaypoint,Waypoint targetWaypoint) { ArrayList openList = new ArrayList(); ArrayList closedList = new ArrayList(); PathNode currentPathNode; PathNode startField = new PathNode(startWaypoint); PathNode targetField = null; ArrayList pathArray = new ArrayList(); openList.Add(startField); //Berechnungen return pathArray; } Jetzt kommen wir zum Kern der Funktion, wir benötigen eine While- Schleife die solange ausgeführt wird bis das targetField gefunden ist oder die openList leer ist. Zuvor sollte aber noch getestet werden ob nicht zufällig der startWaypoint gleich der targetWaypoint ist damit es auch da nicht zu einem Fehler kommen kann. Also dort wo oben //Berechnungen steht kommt jetzt folgender Code hinein. if(startWaypoint != targetWaypoint) { while(targetField == null && openList.Count > 0 ) { //Berechnungen } } Würden wir die Funktion jetzt aufrufen würden wir in eine Endlosschleife kommen, also lassen wir das mal lieber. Was als nächstes kommen muss ist uns aus der openList das Element mit dem niedrigsten F-Wert heraus zu holen, als currentPathNode zu setzen und in die closedList zu stecken. Im ersten Durchgang ist natürlich nur ein Objekt in der openList das ändert sich aber ganz schnell. Um das zu machen brauchen wir aber ersteinmal eine Funktion die uns dieses Element aus der openList zurück gibt. Dafür schreiben wir unter die Funktion GetPath eine neue static Funktion, diese hab ich „ReturnPathNodeWithLowestFValue“ genannt, vielleicht fällt euch ja ein besserer Name ein . Die Funktion empfängt einen Parameter, nämlich ein Array, die openList. Zurück gibt uns die Funktion nur die Indexposition des PathNodes mit dem niedrigsten F-Wert in dem übergebenen Array. So sieht die Funktion aus. static public int ReturnPathNodeWithLowestFValue(ArrayList openL) { PathNode pathNode = null; int index = -1; if(openL.Count >0) { for(int i = 0; i < openL.Count; i++) { if(index == -1) { pathNode = (PathNode)openL[i]; index = i; } else if(pathNode.fValue > ((PathNode)openL[i]).fValue) { pathNode = (PathNode)openL[i]; index = i; } } } return index; } Als erstes deklarieren wir in dieser Funktion die Variable pathNode die uns das Element zwischenspeichert das momentan den niedrigsten F-Wert hat. Die Variable index speichert dessen Indexposition im Array, zu Beginn steht diese auf -1 um anzuzeigen dass noch kein Element als pathNode zwischengespeichert wurde. Anschließend kommt eine If-Anweisung die einfach noch mal kontrolliert ob die openList wirklich nicht leer ist erst dann geht’s mit einer For-Schleife weiter in der wir das gesamte Array durchlaufen. In dieser stehen 2 If- Anweisung, die erste schaut ob der Index momentan auf -1 steht, d.h. das dies momentan der erste Durchlauf der For-Schleife ist, damit wird das erste Element des Arrays auch direkt als pathNode abgespeichert weil es eben bisher den niedrigsten F-Wert hat. In der zweiten If-Anweisung wird geschaut ob der fValue des Objektes an Stelle i der openList kleiner ist als der fValue des zwischengespeicherten Objektes pathNode dessen fValue bis dahin am kleinsten war. Ist der fValue dieses Objekts nun kleiner wird dieses als pathNode und dessen Index abgespeichert. Wenn Die For-Schleife durch ist haben wir die Indexposition des PathNodes in der openList mit dem niedrigsten fValue und geben diesen Index zurück. Jetzt können wir wieder in die Funktion GetPath gehen und endlich den currentPathNode ein Objekt zuweisen. Dazu fügen wir direkt in die While-Schleife folgende Zeilen ein. int index = ReturnPathNodeWithLowestFValue(openList); currentPathNode = (PathNode)openList[index]; openList.RemoveAt(index); closedList.Add(currentPathNode); Jetzt haben wir also den PathNode mit dem niedrigsten F-Wert gefunden, als currentPathNode abgespeichert, aus der openList entfernt und in die closedList geworfen. Nun können wir uns daran machen alle Nachbarwegpunkte dieses PathNodes bzw. dessen Waypoints zu berechnen. Was wir dafür zunächst machen ist uns dessen Nachbarwegpunkte ersteinmal in einem Array zwischen zu speichern um schneller an diese ran zu kommen. ArrayList neighbourWaypoints = currentPathNode.waypoint.waypointsInRange; Anschließend machen wir folgendes: Wir schreiben uns eine For-Schleife in der wir alle Nachbarwegpunkte durchlaufen, für jeden Nachbarwegpunkt müssen wir aber nun zunächst kontrollieren ob dieser nicht schon als PathNode in der openList oder der closedList abgelegt wurde. Zunächst überprüfen wir auf die closedList, denn sollte dieser Wegpunkt dort schon drin liegen heißt dass für uns das wir für diesen keinesfalls mehr irgendwelche Berechnungen durchführen müssen. Um jetzt aber heraus zu bekomme ob der Wegpunkt schon in der closedList liegt brauchen wir wieder eine Funktion. Diese schreiben wir wieder unter die Funktion GetPath, ich habe dies „IsInCL“ genannt. Diese empfängt 2 Parameter, einmal die closedList und den Wegpunkt nach dem gesucht werden soll. Zurück gibt uns die Funktion einen booleschen Wert, also ob er drin ist oder nicht. Die Funktion sieht so aus: public static bool IsInCL(ArrayList closedL, Waypoint wayPoint) { bool isIn = false; for(int i = 0;i<closedL.Count;i++) { if(((PathNode)closedL[i]).waypoint == wayPoint) { isIn = true; break; } } return isIn; } Ich denke die Funktion ist selbsterklärend, wir haben einen Boolean der zu Beginn auf false steht und uns angibt ob der Wegpunkt schon drin ist. Jedes Element der closedList wird dann durchlaufen. Wurde der Wegpunkt gefunden wird der Boolean auf true gesetzt und die For-Schleife abgebrochen. Sollte der Wegpunkt nicht gefunden werden wird false zurückgegeben. Da wir auch die openList durchsuchen müssen falls der Wegpunkt nicht schon in der closedList ist brauchen wir dafür auch noch eine Funktion, diese sieht im Grunde genauso aus wie IsInCl allerdings liefert sie statt eine Boolean die Indexposition in der openList zurück wenn der Wegpunkt in der openList liegen sollte. Liegt der Wegpunkt nicht in der openList wird -1 zurückgegeben. Diese Funktion habe ich „IsInOL“ genannt und sie sieht so aus: static public int IsInOL(ArrayList openL, Waypoint wayPoint) { int index = -1; for(int i = 0; i<openL.Count; i++) { if(((PathNode)openL[i]).waypoint == wayPoint) { index = i; break; } } return index; } Damit hätten wir schon fast alle Funktionen die wir brauchen, nur ein fehlt uns noch aber die kommt später. Jetzt können wir uns wieder an die Funktion GetPath machen und die Nachbarwegpunkte des currentPathNode darauf kontrollieren ob sie schon in einer der beiden Listen liegen. Wir fangen also damit an zu überprüfen ob der Wegpunkt schon in der closedList ist bzw. ob er nicht drin ist, dafür schreiben wir eine If-Anweisung: if(!IsInCL(closedList,neighbourWaypoints[i])) Ist der Wegpunkt nun nicht in der closedList, gibt uns IsInCL also false zurück muss noch geschaut werden ob er in der openList ist. Dafür deklarieren wir zunächst in dieser If-Anweisung eine Variable „indexInOL“ vom Typ Integer und übergeben ihr sofort den Rückgabe Wert des Funktionsaufrufes IsInOL: int indexInOL = IsInOL(openList,neighbourWaypoints[i]; Nun wissen wir wenn der Rückgabewert größer oder gleich 0 ist und nicht -1 dann liegt der Wegpunkt in der openList. Ist dies der Fall muss der G-Wert dieses Wegpunktes bzw. dessen PathNodes eventuell neu berechnet werden. Dafür schreiben wir jetzt unter die Variable indexInOL eine weiter If-Anweisung in der genau darauf kontrolliert wird ob der Index größer gleich 0 ist. if(indexInOL >= 0) Für den Fall das der Rückgabe Wert -1 ist schreiben wir zu dieser If-Anweisung noch ein else in der wenn nötig ein neuer PathNode zu diesem Wegpunkt angelegt wird. So sieht bis dahin unsere gesamte Funktion GetPath aus: public static ArrayList GetPath(Waypoint startWaypoint,Waypoint targetWaypoint) { ArrayList openList = new ArrayList(); ArrayList closedList = new ArrayList(); PathNode currentPathNode; PathNode startField = new PathNode(startWaypoint); PathNode targetField = null; ArrayList pathArray = new ArrayList(); openList.Add(startField); if(startWaypoint != targetWaypoint) { while(targetField == null && openList.length > 0 ) { int index = ReturnPathNodeWithLowestFValue(openList); currentPathNode = (PathNode)openList[index]; openList.RemoveAt(index); closedList.Add(currentPathNode); ArrayList neighbourWaypoints = currentPathNode.waypoint.waypointsInRange; for(int i = 0;i<neighbourWaypoints.Count;i++) { if(!IsInCL(closedList,(Waypoint)neighbourWaypoints[i]))//Wenn der Wegpunkt nicht in der closedList ist { int indexInOL = IsInOL(openList,(Waypoint)neighbourWaypoints[i]); if(indexInOL >= 0)//Wenn der Wegpunkt in der openList ist { //Eventuell neu berechnen } else//Wenn der Wegpunkt nicht in der openList ist { //Neuen PathNode erzeugen } } } } } return pathArray; } Das Grundgerüst des A* steht schon, jetzt müssen wir nurnoch die PathNodes erzeugen. Wir machen weiter mit der else – Verzweigung in der die neuen PathNodes zu noch nicht berechneten Wegpunkten instanziert werden, erst danach kümmern wir uns um die If-Anweisung oben drüber. Was wir als erstes machen müssen ist den G-, H- und F-Wert zu berechnen, dazu legen wir uns 3 neue Variablen vom Typ float an. GValue, HValue und FValue. Diese berechnen wir auch gleich. Der G-Wert ergibt sich wie wir wissen aus dem G-Wert des currentPathNodes von dem dieser Wegpunkt berechnet wird plus die Entfernung von currentPathNode zum Wegpunkt. float GValue = currentPathNode.gValue + Vector3.Distance(currentPathNode.waypoint.transform.position,((Waypoint)neighbourWaypoints[i]).transform.position); Im dreidimensionalen brauchen wir natürlich keine Schätzwerte verwenden wie ich es in Teil 1 des Tutorials getan habe, dies ist ja nur nötig wenn man die Karte in ein Raster unterteilt hat und jedes Feld in einem Mehrdimensionalen Array abgelegt hat. Wir können also die tatsächlich Entfernung verwenden. Als nächstes der H-Wert. float HValue = Vector3.Distance(((Waypoint)neighbourWaypoints[i]).transform.position,targetWaypoint.transform.position); Dieser ermittelt sich aus der Entfernung vom Wegpunkt zum Ziel. Zu guter letzt noch der F-Wert der sich aus der Addition von G und H ergibt. float FValue = GValue + HValue; Jetzt müssen wir nur noch den neuen PathNode erzeugen und ihm die Werte als auch seinen parentPathNode, also unseren currentPathNode von dem dieser berechnet wurde zuweisen. Anschließend werfen wir diesen neuen PathNode auch gleich in die openList. PathNode newPathNode = (PathNode)ScriptableObject.CreateInstance<PathNode>(); newPathNode.waypoint = (Waypoint)neighbourWaypoints[i]; newPathNode.gValue = GValue; newPathNode.hValue = HValue; newPathNode.fValue = FValue; newPathNode.parentPathNode = currentPathNode; openList.Add(newPathNode); Jetzt fehlt natürlich noch etwas ganz wesentliches, und zwar die überprüfung ob der Wegpunkt nicht auch das gesuchte Ziel ist. Dazu schreiben wir einfach eine weitere If-Anweisung unter das gerade geschriebene in der wir dies abfragen. Sollte es wirklich der gesuchte targetWaypoint sein wird der eben erzeugte PathNode an das targetField übergeben und die For-Schleife mit einem break abgebrochen. if(newPathNode.waypoint == targetWaypoint) { targetField = newPathNode; //Pfad berechnen break; } An der Stelle an der momentan „//Pfad berechnen“ steht rufen wir später noch eine Funktion auf die uns den Pfad in unser pathArray schreibt. So sieht es momentan aus: int indexInOL = IsInOL(openList,neighbourWaypoints[i]); if(indexInOL >= 0)//Wenn der Wegpunkt in der openList ist { //Eventuell neu berechnen } else//Wenn der Wegpunkt nicht in der openList ist { float GValue = currentPathNode.gValue + Vector3.Distance(currentPathNode.waypoint.transform.position,((Waypoint)neighbourWaypoints[i]).transform.position); float HValue = Vector3.Distance(((Waypoint)neighbourWaypoints[i]).transform.position,targetWaypoint.transform.position); float FValue = GValue + HValue; PathNode newPathNode = (PathNode)ScriptableObject.CreateInstance<PathNode>(); newPathNode.waypoint = (Waypoint)neighbourWaypoints[i]; newPathNode.gValue = GValue; newPathNode.hValue = HValue; newPathNode.fValue = FValue; newPathNode.parentPathNode = currentPathNode; openList.Add(newPathNode); if(newPathNode.waypoint == targetWaypoint) { targetField = newPathNode; break; } } Jetzt kümmern wir uns um den Fall das der Wegpunkt schon berechnet in der openList liegt. Dafür müssen wir in der If-Anweisung if(indexInOL>=0) den G-Wert des Wegpunktes neu berechnen und schauen ob er eventuell kleiner ist als der bisherige. Dafür legen wir uns wieder eine neue Variable vom Typ float an, ich nenne sie newGValue. Der neue G-Wert ergibt sich aus der Addition des G-Wertes des currentPathNode + die Entfernung von diesem zum Wegpunkt. Wenn dieser nun berechnet ist müssen wir in einer weiteren If-Anweisung überprüfen ob der G-Wert kleiner ist als der alte. Ist dies der Fall wird der parentPathNode auf den currentPathNode geändert, der neue G-Wert übergeben und der F-Wert neu berechnet. Das ganze sieht so aus: float newGValue = currentPathNode.gValue + Vector3.Distance(currentPathNode.waypoint.transform.position,((PathNode)openList[indexInOL]).waypoint.transform.position); if(newGValue < ((PathNode)openList[indexInOL]).gValue) { ((PathNode)openList[indexInOL]).parentPathNode = currentPathNode; ((PazhNode)openList[indexInOL]).gValue = newGValue; ((PathNode)openList[indexInOL]).fValue = (newGValue + ((PathNode)openList[indexInOL]).hValue); } Damit sind wir so gut wie fertig. Würden wir die Funktion jetzt aufrufen sollten wir zwar keinen Fehler bekommen, allerdings bekommen wir ein leeres Array zurück, der Pfad wurde also nicht wirklich berechnet. Wie wir wissen können wir den Pfad ermitteln indem wir vom gefundenen targetField aus die parentPathNodes rückwärts gehen und alle Wegpunkte abspeichern bis wir wieder am Start angelangt sind. Dann müssen den Pfad gerade noch rumdrehen damit er auch in der richtigen Reihenfolge vorliegt. Dafür brauchen wir wieder eine neue static Funktion. Ich habe sie „ReturnPath“ genannt. Diese erhält 2 Parameter. Das startField und das targetField vom Typ PathNode. Als Rückgabewert erhalten wir das Array mit unserem korrekten Pfad. In dieser legen wir uns als erstes wieder ein neues Array an, ich habe es wieder pathArray genannt weil es ja schließlich auch genau dieses ist. Dazu noch eine Variable vom Typ PathNode die gleich zu Beginn unser targetField entgegen nimmt. In dieser Variable die ich auch currentPathNode genannt habe wird immer der PathNode gespeichert dessen Wegpunkt gleich anschließend in das PathArray geworfen wird. Anschließend erzeugen wir wieder eine while-Schleife die solange durchläuft bis der currentPathNode das startField ist. In dieser While-Schleife wird der Wegpunkt des currentPathNode in das pathArray gelegt und der currentPathNode auf dessen parentPathNode geändert. Sollte dieser jetzt das startField sein, wird die While-Schleife nicht mehr ausgeführt und somit der Wegpunkt des startField auch nicht in das pathArray gespeichert. Dies wäre ja auch nicht sinnvoll da auf diesem ja die Figur steht, also brauch sie dort auch nicht mehr hinlaufen. Zu guter letzt drehen wir das Array noch über den Befehl Reverse() um und geben es zurück. So sieht die Funktion vollständig aus. static public ArrayList ReturnPath(PathNode startField, PathNode targetField) { ArrayList pathArray = new ArrayList(); PathNode currentPathNode = targetField; while(currentPathNode != startField) { pathArray.Add(currentPathNode.waypoint); currentPathNode = currentPathNode.parentPathNode; } pathArray.Reverse(); return pathArray; } } Was wir jetzt noch machen müssen ist noch einmal in die Else-Verzweigung unserer Funktion GetPath zu gehen in der wir die neuen PathNodes instanzieren und überprüfen ob das Ziel gefunden wurde. Dort rufen wir nur noch, wenn das Ziel gefunden wurde die Funktion ReturnPath auf und übergeben den Rückgabewert an unser pathArray. if(newPathNode.waypoint == targetWaypoint) { targetField = newPathNode; pathArray = ReturnPath(startField,targetField); break; } Damit ist der A* Algorithmus fertig programmiert und kann angewendet werden. Aufgerufen werden kann er dann von überall durch Pathfinder.GetPath(startWaypoint,targetWaypoint). Außerdem wissen wir dass wenn das pathArray das zurück geliefert wird null ist oder die Länge gleich 0 das es keinen Weg zum Ziel gibt. Hier nochmal beide Dokumente auf einem Blick: using UnityEngine; using System.Collections; public class PathNode : ScriptableObject { public float fValue = 0.0f; public float gValue = 0.0f; public float hValue = 0.0f; public PathNode parentPathNode; public Waypoint waypoint; } using UnityEngine; using System.Collections; public class PathFinder : MonoBehaviour{ static public ArrayList GetPath(Waypoint startWaypoint, Waypoint targetWaypoint) { ArrayList openList = new ArrayList(); ArrayList closedList = new ArrayList(); PathNode currentPathNode; PathNode startField = (PathNode)ScriptableObject.CreateInstance<PathNode>(); startField.waypoint = startWaypoint; PathNode targetField = null; ArrayList pathArray = new ArrayList(); currentPathNode = startField; openList.Add(startField); if(startWaypoint != targetWaypoint) { while(targetField == null && openList.Count > 0) { int index = ReturnPathNodeWithLowestFValue(openList); currentPathNode = (PathNode)openList[index]; openList.RemoveAt(index); closedList.Add(currentPathNode); ArrayList neighbourWaypoints = currentPathNode.waypoint.waypointsInRange; for(int i = 0; i < neighbourWaypoints.Count; i++) { if(!IsInCL(closedList,(Waypoint)neighbourWaypoints[i])) { int indexInOL = IsInOL(openList,(Waypoint)neighbourWaypoints[i]); if(indexInOL >= 0)//Wenn der Wegpunkt in der openList ist { float newGValue = currentPathNode.gValue +Vector3.Distance(currentPathNode.waypoint.transform.position,((PathNode)openList[indexInOL]).waypoint.transform.position); if(newGValue < ((PathNode)openList[indexInOL]).gValue) { ((PathNode)openList[indexInOL]).parentPathNode = currentPathNode; ((PathNode)openList[indexInOL]).gValue = newGValue; ((PathNode)openList[indexInOL]).fValue = (newGValue +((PathNode)openList[indexInOL]).hValue); } } else//Wenn der Wegpunkt nicht in der openList ist { float GValue = currentPathNode.gValue + Vector3.Distance(currentPathNode.waypoint.transform.position, ((Waypoint)neighbourWaypoints[i]).transform.position); float HValue = Vector3.Distance(((Waypoint)neighbourWaypoints[i]).transform.position, targetWaypoint.transform.position); float FValue = GValue + HValue; PathNode newPathNode = (PathNode)ScriptableObject.CreateInstance<PathNode>(); newPathNode.waypoint = (Waypoint)neighbourWaypoints[i]; newPathNode.gValue = GValue; newPathNode.hValue = HValue; newPathNode.fValue = FValue; newPathNode.parentPathNode = currentPathNode; openList.Add(newPathNode); if(newPathNode.waypoint == targetWaypoint) { targetField = newPathNode; pathArray = ReturnPath(startField,targetField); break; } }//else }//if }//for }//while } return pathArray; } //////////////////////////////////////////////////////////////////////////// public static bool IsInCL(ArrayList closedL, Waypoint wayPoint) { bool isIn = false; for(int i = 0;i<closedL.Count;i++) { if(((PathNode)closedL[i]).waypoint == wayPoint) { isIn = true; break; } } return isIn; } //////////////////////////////////////////////////////////////////////////// static public int IsInOL(ArrayList openL, Waypoint wayPoint) { int index = -1; for(int i = 0; i<openL.Count; i++) { if(((PathNode)openL[i]).waypoint == wayPoint) { index = i; break; } } return index; } //////////////////////////////////////////////////////////////////////////// static public int ReturnPathNodeWithLowestFValue(ArrayList openL) { PathNode pathNode = null; int index = -1; if(openL.Count >0) { for(int i = 0; i < openL.Count; i++) { if(index == -1) { pathNode = (PathNode)openL[i]; index = i; } else if(pathNode.fValue > ((PathNode)openL[i]).fValue) { pathNode = (PathNode)openL[i]; index = i; } } } return index; } //////////////////////////////////////////////////////////////////////////// static public ArrayList ReturnPath(PathNode startField, PathNode targetField) { ArrayList pathArray = new ArrayList(); PathNode currentPathNode = targetField; while(currentPathNode != startField) { pathArray.Add(currentPathNode.waypoint); currentPathNode = currentPathNode.parentPathNode; } pathArray.Reverse(); return pathArray; } }
  44. 3 points
    Hey Leute, wir sind ein Team von vier Studenten und moechten euch unser Spiel GUILT vorstellen, welches wir in diesem Semester fuer eine Uebung an unserer Uni erstellt haben. GUILT ist ein narratives Spiel und wir versuchten eine spannende Story mit einem ernsten Thema zu verbinden. Download: https://gamejolt.com...es/guilt/236268 Trailer: Concept Art: Hier noch einige Screenshots: Nun wuerden wir uns ueber jeden Spieler und besonders ueber euer Feedback freuen! LG Tobias
  45. 3 points
    Ich persönlich nutze Cinema4D für eigentlich alles. Habe aber auch seit kurzem 3D-Coat, welches sau gut zum UV-Mappen und auch bemalen der Textur ist. Was aber mal einen Blick wert sein sollte ist Modo. https://www.thefoundry.co.uk/products/modo/
  46. 3 points
    Tja, da hast du natürlich völlig Recht. An meinem Arbeitsplatz ist neben Visual Studio auch Resharper und Stylecop im Einsatz. Jede Zeile Code wird von mindestens einem anderen Kollegen begutachtet, bevor was produktiv geht. Die Coding Conventions sind ziemlich restriktiv. Im täglichen Einsatz sehe ich dann aber, dass genau diese Praxis dafür sorgt, dass der Code übersichtlich und gut strukturiert bleibt, dass keine überflüssigen Abhängigkeiten bestehen und dass jeder den Code versteht, auch nach Jahren noch. Wenn ich dann hier sehe, wie Einsteiger mit immer den gleichen Problemen auf immer die gleichen, weil simplen Lösungen zurückfallen, geht's manchmal mit mir durch. Aus professioneller Sicht kann ich Silveryard nur zustimmen. Allerdings muss man diese Aussagen in diesem Umfeld hier relativieren. Also nochmal: public static-Variablen würde ich auch bei kleinsten Spielen nur benutzen, wenn es absolut keinen anderen Weg gibt. Und den gibt's eigentlich immer. public-Variablen sollten nicht inflationär benutzt werden. Wenn ein Script mehr als drei oder vier public-Variablen beinhaltet, ist es eventuell besser, ein neues Script zu erstellen, das einen Teil der Aufgaben übernimmt. Der Grund für beide Behauptungen: jede public Variable in einem Script kann von jedem anderen Script aus mit jedem beliebigen Wert geändert werden. Ein Beispiel: Ich habe im Player-Script eine Variable public int Life; Wenn der Gegner auf den Player schießt, fügt er direkt aus dem Gegnerscript heraus Schaden zu: playerScript.Life -= 10; Wenn ich eine Health-Box finde, setzt das Healthbox-Script das Playerleben hoch: playerScript.Life += 10; Der Spieler soll allerdings nicht über 100 Leben bekommen können, und wenn er Null Leben "erreicht", ist er tot. Daher wäre es hier sinnvoller, das Ganze so zu implementieren: // SpielerScript: private int life; public void ChangeLife(int amount) { life += amount; if (life < 0) { life = 0; ProcessPlayerDeath(); } if (life > 100) { life = 100; } } Jetzt können andere Scripts nur auf die Methode zugreifen und die Plausibilitätsprüfung erfolgt dort, wo sie hingehört, im Spielerscript. Wenn jetzt ein Einsteiger das doch auf die erste Art regelt? Tja, dann ist das halt so. Solange es funktioniert, spielt es keine Rolle. Wenn das Spiel immer weiter wächst und neue Funktionalitäten dazukommen, fliegt ihm diese Praxis um die Ohren, weil er nicht mehr durchblickt, wann was von wem geändert wird. Dann muss er halt das Ganze verwerfen und Teile neu schreiben. Sollen wir ihm das von Anfang an gleich sagen? Wenn du es so machst, ist es auf lange Sicht Kacke? Wohl eher nicht. Man lernt praktisch nur aus Fehlern, die man selber macht. Allerdings kann ich auch nicht aus meiner Haut. Bei bestimmten Programmierpraktiken geht mir der Hut hoch, weil sie so viel Fehlerpotenzial enthalten. Darauf werde ich auch in Zukunft hinweisen. Ich kann nicht anders.
  47. 3 points
    Jemand, der online und immer ansprechbar für Probleme ist? Gegen Bezahlung sicher
  48. 3 points
  49. 3 points
    Ich hab neben mir immer einen Ein-Euro-Jobber sitzen, der extra für mich die "alt gr" Taste drückt. So sorge ich für Arbeitsplätze, man hat es immer warm und nett neben mir und ich hab auch einen Gesprächspartner, wenn ich mich mal unterhalten möchte. Letztens hatte ich sogar einen ehemaligen Informatik Professor aus Russland neben mir sitzen. Junge hat DER mir noch was beigebracht.............alt gr hat er trotzdem für mich gedrückt. Edit: Like, Comment and Subscribe, wenn ihr das auch so seht!!!einself
  50. 3 points
    Liebe Community, wir haben unsere Unity 3 Übersicht Überarbeitet: http://www.unity-insider.de/unity-3 Wie findet ihr die neue Plattform? Viele Grueße, Lars

Announcements

Hy, wir programmieren für dich Apps(Android & iOS):

Weiterleitung zum Entwickler "daubit"



×
×
  • Create New...