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
    Hab mal eine Frage bezüglich Background Images. Ich will ein Menü bauen, dass ein Hintergrundbild hat. Das Backgroundimage kann ich beliebig skalieren. Hab nun erstmal eine 2048x2048 Grafik erstellt. Auf der Unity Seite konnte ich lesen, dass man einfach ein Würfel im Format 16:9 anlegt (16 Breite, 9 Höhe) und die Kamera drauf zeigen lässt. => Unity Tutorial: How to make a SplashScreen. 1. Sache: Die Grafik wird bei dem Format logischerweise gestaucht, hatte eine Grafik im Format 2048x 1152 angelegt und die draufgelegt damit es passt, beim Import MipMaps aus und NonPower auf ToNearest gestellt. Sieht gut aus und funktioniert alles. Mein Problem dabei ist, dass ich denke, dass ich das eher schlecht als recht so gelöst habe. 2. Sache: Ich muss Auflösungen im Verhältnis 16:9 und 16:10 unterstützen. Jetzt müsste ich eigentlich die Kamera weiter nach hinten schieben. Sollte ich nun ein Skript schreiben, welches die Auflösung ausließt und dann entscheidet wie der Würfel gezeichnet wird? Gibt es eine bessere Möglichkeit ein Hintergrundbild unter den von mir genannten Bedingungen (16:10, 16:9, scharf und optimiert) zu erstellen ? Viele Grüße, Tenshi Achja, ich verwende derzeit kein Skript zum Einbinden der Grafik, die wird nur auf den Würfel gezogen.
  6. 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
  7. 16 points
    Hallo zusammen ich hock jetzt schon paar Stunden daran und komm nicht mehr auf die Lösung ich versuchs nacher nochmal. Das Menü öffnen geht mit ESC und die anderen aber muss noch schließen hinbekommen. Wäre auch Dankbar für verbesserungen. using UnityEngine; using System.Collections; public class IngameMenu : MonoBehaviour { //Variabeln static string MenuState = "Main"; static bool MenuOpen = false; // Use this for initialization //Update funktion zu abfrage der Taste ESC für den Menü Aufruf und Schließen void Update () { if(Input.GetKeyDown(KeyCode.Escape)){ MenuOpen = !MenuOpen; //Sorgt dafür das das Menü immer beim aufruf von ESC im Mainmenü steht egal in welchem menu man zuletzt war MenuState = "Main"; } } //GUI mit unter GUIs void OnGUI () { if (MenuOpen == true){ if (MenuState == "Main") { Main (); } if (MenuState == "Grafik") { Grafik (); } if (MenuState == "Sound") { Sound (); } if (MenuState == "Tastaturbelegung") { Tastaturbelegung (); } if (MenuState == "Spiel_Verlassen") { Spiel_Verlassen (); } if (MenuState == "Zurueck_zum_Spiel") { Zurueck_zum_Spiel (); } } } //Main Menue static void Main () { GUILayout.BeginArea (new Rect(Screen.width /2 -50, Screen.height/2 -50, 200, 200)); GUILayout.Box ("Spielmenue"); //Menupunkt Grafik if (GUILayout.Button("Grafik")){ MenuState = "Grafik"; } //Menupunkt Sound if (GUILayout.Button("Sound")){ MenuState = "Sound"; } //Menupunkt Tastaturbelegung if (GUILayout.Button("Tastaturbelegung")){ MenuState = "Tastaturbelegung"; } //Spiel Beenden, geht nicht im debug Modus if (GUILayout.Button("Spiel Beenden")){ Application.Quit(); } //Spielemenü schließen if (GUILayout.Button("Zurueck zum Spiel")){ MenuState = "Zurueck_zum_Spiel"; } GUILayout.Label ("Version 0.2"); GUILayout.EndArea(); } //Erste Untermenü des Spielmenüs Grafik static void Grafik () { GUILayout.BeginArea (new Rect(Screen.width /2 -100, Screen.height/2 -200, 200, 400)); GUILayout.Box("Grafik"); //Button Grafik im Menü Option if (GUILayout.Button("Grafik")){ MenuState = ""; } // Zurück Button um ins Hauptmenü zu kommen if (GUILayout.Button("Zurueck")){ MenuState = "Main"; } GUILayout.EndArea(); } //Zweites Untermenü des Spielmenüs Sound static void Sound (){ GUILayout.BeginArea (new Rect(Screen.width /2 -100, Screen.height/2 -200, 200, 400)); GUILayout.Box("Sound"); // Zurück Button um ins Hauptmenü zu kommen if (GUILayout.Button("Zurueck")){ MenuState = "Main"; } GUILayout.EndArea(); } //Drittes Untermenü des Spielmenüs Tastaturbelegung static void Tastaturbelegung (){ GUILayout.BeginArea (new Rect(Screen.width /2 -100, Screen.height/2 -200, 200, 400)); GUILayout.Box("Tastaturbelegung"); // Zurück Button um ins Hauptmenü zu kommen if (GUILayout.Button("Zurueck")){ MenuState = "Main"; } GUILayout.EndArea(); } //Viertes Untermenü des Spielmenüs Spiel verlassen static void Spiel_Verlassen (){ GUILayout.BeginArea (new Rect(Screen.width /2 -100, Screen.height/2 -200, 200, 400)); GUILayout.Box("Spiel_Verlassen"); // Zurück Button um ins Hauptmenü zu kommen if (GUILayout.Button("Zurueck")){ MenuState = "Main"; } GUILayout.EndArea(); } //Fünftes Untermenü des Spielmenüs zurück zum Spiel static void Zurueck_zum_Spiel (){ MenuState = "Main"; MenuOpen = false; } }
  8. 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
  9. 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!
  10. 15 points
    Hey Leute, ich hab ein stilisiertes Vikingerschiff und eine kleine Testszene dazu gebastelt. Hier sind ein paar Screenshots aus Unity dazu, hoffe es gefällt. lg, holzi
  11. 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?
  12. 14 points
    In dieser Tutorial reihe wollen wir uns mithilfe von Unity und der Programmiersprache C# einen Space Shooter basteln. Dabei werde ich euch sanft mit Unity und C# vertraut machen. Deshalb ist diese Reihe sowohl für absolute Anfänger in Sachen Unity als auch im Programmieren geeignet. Im ersten Teil wollen wir unseren Grundstein legen, wir wollen unser Spieler Raumschiff mithilfe der Tastatur steuern. Das Spielerraumschiff stellen wir uns zuerst als einfachen Würfel zur Verfügung. In einem späteren Tutorial werden wir diesen Würfel durch komplexere Gebilde austauschen. Folgendes soll unser Raumschiff in der ersten Version können: - Beschleunigen wenn wir die Vorwärtstaste drücken - Negativ Beschleunigen wenn wir die Rückwärtstaste drücken - Nach links drehen wenn wir die Linkstaste drücken - Und nach rechts drehen wenn wir die Rechtstaste drücken Wir beginnen das ganze indem wir uns ein neues Projekt in Unity erstellen, komplett leer ohne Schnickschnack. Wählt dazu, innerhalb von Unity den Menüpunkt "File"->"New Project.." aus und wählt im darauf folgenden Dialog einen geeigneten Namen und Speicherort für euer Projekt aus. Selektiert zusätzlich die Option "3D" unter dem Punkt "Setup defaults to" im unteren Bereich des Dialoges. Klickt danach auf OK. Wir erstellen nun unser Raumschiff. Dazu wählen wir im Menü "GameObject"->"Create Other"->"Cube". Dies erzeugt uns unser erstes GameObject, einen Würfel mit dem Namen "Cube". Da der Name "Cube" wenig aussagt benennen wir ihn kurzerhand in "Spieler Raumschiff" um: Wählt dazu den Würfel in der Ansicht die "Hierarchy" genannt wird (ganz links) aus. Dann könnt ihr im Fenster welches "Inspector" genannt wird (ganz rechts) den Namen ändern indem ihr in das Textfeld mit dem Inhalt "Cube" klickt und dort den Text "Spieler Raumschiff" eintippt. Die Anführungszeichen bitte weglassen. Ihr könnt zum Umbenennen eines GameObjectes auch, nachdem ihr es in der "Hierarchy" Ansicht ausgewählt habt, die F2 Taste drücken und dort den Namen eintippen. Das Spieler Raumschiff ist nun noch etwas ungünstig positioniert und um das zu ändern wählen wir es wie bereits getan aus und ändern in der "Inspector" Ansicht die Position. Dies kann gemacht werden indem ihr unter "Position" alle 3 Werte (X, Y und Z) auf 0 ändert. Um auch die Kamera etwas passender zu positionieren und zu drehen wählen wir nun auch das "Main Camera" GameObject aus und ändern im Inspektor die Position auf: X = 0, Y = 6 und Z = -2 Sowie die Rotation auf X = 70 Dadurch schaut unsere Kamera auf unser Raumschiff. Speichert nun die Scene ab indem ihr Strg+S drückt. Unity wird euch darauf hin fragen wie die Scene heißen soll. Ich habe meine Scene "Scene0" genannt. Wärend der Entwicklung solltet ihr darauf achten immer wieder Strg+S zu drücken um eure Zwischenstände beim verändern der Scenen auch zu speichern. Nichts ist ärgerlicher als zuvor gemachte Änderungen wiederholen zu müssen. Weil Unity zB abgestürzt ist. Damit sind die Vorbereitungen abgeschlossen und wir können uns nun ein Script erstellen welches unser Raumschiff steuert. Dazu klicken wir mit der rechten Maustaste in den "Assets" Bereich (ganz unten) und wählen den Kontextmenüpunkt "Create"->"C# Script" Anschließend wählen wir als Namen für dieses Script "PlayerControler". "PlayerControler" deshalb weil dies unser Script ist welches das Spieler Schiff kontrolliert. Wir werden in dieser TutorialReihe englische Bezeichner verwenden sofern es sich um das Programmieren handelt. In Unity ist der Name des Scriptes gleichbedeutend mit dem Klassennamen. Dazu aber gleich mehr. Um nun dieses Script zu editieren könnt ihr es doppelt anklicken. Darauf hin sollte sich ein Editor öffnen und ihr seht folgendes: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } } Die ersten beiden Zeilen (die die mit using beginnen) helfen dabei uns einige Grundfunktionen zu geben die wir in unserem Script verwenden können. using UnityEngine gibt uns alle Funktionen die direkt mit Unity zu tun haben. using System.Collections gibt uns einige Hilfsmittel um Ansammlungen von Daten zu verwalten, zB eine Reihe von Werten die wachsen kann und aus der Elemente entfernt werden können. Ausserdem stellt es uns etwas bereit was wir benötigen sobald es um Coroutinen geht. Die nächste Zeile definiert den Namen unseres Scriptes (PlayerController) und gibt an was genau es sein soll (class, eine Klasse) und von wem es einige Grundfunktionen bekommen soll (MonoBehaviour). Jedes Script welches wir auf GameObjects legen können leitet von MonoBehaviour ab. "leitet" bedeutet hier dass unser Script zugleich selbst ein MonoBehaviour ist und damit einige Grundfunktionen beherbergt die dieses MonoBehaviour selbst bereit stellt. Stellt euch einfach ein Auto vor. Ein Auto leitet zB von Fahrzeug ab und kann dadurch gefahren werden. Die Zeile mit void Start() bedeutet dass wir hier eine Methode definieren die Start heißt. Im Bezug auf Unity bedeutet dies dass diese Methode automatisch von Unity aufgerufen wird wenn das Script auf ein GameObject liegt und die Scene betritt. Innerhalb des Bereiches der durch { und } eingegrenzt ist können wir schreiben was unser Script machen soll wenn es startet. Der durch { und } eingegrenzte Bereich wird Scope genannt. Die Zeile Update ist ähnlich wie Start, nur wird diese Update Methode nicht aufgerufen wenn unser Script die Welt betritt sondern in jedem Framedurchgang wenn unser GameObject und Script aktiv sind. Frame bedeutet der Moment in dem ein Bild fertig gezeichnet wurde. Je schneller ein Computer desto mehr Frames in einer Sekunde können erreicht werden. In der Update Methode können wir überprüfen ob der Spieler eine Taste gedrückt hat und dies werden wir nun auch tun. Mithilfe der Input Klasse von Unity können wir abfragen ob eine Taste gedrückt wurde, wir erweitern deshalb die Update Methode um folgendes: if (Input.GetKey(KeyCode.UpArrow)) { print("Power!"); } Denkt daran: Schreibt das ganze zwischen { und } der Update Methode. Das if ist ein Schlüsselwort von C# welches besagt dass hier eine Abfrage statt finden soll und das innerhalb der Klammern ist die Bedingung die dazu erfüllt sein muss damit der darauf folgende Bereich (Scope) innerhalb der { und } Zeichen ausgeführt wird. Input gibt eine Klasse an welche die GetKey Methode bereit stellt. Die GetKey Methode benötigt einen Parameter der besagt welche Taste geprüft werden soll und dies definieren wir mit KeyCode.UpArrow. Input.GetKey liefert einen bool Wert zurück. bool bedeutet dass es 2 verschiedene Werte gibt, entweder true, was wahr oder ja bedeutet, oder false, welches unwahr, nein oder falsch bedeutet. Bedingungen erwarten immer ein bool welches sie auswerten können. Nur wenn dieses bool true ist wird die Ausführung des Scopes vom if gestartet. Die print Methode gibt den String aus der ihr als Parameter übergeben wurde. Ein String ist eine Zeichenkette, ein Text. Wenn wir direkt einen Text angeben wollen so müssen wir ihn innerhalb der Anführungszeichen schreiben. Dann speichern wir das ganze und wechseln zurück zu Unity. Unity wird nun dieses von uns gespeicherte Script kompilieren (in Maschinencode umwandeln) und uns auf eventuelle Fehler hinweisen. Wenn alles erfolgreich kompiliert wurde können wir unser Script auf unser Spieler Raumschiff zuweisen. Dazu wählt das Spieler Raumschiff aus und klickt im unteren Inspektor Bereich (möglicherweise müsst ihr nach unten scrollen) auf den Knopf "Add Component" wählt dann "Scripts"->"Player Controller" aus. Unser Script sollte nun unserem Raumschiff zugewiesen worden sein. Das zuweisen ist auch möglich indem man das Script aus dem Asset Bereich auf das Raumschiff schiebt. Jetzt können wir auf den Play Knopf drücken (das Dreieck im oberen Bereich von Unity). Wenn wir nun die nach Oben Taste drücken sollte im unteren linken Bereich von Unity der Text "Power!" stehen. Glückwunsch du hast soeben dein erstes Script geschrieben und es einem GameObjekt zugewiesen und siehst nun noch wie es agiert. Das Raumschiff bewegt sich aber dennoch nicht, was ziemlich öde ist und schleunigst geändert werden sollte. Unser Raumschiff benötigt dazu die Möglichkeit physikalisch bewegt zu werden, dies geht indem wir dem Raumschiff die Rigidbody Komponente (Komponenten sind Scripte!) zuweisen. Dazu klicken wir wieder im Inspektor den Knopf "Add Component" aus und benutzen "Physics"->"Rigidbody". Im Inspektor erscheint nun ein zusätzlicher Bereich mit dem Titel "Rigidbody". Hier müssen wir auch noch einige Änderungen vornehmen, zB müssen wir dafür sorgen dass unser Raumschiff nicht nach unten fällt. Dazu deaktivieren wir die Checkbox "Use Gravity". Nun können wir mithilfe des Rigidbodies unser Raumschiff steuern. Dazu gehen wir wieder in unser PlayerController Script und entfernen die Zeile mit dem print("Power!") und ersetzen die Zeile durch die Anweisung welche unser Raumschiff beschleunigt: rigidbody.AddForce(transform.forward); rigidbody ist eine Abkürzung die es uns erlaubt ohne extra Code direkt auf unser Rigidbody zuzugreifen, diese Abkürzung gibt es in ähnlicher Form auch für andere Komponenten, zB der Transform Komponente (transform). AddForce ist eine Methode der Rigidbody Komponente die wir aufrufen können um etwas Kraft auf das rigidbody auszuüben. Da wir es vorwärts, entlang der Blickrichtung des Objektes beschleunigen wollen, benutzen wir transform.forward als Parameter. transform ist die Abkürzung auf die Transform Komponente welche Position, Richtung, Skalierung des GameObjektes beinhaltet. Und forward ist ein Richtungsvector der dorthin zeigt wo das GameObjekt "hin blickt". Wenn wir nun das Script speichern und uns das Resultat in Unity ansehen (Play drücken) dann können wir sehen wie sich unser Raumschiff bewegt wenn wir die nach Oben Taste drücken. Leider ist diese Beschleunigung stark von der Anzahl der Frames pro Sekunde abhängig. Was bedeutet das auf schnelleren Computer die Beschleunigung höher ist als auf langsameren Computern. Ausserdem können wir die Beschleunigung nicht genauer regeln und entspricht immer der Länge des Richtungsvectors. Um das zu ändern erweitern wir unser Script wieder ein wenig. Als aller erstes machen wir die Beschleunigung unabhängig von der Anzahl der Frames pro Sekunde: aus rigidbody.AddForce(transform.forward); wird deswegen: rigidbody.AddForce(transform.forward * Time.deltaTime); Time ist die Klasse von Unity die einiges an Operationen und Variablen bereit stellt die etwas mit der Zeit zu tun haben. deltaTime zB ist die Zeit in Sekunden die der letzte Frame benötigte. Indem wir transform.forward mit diesem Wert multiplizieren bleibt unsere Beschleunigung auch dann konstant wenn wir einen sehr langsamen oder sehr schnellen Computer verwenden. Wenn wir nun das ganze in Unity ausprobieren werden wir allerdings ein unangenehmes Wunder erleben. Unser Würfel bewegt nur sehr langsam. Deswegen werden wir nur die Kraft erhöhen. Dazu erweitern wir unser Script indem wir innerhalb der Klassendefinition über void Start folgendes schreiben: public float Acceleration = 10.0f; public besagt dass die float Variable öffentlich erreichbar ist, also jeder der unser Script zu fassen bekommt diesen Wert lesen und/oder verändern kann. Dies besagt zB auch dass dieser Wert im Inspektor von Unity angepasst werden kann. Acceleration ist der Name unserer Variable und float der Datentyp. float gibt an dass wir einen Zahlentyp wollen der Kommawerte darstellen kann. Ein Zahlentyp der nur ganze Zahlen darstellen kann wäre zB int. Wir verwenden unsere Acceleration Variable nun indem wir unser Zeile in der wir Kraft auf das Rigidbody wirken lassen erweitern und zwar: rigidbody.AddForce(transform.forward * Time.deltaTime * Acceleration); Wir multiplizieren also unsere Frame unabhängige Kraft mit unserer Variable. Dies ermöglicht es uns nun anzugeben wie Stark die Beschleunigung ist. Wenn wir nun wieder das ganze innerhalb von Unity testen sehen wir dass unser Würfel sich schneller bewegt als zuvor. Wir können diesen Wert anpassen indem wir im Inspektor nach unsererm Script Player Controller suchen und dort den Eintrag mit dem Namen "Acceleration" ändern. Unser Script sollte aktuell wie folgt aussehen: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { public float Acceleration = 10.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (Input.GetKey(KeyCode.UpArrow)) { rigidbody.AddForce(transform.forward * Time.deltaTime * Acceleration); } } } Da wir nun schon das Beschleunigen haben werden wir nun noch das Abbremsen hinzufügen. Das ganze ist ziemlich identisch zum Beschleunigen und ist eine Ideale Übung zum selber probieren des bereits erlernten. Wenn ihr euch ausgetobt habt oder einfach nur weiter machen wollt, hier ist der Code welcher auch die negative Beschleunigung beinhaltet: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { public float Acceleration = 10.0f; public float Deacceleration = 10.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (Input.GetKey(KeyCode.UpArrow)) { rigidbody.AddForce(transform.forward * Time.deltaTime * Acceleration); } if (Input.GetKey(KeyCode.DownArrow)) { rigidbody.AddForce(-transform.forward * Time.deltaTime * Deacceleration); } } } Was nun noch fehlt ist das Drehen unseres Raumschiffes. Dies funktioniert auf leicht ähnliche Weise wie das Beschleunigen und Abbremsen. Deswegen legen wir uns wieder eine Variable an welche angibt wie schnell die Drehung statt finden soll. public float RotationAcceleration = 30.0f; Als Nächstes müssen wir nun noch auf den Tastendruck reagieren der das Drehen veranlassen soll, ich werde mit dem nach rechts drehen anfangen was relativ einfach auf eine links Drehung erweitert werden kann: if (Input.GetKey(KeyCode.RightArrow)) { rigidbody.AddTorque(0, Time.deltaTime * RotationAcceleration, 0); } So wie AddForce fügt AddTorque etwas an unser Rigidbody. und zwar ein Drehmoment. Wir beschleunigen das Drehmoment unseres Rigidbdies um die Y Achse. Wenn wir das Ganze nun in Unity testen können wir unseren Cube schon vor der Kamera bewegen. Nur fühlt es sich momentan so an als würden wir auf dem Eis laufen. Dies liegt daran dass wir direkt Kräfte auf unseren Würfel anwenden, was ansich realistisch ist, aber leider das ganze schwieriger Steuerbar macht. Die Lösung dazu? Wir steuern durch das Beschleunigen oder Abbremsen die Kraft die unser Antrieb pro Frame automatisch auf den Würfel anwenden soll. Dazu fügen wir eine Reihe weiterer Variablen ein die dafür zuständig sind die maximale und minimale Kraft zu regeln als auch die momentane Kraft die unser Antrieb generieren soll zu speichern: public float MaxForce = 1000.0f; public float MinForce = -200.0f; public float CurrentForce = 0.0f; Nachdem wir das nun haben müssen wir natürlich auch die Handhabung der nach Oben und Unten Tasten ändern. Wir ändern hierbei jeweils die AddForce Zeilen indem wir den Frame unabhängigen Acceleration Wert zu CurrentForce hinzu addieren und den Deacceleration Wert abziehen. if (Input.GetKey(KeyCode.UpArrow)) { CurrentForce += Time.deltaTime * Acceleration; } if (Input.GetKey(KeyCode.DownArrow)) { CurrentForce -= Time.deltaTime * Deacceleration; } += bedeutet hierbei dass der Wert rechts von += zu den Wert links vom += addiert werden soll. Das ganze ist gleich zu a = a + b -= bedeutet dass der Wert abgezogen anstatt hinzugefügt wird. Jetzt haben wir die CurrentForce zwar angepasst aber noch kann sie sowohl zu hoch als zu niedrig werden und verwenden tun wir diesen Wert ebenso wenig. Daher grenzen wir CurrentForce zuerst ein. Dies geht indem wir die Clamp Methode der Mathf Klasse von Unity verwenden. Schreibe folgendes ans Ende der Update Methode: CurrentForce = Mathf.Clamp(CurrentForce, MinForce, MaxForce); Mathf.Clamp begrenzt den Wert im ersten Parameter innerhalb von den Wert des zweiten und dritten Parameters und gibt das Resultat wieder zurück. Damit ist sicher gestellt dass die CurrentForce niemals größer oder kleiner unserer Grenzwerte wird. Nun müssen wir CurrentForce nur noch irgendwie auf unser Raumschiff angewendet bekommen. Hierfür benutzen wir einfach unser rigidbody.AddForce wieder. Wir multiplizieren CurrentForce mit der Zeit die der letzte Frame zum ausführen brauchte und der Richtung unseres Raumschiffes: rigidbody.AddForce(CurrentForce * Time.deltaTime * transform.forward); Der aktuelle Code des PlayerController Scriptes sollte nun so aussehen: using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { public float Acceleration = 100.0f; public float Deacceleration = 100.0f; public float RotationAcceleration = 30.0f; public float MaxForce = 1000.0f; public float MinForce = -200.0f; public float CurrentForce = 0.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (Input.GetKey(KeyCode.UpArrow)) { CurrentForce += Time.deltaTime * Acceleration; } if (Input.GetKey(KeyCode.DownArrow)) { CurrentForce -= Time.deltaTime * Deacceleration; } if (Input.GetKey(KeyCode.RightArrow)) { rigidbody.AddTorque(0, Time.deltaTime * RotationAcceleration, 0); } if (Input.GetKey(KeyCode.LeftArrow)) { rigidbody.AddTorque(0, -Time.deltaTime * RotationAcceleration, 0); } CurrentForce = Mathf.Clamp(CurrentForce, MinForce, MaxForce); rigidbody.AddForce(CurrentForce * Time.deltaTime * transform.forward); } } Das Raumschiff beschleunigt nun immer mit der aktuellen Antriebskraft in die aktuelle Schiffsrichtung. Das fühlt sich nun schon alles viel besser an als vorher, die Steuerung ist leichter zu beherrschen, aber etwas fehlt noch.. Spielt ein wenig mit den Werten Im Inspektor der Rigidbody und PlayerController Komponenten herum um ein leichtes Gefühl dafür zu bekommen wie sich jeder Wert auswirkt. Gute Werte sind zB folgende: PlayerController: Acceleration 100 Deacceleration 50 Rotation Acceleration 100 Max Force 100 Min Force -20 Rigidbody: Mass 1 Drag 0 Angular Drag 10 Das war es für den ersten Teil der Tutorial Reihe. Im nächsten Teil werden wir die Kamera so umbauen dass diese immer auf das Spieler Raumschiff gerichtet ist. Feedback ist gern gesehen. Teil2: http://forum.unity-c...__fromsearch__1
  13. 14 points
    Hallo Leute ich möchte gerne ein Inventar erstellen. Man sollte Objekte im Inventar verschieben und wegwerfen können. Ales was ich weiß ist das ich ganz viele GUI Buttons erzeugen muss. Leider habe keine Ahnung wie ich die Funktion zum verschieben und wegwerfen und so machen soll. Hat jemand ein Denkanstoß für mich oder eine gute Idee dazu???
  14. 14 points
    Hey, meine Frage ist die: Wie kriege ich es hin, dass sich die GUI die ich erstellt habe an der Gleichen Position bleibt egal bei welcher Auflösung? Also das sozusagen die x, y, height, width die gleichen werte haben bei allen Auflösungen, so das sich nichts Verschiebt wenn man eine andere Auflösung einstellt. Ich hoffe ich könnt meine Frage verstehe Mfg
  15. 14 points
    Hi, Ich bin neu hier und noch nicht so sehr geübt im Programmieren. Ich wollte mal Fragen wie ich eine GUI.Box immer, egal wie groß das fenster ist, den ganzen hintergrund ausfüllt. Also von links oben bis ganz rechts unten ist. MFG Moroks
  16. 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?
  17. 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"
  18. 13 points
    Gerade unerfahrene Leute haben starke Bedenken, wenn es um die Performance in Unity geht. Sie haben aus unterschiedlichen Quellen gehört, dass Dies oder Jenes schlecht sein soll und jetzt gehen sie total ängstlich an das Projekt heran. Denen möchte ich aber sagen: Macht euch nicht verrückt! Denn wie immer ist alles eine Frage von der Menge an leistungsfressenden Dingen, dem gewünschten Resultat und der Hardware, auf der das Spiel laufen soll. Viele Dinge beziehen sich auf Hardware, die kaum noch genutzt wird, wie z.B. das iPad1, welches nicht mehr als 120MB Ram für das Spiel zur Verfügung gestellt hatte. Oder aber es bezog sich auf veraltete Techniken bei denen Unity schon längst performatere Dinge gebastelt hat, wie die unterschiedlichen Lighting-Arten. Ich will mal versuchen, die einzelnen Bereiche abzuarbeiten. Das Grafische: Alles was ich sehen kann kostet Leistung. Mal mehr, mal weniger! Je mehr Realismus ich in ein Spiel rein bringen will umso mehr Leistung wird verbraucht. Denn jedes Poygon, jedes Licht, jede Schattenberechnung und jedes Material verbraucht etwas. Diese Dinge belasten meist die Grafikkarte, denn die ist es, die das ja anzeigen und vorher berechnen muss. Habt ihr ein Spiel mit üppiger Szenerie und schaut euch zur Laufzeit mal den Profiler an, dann seht ihr, dass der Grafikbereich die meiste Leistung schluckt. Unity ist von sich aus schon bemüht, sparsam zu sein und kann gleichartige Dinge zusammen fassen, wenn sie z.B. das gleiche Material haben und auch vom selben Licht bestrahlt werden. Das reduziert die Drawcalls, die einen Großteil der Performance ausmachen. Man hat die Möglichkeit Beleuchtungen und Verschattung vor zu berechnen und die Ausleuchtung einer Szene zu backen. Das reduziert ganz massiv die Last auf der Grafikkarte, kostet aber Speicherplatz und natürlich geht die Dynamik der Beleuchtung dadurch flöten. Aber was bringt es, wenn man genau diese Dynamik haben will. Genau, es bringt nichts! Mit Shadern sieht es genauso aus. Ein einfacher Shader mit nur einer Textur ist recht sparsam. Er gibt aber auch nicht viel her. Schöner ist er, wenn er glänzen kann, transparent ist, erhaben ist und eben realistisch aussieht. Will ich das haben muss ich ihn auch einsetzen. Die Geometrie eines Körpers sollte natürlich so sparsam wie möglich aufgebaut sein. Die Zeit, wo ein Oberarm aus 12 Polygonen besteht, ist aber längst vorbei. Ich kann zwar vieles mit einer NormalMap simulieren, aber wenn die Ränder des Polygons zu eckig sind, sieht man das und es wirkt einfach nicht. Also auch da muss ich mich den Qualitätsansprüchen stellen und mehr Polygone nutzen. Ich kann sogar DirectX11 einsetzen und aus wenigen Polygonen eine Million Polygone machen. Ja, aber eben nur auf einer Grafikkarte, die das auch kann. Egal, wollt ihr die Technik einsetzen dann funktioniert es eben auch nur auf einer Hardware, die das auch kann. Lasst euch also nicht zu stark einschränken. Ihr werdet natürlich irgendwann merken, dass das Ganze zu viel geworden ist und die Framerate einbricht. Meist lässt sich das aber mit etwas geänderter Beleuchtung, LOD oder anderen Dingen schon wieder beheben. Da aber niemand genau sagen kann, wo die Grenzen liegen werden, bringt es auch nichts, wenn man vorher seitenweise darüber diskutiert. Die Physik: Die physikalischen Berechnungen kosten Leistung, die der Prozessor bringen muss. Und es ist klar, dass viele Berechnungen auch viel Leistung kosten. Trotzdem ist das jetzt kaum ein Bereich, wo man "extrem" sparen muss. Unity kann ohne weiteres mehrere 100 Objekte gleichzeitig physikalisch berechnen. Einfach schauen, dass nur die Objekte einen Rigidbody bekommen, die auch einen haben müssen weil sie der Schwerkraft oder anderen Forces folgen sollen und/oder Kollisionen auswerten sollen. Collider so einfach wie möglich halten und lieber einen Box-Collider mehr nehmen, als einen Mesh Collider zu nutzen. Meist muss es nämlich gar nicht ganz genau sein. Merkt eh keiner. Der Code: Ja, hier kann und muss man aufpassen. Denn wenige Kleinigkeiten können das System ganz schön verlangsamen. Aber macht euch auch hier nicht verrückt. Denn auch hier ist alles eine Frage der Dosis. Ein paar extreme Dinge will ich aber mal aufzählen. Als Faustregel gilt, dass jede Unity-eigene Funktion, die im Script steht, auch durchlaufen wird. Egal, ob innerhalb dieser Funktion etwas drin steht oder nicht. Die OnGUI Funktion ist die hungrigste, weswegen man so wenig Scripte wie möglich mit dieser Funktion bestücken sollte. Legt also nur die Funktionen an, die auch wirklich nötig sind, auch wenn das einsparpotential hier (außer bei der OnGUI) nur gering ist. Immer wenn aus einem Script heraus ein anderes Objekt oder dessen Komponente gesucht wird, kostet es Performance. Es hilft aber nichts. Manchmal muss man einfach nach anderen Objekten oder Komponenten suchen. Um jedoch so wenig Leistung wie möglich zu verbrauchen, sollte man nur einmal danach suchen und einfach die Referenz des Gesuchten in eine Variable speichern. Manche Befehle sind recht hungrig, wie z.B. die Entfernungsmessung über Vector3.Distance. Da sollte man sich überlegen, ob denn wirklich in jedem Frame die Entfernung gemessen werden muss oder reicht es vielleicht auch, wenn es nur wenige Male pro Sekunde passiert. So eine Messung würde ja in jedem Frame eine gewisse Grundlast verursachen, die nicht unbedingt sein muss. Und gerade wenn viele Objekte viele Entfernungsmessungen machen, ist es sinnvoll das Ganze in der Zeit aufzuteilen um die Grundlast zu verringern. So ist das natürlich auch mit komplexen Berechnungen die in einer Schleife ausgeführt werden. SendMessage ist ein teurer Befehl, der an ein gewisses Objekt und an all seine Komponenten etwas sendet. Egal ob die Komponente damit etwas anfangen kann, oder nicht. Diesen Befehl sollte man wirklich nur sparsam nutzen. Will ich einem anderen Script jedes Frame etwas mitteilen, dann ist dieser Befehl dafür total ungeeignet. Für ein einmaliges oder seltenes Senden ist er aber voll ok. Für das ständige Übergeben von Informationen bietet sich an, die Komponente, also das andere Script, vorher zu suchen und als Referenz in eine Variable zu speichern. Jetzt kann ich auf alle Public Variablen oder Funktionen des anderen Scripts zugreifen und sie aufrufen oder manipulieren. Das kostet nicht oder kaum mehr, als wenn es eine Variable des eigenen Scripts wäre. Alles das, was ich da aufgezählt habe macht sich erst ab einer gewissen Menge bemerkbar. Je schwächer die Hardware ist, desto früher merkt man es. Nur weil etwas viel Performance kostet, müsst ihr nicht darauf verzichten. Ihr solltet aber weise damit umgehen. Nur weil ich etwas jedes Frame tue, muss das nicht schlecht sein. Es kommt halt immer darauf an, was ich da tue und wie viele Objekte das gleichzeitig machen. Macht euch vorher Gedanken, was ihr für euer Spiel alles braucht und wie ihr das am besten lösen könnt. Aber lasst euch durch diese Planung nicht blockieren. Fangt einfach an. Vieles ist im Nachhinein leicht änderbar. Spiele, die auf einem PC schön aussehen sollen und können, können nicht unbedingt genauso für ein Handy übernommen werden. Es macht aber nicht unbedingt Sinn, sich auf den kleinsten gemeinsamen Nenner zu einigen. Manchmal muss man einfach mehrere Versionen für ein und das selbe Spiel bauen, weil die Qualitysettings nicht ausreichen werden. Fazit: Es ist also alles gar nicht so schlimm!
  19. 13 points
    Jedes Mal, wenn jemand einen Thread nach Schema X eröffnet, und daraufhin einige Aktive hier sich bemühen, die gleiche Antwort Y darauf zu geben, sehe ich Handlungsbedarf. Eigentlich sollte es in solchen Fällen reichen, auf einen Text zu verweisen, der jene Frage beantwortet, anstatt Zeit dafür zu opfern, denselben Sachverhalt erneut in eine passende Form zu gießen. Heute habe ich die, wie ich glaube, richtigen Worte zum diesem Thema gefunden: "Hat jemand ein Tutorial für [...]?" Entschuldige die direkte Formulierung, wenn du das hier liest, weil du gerade dieselbe Frage gestellt hast, aber: Nein. Denn dafür sind Tutorials nicht da. Ich erkläre das mal. Spieleentwicklung ist ein künstlerischer Prozess - auch das Programmieren! Auch wenn es eine sehr handwerkliche Kunst ist, so ist es dennoch eine Kunst. Denn man entwickelt ein Design dafür, wie das Programm sich verhält. Das kann man gerne mit der Malerei vergleichen. "Ich hätte gerne ein Spiel, das XY tut" ist nicht weit weg von "ich hätte gerne ein Bild, das in dem Betrachter Gefühl AB hervorruft". Jetzt denke man an Tutorials in der Malerei. "So male ich eine Hand." "So sieht ein Asiate aus." "So malt man die Mona Lisa." Man kann diese Tutorials jetzt durcharbeiten, und wenn man selbst und das Tutorial gut genug sind, so hat man am Ende... eine schöne Kopie der Mona Lisa. Ob das jemand sehen will? Eher nicht. Denn die Mona Lisa gibt's schon. Im Zweifelsfall sehr viel schöner. "Aber die Metapher ist ja blöd. Mein Spiel wird ganz andere Modelle kriegen. Dass mein Gameplay genau wie im Tutorial ist, stört mich nicht." Wenn du ein Tutorial findest, dessen Ergebnis dir genügt, okay. Aber selber keines zu finden und dann andere auf die Suche zu schicken, ist etwas anderes. Tutorials sind nämlich, wie eingangs erwähnt, nicht dafür da, dich dabei zu leiten, dein Spiel zu implementieren. Glaubst du nicht? Ist aber so! Es gibt nämlich genau zwei Arten von Tutorials: Grundlagentutorials Spezifische Hilfstutorials Zweitere erklären die Lösungen zu bestimmten, häufig Auftretenden Problemen, die Leute immer wieder haben, egal, was sie tun. Beispiel: "Hände malen" und Raycasts. Erstere zeigen dir, wie (in diesem Fall) Spieleentwicklung im Allgemeinen funktioniert. Das kann relativ trocken, dafür zielgerichtet funktionieren (Beispiel: Meine Scripting-Tutorials), oder auf der anderen Seite anhand eines konkreten Beispiels (siehe Marks Space Shoot em Up). Letztere Kategorie ist oft anschaulicher, da man ohne weiteres einen Kontext mitbekommt ("Aha: X kann ich machen, wenn ich Y haben will"). Das Problem: Viele Leute denken "Wenn es ein Tutorial für ein Space Shoot em Up gibt, warum nicht auch für ein FPS?". Die Antwort ist einfach: Marks Tutorial ist nicht dafür da, zu lernen, wie man ein Space Shoot em Up macht. Es ist dafür da, zu lernen, wie man überhaupt ein Spiel entwickelt - und das anhand eines Space Shoot em Ups. Und das ist so ziemlich der Kern dessen, was ich sagen will. Wenn du also ein FPS bauen willst, aber nicht weißt, wie - suche dir gerne ein FPS-Tutorial. Dieses wird dir aber nicht helfen, wenn du es einfach nur durcharbeitest, und dabei ignorierst, was das eigentliche Lernziel sein soll - und das merkst du dann in dem Moment, in dem du etwas minimal anders haben willst, als es im Tutorial beschrieben wird. Wenn du dann kein Tutorial findest, das genau das beschreibt, was du brauchst - dann ist das so! Was du nämlich brauchst, sind Kenntnisse über Spieleentwicklung im Allgemeinen. Die kriegst du z.B. über Marks Tutorial. Dann hast du am Ende ein Space Shoot em Up gebaut, das dich vielleicht nicht interessiert. Aber darum ging es ja nicht. Du kannst jetzt ein Stück weit besser mit Unity arbeiten. Als nächstes baust du einen Super Mario-Klon, der dich auch nicht sonderlich interessiert. Und so weiter. Und irgendwann bist du dann soweit und baust dein Traumspiel. Ein Tutorial kann dir das nicht abnehmen. Das kannst nur du selbst machen. Zu lernen, wie das überhaupt geht, dabei helfen dir Tutorials. Das Kriterium bei der Tutorial-Wahl muss dann aber die Qualität sein. Nicht, ob am Ende ein FPS dabei heraus kommt.
  20. 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!
  21. 13 points
    Hi Timtrucker, die Idee ist nicht schlecht. Präfixe könnten vielleicht klappen, ohne irgendein Modul zu installieren. Warten wir mal ab, was die restliche Community sagt und dann schauen wir mal. Viele Grüße
  22. 13 points
    Titel sagt alles sobald das spiel geöffnet ist soll ein Text erscheinen. der text soll aber nur für 10s, also nicht dauerhaft da sein weiß jemand wie das funktioniert? danke im vorraus
  23. 13 points
    Hallo zusammen, und zwar hab ich folgendens Problem mit NGUI. Das ist mein Main_Menu script den hab ich auf das Panel gezogen in der UI dann hab ich unter Button "Neues Spiel" den UI Button Messanger hinzugefügt. Desweiteren hab ich das Panel in den Target gezogen und Funkiton Name OnNeues_Spiel eingegeben. Ebenfalls sind die Maincameras aus beiden Scenen gelöscht. So wenn ich jetzt Menü starte und auf neues Spiel klicke komm die andere szene aber die alte bleibt sichtbar im hintergrund. Hat jemand vielleicht eine Idee wie man das beheben kann using UnityEngine; using System.Collections; public class Main_Menu : MonoBehaviour{ void OnNeues_Spiel () { Application.LoadLevel("Untermenu neues Spiel"); } }
  24. 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
  25. 12 points
    Jetzt endlich habe ich etwas Zeit gefunden um eine neue Tutorialreihe zu starten. Es geht diesmal um das Animieren mit Mecanim. Worauf muss beim importierten Character geachtet werden und wie füge ich die erste Animation hinzu. Hier ist der erste Teil der neuen Reihe zu sehen: Weitere Teile werden folgen, jedoch weiss ich noch nicht wann.
  26. 12 points
    Hallo zusammen, heute Nacht wird ein Serverwechsel stattfinden. Grund dafür ist folgender: wie ihr sicherlich wisst, bin ich die letzten Jahre sehr inaktiv gewesen. Deshalb habe ich mich dazu entschlossen, dass Projekt einem sehr guten Freund, dem Roman Engel, zu übergeben. Heute Nacht wird der Server gewechselt, es dürfte eigentlich keine Komplikationen geben, aber von 2 bis 6 Uhr sind wir definitiv nicht erreichbar. Danach müssen ggf. noch mal ein paar Pfade angepasst werden, aber das hält sich im Rahmen. Für euch wird sich nichts verändern, ich werde weiterhin im Hintergrund mitwirken. Ihr seht höchstens mal einen neuen Administratoren hier im Forum posten. Aber vor allem wird eines passieren: Das Portal wird wieder mit neuen, frischen Inhalten gefüllt! Ich hoffe, ihr seid und bleibt mit Unity Insider zufrieden und wünsche euch die besten Grüße, Lars
  27. 12 points
    Hallo Community, Ich schreibe diesen Thread um mich einfach mal bei euch für die großartige Unterstützung und Hilfe zu bedanken. Ich habe selten so eine freundliche und hilfsbereite Community mit so vielen fähigen, netten Menschen erlebt. Ich weiß nicht ob es einen ähnlichen Thread bereits gibt, aber ich möchte an dieser Stelle einfach mal Danke sagen. Ich habe mich als totaler Anfänger vor 1 1/2 Jahren hier angemeldet und extrem viel dazu gelernt. Ich denke ein Forum wie dieses kann beim Lernprozess sehr helfen, da es einfach Dinge gibt, die man mit den Jahren als Entwickler erst lernt. Und solche Dinge sollten meiner Meinung nach weitergegeben werden, wie es hier geschieht. Mit Unity habe ich es gelernt Spiele zu programmieren und erste Projekte fertiggestellt. Auch wenn ich derzeit mehr mit Goo Create und der Goo Engine arbeite, hat mir Unity viel auf meinem Weg mitgeben können. Dadurch habe ich beispielsweise im Januar diesen Jahres bei der Mozilla and Goo Technologies Game Creator Challenge 1000$ gewonnen. Und ich denke, daran habt ihr alle ebenfalls einen Anteil, da Unity Insider mein Interesse in Programmierung, und Spielentwicklung geweckt hat und wie bereits erwähnt sowohl Motivation als auch technisches Wissen gespendet hat. Besonders möchte ich hier auch offiziell Sascha und Stephan Rauh (damuddamc) danken, die mich bei meiner Belegarbeit dieses Jahr unterstütz haben. Sascha stand mir als Außenbetreuer zur Seite und Stephans A* Tutorials haben mir ebenfalls sehr geholfen. Abschließend bleibt mir nur zu sagen, dass ich auf weitere gute Unterstützung, gegenseitige Hilfe und natürlich viel Spaß hoffe! Ich denke, ich kann im Namen vieler anderer Mitglieder hier sagen: DANKE!
  28. 12 points
    Hallo Leute, kann mir jemand sagen wie ich es hinbekomme das eine GUI.Box (oder allgmein GUI objekte) nach einer Zeit verschwinden ? ich habe in der scripting referenz schon einiges mit yield WaitForSeconds gefunden und viel herumprobiert allerdings klappt das bei mir nicht so recht ich hoffe ihr könnt mir helfen , danke im vorraus!
  29. 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?
  30. 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
  31. 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
  32. 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.
  33. 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?
  34. 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?
  35. 11 points
    Hallo miteinander, wird mal wieder Zeit für ein neues Tutorial! Das Thema heute: Wie deklariere ich meine Variable? Wer noch nicht so viel programmiert hat, hört manchmal in einem Tutorial von einer Lösung für sein Problem, versteht diese nicht ganz, probiert es aus und: Tadaa! Es funktioniert. Ähnlich wie bei php gibt es aber auch für Unity eine Menge motivierter Menschen, die freundlicherweise nach ihren ersten eigenen Erfolgen gleich mit anderen Teilen, wie sie es geschafft haben. Entsprechend wird das "warum" oft nicht weitergegeben, oder noch schlimmer: Es ist nur gefährliches Halbwissen. Dann bekommt man das nächste Problem, findet keine Lösung und besucht z.B. dieses Forum. Und da kommt's: Wie, deine Variable ist static?? Das ist ja großer Humbug! Heute will ich möglichst knapp ein paar wichtige Modifikatoren auflisten, die Variablen in C# haben können. Kleine Anmerkung: Dieses Tutorial wird alles andere als umfassend, da es für (Fast-noch-)Anfänger gedacht ist. Außerdem: Es geht um Objekt- und Klassenvariablen, also nicht die Variablen, die man in einer Methode definiert. Zuerst einmal werde ich auf allgemeines Programmieren eingehen. Die Bedeutung dieser Begriffe ist in C# immer so, wird aber in Unity erweitert. C# allgemein Variablentyp und Name Darf bei keiner Variable fehlen: Der Variablentyp und der Name. Dahinter ein Semikolon. typ name; Der Typ gibt an, welche Werte die Variable haben kann: Gibt man z.B. int (also Ganzzahl) an, dann sind mögliche Werte 0, -4, 20 usw. Der Name wird benötigt, um die Variable woanders benutzen zu können. Beispiel: int lives; Initialisierung Eine Variable kann gleich bei der Definition initialisiert werden, also einen Wert kriegen. Einfach ein "= wert" zwischen Name und Semikolon quetschen. Je nach Kontext wird das mal mehr, mal weniger als guter Programmierstil angesehen; für Unity ist das meist sehr gebräuchlich. Der Wert kann sich später natürlich wieder ändern. Beispiel: int lives = 5; Zugriffsmodifikatoren Jetzt wird's interessant. Ein Zugriffsmodifikator gibt an, wer alles diese Variable benutzen darf. Für den Anfang relevant sind erst einmal nur public und private. public gibt dabei an, dass alles und jeder den Wert der Variable herausfinden und ihn auch ändern kann. private ist quasi das Gegenteil: Nur die Klasse, in der die Variable definiert ist, weiß überhaupt, dass es sie gibt. Gibt man keinen Zugriffsmodifikator an, ist das in C# (!) gleichbedeutend mit private. Ob man private also hinschreibt oder nicht, ist also Geschmackssache. Beispiel: public Person bestFriend; public ist immer mit etwas Vorsicht zu genießen. Grundsätzlich gilt: Je weniger eine Klasse von sich öffentlich macht, desto unwahrscheinlicher wird es, das eine andere Klasse etwas von außen vermurkst. Klassen sollen als Einheiten funktionieren und nicht eng verwoben mit drölf anderen Klassen sein. Wann immer es allerdings völlig okay ist, wenn eine andere Klasse völlig uneingeschränkt den Wert einer Variablen ändert, benutzt in Unity ruhig public. public hat allerdings in Unity einen Nebeneffekt, auf den ich später eingehen werde. Static Das Keyword static hat eine besondere Bedeutung, die eine Variable von ihrer Bedeutung her komplett woanders hin verschiebt. Ich erklär's nochmal so kurz wie möglich: Wenn man ein Klasse hat, und diese hat eine Variable: class Person { int alter; } ...dann kann man mehrere Objekte dieser Klasse erstellen: jemand = new Person(); jemandAnderes = new Person(); ...und diese Objekte können unterschiedliche Werte für diese Variable haben. jemand könnte als Alter 18 haben und jemandAnderes 42. static hebt diesen Umstand auf und sagt: Diese Variable hat nicht pro Objekt einen eigenen Wert, sondern nur einen einzigen, der global gilt. Anwendungsbeispiel: class Checkpoint { static int checkpointsLeft = 0; public void Initialise() { ++checkpointsLeft; } public void CheckpointTouched() { --checkpointsLeft; if(checkpointsLeft == 0) { WinTheGame(); } } } Die Variable checkpointsLeft zählt hier, wie viele Checkpoints es (noch) gibt, und wann immer einer berührt wird, wird 1 abgezogen. Hat man alle Checkpoints berührt, gewinnt man. Wäre die Variable nicht statisch (also mit static markiert), dann würde jeder Checkpoint seinen eigenen Wert für checkpointsLeft haben. So aber gibt es global nur eine Variable checkpointsLeft. Alles, was man mit static machen kann, kann man irgendwie auch ohne machen. Auch static sollte nur dann eingesetzt werden, wenn es wirklich sinnvoll ist - da es aber einige Vorteile gegenüber den Alternativen bietet, kommt es immer mal wieder vor. Zugriffsmodifikatoren und static lassen sich kombinieren: public static int amount; ...aber gerade die Kombination public static ist fast immer ein schlechtes Zeichen C# für Unity Jetzt kommt der Teil, der den C#-Standard erweitert, wann immer man Unity benutzt. public Wie erwähnt, hat public in Unity eine Sonderbedeutung: Öffentliche Variablen werden serialisiert und im Editor exponiert. Das heißt, dass eine Variable mit public davor im Editor angezeigt wird (exponiert), man den Wert im Editor einstellen kann und dieser dann in der Szenendatei mitgespeichert wird (serialisiert). Nnach dem Motto: "Dieses" GameObject hat "diese" Komponente (die ihr programmiert habt) und "diese" Variable davon hat "diesen" Wert. So ziemlich jedes Unity-Scripting-Anfängertutorial zeigt einem das, aber der allgemeine Effekt von public (s.o.) wird gerne erst einmal unterschlagen. Was macht man denn jetzt, wenn eine Variable vor Zugriff von anderen Klassen geschützt sein soll, aber trotzdem im Editor exponiert sein soll? Oder umgekehrt: Eine Variable, die public sein soll, aber immer denselben Startwert hat und damit im Editor nichts zu suchen hat? Zum Glück gibt es dafür Lösungen. Serialisierungs-Attribute C# hat so genannte "Attribute", die man über Variablen, aber auch, je nach Attribut, vor Methoden und Klassen setzen kann. Sie sehen so aus: [Attribut] int zahl; Für uns jetzt relevant sind [serializeField] und [system.NonSerialized]. Sie machen das, was man vermutet: [serializeField] sorgt für das, was public macht, nur eben auch bei privaten Variablen. Beispiel: [serializeField] private int lives = 5; Der umgekehrte Fall, dass eine Variable public sein soll, aber nicht im Editor auftauchen soll, kommt eher selten vor, aber falls doch, hilt [system.NonSerialized] auf gleiche Art. Kleiner Zusatz für Fortgeschrittene: Propertys Propertys sind "Dinger", die von außen so aussehen wie Variablen, aber wesentlich mehr können. Die einfache Anwendungsform erlaubt es, Lesen uns Schreiben eines Variablenwertes mit unterschiedlichen Zugriffsmodifikatoren zu versehen: public int lives { private set; get; } Mit diesem Code ist lives von allen anderen Klassen auslesbar; den Wert ändern kann man allerdings nur von innerhalb der Klasse. get nimmt hierbei den Zugriffmodifikator der Property selbst an. Benutzt werden Propertys, als wären es Variablen: if(lives == 0) // oder lives = 5; Propertys können noch mehr, aber zuerst zu den Einschränkungen: 1. Propertys können von sich aus nicht direkt initialisiert werden, da muss man tricksen 2. Propertys kann Unity nicht serialisieren, weder mit public, noch mit [serializeField]. Kommen wir direkt dazu, was Propertys noch können. Und zwar: Seiteneffekte. Oft will man, dass etwas passiert, wenn sich ein Wert ändert; z.B. Tod, wenn die HP 0 erreichen. Propertys können Code ausführen, wann immer der Wert ausgelesen oder geändert wird. Tatsächlich kann der Wert beim Auslesen überhaupt erst generiert werden! Um so etwas zu erreichen, einfach geschweifte Klammern hinter get und/oder set schreiben. Beispiel "Direkte Weitergabe": public Color color { get { return renderer.material.color; } set { renderer.material.color = value; } } Man bemerke hier die Verwendung von return und dem speziellen Keyword value. Beispiel "Daten generieren": private Konto[] konten; public int gesamtVermoegen { get { var result = 0; foreach(var konto in konten) { result += konto.saldo; } return result; } } Auffällig hier: Es gibt kein set. Das bedeutet, man kann die Variable nur auslesen, aber nicht einen Wert setzen. Natürlich kann man auf die Konten einzahlen, um den Wert zu ändern. Beispiel "Gemischte Zugirffsmodifikatoren mit Serialisierung": [serializeField] private int _lives = 5; public int lives { get { return _lives; } private set { _lives = value; } } Dieser Code ist besonders nützlich: lives ist öffentlich auslesbar, aber nur privat änderbar. _lives wird serialisiert. Der Unterstrich ist eine verbreitete Schreibweise für private Variablen (die ich in Unity allerdings nur in Verbindung mit Code wie diesem verwende) und wird beim Anzeigen im Editor entsprechend weg gelassen. Von aussen sieht es damit aus, als gäbe es nur eine Variable lives, die im Editor zu sehen ist, deren Wert aber auf magische Weise nicht von anderen Klassen geändert werden kann. Das war's dann für heute Hoffentlich schafft diese kleine Auflistung etwas Klarheit darüber, wann man wie Variablen deklariert. Frohes Schaffen!
  36. 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"; } }
  37. 10 points
    Hier die bisherigen Teile der "Wir bauen uns ein Space Shoot em Up" Tutorial Reihe. Teil 1 - Schiffssteuerung Teil 2 - Camera Teil 3 - Antrieb Teil 4 - Waffen Teil 5 - Schaden Teil 6 - Optik Teil 7 - KI Teil 8 - Kollisionen & Kamera Teil 9 - Strahlenwaffen Teil 10 - Waffenslots Teil 11 - Items Teil 12 - Waves Teil 13 - Manöver Teil 14 - Loot Teil 15 - HUD Teil 16 - Mehr Gegner
  38. 10 points
    Hallo allerseits! Da ist mein erster Post hier gleich eine Projektvorstellung? Schoen08 hat mich auf eure Community verwiesen, und mich gefragt ob ich nicht mal Lust hätte hier über unser aktuelles Spiel zu schreiben. Einem technisch interessierten Publikum gegenüber könnte das in der Tat Spaß machen und vielleicht auch dem ein oder anderen nützliche Einblicke in unsere Arbeit geben. Also warum nicht? Ich bin Technical Director bei King Art und seit 4 Jahren setzen alle unsere Neuentwicklungen auf Unity. Das wären also das Adventure The Raven (zu dem Zeitpunkt war meine Beziehung zu Unity bestenfalls eien Hassliebe) danach Battle Worls: Kronos ein Rundenstrategiespiel dann das 2,5D Adventure Book of Unwritten Tales und jetzt gerade in der Entwicklung: Die Zwerge. Ich möchte nicht zu viele Worte über das Spiel selbst verlieren, denn im Moment läuft ein Kickstarter dazu und andere haben sich dort bereits große Mühe gegeben das Spiel so gut wie möglich vorzustellen. Also einfach mal dem Link folgen: Ich kann ohne Übertreibung sagen, dass Die Zwerge für mich das spannendste Projekt meiner bisherigen Karriere darstellt. Als Anfang der Nullerjahre der Herr der Ringe in die Kinos kam, haben mich vorallem die Massenschlachten beeindruckt. Dahinter stand ein System mit dem Namen "Massive" zu dem es leider nie allzuviele öffentliche Informationen gab. Aber trotzdem hat mich das Thema sehr fasziniert auch wenn es natürlich nicht real-time war. Ich weiß noch, dass ich damals dachte, dass die Computer vielleicht in 10-15 Jahren so leistungsfähig sind, dass man sowas auch in Echtzeit versuchen kann. Und nun bin ich in der glücklichen Situation mich bei den Zwergen dieser Vision zumindest annähern zu können. Viele Computerspiele besonders im Genre der RPGs modellieren den Kampf nicht viel anders als Pen&Paper-RPGs oder Brettspiele. Da wird viel gewürfelt, es gibt zalreiche Attribute und Formalismen für Rüstung, Kritische Treffer, Ausweichwürfe und so weiter... am Ende läuft alles darauf hinaus die Lebenspunkte der Gegner auf Null zu senken und selbiges für die eigenen Figuren zu verhindern. Für die Zwerge setzen wir dagegen auf eine physikalische Simulation der Dynamik von Massenschlachten. Natürlich ist auch das nur ein krudes Modell der Wirklichkeit aber eben doch ein fundamental anderer Ansatz als wir ihn zum Beispiel noch bei Battle Worlds gegangen sind, wo Zeit (turnbased) und Raum (feldbasiert) disktretisiert waren und das Spiel fast einer Brettspielumsetzung gleichkam. In Die Zwerge simulieren wir stattdessen das Verhalten und Zusammenspiel einzelner Agenten ohne zeitliche und räumliche Diskretisierung auf Basis einer physikalischen Simulation mit einfachen Shapes wie Kreiseln und Kapseln, die anders als in fast allen 3rd Party Lösungen allerdings keine Rigid-Bodies sind. Diese Basis wird ergänzt um weitere Systeme wie ein relativ komplexes Pathfinding oder eine flexible Schnittstelle um Kräfte in die Simulation zu induzieren und damit z.B. Schläge, Explosionen oder ganz allgemein Spezialattacken zu simulieren. Das Framework, das all diese Berechnungen durchführt und das wir HORDE (Horde Onslaught Realtime Dynamcs Engine) genannt haben ist von Unity weitestgehend entkoppelt. Unity hostet diesen Prozess nur - Es ruft irgendwo im Update-Schritt einer Component eine unverdächtig klingende Battle.Update() Methode auf die dann mehrere Millisekunden braucht um zu terminieren. Performance ist durchaus eine knappe Resource da man sich mit dem gesammten Rest des Spiels um die knappen Cycles des Unity Mainthreads streiten muss. Nach dem erfolgreichen Update ist die Simulation um die aktuelle Deltatime fortgeschritten und wieder synchron zur Spielzeit. Für jeden Orc oder Zwerg gibt es auch ein "normales" Gameobjekt in der Unity-Szene dessen Aufgabe aber lediglich darin besteht die Vorgaben seines Gegenparts der Simulation (Grunt genannt) visuell umzusetzen. Die beiden Fragen, "wie berechne ich eine Massenschlacht zwischen Orcs und Zwergen so, dass realistisch aber auch spaßig zu spielen ist" und "wie visualisiere ich diese Daten so, dass man den Ansprüchen an moderne Spiele genügt" stehen im Mittelpunkt meiner bisherigen Arbeit. Und ich freue mich darauf meine Antworten und Ansätze hier mit euch zu teilen. Für's erste ist der Post aber schon lang genug geworden. Lasst mich hören, was euch besonders interessiert, es muss auch nicht unbedingt mit den Zwergen zu tun haben. ~Thomas
  39. 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.
  40. 10 points
    Ja moin Ich bins mal wieder, und habe folgendes anliegen: Und zwar benötige ich einen transparenten rauscheffekt der wenn er benötigt wird eingeschaltet und ausgeschaltet werden kann. Ich habe mal einen Rauscheffekt mit Sony Vegas erstellt und gerendert. (er soll dann in etwa so aussehen wie z.B. bei Slender oder wie wenn man bei einem fernseher den falschen kanal nimmt. Jetz weiß ich aber überhaupt nicht wie da rangehen soll. Kann man überhaupt mit einem video das ganze machen oder wie würdet ihr das machen (mit einer textur), wobei die sich ja nicht bewegt ? Also ich weiß das überhaupt nicht (habe nur die freeversion) Gruß Dosenbiiir
  41. 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?
  42. 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
  43. 9 points
    Hey, wir haben gestern unser Spiel Juicy Jelly Barrel Blast in den Playstore gestellt, iOS version kommt demnächst. Das Spiel ist ein von den Kanonen Sequenzen aus Donkey Kong Country inspirierter one button shooter. Komplett kostenlos, und ohne Werbeunterbrechung wenn man es nicht will. Wir freuen über jeden der es ausprobiert Googleplay https://play.google....otale.cannonrun Trailer Website http://www.barrelblast.net
  44. 9 points
    Ich habe mich die letzte Woche mal ein wenig um die Grafiken gekümmert. Und so sieht mein Pinball jetzt aus:
  45. 9 points
    Howdi, wir wollen euch einladen am Donnerstag um 20 Uhr zu uns in den Chat zu kommen. Wieso? Es ist nervig für alle einfach nur den IRC Thread zu pushen. Durch einen festen Zeitpunkt und Datum ist es möglich mal mehr als nur 2 oder 3 Leute dort anzutreffen. In letzter Zeit gab es durch einen Troll einiges an Probleme die zum Forum übergeschwappt sind (siehe Kommentarbereich), wir wollen zeigen dass wir nun Trollfrei sind. Nicht jeder mag Skype/TS oder ist in der Nähe eines RL Stammtisches. Wann? Diesen Donnerstag den 31 März Um 20 Uhr. Wo? Wir treffen uns im IRC Addresse: irc.euirc.net Port: 6666 Channel: #unity Wie? Einfach per WebIRC: [Link] Oder einen der feinen IRC Clients: [Nettalk] [Mirc] [oder viele weitere]
  46. 9 points
    Multiplayer Tutorial Teil 1 - Einfaches Verbinden über direkte IP und ein kleiner Chat Das Programmieren von Multiplayer-Spielen ist nicht einfach und kann sich leicht zu einer zeit- und lustraubenden Angelegenheit "verwachsen". Aber wir haben Glück, denn in Unity3D ist die Netzwerkprogrammierung fast ein Kinderspiel! Als erstes werden wir uns mit den Grundlagen beschäftigen und einen kleinen Chat programmieren. Hier gibt es das ganze als Webplayer-Beispiel. Am besten öffnet ihr es in 2 Tabs und macht eins zum Server und 1 zum Client: http://www.doublem.b...ltiplayer1.html Verbinden über direkte IP Fangen wir ganz vorne beim leichtesten an: Erstelle eine neue Javascript-Datein und nenne sie z.B. Connect.js. Diese kannst du jetzt einem GameObject zuweisen, ich habe es bei der Camera gemacht, geht aber natürlich auch bei jedem anderen. Damit das Skript auch funktioniert musst du noch einen Network-View hinzufügen (Component -> Miscellaneous -> Network View) Wir brauchen ein paar Variablen um die IP des Servers, den Port und einen kleinen Statustext zu speichern. var ip_Address : String = "127.0.0.1"; // Die Variable die die ipAdresse beinhaltet (als Standard localhost) var Port : String = "25001"; // Die Variable für den Port (standardmäßig 25001) var Status : String = "Standby"; // Ein Statuskommentar das wir in einem Label anzeigen, in etwa "Verbinde zu Server...." Ich denke das ist selbsterklärend. Der Statuskommentar ist nicht unbedingt notwendig, sondern nur dafür da, um den Benutzer nicht im trüben fischen zu lassen und ihm ggf. einen Fehler auszugeben. Als nächstes müssen wir irgendwie dem Benutzer die Möglichkeit geben den Server/Client zu starten und zu bedienen. Dafür brauchen wir was? - Eine GUI natürlich! function OnGUI(){ // Fenster anzeigen [...] GUI.Window(0,Rect(0,0,200,200),NetworkingWindow,"Verbindungsmanager"); } /* Die Fensterfunktion vom Hauptmenuefenster */ function NetworkingWindow (id : int) { /* noch leer */} Jetzt müssen wir das Fenster nur noch mit Leben füllen, und das ist der wirklich spannende Teil: Wir werden generell überprüfen, ob wir schon verbunden sind, oder noch "Standby" und dementsprechend handeln. Das kann man mit Network.peerType abfragen, der z.B. die Werte NetworkPeerType.Disconnected, NetworkPeerType.Connected, NetworkPeerType. Client usw. annehmen kann. if (Network.peerType == NetworkPeerType.Disconnected) // Wenn keine Verbindung besteht... { // tue etwas... } else // Wir sind schon verbunden bzw. der Server läuft schon! { // tue etwas (oder nichts) } Zuerst werden wir uns um den ersten Teil kümmern, also wenn keine Verbindung besteht. if (Network.peerType == NetworkPeerType.Disconnected) // Wenn keine Verbindung besteht... { // tue etwas... } else // Wir sind schon verbunden bzw. der Server läuft schon! { // tue etwas (oder nichts) if(Status.length > 0) // Wenn der Status-text länger als 0 Zeichen ist... GUILayout.Label(Status); // Status anzeigen Wenn der Statuskommentar mehr als 0 Zeichen hat zeigen wir ihn mit einem Label an // Ein Eingabefeld für den Spielernamen erzeugen und den Text in der Registry speichern GUILayout.Label("Name:"); PlayerPrefs.SetString("Player Name",GUILayout.TextField(PlayerPrefs.GetString("Player Name"))); // Ein Eingabefeld für den Spielernamen erzeugen und den Text in der Registry speichern GUILayout.Label("Name:"); PlayerPrefs.SetString("Player Name",GUILayout.TextField(PlayerPrefs.GetString("Player Name"))); Das ist schon etwas komplizierter. Es wird als erstes ein Text "Name" angezeigt. Danach ein Eingabefeld, und der darin eingegebene Name wird in den PlayerPrefs unter dem Key "Player Name" gespeichert. Anmerkung: Mit PlayerPrefs kann man auf die Registry zugreifen und darin auch über das Programmende hinaus Daten speichern. Mit PlayerPrefs.SetString("Key", "Wert"); kann man unter dem Schlüsselwort "Key" dort einen Wert "Wert" abspeichern und dann mit PlayerPrefs.GetString("Key"); wieder abfragen. Es folgen zwei Text- und Eingabefelder für die IP-Adresse und den Port: GUILayout.Label("IP-Adresse:"); // Den text IP-Adresse anzeigen ip_Address = GUILayout.TextField(ip_Address); // Eingabefeld für die IP-Adresse GUILayout.Label("Port:"); // Den Text Port: anzeigen Port = GUILayout.TextField(Port); // Eingabefeld für den Port Und jetzt kommt der Kern des ganzen. Wir erstellen einen Button um sich als Client zum Server an der angegebenen IP zu verbinden. if (GUILayout.Button("Zu direkter IP verbinden")) // Button erstellen { // Status auf "Verbinde zu Server...." setzen Status = "Verbinde zu Server..."; Debug.Log(Status); // Wenn Button geklickt ist: // Netzwerkeinstellung Nat ausschalten Network.useNat = false; // zu bestimmter IP und bestimmtem Port verbinden // [Port wird mit ParseInt nach int konvertiert] Network.Connect(ip_Address, parseInt(Port)); } Die Einstellung useNat wird ausgeschaltet weil sie hier nicht so wichtig ist, aber ihr könnt sie gerne testweise anschalten. Mit der Funktion Network.Connect(ip,port) kann man nun den ganzen Zauber tun und sich verbinden. Zusätzlich wird der aktuelle Status auf dem Label und im Log ausgegeben. Das gleiche folgt noch für den Fall, dass der Benutzer sich nicht verbinden, sondern einen Server eröffnen will: if (GUILayout.Button("Server starten")) // Button erstellen { // Status auf "Starte Server..." setzen Status = "Starte Server..."; Debug.Log(Status); // Wenn Button geklickt ist: // Netzwerkeinstellung Nat ausschalten Network.useNat = false; // Server aufmachen // der erste Parameter gibt die maximale Spielerzahl an // der zweite den Port für den server // (ich nehme hier einfach den normalen Port wie beim Client, kann man aber auch anders machen) // [Port wird mit ParseInt nach int konvertiert] Network.InitializeServer(32, parseInt(Port)); } Auch hier wieder der Statuskommentar und Nar, ansonsten ist nur die Funktion Network.InitializeServer anders. als ersten Parameter gibt man die maximale Spieleranzahl und als zweiten den gewünschten Serverport an. Damit sind wir mit dem verbinden-Teil schon durch. Jetzt noch den teil zum ausschalten... else // Wir sind schon verbunden bzw. der Server läuft schon! { if(Status.length > 0) // Wenn der Status-text länger als 0 Zeichen ist... GUILayout.Label(Status); // Status anzeigen if (GUILayout.Button ("Verbindung beenden")) { // Status auf "Trenne Verbindung(en)..." setzen Status = "Trenne Verbindung(en)..."; Debug.Log(Status); Network.Disconnect(200); // Die Verbindung trennen (innerhalb von 200ms) } Wenn es einen Statuskommentar gibt wird er angezeigt, sonst nicht. Dann wird noch ein Button erstellt zum Beenden, und wenn man ihn drückt terminiert Network.Disconnect unsere schöne verbindung Jetzt wäre das ganze schon testfähig, aber es kommt noch ein kleiner Zusatz, damit man auch etwas damit machen kann: ein Chat. Chat Erstelle wieder mal eine neue Datei (z.B. Chatscript.js) und weise sie dem selben GameObject zu wie das Connect-Script. Wir brauchen auch wieder Variablen: var scrollPosition : Vector2; // Die Position des Skrollbalken var chatEntrys = ArrayList(); // Ein Array mit dem Chatinhalt var window = Rect(Screen.width/2-200,Screen.height/2-100,400,200); // Rechteck für das Chatwindow var inputField : String = ""; // Textfeldstring var scrollPosition : Vector2; Das wichtigste ist chatEntrys, ein Array von Strings in dem der gesammte Chatverlauf gespeichert wird. Das fensterrechteck window wird hier nur aufgeführt und deklariert, damit man das Fenster auch verschieben kann. Beim Code für das Chatfenster ist eine Minimalität anders: Wir weisen window den Rückgabewert von GUI.Window zu. Dadurch wird window, wenn das Fenster verschoben wird, aktualisiert und das Fenster kann verschoben werden. function OnGUI(){ window = GUI.Window (1, window, GlobalChatWindow, "Chat"); } function GlobalChatWindow (id : int) {/* noch leer */} Als nächstes geht es in "GlobalChatWindow" weiter. Wir erstellen einen Skrollbalken und listen dann alle Einträge aus dem Array nacheinander auf. // Skrollbalken erstellen scrollPosition = GUILayout.BeginScrollView (scrollPosition); for (var entry : String in chatEntrys) // jeden einzelnen durchgehen { GUILayout.BeginHorizontal(); GUILayout.Label(entry); // und für jeden ein textfeld erstellen GUILayout.EndHorizontal(); } // Skrollbalken Ende GUILayout.EndScrollView (); Dann müssen wir natürlich noch ein Eingabefeld erstellen... inputField = GUILayout.TextField(inputField); Und das Event abfangen wenn jmd dort "Enter" drückt... if (Event.current.type == EventType.keyDown && Event.current.character == "\n" && inputField.Length > 0) // Wenn Enter gedrückt ist und mehr als 0 zeichen im Textfeld { var playerName = PlayerPrefs.GetString("Player Name"); // Den Spielernamen aus der Registry auslesen if(playerName == "") // Wenn kein Name eingegeben oder Fehler... { playerName = "Benutzername"; //... den Namen zu Benutzername setzen } // dann den Namen, einen Doppelpunkt und den Text per rundnachricht im Netzwerk versenden networkView.RPC("ChatText", RPCMode.AllBuffered, playerName+": "+inputField); inputField = ""; // Textfeld zurücksetzen } Wenn Enter gedrückt wird lesen wir erstmal den vorher in der Registry gespeicherten Namen aus und versenden dann mit networkView.RPC eine "Rundnachricht" im Netzwerk. Als Parameter nimmt es einen Funktionsnamen (s.u.), den Empfänger und den Text (bzw andere Parameter) Wenn man z.B. networkView.RPC("HelloWorld", RPCMode.AllBuffered,"Hello!") angibt wird in jedem verbundenen Client und Server die Funktion "HelloWorld" gestartet mit dem Parameter "Hello!". Als 2. Parameter kann man auch eine IP-Adresse angeben oder RPCMode.Server, RPCMode.others usw. Dann noch dafür sorgen, dass man das Fenster auch wirklich verschieben kann und dann sind wir mit der Funktion durch. GUI.DragWindow(); Hier kommt jetzt die oben erwähnte RPC-Funktion. @RPC function ChatText(chatText : String){ chatEntrys.Add(chatText); } Das @RPC ist UNBEDINGT notwendig! Wenn in der Fensterfunktion der RPC "ChatText" aufgerufen wird wird auf jedem Client (und Server) und bei uns selber die Funktion ChatText ausgeführt. Dann fügen wir den aktuell geschriebenen Text noch zum Array dazu und sind damit dann auch fertig. Und noch etwas generelles: Wenn ihr mehrere Clients bzw. Client und Server auf einem PC laufen lasst solltet ihr auf jeden Fall Edit -> Project Settings -> Player -> Run in Background anschalten. So, das wars erstmal, ich hoffe ich konnte Euch helfen! Hier nochmal die beiden Skriptdateien zum Downloaden:http://www.doublem.b...ads/Scripts.zip Ich habe insbesondere in der 1. Datei noch ein paar Eventverarbeitungen usw. hinzugefügt, also unbedingt ansehen mfg DoubleM [PS: Aus dem Alten Forum kopiert] [PPS: Überarbeitet, da beim Kopierungsvorgang aus dem alten Forum sämtliche Formatierungen durcheinander geraten sind]
  47. 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
  48. 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; } } }
  49. 9 points
    Mann kann sogar mit der Pro anfangen und mit der Indie das Spiel fertig machen! Ne mal im Ernst. Ihr habt ja alles seweit erwähnt, ich will es aber noch einmal bündeln. Darf ich mit der Indy Lizenz ein Spiel verkaufen - JA! Ist sogar erwünscht! Darf ich im Team Indie und Proversionen vermischen, wenn ich dann beim fertigen Spiel Pro Features drin habe? - NEIN! Muss ich die Pro Version kaufen wenn ich über 100.000$ im Jahr verdiene? - JA! Jetzt kommt die Frage auf, wie die denn den Verdienst überprüfen wollen und feststellen wollen ob alle im Team die Pro hatten. Das geht nicht, oder nur mit Aufwand. Deswegen ist das alles auf Vertrauensbasis! Und seine wir mal ehrlich. Wenn jemand so viel Geld damit verdient, wird er von ganz alleien die Pro Version kaufen. Wenn da ein professionelles Team ist, welches mit Unity ein Spiel machen will, wird es aufgrund der Kompatibilität auch alle Versionen auf einen Stand bringen und die werden sich sogar die Assetserver Lizenz holen, was sowieso schon die Pro Version vorraus setzt. Profis haben ein Gewerbe und können das Werkzeug (Unity) von der Steuer absetzen. Profis werden somit eh die Pro Version kaufen. Natürlich nicht für jeden, der In der Firma arbeitet. Denn ein Grafiker oder ein Sounddesigner braucht ja kein Unity für seine Arbeit.
  50. 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"



×
×
  • Create New...