Jump to content
Unity Insider Forum

Leaderboard

Popular Content

Showing content with the highest reputation since 06/06/2010 in Posts

  1. 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.
    34 points
  2. Wer die vorherigen zwei Tutorials gelesen hat, kann zwar einige Sturkturen eines JavaScripts anfertigen, hat aber eventuell noch keine große Vorstellung davon, wie man diese zum Entwickeln eines Spiels in Unity zu benutzen hat. Dieses Tutorial, quasi nachträglich geliefert, soll dafür sorgen, dass jeder Leser mit den erworbenen Grundkenntnissen in JavaScript auch in Unity scripten kann. Ergo: Bitte auf jeden Fall die ersten beiden Scripting-Tutorials lesen, dann dieses hier. Scripten in Unity mit Unity Teil 1 - Unity-Dinge Die Unity Engine muss man sich, wie vieles andere auch, so vorstellen: Der Kern der Engine ist eine so genannte "Black Box". Man weiß als Benutzer erst einmal nicht, wie sie intern funktioniert. Wie sie letztenendes die Grafikkarte anspricht und ein 3D-Objekt damit zeichnet, ist einem schleierhaft und das ist auch in Ordnung so. Ein großer Teil der Engine ist dagegen nämlich Teil der "Schnittstelle" der Engine, also des sichtbaren Teils. Schreibt man ein Script für die Unity Engine, dann benutzt man neben den grundlegenden Sprachkonstrukten der Sprache (also den Keywords var, function, if usw., den Klammern und allem anderen) eben diese Elemente der Schnittstelle. Diese Elemente sind die sichtbaren Teile vieler Klassen, aus denen Unity besteht, wie z.B. Transform, Mathf, Physics oder GameObject. Diese Schnittstelle durch und durch zu kennen ist optimal, immerhin ihren groben Aufbau zu verstehen essenziell. Im Folgenden werde ich diesen groben Aufbau einmal erläutern. Ein perfektes Verständnis wird dabei garantiert nicht vermittelt, das erhält man nur durch lange Zeit des Übens. Allem voran steht die Scripting Reference, die man auch lokal auf seinem Rechner hat, wenn man Unity installiert hat. Sie ist das Nachschlagewerk für die gesamte Schnittstelle und begleitet den Entwickler durchgehend. Auch nach Jahren der Erfahrung mit Unity kommt man ohne noch nicht aus. Wann immer man also einen Begriff nicht kennt, eine Methode sucht oder eine Parameter-Reihenfolge vergessen hat - im Scirpt-Editor F1 drücken, und man erhält (hoffentlich) die Reference. Teil 2 - Das grundlegende Prinzip Man kann es nicht oft genug nennen, darum auch hier noch einmal: Ein Spiel besteht aus mehreren Szenen (Scenes) Eine Szene besteht aus mehreren GameObjects Ein GameObject besitzt mehrere Komponenten (Component) Scenes gibt es in der Schnittstelle so erst einmal nicht, aber GameObjects und Komponenten. GameObject ist einfach eine Klasse in der Schnittstelle, fertig. Component dagegen ist eine Superklasse (der Begriff muss einem nichts sagen) für eine Menge Komponenten. Jede dieser Komponenten ist eine Klasse. Man sollte sich mit der Zeit aneignen, welche Komponenten was können und wofür sie zuständig sind. Neben GameObject und den Komponenten gibt es noch eine dritte Art von Klassen, die (erstmal!) wichtig ist: Statische Klassen. Die Klasse GameObject wird instanziiert, wenn man ein GameObject erzeugt. Dann hat man ein so genanntes "Exemplar" der GameObject-Klasse. Mit diesem Exemplar arbeitet man dann in der Regel, weniger mit der Klasse selbst. Statische Klassen dagegen bieten Funktionalität innerhalb der Klasse selbst an. Als erstes und wichtigstes Beiepiel: Mathf. Diese Klasse wird nicht instanziiert, man erzeugt nie ein neues "Mathf-Objekt". Stattdessen benutzt man die Klassenmethoden direkt: Mathf.Methodenname(); Mehr zu statischen Strukturen gibt es hier. Andere statische Klassen kommen später hinzu. So viel zu den "Kategorien". Teil 3.1 - Komponenten Hier ein paar der wichtigsten Komponenten: Transform Jedes GameObject hat immer genau eine Transform-Komponente. Sie beinhaltet Position Rotation Skalierung des Objekts und Methoden, um diese Werte zu verändern. Objekte verschieben, drehen oder die Größe ändern geht also über dessen Transform-Komponente.[*]Renderer Eine abstrakte Basisklasse. Von ihr sind mehrere Rendererklassen abgeleitet, z.B. MeshRenderer oder LineRenderer. Ein Renderer kennt eine Liste von Materialien, die auf das Objekt gepackt werden, das der Renderer rendern soll. [*]Collider Auch eine abstrakte Basisklasse. Von ihr abgeleitet sind z.B. BoxCollider, SphereCollider oder MeshCollider. Kann auf Trigger gestellt werden, stellt sonst ein Hindernis für bewegliche Objekte dar. [*]Rigidbody Sorgt dafür, dass PhysX das GameObject als Physik-Objekt behandelt, dass von Kräften, wie z.B. der Gravitation, beeinflusst werden kann. Hat Eigenschaften für Masse, Trägheit und Beschränkungen und Methoden zum Hinzufügen von Kräften. [*]Light Beleuchtet Meshes, also die 3D-Objekte der Szene. Hat Eigenschaften für Lichtfarbe und -Stärke, Schatten und Lightmapping. [*]Camera Sorgt dafür, dass die Renderer ein Bild Rendern, als würde es durch diese Kamera aufgenommen werden. Hat Eigenschaften zum Thema Field Of View, Hintergrundfarbe, Skybox oder RenderTarget. [*]Animation Animiert das GameObject und seine untergeordneten GameObjects nach Vorgaben einer Animation in den Assets. Hat Methoden zum Abspielen, Überblenden oder Stoppen von Animationen. [*]AudioSource Kann Klänge abspielen. Hat Eigenschaften über die Audio-Datei, Geschwindigkeit, Tonhöhe oder Doppler-Effekt und Methoden zum Abspielen oder Stoppen der Klänge. [*]MonoBehaviour Die abstrakte Basisklasse für Skripts. Jede Klasse, die man mit einem Skript definiert, ist davon abgeleitet. Hat eine Menge Methoden wie Start(), Update() und Events wie OnTriggerEnter(), OnEnable() oder OnMouseDown(). Gehen wir mal auf den letzten Punkt ein. MonoBehaviour hat eine Menge Methoden. Die Events sind auch Methoden. Besonders interessant sind die "Overridable Functions", wie sie in der Reference betitelt werden. Im ersten Tutorial erwähnte ich spezielle Methoden, die die von sich Engine aufruft. Genau um dise Methoden handelt es sich hierbei. Erst schlägt man nach, wann sie ausgelöst werden, dann benutzt man sie dem entsprechend. So wird z.B. OnApplicationFocus() ausgelöst, wenn das Spiel den Fokus erhält oder verliert. Also überschreiben wir die Methode in unserem Skript: function OnApplicationFocus(focus : boolean) { if(focus) PauseZuende(); else Pause(); } So viel zu den Overridable Functions. Was aber viel interessanter sein dürfte: Wie benutze ich jetzt die verschiedenen Komponeten in meinen Scripts? Hierzu gibt es zuerst einmal GetComponent(). Mit dieser Methode lässt man sich eine Komponente eines bestimmten Typs von einem GameObject zurück geben: var licht : Light = einGameObject.GetComponent(Light); //"licht" ist jetzt die Lichtkomponente von einGameObject. //Wenn dieses GameObject kein Licht hat, ist licht null licht.intensity = 3; //Mach was mit dem Licht Lässt man das GameObject vor dem Punkt weg, meint man immer das GameObject, das auch das Script hat, in dem GetComponent() aufgerufen wird. Geben wir also einem GameObject ein Licht und dieses Skript: function Start() { GetComponent(Light).intensity = 0; } ...dann heisst es: Licht aus, sobald das Spiel losgeht. Einige Komponenten, genauer, die, die jedes GameObject maximal ein Mal haben kann, sind direkt ansprechbar: transform // = GetComponent(Transform) renderer // = GetComponent(Renderer) light // = GetComopnent(Light) rigidbody // = GetComponent(Rigibody) camera // = GetComponent(Camera) animation // = GetComponent(Animation) audio // = GetComponent(AudioSource) //und weitere Ich habe irgendwann mal irgendwo gelesen, dass diese Shortcuts nur wieder GetComponent() auslösen, also nicht performancefreundlicher sind (warum auch immer). Deswegen speichere ich auch diese Komponenten oft irgendwo zwischen (siehe unten). Ist aber nicht so wichtig. GetComponent() durchsucht immer die ganze Komponentenliste und ist deswegen nicht soo performant (auch wenn man's wohl nicht merkt). Deswegen ist es eine saubere Sache, den Rückgabewert zu speichern: private var filter : MeshFilter; //private, damit kein anderes Script Zugriff hat, und damit es nicht im Editor angezeigt wird (wozu auch) function Awake() { filter = GetComponent(MeshFilter); } function Update() { //Mach Dinge mit "filter" } So, jetzt wenden wir das alles einmal an. Wir haben zwei GameObjects: DER TRIGGER Transform (logisch) Collider (auf isTrigger gestellt) Ein Script (kommt gleich) DIE TÜR Transform (klar) MeshRenderer (für's sichtabare, arbeitet mit MeshFilter zusammen) MeshFilter (kennt den zu rendernden Mesh) Collider (damit man auch nicht durchkann, wenn sie im weg ist) Animation (hat eine Animation, die die Tür öffnet) Der Trigger soll jetzt die Tür öffnen (sprich: Die Animation auslösen), wenn ein Objekt hinein gerät. Das Script des Triggers muss dann so oder so ähnlich aussehen: var tuer : GameObject; //Hier wird im Editor die Tür rein gezogen function OnTriggerEnter() { var tuerAnimation : Animation = tuer.GetComponent(Animation); // oder "= tuer.animation;" tuerAnimation.Play(); //löst die Animation aus. } Tadaa! Auf welche Arten man alles an andere GameObjects heran kommt, wird im nächsten Tutorial behandelt. Teil 3.2 - Statische Klassen Wie erwähnt gibt es Klassen in der Schnittstelle, die statische Methoden anbieten. Allen voran die Klasse Mathf. Sie bietet alle möglichen mathematischen Operationen an, darunter: Runden Eingrenzen x hoch y Logarithmen Trigonometrie Interpolation PI weitere Einfach zu benutzen: var tausendvierundzwanzig : float = Mathf.Pow(2, 10); // 2^10 = 1024 var zweiMalPi : float = Mathf.PI * 2; Dann gibt es als weitere wichtige statische Klasse Physics. Sie ist Teil der Schnittstelle zur PhysX-Engine und enthält wichtige Eigenschaften und Methoden für alles, was mit Physik und Kollision zu tun hat. So z.B. die globale Gravitation: Physics.gravity Sehr wichtig sind auch die Raycast-Methoden. Sie simulieren einen Strahl oder ähnliches in der Scene und geben zurück, ob dieser irgendetwas an Collidern getroffen hat. Die Methoden der Physics-Klasse sind die ersten Anlaufstellen, wenn man wissen will, ob und wo irgendwo ein Objekt ist, oder wo man dagegen Platz hat. Ebenfalls sehr wichtig ist die Klasse Input. Sie hat jede Menge Eigenschaften und Methoden, die einen ermitteln lassen, was der Spieler gerade so an seiner Hardware macht. Sprich: Status der Tastatur, der Maus, irgendwelchen GameControllern oder des Touch-Bildschirms und des Accelerometers bei iOS oder Android. Bevor ich hier aber in's Detail gehe, verweise ich lieber auf diese Manual-Seite. Mit dem Input-Manager kann man sich Achsen zusammenstellen, die in der Regel einen Wert von -1 bis 1 haben können und einen Namen haben. Bei einem Joystick hieße das: Ist der Stick zur einen Seite geneigt, gibt die Achse für diesen Stick -1 zurück, auf der anderen 1, in der Mitte 0 und ansonsten irgendetwas dazwischen. Bei einer Tastatur-Achse gibt man zwei Tasten an, z.B. Pfeiltaste links und rechts. Ist links gedrückt, gibt's -1, bei rechts +1 und bei keinem von beiden oder beiden gibt's 0. Den momentanen Wert der Achse lässt man sich dann mit Input.GetAxis(Name_der_Achse) zurück geben: var speed : float = 10; function Update() { transform.Translate(Input.GetAxis("Horizontal") * Time.deltaTime * speed, 0, 0); //Bis zu 10m pro Sekunde auf der X-Achse bewegen, abhängig von der Achse "Horizontal" (Standard-Achse für links/rechts bzw. A/D) } Teil 4 - Was wir daraus lernen So, was haben wir jetzt von alledem? Zwei Dinge: Du weisst jetzt grob, was Unity dir an Funktionen anbietet und kennst einige wichtige Komponenten und statische Klassen. Du weisst, dass Du, um die richtige Funktion für einen Zweck zu finden, in der Scripting Reference nachschauen musst. Und durch diesen Text weisst Du hoffentlich auch, wo Du darin zu suchen anfangen kannst. So, auf zum nächsten Tutorial
    18 points
  3. Unity wirkt ja mit großer Anziehungskraft auf Designer, während die Benutzerfreundlichkeit einen Programmierer erst einmal abschreckt, ist diese doch meist mit Einschränkungen oder sonstigen Ärgernissen verbunden. Wer sich gerne Spiele ausdenkt oder designed, muss sich auch in Unity ersteinmal mit Programmierung auseinander setzen, und dafür dieses Tutorial. Bis auf den letzten Teil (3.2) nicht unbedingt für Leute, die schon gut mit JavaScript programmieren können. Dieses Tutorial beschäftigt sich ausschließlich mit Javascript, da es noch einen Satz einfacher ist als c#. Scripten in Unity für Einsteiger Teil 1 - Scripts benutzen / Unity-Grundlagen Wer sich Unity schon einmal angesehen hat, sieht: Unity läuft grundlegend über dieses Prinzip: Ein Spiel besteht aus mehreren Szenen (Scenes) Eine Szene besteht aus mehreren GameObjects Ein GameObject besitzt mehrere Komponenten (Component) Ein GameObject ansich ist nichts weiter als eine Instanz. Es ist kein Licht, hat keine Form, keine Physik und ist in keiner Weise sichtbar. Erst die Komponenten des GameObjects definieren, was es ist. Fügt man z.B. ein Licht hinzu, so ist das kein Licht-GameObject, sondern ein GameObject mit einer Licht-Komponente. Sichtbare Objekte haben eine Komponente, die von der Renderer-Klasse abgeleitet ist (was das genau heißt, muss man erstmal nicht wissen), ohne diese wären sie unsichtbar. Sogar "Transform" ist eine Komponente, die sich nur nicht löschen lässt, und pro GameObject einmalig ist, trotzdem ist es nur eine Komponente. Eine Komponentenart hebt sich stark von den anderen ab: Script (MonoBehaviour). Ein Script ist eine weitere Komponente, die einem GameObject hinzugefügt werden kann. Warum es gut ist, dass man pro GameObject Scripts hat, und nicht ein großes Programm, das alles steuert, wie es sonst üblich ist, sei später erwähnt. Was hier zu merken ist: Wir wollen jetzt ein Script erstellen, das einen Würfel dreht. Erstelle eine neue Szene. Erstelle einen Würfel und platziere ihn im Weltzentrum (0,0,0). Erstelle jetzt einen neuen Javascript und nenne ihn RotateMe. Weise den Skript dem Würfel zu. >>> Noch passiert nichts, der Skript ist ja noch leer! Teil 2 - Variablen Variablen erfüllen viele Zwecke. Sie haben einen Datentyp und einen Wert. Der Datentyp gibt an, was die Variable beinhaltet; das kann ein Wort sein, eine Zahl, ein Vektor oder sogar ein GameObject. Die Zahl der Datentypen ist unbegrenzt, allein schon, weil man sich selber welche erstellen kann. Die wichtigsten Datentypen sind die so genannten primitiven Datentypen: (Kleiner Nachtrag hier: String ist nicht wirklich ein primitiver Datentyp im eigentlichen Sinne, verhält sich aber ungefähr so.) Eine Variable definiert man in Javascript (!) wie folgt: var name; Das Schlüsselwort "var" wird vom Editor gesondert markiert und bedeutet: Im folgenden wird eine Variable definiert. Das Wort "name" ist der Name der Variable und kann beliebig heißen, aber: - nur Buchstaben und Zahlen, Tiefstriche gehen auch ( _ ) - das erste Zeichen muss ein Buchstabe sein - keine Sonderzeichen (auch keine Umlaute) und erst recht keine Leerzeichen - die Konvention liegt bei CamelCase-groß-und-klein-schreibung, d.h.: erster Buchstabe klein und dann Kapitälchen, z.B. nameDerVariable Das Semikolon ( ; ) am Ende beendet die Anweisung, das gilt für alle Anweisungen. RICHTIG: FALSCH öffne das Script RotateMe und lösche den gesamten Inhalt Erstelle eine Variable "rotateSpeed" >>> Das Ergebnis müsste nun so aussehen: var rotateSpeed; Teil 2.1 - Variablen initialisieren Variablen erstellen, schön und gut, sie müssen auch Werte haben. Eine Wertzuweisung funktioniert ganz simpel per Gleichzeichen ( = ): health = 100; Dies ist eine Anweisung. Anweisungen lassen sich nicht einfach so in das Script schreiben, einzige Ausnahme sind Variablen, die erstellt werden (siehe vorheriger Abschnitt). Wie man diese Zuweisung anwendet, sei später erklärt. Eine Wertzuweisung lässt sich mit einer Variablendeklaration kombinieren, das nennt man dann Initialisierung: var health = 100; Hierbei gibt es eine Besonderheit in Javascript. Die meisten Programmiersprachen verlagen danach, dass eine Variable bei der Deklaration einen Typ angegeben bekomen. JavaScript tut dies nicht; weist man z.B. health den Wert 100 bei der Initialisierung zu, wird health bis auf weiteres als int behandelt, was es ja auch ist. Die Initialisierung einer Variable ist also eine Art vorläufiger Festsetzung des Datentyps. Man kann den Datentyp aber auch endgültig angeben... Teil 2.2 - Variablen-Typ angeben ...das funktioniert durch einen Dopelpunkt ( : ). Beispiel: var health : int; Health ist nun eine int-Variable. Die Performance des Scripts steigt, wenn man immer fleißig die Datentypen angibt, weil das Programm dann nicht immer selbst herausfinden muss, was für einen Datentyp eine Variable hat. Insbesondere, weil Variablen ohne angegebenen Typ ihren Typ später wechseln können. Initialisieren wir jetzt unsere Variable, sieht sie so aus: var health : int = 100; ändere jetzt die Variable rotateSpeed so ab, dass sie ein float ist und den Wert 10 hat. >>> Das Ergebnis müsste jetzt so aussehen: var rotateSpeed : float = 10; Teil 2.3 - Variablen im Unity-Editor Jetzt haben wir ein Script an unserem Würfel, der eine Variable hat: rotateSpeed. Diese Variable soll natürlich die Geschwindigkeit angeben, mit der gedreht wird. Eine normale Variable, die einen Datentyp hat (und sei er auch nur durch die Initialisierung angegeben), ist in Unity im Editor einstellbar. Speichere und schließe den Script-Editor. Markiere den Würfel und sieh Dir die Komponentenliste an. Das Script in der Komponentenliste, unser RotateMe-Script, hat jetzt eine Einstellung erhalten: Rotate Speed! Sie hat den Wert 10, weil wir ihn so zugewiesen haben. Den kann man jetzt aber ändern, und diese änderung beeinflusst dann das Verhalten der Script-Komponente, nicht des Scripts selbst! Hätten wir z.B. zwei Würfel mit RotateMe-Script und änderten wir den Rotate Speed-Wert des einen, so hat das keinen Einfluss auf den anderen, genau wie die änderung der Position eines Objekts die anderen nicht beeinflusst. Man muss also nicht jedes mal das Script ändern, um eine Einstellung zu ändern, und schon gar nicht zwei Scripts schreiben, um zwei Objekte unterschiedlich schnell drehen zu lassen! Teil 3 - Funktionen Eine Funktion ist nun das entscheidenste Konstrukt in einem Script. Sie kann ausgelöst werden und beinhaltet eine beliebige Anzahl von Aweisungen, wie z.B. die Wertzuweisung (siehe oben), oder ruft andere Funktionen auf. Eine Funktion sieht folgendermaßen aus: function Name() { } Für den Namen deiner Funktion gelten die selben Richtlinien wie für Variablen, nur, dass der Konvention nach Funktionsnamen am Anfang groß geschrieben werden. Zwischen den geschweiften Klammern ( { und } , AltGr+7 und AltGr+0 auf der PC-Tastatur) stehen dann die Anweisungen, die ausgeführt werden sollen, sobald die Funktion aufgerufen wird. Beispiel: function LogVier() { print("4"); } Wird die Funktion LogVier aufgerufen, wird eine andere Funktion aufgerufen, die in diesem Fall print() heißt. print() zeigt einen String in der Unity-Konsole an (und unten links im Editor), und zwar den, der in den Klammern steht. Variablen, die in den Klammern stehen, heißen Parameter. Durch Parameter lässt sich das Verhalten von Funktionen beeinflussen. Teil 3.1 - Parameter Parameter müssen in einer Funktion deklariert sein, sonst kann man sie nicht benutzen. Sie werden ähnlich wie normale Variablen deklariert, nur ohne "var", und in der Klammer der Funktion: function PrintPlusOne(zahl : int) { } diese Funktion erwartet nun einen Parameter vom Typ int. Versucht man, diese Funktion mit anderen Paramenetern aufzurufen, gibt es einen Fehler. Parameter sind, genau wie Variablen, die innerhalb einer Funktion deklariert werden, nur innerhalb der Funktion verfügbar. Versucht man, sie von anderswo auszulesen oder zu ändern, gibt es einen Fehler. Zuweisungen und andere Funktionen aufrufen sind nicht alles, was man tun kann, man kann z.B. auch rechnen: function PrintTwoPlus(zahl : int) { print(zahl + 2); } Diese Funktion zeigt in der Konsole das Ergebnis der Addition Zahl + 2 an. So ruft man diese Funktion auf: PrintTwoPlus(5); Dann erhält man "7" in der Konsole. Mehrere Parameter sind genauso möglich wie gar keiner, man trennt mehrere mit einem Komma ( , ). Aber wie ruft man eine Funktion auf, wenn man sie nur aus anderen Funktionen aufrufen kann (die müssen ja auch aufgerufen werden!)? Teil 3.2 - Unity-Funktionen Ganz einfach: Es gibt in Unity ein paar reservierte Funktionen, die nicht der Programmierer aufruft, sondern die Unity Engine selbst. Die wichtigsten: Awake() Wird aufgerufen, wenn das GameObject, dem das Script zugewiesen ist, initialisiert wird (also ganz am Anfang) Start() Wird aufgerufen, wenn das GameObject, dem das Script zugewiesen ist, fertig initialisiert ist Update() Wird immer wieder aufgerufen, solange das Script aktiv ist Füge die Update-Funktion zum Rotate-Script hinzu, denn wir wollen den Würfel kontinuierlich drehen >>> Das Ergebnis müsste jetzt so aussehen: var rotateSpeed : float = 10; function Update() { } Jetzt müssen wir in der Update-Funktion den Würfel drehen. Dazu greifen wir auf eine andere Komponente des selben GameObjects zu, das das Script angeheftet hat. In den meisten Fällen kann man ganz bequem auf die anderen Komponenten zugreifen, zum Beispiel die transform-Komponente: transform Transform hat eine Funktion namens Rotate(xAngle : float, yAngle : float, zAngle : float). Diese nutzen wir in der Update-Funktion, um den Würfel bei jedem Aufruf ein kleines Stück weiter zu drehen, und zwar um die y-Achse: transform.Rotate(0, 1, 0); Aber wir wollen den Würfel ja mit unserer angegeben, variablen Geschwindigkeit drehen! transform.Rotate(0, rotateSpeed, 0); Eines müssen wir aber noch beachten. Jede Art von zeitabhängigen Aktionen muss modifiziert werden. Das liegt daran, dass die Update()-Funktion je nach Leistung des Systems, auf dem die Unity Engine läuft, mal öfter und mal weniger oft aufgerufen wird. Da die Drehung ganz klar zeitabhängig ist (der Würfel dreht sich ja mit x Grad pro Sekunde), multiplizieren wir die Gradzahl mit der Zeit seit dem letzten Update()-Aufruf. Wer ein gewisses Grundverständnis von Mathematik hat, sieht: Wenn wir bei jedem Aufruf um x Grad mal diese Zeit drehen, entsteht eine gleichmäßige Drehung, denn wenn ein Aufruf etwas früher kommt, wird der Würfel entsprechend weniger gedreht und umgekehrt. Dieser Wert seit dem letzten Update()-Aufruf lässt sich jedenfalls so auslesen: Time.deltaTime also muss unsere Funktion jetzt so aussehen: function Update() { transform.Rotate(0, rotateSpeed * Time.deltaTime, 0); } (Das * ist ein Mal-Zeichen). Passe die Update-Funktion des Scripts wie beschrieben an. >>> Zeit zum Testen! Der Würfel müsste sich drehen! Dreht er sich zu langsam? Einfach Rotate Speed im Unity Editor ändern! Teil 4 - Rückgabewerte Eine Funktion kann irgendwas für eine andere Funktion machen. Das ist zum Beispiel dann dann sinnvoll, wenn man etwas bestimmtes an mehreren Stellen machen will. Man schreibt dann für diese Aufgabe eine Funktion, die dann immer aufgerufen wird, anstatt ihren Code überall in anderen Scripts zu verteilen. In den meisten Fällen einer solchen Auslagerung benutzt man die Funktion, um einen Wert zu ermitteln. Diesen Wert kann die Funktion dann an die Funktion, die sie aufgerufen hat, zurück geben. Dies geschieht mit dem return-Statement: function QuadratZahl(zahl : int) { return zahl * zahl; } Der Rückgabewert wird dann verwendet, indem man die Funktion bei ihrem Aufruf wie einen Wert behandelt: var ergebnis : int = QuadratZahl(8); print("Das Quadrat von 8 ist "+ergebnis); Die Funktion, die QuadratZahl() aufruft, wartet mit dem Ausführen, bis sie den Rückgabewert hat. Teil 5 - Abschließende Worte Jetzt kannst du scripten. Auch wenn du - verhältnismäßig - nicht sehr viel gemacht hast: Wenn Du dir alles genau durchgelesen hast, hast du nicht nur die Grundlagen gelernt, sondern auch einiges mehr, das dir beim Verstehen komplizierterer Dinge helfen wird. Als nächstes könntest du dich in die offizielle Scripting-Referenz einlesen: http://unity3d.com/s...criptReference/ Der Anfang dürfte dir bekannt vorkommen, aber das ist hoffentlich auch der Grund, warum du alles schneller verstehst, was nun kommt. Viel Erfolg beim Scripten! Das nächste Tutorial wartet schon!
    17 points
  4. Ü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
    17 points
  5. Mein Ringmenü tut ja soweit ganz gut, Nun musste ich aber den Layer ändern. Und nach der änderung gehen die Buttons nicht mehr. Also es fehlen die Effekte, die Geräusche, und man kann die Buttons nicht mehr anklicken. Wie kann ich denn den Layer meine UI nachträglich ändern ohne die Funktionen zu verlieren? Edit, haha, Klassiker. Fünf Minuten nach posten des Problems find ich die Lösung selber Unter Kamera im UICamera Script muss man die Event Receiver Mask natürlich auch umstellen
    16 points
  6. Wer sich das Tutorial für Einsteiger in aller Gemütlichkeit durch gelesen hat, wird jetzt vermutlich etwas ordentliches scripten wollen. Also machen wir uns mal an die etwas fortschrittlicheren Sachen ran. Scripten in Unity für Nicht-mehr-Einsteiger Teil 1 - Kommentare Das ist eigentlich fast schon eine Nebensache, da sie für das Programm keine Relevanz haben: Kommentare. Sie sind dazu da, um den Überblick zu behalten, oder auch, um anderen etwas zu verdeutlichen. Es gibt zwei Sorten von Kommentaren: Einzeilige und Blockkommentare. Einzeilig: //Alles was hinter den beiden Schrägstrichen steht, ist ein Kommentar und wird vom Programm nicht beachtet Block: /* Hiermit gehen auch mehrzeilige Kommentare */ Kommentare werden von ordentlichen Editoren (und der Unity-Scripteditor gehört dazu) farblich abgesondert; praktisch. Kommentare haben einen weiteren Nutzen: Man kann Programmteile "auskommentieren". Setzt man vor eine Anweisung die beiden // , dann wird sie natürlich nicht mehr ausgeführt. So kann man, falls man will, das Script testen, wie es ohne läuft. Teil 2 - Die If-Abfrage Eine Funktion löst eine andere aus - das ist ja schon ganz nett. Aber will man ein Spiel schreiben, reicht das noch lange nicht aus. Es gibt ein paar Strukturen in der Programmierung, die sollte jeder kennen. Da wäre zum ersten die If-Abfrage. "if" bedeutet auf deutsch "falls". Die If-Abfrage besteht aus einem Kopf, in dem eine Bedingung steht - diese ist eine Variable vom Typ boolean (erinnere: ja oder nein) - und einem Körper, in dem wie bei der Funktion Anweisungen stehen. if(bedingung) { } Sollte die Bedingung den Wert "true", also "ja", haben, dann werden die Funktionen im Körper, also zwischen den geschweiften Klammern, ausgeführt, sonst nicht. Beispiel: if(true) { print("Ich scripte hier!"); } if(false) { print("Ich scripte nicht."); } Sollte in dem Körper der Abfrage nur eine einzige Anweisung stehen, darf man die geschweiften Klammern weg lassen. Ein weiteres Besipiel: var klappt : boolean = true; function Start() { if(klappt) print("Es klappt."); klappt = false; if(klappt) print("Wenn das klappt, fress ich einen Besen."); } Nun aber weiter im Text: In If-Abfragen kann man auch andere Variablen benutzen als boolean. Dafür muss man den Wert mit einem anderen vergleichen. Dabei kommt dann heraus: "Ist gleich" oder "ist ungleich", und das ist dann wieder unser benötigter boolean-Wert. Zum Vergleichen benutzt man ein doppeltes Gleichzeichen ( == ), denn das einfache ist ja schon für die Wertzuweisung zuständig. Beispiel: if(3 == 4) print("Diese Anweisung wird nicht ausgeführt werden."); var hi : String = "Hallo"; if(hi == "Hallo") print("Diese Anweisung wird ausgeführt."); // Achtung: String-Vergleiche funktionieren nicht immer so einfach, in diesem Fall geht's Teil 3 - Logische Operatoren Mehrere Bedingungen ( = boolean-Variablen) lassen sich zu einer zusammenfügen, indem man so genannte logische Operatoren verwendet. So kann man im Script abfragen, ob zwei Bedingungen stimmen, oder nur eine von beiden. Da wäre zum einen das logische Und ( && ). if(4 > 3.5 && "wurst" != "käse") print("Das hier wird ausgeführt"); if(4 > 3.5 && "wurst" == 5) print("Das hier bestimmt nicht."); Auf diese Weise lassen sich belieblig viele Bedingungen kombinieren. So spart man sich das Stapeln von Abfragen: if(4 > 3.5) { if("wurst" != "käse") print("Das hier würde genau so funktionieren wie das oben."); } Dann gibt es noch das logische Oder ( || ). Einen | erzeugt man mit AltGr+< (die Taste neben der linken Umschalttaste). Eine Oder-Bedingung gilt dann als wahr, wenn eine ihrer Teilbedingungen wahr sind: var zahl : int = 4; if(zahl == 2 || zahl == 4) print("Das hier wird sowas von ausgeführt..."); Mehrere logische Operationen sollten unbedingt voneinander abgetrennt werden, das geschieht mit normalen Klammern ( ( und ) ): if(("a" == "b" || 3 == 3) && 2 > 0) print("Auch das hier wird ausgeführt."); Teil 4 - Else Jetzt kennst Du bereits if. Aber gibt noch mehr solcher Strukturen. Da wäre zum einen else. "else" heißt auf deutsch "sonst", und das passt. Wer aufgepasst hat, kann sich die Funktion schon denken: if(3 > 5) print("Das wird nicht ausgeführt"); else print("Das aber schon.") auch else kann einen Körper mit geschweiften Klammern haben, damit es mehrere Anweisungen gibt: if(false) { //... } else { //... } Teil 5 - While-Schleife Jetzt kommt noch eine weitere Struktur. Sie ist der If-Abfrage sehr ähnlich, hat aber den Unterschied, dass die Anweisungen im Körper nicht nur ein Mal ausgeführt werden, wenn im Kopf "true" steht, sondern immer wieder ausgeführt werden, bis "false" im Kopf steht. Das ist in den allermeisten Fällen nur sinnvoll, wenn der Wert, um den es im Kopf geht, im Körper geändert wird. var zahl : int = 0; while(zahl < 5) { print("Es wird getan!"); zahl = zahl + 1; } Am Ende haben wir hier fünf Mal den angegebenen Satz in der Konsole stehen. Daran sieht man schon einmal den primären Nutzen von Schleifen: etwas soll x Mal ausgeführt werden. Für diesen Zweck allerdings ist die For-Schleife nützlicher. Bevor die dran kommt, erst einmal neue Operatoren! Teil 6 - Neue Operatoren Es gibt ein paar nützliche Operatoren, die noch gar nicht dran kamen. Da gibt es z.B. + als "Concat"-Operator, der Strings zusammenfügt: var name : String = "Hase"; print("Mein Name ist "+name+"."); Wer errät's? Am Ende steht da "Mein Name ist Hase." Sehr wichtig! Das funktioniert auch mit Strings und Zahlen: var zahl : int = 3; print("Die Nummer lautet "+zahl); Aber Vorsicht bei Rechnungen in diesem Falle, benutze Klammern! print("Drei plus zwei ist "+(3+2)); ...denn das + ist immernoch auch zum Rechnen da. Jetzt gibt es noch schnelle Rechenoperatoren: zahl++; Erhöht "zahl" um 1, genau wie zahl--; ..."zahl" um 1 verringert. Dann gibt es noch diese: zahl += 5; //entspricht zahl = zahl + 5; zahl -= 5; //entspricht zahl = zahl - 5; zahl *= 5; //entspricht zahl = zahl * 5; zahl /= 5; //entspricht zahl = zahl / 5; ...für schnelleres Rechnen. Yay! Dann gibt es noch das Ausrufezeichen ( ! ). Dieses kann man benutzen, um boolean-Werte umzukehren. Dieser Code: if(lebtNoch == false) ...entspricht diesem: if(!lebtNoch) Das ist nicht notwendig, aber ganz schön praktisch! Teil 7 - Die For-Schleife Mit der For-Schleife geht Zählen besser als je zuvor! Anstatt sie aber im Detail zu erläutern, werde ich jetzt erstmal einfach die gängigste Verwendungsweise vorstellen: var wieOft : int = 5; for(var i : int = 0; i < wieOft; i++) { print("Wir sind bei "+i); } Der Kopf der For-Schleife besteht aus drei Teilen, die per Semikolon ( ; ) voneinander getrennt sind. Der erste Teil ist eine Anweisung, die am Anfang der Schleife ausgeführt wird (hier kann man eine Variable deklarieren und initialisieren!), der zweite Teil ist eine Bedingung, die vor jedem Durchlauf der Schleife abgefragt wird (genau wie in der While-Schleife, bei false wird die Schleife abgebrochen), der dritte Teil ist wieder eine Anweisung, die nach jedem Durchlauf ausgeführt wird. Dieser Code ist also von der Funktion her identisch mit diesem: var wieOft : int = 5; var i : int = 0; while(i < wieOft) { print("Wir sind bei "+i); i++; } ...und ist dabei wesentlich sauberer und schicker. Teil 8 - Return und Break Return wurde im vorherigen Tutorial benutzt; mit return lassen sich Werte von einer Funktion zurück geben. Wer sich nicht erinnert: Die Funktion wird dadurch beendet. Man kann return auch ohne Wert benutzen: return; ...einfach, um die Funktion zu beenden. Das hat einen ganz einfachen nutzen: Man spart sich jede Menge geschweifter Klammern. Denn anstatt einen riesigen Anweisungsblock einer Funktion komplett in eine If-Anweisung zu packen (man denke nur ans nachträgliche Einrücken...), kann man einfach die Funktion am Anfang mit diesem Code versehen: if(!wirdGemacht) return; //... Break ist ein Befehl, der ähnlich wie return verwendet wird, er beendet aber nicht die Funktion, sondern die Schleife, in der er steht. Logischerweise gibt's hier keinen Rückgabewert, Schleifen haben ja keinen. while(zahl < 5) { if(zahl == 4) //Ich würd gerne vorher abbrechen... break; print("Wetten, das hier wird nicht ausgeführt, wenn zahl gleich 4 ist?"); } Dieser Code macht nicht viel Sinn, aber break kann durchaus von großem Nutzen sein. Teil 9 - Switch-Case-Abfrage Eine interessante Abfrage gibt es noch: Man stelle sich vor, man hat eine Zahl, und abhängig von dieser möchte man etwas tun. Eine KI zum Beispiel hat eine Variable "modus". Ist modus 1, so steht sie herum, bei 2 sucht sie den Spieler, bei 3 schießt sie wild umher und bei 4 tanzt sie Disco Fox. Will man das Verhalten nun abhängig vom Modus gestalten, bietet sich theoretisch eine Reihe von If-Abfragen an: if(mode == 1) Idle(); else if(mode == 2) Search(player); //usw. Das ist etwas unschön und verstößt auch gegen einige Regeln für sauberes Programmieren. Ändert man nämlich den Namen der Variable, schaut man doof aus der Wäsche. Deshalb schafft switch-case Abhilfe. Der Code oben lässt sich in einer Case-Abfrage so ausdrücken: switch(mode) { case 1: Idle(); break; case 2: Search(player); break; //usw. } Das break ist wichtig, weil die Engine ohne das Schlüsselwort auch bei einer Übereinstimmung den Switch-Block weiter abarbeitet. Sollte in einem Case-Block "return" mit Sicherheit aufgerufen werden, darf man sich das break natürlich sparen. Ein Schlüsselwort gibt es noch: default. switch(mode) { default: //... break; } Der Code in einem default-Block wird immer ausgeführt und kommt daher z.B. an die Reihe, wenn mode aus irgendeinem Grund -1 ist. Er kommt nach allen case-Blöcken vor Ende des Switch-Blocks. Er sollte samt break immer vorhanden sein, auch wenn kein Code darin steht, sonst gibt es ja kein break! Teil 10 - Abschließende Worte All das hat in Unity überhaupt keinen direkten Nutzen gehabt. Wir haben ja nicht einmal ein neues Script geschrieben. Aber sicherlich kannst Du Dir denken, dass man ohne Abfragen und Schleifen nicht weit kommt. Und ich glaube, der passende Artikel für Script ist "das". Ich kann es mir trotzdem nicht angewöhnen. Viel Spaß beim Scripten!
    16 points
  7. Hey Leute, ich hab ein stilisiertes Vikingerschiff und eine kleine Testszene dazu gebastelt. Hier sind ein paar Screenshots aus Unity dazu, hoffe es gefällt. lg, holzi
    15 points
  8. 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
    14 points
  9. is das normal oder bin isch nun dodal bescheuert ? wenn man ein naktes neues projekt macht und in der hirarchie auf die camere 1x linksklick macht, steht das Camera objekt "offen" im inspector. man sieht in der scene auch das Fov wenn man nun aber die cam im inspector mit dem pfeilchen "zusammenschiebt" ist in der scene das Fov wech das doch nich absicht oder ? das FoV existiert und arbeitet ja weiterhin "normal"
    14 points
  10. Einige Zeit nach dem Erstellen der ersten beiden Scripting-Tutorials kristallisiert sich heraus, dass noch ein drittes sinnvoll wäre. Zwar kann man nach den ersten beiden schon ein wenig scripten, aber wie man mit diesen Skripts ein ganzes Spiel steuern kann, ist manchen Schleierhaft. Für dieses Tutorial sollten "Scripten in Unity für Einsteiger" und "Scripten in Unity für Nicht-mehr-Einsteiger" gelsen worden sein. Scripten in Unity für Scripterfahrene (an Objekte kommen) Oder: Richtig Scripten in Unity Um ein wenig Licht ins Dunkel zu bringen, noch einmal die Erinnerung aus dem ersten Scripting-Tutorial: Ein Spiel besteht aus mehreren Szenen (Scenes) Eine Szene besteht aus mehreren GameObjects Ein GameObject besteht aus mehreren Komponenten (Component) Ein Script ist dabei eine Komponentensorte, die einem GameObject zugewiesen werden kann. Damit alles folgende verständlich ist, hier ein Grundsatz: Ein Fehler, den ich immer häufiger sehe, ist, dass Anfänger versuchen, alle ihre Operationen über ein paar wenige Skripts zu erledigen. Dafür ist Unity nicht ausgelegt, und so sollte man es dem entsprechend auch nicht machen. Stattdessen soll man versuchen, jedem GameObject ein oder ein paar Scripts zuzuweisen, sodass sich möglichst ausschließlich diese Scripts um dieses GameObject kümmern. Das wird auf Dauer nicht funktionieren, sollte aber angestrebt werden. Falls das mal überhaupt nicht hin kommt (und das wird es), gibt es immernoch andere Methoden. Beispiel 1: Der Spieler und die Box Simples Beispiel: Wie haben eine Spielfigur, Capsule, Character Controller, Bewegungsskript. Außerdem steht da eine Box mit Namen "Cube", die, wenn man Space gedrückt hält, sich genau so bewegen soll wie der Spieler. Für "Space gedrückt" nehmen wir Input.GetButton("Jump") Eine Idee wäre es, den PlayerMovement-Skript anzupassen: function Update() { var bewegung : Vector3 = Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); GetComponent(CharacterController).Move(bewegung * Time.deltaTime); //Die Box if(Input.GetButton("Jump")) GameObject.Find("Cube").transform.Translate(bewegung * Time.deltaTime); } Sieht gut aus, eigentlich. Das Problem: Es verstößt gegen den oben genannten Grundsatz, denn ein Skript, das dem Player-Objekt zugewiesen wird, steuert den Würfel. GameObject.Find("Cube") ist nämlich ein Befehl, der ein Mal die komplette Objekthierarchie nach einem Objekt namens "Cube" durchsucht, und das in jedem Frame, weil er in der Update()-Funktion steht. Man stelle sich nur die Performance-Einbrüche vor, wenn so ein Befehl durch 50 GameObjects in einer Hierarchie von weit mehr GameObjects ausgeführt wird - jeden Frame. Versuchen wir es also gleich richtig und schreiben einen neuen Skript für den Würfel: function Update() { if(Input.GetButton("Jump")) { var bewegung : Vector3 = Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); transform.Translate(bewegung * Time.deltaTime); } } ...weisen den Skript der Box zu und streichen den Box-Teil komplett aus dem Player-Skript. Sauber! Beispiel 2: Die anhängliche Kamera Der SmoothFollow-Skript in den Standard Asstes ist vielleicht nett zu benutzen, aber man möchste bestimmt mal etwas eigenes basteln. Hier ein Beispiel: Wieder eine steuerbare Kapsel, Character Controller, Player-Skript. Dazu eine Kamera, die nun einen eigenen Skript kriegen soll, der die Kamera an die Fersen des Spielers heftet, aber ohne sich mitzudrehen, wie es der Fall wäre, wenn man die Kamera dem Spieler unterordnen würde. Die erste Idee mit GameObject.Find("Player") schlagen wir uns gleich wieder aus dem Kopf. Stattdessen denken wir an Beispiel 1, aber wir müssen feststellen, dass diese Technik schlecht funktioniert, da die Kamera irgendwie an die Transform-Infos des Players heran kommen muss, um die position festzustellen. Also basteln wir uns folgenden Skript: var player : GameObject; function Update() { if(player) transform.position = player.transform.position + Vector3(0,10,40); } "Vector3(0,10,40)" bestimmt hierbei den Abstand der Kamera zur Spielfigur, da könnte man stattdessen auch eine Variable nehmen. Wenn man nun diesen Skript der Kamera zuweist, dann muss man noch die Spielfigur aus der Scene View oder der Szenenhierarchie heraus in die Eigenschaft "Player" der Kamera im Inspektor ziehen. Der Code "if(player)" sorgt dabei dafür, dass keine Fehlermeldung entsteht, wenn der Kamera mal aus irgendeinem Grund die Player-Variable nicht richtig zugewiesen wurde, oder der Player im Laufe des Spiels verschwindet. Mehr über den "Existenzoperator" in diesem späteren Tutorial. Auf diese Weise erspart man der Engine das Suchen nach dem Objekt, denn wenn das gewünschte Objekt als Variable vorhanden ist, dann weiß die Engine sofort, an welcher Stelle das Objekt ist. Es ist genau so wie beim Menschen: Wenn man ihm sagt, wo etwas ist, muss er nicht suchen. Komponenten anstatt GameObject nehmen Im letzten Beispiel habe ich eine Variable vom Typ GameObject deklariert, schön und gut. Aber da gibt es noch eine wesentlich schönere Methode: Eine Komponente stattdessen nehmen! Anstatt GameObject schreibe ich z.B. Transform. Eine Variable wie diese: var player : Transform; erwartet auch ein GameObject als Wert, sodass man den Player hineinziehen kann, Bedingung ist aber, dass das Objekt eine Komponente des Typs "Transform" hat, was natürlich immer der Fall ist. Beim Typ "Light" z.B. ist das aber eine richtige Bedingung, wenn der Player keine Light-Komponente hat, kann man ihn hier dann nicht zuweisen. Der Vorteil bei der Sache ist: Man kann direkt auf die Komponente zugreifen. Anstatt des vorherigen player.transform.position können wir nun einfach player.position schreiben, da "player" ja jetzt der Transform des Objekts ist anstatt das Objekt selbst. Beispiel 3: Die hinterhältige Taschenlampe Manchmal wird es etwas problematisch, die Technik aus Beispiel 2 zu benutzen. Dazu hier ein etwas komplexeres Beispiel: Ein Spieler rennt mir einer Taschanlampe (nur Licht, kein Modell) durch die Gegend. Die Lichtstärke der Lampe ist proportional zur Energie des Spielers, wenn man also ganz gesund ist, leuchtet sie am hellsten und wenn man schwächer wird, wird es auch das Licht der Lampe. In diesem Fall muss der Player-Skript auf die Taschenlampe zugreifen und ihr, abhängig von einer eigenen Variable "health" eine neue Lichtstärke "light.intensity" geben. Nun könnte man an Beispiel 2 denken, aber da gibt es ein Problem: Der Inspektor ist bei solchen Aktionen irgendwann voll von Eigenschaften, die nicht geändert werden (es ist immer die selbe Taschenlampe), und das ist auf Dauer unschön und unübersichtlich. In diesem Fall kann man dann ruhig zur dritten Methode greifen: var health : float = 100; private var lampe : Light; function Awake() { lampe = Transform.Find("Lampe"); } function Update() { lampe.intensity = health / 100.0; } In diesem Skript sucht er sich mit Transform.Find() (dazu gleich mehr) seine Lampe (sie muss auch den Namen "Lampe" haben) heraus und speichert sie sich ganz am Anfang, bevor das erste Update aufgerufen wird. Auf diese Weise kann man die ansonsten zu vermeidenden Funktionen wie GameObject.Find() benutzen, ohne dafür ärger kriegen zu müssen, denn ein, zwei mal darf man sich die Verwendung ruhig erlauben. Folgendes ist hier interessant: "private" vor der Variable Eine als "private" deklarierte Variable kann nicht von anderen Skripts aus ausgelesen oder verändert werden. Des weiteren erscheint diese Variable nicht als Eigenschaft im Inspektor, was die in diesem Falle gewünschte übersicht erhält. Awake() benutzt Awake ist genau die richtige Funktion, um anfängliche Wertzuweisungen wie diese zu machen, da man dann in Start(), was danach aufgerufen wird, damit weiter arbeiten kann. Transform.Find() benutzt Transform.Find() ist ähnlich wie GameObject.Find(), mit dem Unterschied, dass es nur in den Objekten sucht, die dem GameObject untergeordnet sind, zu dem die angegebene Transform-Komponente gehört. Alternativ könnte die Taschenlampe auch auf den Player zugreifen, aber das läuft auf den selben Aufwand hinaus. Abschließend Jetzt kennt ihr die drei Methoden, mit denen man arbeiten muss, damit man nicht in die falsche Richtung programmiert. Versucht, sie der Reihenfolge nach anzuwenden: Jedes GameObject wird nur durch die eigenen Scripts gesteuert, wenn das nicht geht, dann Variable deklarieren und im Inspektor zuweisen, aber wenn das nicht gut ist, dann Private Variablen deklarieren und in Awake() zuweisen Beachtet diese Regeln immer, dann programmiert ihr schonmal viel sauberer, und eure Performance wird bei komplexen Projekten auch mithalten!
    13 points
  11. Jetzt endlich habe ich etwas Zeit gefunden um eine neue Tutorialreihe zu starten. Es geht diesmal um das Animieren mit Mecanim. Worauf muss beim importierten Character geachtet werden und wie füge ich die erste Animation hinzu. Hier ist der erste Teil der neuen Reihe zu sehen: Weitere Teile werden folgen, jedoch weiss ich noch nicht wann.
    12 points
  12. Hallo zusammen, heute Nacht wird ein Serverwechsel stattfinden. Grund dafür ist folgender: wie ihr sicherlich wisst, bin ich die letzten Jahre sehr inaktiv gewesen. Deshalb habe ich mich dazu entschlossen, dass Projekt einem sehr guten Freund, dem Roman Engel, zu übergeben. Heute Nacht wird der Server gewechselt, es dürfte eigentlich keine Komplikationen geben, aber von 2 bis 6 Uhr sind wir definitiv nicht erreichbar. Danach müssen ggf. noch mal ein paar Pfade angepasst werden, aber das hält sich im Rahmen. Für euch wird sich nichts verändern, ich werde weiterhin im Hintergrund mitwirken. Ihr seht höchstens mal einen neuen Administratoren hier im Forum posten. Aber vor allem wird eines passieren: Das Portal wird wieder mit neuen, frischen Inhalten gefüllt! Ich hoffe, ihr seid und bleibt mit Unity Insider zufrieden und wünsche euch die besten Grüße, Lars
    12 points
  13. 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!
    12 points
  14. Ich hab das Projekt wieder aufgenommen - tue dies aber nun in kleinerem, privateren Rahmen. Ging durch eine recht heftige persönliche Zeit letztes Jahr, wo mir die Energie für sowas leider fehlte. Nun ist sie langsam wieder da.
    12 points
  15. 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!
    11 points
  16. Hier die bisherigen Teile der "Wir bauen uns ein Space Shoot em Up" Tutorial Reihe. Teil 1 - Schiffssteuerung Teil 2 - Camera Teil 3 - Antrieb Teil 4 - Waffen Teil 5 - Schaden Teil 6 - Optik Teil 7 - KI Teil 8 - Kollisionen & Kamera Teil 9 - Strahlenwaffen Teil 10 - Waffenslots Teil 11 - Items Teil 12 - Waves Teil 13 - Manöver Teil 14 - Loot Teil 15 - HUD Teil 16 - Mehr Gegner
    10 points
  17. Hallo allerseits! Da ist mein erster Post hier gleich eine Projektvorstellung? Schoen08 hat mich auf eure Community verwiesen, und mich gefragt ob ich nicht mal Lust hätte hier über unser aktuelles Spiel zu schreiben. Einem technisch interessierten Publikum gegenüber könnte das in der Tat Spaß machen und vielleicht auch dem ein oder anderen nützliche Einblicke in unsere Arbeit geben. Also warum nicht? Ich bin Technical Director bei King Art und seit 4 Jahren setzen alle unsere Neuentwicklungen auf Unity. Das wären also das Adventure The Raven (zu dem Zeitpunkt war meine Beziehung zu Unity bestenfalls eien Hassliebe) danach Battle Worls: Kronos ein Rundenstrategiespiel dann das 2,5D Adventure Book of Unwritten Tales und jetzt gerade in der Entwicklung: Die Zwerge. Ich möchte nicht zu viele Worte über das Spiel selbst verlieren, denn im Moment läuft ein Kickstarter dazu und andere haben sich dort bereits große Mühe gegeben das Spiel so gut wie möglich vorzustellen. Also einfach mal dem Link folgen: Ich kann ohne Übertreibung sagen, dass Die Zwerge für mich das spannendste Projekt meiner bisherigen Karriere darstellt. Als Anfang der Nullerjahre der Herr der Ringe in die Kinos kam, haben mich vorallem die Massenschlachten beeindruckt. Dahinter stand ein System mit dem Namen "Massive" zu dem es leider nie allzuviele öffentliche Informationen gab. Aber trotzdem hat mich das Thema sehr fasziniert auch wenn es natürlich nicht real-time war. Ich weiß noch, dass ich damals dachte, dass die Computer vielleicht in 10-15 Jahren so leistungsfähig sind, dass man sowas auch in Echtzeit versuchen kann. Und nun bin ich in der glücklichen Situation mich bei den Zwergen dieser Vision zumindest annähern zu können. Viele Computerspiele besonders im Genre der RPGs modellieren den Kampf nicht viel anders als Pen&Paper-RPGs oder Brettspiele. Da wird viel gewürfelt, es gibt zalreiche Attribute und Formalismen für Rüstung, Kritische Treffer, Ausweichwürfe und so weiter... am Ende läuft alles darauf hinaus die Lebenspunkte der Gegner auf Null zu senken und selbiges für die eigenen Figuren zu verhindern. Für die Zwerge setzen wir dagegen auf eine physikalische Simulation der Dynamik von Massenschlachten. Natürlich ist auch das nur ein krudes Modell der Wirklichkeit aber eben doch ein fundamental anderer Ansatz als wir ihn zum Beispiel noch bei Battle Worlds gegangen sind, wo Zeit (turnbased) und Raum (feldbasiert) disktretisiert waren und das Spiel fast einer Brettspielumsetzung gleichkam. In Die Zwerge simulieren wir stattdessen das Verhalten und Zusammenspiel einzelner Agenten ohne zeitliche und räumliche Diskretisierung auf Basis einer physikalischen Simulation mit einfachen Shapes wie Kreiseln und Kapseln, die anders als in fast allen 3rd Party Lösungen allerdings keine Rigid-Bodies sind. Diese Basis wird ergänzt um weitere Systeme wie ein relativ komplexes Pathfinding oder eine flexible Schnittstelle um Kräfte in die Simulation zu induzieren und damit z.B. Schläge, Explosionen oder ganz allgemein Spezialattacken zu simulieren. Das Framework, das all diese Berechnungen durchführt und das wir HORDE (Horde Onslaught Realtime Dynamcs Engine) genannt haben ist von Unity weitestgehend entkoppelt. Unity hostet diesen Prozess nur - Es ruft irgendwo im Update-Schritt einer Component eine unverdächtig klingende Battle.Update() Methode auf die dann mehrere Millisekunden braucht um zu terminieren. Performance ist durchaus eine knappe Resource da man sich mit dem gesammten Rest des Spiels um die knappen Cycles des Unity Mainthreads streiten muss. Nach dem erfolgreichen Update ist die Simulation um die aktuelle Deltatime fortgeschritten und wieder synchron zur Spielzeit. Für jeden Orc oder Zwerg gibt es auch ein "normales" Gameobjekt in der Unity-Szene dessen Aufgabe aber lediglich darin besteht die Vorgaben seines Gegenparts der Simulation (Grunt genannt) visuell umzusetzen. Die beiden Fragen, "wie berechne ich eine Massenschlacht zwischen Orcs und Zwergen so, dass realistisch aber auch spaßig zu spielen ist" und "wie visualisiere ich diese Daten so, dass man den Ansprüchen an moderne Spiele genügt" stehen im Mittelpunkt meiner bisherigen Arbeit. Und ich freue mich darauf meine Antworten und Ansätze hier mit euch zu teilen. Für's erste ist der Post aber schon lang genug geworden. Lasst mich hören, was euch besonders interessiert, es muss auch nicht unbedingt mit den Zwergen zu tun haben. ~Thomas
    10 points
  18. Dieses Tutorial beschäftigt sich mit der Entwicklung eines Scripts zur Steuerung eines CharacterControllers für ein Jump'n'Run mit 2D-Gameplay. Kurz gesagt: Links/Rechts/Springen und Schwerkraft. Dieses Tutorial setzt voraus, dass die ersten drei Scripting-Tutorials gelesen wurden, damit die Grundlagen des Scriptens vorhanden sind. Ich benutze weiterhin JavaScript in diesem Tutorial. Anmerkung zum Üben: Die Script-Zwischenstände sind dieses Mal, insbesondere im späteren Teil, meistens in Spoiler-Tags, also versteckt. Versucht, beim Durcharbeiten des Tutorials, diese Spoiler nicht anzuklicken, sondern selbst zu programmieren. Geht irgendetwas schief, versucht es zu lösen, klappt das nicht, schaut nach. Wenn ihr das schafft, trainiert es das Programmieren ohne Skriptbeispiele. Viel Spaß und Erfolg! Scripten in der Praxis - Der CharacterController Heute widmen wir uns einmal einer Komponente in Unity, die, wenn ein Script dafür erstellt werden soll, vielen Scripting-Einsteigern einen Schauer über den Rücken laufen lässt: Dem CharacterController. Die Standard-Scripts zur Steuerung dieser Komponente sind zwar ausreichend, wenn man eine Welt baut und diese ingame ansehen will, aber in den meisten Fällen behindern sie einen, das gewünsche Gameplay zu erstellen. Darum programmieren wir jetzt selbst. Wann benutzen? Der CharacterController und der Rigidbody sind die einzigen beiden Komponenten, die es ermöglichen, Körper durch den Raum zu bewegen, sodass sie mit anderen Objekten kollidieren können, und nicht einfach hindurch gehen. Beide haben dabei unterschiedliche Anwendungsfelder, und es ist von der Anwendung abhängig, welche der beiden Komponenten man benutzen sollte. Faustregel: Der Rigidbody ist für Physikobjekte, sie sich physikalisch korrekt verhalten sollen: Rollende Bälle, fallende Kisten, Fahrzeuge. Rigidbodys haben Masse und Trägheit imlpementiert und werden jeden FixedFrame von der PhysikEngine beeinflusst, es sei denn, sie "schlafen". Der CharacterController dagegen ist, wie der Name schon andeutet, für Charaktere, also Menschen konzipiert, ist aber in den meisten Fällen für alle Arten von Lebewesen geeignet. Ein CharacterController kann nicht gekippt werden, "rollt" nicht "aus" und bewegt oder dreht sich nur, wenn man es will, was parallel steht zu "wenn die Spielfigur es will". Spielt man also nicht gerade ein Murmel, sondern eine Figur, sollte dieser per CharacterController realisiert sein. Zur Komponente Jetzt klären wir erst einmal: Was ist der CharacterController überhaupt? Die Scripting Reference fasst es ganz gut zusammen: Es ist eine Unterart eines Colliders Er bewegt sich nur, wenn man die Move()-Funktion benutzt Er beachtet dabei Kollisionen Aus 1. ergibt sich: Ein GameObject kann nur einen CharacterController oder einen normalen Collider haben, weil es immer nur einen Collider pro GameObject geben kann und der CharacterController selbst einer ist. Ausserdem bedeutet es: Andere Objekte werden mit dem CharacterController kollidieren können. 2. bedeutet: Der CharacterController hat keine Gravitation und keine Trägheit. Wenn man ihm sagt, bewege dich drei Meter weit, dann bewegt er sich drei Meter weit. Sagt man es nicht, bewegt er sich nicht. 3. schränkt das ein: Kollidiert der CharacterController während das Move()-Aufrufs mit einem anderen Collider, so bewegt er sich nicht weiter in diese Richtung. 2. ist allerdings nicht ganz richtig: Man kann ein CharacterController-GameObject auch über seine Transform-Komponente bewegen - in diesem Fall gibt es aber wieder keine Kollisionen! Zusammenfassend müssen wir also nur einen schönen Vektor finden, der sich vermutlich aus dem Input ergibt, und dann Move() mit diesem Vektor aufrufen. Alles bereit Wir erstellen uns jetzt am besten eine kleine Scene, die wie folgt aussieht: Ein paar Cubes, ein Licht, eine Kamera, die eine Rotation von (0,0,0) hat, und eine Kapsel (GameObject => Create Other => Capsule). Keine Prefabs aus den Standard Assets! Die Spielwelt erstreckt sich auf der x-Achse, da sich der Controller auf dieser Achse bewegen wird. Die Kapsel sollte den Boden nicht berühren, da der CharacterController einen kleinen Mindestabstand braucht. Jetzt die Kapsel markieren und über "Component => Physics => Character Controller" einen CharacterController hinzufügen. Da die Kapsel schon einen Capsule Collider hat, muss dieser ersetzt werden, bei der erscheinenden Meldung also "Replace" anklicken. Wie man sieht, hat der CharacterController exakt die selbe Form wie der Capsule Collider. Diese Form ist nämlich für alle aufrecht gehenden Lebewesen gut geeignet und hat auch weitere Vorteile. Dazu später mehr. Los geht's Fangen wir jetzt also mit einem neuen Skript an: function Update() { } Wir fügen eine private Variable hinzu, die die Referenz auf die CharacterController-Komponente halten soll, sodass wir darauf zugreifen können. Diese Referenz holen wir uns in Awake() mit GetComponent(): private var controller : CharacterController; function Awake() { controller = GetComponent(CharacterController); } function Update() { } Wir holen uns die Referenz schon am Anfang, damit wir GetComponent() nur einmal und nicht in jedem Frame aufrufen müssen, weil es keine sehr performante Funktion ist. Wir machen das in Awake() und nicht in Start(), weil Awake() gerade für solche Referenzzuweisungen sind; Start() dagegen ist für erste Aktionen, die z.T. fertige Referenzzuweisungen benötigt. Wenn z.B. ein anderes Skript beim Start auf "controller" zugreifen wollen würde, würden wir so sicherstellen, dass controller auch schon gesetzt ist, weil alle Start()-Aufrufe nach allen Awake()-Aufrufen ausgeführt werden. "controller" ist ausserdem private, damit die Variable nicht im Inspektor auftaucht (warum auch) und nicht von anderen Skripts angefasst werden kann. Ab jetzt können wir in Update() jederzeit mit "controller" auf unseren CharacterController zugreifen. Damit wir keinesfalls ein null bei GetComponent() bekommen, sorgen wir mit einem netten Trick dafür, dass dieses Skript beim Hinzufügen automatisch einen CharacterController mit hinzufügt: @script RequireComponent(CharacterController) Diesen Code, so wie er ist (ohne Semikolon!), ganz an den Anfang (wahlweise auch das Ende...) des Skripts platzieren. Soll der CharacterController jetzt gelöscht werden, und dieses Script ist auch eine Komponente des Objekts, dann gibt es eine Warnmeldung, dass zuerst die Skriptkomponente entfernt werden soll. So sieht das Script jetzt aus: Den Input-Vektor finden Jetzt erstellen wir uns in der Update-Funktion einen Vector3 namens dir (für direction), den wir am Ende der Move()-Funktion übergeben werden. Diesen initialisieren wir mit dem Wert der standardmäßigen horizontalen Input-Achse (links/rechts bzw. A/D) als X-Komponente. var dir : Vector3 = Vector3(Input.GetAxis("Horizontal"), 0, 0); Diesen übergeben wir jetzt, multipliziert mit Time.deltaTime, an Move(). controller.Move(dir * Time.deltaTime); Jetzt sieht das Script so aus: Das Script wird jetzt der Kapsel gegeben. Zeit zum Testen! Wie man sieht, kann man das Objekt bewegen - es geht nicht durch die Wand, fällt aber auch nicht herunter. Das ist richtig so - schließlich haben wir die volle Kontroller über die Move-Funktion, und in der steckt noch nichts über Schwerkraft drin. Jetzt fügen wir aber erstmal eine Variable "speed" ein, die für etwas mehr Geschwindigkeit sorgt. Diese wird mit dem Rückgabewert von GetAxis() multipliziert. Herunter fallen Jetzt wollen wir uns um die Schwerkraft kümmern. Dazu führen wir eine Schwerkraft-Variable ein: var gravity : float = 9.81; Jetzt wird es Zeit, sich ein wenig Gedanken zu machen. Im Moment haben wir einen direction-Vektor für Move(), der in jedem Update(), also in jedem Frame, einmal neu erzeugt wird. Die Geschwindigkeit aus vorherigen Frames sind also nicht vorhanden, da der Vektor am Ende jedes Update()s verloren geht. Die Fallgeschwindigkeit ist aber abhängig von der letzten Fallgeschwindigkeit, die sich ja konstant erhöht (wir haben ja eine Beschleunigung). Wir müssen also zumindest die y-Komponente von dir als Variable außerhalb des Update()s speichern, um sie in Update() zu verändern und im nächsten Frame wieder zu benutzen. Wer jetzt ein bisschen weiter denkt oder schon probiert hat, merkt: Meist will man noch z.B. Air Control haben, also eine Beschleunigung nach links und rechts, wenn man in der Luft ist. Also entschließen wir uns am besten dazu, gleich den ganzen dir-Vektor aus Update() raus zu holen. @script RequireComponent(CharacterController) private var controller : CharacterController; private var dir : Vector3 = Vector3.zero; var speed : float = 6; function Awake() { controller = GetComponent(CharacterController); } function Update() { controller.Move(dir * Time.deltaTime); } dir sollte dabei private sein, da wieder kein Bedarf besteht, im Inspektor an den Werten zu spielen, und auch kein anderes Skript daran rumpfuschen können sollte. Jetzt können wir dir.x und dir.y in Update() beeinflussen. dir.x = Input.GetAxis("Horizontal") * speed; dir.y -= gravity * Time.deltaTime; //dir.y um "gravity"m/s senken Jetzt fällt der Charakter herunter, aber etwas stimmt noch nicht. dir.y sinkt jetzt kontinuierlich, auch, wenn man steht. Das heißt: Steht man eine Minnute lang irgendwo und geht dann die Kante herunter, fällt man sofort mit einer wahnsinnigen Geschwindigkeit. Das Problem wird natürlich gelöst, wenn man die Gravitation nur dann wirken lässt, wenn man auch auf dem Boden steht. Ob man auch auf dem Boden steht, ... ...sieht man, wenn controller.isGrounded abgefragt wird. isGrounded (boolean) ist ein sehr nützlicher Shortcut, der angibt, ob der Controller mit einem Collider unter sich kollidiert ist oder nicht, sprich: Ob er auf dem Boden steht. Wir benutzen diese Eigenschaft folgendermaßen in Update(): if(controller.isGrounded) //auf dem Boden { dir.x = Input.GetAxis("Horizontal") * speed; } else //in der Luft { dir.y -= gravity * Time.deltaTime; } Wie Du siehst: dir.x wird jetzt nur noch neu gesetzt, wenn man auf dem Boden ist, sonst bleibt der Wert wie im letzten Frame. Jetzt ist der Schwerkraft-Feher immernoch nicht ganz behoben: Fällt man, sodass dir.y verändert wird, und landet danach, dann bleibt dir.y danach auf diesem Wert. Läuft man nochmal von der Kante, fällt man sofort mit dieser Geschwindigkeit weiter. Also müssen wir den Wert neu setzen, wenn wir auf dem Boden stehen: dir.y = -1; Warum -1? Ganz einfach: Wenn wir nicht ein bisschen nach unten Move()n, wenn wir auf dem Boden sind, kollidieren wir ja nicht mehr damit, und isGrounded gibt false zurück! Das bedeutet zwar, dass wir im nächsten Frame fallen und somit wieder mit dem Boden kollidieren, aber nur jeden zweiten Frame springen zu können ist ungut, oder? Das vollständige Script bis jetzt: Zeit zum Testen! Bewege die Kapsel ggf. auf einen der Blöcke, um das Fallen zu testen. Bei dieser Gelegenheit darf "Gravity" gerne ein wenig angepasst werden, aber bitte gleich über den Inspektor Springen! Jetzt müssen wir nur noch eines: Richtig springen. Dazu definieren wir uns einfach eine Variable jumpPower vom Typ float, z.B. mit Wert 5. Wird der "Jump"-Button gedrückt, während man auf dem Boden ist, wird dir.y nicht auf -1, sondern auf jumpPower gesetzt. Das Skript sieht dann also so aus: Da es für den Spieler etwas frustrierend sein kann, sekundenlang durch die Luft zu fliegen, während er schon weiß, dass er den Sprung verpatzt hat, bauen wir Air Control ein, damit er sich im Sprung noch retten kann. Dazu wieder eine neue Variable, die nennen wir airControl, sie ist wieder vom Typ float und hat bei mir erstmal den Wert 15 (Warum höher als speed? Siehst Du gleich). (Stattdessen kann man z.B. auch airControlFactor einbauen, der mit speed mutlipliziert den selben Zweck erfüllt. Ich bleibe aber beim ersten Ansatz.) In den Rumpf des else, also, wenn der Spieler in der Luft ist, bauen wir dazu ein, dass dir.x um Input.GetAxis("Horizontal") erhöht (nicht gesetzt) wird, und zwar multipliziert mit Time.deltaTime (weil es eine Beschleunigung ist und damit FPs-abhängig!) und airControl anstatt speed. Das fertige Skript sieht jetzt so aus: Wie man sieht, muss airControl mit Time.deltaTime multipliziert werden. airControl ist daher nicht direkt mit speed zu vergleichen und kann (und sollte, testet es ) daher durchaus einen höheren Wert annehmen. Das Skript ist somit erst einmal fertig - man kann laufen, springen und hat sogar Air Control. Damit also... Zurück zum CharacterController Eingangs erwähnte ich, dass die Kapselform sehr gut für Menschen und dergleichen geeignet ist. Warum? Nun, zuerst einmal ist die Kapsel ein Rotationskörper und, da man den CharacterController nicht neigen kann, in diesem Fall um die y-Achse. Das heisst: Man kann den Controller um die y-Achse drehen, ohne dass sich die Form, von irgendeiner Seite gesehen, ändert. Ergo kann man beim Drehen um die y-Achse nirgendwo mit kollidieren! Vergleich mit einem BoxCollider: Dreht man ihn, könnte man mit einer Kante irgendwo anecken. Für uns bedeutet das jedenfalls, dass man den CharacterController völlig sorglos über transform.Rotate() um die y-Achse drehen kann, ohne fürchten zu müssen, schlaue Spieler könnten sich damit in eine Wand hineindrehen. Deshalb hat der CharacterController auch keine Rotate()-Funktion. Des Weiteren ist die Kapsel nicht nur ein Zylinder, sondern hat oben und unten je eine Halbkugel. Das ermöglicht es, die Kapsel Schrägen oder kleine Stufen hinauflaufen zu lassen, ohne dass das als Kollsion mit der Seite erkannt wird. Die Halbkugelform ermöglicht es sogar, einen genauen Winkel anzugeben, bis zu dem eine Schräge erklommen werden kann (Eigenschaft "Slope Limit"). Erweiterungsbeispiele Dieses Skript kann man natürlich nach blieben mit feinen Raffinessen erweitern. Wie wäre es z.B. mit einem Multijump? Anstatt einer einfachen Geschwindigkeit könnte man auch ein beschleunigtes Laufen bauen! Fertig So, jetzt kannst Du einen CharacterController für einen Sidescroller programmieren! Viel Spaß damit! Anmerkungen und Kritik: Immer her damit. Sascha Ende.
    10 points
  19. Hey, wir haben gestern unser Spiel Juicy Jelly Barrel Blast in den Playstore gestellt, iOS version kommt demnächst. Das Spiel ist ein von den Kanonen Sequenzen aus Donkey Kong Country inspirierter one button shooter. Komplett kostenlos, und ohne Werbeunterbrechung wenn man es nicht will. Wir freuen über jeden der es ausprobiert Googleplay https://play.google....otale.cannonrun Trailer Website http://www.barrelblast.net
    9 points
  20. Howdi, wir wollen euch einladen am Donnerstag um 20 Uhr zu uns in den Chat zu kommen. Wieso? Es ist nervig für alle einfach nur den IRC Thread zu pushen. Durch einen festen Zeitpunkt und Datum ist es möglich mal mehr als nur 2 oder 3 Leute dort anzutreffen. In letzter Zeit gab es durch einen Troll einiges an Probleme die zum Forum übergeschwappt sind (siehe Kommentarbereich), wir wollen zeigen dass wir nun Trollfrei sind. Nicht jeder mag Skype/TS oder ist in der Nähe eines RL Stammtisches. Wann? Diesen Donnerstag den 31 März Um 20 Uhr. Wo? Wir treffen uns im IRC Addresse: irc.euirc.net Port: 6666 Channel: #unity Wie? Einfach per WebIRC: [Link] Oder einen der feinen IRC Clients: [Nettalk] [Mirc] [oder viele weitere]
    9 points
  21. Hehyo liebe Unity Insider, leider war ich im letztem Jahr hier ja leider nur noch sehr passiv aktiv - ich habe viele Projekte hier verfolgt, hatte aber leider keine große Zeit selber aktiv zu sein. Kann ja aber wieder kommen. Wie manche von euch schon wissen, habe ich in den letzen 9 Monaten bei King Art an so coolen Projekten wie "The Book Of Unwritten Tales 2" und "Die Zwerge" arbeiten dürfen. Jetzt im Oktober geht es endlich mit dem Studium los, und darum geht es in diesem "Showroom". Ich habe mich im Februar bei der Filmakademie Baden-Württemberg beworben, für den Studiengang "Interaktive Medien". Die Hochschule ist sehr gefragt, und in der Branche super anerkannt - und es werden jedes Jahr nur 3 neue Studenten für Interaktive Medien aufgenommen. Im Juni kam dann der Bescheid, dass ich zur Aufnahmeprüfung zugelassen bin - das hat mich schonmal sehr gefreut. Die Prüfung lief über 3 Tage - nach einem persönlichen Gespräch konnte man nach Hause fahren und an der gestellten 72h Aufgabe arbeiten. Diese musste dann pünktlich vor Ort abgeben und präsentieren. Man konnte aus 3 Themen wählen, ich habe mich für die erste Aufgabe entschieden: Man sollte einen Prototypen für ein Spiel entwickeln, zum Thema "Beleben Sie das Objekt" und dem Bild einer Blumenknospe. Und das habe ich in den 3 Tagen daraus gebaut: Man spielt einen kleinen Marienkäfer, der über eine riesige Knospe klettern muss und gegen die Zeit die Knospe von Blattläusen befreit. Schafft man es, alle Blattläuse in der Zeit zu fressen, öffnet sich die Blüte und man hat das Spiel gewonnen. Alle Modelle, Texturen, Scripts & was damit zusammenhängt wurde im gegebenen Zeitraum erstellt. Wer das Spiel mal selber spielen möchte (das geht wirklich sehr schnell ), kann es gerne hier bei meiner Drive herunterladen (Windows + Mac): https://drive.google...ZNm8&authuser=0 Bei der Präsentation zeigten sich die Prüfer sehr begeistert - auch wenn ich es tatsächlich geschafft habe, das Spiel während der Präsentation nicht zu gewinnen . Vor kurzem kam dann der Bescheid, dass ich zum Studium zugelassen bin. Also findet ihr mich ab Oktober in Ludwigsburg!
    9 points
  22. Multiplayer Tutorial Teil 1 - Einfaches Verbinden über direkte IP und ein kleiner Chat Das Programmieren von Multiplayer-Spielen ist nicht einfach und kann sich leicht zu einer zeit- und lustraubenden Angelegenheit "verwachsen". Aber wir haben Glück, denn in Unity3D ist die Netzwerkprogrammierung fast ein Kinderspiel! Als erstes werden wir uns mit den Grundlagen beschäftigen und einen kleinen Chat programmieren. Hier gibt es das ganze als Webplayer-Beispiel. Am besten öffnet ihr es in 2 Tabs und macht eins zum Server und 1 zum Client: http://www.doublem.b...ltiplayer1.html Verbinden über direkte IP Fangen wir ganz vorne beim leichtesten an: Erstelle eine neue Javascript-Datein und nenne sie z.B. Connect.js. Diese kannst du jetzt einem GameObject zuweisen, ich habe es bei der Camera gemacht, geht aber natürlich auch bei jedem anderen. Damit das Skript auch funktioniert musst du noch einen Network-View hinzufügen (Component -> Miscellaneous -> Network View) Wir brauchen ein paar Variablen um die IP des Servers, den Port und einen kleinen Statustext zu speichern. var ip_Address : String = "127.0.0.1"; // Die Variable die die ipAdresse beinhaltet (als Standard localhost) var Port : String = "25001"; // Die Variable für den Port (standardmäßig 25001) var Status : String = "Standby"; // Ein Statuskommentar das wir in einem Label anzeigen, in etwa "Verbinde zu Server...." Ich denke das ist selbsterklärend. Der Statuskommentar ist nicht unbedingt notwendig, sondern nur dafür da, um den Benutzer nicht im trüben fischen zu lassen und ihm ggf. einen Fehler auszugeben. Als nächstes müssen wir irgendwie dem Benutzer die Möglichkeit geben den Server/Client zu starten und zu bedienen. Dafür brauchen wir was? - Eine GUI natürlich! function OnGUI(){ // Fenster anzeigen [...] GUI.Window(0,Rect(0,0,200,200),NetworkingWindow,"Verbindungsmanager"); } /* Die Fensterfunktion vom Hauptmenuefenster */ function NetworkingWindow (id : int) { /* noch leer */} Jetzt müssen wir das Fenster nur noch mit Leben füllen, und das ist der wirklich spannende Teil: Wir werden generell überprüfen, ob wir schon verbunden sind, oder noch "Standby" und dementsprechend handeln. Das kann man mit Network.peerType abfragen, der z.B. die Werte NetworkPeerType.Disconnected, NetworkPeerType.Connected, NetworkPeerType. Client usw. annehmen kann. if (Network.peerType == NetworkPeerType.Disconnected) // Wenn keine Verbindung besteht... { // tue etwas... } else // Wir sind schon verbunden bzw. der Server läuft schon! { // tue etwas (oder nichts) } Zuerst werden wir uns um den ersten Teil kümmern, also wenn keine Verbindung besteht. if (Network.peerType == NetworkPeerType.Disconnected) // Wenn keine Verbindung besteht... { // tue etwas... } else // Wir sind schon verbunden bzw. der Server läuft schon! { // tue etwas (oder nichts) if(Status.length > 0) // Wenn der Status-text länger als 0 Zeichen ist... GUILayout.Label(Status); // Status anzeigen Wenn der Statuskommentar mehr als 0 Zeichen hat zeigen wir ihn mit einem Label an // Ein Eingabefeld für den Spielernamen erzeugen und den Text in der Registry speichern GUILayout.Label("Name:"); PlayerPrefs.SetString("Player Name",GUILayout.TextField(PlayerPrefs.GetString("Player Name"))); // Ein Eingabefeld für den Spielernamen erzeugen und den Text in der Registry speichern GUILayout.Label("Name:"); PlayerPrefs.SetString("Player Name",GUILayout.TextField(PlayerPrefs.GetString("Player Name"))); Das ist schon etwas komplizierter. Es wird als erstes ein Text "Name" angezeigt. Danach ein Eingabefeld, und der darin eingegebene Name wird in den PlayerPrefs unter dem Key "Player Name" gespeichert. Anmerkung: Mit PlayerPrefs kann man auf die Registry zugreifen und darin auch über das Programmende hinaus Daten speichern. Mit PlayerPrefs.SetString("Key", "Wert"); kann man unter dem Schlüsselwort "Key" dort einen Wert "Wert" abspeichern und dann mit PlayerPrefs.GetString("Key"); wieder abfragen. Es folgen zwei Text- und Eingabefelder für die IP-Adresse und den Port: GUILayout.Label("IP-Adresse:"); // Den text IP-Adresse anzeigen ip_Address = GUILayout.TextField(ip_Address); // Eingabefeld für die IP-Adresse GUILayout.Label("Port:"); // Den Text Port: anzeigen Port = GUILayout.TextField(Port); // Eingabefeld für den Port Und jetzt kommt der Kern des ganzen. Wir erstellen einen Button um sich als Client zum Server an der angegebenen IP zu verbinden. if (GUILayout.Button("Zu direkter IP verbinden")) // Button erstellen { // Status auf "Verbinde zu Server...." setzen Status = "Verbinde zu Server..."; Debug.Log(Status); // Wenn Button geklickt ist: // Netzwerkeinstellung Nat ausschalten Network.useNat = false; // zu bestimmter IP und bestimmtem Port verbinden // [Port wird mit ParseInt nach int konvertiert] Network.Connect(ip_Address, parseInt(Port)); } Die Einstellung useNat wird ausgeschaltet weil sie hier nicht so wichtig ist, aber ihr könnt sie gerne testweise anschalten. Mit der Funktion Network.Connect(ip,port) kann man nun den ganzen Zauber tun und sich verbinden. Zusätzlich wird der aktuelle Status auf dem Label und im Log ausgegeben. Das gleiche folgt noch für den Fall, dass der Benutzer sich nicht verbinden, sondern einen Server eröffnen will: if (GUILayout.Button("Server starten")) // Button erstellen { // Status auf "Starte Server..." setzen Status = "Starte Server..."; Debug.Log(Status); // Wenn Button geklickt ist: // Netzwerkeinstellung Nat ausschalten Network.useNat = false; // Server aufmachen // der erste Parameter gibt die maximale Spielerzahl an // der zweite den Port für den server // (ich nehme hier einfach den normalen Port wie beim Client, kann man aber auch anders machen) // [Port wird mit ParseInt nach int konvertiert] Network.InitializeServer(32, parseInt(Port)); } Auch hier wieder der Statuskommentar und Nar, ansonsten ist nur die Funktion Network.InitializeServer anders. als ersten Parameter gibt man die maximale Spieleranzahl und als zweiten den gewünschten Serverport an. Damit sind wir mit dem verbinden-Teil schon durch. Jetzt noch den teil zum ausschalten... else // Wir sind schon verbunden bzw. der Server läuft schon! { if(Status.length > 0) // Wenn der Status-text länger als 0 Zeichen ist... GUILayout.Label(Status); // Status anzeigen if (GUILayout.Button ("Verbindung beenden")) { // Status auf "Trenne Verbindung(en)..." setzen Status = "Trenne Verbindung(en)..."; Debug.Log(Status); Network.Disconnect(200); // Die Verbindung trennen (innerhalb von 200ms) } Wenn es einen Statuskommentar gibt wird er angezeigt, sonst nicht. Dann wird noch ein Button erstellt zum Beenden, und wenn man ihn drückt terminiert Network.Disconnect unsere schöne verbindung Jetzt wäre das ganze schon testfähig, aber es kommt noch ein kleiner Zusatz, damit man auch etwas damit machen kann: ein Chat. Chat Erstelle wieder mal eine neue Datei (z.B. Chatscript.js) und weise sie dem selben GameObject zu wie das Connect-Script. Wir brauchen auch wieder Variablen: var scrollPosition : Vector2; // Die Position des Skrollbalken var chatEntrys = ArrayList(); // Ein Array mit dem Chatinhalt var window = Rect(Screen.width/2-200,Screen.height/2-100,400,200); // Rechteck für das Chatwindow var inputField : String = ""; // Textfeldstring var scrollPosition : Vector2; Das wichtigste ist chatEntrys, ein Array von Strings in dem der gesammte Chatverlauf gespeichert wird. Das fensterrechteck window wird hier nur aufgeführt und deklariert, damit man das Fenster auch verschieben kann. Beim Code für das Chatfenster ist eine Minimalität anders: Wir weisen window den Rückgabewert von GUI.Window zu. Dadurch wird window, wenn das Fenster verschoben wird, aktualisiert und das Fenster kann verschoben werden. function OnGUI(){ window = GUI.Window (1, window, GlobalChatWindow, "Chat"); } function GlobalChatWindow (id : int) {/* noch leer */} Als nächstes geht es in "GlobalChatWindow" weiter. Wir erstellen einen Skrollbalken und listen dann alle Einträge aus dem Array nacheinander auf. // Skrollbalken erstellen scrollPosition = GUILayout.BeginScrollView (scrollPosition); for (var entry : String in chatEntrys) // jeden einzelnen durchgehen { GUILayout.BeginHorizontal(); GUILayout.Label(entry); // und für jeden ein textfeld erstellen GUILayout.EndHorizontal(); } // Skrollbalken Ende GUILayout.EndScrollView (); Dann müssen wir natürlich noch ein Eingabefeld erstellen... inputField = GUILayout.TextField(inputField); Und das Event abfangen wenn jmd dort "Enter" drückt... if (Event.current.type == EventType.keyDown && Event.current.character == "\n" && inputField.Length > 0) // Wenn Enter gedrückt ist und mehr als 0 zeichen im Textfeld { var playerName = PlayerPrefs.GetString("Player Name"); // Den Spielernamen aus der Registry auslesen if(playerName == "") // Wenn kein Name eingegeben oder Fehler... { playerName = "Benutzername"; //... den Namen zu Benutzername setzen } // dann den Namen, einen Doppelpunkt und den Text per rundnachricht im Netzwerk versenden networkView.RPC("ChatText", RPCMode.AllBuffered, playerName+": "+inputField); inputField = ""; // Textfeld zurücksetzen } Wenn Enter gedrückt wird lesen wir erstmal den vorher in der Registry gespeicherten Namen aus und versenden dann mit networkView.RPC eine "Rundnachricht" im Netzwerk. Als Parameter nimmt es einen Funktionsnamen (s.u.), den Empfänger und den Text (bzw andere Parameter) Wenn man z.B. networkView.RPC("HelloWorld", RPCMode.AllBuffered,"Hello!") angibt wird in jedem verbundenen Client und Server die Funktion "HelloWorld" gestartet mit dem Parameter "Hello!". Als 2. Parameter kann man auch eine IP-Adresse angeben oder RPCMode.Server, RPCMode.others usw. Dann noch dafür sorgen, dass man das Fenster auch wirklich verschieben kann und dann sind wir mit der Funktion durch. GUI.DragWindow(); Hier kommt jetzt die oben erwähnte RPC-Funktion. @RPC function ChatText(chatText : String){ chatEntrys.Add(chatText); } Das @RPC ist UNBEDINGT notwendig! Wenn in der Fensterfunktion der RPC "ChatText" aufgerufen wird wird auf jedem Client (und Server) und bei uns selber die Funktion ChatText ausgeführt. Dann fügen wir den aktuell geschriebenen Text noch zum Array dazu und sind damit dann auch fertig. Und noch etwas generelles: Wenn ihr mehrere Clients bzw. Client und Server auf einem PC laufen lasst solltet ihr auf jeden Fall Edit -> Project Settings -> Player -> Run in Background anschalten. So, das wars erstmal, ich hoffe ich konnte Euch helfen! Hier nochmal die beiden Skriptdateien zum Downloaden:http://www.doublem.b...ads/Scripts.zip Ich habe insbesondere in der 1. Datei noch ein paar Eventverarbeitungen usw. hinzugefügt, also unbedingt ansehen mfg DoubleM [PS: Aus dem Alten Forum kopiert] [PPS: Überarbeitet, da beim Kopierungsvorgang aus dem alten Forum sämtliche Formatierungen durcheinander geraten sind]
    9 points
  23. 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
    9 points
  24. Lost in Nature ist jetzt auf Steam im Early Access verfügbar! Lost in Nature auf Steam kaufen Lost in Nature Webseite besuchen Lost in Nature auf Facebook Lost in Nature auf Twitter Lost in Nature auf YouTube Deutscher Newsletter Abonnieren English Newsletter Subscription Hey Gerne stelle ich euch mein Spiel vor! Spielbeschreibung Man ist ein Schiffbrüchiger, der auf einer Insel gestrandet ist und versucht zu überleben, indem man nicht verdurstet, verhungert, zusammenbricht, durch die Einsamkeit durchdreht oder von wilden Tieren getötet wird. Durch sammeln, jagen, pflanzen, kombinieren und bauen von Objekten richtet man sich auf der Insel ein und erforscht sie nach und nach. Ziel des Spiels ist es, die Insel wieder heil zu verlassen. Hier ein Video, wie man 4.5 Tage überleben kann (veraltete Version V0.15): Update V0.25 vom 01.01.2015 Change Log Update V0.2 vom 25.05.2014 Change Log Update V0.15 vom 27.04.2014 Change Log Welche Features sind bereits vorhanden und Spielbar? -> Ingame Intro -> Lokalisierungs System Deutsch & Englisch -> Dynamischer Tag/Nacht Zyklus -> Inventar & Bau Menü -> Tagebuch mit Notizen und Hinweisen -> Status Menü -> Authentische Soundkulisse (Wind, Wellen, Schritte, ...) -> Schwimmen & tauchen -> Über 50 Items die gesammelt und gebaut werden können -> Die Vegetation wächst und erholt sich realistisch -> Samen können gepflanzt werden -> Bienenstöcke plündern -> Fallen bauen und stellen -> Authentische Tierwelt mit Wölfen, Bären und Fröschen Was wird als nächstes eingebaut? -> Krabben am Strand -> Rebhühner -> Speicher/Lade System -> Buff/Debuff System -> Wettersystem -> Fischrute mit Fischereispiel -> Steinschleuder und Bogen -> Verbessertes, interaktives Intro Steuerung Bewegen mit [W][A][D], Springen [LEER], Sprinten [uMSCHALT], kleine Objekte aufheben [E], Objekte benutzen [Maus L] gedrückt halten oder [E] bei gebauten Objekten. Statusmenü , Tagebuch [J], Inventar und Baumenü [TAB]. Objekte werden per Drag & Drop bewegt. Einfach in die Spielwelt ziehen um sie abzulegen oder auf ein Objekt ziehen (z.B. Lagerfeuer) um sie zu kombinieren. Einige Dinge können mit dem Boden benutzt werden, indem man sie darüber draggt und [E] drückt. Objekte werden gebaut, indem man sie aus dem Baumenü in die Spielwelt draggt. Sprache Deutsch/Englisch mit [F11][F12] in Echtzeit änderbar. Items können im Inventar durch [Maus R] benutzt werden (essen/trinken/ausrüsten). In der oberen linken Ecke siehst du 5 Balken. Grün = Vitalität, Orange = Hunger, Blau = Durst, Lila = Psyche und Gelb = Erschöpfung. Wenn ein Balken den rechten Rand erreicht, verlierst du Vitalität und stirbst. Die Nacht überlebst du nur, wenn du an einem brennenden Lagerfeuer bist. Sorge für genügend Brennholz und Nahrungsmittel. Wenn du zum Beispiel Rinde sammelst, dann ist es vielleicht schlau, ein Messer zu benutzen, weil es schneller geht. So ist alles in LIN1 – du kannst alles auf verschiedene Methoden tun, aber einige sind schlauer als andere. Ob du dieses Abenteuer überleben wirst, hängt ganz von dir ab. Fragen -> Was haltet ihr vom Depth of View Effekt? -> Der Spielname? "LIN1 - Lost in Nature" ist nur der vorübergehende Name. Habt ihr eine gute Idee für einen neuen? -> Realistisch oder Lowfantasy? Ich fände es absolut genial, wenn es auf der Insel auch Riesenspinnen, Skelette und Rattenmenschen gäbe (mit den Rattenmenschen könnte man auch kooperieren oder mit ihnen Krieg führen) und ein cooles Alchemie-Tränkebrau-System. Aber auf der anderen Seite ist eine realitätsnahe Simulation auch ganz interessant. Was denk ihr? Auch im Lowfantasy Stil wäre es eine knallharte Simulation und kein "Comic-Arcade" geplänkel. -> Ab wann empfehlt ihr dieses Spiel bei Steam Greenlight anzumelden? Es soll ja auch schon was zu bieten haben und Spass machen. Es gibt zu viele "Verarschungen" auf Greenlight. -> Hab ihr Ideen, Tipps, Bugs, Anregungen, Kritik, Lob?!?! Ich will alles wissen!! Cheatcodes Download LIN V0.25 Windows 32 Bit Version ~240MB http://www.mmoinside...-V0.2-win32.rar LIN V0.25 Mac 32 Bit Version ~270MB http://www.mmoinside...-V0.2-mac32.zip ---------- LIN V0.2 Windows 32 Bit Version ~200MB http://www.mmoinside...-V0.2-win32.rar LIN V0.2 Mac 32 Bit Version ~230MB http://www.mmoinside...-V0.2-mac32.zip ---------- LIN V0.15 Windows 32 Bit Version ~200MB http://www.mmoinside...-V0.15-win32.rar LIN V0.15 Mac 32 Bit Version ~220MB http://www.mmoinside...-V0.15-mac32.zip ---------- LIN V0.1 Windows 32 Bit Version ~180MB http://www.mmoinside...-V0.1-win32.rar LIN V0.1 Mac 32 Bit Version ~180MB http://www.mmoinside...-V0.1-mac32.zip
    8 points
  25. Zeit für etwas Umbauarbeiten, bisher haben wir einen PlayerController der etwas mehr macht als nur zu kontrollieren. Schwer zu glauben bei nur 43 Zeilen nicht wahr? Schauen wir uns einmal an was unser PlayerController so macht, er reagiert auf Tastendrücke vom Spieler und verändert direkt dadurch die Geschwindigkeit und Richtung unseres Raumschiffes. Das sind 2 verschiedene Aufgabenbereiche die wir trennen sollten. Um anschließend die Stärke des von Unity uns auferlegten Komponentensystemes auszuspielen. Wiederverwendbarkeit! Unser PlayerController Script sollte wie der Name schon impliziert nur dafür zuständig sein die Eingaben des Spielers auszuwerten und das was dann damit gemacht werden sollte an andere Komponenten übergeben. Deswegen die Zweiteilung des Scriptes. Ein Teil wird die Eingaben entgegen nehmen, dies bleibt unser PlayerController und der andere Teil wird die Geschwindigkeit und Drehung des Schiffs kontrollieren. Wir fangen an indem wir uns ein neues C# Script erstellen und es "Drive" (Antrieb) nennen. Für unsern Antrieb, welchen wir später wiederverwenden werden (zB für gegnerische Raumschiffe) benötigen wir folgende Dinge: - Beschleunigen (Accelerate) - Abbremsen (Deaccelerate) - Drehen Und um die Steuerung leichter zu machen fügen wir noch etwas hinzu: - Automatisches abbremsen Automatisches abbremsen ist eine passive Eigenschaft unseres Scriptes, bedeutet das wir es nicht extra aufrufen müssen damit es funktioniert. Wir beginnen daher damit dass wir uns einige Basiselemente vom PlayerController nehmen und diese im Drive Script einfügen. Und zwar alle Elemente die direkt den Antrieb des Schiffes betreffen. Was so ziemlich alle Variablen des PlayerController Scriptes wären: public float Acceleration = 1000.0f; public float Deacceleration = 1000.0f; public float RotationAcceleration = 300.0f; public float MaxForce = 1000.0f; public float MinForce = -500.0f; public float CurrentForce = 0.0f; Dies sollte im Drive Script stehen und nicht im PlayerController Script. Nun legen wir mit der ersten Fähigkeit unseres Drive Scriptes los: Beschleunigen! Erstellt eine neue Methode mit dem Namen Accelerate welche einen zusätzlichen float Parameter entgegen nimmt. Dieser Parameter soll dazu dienen fein zu regeln wie sehr wir beschleunigen wollen. Da wir bisher selbst noch keinerlei Parameter in selbst geschriebenen Methoden benutzt haben, hier die Methode: public void Accelerate(float factor) { } Wir benutzen hier so wie bei den Variablen ein public direkt vor der Methode. Da wir diese Methode von dem PlayerController Script aufrufen wollen muss diese public sein (öffentlich erreichbar). Wer nun ein wenig aufgepasst hat weiß auch was ungefähr in diese Methode hinein muss. Den Part vom PlayerController Script welcher ausgeführt wird wenn wir die nach Oben Taste drücken: CurrentForce += Time.deltaTime * Acceleration; Wir müssen noch unseren factor verwenden, wir verwenden ihn indem wir alles rechts von += mit dem factor multiplizieren. CurrentForce += Time.deltaTime * Acceleration * factor; Genau das Gleiche erledigen wir für die Deaccelerate Methode: public void Deaccelerate(float factor) { CurrentForce -= Time.deltaTime * Deacceleration * factor; } Wenn ihr euch noch daran erinnert haben wir im PlayerController Script eine Begrenzung der CurrentForce eingebaut, bisher machen wir dies immer egal ob wir Beschleunigt haben oder Abgebremst haben. Diesen kleinen Fehler können wir beheben indem wir die Stelle welche die Begrenzung aufrechterhält, nach Accelerate und Deaccelerate überträgt. Als Beispiel, so sollte die Accelerate Methode nach dieser Ergänzung aussehen: public void Accelerate(float factor) { CurrentForce += Time.deltaTime * Acceleration * factor; CurrentForce = Mathf.Clamp(CurrentForce, MinForce, MaxForce); } Nun haben wir die Beschleunigung und Abbremsung, aber etwas Kleines fehlt hierbei noch, wir wollen ja eine automatische Abbremsung wenn weder beschleunigt als auch abgebremst wurde. Aus diesem Grund merken wir uns wenn eines dieser beiden Aktionen ausgeführt wurde. Dazu erstellen wir uns zuerst eine neue Variable für das Drive Script: private bool hasChangedCurrentForce = false; Wir ihr bemerkt habt ist diesmal ein private vor dem Typ und kein public. Dies liegt daran dass wir nicht wollen dass diese Variable ausserhalb des Scriptes Bedeutung hat und auch nicht angefasst werden soll. private bedeutet dass die Variable oder Methode nur im inneren der Klasse in der es definiert wurde verwendet werden kann. Wir setzen hasChangedCurrentForce auf true immer dann wenn wir Accelerate oder Deaccelerate verwenden: hasChangedCurrentForce = true; Fügt diese Zeile in beide Methoden ein. Nun fehlt nur noch das Drehen, dies können wir lösen indem wir uns 2 Methoden, für jede Richtung in die wir drehen können wollen eine, einfügen. Wie bei Accelerate und Deaccelerate benutzen wir einen factor Parameter. Und wie zuvor übernehmen wir zu aller erst den Inhalt aus dem PlayerController Script welche für die Drehungen zuständig waren: public void RotateRight(float factor) { rigidbody.AddTorque(0, Time.deltaTime * RotationAcceleration * factor, 0); } public void RotateLeft(float factor) { rigidbody.AddTorque(0, -Time.deltaTime * RotationAcceleration * factor, 0); } Das wars. Die Drehung wurde eingebaut und sollte funktionieren. Was nun noch fehlt ist die automatische Abbremsung und die Anwendung der CurrentForce. Der letzte Part ist der einfachste, da wir auch hier direkt den Code aus dem PlayerController übernehmen können. Daher kopiert die notwendige Zeile und fügt sie in die Update Methode des Drive Scriptes ein: void Update () { rigidbody.AddForce(CurrentForce * Time.deltaTime * transform.forward); } Nun zum letzten Feature des Scriptes, die automatische Abbremsung. Da wir am Anfang des Tutorials klar gestellt haben wann dies einsetzen soll und zwar nur dann wenn weder beschleunigt noch abgebremst wurde, können wir bereits die ersten Zeilen dafür schreiben (wieder in die Update Methode): if (!hasChangedCurrentForce) { } Wir setzen hasChangedCurrentForce immer auf true wenn wir beschleunigen oder abbremsen. Nach der Prüfung sollten wir daher die Variable wieder auf false setzen. Schreibt dies direkt unter den if Scope: hasChangedCurrentForce = false; Wie soll nun aber die automatische Abbremsung funktionieren? Ganz einfach, wir schauen uns die Richtung an in der unser Raumschiff schaut (transform.forward) und vergleichen dies mit der Richtung und Geschwindigkeit in der wir uns bewegen (rigidbody.velocity). Wir benutzen dazu die Vector3.Dot Methode welche uns einen Wert liefert den wir benutzen werden um zu entscheiden ob wir Beschleunigen müssen (wenn wir rückwärts fliegen) oder Abbremsen müssen (wenn wir vorwärts fliegen). Tragt dazu folgendes in den if Scope für die automatische Abbremsung ein: var forwardFactor = Vector3.Dot(transform.forward, rigidbody.velocity); Diesen forwardFactor verwenden wir nun für die Entscheidung was gemacht werden soll: if (forwardFactor > 1.0f) { Deaccelerate(1.0f); } else if (forwardFactor < -1.0f) { Accelerate(1.0f); } else { CurrentForce = 0; } Das if Keyword kennen wir ja bereits, neu sind dagegen das else if und das else. else if wird aufgerifen wenn das vorherige if oder ein vorheriges else if selbst nicht erfüllt werden konnten. else if bietet also einen alternativen Weg an. Sollten auch alle folgenden else if scheitern so wird der Code im else Scope ausgeführt. if, else if und else können also übersetzt werden mit: Wenn X dann Y, Ansonsten Wenn Z dann W, Ansonsten R. Um einen Ping Pong Effekt zu vermeiden interessieren wir uns nur forwardFactor wenn er größer 1 oder kleiner -1 ist, im anderen Fall lassen wir den Luftwiederstand für uns arbeiten, den es im Weltraum zwar nicht gibt, aber für das Spielgefühl dennoch zuträglich ist. Wir stellen daher den "Drag" Wert in der "Rigidbody" Komponente des Spieler Raumschiffs auf 1. Dies sorgt dafür dass wir wenn wir keine Kraft auf unser GameObject auswirken, dieses GameObjekt immer langsamer wird. Unser Drive Script ist damit fertig. Zur Vollständigkeit halber, hier das Drive Script: using UnityEngine; using System.Collections; public class Drive : MonoBehaviour { public float Acceleration = 1000.0f; public float Deacceleration = 1000.0f; public float RotationAcceleration = 300.0f; public float MaxForce = 1000.0f; public float MinForce = -500.0f; public float CurrentForce = 0.0f; private bool hasChangedCurrentForce = false; // Use this for initialization void Start () { } // Update is called once per frame void Update () { rigidbody.AddForce(CurrentForce * Time.deltaTime * transform.forward); if (!hasChangedCurrentForce) { var forwardFactor = Vector3.Dot(transform.forward, rigidbody.velocity); if (forwardFactor > 1.0f) { Deaccelerate(1.0f); } else if (forwardFactor < -1.0f) { Accelerate(1.0f); } else { CurrentForce = 0; } } hasChangedCurrentForce = false; } public void Accelerate(float factor) { CurrentForce += Time.deltaTime * Acceleration * factor; CurrentForce = Mathf.Clamp(CurrentForce, MinForce, MaxForce); hasChangedCurrentForce = true; } public void Deaccelerate(float factor) { CurrentForce -= Time.deltaTime * Deacceleration * factor; CurrentForce = Mathf.Clamp(CurrentForce, MinForce, MaxForce); hasChangedCurrentForce = true; } public void RotateRight(float factor) { rigidbody.AddTorque(0, Time.deltaTime * RotationAcceleration * factor, 0); } public void RotateLeft(float factor) { rigidbody.AddTorque(0, -Time.deltaTime * RotationAcceleration * factor, 0); } } Nun müssen wir es nur noch benutzen. Auf auf zum PlayerController Script, welches nun so aussehen sollte: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (Input.GetKey(KeyCode.UpArrow)) { } if (Input.GetKey(KeyCode.DownArrow)) { } if (Input.GetKey(KeyCode.RightArrow)) { } if (Input.GetKey(KeyCode.LeftArrow)) { } } } Dieses Script wird unser Drive Script benutzen, daher sollten wir uns dieses Drive Script auch suchen. Fügt daher eine neue Variable in das PlayerController Script hinzu: private Drive drive; private deswegen weil es nur für die Internas des PlayerController Scriptes von Relevanz ist. Wir weisen diese Variable innerhalb der Start Methode zu: drive = GetComponent<Drive>(); GetComponent sucht eine Komponente, welche Komponente geben wir über einen generischen Parameter an. Die spitzen Klammern (< und >) umgrenzen die generischen Parameter. Generische Parameter sind immer Typen, mit GetComponent<Drive>() sagen wir demnach dass wir eine Komponente an unserem GameObjekt suchen welche vom Drive Typ ist. Alternativen zu dieser Variante eine Komponente zu suchen gibt es ebenfalls, entweder per Namen der Komponente (GetComponent("Drive")) oder wieder über den Typen der Komponente (GetComponent(typeof(Drive))). Wir benutzen die generische Variante, weil es automatisch einen Fehler gibt wenn es den Typen nicht gibt (zB: ein anderen Namen hat) was bei GetComponent("Drive") keinen Fehler geben würde. Ausserdem hat sie den Vorteil besser lesbar zu sein als GetComponent(typeof(Drive)). Ausserdem bekommen wir direkt als Rückgabetyp unser Drive zurück. Bei allen anderen Varianten müssen wir anschließend den Rückgabewert erst umwandeln, was wieder mehr Schreibarbeit bedeutet und auch unlesbarer wäre, vergleicht selber: drive = GetComponent<Drive>(); vs: drive = (Drive)GetComponent(typeof(Drive)); Fügt nun die Verwendung unseres Drive Scriptes in die Update Methode ein: void Update () { if (drive) { if (Input.GetKey(KeyCode.UpArrow)) { drive.Accelerate(1.0f); } if (Input.GetKey(KeyCode.DownArrow)) { drive.Deaccelerate(1.0f); } if (Input.GetKey(KeyCode.RightArrow)) { drive.RotateRight(1.0f); } if (Input.GetKey(KeyCode.LeftArrow)) { drive.RotateLeft(1.0f); } } } Wie man vermutlich gut sehen kann haben wir eine weitere if Bedingung eingefügt: if (drive) Damit können wir testen ob unsere Drive Komponente eventuell nicht gefunden oder gar gelöscht wurde. Das fertige PlayerController Script sollte nun so aussehen: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { private Drive drive; // Use this for initialization void Start () { drive = GetComponent<Drive>(); } // Update is called once per frame void Update () { if (drive) { if (Input.GetKey(KeyCode.UpArrow)) { drive.Accelerate(1.0f); } if (Input.GetKey(KeyCode.DownArrow)) { drive.Deaccelerate(1.0f); } if (Input.GetKey(KeyCode.RightArrow)) { drive.RotateRight(1.0f); } if (Input.GetKey(KeyCode.LeftArrow)) { drive.RotateLeft(1.0f); } } } } Startet nun wieder die Szene in Unity und schaut wie sich das ganze nun verhält. Abseits vom automatischen Abbremsen sollte sich alles wie gehabt verhalten. Feedback ist wiederum gern gesehen. PS: Das Forum macht mir immer die Tabs im Code kaputt ;( Teil 4: http://forum.unity-community.de/topic/7112-wir-bauen-uns-ein-space-shoot-em-up-teil-4-waffen/
    8 points
  26. 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!
    8 points
  27. C# ist schon seit langer Zeit das Mittel der Wahl für Programmieren für Unity. Der Support für Boo wurde gänzlich abgeschafft, und Unitys JavaScript-Variante beschert einem mehr Probleme als Vorteile. Leider stellt C# eine etwas größere Einstiegshürde dar als "UnityScript", aber nach eventuellen anfänglichen Scherereien wird es immer einfacher zu verwenden. Dieses Tutorial richtet sich an reine Programmieranfänger, aber auch Erfahrenere, die neu in Unity sind, können etwas davon haben. Dieses Tutorial ist weniger ein "Wie mache ich XY", als mehr eine generelle Einführung, die alle folgenden Schritte in der Welt von C# in Unity (besser) verstehbar machen soll. Am Ende wird also kein großartiges Ergebnis zum Zeigen vorliegen, aber dafür solltest du ein gutes Verständnis dafür haben, was in Unity überhaupt passiert. Mein Code als Komponente Wer dieses Tutorial liest, hat vermutlich den grundlegenden Workflow des Unity-Editors bereits vor Augen gehabt. Objekte in einer Szene (GameObjects) bestehen aus einer Reihe von Komponenten, die das GameObject ausmachen. So ist ein GameObject mit einem Collider etwas, wogegen andere Objekte prallen können, und eines mit MeshRenderer und MeshFilter ist ein sichtbares 3D-Modell. Folglich ist ein GameObject mit MeshRenderer, MeshFilter und Collider ein sichtbares 3D-Objekt, mit dem man kollidieren kann. Ein GameObject ist also die Summe aller seiner Komponenten. Für jedes Spiel muss das, was passieren soll, programmiert werden. Im Unity-Kontext kommt der Code, den man dabei schreibt, in Form von Komponenten zum Einsatz. Man schreibt also etwas Code in eine Datei und fügt diesen als Komponente zu einem GameObject hinzu. Diese Scripts sind größtenteils dazu da, das GameObject, dem sie hinzugefügt werden, zu beeinflussen. Ein Beispiel wäre ein Script, das das Objekt rotieren lässt. Man nehme also einen MeshRenderer, einen MeshFilter, einen Collider und dazu dieses Script als Komponente. Das ergebnis ist ein sichtbares 3D-Objekt, mit dem man kollidieren kann und das sich dreht. Diese Grundidee, in welcher Form ein Script das Spiel, das man baut, formt und beeinflusst, ist immer im Hinterkopf zu behalten. Mein erstes Script Um ein neues Script zu erstellen, einfach mit Rechts in die Assets (oder oben links aus "Assets") klicken, dann auf "Create", dann "C# Script". Die entstandene Script-Datei kann und sollte man direkt vernünftig benennen. Dabei ist es nicht unsinnvoll, sich an Unitys Namensgebung zu orientieren, denn der Dateiname wird gleichzeitig der Name der Komponente. Neben "MeshRenderer", "Collider" und "Light" würde also "SimpleRotation" oder "SimpleRotator" passen. Öffnet man das Script, wird der eingestellte Script-Editor gestartet. Zu empfehlen ist Visual Studio, aber hier kann man nach eigenen Präferenzen gehen. Was man dann sieht, ist in etwa folgendes: using UnityEngine; using System.Collections; public class ScriptName : MonoBehaviour { // Use this for initialisation void Start() { } // Update is called once per frame void Update() { } } Sehen wir uns die Einzelteile davon einmal an, damit wir anfangen können, darin zu arbeiten. Am Anfang stehen diese beiden Zeilen: using UnityEngine; using System.Collections; Diese Zeilen heißen "Imports" oder "Using-Statements". Sie geben für den Rest des Codes an, was alles an bereits existierendem Code bekannt sein soll. Unity ist ein riesiger Haufen Code, den unser Code kennen muss, damit man damit arbeiten kann. Die Light-Komponente z.B. ist irgendwo in "UnityEngine" vorhanden, und wenn man von einer solchen Komponente die Farbe ändern will, dann muss bekannt sein, was ein Licht überhaupt ist. Es ist also etwa so, als würde ein Schullehrer ein neues Thema anfangen und zum Beginn der Stunde einige vorherige Themen in's Gedächtnis rufen, auf denen der neue Stoff aufbaut. System.Collections ist standardmäßig mitimportiert, wird aber erst einmal gar nicht gebraucht. Diese Zeile kann man sogar problemlos löschen, bevor man weiter macht. Als nächstes folget ein Haufen Begriffe, gefolgt von einem Rumpf. Einen Rumpf erkennt man immer an den geschweiften Klammern { } . public class ScriptName : MonoBehaviour { // Das hier ist im Rumpf } Der C#-Standard ist eigentlich, die geschweifte Klammer in die nächste Zeile zu machen. Ist absolut Geschmackssache - bei mir sieht das jedenfalls so aus: public class ScriptName : MonoBehaviour { // Das hier ist im Rumpf } Wir sehen hier also etwas Text, gefolgt von einem Rumpf. In den allermeisten Fällen ist das, was vor dem Rumpf steht, mit dem Rumpf verbunden, oder besser: Der Rumpf gehört zu diesem Text. Was also sagt dieser Text aus? public class ScriptName : MonoBehaviour Zuerst einmal steht da "public class". Das public können wir jetzt erstmal ignorieren - es steht da und macht irgendetwas. "class" ist da schon wichtiger - es sagt aus, dass im folgenden Rumpf eine so genannte Klasse deklariert wird. Eine Klasse ist ein Begriff aus der Objektorientierten Programmierung und ist eine Art Bauplan für Objekte. Der Bildschirm vor dir ist sozusagen ein Objekt (oder "Instanz" oder "Exemplar") der Klasse "Bildschirm". Oder vielleicht der Klasse "Flachbildschirm", je nach dem, wie spezifisch da deklariert wurde. In unserem aktuellen Spezialfall ist ein Objekt eine Komponente. Man kann mehreren GameObjects eine Light-Komponente geben, und sie sind alle mehr oder weniger gleich. Sie alle haben eine Farbe, eine Intensität und mehr Eigenschaften, und sie alle machen auf irgendeine Weise Licht in der Szene. Und doch können sie alle unterschiedlich sein: Das eine Licht ist rot, das andere blau. Es handelt sich hierbei um verschiedene Objekte, aber der grundlegende Aufbaum, also die Klasse, ist immer dieselbe: Light. Wann immer wir in einer objektorienterten Sprache wie C# Code schreiben, existiert dieser in Form von Klassen. Jedes Script ist eine Klasse und jedes Mal, wenn wir das Script zu einem GameObject hinzufügen, wird ein neues Objekt, also eine neue Komponente dieser Klasse erstellt, die dann auf diesem GameObject existiert, bis sie oder das GameObject wieder gelöscht werden. Der nächste Begriff kommt uns bekannt vor: Es ist der Dateiname und damit der Name der Klasse, die wir hier schreiben wollen. Anschließend steht da noch etwas kryptisches: : MonoBehaviour Hier steckt eine ganze Menge drin, aber zu diesem Zeitpunkt kann man diesen Teil vereinfachen und sagen: Dieser Ausdruck sorgt dafür, dass Objekte unserer Klasse auch wirklich Komponenten sind. Löscht man diesen Teil, kann man das Script anschließend nicht mehr zu einem GameObject hinzufügen. Damit haben wir jetzt die Klassendefinition gesehen. Der darauffolgende Rumpf wird alle möglichen Informationen über unsere Klasse enthalten, so wie der Rumpf der Light-Klasse irgendwo in UnityEngine den Code enthält, der Objekte anleuchtet und der angibt, dass eine Lichtkomponente eine Farbeigenschaft hat. Im Rumpf stehen bereits irgendwelche Dinge, die wir uns gleich ansehen werden. Zuerst einmal gehen wir aber zurück in den Editor und sehen uns eine wichtige Kleinigkeit an - und zwar, wie man ein Script einem GameObject hinzufügt. Das ist denkbar einfach: Per Drag & Drop kann man die Datei auf den leeren Platz eines GameObjects im Inspektor ziehen; unter die anderen Komponenten. Man kann sie auch auf das GameObject in der Hierarchie ziehen oder den "Add Component"-Button im Inspektor benutzen. Scripts sind standardmäßig in der Kategorie "Scripts". Methoden Zwei Absätze weiter oben habe ich zwei Beispiele dafür gegeben, was in einer Klasse drinsteht: Die Light-Komponente leuchtet Objekte an, sie tut etwas, und sie hat eine Fabreigenschaft, sie ist irgendwie. Diese beiden Dinge, "wie es ist" und "was es tut" sind genau die beiden Bausteine, aus denen eine Klasse aufgebaut ist. Fangen wir an mit "was es tut". Die Abläufe dessen, was ein Objekt tut, sind in so genannten Methoden deklariert. Von denen hat unser neues Script bereits zwei: // Use this for initialisation void Start() { } // Update is called once per frame void Update() { } Eine leere Methode ist nicht ganz unähnlich einer leeren Klasse aufgebaut. Wieder haben wir einnige Worte, gefolgt von einem Rumpf. Diese beiden Methoden heißen Start und Update. Bei beiden steht vorher "void" und beide haben danach runde Klammern ohne etwas darin. Beides wird für uns erst später relevant und kann daher ignoriert werden. Interessant sind für uns die Namen der Methoden, aber dazu gleich mehr. Eine Methode ist grundätzlich eine Reihe von Anweisungen, die nacheinander ausgeführt werden, wenn die Methode aufgerufen wird. Eine Methode aufzurufen ist verlgleichbar damit, einen Knopf bei einem Gerät zu drücken. Plötzlich passieren im Gerät irgendwelche Dinge, und vielleicht kommt sogar ein sichtbares Ergebnis heraus, zum Beispiel eine Flasche Wasser. Allem voran steht aber das Drücken des Knopfes, oder eben das Aufrufen der Methode. Um eine Methode aufzurufen, ist es sozusagen erst einmal nur nötig, ihren Namen zu schreiben. Das testen wir gleich, aber vorher sehen wir uns die beiden Methoden an, die wir schon haben. Über den Methoden stehen so genannte Kommentare. Alles, was rechts von "//" steht, ist ein Kommentar und wird vom Computer komplett ignoriert. Man kann dort also alles hinschreiben, was den Code erklärt, wenn gewünscht auch auf deutsch. Die beiden Kommentare über unseren Methoden beschreiben eine Besonderheit, wenn man mit Unity entwickelt. Anhand ihrer Namen nimmt sich Unity das Recht, diese Methoden geradezu magisch im richtigen Moment aufzurufen. Eine Methode mit dem Namen "Start" wird einmalig aufgerufen, wenn das Objekt neu entsteht - das versteht sich inklusive aller Objekte zum Start des Spiels. Innerhalb eines Methodenrumpfs können jetzt unter anderem weitere Methoden aufgerufen werden. So entsteht eine sinnvolle Kette von Abläufen. Die verschiedenen Objekte werden hierbei wie eine Firmenhierarchie: Ein Kunde ruft in einer Firma an und bestellt eine Dienstleistung. Die Sekretärin informiert die Chefin. Die Chefin schickt einen Mitarbeiter los. Jede dieser Interaktionen ist eine mögliche Metapher für jeweils einen Methodenaufruf, und die verscheidenen Leute sind die Objekte. Wir bauen jetzt eine ganz simple Kette und fügen folgenden Code in die Start-Methode ein: // Use this for initialisation void Start() { Debug.Log("Hallo!"); } Debug.Log ist eine bestimmte Methode. Warum sie so heißt, wie sie heißt und wieso da ein Punkt drinsteckt, ist erst einmal egal. Die runden Klammern deuten den Methodenaufruf an. In den Klammern steht dieses Mal allerdings etwas. In den Anführungszeichen steht ein String, eine Zeichenkette, also etwas, das Worte, Sätze oder ganze Texte beinhalten kann. Debug.Log nimmt diesen String und schreibt ihn in Unitys Konsole. Am Ende jeder Anweisung wie dieser steht ein Semikolon ; , das die Anweisung beendet. Speichere das Script und ab zurück zu Unity. Stelle sicher, dass irgendein Objekt in der Szene das Script als Komponente hat und starte das Spiel. Du müsstest in der Konsole (Strg + Shift + C) und ganz unten links im Editor das "Hallo!" sehen können. Hier hat also Unity Start() aufgerufen und in Start wurde dann wieder Debug.Log aufgerufen. Der String in den Klammern nennt sich dabei Parameter. Ein Parameter spezifiziert genauer, was eine Methode tun soll. Während bei einigen Tätigkeiten keine Parameter nötig sind (z.B. "starte das Auto"), benötigen einige andere genauere Angaben, wie sie im jeweiligen Moment zu funktionieren haben (z.B. "Tritt das Gaspedal" - aber wie doll?). Bei Debug.Log muss angegeben werden, was genau in die Konsole geschrieben werden soll. Würde man der Methode nichts geben, also die runden Klammern leer lassen, würde der ganze Aufruf keinen Sinn mehr ergeben und der Code wird als fehlerhaft erkannt. Jetzt machen wir aber mal etwas Unity-bezogenes. Das Drehskript Wir bauen jetzt ein Skript, das das Objekt dreht, auf dem es als Komponente liegt. Zuerst machen wir das in der Start-Methode. Unser Objekt wird also einmalig zum Spielstart auf eine bestimmte Drehung gesetzt und bleibt dann so. Wenn es um Position oder Rotation geht, ist immer die Transform-Komponente wichtig. Sie ist immer ganz oben in der Komponentenliste und beim Verschieben oder Drehen eines Objekts geht es immer um ihre Felder. Sie hat außerdem die Besonderheit, dass jedes GameObject genau eine davon hat. Die Transform-Komponente kann man ansprechen, indem man einfach transform schreibt: void Start() { transform.Rotate(20, 20, 20); } Woah, jetzt aber mal langsam! Wir sehen hier wieder einen Methodenaufruf, zu erkennen an den Klammern nach einem Methodennamen (Rotate). Doch vor dem Methodennamen steht jetzt transform und dazwischen ein Punkt! Der Punkt ist extrem wichtig. Er erlaubt es, Methoden von anderen Objekten aufzurufen. Die Methode Rotate ist Teil der Klasse "Transform", also der Klasse, die als Bauplan für die Transform-Komponenten dient, die auf den GameObjects liegen. Um nun ein bestimmtes Objekt zu drehen, muss man angeben, welches Objekt das überhaupt ist. Beachte hierbei, dass ich nicht einmal von GameObjects rede, sondern von Objekten im programmiertechnischen Sinne. Die Transform-Komponente unseres GameObjects ist ein solches Objekt. Mit dem Punkt vereint man nun zwei Dinge: Zur linken steht das Objekt, von dem man etwas will, und rechts steht der Name der Methode, also das, was man vom Objekt will. In diesem Fall also: Ich will, dass sich unsere Transform-Komponente dreht. In den Klammern sind dieses Mal gleich drei Parameter. Diese entsprechen den drei Achsen, um die ein Objekt gedreht werden kann. Der Reihenfolge nach X, Y, und Z. Dieses Skript kannst du jetzt ausprobieren und nach Belieben die Zahlen ändern. Als nächstes wollen wir, dass sich das Objekt durchgehend immer weiter dreht, womit wir dem Ende dieses ersten Einstiegs nahe kommen. Wie in den Kommentaren im Code angedeutet, wird Update immer und immer wieder aufgerufen. Genauer: Es werden alle Update-Methoden von allen Komponenten auf allen GameObjects in der Szene aufgerufen, und danach wird das Bild neu gemalt. Der Ablauf ist eigentlich etwas komplexer, aber das ist das grundlegende Prinzip. Update findet auf jedem Objekt also genau so oft statt wie das Bild neu gezeichnet wird, also abhängig von den FPS des Spiels. Verschiebe nun einfach die Zeile mit transform.Rotate von Start() nach Update(), also in dessen Rumpf, speichere das Script und teste das Spiel. Dein Objekt dreht sich nun immer weiter, weil es in jedem Frame, also in jedem Update ein bisschen weiter gedreht wird. Aber warte mal... wenn das Spiel jetzt doppelt so viele FPS hast, dann wird Update doppelt so oft aufgerufen! Und wenn es sich doppelt so oft gleich weit dreht, dann dreht es sich doppelt so schnell! Hierfür gibt es Abhilfe: Time.deltaTime. Wie an den nicht vorhandenen Klammern zu erkennen, handelt es sich hier nicht um einen Methodenaufruf, sondern um eine so genannte Eigenschaft. Eine Eigenschaft stellt einen einzelnen Wert dar. Ein Beispiel dafür wäre wieder die Farbe einer Light-Komponente. Time.deltaTime allerdings gehört zu keinem Objekt - wie das kommt, ist allerdings etwas für ein anderes Kapitel. Time.deltaTime hat als Wert nicht etwa rot oder blau, sondern einen Zahlenwert; genauer: Die Zeit seit dem letzten Frame in Sekunden, inklusive Nachkommastellen. Diese Zahl ist also halb so groß, wenn wir doppelt so viele FPS haben, und umgekehrt. Wenn wir Time.deltaTime mit einer beliebigen Zahl multiplizieren, so bedeuetet das quasi "pro Sekunde". Wenn wir wegen hoher FPS doppelt so oft drehen, aber dafür jedes Mal nur halb so weit, dann haben wir eine konstante Rotation unabhängig von der Framerate. Multiplikation funktioniert, wie zu erwarten, mit dem *-Operator: void Update() { transform.Rotate(0, 20 * Time.deltaTime, 0); } Von hier aus gibt es noch jede Menge zu erzählen, bevor man so richtig loslegen kann. Ich hoffe allerdings, dass dieser Text hilft, zu verstehen, was man da genau schreibt, wenn man C# codet, und wie die Begriffe richtig heißen. Mal sehen, ob ich die Zeit finde, von hier aus kleinere Tutorials zu schreiben, die auf diesem aufbauen. Bis dahin kannst du hoffentlich andere Tutorials mit diesem hier kombinieren, um weiter zu kommen. Auf lange Sicht soll dieser Text und die darauf folgenden aber unsere JS-Tutorials ersetzen. Bis dahin!
    8 points
  28. 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);
    8 points
  29. Hallo zusammen, das Forum wird am Sonntag (05.03.) ab ca. 10 Uhr bis voraussichtlich 14 Uhr nicht erreichbar sein. Grund dafür ist das Aktualisieren der Forensoftware. Grüße Dark
    8 points
  30. Ich habe gerade einen kleinen Schneeshader gebastelt... Bin mir nicht sicher, ob es so etwas nicht schon gibt. Ist grafisch alles andere als ausgefallen, es tut lediglich folgendes: Er nimmt eine Diffuse- und eine Schneetextur Dazu zwei Zahlen (Schneemenge und Extrusion Amount) Je mehr die Normale der Fläche nach oben zeigt, umso mehr Schnee liegt darauf (aka. Schneetextur statt der normalen) Je mehr Schnee liegt, umso mehr wird der Vertex nach oben verschoben (simuliert, dass der Schnee ja auch Masse hat) Sieht dann so aus: Der Shader ist ganz kurz, bitte schön: Shader "Snow/Diffuse" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _Snow ("Snow (RGB)", 2D) = "white" {} _Amount ("Snow Amount", Range(0,1)) = 0.5 _Extrusion ("Extrusion Amount", Range(0,1)) = 0.5 } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert vertex:vert struct Input { float2 uv_MainTex; float2 uv_Snow; float snow; }; float _Amount; float _Extrusion; void vert (inout appdata_full v, out Input o) { float snow = max(0, v.normal.y) * _Amount; v.vertex.y += snow * _Extrusion; UNITY_INITIALIZE_OUTPUT(Input, o); o.snow = snow; } sampler2D _MainTex; sampler2D _Snow; void surf (Input IN, inout SurfaceOutput o) { half3 main = tex2D (_MainTex, IN.uv_MainTex).rgb; half3 snow = tex2D (_Snow, IN.uv_Snow).rgb; o.Albedo = lerp(main, snow, min(1, IN.snow * 10)); } ENDCG } FallBack "Diffuse" } Vielleicht peppt man den grafisch nochmal irgendwie auf... aber mehr hab ich erstmal nicht gemacht
    8 points
  31. Die Geschichte dieses Buches ist eine ziemlich lange (angefangen habe ich mit diesem Projekt 2010) - aber das möchte hier nicht ausbreiten, bei Interesse findet Ihr die wichtigsten Stationen im Blog auf der Webseite zum Buch. Die gute Nachricht: Das Buch ist jetzt fertig gedruckt und über buecher.de und Amazon bestellbar (einige Buchläden müssten es auch schon haben). Bisher hab ich's selbst gedruckt noch nicht in den Händen gehalten - heute ist's zwar bei mir angekommen (hat mir meine Freundin erzählt), aber ich bin seit gestern in Seattle. Da muss ich mich jetzt also noch knapp zwei Wochen gedulden. Zu dem Buch gibt es auch ein Fragen-Forum, das ein wenig an StackOverflow bzw. auch Unity Answers angelehnt ist. Das ist aber natürlich nicht als Konkurrenz zum Unity Insider Forum gedacht, sondern speziell für Fragen zum Buch. Daher verweise ich auch gleich auf der Startseite der Webseite zum Buch auf hier und bin hier natürlich auch weiterhin aktiv (die Forum-Software, die ich auf der Webseite zum Buch verwende hat auch noch Bugs, die trotz Open Source leider schwieriger zu beheben sind, als ich dachte ... oh well ;-) ). Auf der Webseite zum Buch habe ich eine Menge Tutorials, die auch ohne das Buch funktionieren sollten - wenn Ihr Lust habt, schaut da ruhig mal vorbei: Screencasts zu Das Unity-Buch. Mindestens einer davon war übrigens von einer Frage im Forum inspiriert (und ist dementsprechend natürlich auch schon lange von hier aus verlinkt). Die Arbeit an der Website zum Buch ist noch nicht abgeschlossen, Ihr findet dort aber bereits jetzt eine Vielzahl von Links, die zwar nach den Kapiteln gruppiert sind, aber auch so interessant sein sollten. Jetzt hoffe ich, dass Carsten nicht an seinen Verkaufszahlen merkt, dass es zwei deutsche Unity-Bücher gibt. Viel besser ist, wenn einfach mehr Leute Unity lernen. Sein Buch habe ich zwar bisher noch nicht komplett gelesen (sorry, hatte einfach nicht die Zeit - ist aber soweit ich's gesehen habe ein echt gutes Buch) ... und ich denke die Bücher sind verschieden genug, dass es sich lohnt, beide zu kaufen ;-) Über Feedback freue ich mich natürlich - nach der ersten Auflage ist vor der zweiten Auflage, und da ich Webseite und Buch recht eng verknüpft habe, kann ich Erweiterungen und Ergänzungen auch zwischendrin gut nachliefern. Und natürlich wünsche ich denen, die sich das Buch kaufen viel Freude beim Lesen und hoffe, dass die Arbeit mit Unity während und nach der Lektüre noch mehr Spaß macht! Enjoy! :-)
    8 points
  32. Willkommen im 2ten Teil der Tutorial Reihe. In diesem Teil wollen wir uns näher mit der Kamera beschäftigen. Bisher zeigt diese immer nur starr auf den Ursprungspunkt der Szene. Besser wäre es wenn die Kamera immer unser Raumschiff verfolgen würde. Wir beginnen damit indem wir ein neues Script schreiben. Erstellt daher wie im ersten Teil ein neues C# Script und nennt es "CameraController". Dieses Script könnt ihr nun gleich dem "Main Camera" GameObject zuweisen, wie das geht wurde euch bereits im ersten Teil der Tutorial Reihe gezeigt. Öffnet danach das Script um es editieren zu können. Ihr solltet nun genau dies hier sehen: using UnityEngine; using System.Collections; public class CameraController : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } } Wir wollen das dieses Script immer auf unser Zielobjekt schaut. Deswegen geben wr dem Script eine Variable die uns ermöglicht per Inspektor dieses Zielobjekt zu setzen. Fügt daher folgendes hinzu: public Transform Target; Transform bedeutet dass hier eine Transform Komponente erwartet wird, jedes GameObjekt hat diese Komponente und uns interessiert auch nur das was in dieser Transform Komponente zu finden ist (die Position). Wenn wir das Script nun speichern und nach Unity gehen können wir nun im Inspektor des ausgewählten "Main Camera" GameObjectes unser Camera Controller sehen und dort auch ein Platz in der wir unser "Target" angeben können. Klickt auf dieses Feld und wählt im darauf folgenden Dialog unser "Spieler Raumschiff" aus. Wenn wir nun auf Play drücken passiert noch rein gar nichts, dies werden wir aber schnell ändern. Geht dazu wieder in das "CameraController" Script und ändert void Update zu void LateUpdate. LateUpdate wird so wie Update jeden Frame aufgerufen. Der Unterschied ist aber dass LateUpdate aufgerufen wird nachdem sowohl die Physik Berechnungen als auch alle anderen Updates durchgeführt wurden. Der Ideale Zeitpunkt also um unsere Kamera zu positionieren ohne dass es zu hässlichen Ruckeleffekten kommt. Fügt nun in diese Methode folgendes hinzu: if (!Target) { return; } !Target bedeutet dass wenn wir kein Target ausgewählt wurden der bool Wert true generiert wird. Einfach nur Target ohne das ! Symbol würde bedeuten dass true nur dann generiert wird wenn Target einen Wert hat. Wir wollen an dieser Stelle aber die Ausführung der LateUpdate Methode beenden wenn kein Target gewählt wurde, weswegen wir auf die Nicht Existenz des Targets prüfen und mit return die Methode wieder verlassen sollte Target nicht gesetzt sein. Dies verhindert Fehler wenn wir mal in die Situation kommen in der unser Spieler Raumschiff verschwindet, weil es zb zerstört wurde. Um die Kamera immer mit einer gewissen Distanz von unserem Target zu halten, fügen wir eine weitere public Variable ein: public float Distance = 10.0f; Hierzu gibt es nicht viel zu sage, da die Verwendung das Interessante ist. Dazu erweitern wir die LateUpdate Methode ein weiteres mal, fügt unter die if Bedingung folgendes hinzu: transform.position = Target.position - Distance * transform.forward; Der Code berechnet eine Position der Camera relativ zum Target. Wir benutzen dazu die Position des Targets (Target.position) und verschieben diesen Wert mithilfe der Blickrichtung unserer Camera (transform.forward). Indem wir unsere Distance Variable mit der Blickrichtung multiplizieren setzen wir die Entfernung fest in der sich die neue Position zum Target befinden soll. Mit dem = weisen wir die neu berechnete Position unserer Camera zu (transform.position). Wenn wir nun das Script speichern sollte es so aussehen: using UnityEngine; using System.Collections; public class CameraController : MonoBehaviour { public Transform Target; public float Distance = 10.0f; void Start() { } void LateUpdate() { if (!Target) { return; } transform.position = Target.position - Distance * transform.forward; } } Wenn wir nun unsere Scene in Unity starten und unser Raumschiff bewegen wollen, sehen wir.. dass sich unser Raumschiff nur dreht aber nicht bewegt! Dies liegt daran dass uns Anhaltspunkte zu dieser Bewegung fehlen, da die Camera ja nun immer auf unser Raumschiff schaut. Um einen Anhaltspunkt zu erschaffen fügen wir ein neues GameObjekt in die Szene ein. Wir benutzen dafür den Menüpunkt: "GameObject"->"Create Other"->"Sphere" Wählt die "Sphere" in Hierarchy Bereich aus und wählt eine Position im Inspektor aus welche in der Nähe unseres Raumschiffes liegt, dieses aber nicht überlappt. Wählt zB die Position: X = 0, Y = 0 und Z = 3. Wenn wir nun die Scene erneut starten können wir sehen dass sich unser Raumschiff relativ zu dieser "Sphere" bewegt. Die Camera zeigt dabei immer fleißig auf unser Raumschiff. Hurra! Im nächsten Teil der Reihe lösen wir den Code des PlayerControllers etwas auf und erstellen uns ein Script für den Antrieb des Schiffes. Feedback ist gern gesehen. Teil 3: http://forum.unity-community.de/topic/7102-wir-bauen-uns-ein-space-shoot-em-up-teil-3-antrieb/
    8 points
  33. Inspiriert von der Frage von Felix zu SelectionList habe ich eben mal ein kleines Video-Tutorial zu eben diesem Thema erstellt und freue mich über Feedback (und natürlich, wenn es dem Einen oder Anderen nützt): Und das Beispielprojekt dazu (ist ein Unity-Package, kann man in ein beliebiges Projekt importieren, entpackt sich ins Verzeichnis "Examples"): SelectionListExample
    8 points
  34. Hi, ich bastel grade an dem Kerl hier. Bisschen viel DoF, um die fehlenden / noch nicht gebakten Normal-Maps an manchen stellen zu kaschieren *g* Hoffe es gefällt, wird aber wohl noch etwas dauern bis der in Unity reinkommt. lg, holzi
    8 points
  35. Einstieg in die prozedurale Erzeugung von Meshes Download PDF Inhalt 1. Grundlagen - Mesh - Vertices - Indices - Normalen - Uvs - Bounds 2. Die Umsetzung in Unity und C# 1. Grundlagen Mesh Ein Mesh ist ein Gerüst aus Punkten, Kanten und Flächen welche zusammen ein dreidimensionales Objekt bilden. Ein Mesh ist ein komplexer Datentyp in dem alle notwendigen Daten zusammengeführt sind, dazu zählen hauptsächlich die Vertices, Indices, Normalen und die UV-Koordinaten. In Unity ist der Typ Mesh meist in Verbindung mit einem MeshFilter und einem MeshRenderer zu verwenden um das Mesh zur Anzeige zu bringen. Vertices Ein Mesh besteht immer aus einer Menge an Punkten im 3D Raum welche das Grundgerüst für das 3D Objekt bilden. Ein solcher Punkt nennt sich Vertex die Mehrzahl Vertices. Ein Vertex ist immer in Form eines Vectors angegeben mit x,y und z Koordinaten z.B. (0,0,0). In Unity entspricht das einem Vector3. Die Koordinaten jedes Vertex sind immer in Abhängigkeit zum Nullpunkt des Objektes nicht zum Weltkoordinatensystem, was auch nur Sinnvoll ist wenn das Objekt verschoben werden muss, Da eine Mesh natürlich aus mehreren Punkten besteht, mindestens 3 um eine Fläche zu bilden hat ein Mesh eine Liste an Vertices zugewiesen, in Unity über mesh.vertices zu erreichen. Indices (Triangles) Die Indices oder auch oft Triangels genannt geben die Verknüpfung der einzelenen Vertices zu dem anzuzeigenden Mesh an. Die Indices sind nichts anderes als eine Liste mit Integern welche auf Positionen im Array der Vertices verweisen. Dabei beschreiben immer 3 aufeinanderfolgende Werte in dieser Liste ein Triangle also eine dreieckige Fläche. D.h. die Länge dieser Liste ist immer ein vielfaches von 3. Ein kleines Beispiel. Wir haben eine Liste an Vertices mit 3 Punkten, also die minimale Anzahl die nötig ist um ein Mesh zu erzeugen. Die Zahlen im Namen der der einzelnen Vertices geben die Position in dem Array der Vertices an, dies ist wichtig um ein korrektes Verbinden zu gewährleisten. Es gibt mehrere Möglichkeiten diese Punkte zu verbinden, so z.B. 0 → 1 → 2 oder 0 → 2 → 1. Man könnte meinen wie wir nun diese Punkte miteinander Verbinden ist egal, das ist es aber nicht. Denn ein Triangle wird meistens später nur in eine Richtung gerendert, die Rückseite gibt es nicht. Das nennt man Backfaceculling um zu verhindern das Triangles die nicht der Kamera zugewand sind nicht zeichnen zu müssen, mit manchen Shadern können natürlich auch die Rückseiten gerendert werden wenn Backfaceculling deaktiviert ist, das ist aber nicht die Regel daher gilt es die Reihenfolge zu beachten. Die einzelnen Punkte müssen im Uhrzeigersinn zur Kamera hin verbunden werden, wenn wir also von unser Sicht ausgehen mit der wir auf diese Punkte schauen dann wäre von den beiden oben genannten Möglichkeiten die erste die Richtige. 0 → 1 → 2 . 0 → 2 → 1 wäre falsch weil wir sie gegen den Uhrzeigersinn miteinander verbunden hätten, wir könnten das Triangle von unser Position aus also nicht sehen sondern nur von der Rückseite. Das Ergebnis sieht also so aus. Jetzt hätten wir ein einzelnes Dreieck das wir zur Anzeige bringen können, ein Dreieck ist natürlich etwas wenig daher jetzt noch ein kurzes Beispiel mit 2 Dreiecken die zusammen ein Viereck bilden. Nun ist eine weiterer Vertex dazu gekommen in dem Vertice Array, das zweite Dreieck hat die Indices 0 → 2 → 3, unsere Liste sieht nun für dieses Viereck wie folgt aus: 0,1,2,0,2,3 zwei Triangle teilen sich somit mehrere Vertices. Man hätten diese natürlich auch klonen können, aber wenn es möglich ist einen Vertex doppelt zu verwenden sollte dies auch gemacht werden, es gibt allerdings auch Fälle in denen ein klonen von Nöten ist, z.B. dann wenn es sonst Probleme mit der anzuzeigenden Textur gibt, dazu aber später mehr. In Unity kommt man an die Liste der Indices über mesh.triangles ran. Normalen Jeder Vertex hat immer eine Normale, diese wird vor allem zur Lichtberechnung gebraucht. Eine Normale ist ebenalls ein Vector3 und gibt die Richtung eines Punktes an. Damit ist auch klar, die Liste der Normalen ist immer genauso lang wie die Liste der Vertices. Für die oben genannten Vertices wären alle Normalen gleich, alle würden in Unsere Richtung blicken damit ein Lichteinfall richtig dargestellt werden kann, denn die Triangles sind ja auch uns zugewandt. Die Normale für Vertex 0 und auch für alle anderen oben müsste also wie folgt lauten: (0,0,-1) Sie würde also direkt auf uns zeigen. Ich lasse jetzt hier aber eine weiter Erklärung und auch Beispiele dazu weg weil die eigenehändige Zuweisung der Normalen in Unity nicht nötig ist, der Datentyp Mesh in Unity bietet nämlich eine Funktion an die die korrekte Berechnung für uns übernimmt. Diese lautet mesh.RecalculateNormals(). UV-Koordinaten Eine sehr sehr wichtige Komponente eines jeden Meshes sind die UV Koordinaten, diese sind zur korrekten Anzeige einer Textur auf dem Mesh wichtig. Jeder Vertex hat mindestens eine UV Koordinate wodurch wiederum klar ist das die Liste der Uvs ebenfalls genauso lang ist wie die Liste der Vertices. Eine UV Koordinate wird als Vector2 angegeben mit x und y Koordinaten auf der Textur. Der Name UV Koordinate könnte eigentlich auch XY Koordinate heißen, weil x und y aber schon im 3D Raum für die Angabe der Punkte verwendet wird nimmt man bei den Texturkoordinaten u und v statt x und y. Ich werde dem Verständnis halber aber von x und y reden weil ja auch im Vector2 mit x und y gearbeitet wird. Die Werte für x und y liegen immer zwischen 0 und 1. Für x wäre 0 also ganz links und 1 ganz rechts auf der Textur. Bei y wäre 0 ganz unten und 1 ganz oben. Eine UV Koordinate die ganz oben links auf der Textur liegen soll muss also (0,1) lauten. Hier ein Beispiel anhand des oben beschriebenen Meshes mit seinen 4 Vertices. Wir nehmen uns eine beliebige Textur die wir komplett auf die von uns erzeugten Triangles legen möchten. Die Textur sieht so aus: Nun bestimmen wir die UV-Koordinaten für jeden Vertex in unserer Liste. Vertex 0 soll ganz unten links sein, daraus folgt die Koordinate (0,0). Vertex 1 soll ganz oben links sein (0,1). Vertex 2 soll ganz oben rechts sein (1,1) und Vertex 3 ganz unten links (1,0). Diese 4 zweidimensionalen Vektoren werden nun in ein Array vom Typ Vector2 gespeichert und dem Mesh übergeben. An die UV Koordinaten kommt man über mesh.uv. Ein Mesh kann auch meherer UV Koordinaten besitzen, in Unity maximal 3. an die anderen beiden kommt man über uv1 und uv2. Wir bleiben aber hier bei einem Set an UV Koordinaten. Bounds Zuletzt noch eine kurze Erläuterung zu den Bounds eines Meshes. Die Bounds kann man sich als Kasten um das Mesh herum vorstellen in dem das gesamte Mesh eingeschlossen ist. In den Bounds sind die minimalen und maximalen x,y und z Koordinaten aller Vertices eines Meshes hinterlegt. Damit kann schnell die ungefähre Gesamtgröße eines 3D Objektes abgefragt werden. Diese Bounds sind vorallem dafür da um für ein funktionierendes Frustumculling zu sorgen, also das ausklinken eines 3D Objektes aus derm Renderprozess wenn dieses sich außerhalb des Sichtbereiches der Kamera befindet. Stimmen die Bounds nicht, kann es passieren dass das Objekt zufrüh aus dem Renderprozess genommen wird also schon bevor es wirklich außerhalb ist oder zuspät. An die Bounds des Meshes kommt man in Unity über mesh.bounds. Aber auch hier müssen wir die Bounds nicht selbst berechnen, was aber auch so nicht sonderlich schwer wäre. Aber Unity und der Datentyp Mesh stellt uns auch hier eine einfache Funktion zur Verfügung um die Bounds für ein von uns generiertes Mesh schnell und einfach berechnen zu lassen. Das geht über mesh.RecalculateBounds(). So damit wären theoretisch alle zunächst relevanten Elemente abgehandelt, es gibt zwar noch mehr Elemente die man für ein Mesh einstellen kann aber die lasse ich jetzt bewusst weg weil sie nicht unbedingt von Nöten sind. Als nächstes machen wir uns an die Umsetzung des gerade gelernten und bringen das eben beschriebene Mesh in Unity zur Anzeige. 2. Die Umsetzung in Unity und C# Nun beginnen wir damit das oben beschriebene und vorerst noch sehr einfache Mesh in Unity zur Anzeige zu bringen. Dazu erzeugen wir uns zunächst ein Empty GameObject und legen es auf die Position (0,0,0). Die Kamera richten wir direkt mal so aus das sie auf den Nullpunkt schaut. Als nächstes geben wir dem GameObject einen MeshRenderer und einen MeshFilter, außerdem sollten wir uns ein neues Material erzeugen. Diesem könnt ihr nun eine eigene Textur geben oder die von mir hier genommene. Das Material ziehn wir einfach in die Material Liste des MeshRenderers. Damit wäre die erste Arbeit getan, nun machen wir uns an das wesentliche, unser Script. Dazu erzeugen wir uns ein neues C# Script, der Name ist euch überlasse. In diesem Script erstellen wir uns eine neue Methode, ich nenne sie CreateMesh(). public void CreateMesh(){ } Nun erzeugen wir uns in dieser Methode ein neues Mesh. Mesh mesh = new Mesh(); Außerdem erstellen wir uns schonmal alle notwendigen Listen die wir brauchen für die Vertices, Indices und UVs . Wir könnten auch die Normalen schon anlegen aber das kann ja Unity für uns machen. Vector3[] vertices; int[] indices; Vector2[] uvs; Nachdem wir dies getan haben können wir bereits anfangen unser Liste mit Vertices zu füllen. Die Werte entnehme ich dem theoretischen Teil. Ich werde hier also nicht mehr auf die Werte eingehn. Weil wir vier Vertices haben müssen wir unser Array auch mit 4 freien Feldern initialisieren und diese anschließend zuweisen. vertices = new Vector3[4]; vertices[0] = new Vector3(0,0,0); vertices[1] = new Vector3(0,1,0); vertices[2] = new Vector3(1,1,0); vertices[3] = new Vector3(1,0,0); anschließend kommen die Indices indices = new int[6]; indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 0; indices[4] = 2; indices[5] = 3; und dann die UV Koordinaten. uvs = new Vector2[4]; uvs[0] = new Vector2(0,0); uvs[1] = new Vector2(0,1); uvs[2] = new Vector2(1,1); uvs[3] = new Vector2(1,0); Nun können wir alle Listen dem Mesh übergeben. mesh.vertices = vertices; mesh.triangles = indices; mesh.uv = uvs; Jetzt fehlen noch die Normalen und die Berechnung der Bounds. Das geht folgendermaßen. mesh.RecalculateNormals(); mesh.RecalculateBounds(); Im Grunde sind wir nun fertig, wir müssen nur noch unserem MeshFilter das Mesh übergeben. gameObject.GetComponent<MeshFilter>().mesh = mesh; Das wärs, jetzt noch in der Awake Methode die von uns erzeugte Methode aufrufen und fertig. void Awake(){ CreateMesh(); } Wenn wir nun das Spiel starten sollten wir ein Viereck mit unserer Textur angezeigt bekommen. Im Editor Fenster sollte es so aussehn. Damit wären wir am Ende der Einführung. In folgenden Tutorials werde ich zeigen wie man auf die gleiche Art und Weise größere zusammenhängende Objekte erzeugt. Jeder der das Prinzip verstanden hat könnte aber theoretisch jetzt schon einen Würfel erzeugen. Dabei ist jedoch zu beachten das man bei einem Würfel für jede Seite 4 Vertices nimmt, mehrere Seiten sollten sich also keine Vertices teilen weil man sonst Probleme bei den UV Koordinaten und den Normalen bekommt. Hier gehts zu Teil 2 Gruß Stephan
    8 points
  36. Über damuddamc's A* Pathfinding-Tutorials Wann immer in einem Spiel der kürzeste Weg zwischen zwei Positionen gefunden werden soll und Hindernisse im Weg stehen oder nur bestimmte Wege benutzt werden können, steht man als Programmierer vor einem Problem für Fortgeschrittene - sowohl mathematisch als auch der Implementierung wegen. Dabei braucht man automatische Wegfindung so oft: Sei es ein Gegner bei einem Shooter, einer Einheit in einem Strategiespiel oder vielleicht sogar einem Kontrahenten in einem Wettrennen - immer muss dem Spiel beigebracht werden, wie es den kürzesten, schnellsten oder anderweitig besten Weg findet. @damuddamc's Tutorialreihe beschreibt Schritt für Schritt, wie einer der bekanntesten und beliebtesten Pathfinding-Algorithmen nicht nur funktioniert, sondern auch in Unity verwendet werden kann. A* Pathfinding - Teil 1 Grundfunktionsweise des A* Algorithmus A* Pathfinding - Teil 2 Erstellen eines Wegpunktesystems in Unity A* Pathfinding - Teil 3 Umsetzung des A* Algorithmus in Unity mittels JS Ein großes Dankeschön an dieser Stelle an @damuddamc für die ausführlichen Schilderungen der Abläufe vor und hinter der Fassade.
    8 points
  37. Hallöchen! Ich war mir nicht sicher, ob schon genug Content für den Vorstellungsthread da ist oder ob es besser in die Galerie passt. Ich versuch mal mein Glück hier und möchte euch Lucy vorstellen. Das Vorhaben Geplant ist ein klassisches 2d jump n' run in oldschool Optik. Interessante Figuren in einer schaurig schönen Welt, entführen den Spieler in ein märchenhaftes Abenteuer. Neben der Hauptstory soll es einen Level-Editor geben, der die Möglichkeit bietet, eigene Level zu erstellen und diese für eine Kampagne aneinander zu reihen. Spielmechanik und Grafik sind an die SNES "Donkey Kong Country" Trilogie angelehnt. Man wird also nicht nur springen, sondern auch kämpfen, werfen und klettern können. Die Handlung Die Geschichte handelt von Lucy, einer jungen Frau, die kein Wort spricht und auf magische Art mit dem Licht in ihrer Lampe verbunden ist. Sie folgt dem Licht bis ans Ende der Welt und trifft dort auf die kleine Clementine. Doch das Mädchen hat eine neue Bedrohung eingeschleppt und das Pech haftet ihr an den Fersen. Um den Fluch zu brechen, müssen die Beiden jede Menge Herausforderungen meistern und den mächtigsten Feinden trotzen. Einige gieren nach Lucys magischer Lampe und andere wollen die Gefahr die von Clementine ausgeht mit Gewalt beenden. Zum Glück finden sie auf ihrer Reise Freunde -wie den cleveren Finn und Abigail die Vogeldame-, welche ihnen im Kampf zur Seite stehen. Gemeinsam müssen sie herausfinden wer Clementine ist und warum die mysteriöse Lampe scheinbar über Leichen geht, um das Mädchen zu schützen. Die Grafik Ein bereits fertiges Piratenschifflevel. Für das Tile-Set wurden erst 3D Modelle in Maya erstellt, dann in low res rausgerendert und später in Photoshop angepasst und zusammengefügt. Daneben sind Moodbilder für geplante Settings. Was bisher geschah Das Spiel ist seit knapp drei Jahren in Planung, aber die eigentliche Arbeit daran begann erst im Sommer 2016. Grund dafür war meine Ausbildung als Multimediaartist. Als solcher lernt man alles über Filme und wie man diese produziert. Super interessant und definitiv hilfreich in punkto Dramaturgie, Design und Animation, aber eben kein Spiel ... . Das Studium hat viel Zeit in Anspruch genommen und die Ideen für das Game entstanden nur langsam. Erst im Kopf, dann auf Papier und später in all meinen Arbeiten. Kein Wunder also, dass mein Diplomprojekt ein Lucy Lamplight Kurzfilm wurde. Auch wenn es nicht das ist, was ich eigentlich machen wollte, finden sich durch das Video vielleicht Gleichgesinnte, die sich für das Projekt begeistern können. So der Gedankengang. Der Aktuelle Stand Leider bin ich kein Programmierer und Unity ist noch Neuland für mich. Deshalb gibt es bis jetzt nur ein Spielkonzept und jede menge Grafiken ohne Zweck. Eine hübsche Hülle die Inhalt braucht. Ein -mit After Effects erstelltes- Preview soll zeigen wie es in Bewegung aussehen kann. Im Moment bin ich dabei mich mit der Engine vertraut zu machen und bestehende C# Scripte in -für mich verständliche- Bestandteile zu zerlegen. wo Lucy zuhause ist Website: https://polywonk.wordpress.com/ Soweit, so gut. Ich hoffe das ich nichts wichtiges vergessen hab zu erwähnen. Danke fürs durchlesen ~ Polywonk
    7 points
  38. Ohwe ohwe war ich hier lange nicht mehr unterwegs... Kennt mich hier überhaupt noch jemand? Das Studium frisst einen auf... Aber: Ich kann endlich mal wieder was (öffentlich) von meiner Arbeit zeigen! Und noch dazu ein Unity-Projekt, was doch eher untypisch ist. In den letzten 4 Monaten habe ich im Rahmen des 5. Semesters ein Augmented Reality Sandbox Game entwickelt - vielleicht kennt ihr das aus Museen, Sea Life, etc, manchmal gibt es da so Sandkästen, auf die mehr oder weniger in Echtzeit Höhenlinien projeziert werden. Ich habe das gesehen und mir gedacht: Da muss man doch mehr mit machen können. Und nach viel viel Arbeit kann ich euch meine erste Installation präsentieren: Basic Info: Man nutzt den Schatten seiner Hände um herumwirbelnde Natur-Teilchen einzufangen, diese kann man in vorher gegrabenen Mulden sammeln und so Stück für Stück die Landschaft vergrößern. Technischer Breakdown: Ich nutze eine Microsoft Kinect 2 um die Höheninformationen des Sandes und der Hände zu bekommen. Aber: Das "Raw" Kinect Tiefenbild ist für diesen Zweck eigentlich komplett unbrauchbar. Das rauscht wie verrückt, und immer wenn es eine harte Kante gibt werden die Höheninfos an dieser Kante einfach null - lauter so Zeug. Deshalb war mein erster Schritt das Kinect Bild möglichst performant und vor allem schnell zu filtern und zu smoothen. Dieses GIF zeigt den Effekt. Normalerweise ist es ja so, dass wenn man seine Hand jetzt über den Sandkasten hält, diese Höheninformation auch in die Projektion aufgenommen wird und dort ein sehr hoher Berg entsteht (einer der Gründe für die große Verzögerung der bekannten AR Sandboxes). Ich wollte das auf keinen Fall, weshalb ich nach einem Weg gesucht habe, die Hände aus den Höheninformationen herauszufiltern. Das funktioniert viel billiger als man vielleicht annimmt: Man kalibriert den Tisch einmal ohne Hände und ab diesem Moment werden alle Informationen, die über einem bestimmten Threshold sind, einfach ignoriert. Sobald man die Hand 3cm über den Sand hält, wird diese wieder als Terrain wahrgenommen (Es gibt dann mehrere Thresholds für schnelle Bewegungen etc.) Das Resultat fühlt sich nach einer sehr "magischen" Interaktion an, da eben nur die wirklichen Sandinteraktionen auch Änderungen in der Projektion hervorrufen. Zugleich bekomme ich aus den gefilterten Höhendaten dann eine Maske für die Hände, mit der ich über OpenCV Handgesten und Formen für den Game Input erkennen kann. Da dieses ganze System dann doch immer langsamer und nicht richtig "befriedigend" auf den Input reagiert habe ich das Kinect Bild in 8, sich überlappende Teile aufgeteilt, die auf unterschiedlichen Threads verarbeitet werden. Threading (und vor allem Multi Core Threading) ist in Unity ja bekanntermaßen eher, nennen wir es mal: kompliziert. Aber für ein Spiel wie dieses war es super essentiell, dass alles so schnell wie möglich reagiert. Ich berechne die Positionen der Hand Schatten aus der Projekterposition und den Kinect Daten, um die Spieler damit Objekte einfangen zu lassen - da will man natürlich keinen Lag haben. Und auch das Graben fühlt sich so richtig gut an. Visuall Stuff Das Terrain wird aus einer Displacement Map (die aus den verbesserten Höheninfos generiert wird) erstellt. Der Rest passiert dann in einem Shader: Ich berechne mir die Normalen aus der Heightmap, indem einfach ein Vektor aus einem umliegenden Dreieck von Höhendaten berechnet wird - die Normalmap brauche ich für die richtige Beleuchtung der Landschaft. Wenn man jetzt die Höhe vom Mittelpunkt dieses Dreiecks mit der Höhe von den Eckpunkten vergleicht, kann man eine Realtime Curvature Map berechnen - die ist für schöne Schneebedeckte Bergspitzen und saftig grüne Täler. Die Steigung der Normalen wird auch noch dazu berechnet um Felsen an Steilwänden zu erzeugen und die Wälder an solchen Stellen auszumaskieren. Hier mal eine kleiner Debug Ansicht davon: Der ganze Landschaftsshader ist dann doch sehr groß - es gibt eine Art "Fake"-Erosion, damit die Felsen schick aussehen und eine riesige Anzahl Noises um die Sandstrände, Schneeverteilung, Wälderfarben, Meereswellen etc. zu erzeugen. Das ist aber nur die Landschaft! Es gibt auch noch die dunkle, zerglitchte Welt mit dem selben Setup, aber eben anderen erzeugten Effekten. Da sich das Spiel ja um das Fangen und Verbinden von Natur-Fragmenten dreht sollte diese "Verbindungs-Interaktion" etwas ganz besonderes werden und dem Spieler das visuelle Feedback geben, etwas richtiges getan zu haben. Mithilfe eines Voronoi Effekts habe ich die ganzen Natur-Teile sich miteinander verbinden lassen - jedes Natur Objekt kann sich über einen Shader so hin morphen, dass es in seine umgebenden Voronoi Positionen passt. Das erzeugt einen sehr coolen, snappy Effekt als ob man ein passendes Puzzlestück gefunden hat. Solltet ihr in zwei Wochen zufällig in Stuttgart sein könnt ihr gerne auf der FMX Conference 2018 vorbeischauen, wo das Spiel öffentlich präsentiert wird und dann auch tagsüber spielbar ist. Wenn nicht - ich versuche gerade Museen und Ausstellungen ausfindig zu machen, die sich für so ein Exponat interessieren könnten. Wenn ihr da Ideen habt, schreibt mir doch gerne! Zum Abschluss noch zwei schöne Making Of Bilder Vielen Dank fürs Lesen & ich freue mich auf Fragen und Feedback!
    7 points
  39. So! Ohne viel Schnickschnack: Ab jetzt könnt ihr den Flipper downloaden und spielen. Hier zu finden: www.malzbie.com/unity/pinball/TimeToFight_Beta.zip Es würde mich freuen, wenn ihr mir sagt wie ihr ihn findet und natürlich ob ihr irgendwo einen Bug entdeckt habt. Mich interessiert auch, auf welcher Hardware er wie gut läuft. Vom Prozessor her sehe ich keine großen Probleme. Aber die Grafik ist hungrig. Auf einen Intel Chipsatz im Notebook wird der wohl nicht laufen.
    7 points
  40. Heute möchte ich euch meinen Cross Platform Patcher vorstellen. Der Patcher managed eure Versionen und Patches zwischen den Versionen und vermittelt diese an eure Spieler: Neue Spieler bekommen die neueste Version und bestehende Installationen werden automatisch geupdatet, wenn ein neuer Patch verfügbar ist, ohne dass das gesamte Spiel neu gedownloadet werden muss. Aber wie funktioniert das ganze jetzt? Das System besteht aus 3 Komponenten: Ein AdminTool zum Erstellen und Verwalten eurer Daten online und offline Ein Server, auf den die Daten hochgeladen werden. Dieser Server wird von mir bereit gestellt und von allen Nutzern gleichzeitig verwendet. Ein Client, den eure Spieler erhalten. Dieser übernimmt alle Arbeit, die man sich von einem Launcher/Patcher vorstellen kann von erstmaligem Downloaden über Patchen bis zur Reparatur von beschädigten Dateien. Ist das ganze sicher? Jeder Account kann mehrere User beherbergen, deren Permissions angepasst werden können. Dadurch kann jeder nur auf die Informationen und Aktionen zugreifen, die für ihn bestimmt sind. Ist das System Cross-Platform? Ja! Das System wurde darauf ausgelegt, auf möglichst vielen Systemen zu laufen. Das Admin Tool ist Java-basiert und kann unter Windows OS X und Linux per Terminal/Cmd aufgerufen werden. Unter Windows empfehle ich den Installer, der bringt alle Dependencies mit sich und stellt außerdem eine .exe statt einer .jar Datei bereit. Desweiteren ist gerade eine GUI Version des AdminTools in Entwicklung. Diese wird zuerst für Windows kommen, später dann auch als OSX App. Bei den Clients habt ihr die Wahl zwischen einem Cross Platform Client (Java basiert) und einem Windows Client (C#). Außerdem könnt ihr die jeweiligen SDKs auch separat downloaden und euren eigenen Client erstellen, der alle eure Featurewünsche abdeckt. Die Clients bieten nur ein Subset an dem, was die Platform her gibt. Weitere SDKs und ein OSX Client, der in die eigentliche App eingebunden werden kann folgen. Die von mir erstellen Clients können individualisiert werden (Aussehen und Informationen über Server/Version etc). Das erstellte Datenformat .pcc kann unabhängig des Clients verwendet werden. Außerdem kann die .pcc mit dem Client gepacht werden, um z.B. das Aussehen des Clients zu ändern oder ab einer bestimmten Version einen Serverwechsel zu vollziehen. Ich habe verschiedene Binaries für Windows, OSX und Linux / Sind Beta Versionen möglich? Auch das ist ohne Probleme möglich. Das System baut auf sogenannte "Channels". Jeder Channel enthält seine eigenen Versionen und Patches. Und jeder Client ruft nur die Daten eines einzelnen Channels ab. So sind z.B. Channels für die verschiedenen Betriebssysteme möglich oder Builds, die zuerst für bestimmte Testergruppen bestimmt sind und danach auf einen allgemeinen Channel verschoben werden. Mit etwas Kreativität ist somit auch das wellenweise Verteilen von neuen Versionen möglich. Das klingt alles sehr kompliziert Ist es aber nicht Die schwere Arbeit übernimmt der Patcher, ihr könnt euch getrost auf die Entwicklung des Spiels, das ihr schon immer entwickeln wolltet, konzentrieren. Das AdminTools tellt derzeit fast 90 verschiedene Befehle bereit, die euch die Arbeit erleichtern sollen, aber die nötige Flexibilität gewährleisten. Lässt sich das auch automatisieren? Ja das geht! Das Admin Tool unterstützt Scripts, in denen ihr Routinen in Dateien beschreiben könnt. Zusammen mit dem in alle Befehle integrierten Variablen-System lassen sich so vollautomatische Releases erstellen. Wo bekomme ich diesen Patcher? Hier: http://game-patcher.de/ Momentan befindet sich der Patcher in einer Beta Phase. Deswegen ist nur der Free Plan verfügbar (dafür aber mit etwas großzügigeren Werten). Bitte beachtet, dass sich am Patcher noch einiges ändern kann. Einen wirklichen Release würde ich damit noch nicht machen. Aber wenn man an eigene Tester sein Spiel weiter leiten will, steht dem ganzen nichts im Wege. Auch zu beachten ist, dass aufgrund der Beta noch nicht alle Dokumentationen verfügbar sind und auch noch nicht alle Guides veröffentlich sind. Aber ich sitze dran und versuche, den Content zu liefern Grüße, Silveryard
    7 points
  41. Moin Leute, nachdem ich jetzt hoffentlich endlich meinen Durchbruch beim LOD hatte, möchte ich euch gerne meine Voxel Engine vorstellen - das war so mein eigenes kleines Ziel das ich erfüllen wollte bevor ich es vorstelle Das Projekt gibt es übrigens mittlerweile schon seit über 1 Jahr (die 1. Version ist vom 28. Juli 2015 ), damals habe ich mich erst mal in Boxel (Minecraft-Style) eingearbeitet, um überhaupt in das Thema zu finden und mich dann über Marching Cubes zum Dual Contouring gehangelt. Zwischenzeitig habe ich auch noch die Datenstruktur der Chunks von 3D-Arrays auf Octrees umgestellt - was Anfangs auch erst mal gedauert hat die zum Laufen zu bringen - und mich generell vorwiegend belesen, altes verworfen, neues hinzugefügt, etc. Im November letzten Jahres dann (wow.. das ist jetzt auch schon wieder knapp 11 Monate her) musste ich das Projekt wegen allgemeinem Uni-Stress pausieren und das bis vor kurzem, denn bis auf ein paar wenige und sehr kurze Phasen in denen ich Zeit hatte - wobei der Großteil davon erst mal drauf gegangen ist mich wieder in das Thema einzuarbeiten - hab ich nicht viel daran schreiben können. Jedenfalls freut es mich jetzt natürlich umso mehr euch jetzt ein paar Bilder davon präsentieren zu können ^^ Das ganze befindet sich nach wie vor in einer sehr frühen Version, bis dato ist tatsächlich nur das Generieren des Terrains möglich und die Meshes sind auch noch untexturiert etc. Dafür klappt es 2D und 3D Meshes zu generieren und die Generierung des Octrees und Meshes sind Multithreading-fähig, wobei ich noch am überlegen bin, ob und wie ich die Octreegenerierung (samt Densityberechnung) auf die GPU auslagere. Das ganze läuft übrigens mit diesen Stats: Insgesamt werden übrigens ~44k Triangles generiert und das Terrain bietet eine Sichtweite von 256m. Ist zwar nicht wirklich viel aber es ist ja auch nur bedingt optimiert und Singlethreaded (obwohl es multithreadingfähig wäre, dann geht das in einem Bruchteil der Zeit ^^) @edit: Achja, kurze Erklärung: Die Generation Time ist wie lange es dauert die Octrees zu generieren, die Update Time ist das Meshing (sollte ich vllt mal umbenennen) und die Buffer Time ist das Befüllen des Density Buffers ^^ Mein Laptop hat übrigens einen 2.4GHz Quad-Core Prozessor, 16GB DDR4 RAM und eine Nvidia GeForce GTX 675MX mit 4GB GDDR5 Grafikspeicher, falls es jemanden interessiert bzw. um die Zeiten/Rechenaufwand einschätzen zu können.. ^^ Bin auch sehr froh darüber, dass der Algorithmus nicht auf restricted Octrees beschränkt ist, d.h. es ist vollkommen egal, um wie viele Auflösungsstufen sich die benachbarten Chunks unterscheiden, es klappt Hier mal noch ein kleines Bild mit 3D Terrain (kein besonders tolles): Ich weiß ehrlich gesagt noch nicht woran ich mich als nächstes wage. Wahrscheinlich überarbeite ich das LOD System noch ein paar Mal, damit das auch wirklich 100%ig klappt und nichts unnötig berechnet wird. Ich würde dann demnächst gerne mal einen vernünftigen Worldgenerator basteln (also, dass das Terrain auch nach was aussieht, verschiedene Materialien hat, etc..), aber ich glaube das wird auch wieder ein eher langwieriger Prozess, da bastel ich wohl immer mal wieder dran. Auf der Liste steht auf jeden Fall noch so einiges, da hab ich noch sehr lange was zu tun und ich hoffe, dass ich demnächst öfter mal ein paar weitere Errungenschaften präsentieren kann, warne aber schon mal vor, dass sich das wohl sehr zieht Tiwaz
    7 points
  42. Gefällt mir ausgesprochen gut was du da machst Tom. Durch den Dungeon sehen die Texturen fast schon bräunlich aus und alles ist recht dunkel. Nehme mit dem Char gerade an einem Contest teil, daher würde ich ihn gerne mal bei etwas besserem Licht zeigen. Hoffe das ist ok für dich Tom, wenn nicht sag bescheid und ich passe mein Post an. Hier also noch mal in voller Pracht.
    7 points
  43. Hi da! Ich mache hier mal einen Sammelthread für einige Grundlagentutorials, die ich demnächst erstellen will. Es geht hier aber nicht um sowas wie die Oberfläche von Unity oder wo man etwas findet. Nein, es geht um Grundlagen der Funktionen, die Objekte und deren Vererbung, Zugriff auf Gameobjects und deren Komponenten usw. . Anfangen will ich mit zwei Videos wobei das erste um das Objekt geht und wie gewisse Dinge vererbt werden. Beim zweiten Video erkläre ich die vorgefertigten Funktionen im MonoBehaviour und wann welche abgearbeitet wird. Hier die ersten 2 Videos: Grundlagen über Objekte und ihre Vererbung Grundlagen über die Scriptfunktionen und ihre Reihenfolge der Abarbeitung
    7 points
  44. Hallo Leute, als meinen ersten Post hier bei euch will ich euch hier mal mein letztes Freizeitprojekt vorstellen, das auch grade im Unity Asset Store live gegangen ist: Old Medieval Inn + Modular Asset Pack Dabei handelt es sich um ein altes Gasthaus mit 2 begehbaren Stockwerken und vielen Items. Jedes Modell wurde von Hand gemodelt, gesculpted und auch händisch - ohne Foto-Texturen- bemalt. Hier könnt ihrs ausprobieren: Webplayer-Link Hier gehts zur Asset-Store Seite: Link Ein paar Fakten 74 FBX Meshes, alle sorgfältig modelliert und gesculpted 58 Highres Texturen, hand-painted 33 Materials. Für mehr Variation kann man sie auch auf den Prefabs tauschen, so ergeben sich noch mehr Möglichkeiten 65 Prefabs für drag'n'drop Levelerstellung Oculus Rift Support eingebaut In der Szene ist alles sauber Light-Baked,und es verwendet einige Effekte wie zB. das volumetrische Licht, das ihr auf den Screens sehen könnt. Zusätzlich zur Szene hab ich alles noch modular aufgeteilt, so dass man sich schnell ein Level oder einen Dungeon zusammenziehen kann. Ich hab echt viel Herzblut und Zeit in dieses Pack gesteckt, bin schon auf euer Feedback gespannt und hoffe, dass es euch gefällt oder für den Einen oder Anderen nützlich ist. Liebe Grüße, Holzi PS: ist ja mein erster Post, also noch etwas zu mir : ich bin 26 und arbeite als 3D Artist, beschäftige mich in meiner Freizeit mit Game-Dev und komme aus Österreich
    7 points
  45. 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/
    7 points
  46. Hey ich habe mal eine Frage bezüglich der Skalierung der GUI Elemente. Nutze bisweilen immer eine Berechnung durch die Bildschirmbreite und - höhe (Screen.width, Screen.height).... Bin durch das Forum hier und im Inet auf die Matrix4x4 GUI.matrix gestoßen und fand den Ansatz echt gut (hätte man auch selber drauf kommen können ) Wie dem auch sei, ich gebe also meine native Auflösung an, 1920 x 1080. Paar Hintergrundinfos. Die Anwendung soll nur im Vollbildmodus sein und benutze ein Hintergrundbild und sonst Buttons, die aus 16x16 Grafiken hochskaliert werden. Bei Auflösungen, die kleiner sind als 1920 x 1080, zB 1280 x 720 skaliert er mir das alles oben links in die Ecke. Es ergibt sich eine Ratio von 0.667. Der Rest ist Hintergrundfarbe. Wenn ich das Hintergrundbild vor der Matrixskalierung zeichnen lasse, passen die Buttonspositionen nicht mehr dazu. Wähle ich als native Auflösung 1280 x 720 so ist der Effekt gespiegelt. Ratio ist dann 1,667. Man sieht nicht mehr alles. Codetechnisch sieht das ungefähr so aus (schematisch) private OnGUI() { this.scale.x = Screen.width / ORIGIN_WIDTH; this.scale.y = Screen.height / ORIGIN_HEIGHT; GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, this.scale); //GUISTUFF zB Buttons und und und } Was kann ich da machen? Edit: Muss ich die Größen über GUI.matrix.multiplyVector neu berechnen? Edit2: Habe es nun gelöst, lag daran, dass ich meine Werte für die späteren GUIElemente vorher noch durch Screen.height und Screen.width bezogen habe und darauf wiederum die Matrixtransformation angewendet habe. LG, Tenshi
    7 points
  47. Hallo *, mein neues Projekt nähert sich der Fertigstellung. Es geht bei diesem kleinen Spiel darum, während einer Klassenarbeit die Schummler ausfindig zu machen. Das Spiel hat 75 Level und drei verschieden schwierige Endlos-Spiele. Ein Video der Preview könnt ihr ansehen unter: Ich freue mich über Kritiken & Meinungen. Ulrich
    6 points
  48. Jeder, der etwas mehr Zeit hier im Forum ist, ist quasi schon gewohnt, dass "neue" kommen, die gerade erst Unity oder gar erst das Programmieren lernen und ihren Traum von einem Multiplayer-RPG mit großer Fantasy Welt verwirklichen wollen und Mitstreiter suchen. Nachdem jeder erfahrene Entwickler weiß, wie viel Arbeit so etwas ist und dass die Erfolgschance sehr genau 0 beträgt, wirst du hier wenndann gleichgesinnte Neulinge finden, die aber meistens schon ihr eigenes RPG umsetzen wollen. Was die "Veteranen" allerdings des öfteren vergessen: Jeder der in dieses Gebiet kommt, träumt am Anfang und setzt sich viel zu hohe Ziele. Und ich fände es falsch, dieses Projekt abzubrechen, bevor es begonnen hat, denn so bekommt der lieber Thread Ersteller auch keinen Eindruck davon, warum ein AAA oder Online Spiel a) sehr viele Entwickler b ) sehr viele TALENTIERTE Entwickler und c) unglaublich viel Zeit braucht. Das kann man ihm zwar sagen, aber ich bin der Meinung, dass man selbst erst gegen die Mauer laufen muss, bevor man sie etwas tiefer baut, um darüber springen zu können. Blackraciel, nimm anderen z.B. Mark und Tiwaz nicht übel, wenn sie nicht begeistert von deinem Projekt sind und vergiss nicht, dass jemand, der 2.300 Beiträge in diesem Forum hinterlassen hat, durchaus mehr Ahnung hat als ein Anfänger ^^
    6 points
  49. Okay, du hast die Scripting-Tutorials (oder andere) gelesen und weißt jetzt, wie man [einen Würfel dreht/eine Farbe ändert/irgendetwas anderes macht]. Aber wie kannst du jetzt [eine zufällige Farbe erstellen/machen, dass die Wand durchlässig wird/noch irgendetwas anderes machen]? Einfach mal im Forum fragen. Was kriegt man dann als Antwort zurück? "Das sollte mit [link] und [noch ein link] zu machen sein". Die Links führen in die Unity Scripting Reference, wo man sich alles weitere anlesen und dann mit den erworbenen Grundkenntnissen und den Informationen über dies und das eine eigene Lösung programmieren kann. Denn jemand anderem eine fertige Lösung zu schreiben ist etwas, das Leute nur machen, wenn sie gerade zufällig richtig gut drauf sind oder Geld dafür kriegen. Nun ist aber das Problem: Da steht alles so kryptisch! Ohne Beispiele kann ich damit nichts anfangen! Das ist schade, denn Besipiele sind für Anfänger noch begrenzt nützlich, aber dass sie doch eher der Sonderfall sind, liegt daran, dass bereits alles nötige da steht - ohne Beispiel. Man muss es nur lesen können. Die Scripting Reference lesen Teil 1 - Die Oberfläche Zuerst einmal - hier ist sie, die Reference: http://docs.unity3d....criptReference/ Beim ersten Besuch sollte man sich oben rechts die Lieblingssprache aussuchen, damit eventuelle Beispiele immer in dieser Sprache angezeigt werden. Achtung: Einige Beispiele sind nur in einer Sprache verfügbar, also nicht wundern, wenn der Code bei euch nicht geht. Schaut am besten generell, was das Beispiel macht, anstatt den Code zu kopieren, da habt ihr sowieso mehr von. Wie man durch die Dokumentation navigiert, ist euch überlassen. Ich haue immer meinen Suchbegriff direkt oben rechts in die Suche und spare mir dabei mehrere Male Klicken und Scrollen. Teil 2 - Klassen / Structs Das erste, was man oft in der Reference findet, sind Klassen bzw. Structs. Sie sind die logischen Einheiten, in denen der Unity-Code aufbewahrt wird. Leider verschweigt die aktuelle Reference gerne mal, ob es sich bei einer Klasse nicht doch um ein Struct handelt. Das muss man sich dann im Zweifelsfall zusammenreimen, das "Wie" führt hier aber zuweit. Ob man gerade eine Klasse oder ein Struct ansieht, erkennt man allerdings daran: Die Überschrift der Seite ist [Name]. Nichts weiter. Sucht z.B. mal nach der Klasse Collider. Die Überschrift der Seite ist "Collider". (Im Folgenden lasse ich das "bzw. Struct" bei "Klasse" immer weg, sonst werde ich irre ) Es folgt eine kurze Beschreibung der Klasse und dann kommen die so genannten "Member" der Klasse. Damit sind alle Einzelteile gemeint, die in der Klasse stecken: Die Variablen und die Methoden, die hier "Funktionen" genannt werden. Dabei gibt es noch weitere Unterteilungen, z.B. ob der Member statisch oder geerbt (inherited) ist. Dann gibt es noch Messages, die sind ein wenig besonders und nicht Teil einer gewöhnlichen API. Auf diese Member kann man klicken, um mehr zu erfahren. Schauen wir uns doch einen Member der Collider-Klasse an: Klickt auf "isTrigger". Teil 3 - Variablen Dass wir jetzt einen Member der Klasse betrachten, erkennt man am Titel. Hier steht jetzt "Collider.isTrigger". Damit wissen wir, dass wir isTrigger ansehen, welches Member von Collider ist. Die kleine Zeile darunter ist jetzt das wichtigste an der ganzen Seite: Der Kopf des Members. In unserem Fall steht da bool isTrigger; was bedeutet: Es ist eine bool-Variable und sie heißt isTrigger. Das ist noch relativ langweilig, aber bedeutet schon recht viel. Dass hinter isTrigger keine Klammern stehen, sagt uns, dass es sich nicht um eine Funktion, sondern eben um eine Variable handelt. Naja... das ist nur die halbe Wahrheit. Es kann nämlich genauso gut eine sog. "Property" sein. In den meisten Fällen ist das aber bei der Anwendung total egal. Schaut euch allerdings gerne Collider.attachedRigidbody an - das ist eine Property. Den Unterschied merkt man hier lediglich darin, dass man den Wert zwar auslesen, aber nicht ändern kann. Das ist aber keine notwendige Eigenschaft einer Property. Jedenfalls kann man mit den Variablen so arbeiten, wie man es von seinen eigenen Variablen kennt: if(irgendeinCollider.isTrigger) //isTrigger ist ja schließlich ein bool oder irgendeinCollider.attachedRigidbody.AddForce(10,0,0); //attachedRigidbody hält die Referenz auf den Rigidbody, der für den Collider zuständig ist Anders funktioniert das bei statischen Variablen. Aber dazu später mehr. Teil 4 - Funktionen Bevor ihr euch fragt: Was ist der Unterschied zwischen Funktionen und Methoden? Methoden sind Funktionen, die Member einer Klasse sind. In anderen Programmiersprachen als den dreien von Unity kann man Funktionen auch außerhalb von Klassen definieren, daher die Unterscheidung. In Unity sind die Begriffe gleichbedeutend. Und wehe, mir kommt einer mit "aber in JS geht das doch". Nicht in Unity. Genau genommen sind nicht alle "Funktionen" "Funktionen", auch wenn das da steht. Gibt die Methode nichts zurück, ist's eigetlich eine "Prozedur". Funktionen/Methoden sind ebenfalls Member von Klassen. Sie sind unter "[...] Functions" eingeordnet, und der aufmerksame Leser ahnt schon: Beim Kopf des Members erkennt man Funktionen an den Klammern. Schaut euch z.B. Camera.ScreenPointToRay an. Der Kopf der Methode besteht nun aus folgenden Teilen: Ray - das gibt die Methode zurück. Ein Ray-Objekt. Steht da "void", gibt die Methode nichts zurück. ScreenPointToRay - vermutlich der Name der Methode. Dann kommen die Klammern, an denen man Funktionen erkennt und darin stehen die Parameter, die man beim Aufruf der Methode übergeben muss. In diesem Fall: Vector3 position - Man soll ein Vector3-Objekt (kein Klugscheissen wegen Struct pls ^^) übergeben. Dass der Parameter "position" heißt ist beim Programmieren egal. Allerdings steht weiter unten oft, was die einzelnen Parameter machen - und nur durch die Namen weiß man dann, was gemeint ist. Aber wie gesagt, in eurem Code spielt das keine Rolle. Aus all diesen Informationen (und einer weiteren aus der Reference) kann ich jetzt schließen, dass das hier gültiger Code ist: var ray = irgendeineKamera.ScreenPointToRay(Vector3.zero); Teil 5 - Static Wer jetzt den Link in der Klammer angeklickt hat, findet im Kopf von "Vector3.zero" das wort "static" Was static überhaupt bedeuet, könnt ihr hier lesen. Wenn ihr das verstanden habt, bleibt nur zu sagen, dass das static der Hinweis darauf ist, dass man statt irgendeinVector3.zero einfach Vector3.zero schreibt. Teil 6 - Messages Messages sind kein eigentlicher Teil der Klasse. Sind Messages für eine Koponente gelistet, bedeutet das nur genau eines: Ihr könnt durch das Deklarieren einer Methode mit dem Namen der Message in einem Skript auf Events reagieren. Dieses Skript muss auf demselben GameObject liegen wie die Komponente, die das Event bekommt. Ausnahme ist hierbei "MonoBehaviour" (die Superklasse aller eurer Scripts), auf dessen Seite alle Events, für die Unity Messages versendet, aufgelistet sind. Wenn also beim Collider das Event "OnTriggerEnter" steht, dann könnt ihr an ein GameObject mit einem Collider ein Script hängen, das folgendes beinhaltet: JS: function OnTriggerEnter(other : Collider) { //was passieren soll, wenn jemand in den Trigger läuft oder wir in einen Trigger laufen } C#: function OnTriggerEnter(Collider other) { //was passieren soll, wenn jemand in den Trigger läuft oder wir in einen Trigger laufen } Den Parameter kann man übrigens in diesem Fall einfach weg (und die Klammer leer) lassen, wenn man ihn nicht braucht. Teil 7 - Die Beschreibung Ich halte den Kopf der Member für das wichtigste, weil er einem sagt, wie man ihn beim Coden benutzen kann. Die Beschreibung, die darauf folgt, ist aber meist natürlich nicht weniger wichtig. So z.B. bei OnTriggerEnter: Liest man die Beschreibung nicht, weiß man vermutlich nicht, dass 1. das Event auch bei dem ausgelöst wird, der den Trigger betritt (oder das Gegenteil...) 2. das Event nur ausgelöst wird, wenn ein nicht-kinematischer Rigidbody im Spiel ist. Teil 8 - Abschließend Sinn dieses Tutorials ist nur eines: Dass ihr, wenn ihr das, was ihr zum Lösen eures Problems braucht, in der Reference gefunden habt, auch wisst, wie ihr es benutzen könnt. Dass, wenn ihr z.B. Light.intensity vor Augen habt, lesen könnt: float intensity; Das ist eine Variable vom Typ float. Und in der Beschreibung steht "geht von 0 bis 8". Also kann ich schreiben: light.intensity += 0.5f; Ich hoffe, es hat geholfen Nachtrag: Zwei Beiträge weiter unten hat Marrrk eine praktische Kurzreferenz gepostet!
    6 points
  50. Dieses Tutorial beschäftigt sich mit Funktionen, die die UnityEngine abietet, von denen (nicht nur) ich der Meinung bin, dass man sie trotzdem möglichst nicht bis nie benutzen sollte. Ich werde kurz erklären, warum man sie meiden möchte und was man tun kann, um exakt das selbe zu erreichen, ohne sie zu benutzen. Kleine Anmerkung: Ich finde es etwas schade, dass die offiziellen Einstiegspunkte für Unity-Scripting diese Methoden teilweise vorschlagen. Ich kann euch versichern, dass ich weiß, das Unity Tech. selbst die Verwendung vorschlägt, und dass es trotzdem unschön ist. HINWEIS: In diesem Tutorial steht in den Spoilern immer der schlechte Code, der Code darunter ist dazu äquivalent, aber eben ohne die böse Funktion. Noch mehr Scripten in Unity - Teil 4: Böse Funktionen beseitigen Fangen wir mit dem Staatsfeind nummer eins an: GameObject.Find Was macht es? Findet ein GameObject in der aktuellen Szene anhand des Namens. Warum ist es schlecht? Der Name eines GameObjects sollte einen rein dekorativen Zweck haben, da man sich auf den Namen nicht wirklich verlassen kann. Spätestens, wenn man im Team arbeitet, kann man sich nicht mehr sicher sein, dass nicht das falsche oder kein Objekt mehr gefunden wird. Es ist inperformant. Ich weiß nicht, ob die ganze Hierarchie durchsucht wird oder Unity eine Dictionary benutzt, aber schneller als die folgende Lösung wird GameObject.Find niemals sein. Was mache ich stattdessen? Wie bereits vorhergehend beschrieben, sollen alle GameObjects möglichst autonom agieren. Da GameObject.Find dafür da ist, eine Verbindung zwischen zwei GameObjects herzustellen, ist der erste Lösungsansatz, zu überdenken, ob da wirklich zwei Objekte kommunizieren sollen. Kann man nicht ein neues Script schreiben, und dieses tut dann seinen Kram alleine? Wenn das nicht der Fall ist, und zwei Objekte wirklich miteinander kommunizieren sollen, dann hilft eine Eigenschaft. Originalcode: var auto : GameObject; function Update() { auto.transform.Translate(Vector3.forward); } Jetzt muss nur noch das Auto im Unity Editor in die Eigenschaft "Auto" gezogen werden. Erfahreneren wird aufgefallen sein: Ich benutze immer nur die Transform-Komponente des Autos. In diesem Fall kann ich auch gleich das hier machen: var auto : Transform; //Typ einer Komponente, die ein GameObject haben muss, um //in die Eigenschaft gezogen werden zu können function Update() { auto.Translate(Vector3.forward); //auto.transform entfällt dann } GameObject.FindGameObjectsWithTag Was macht es? Findet alle GameObjects mit dem angegebenen Tag Warum ist es schlecht? Es ist stark davon auszugehen, dass Unity nicht so doof ist, die ganze Szene nach GameObjects mit einem bestimmten Tag zu durchsuchen, sondern dafür irgendwo ein Dictionary hat. Dennoch gibt diese Funktion eine Referenz auf ein Array zurück, das höchstwahrscheinlich nicht ohne weiteres gebildet werden kann. Kurz: Inperformant. Was mache ich stattdessen? Wieder erst einmal überlegen: Brauche ich überhaupt ein zentrales Script, das alles steuert? Können meine Objekte mit Tag xy nicht eigenständig die Aufgaben erledigen? Wenn nein: Eine statische generische Liste in einem Skript, das alle Objekte kriegen, die vorher den gesuchten Tag hatten. Eine generische Liste ist eine Liste (kein Array, Listen können dynamisch wachsen) von Objekten, das nur Objekte eines bestimmten Typs auflisten kann/darf (Generizität). (Für Fortgeschrittene: Ich benutze lieber Lists als HashSets weil ich keine Lust auf eine Wrapper-Klasse für eine Readonly-Kopie habe) Originalcode: Dieses Skript kriegen alle Objekte, die vorher den gesuchten Tag gehabt haben (was sie auch weiterhin dürfen): //man beachte den zusätzlichen, nötigen Import! import System.Collections.Generic; private static var allHouses = List.< NameDesScripts >();//Die Leerzeichen in den Spitzen Klammern gehören da nicht hin - das Forum macht mir sonst meinen Post kaputt ^^ function Awake() { //eintragen, wenn ich neu bin allHouses.Add(this); } function OnDestroy() { //austragen, wenn ich zerstört werde allHouses.Remove(this); } static function GetAllHouses() : ReadOnlyCollection.< NameDesScripts > { return allHouses.AsReadOnly(); } Das ursprüngliche Script kann sich jetzt diese Objekte ganz einfach besorgen: function Update() { var alleHaeuser = NameDesScripts.GetAllHouses(); //Mit NameDesScripts ist natürlich das obere Script gemeint for(var haus : NameDesScripts in alleHaeuser) { MachIrgendwasMit(haus); //wenn wir im Originalcode wirklich das GameObject gebraucht wird, einfach haus.gameObject schreiben } } GetComponent in Update Was macht es? GetComponent gibt eine Komponente eines bestimmten Typs auf dem aktuellen/benannten GameObject zurück Warum ist es schlecht? Aus dem gleichen Grund, warum die bisher genannten Methoden inperformant sind, ist auch GetComponent inperformant. Was mache ich stattdessen? GetComponent kann man auch umgehen, indem man eine Eigenschaft erstellt und die Komponente rein zieht (siehe letzter Code zu GameObject.Find). Gerade, wenn die Komponenten allerdings auf dem jeweils eigenen GameObject liegen, ist das aber unnötige Arbeit. Stattdessen: Component Caching! Originalcode: Stattdessen: //private, weil die Referenz nur dieses Script etwas angeht private var komponente : TypDerAnderenKomponente; function Awake() { komponente = GetComponent.< TypDerAnderenKomponente >(); //man beachte die spitzen Klammern, die performanter sind als der String im Originalcode //auch hier gehören die Leerzeichen wieder nicht wirklich da hin } function Update() { MachWasMit(komponente); } Übrigens: Die Shortcuts für GetComponent, also transform, light, renderer und so weiter, sind nichts weiter als "versteckte" GetComponent-Aufrufe. Auch die Komponenten mit Shortcut dürfen daher gerne gecached werden. Das war's auch schon Diese drei sind häufige zu Performanceverlust lockende Wiederholungstäter. Während des Schreibens sind mir einige Wege eingefallen, wie Unity Tech diese Methoden hätte implementiert haben können, sodass sie gar nicht so inperformant sind, wie man denken könnte. Der geschickte Einsatz von Dictionarys wäre da sehr gut möglich. Trotzdem kann man mit diesen Tipps auf jeden Fall mindestens ein bisschen Performance gut machen, was zumindest auf mobilen Geräten einen Unterschied machen könnte.
    6 points

Announcements

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

Weiterleitung zum Entwickler "daubit"



×
×
  • Create New...