Jump to content
Unity Insider Forum

Leaderboard


Popular Content

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

  1. 48 points
    Weil viele Leute ein Spiel mit Unity erstellen wollen, aber ein paar wichtige Infos fehlen, werde ich hier einmal erklären was alles wichtig ist und warum es manchmal zu Problemen kommt. Warum mach ich das? Weil ich mich eben gerade geärgert habe! Eines unserer Forenmitglieder (den Namen werde ich nicht nennen) hatte mich eben über Skype gefragt, ob ich jemanden kennen würde, der 3D Objekte erstellt. Das war die Frage, nicht mehr und nicht weniger! Natürlich war mir klar was er wollte. Er wollte, dass ich ihm jemanden vermittle, der kostenlos Modelle erstellt oder aber das ich sie für lau erstelle. Als ich ihm gesagt hatte, dass ich einige Leute kenne, die das aber professionell, also gegen Bezahlung machen, und ich selber keine Zeit dafür habe, hat er mich aus den Kontakten raus gekickt! Das war nicht die feine Art und außerdem unrealistisch. Vorallem ärgert es mich, da ich ihm schon einige Male geholfen habe. Somit sind wir beim Thema: Jeder, der ein Spiel erstellen will sollte beachten, dass ein Spiel nicht nur aus dem Code besteht, der die Engine (in unserem Fall Unity3d) steuert. Nein, das Wichtigste sind die Objekte, die gesteuert werden! Die müssen in einem 3D Programm oder, wenns 2D sein soll, in einem 2D Grafikprogramm erstellt werden. Diese Sachen kosten eine Menge Zeit und Können! Die meisten Leute denken, dass sowas mal eben gemacht ist. Dem ist nicht so. Ein einfaches Haus welches ne nette Textur hat, dauert schon mehrere Stunden. Ein Männchen, welches mit Bones versetzt und animiert wird, dauert mindestens einen Tag, wenn nicht mehrere Tage!!!! Die Objekte für ein ganzes Spiel dauern Wochen bis Monate, bis sie alle gebaut, texturiert, animiert und arangiert sind! Natürlich braucht ein Spiel auch Geräusche und Musik. Einfache Geräusche sind teilweise im Netz für lau zu bekommen. Gute Geräusche muss man kaufen oder aber selber aufnehmen bzw. selber erstellen. Dieses dauert auch. Nicht ganz so lang, aber es summiert sich. Bei der Musik sieht es schon ganz anders aus. Wer kann schon selber Musikstücke erstellen. Also entweder kaufen oder jemanden komponieren lassen. Natürlich gibt es auch GEMA freie Stücke im Netz, meist passen die aber nicht oder andere Spiele haben diese Musik schon drin. Das will man natürlich nicht. Jetzt kommt die GUI, die Menüs, die Anzeigen, und und und... auch dafür braucht es jemanden, der sowas kann, wenn es nach etwas aussehen soll. Auch hier kann man mehrere Tage einplanen, bis alles Nebensächliche erstellt worden ist. Story, Dialoge, Spielziele lasse ich jetzt einfach mal weg. Da gehe ich davon aus, dass wenigstens das vorher schon gemacht wurde. Das Programmieren des Codes rechne ich auch nicht ein, denn ich gehe davon aus, dass die Jungs hier, genau das machen. So, jetzt seht ihr, dass ein Spiel viel mehr ist, als einen Cube von a nach b zu bewegen. Ihr solltet jetzt auch erkennen, dass komplexere Spiele nur schwer von einer Person zu bewältigen sind. Ihr solltet außerdem bedenken, dass die Sachen, die ihr nicht könnt, nicht immer umsonst von anderen Leuten hergestellt werden. Nein, gerade das Gegenteil ist der Fall. Es dauert nämlich unheimlich lange und warum sollte der Mensch seine Freizeit für jemanden anderes opfern, von dem er nichts hat? Was mir auch noch ganz wichtig ist: Nur weil ich Tutorials umsonst ins Netz stelle, heißt das nicht dass ich alles umsonst mache und nix anderes zu tun habe als hilflosen Fans zu helfen. Die Tutorials sind gerade deswegen da, damit andere Leute es lernen und selber machen können! Es geht nicht nur mir so, nein allen Jungs geht's so, die Tut's ins Netz stellen. Ganz so als wäre man Mutter Theresa! Versteht einfach, dass so Leute wie ich 40Stunden in der Woche arbeiten, eine Familie haben, eigene Hobbys betreiben und dann nur noch wenig Zeit für anderes da ist. Diese wenige Zeit ist kostbar und wird nicht leichtfertig an Andere abgetreten. Somit macht euch klar, dass nix umsonst ist! Manchmal hat man Glück und bekommt etwas für lau. Meistens aber nicht. Also bleibt euch nur alles selber zu machen, sich mit Gleichgesinnten zusammen zu schließen um alle Felder abzudecken oder aber Geld für Leistungen zu bezahlen. Selbst die Gleichgesinnten wollen was haben. Meist arbeiten sie in Vorleistung und wollen einen Teil des Gewinns, wenn es denn einen Gewinn gibt. Das Ganze soll euch nicht entmutigen, nur die Augen öffnen!
  2. 34 points
    Es gibt eigentlich keine richtitge Rubrik für dieses Thema, deswegen poste ich es in den Texttutorials, da es am naheliegendsten ist. Weil doch einige Leute nicht alle Begrifflichkeiten aus dem Grafikbereich kennen, weil sie z.B. Programmierer sind und einfach nichts damit zu tun haben, will ich hier mal etwas Licht ins Dunkel bringen es geht hier um das 3D Objekt und was dazu gehört. Jedes 3D Spiel braucht 3D Objekte. Da in Unity nur einige Grundkörper zur Verfügung stehen, muss man ein anderes Programm nutzen um 3D Objekte wie z.B. ein Männchen, Monster, Gegenstände, Häuser oder Fahrzeuge zu erstellen. Alle diese Programme arbeiten ähnlich und wenn das Objekt erst einmal erstellt ist muss man es nach Unity rüber bringen. Hier und da gibt es Schwierigkeiten beim im/export aber darauf will ich nicht eingehen. Ich möchte hier mal mit recht einfachen Worten erklären, was ein 3D Objekt ist, wie es sein sollte, warum man auf gewisse Sachen achten muss und wie sich das mit den Texturen und Animationen verhält. Das 3D Objekt: Jedes 3D Objekt wird aus Flächen gebildet. Auch wenn das Objekt nur aus einer einzigen Fläche besteht, ist es ein 3D Objekt denn diese Fläche wird aus Eckpunkten gebildet und diese Punkte haben Informationen wo sie sich im Raum befinden. Eine Fläche muss mindestens 3 Eckpunkte haben, was ja klar ist, denn nur mit 2 Punkten wäre es eine Linie und keine Flächen. Der Fachausdruck für eine Fläche ist das "Polygon". In den gängigen 3D Programmen wird mit viereckigen Flächen gearbeitet. Es können aber auch mehr Ecken sein. Eine dreieckige Fläche nennt man "Tris". Eine viereckige Fläche nennt man "Quad". Flächen die mehr als 4 Punkte haben nennt man "N-Gons" (das "n" steht für irgendeine Zahl über 4), denn sobald diese Flächen über 4 Eckpunkte besitzen ist das 3D Programm gefordert und es macht keinen Unterschied mehr, wieviele Punkte es nun wirklich sind. Ganz wichtig zu wissen ist die Tatsache, dass fast in jedem Programm aus den Quads und n-Gons intern Dreiecke bildet. Unity arbeitet auch nur mit Dreiecken und die Objekte werden schon beim Import gewandelt. Es gibt da aber Schwierigkeiten bei den n-Gons! Ist bei es bei einem Quad einfach die Fläche zu unterteilen um 2 Tris zu bekommen (kann ja nur von Punkt 1 nach Punkt 3 oder aber von Punkt 2 nach Punkt 4 geschnitten werden) so gibt es bei n-Gons viele Möglichkeiten des Schneidens. Je nachdem können da gleichmäßige Dreiecke entstehen oder aber sehr große und ganz spitze. Das wirkt sich später auf das Aussehen des Objektes aus und kann ungewünschte Ergebnisse erzielen. Deswegen auf n-Gons verzichten und einfach selber den Schitt setzen um maximal Quads zu haben. Wie viele Polygone ein Objekt hat oder haben sollte hängt davon ab was ich in meinem Spiel zeigen will, welche Shader ich nutzen will, wie beleuchtet wird, wieviel Performance mir zur Verfügung steht und wieviele Polygone insgesamt in der Szene zu sehen sind. Eine ganz normale Schachtel, die keine abgerundeten Kanten hat, braucht wirklich nur 12 Polygone (6 Seiten a 2 tris). Jede größere Unterteilung wäre Verschwendung. Würde diese Schatel sich nicht bewegen lassen, bliebe also immer am selben Fleck liegen, dann kann man sogar die 2 Polygone den Bodens weg lassen. Habe ich eine Spielfigur, welches viele Details haben soll, da es nah zu sehen ist, brauche ich auch ein Mindestmaß an Polygonen. Wieviele das werden ist schwer zu sagen, da man vieles auch mit Texturen lösen kann. So braucht man leichte Unebenheiten der Kleidung oder Rüstung nicht ausmodellieren sondern kann das über Texturen vorgaukeln. Aber wie auch immer, solch ein Modell wird schon mehr als 1000 Polygone haben. Je realistischer das Ganze werden soll umso mehr muss das Polygon bringen. Bei modernen Spielen, die sich nicht mehr auf einfachen Grafikkarten spielen lassen, haben die Figuren auch schon mal mehr als 10.000 Polygone. Aber auch da wird geschaut, wo man bei anderen Objekten die Polygone einsparen kann, sodass die Summe an Polygonen in der Szene nicht zu hoch wird. Die Animation mit Bones: Um eine Spielfigur laufen zu lassen, muss man dem 3D Objekt ein Skelett verpassen. Man legt fest, welche Punkte des 3D Objektes mit welchen bones (Knochen) interagieren. An einem Bein will ich das mal verdeutlichen. Dieses Bein hat jetzt nur einen Oberschenkelknochen und einen Unterschenkelknochen. Wenn ich den Oberschenkelknochen an seinem Gelenk drehe, dreht sich das ganze Bein. Drehe ich den Unterschenkelknochen, bleibt der Oberschenkel wie er ist, aber der Unterschenkel dreht sich über das Kniegelenk. Damit das so ist, muss ich die Knochen an das Mesh binden. Ich lege also fest, welche Punkte sich in welcher Stärke mit dem zugehörigen Knochen bewegen sollen. Somit hat der Oberschenkelknochen die Punkte des Oberschenkels und der Unterschenkelknochen die Punkte des Unterschenkels. Am Knie teilen sich beide Knochen die Punkte. WIe stark welcher Knochen einen Punkt bewegen darf, nennt man Wichtung. Jeder Punkt muss am Schluß zu 100% gewichtet sein. Egal ob nur ein Knochen auf den Punkt einwirkt oder mehrere sich die Einwirkung teilen. Damit das Knie schön rund bleibt, wenn es einknickt, reicht es nicht nur direkt am Knie Punkte zu haben. Man braucht auch noch Loops (Unterteilungen im Mesh) etwas oberhalb und etwas unterhalb vom Knie. Der Oberschenkelknochen übergibt den Punkten unterhalb nur wenig Wichtung und beim Unterschenkel ist es genau umgekehrt. Dadurch kann man das Knicken etwas weicher gestalten lassen und es kommt nicht zu unschönen Deformationen. Ist jeder Punkt zu 100% gewichtet, kann man das Objekt animieren. Dafür macht man quasi Fotos der Position der Punkte in der Zeitleiste. Am Schluss hat man eine gewisse Animationsdauer und während dieser Dauer mehrere Fotos von den Positionen der Punkte gemacht. Dieses wird auch nach Unity importiert und kann dort abgespielt werden. Dieses 3D Objekt hat jetzt ein Skinned Mesh! Unity weiss also, dass die Punkte nicht starr sind sondern sich innerhalb der Animation bewegen. In Unity kann eingestellt werden, wieviele bones jetzt wirklich auf so einen Gelenkbereich wirken sollen. Voreingestellt ist glaube ich 2. Je mehr Bones auf das Mesh wirken, umso mehr muss Unity die Sache berechnen. Sowieso braucht jeder Bone Performance, weswegen es auch gut ist so wenig Bones wie möglich zu nutzen. Es gibt noch weitere Einstellungsmöglichkeiten, die etwas Performance zurück bringen, denn wenn z.B. ein Skinned Mesh nicht im Bild ist, muss es auch nicht unbedingt animiert werden. Texturen, Shader und die UV Map: 3D Objekte sollen ja auch nach etwas aussehen und somit brauchen die ein Material. Das Material ist erstmal nur ein Container in dem ein Shader eingestellt ist. Dieser Shader kann unterschiedliche Kanäle haben um einen Farbanteil, eine Transparenz, eine Textur und einen Glanz/Spiegelung zu haben. Habe ich jetzt wieder mal eine Schachtel, die z.B. wie Holz aussehen soll und zusätzlich einen Aufdruck auf einer der Seiten hat, reicht mir die Farbe braun nicht aus. Ich muss einen Shader nutzen, in dem ich eine Textur einladen kann. Diese Textur ist erstmal eine ganz normale Grafik. Hätte ich z.B. ein Foto von einer Holzmaserung und würde das jetzt dem Shader zuweisen, so würden alle Seiten der Kiste mit dieser Holzmaserung bestückt werden. Da nichts weiter an Informationen vorliegt, wird Unity von sich aus auf jede Seite die Maserung drauflegen und skalieren. Man sieht das, denn die Ausrichtung der Maserung ist nicht immer richtig. Mehrere Seiten sind verzerrt. Um das zu umgehen, brauche ich eine UV Map. Man stelle sich ein Blatt Papier vor, auf das ich die Kiste lege. Die Unterseite der Kiste belegt jetzt einen gewissen Bereich des Papieres. Würde ich an den Eckpunkten der Kiste jetzt echte Punkte auf das Papier malen, hätte ich einen Bereich definiert, der später der untenliegenden Seite der Kiste zugewiesen würde. Die Kiste könnte ich jetzt über eine Kante abrollen und wieder Punkte auf das Papier bringen, wo die Ecken der neuen Seite der Kiste sind. Ihr würdet sehen, dass ich nur 2 neue Punkte setzen muss, denn die ersten 2 Punkte sind die gleichen 2 Punkte der Unterseite der Kiste. Das würde ich mit allen Seiten der Kiste so machen und hätte am Schluss 6 Bereiche auf meinem Blatt Paier definiert. Ich hätte eine UV Map erzeugt. So ist das auch bei den 3D Objekten. Ist eine UV Map erzeugt worden hat das Objekt eine Information darüber, welches Polygon welchen Bereich meiner Grafik zugewiesen bekommt. Da es ja weiß welche Punkte wo auf dem Blatt liegen. Mit einem Grafikprogramm könnte ich jetzt eine Holztextur in die richtigen Bereiche der Grafik einfügen und dann auch zusätzlich den Aufdruck, den die Kiste später auf einer Seite haben soll, dazu malen. In Unity würde das 3D Objekt jetzt die Textur mit Hilfe der Informationen genau richtig platzieren. Ich hätte eine Kiste, die von allen Seiten anders aus sehen kann, ganz so, wie ich es wollte. Das erzeugen einer UV Map geht leider nur bedingt automatisch und braucht einiges an Handarbeit. Viele 3D Programme haben diese Funktion zum Erzeugen schon dabei. Es gibt aber auch unabhängige Tools. In Unity braucht jedes neues Material in der Szene Performance. Deswegen ist es gut, wenn sich viele Objekte ein Material (Textur) teilen. So eine Grafik, wo z.B. viele Button Grafiken nebeneinander liegen würden, nennt man Texturatlas. Jeder Button würde wissen, welchen Teil des Textruenatlas er zeigen soll. Man kann es direkt in Unity einstellen, wenns nur 2D ist, oder aber die Objekte haben alle unterschiedliche UV Maps, die immer nur einen Bereich der Großen Texture benötigen. Um Unebenheiten auf Oberflächen zu erzeugen, muss man sie nicht modellieren, nein man kann auch eine NormalMap oder einen Bumpmap (Höhenkarte) dafür nutzen. Das ist wieder so eine UV Textur, die jetzt aber nicht die Farbe des Objektes beinhaltet, sondern Höheninformationen. Bei der Bumpmap wird das mit Graustufen gelöst, wobei weiß hoch bedeutet und schwarz tief. Die Normalmap ist 3 Farbig und kann dadurch auch seitliche Erhebungen darstellen. Es ist viel performanter Unebenheiten über die Textur zu erzeugen anstatt über die echte Geometrie zu gehen. Auch wenn so ein Shader wiederum etwas performance braucht. Natürlich wird dadurch alles nur vorgegaukelt und es scheint, als hätte die Oberfläche eine Struktur. Sie ist aber noch glatt und wenn die Struktur auch Auswirkung auf andere Objekte haben soll, kann das nicht mit der Textur lösen.
  3. 18 points
    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
  4. 17 points
    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!
  5. 17 points
    Über unsere Scripting-Tutorials Dies ist die Liste unserer Scripting-Tutorials. Sie ist gedacht für alle, die noch absolut keine Programmiererfahrung haben, aber auch für jene geeignet, die bereits, wenn auch nicht in Unity, programmieren können. Es werden überhaupt keine Vorkenntnisse voraus gesetzt. Eigentlich sind diese Texte auch weniger Tutorials als vielmehr Lehrtexte Scripten in Unity für Einsteiger Erste Grundlagen für das Scripten im Allgemeinen Scripten in Unity für Nicht-mehr-Einsteiger Der zweite Teil, passend zum ersten Nach diesen beiden beherrscht man die Grundlagen und kann sich an seine ersten eigenen Scripts setzen. Wer jetzt auf C# umsteigen möchte: C#-Tutorial für Um- und Einsteiger Umstieg von JavaScript auf C# Scripten in Unity mit Unity Die Schnittstelle der Unity-Engine, deren Klassen beim Scripten benutzt werden, vorgestellt Scripten in Unity für Scripterfahrene (an Objekte kommen) Der vierte Teil, wichtig für sauberes Programmieren und um das Prinzip hinter Unity zu verstehen Die Scripting Reference lesen Damit man eigenständig neue Dinge über Unity lernen kann Noch mehr Scripten in Unity (Wissen für Fortgeschrittene) Noch mehr Scripten in Unity - Teil 1: Arrays und Existenzoperator Erste leicht fortgeschrittene Techniken, aber einfach erklärt Noch mehr Scripten in Unity - Teil 2: Klassen und Überladungen Objektorientierte Programmierung in Unitys JavaScript Für den Anfang nicht benötigt, für Fortgeschrittene interessant Noch mehr Scripten in Unity - Teil 3: Statische Variablen und Funktionen Für die richtige Anwendung statischer Deklarationen und damit für sauberes Programmieren Noch mehr Scripten in Unity - Teil 4: Böse Funktionen beseitigen Die typischen inperformanten Funktionen durch äquivalenten, besseren Code ersetzen Scripten in der Praxis (Komponenten- und Praxisorientiertes Sctipting) Fehler in Scripts finden Konsole benutzen und Fehlermeldungen lesen können Scripten in der Praxis - Der CharacterController Menschen und andere Lebewesen in Unity Scripten in der Praxis - Raycasts Wozu sie gut sind und wie man sie benutzt
  6. 16 points
    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
  7. 16 points
    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!
  8. 15 points
    Ich schleiche grade um Ngui rum. Und spiele ein wenig mit der Free Version. Eins der Sachen die ich gern einbauen würde wäre ein 3D Ringmenü. Entweder mit Sprites oder echten 3D Objekten. Das soll sich halt je nach Auswahl eins weiterdrehen. Ich googel mir im Moment die Finger wund, aber irgendwie finde ich dazu nichts in Verbindung mit Ngui. Weiss das jemand ob das geht? Wenn ja, wie stelle ich das denn an? Welche brauchbaren Alternativen, eventuell auch kostenlose, gibts eigentlich noch für eine GUI Lösung? Ist NGui sein Geld wert?
  9. 14 points
    Hi, ich habe einen GUIStyle erstellt. Nun möchte ich, dass eine Textur aktiv bleibt, wenn ich den Mausknopf loslasse und wieder wechselt, wenn ich wieder klicke. Wie bekomme ich das hin?
  10. 14 points
    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"
  11. 13 points
    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!
  12. 13 points
    hi, wenn ich ein c# script an dem ich gerade schreibe aktualisiere und z.b. ein bool auf true speicher wird das im inspector wenn ich das script auf ein objekt ziehe als true angezeigt. wenn ich nun das projekt schließe ( ich weis nicht notwendig ) und wieder öffne ist es ja immernoch true. wenn ich nun per editor ( monodev also dem standard ) im script von true auf false bei der deklarierung des boolean stelle ( IM STOP MODE !!! ) bleibt es im inspector auf true wie kann ich das ändern
  13. 12 points
    Moin, bin grade dabei mein Menu zu machen in NGUI. Hat bis jetzt alles gut funktioniert, doch nun möchte ich etwas in einem Atlas von NGUI ändern und ich hab diese änderung in der PSD vorgenommen sie abgespeichert allerdings wenn ich auf das Atlas Prefab gehe wird sie mir dort in der Vorschau nicht angezeigt... Jemand ne Idee?
  14. 12 points
    Hallo Leute Ich weiß, ich habe bereits ein Topic zu einem ähnlichen Thema gemacht (GUI passend zu Auflösung) und habe das Problem auch beheben können. Jetzt habe ich allerdings versucht eine Runde Minimap zu machen. Nach diesem Tutorial. Das Problem ist, dass diese Minimap nichts mit dem GUI zu tun hat, ich aber eine 2D Textur darüber gelegt habe, damit das Ganze ein enig besser aussieht. (Unten ist ein Screenshot) Dieses GUI Element, welches über der Minimap liegt, verändert sich je nach Auflösung von der Größe her, doch diese Minimap dahinter tut es nicht und wird bei manchen Auflösungen sogar links und rechts abgeschnitten. Meine Frage ist nun, wie ich auch diese Minimap (bzw. es ist ja eigentlich nur ne Orthografische Kamera) auch dazu bewegen kann, sich mit der Auflösung von der Größe her zu verändern. Ich freue mich über jede Antwort Gruß, Phillor
  15. 12 points
    wenn ich eine scene habe ( und auch nur eine ) diese lade und dann "select dependancies"mache, sollten mir doch alle sachen die dazugehören markiert werden. (wir gehen davon aus das wirklich alles was benötigt wird markiert ist und unity nichts vergisst ( ja das passiert das unity sachen auslässt ! ) ) gibt es da einen weg alles umzudrehen sprich sowas wie "alles was nicht benötigt ist markieren" oder muss ich den umständlichen weg gehen und alles was mit "select dependancies" markiert wurde in einen ordner (TEST) verschieben und dann händisch wieder in neue unterordner packen damit ich das was in TEST ist alleine im projekt habe ? Ziel : ich will das unbenutzte löschen
  16. 12 points
    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.
  17. 12 points
    Ich brauche noch dringend eine brauchbarere Methode für meine Dialoge. Das ist einfach zu unhandlich wie ich es jetzt gelöst habe. Der Text ist hardgecodet. Ich schleiche zwar um NGui rum. Ich bin aber mit schmalem Geldbeutel unterwegs. Kostenlos wär mir deswegen eigentlich am liebsten. Wer mein Spiel angespielt hat weiss ungefähr was ich brauche. Im Grunde Textboxen. Das ist einmal die ganze GUI Geschichte am Anfang. Sprich zum Beispiel der scrollende Text für den Intro. Und vor allem im Spiel. Denn die Hexe am Anfang wird meinem Helden ein paar Aufgaben stellen. Zum Beispiel einen bestimmten Gegenstand aus einem Level zu besorgen. Und dafür brauche ich eben ein Dialogsystem. Die Frage ist die gleiche wie schon beim Ringmenü: lohnt sich dafür Ngui, oder gibts da billigere Lösungen für? Wie sähe denn was brauchbares mit der Unity GUI Lösung aus? Kennt ihr vielleicht ein paar Beispiele die man sich ansehen könnte um da zu borgen?
  18. 12 points
    Ich bin gerade am überlegen wie ich eine Übersichtskarte realisiere, die die aktuelle Position des Spielers zeigt und sich z.B. oben rechts in der normalen Spielesicht befindet. Wie würdet Ihr so etwas machen?
  19. 11 points
    Ich werd und werd nich schlau aus dem Vieh. Ich dacht eigentlich dass NGui relativ simpel zu bedienen ist. Und das stimmt ja auch fürs eigentliche Setup. Aber wie ich mit dem Ding interagiere das kriege ich ums Verrecken nicht raus Die Scriptreferenz zu NGui habe ich zwar inzwischen gefunden, das ist aber wieder mein übliches Drama, weil ich wie üblich nur Bahnhof verstehe. Es gibt kein einziges Anwendungsbeispiel. Nur die Wörter. Und da steh ich halt einfach auf Kriegsfuss mit weils nich klappen mag. Ich will im Moment einfach den Text eines Labels ändern. Mit Javascript. Rausgefunden habe ich dass das wohl irgendwie mit UILabel.text gehen müsste. Aber wie? Das hier funktioniert jedenfalls schon mal nicht: var texttochange: GameObject; function Update () { if (Input.GetKey (KeyCode.A)){ texttochange.UILabel.text="mytext"; } Assets/Scripts/ngui/changetext.js(9,31): BCE0019: 'UILabel' is not a member of 'UnityEngine.GameObject'. Halp EDIT, haha, das übliche Spiel, ich post die Frage, und in dem Moment macht es Klick Ich muss über get Component gehen. Ich will ja auf das UILabel Script zugreifen. Das hier geht: var texttochange: GameObject; function Update () { if (Input.GetKey (KeyCode.A)){ var change= texttochange.GetComponent(UILabel); change.text="mytext"; } }
  20. 10 points
    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.
  21. 10 points
    Das Wichtigste habe ich natürlich wieder nicht getestet Wie verbinde ich denn eine NGui Ui mit dem Rest des Spiels? Einfach die NGui Scripts umschreiben verbietet sich ja von selbst. Die sollen ja universell bleiben. Sprich, wo und wie hänge ich meine eigenen Scripts und Funktionen ein? Kann ich die Mausklicks auf die Buttons von irgendwo zentral von Ngui empfangen? Oder hänge ich da an jeden Button mein eigenes kleines Script an mit den Sachen die beim Mausklick passieren sollen?
  22. 10 points
    hallo, ich habe ein problem und hoffe das sit nicht zu lächerlich um ein neues thema aufzumachen. hab aber nix änliches gefunden. und zwar möchte ich ein menü machen, das der player am unteren bildschirm rand immer sieht. nun zu meiner frage ich hab diesen c# code (noch nicht ausformulier, noch zum teste, also als einzelner menü punkt) void OnGUI(){ for(int i= 0; i <buildings.Length; i ++) { //erstellt das BauMenü if ((GUI.Button(new Rect(20,40,100,30), Baunemue)){ if (GUI.Button(new Rect(Screen.width/20,Screen.height/15 + Screen.height/12 * i,100,30), buildings[i].name)){ buildingPlacement.SetItem(buildings[i]); } } } aber irgend wie geht nichts und mir wirt der fehler : Assets/BuildingManager.cs(22,72): error CS1525: Unexpected symbol `{' und Assets/BuildingManager.cs(27,9): error CS8025: Parsing error angezeigt. vlt könnt ihr mir helfen wie ich es richtig mache. danke
  23. 9 points
    Multiplayer mit Unity - Teil 2 Masterserver und Statesynchronisation Inhalt: Kommentar Etwas über Unity und Multiplayer Den Masterserver benutzen Spieler Spawnen Einfache Statesynchronisation Dead Reckoning 1. Kommentar Guten Abend zusammen Ich entschuldige mich dafür dass dieses Tutorial so lange hat auf sich warten lassen. Ich arbeite aktuell nicht mit dem eingebauten Netzwerksystem von Unity sondern mit einer eigenen Lösung (basierend auf Sockets) und einem C++ Server, die Gründe dafür werde ich in 2 genauer erläutern. Generell gibt es zu diesem Tutorial zu sagen dass es auf dem Code und dem Wissen vom 1. Tutorial basiert und ich werde demzufolge kein komplettes Programm sondern nur die speziellen Ausschnitte (zum Master Server verbinden, Statesynchronisation...) hier erläutern, da es sonst auch zu lang würde. Das komplette Projekt ist im Anhang zu finden, und eine Webplayer Demo werde ich auch veröffentlichen. 2. Etwas über Unity und Multiplayer "Unity unterstützt Multiplayer, juhuu, jetzt kann ich endlich ein MMORPG schreiben!" Das wäre zu schön um wahr zu sein. Abzusehen davon dass ein MMORPG immernoch sehr viel Arbeit ist, ist Unity für diesen (oder andere Spieltypen) nicht ausgelegt. Hier mal etwas genauer: Unity's Multiplayer ist durchaus einfach zu bedienen und funktionstüchtig, das möchte ich ihm nicht absprechen, aber es ist nur für Spiele geeignet bei dem ein Spieler selber der Server ist, d.h. z.B. FPS oder Strategiespiele. Es ist grundsätzlich bis zu einem gewissen Grad möglich auch Spiele mit Unity zu realisieren bei denen ein eigenständiger Server benutzt wird (z.B. bei einem MMORPG), aber dabei sollte man auf andere Server zurückgreifen, z.B. Photon. Für den Hobby Entwickler liegt das einzige Problem da evtl. beim Preis Bei dieser Lösung wird Unity dann praktisch nur noch benutzt um beim Client die Grafik/Sound usw. zu verwalten. Von der Idee den Server auch in Unity zu realisieren sollte man sich fernhalten. Abgesehen davon dass Unity nicht dafür strukturiert ist wird man spätestens beim 2. Level dass der Server parallel verwalten soll auf Probleme treffen. [beispiel: Im Server hat der NetworkView eine andere ID als im Client weil der Server mehrere Szenen auf einmal hat (Wenn die 1. Szene 3 Network Views hat dann hat der 1. Network View der 2. Szene beim Server die ID 3 und im Client die ID 0...)] Die andere Alternative ist es auf Unitys Netzwerksystem zu verzichten und es selber (z.B. mit Sockets) zu erledigen. 3. Den Masterserver benutzen Ok, let's go. (Ich gehe davon aus dass der Quellcode aus dem 1. Tutorial benutzt wird) Um statt zu einer direkten IP über den Master Server zu verbinden sind einige wenige Änderungen nötig. Beim Server ist lediglich ein weiterer Funktionsaufruf nötig: if (GUILayout.Button ("Start Server")) { // Status auf "Starte Server...." setzen Status = "Starte Server..."; Debug.Log(Status); // zu bestimmter IP und bestimmtem Port verbinden Network.InitializeServer(32, parseInt(Port), !Network.HavePublicAddress()); // Den Server beim Master Server "anmelden" MasterServer.RegisterHost("Multiplayer_Tutorial_2-Test", "Testgame", "free4all"); } Der Aufruf von "RegisterHost" tut die ganze Magie auf der Seite des Servers. Dadurch wird der Server in die Serverliste vom Unity Masterserver aufgenommen. Der Erste Parameter gibt den Typ des Spiels an. Nur Wenn der Server den selben Typ wie der Client hat erscheint der Server auch in der Serverliste des Clients. Der 2. Parameter gibt den Spielnamen und der 3. ein Kommentar an. Hier muss ich noch auf NAT hinweisen (durch Network.useNat benutzbar). Falls es Probleme beim Verbinden geben sollte ist es ratsam Nat an/aus zu schalten. Es gibt viele Computer die nur zu Nat Servern verbinden können oder nur zu nicht Nat Servern. Fortgeschritten ist es möglich die NAT-Möglichkeiten durch Unity herausfinden zu lassen. Link: Test Connection Auf der Seite des Clients wird das ganze schon etwas komplizierter. Die Serverliste kann man mit MasterServer.RequestHostList("Multiplayer_Tutorial_2-Test"); abrufen. Dann muss man sie nur noch auswerten. //in OnGUI // Serverliste abfragen var serverList : HostData[] = MasterServer.PollHostList(); // Alle Server durchgehen for (var server in serverList) { GUILayout.BeginHorizontal(); // Name, Spielerzahl/Spielerlimit und Kommentar anzeigen GUILayout.Label(server.gameName + " (" + server.connectedPlayers + " / " + server.playerLimit + ") | " + server.comment); GUILayout.FlexibleSpace(); // Für jeden Server einen Button erstellen if (GUILayout.Button("Verbinden")) { // Bei Nat richten wir uns nach dem Server Network.useNat = server.useNat; // verbinden Network.Connect(server.ip, server.port); } GUILayout.EndHorizontal(); } Dazu muss man noch sagen dass man RequestHostList öfters aufrufen sollte (über einen "Aktualisieren" Button oder zeitlich alle paar Sekunden) um die Serverliste zu aktualisieren. Das wars schon um den Master Server zu benutzen. 4. Spieler Spawnen Als nächstes kommen wir zu einem spannenderem Punkt: Wie kann man Spieler in der Welt instanziieren/Steuern usw. Um etwas instanziieren zu können brauchen wir erstmal ein Prefab. Dabei habe ich eine einfache Capsule mit Capsule Collider und Rigidbody (Use gravity und Freeze Rotation) genommen, aber das bleibt jedem selbst überlassen. Dem ganzen kann bzw. sollte man dann noch ein Spielerskript und einen NetworkView (mit Standardeinstellungen) hinzufügen. Damit sich auch was bewegt kann man in das Spielerskript ein provisorisches Steuerungssystem einbauen: function Update() { // Wenn es mein NetworkView ist if(networkView.isMine) { // den Spieler bewegen transform.Translate(Vector3(Input.GetAxis("Horizontal"), 0,Input.GetAxis("Vertical")).normalized *Time.deltaTime * 7); } } Dann brauchen wir noch einen Spawner, das ist die Stelle an der die Spieler erstellt werden sollen. Dazu ein leeres GameObject erstellen und an die Position verschieben an der die Spieler gespawnt werden sollen. Dann ein neues Skript (z.B. "Spawner.js") erstellen. // Das Prefab, muss im Inspektor gesetzt werden var playerPrefab : Transform; // Wenn wir zum Server verbunden wurden function OnConnectedToServer() { // das Spielerprefab instanziieren (an der Position des Spawners und mit der Ausrichtung des Spawners) Network.Instantiate(playerPrefab, transform.position, transform.rotation,0); } // Wenn der Server gestartet wurde auch für ihn einen Spieler erstellen function OnServerInitialized() { // das Spielerprefab instanziieren (an der Position des Spawners und mit der Ausrichtung des Spawners) Network.Instantiate(playerPrefab, transform.position, transform.rotation,0); } // Ein Spieler hat sich ausgeloggt (wird nur auf dem Server aufgerufen) function OnPlayerDisconnected(player: NetworkPlayer) { // aufräumen Debug.Log("Der Spieler " + player + " hat sich ausgeloggt, Entferne seine RPCs und Objekte"); // alle von ihm gebufferten RPCs löschen Network.RemoveRPCs(player); // Seine Spielerobjekte zerstören Network.DestroyPlayerObjects(player); } In diesem Codeschnipsel wird zuerst das Prefab für den Spieler definiert (muss ihm Inspektor zugewiesen werden (!) ) und dann werden 3 verschiedene Events behandelt. Bei einer Neuverbindung (Serverside/Clientside) wird jeweils ein neuer Spieler erstellt und wenn eine Verbindung beendet wird wird auch der dazugehörende Spieler gelöscht. [Das Event "OnDisconnectedFromServer" sollte auch behandelt werden ist hier aber unwichtiges. Genaueres dazu ist in der Datei Spawner.js als Kommentar zu finden ] Network.Instantiate erstellt eine Instanz von dem Prefab an der angegebenen Position mit der angegebenen Ausrichtung auf jedem Client/Server. Dieser Aufruf ist gebuffert, d.h. auch Spieler die sich später verbinden werden diesen Aufruf empfangen. Wenn man das Ganze jetzt testet sollte Unity die Spielerpositionen standardmäßig synchronisieren und soweit sollte alles funktionieren. 5. Einfache Statesynchronisation Auch wenn Unity im Falle von Position, Skalierung und Rotation selber synchronisiert ist es doch wünschenswert es auch manuell durchführen zu können. Das kann man z.B. gut gebrauchen wenn auch andere Variablen ausgetauscht werden sollten, z.B. Health oder Mana. Hier ein kleines Beispiel: // Health und Mana als Integer Variablem var health : int = 100; var mana : int = 100; // Bei jeder Synchronization function OnSerializeNetworkView(stream : BitStream, info : NetworkMessageInfo) { // temporäre Kopien anlegen var healthCopy : int = 0; var manaCopy : int = 0; // Es ist unser NetworkView (wir können schreiben) if (stream.isWriting) { // den temporären Kopien die lokalen Werte zuweisen healthCopy = health; manaCopy = mana; // Beides zu dem gleichen NetworkView auf den anderen Clients/Server weiterleiten. stream.Serialize(healthCopy); stream.Serialize(manaCopy); } else { // Die beiden temporären Kopien mit den Werten von der Synchronisation füllen stream.Serialize(healthCopy); stream.Serialize(manaCopy); // Die lokalen Variablen aktualisieren health = healthCopy; mana = manaCopy; } } Der Code ist schnell erklärt. OnSerializeNetworkView wird von Unity immer dann aufgerufen, wenn standardmäßig Daten "synchronisiert" werden. Dabei wird überprüft ob wir Schreibzugriff auf den Stream haben (wenn ja sind wir der Eigentümer, ansonsten nicht) und handeln dann dementsprechend. Zum Schreiben/Lesen wird die Funktion stream.Serialize benutzt, die abhängig davon wem der NetworkView gehört entweder schreibt oder liest. Beim Schreiben wird der Wert des ersten Parameters an alle Clients "verteilt", beim Lesen wird der erste Parameter mit dem empfangenen Wert gefüllt. 6. Dead Reckoning Unitys Statesynchronisation ist relativ simpel, die Position wird alle ~15 Frames synchronisiert. Bei einer langsamen Verbindung kann das allerdings zu dem altenbekannten Lag führen. (Wenn Positionsupdates verspätet oder gar nicht ankommen.) Und die pure Positionssynchronisation ist auch oft recht "ruckelig". Deswegen gibt es eine Technik die sich Dead Reckoning nennt. Der Client bekommt statt der Position die Geschwindigkeit/Richtung des Spielers übertragen und berechnet die neue Position selber um sie nur gegebenenfalls zu berichtigen (und das am besten auch über mehrere Frames verteilt, damit es nicht zu "Sprüngen" kommt). Das wird Thema dieses Kapitels sein. Es haut bei mir gerade zeitlich nicht hin, ich werde das Tutorial (Kapitel 5 und 6) bald fortsetzen, heute geht leider nicht mehr Hier noch die Webplayer Version vom fertigen Projekt (Anhang) [server muss zuerst laufen]. Gute Nacht. Hier die Dateien: MPT2.html MPT2.unity3d
  24. 9 points
    Hey Leute, tut mir wirklich leid das hier zu fragen da es bestimmt schon einen ähnlichen Thread gibt, bzw. die Lösung bestimmt ganz einfach ist. Aber ich habe wirklich gesucht und nix gefunden. Ich will das meine GUI.Buttons ausgeblendet werden wenn man darauf geklickt hat. Dies habe ich mit einer if.Abfrage in der onGUI Function versucht. Aber es funktioniert nicht. Das GUI wird die gesamte Zeit angezeigt. Hier ist mein Code: (wenn playerStatus 1 ist beginnt der Spieler zu hüpfen... function OnGUI () { if (vars.GUIStatus == 1) //vars ist mein Speicherscript für static vars { if (GUI.Button(Rect(350, 275, 100, 50), "Start")) { vars.playerStatus = 1; } } if (vars.GUIStatus == 3) { GUI.Label (Rect(350, 275, 100, 50), "Dein Score beträgt: " + vars.score); if (GUI.Button (Rect(350, 375, 100, 50), "Noch Mal!")) { vars.GUIStatus = 1; } } }
  25. 9 points
    Hi, ich versuche seit einiger Zeit, in einem rundbasiertes Spiel (entwickelt in C#) während Berechnungen die GUI auszublenden zu lassen. Dazu verwende ich eine Variabele, welche den aktuellen Zustand des Spieles angibt (einfach eine "public static int gameStatus = 0" in der Klasse RUNTIME) und folgenden Code: void OnGUI() { switch (RUNTIME.gameStatus) { case 0: if (GUI.Button(new Rect(10, Screen.height - 65, 200, 60), "OK")) { RUNTIME.gameStatus = 1; MachWasWirklichLangeDauert(); RUNTIME.gameStatus = 0; } break; case 1: GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 100, 200, 200), "Please wait..."); break; [...] default: break; } } Der Code funktioniert allerdings leider nicht, wie muss ich ihn modifizieren, dass er funktionsfähig wird? Bereits im Voraus vielen Dank für Antworten! Moder

Announcements

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

Weiterleitung zum Entwickler "daubit"



×