• Announcements

    • Lars

      Allgemeine Forenregeln   03/13/2017

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

About this blog

Mein Blög

Entries in this blog

Xenes
Hallo Leute,
heute möchte ich eine neue Blogreihe anfangen. Genannt Texturieren für Dummies. Also auch für Leute wie mich ;P

Dabei wichtig:
Ich bin weder ein 2D Artist Profi noch sonst was.
Daher ist das auch alles ein Blog.

Meine Save/Load mit txt Reihe ist derzeit eingestellt da ich kaum Zeit habe mir ein komplettes Save/Load System zu überlegen^^

Aber egal, legen wir los.

Zunächst brauchen wir ein Bildbearbeitungsprogramm.
Zum Beispiel GIMP.

Wir beginnen mit einem x-beliebigen Photo, zb dem einer Wiese ([b]siehe gras.jpg im anhang[/b])

Das öffnen wir in unseren Programm und schneiden es auf eine quadratische Form zu ([b]siehe gras_s1 im anhang[/b])
In Gimp wechselt ihr dazu zum Zuschneiden Werkzeug im Werkzeugkasten.

Danach bearbeiten wir erst mal die Helligkeits und Kontrastwerte, damit unsere Textur nicht überbelichtet wirkt.
([b]siehe dazu gras_s2[/b])
Dazu wählt ihr in Gimp [b]Farben->Helligkeit/Kontrast[/b]

Dann testen wir, ob unser Ausschnitt vielleicht schon Kachelbar ist.
Benutzt dazu [b]Filter->Abbilden->Kacheln[/b] und gebt dann eine neue größe ein. Ich würde zu einer doppelten raten (2048*2048 zB, [b]siehe gras_s3[/b])

Seht euch das Vorschau Bild an.
Seht ihr eine hässliche naht, dann schließt das Bild wieder, ohne es zu speichern. Ansonsten könnt ihrs speichern, fertig!

Bei mir wars aber nicht so.
Bei mir war eigentlich nur an den Rändern eine deutliche Naht zu erkennen.
Deswegen werden wir jetzt die rechte seite nach links spiegeln.

Wählt dazu mit dem Auswahlwerkzeug einen Teil der rechten Seite aus und Kopiert ihn ([b]gras_s4[/b]). [b]Verschiebt[/b] ihn ganz nach rechts, [b]spiegelt[/b] ihn mit dem doppelpfeilwerkzeug im Werkzeugkasten und fügt ihn mit dem kleinen Ankersymbol im Reiter Ebenen in das Bild (oder ihr klickt irgendwo in das bild, dann passiert das automatisch, [b]gras_s5[/b])

Jetzt habt ihr ein kachelbares Bild, das aber nun innendrinnen eine Naht hat!
Nun, bei solchen Grastexturen ist es recht schwierig 100%ige nahtlosigkeit zu erreiche, aber es reicht schon eine optische.

Dazu benutzt ihr das Klonwerkzeug.
klickt auf das Schachfigurähnliche Ding, klickt STRG+links auf einen unteren Teil im Bild und vergrößert den radius auf 10. Dann haltet ihr die linke maustaste unten bei beginn der naht gedrückt und zieht die maus nach oben. Voilà! Fast nahtlos! ([b]gras_s6[/b])

Probeweise könnt ihr das nochmal Kacheln und dort wo ihr noch nicht zufrieden seit nachklonen.
Das Ergebnis könnt ihr in [b]Ergebis.jpg [/b]bewundern.

So, ich hoffe ich konnte euch das Texturieren ein wenig näher bringen.
Denk mal ich hab recht schlecht geschrieben, aber was solls^^

In Teil 2 wollen wir eine Normalmap dazu erstellen oder einen Hügel den wir mit dem gras texturieren.

Bis dahin alles Gute,

Xenes
Xenes
„Besser spät als nie!“ heißt es. Und damit willkommen zu Teil 4 der Blogreihe Speichern mit Textdateien. Packen wirs also gleich an, es gibt viel zu tun.

Zunächst einmal möchte ich unser Script aus der alten Reihe verbessern und dann erweitern. Allerdings werde ich nun nicht mehr in JS scripten, sondern in C#.
Daher unser altes Script in C#:

[CODE]
using UnityEngine;
using System.Collections;
using System.IO;
using System;

public class SaveLoad : MonoBehaviour {
public Transform Player;

void Start(){
//zunächst erstellen wir einen Savegameordner
if(!Directory.Exists(Application.dataPath+"/Savegames")){
Directory.CreateDirectory(Application.dataPath+"/Savegames");
}
}

void WriteFile () {
//wir öffnen einen neuen StreamWriter
StreamWriter sw = new StreamWriter(Application.dataPath +"/Savegames/Savegame.txt");
//drei Zeilenschreiben: position x,y,z
sw.WriteLine(Player.position.x);
sw.WriteLine(Player.position.y);
sw.WriteLine(Player.position.z);
sw.Flush();
sw.Close();
//damit schliessen wir den StreamWriter
if(File.Exists(Application.dataPath +"/Savegames/Savegame.txt")){
print("File written!");
}
else{
print("Error writing file");
}
}
void ReadFile () {
//wir öffnen einen neuen StreamReader
StreamReader sr = new StreamReader(Application.dataPath +"/Savegames/Savegame.txt");
//Liest das ganze file
string Inhalt = sr.ReadToEnd();

//schließt den Reader
sr.Close();

//wir füllen ein Array mit unseren Daten...
string[] Zeilen = Inhalt.Split("\n"[0]);

//...und lesen sie aus!
Vector3 PlayerPos = new Vector3 (Convert.ToSingle(Zeilen[0]),Convert.ToSingle(Zeilen[1]),Convert.ToSingle(Zeilen[2]));
Player.position = PlayerPos;
}

void OnGUI() {
if(GUI.Button(new Rect (0,0,150,50),"Save")){
WriteFile();
}

if(GUI.Button(new Rect (0,50,150,50),"Load")){
ReadFile();
}
}
}[/CODE]
änderungen im Vergleich zum alten für die die weiterhin js benutzen wollen:[list]
[*]Wir haben alle System.IO anfänge entfernt (dank dem namespace können wir getrost drauf verzichten)
[*]Wir benutzen den [url="http://msdn.microsoft.com/de-de/library/system.aspx"]System[/url] - [url="http://msdn.microsoft.com/de-de/library/bb979264.aspx"]namespace[/url] für das explizite Konvertieren von string zu float (Convert.ToSingle)
[/list]
Das wars dann aber im Vergleich.

Bevor alles losgeht, will ich drei variablen einführen. Einmal „Filename“ ,„Filending“ und „Filepath“
Alle sind strings. Mit ihnen werden wir komfortabel den Namen unseres Savegames, die Dateiendung und den Dateipfad verändern können:

[CODE] private string Filepath;
private string Filename = "Savegame";
private string Filending = ".txt";[/CODE]

Den Filepath müssen wir in Start() zuweisen:

[CODE]void Start () {

Filepath = Application.dataPath+"/Savegames";

//zunächst erstellen wir einen Savegameordner
if(!Directory.Exists(Filepath)){
Directory.CreateDirectory(Filepath);
}
}[/CODE]

Dann müssen wir noch alle anderen Pfadinfos anpassen:

[CODE] StreamWriter sw = new StreamWriter(Filepath+Filename+Filending);
[...]
if(File.Exists(Filepath+Filename+Filending)){
[...]
StreamReader sr = new StreamReader(Filepath+Filename+Filending);[/CODE]

So, weiter möchte ich ein kleinen Print nochmal beim Loaden einfügen. Was wenn wir gar kein File haben oder es aus irgendeinen anderen grund nicht geladen werden konnte?

Darum fügen wir an die Kopfzeile der Readfile() funktion folgendes hinzu:

[CODE] if(File.Exists(Filepath+Filename+Filending)){
//...
print ("File read!");
}[/CODE]

Und am Ende dann:

[CODE]else {
print ("File does not exist or error reading file!");
}[/CODE]

So, wer jetzt versucht zu laden, aber kein File hat, dem wird unser print ausgegeben.
Das werden auch brauchen für den teil, der jetzt kommt: Savegames wieder löschen.
Dafür erstellen wir uns zunächst einen neuen Button:

[CODE]if(GUI.Button(new Rect (0,100,150,50),"Delete")){
DeleteFile();
}[/CODE]

Um unser File zu löschen, benutzen wir einfach Die File.Delete Funktion. Allerdings müssen wir zuvor prüfen, ob es überhaupt ein Savegame zum löschen gibt:

[CODE]void DeleteFile () {
if(File.Exists(Filepath+Filename+Filending)){
//wir löschen unser Savegame...
File.Delete(Filepath+Filename+Filending);
print("File deleted!");
}
else {
//gibt es nix zum löschen, mache eine fehlermeldung
print ("File does not exist or error deleting file!");
}
}[/CODE]

Ok, jetzt kommen wir zu einer weiteren nützlichen Option. Man stelle sich ein echtes GameMenü vor: Was wird angezeigt? Naja, zunächst einmal alle verfügbaren Savegames, deren Name, wann sie erstellt worden sind und vielleicht die Größe. Dafür benutzen wir ein tolles Feature aus System.IO:
Die Directoryinfo.
Zuvor machen wir uns einen vierten Button, genannt „Show File Info“:

[CODE] if(GUI.Button(new Rect (0,150,150,50),"Show File Info")){
ShowFileInfo();
}[/CODE]

Jetzt legen wir dazu passende Funktion an.
Auch hier wollen wir zuerst prüfen, ob das File erhältlich ist, dann erstellen wir ein neues Objekt, die DirectoryInfo. Sie ist ein Array bestehend aus Fileinfo – Objekten. Aus ihnen können wir die Informationen auswerten. Dazu benutzen wir eine foreach Schleife:

[CODE]void ShowFileInfo(){
if(File.Exists(Filepath+Filename+Filending)){

print ("File exists!");
//wir legen eine neue DirectoryInfo an...
DirectoryInfo dir = new DirectoryInfo(Filepath);
//...durchsuchen jede Datei...
foreach(FileInfo f in dir.GetFiles("*"+Filending)) {
//und lassen Name, Groesse und andere Infos angeben
print (f.Name);
print (f.FullName);
print (f.Length +" Bytes");
print (f.CreationTime);
print (f.Attributes);
print (f.LastAccessTime);
}
}

else {
print ("File does not exist!");
}
}[/CODE]

Das * ist stellvertretend dafür, dass der nur die Dateiendung zählt. Alle files auslesen wäre *.*

Hier Der ganze Code zum Kopieren:

[CODE] using UnityEngine;
using System.Collections;
using System.IO;
using System;
public class SaveLoad : MonoBehaviour {

public Transform Player;
private string Filepath;
private string Filename = "Savegame";
private string Filending = ".txt";

// Use this for initialization
void Start () {

Filepath = Application.dataPath+"/Savegames";

//zunächst erstellen wir einen Savegameordner
if(!Directory.Exists(Filepath)){
Directory.CreateDirectory(Filepath);
}
}

void WriteFile () {
//wir öffnen einen neuen StreamWriter
StreamWriter sw = new StreamWriter(Filepath+Filename+Filending);

//drei Zeilen schreiben: position x,y,z
sw.WriteLine(Player.position.x);
sw.WriteLine(Player.position.y);
sw.WriteLine(Player.position.z);
sw.Flush();
sw.Close();

//damit schliessen wir den StreamWriter
if(File.Exists(Filepath+Filename+Filending)){
print("File written!");
}
else{
print("Error writing file");
}
}
void ReadFile () {
if(File.Exists(Filepath+Filename+Filending)){
//wir öffnen einen neuen StreamReader
StreamReader sr = new StreamReader(Filepath+Filename+Filending);
//Liest das ganze file
string Inhalt = sr.ReadToEnd();

//schließt den Reader
sr.Close();

//wir füllen ein Array mit unseren Daten...
string[] Zeilen = Inhalt.Split("\n"[0]);

//...und lesen sie aus!
Vector3 PlayerPos = new Vector3 (Convert.ToSingle(Zeilen[0]),Convert.ToSingle(Zeilen[1]),Convert.ToSingle(Zeilen[2]));
Player.position = PlayerPos;
print ("File read!");
}

else {
print ("File does not exist or error reading file!");
}
}

void DeleteFile () {
if(File.Exists(Filepath+Filename+Filending)){
//wir löschen unser Savegame...
File.Delete(Filepath+Filename+Filending);
print("File deleted!");
}
else {
//gibt es nix zum löschen, mache eine fehlermeldung
print ("File does not exist or error deleting file!");
}
}

void ShowFileInfo(){
if(File.Exists(Filepath+Filename+Filending)){

print ("File exists!");
//wir legen eine neue DirectoryInfo an...
DirectoryInfo dir = new DirectoryInfo(Filepath);
//...durchsuchen jede Datei...
foreach(FileInfo f in dir.GetFiles("*"+Filending)) {
//und lassen Name, Groesse und andere Infos angeben
print (f.Name);
print (f.FullName);
print (f.Length +" Bytes");
print (f.CreationTime);
print (f.Attributes);
print (f.LastAccessTime);
}
}

else {
print ("File does not exist!");
}
}

void OnGUI() {
if(GUI.Button(new Rect (0,0,150,50),"Save")){
WriteFile();
}

if(GUI.Button(new Rect (0,50,150,50),"Load")){
ReadFile();
}

if(GUI.Button(new Rect (0,100,150,50),"Delete")){
DeleteFile();
}

if(GUI.Button(new Rect (0,150,150,50),"Show File Info")){
ShowFileInfo();
}
}

}[/CODE]

Ja, das wars soweit auch wieder.
Wer sich jetzt ein bisschen mit GUI auskennt, dem wird es ein leichtes fallen, mithilfe eines Textfeld eigene Savegamenamen zu erstellen und mit dem Slider dann alle in ein Menü zu packen.

Mit fortgeschrittenen Scripting Kenntnissen lassen sich mit System.IO leicht ganze Save/Load Systeme erstellen.

Das ist in Teil 5 geplant... Freut euch darauf^^
Es gibt nämlich viel, viel zu verbessern! Dass habt ihr bestimmt selbst gemerkt!

Witzig: ihr könnt jede Dateiendung wählen, die ihr wollt! Egal ob .xxx oder .oXo oder auch .virus - allerdings lässt sich alles mit dem texteditor bearbeiten :P

Bei weiteren Fragen und Anregungen an mich wenden.
Xenes
Willkommen,
zu meinen dritten Teil der Tutorialreihe zum Speichern und Laden in Unity via Textdatei.
Heute geht es um weitere Techniken, die uns der Namespace System.IO bietet.
Ich werde hier an das Script aus den [url="http://forum.unity-community.de/blog/17/entry-25-saveload-mit-txt-2-laden/"]letzten Teil[/url] anknüpfen.

Zuerst werde ich ein Ordner für unsere Savegames erstellen lassen. Dafür benötigen wir die [url="http://msdn.microsoft.com/de-de/library/system.io.directory.aspx"]System.IO.Directory[/url] - Klasse, mit dem wir auf Ordnerfunktionen zugreifen können, also z.B. neue erstellen!
Dazu öffnen wir unser Script und fügen eine Start - Funktion ein, in der wir folgendes schreiben:

[CODE]function Start(){
if(!System.IO.Directory.Exists(Application.dataPath+"/Savegames")){
System.IO.Directory.CreateDirectory(Application.dataPath+"/Savegames");
}
}[/CODE]

So, jetzt machen wir einen Ordner mit der Bezeichnung savegames, falls er denn noch nicht exisitiert. Theoretisch könnten wir das auch in die WriteFile Funktion packen, aber wir müssen unseren Pfad sowieso nur ein mal erstellen.

Dann müssen wir natürlich auch noch den Speicherpfad im StreamWriter und StreamReader anpassen:
[CODE]var sw : StreamWriter = new StreamWriter(Application.dataPath +"/Savegames/Savegame.txt");[/CODE]
und
[CODE] var sr = new StreamReader(Application.dataPath +"/Savegames/Savegame.txt");[/CODE]

Speichern, ausprobieren!
Siehe da! Es funktioniert. Jetzt haben wir unseren eigenen Savegame Ordner.

Weiter wollen wir die Konsole etwas ausgeben lassen, wenn gespeichert und geladen wurde, da das schreiben einer txt so rasend schnell geht und zumindest ich mich immer frage, ob jetzt eigentlich schon was passiert ist.

Na gut, einfach ein print hinzufügen?
Nein, es geht besser.

Dazu hilft [url="http://msdn.microsoft.com/de-de/library/system.io.file.aspx"]System.IO.File[/url]. Hier können wir auf unser File zugreifen, es löschen, kopieren, etc.
Und auch abfrage, ob es exisitiert. Und das machen wir jetzt.

Also gehen wir in unser Script und schreiben in die WriteFile Funktion hinzu:
[CODE]if(System.IO.File.Exists(Application.dataPath +"/Savegames/Savegame.txt")){
print("File written!");
}

else{
print("Error writing file");
}[/CODE]

Ok, weiter gehts.
Jetzt will ich noch eine weitere Auslesemöglichkeit präsentieren, nämlich alle txtzeilen als (String-)Array ausgeben zu lassen, womit sich auch der Auslesevorgang automatisieren lässt.

Dafür schauen wir uns die ReadFile Funktion an und löschen zunächst alles bis auf die deklaration des StreamReaders. Dann fügen wir folgendes hinzu:

[CODE] function ReadFile () {
//wir öffnen einen neuen StreamReader
var sr = new StreamReader(Application.dataPath +"/Savegames/Savegame.txt");
//Liest das ganze file
var Inhalt = sr.ReadToEnd();
//schließt den Reader
sr.Close();

var Zeilen : String[];
Zeilen = Inhalt.Split("\n"[0]);
}[/CODE]

Wir lesen also zunächst das Ganze File aus und speichern das in ein String. Diesen teilen wir dann Zeile für Zeile auf und fügen dass in ein Array ein. Wer will, kann auch keine lokale varaible definieren und dann im Editor beobachten, wie sich das Array füllt.

Und jetzt müssen folgendes für das eigentliche laden einfügen:

[CODE]//...und lesen sie aus!
Player.position.x = parseFloat(Zeilen[0]);
Player.position.y = parseFloat(Zeilen[1]);
Player.position.z = parseFloat(Zeilen[2]);
[/CODE]

Warum Zeilen[0]? naja, weil ein Array immer mit dem nullten Element anfängt...
So und dass ist der Punkt, in dem man dann automatisieren kann!

Zb mit so was wie:
[CODE]for(GameObject in SaveObjekte){
GameObject.transform.position.x = parseFloat(Lines[a]);
GameObject.transform.position.y = parseFloat(Lines[a+1]);
GameObject.transform.position.z = parseFloat(Lines[a+2]);
a +=3;
}[/CODE]

Für Positionskoordinaten für eine Liste mit SaveObjekten... Die könnte man dann vielleicht auch taggen mit "savable" und am Anfang finden lassen...

So, dass wars jetzt für den dritten Teil. Hier nochmal das Ganze Script:
[CODE] #pragma strict
import System.IO;
var Player: Transform;
function Start(){
//zunächst erstellen wir einen Savegameordner
if(!System.IO.Directory.Exists(Application.dataPath+"/Savegames")){
System.IO.Directory.CreateDirectory(Application.dataPath+"/Savegames");
}
}
function WriteFile () {
//wir öffnen einen neuen StreamWriter
var sw : StreamWriter = new StreamWriter(Application.dataPath +"/Savegames/Savegame.txt");
//drei Zeilenschreiben: position x,y,z
sw.WriteLine(Player.position.x);
sw.WriteLine(Player.position.y);
sw.WriteLine(Player.position.z);

sw.Flush();
sw.Close(); //damit schliessen wir den StreamWriter

if(System.IO.File.Exists(Application.dataPath +"/Savegames/Savegame.txt")){
print("File written!");
}

else{
print("Error writing file");
}
}
function ReadFile () {
//wir öffnen einen neuen StreamReader
var sr = new StreamReader(Application.dataPath +"/Savegames/Savegame.txt");
//Liest das ganze file
var Inhalt = sr.ReadToEnd();
//schließt den Reader
sr.Close();
//wir füllen ein Array mit unseren Daten...
var Zeilen : String[];
Zeilen = Inhalt.Split("\n"[0]);
//...und lesen sie aus!
Player.position.x = parseFloat(Zeilen[0]);
Player.position.y = parseFloat(Zeilen[1]);
Player.position.z = parseFloat(Zeilen[2]);
}
function OnGUI() {
if(GUI.Button(Rect (0,0,150,50),"Save")){
WriteFile();
}
if(GUI.Button(Rect (0,50,150,50),"Load")){
ReadFile();
}
}
[/CODE]

Ich bin noch am überlegen für Teil 4...
Zumindest auf das laden einzelner Szenen werde ich noch, wie von CreativBase gewünscht, eingehen.
Gibt es noch andere Wünsche oder Anregungen? Z.B. eigene Namen für Savegames o.ä.?

Bis dahin weiteren Spass am speichern mit Textdateien,

Xenes
Xenes
Hallo nochmal,
dies ist hier gleich der zweite teil zu meinem Tutorial zum speichern und laden via Textdatei.
Jetzt geht es ums Laden.

Wer das vorige Tutorial mitgemacht hat, der öffnet jetzt seine savegame.txt und hat so was in der Art vorliegen:

[quote]2.54939
1.05
1.030781[/quote]

Jetzt müssen wir das ganze aber noch wieder einlesen und laden.
Wie geht jetzt das?

Dazu stöbern wir bei [url="http://msdn.microsoft.com/de-de/library/29kt2zfk(v=vs.80).aspx"]System.IO[/url] im msdn rum und entdecken folgende Klasse:
den [url="http://msdn.microsoft.com/de-de/library/system.io.streamreader(v=vs.80).aspx"]StreamReader[/url], also dem Gegenstück zum StreamWriter?

Ja, ganz genau.
Also öffnen wir unser Script und gehen zu unserer Funktion ReadFile() und fügen folgendes hinzu:

[CODE]function ReadFile () {
//wir öffnen einen neuen StreamReader
var sr = new StreamReader(Application.dataPath +"/Savegame.txt");
//Lese zeilen aus...
Player.position.x = parseFloat(sr.ReadLine()); //parse Float, weil es als String gelesen wird
Player.position.y = parseFloat(sr.ReadLine());
Player.position.z = parseFloat(sr.ReadLine());

sr.Close(); //damit schließen wir unseren StreamReader
}[/CODE]

Wir brauchen das parseFloat, um die Umwandlung von String zu Float wieder vorzunehmen Alles andere erklärt sich von selbst.

Wie,das wars?
Ja, das wars.

Jetzt nochmal das ganze Script:

[CODE] #pragma strict

import System.IO;
var Player: Transform;

function WriteFile () {
//wir öffnen einen neuen StreamWriter
var sw : StreamWriter = new StreamWriter(Application.dataPath +"/Savegame.txt");
//drei Zeilenschreiben: position x,y,z
sw.WriteLine(Player.position.x);
sw.WriteLine(Player.position.y);
sw.WriteLine(Player.position.z);

sw.Flush();
sw.Close(); //damit schliessen wir den StreamWriter
}

function ReadFile () {
//wir öffnen einen neuen StreamReader
var sr = new StreamReader(Application.dataPath +"/Savegame.txt");
//Lese zeilen aus...
Player.position.x = parseFloat(sr.ReadLine()); //parse Float, weil es als String gelesen wird
Player.position.y = parseFloat(sr.ReadLine());
Player.position.z = parseFloat(sr.ReadLine());

sr.Close(); //damit schließen wir unseren StreamReader
}

function OnGUI() {
if(GUI.Button(Rect (0,0,150,50),"Save")){
WriteFile();
}

if(GUI.Button(Rect (0,50,150,50),"Load")){
ReadFile();
}
}[/CODE]
Es gibt aber noch viel viel coolere Techniken!

Aber dafür gibts dann einen dritten Tutorialteil.

Bis dahin:
Viel Spass beim experimentiern mit txt Dateien!

Xenes
Xenes
Hallo,
ich beschäftige mich heute mit dem speichern in Unity mit einer Textdatei.
Textdatei? Das ist doch dieser Krampf, der nix kann!
Von wegen!

Zunächst einmal schauen wir uns eine beliebige Textdatei an, oder öffnen eine neue. Wie im Namen schon enthalten, kann eine Textdate nur Text enthalten, also Zahlen, Buchstaben und Zeichen.

Wie jetzt? Unsere drei Grundvariablen sind doch auch nur Buchstaben oder Zeichenketten!
Also Variablen in einer *.txt schreiben? Ja, ganz genau!

[quote]
Was ist mit Vector3, boolean, etc?
Ja, auch die könnten wir schreiben. Aber die einzige Schwierigkeit in einer txt ist das Lesen, nicht das Schreiben. Bei lesen aus einer txt wird nämlich alles aus String ausgegeben. Während wir Zahlen mit dem ParseInt oder ParsFloat wieder zurückkonvertieren können, wirds mit Vektoren oä schwierig.
[/quote]

Aber wie fangen wir jetzt an? Zunächst einmal bauen wir uns eine Szene mit einem FirstPersonController und einer Plane (nicht vergessen: Main Camera löschen)
Dann setzen wir den FPC auf die Plane und erstellen dann ein neues JavaScript, ich nenne es [b]SaveLoad.js[/b]

Dann öffnen wir das Script und haben folgendes:
[CODE]
#pragma strict
function Start () {
}
function Update () {
}
[/CODE]

Wie können wir nun aber ein File schreiben?
Dazu hilft der namespace [b]System.Io[/b]
Der MSDN sagt dazu:
[quote]
Der System.IO-Namespace enthält Typen, die das Lesen und Schreiben für Dateien und Datenstreams zulassen, sowie Typen, die grundlegende Unterstützung für Dateien und Verzeichnisse bieten.
[/quote]

Also nur diesen Namespace importiern und auf die Funktionen zugreifen? Ja, ganz genau!
Also fügen wir unter dem #pragma hinzu:

[CODE] import System.IO;[/CODE]

Danach löschen wir die Funktionen Update und Start und fügen folgende drei hinzu:
[CODE] #pragma strict
import System.IO;
function WriteFile () {
}
function ReadFile () {
}
function OnGUI() {
}[/CODE]

über das GUI wollen wir auf unsere Funktionen zugreifen, also machen wir zwei Buttons:
[CODE] function OnGUI() {
if(GUI.Button(Rect (0,0,150,50),"Save")){
WriteFile();
}
if(GUI.Button(Rect (0,50,150,50),"Load")){
ReadFile();
}
}[/CODE]

Ok, so weit, so einfach.
Jetzt überlegen wir, was wir speichern wollen.
Ich nehme hier jetzt einfach nur die Spielerposition.

Dafür brauchen wir den [url="http://msdn.microsoft.com/de-de/library/system.io.streamwriter.aspx"]StreamWriter[/url]
Mit ihm können wir eine txt mit einem Namen in einen von uns gewählten Pfad speichern.

Also schreiben wir folgendes:
[CODE] #pragma strict
import System.IO;

var Player: Transform;

function WriteFile () {
//wir öffnen einen neuen StreamWriter
var sw : StreamWriter = new StreamWriter(Application.dataPath +"/Savegame.txt");
//drei Zeilenschreiben: position x,y,z
sw.WriteLine(Player.position.x);
sw.WriteLine(Player.position.y);
sw.WriteLine(Player.position.z);

sw.Flush();
sw.Close(); //damit schliessen wir den StreamWriter
}

function ReadFile () {
}

function OnGUI() {
if(GUI.Button(Rect (0,0,150,50),"Save")){
WriteFile();
}
if(GUI.Button(Rect (0,50,150,50),"Load")){
ReadFile();
}
}
[/CODE]

Application Datapath ist der Pfad, wo unsere zukünftige *.exe ist.
Wenn wir das jetzt in unsere Szene hinzufügen und die variable Player assignen, dann auf Save klicken, wird eine neue txt importiert mit unseren drei Positionen.

Das war der erste Teil des Speichern und Ladens via txt.
Es folgt auf jeden Fall noch laden und später vielleicht noch verfeinern mit Ordnern und eigenen Namen des Savegames etc.

Bis dahin:
Hoffe, es war ein erstes gutes Tutorial und hat euch was gebracht!

Xenes
Xenes
Hallo,
Ich habe in den letzten Tagen mir Gedanken über ein Strategiespiel mit Unity gemacht. Zunächst habe ich mir die Kamera gescriptet und dann bin ich zu Bauplätzen übergegangen und dort überlege ich, wie ich das am besten angehe.

[quote]
überlegungen:
*Es soll für alle mögliche Anzahl an Bauplätze nur ein Script geben
*Beim Klick soll einer selektiert werden und rot unterlegt
*Wenn dieser selektiert ist, öffnet sich ein Baumenü
*Beim bauen eines Haus verschwindet der Platz und ein Haus erscheint
[/quote]

bisher habe ich mir so was in der Art gedacht:
-Jeder Platz hat den tag "Bauplatz", die Variable Empty (boolean) und Selected (boolean)
-Empty ist standardmäßig true

[CODE]var IsSelected : boolean;
var IsEmpty: boolean = true;
[/CODE]

-Beim Klick (OnMouseDown) wird selected auf true geschaltet...
-...in der Update Funktion wird bei selected == true der renderer rot gemacht
-...die statische visible (boolean) variable des Baumenüs auf true gestellt
-Beim Klick auf einen Button im baumenü wird an eine am Anfang gefüllte Liste voller Bauplätze gesendet...

[CODE]for(Platz in Bauplaetze){
Platz.SendMessage("BuildHouse",1.0,SendMessageOptions.DontRequireReceiver);
}[/CODE]

...Mit der HausID (1 = Bauernhof, 2 = wohnhaus, etc.).
In der Funktion "BuildHouse" wird nun Empty auf false umgeschaltet und ein Bauernhof auf die stelle geklont, aber nur bei dem selektierten Bauplatz!

[CODE]function BuildHouse(ID:int){
//...aber nur wenn der Platz leer und selektiert ist
if(IsSelected && IsEmpty){
switch(ID){
case 1:
//fuer die ID eins, baue haus eins, deselektiere
Instantiate(Haus1,transform.position,Haus1.transform.rotation);
IsSelected = false;
IsEmpty = false;
break;

case 2:
//fuer die ID zwei, baue haus zwei, deselektiere
Instantiate(Haus2,transform.position,Haus2.transform.rotation);
IsSelected = false;
IsEmpty = false;
break;
}
}
}[/CODE]

Jetzt haben wir einen Bauernhof da, der Platz ist nicht mehr leer und automatisch deselektiert.
Jetzt könnte man noch hinzufügen:

[CODE]function Update(){
//wenn er NICHT leer ist, dann blende ihn aus
if(!IsEmpty){
renderer.enabled = false;
}
//ansonsten halt nicht
else{
renderer.enabled = true;
}
}[/CODE]

So dass er auch nicht mehr sichtbar ist.

Problem: Jetzt haben wir aber mehrere Bauplätze und müssten beim Klick auf einen Bauplatz allen anderen mitteilen, dass sie nicht mehr selektiert sind. Sonst könnten wir extrem viele Gebäude gleichzeitig bauen und alles würde selektiert bleiben...

Man könnte natürlich beim Klick allen Bauplätzen sende: ihr seid nicht selektiert!, kurz warten und dann den angeklickten auf selektiert schalten:

[CODE] function OnMouseDown () {
//...wenn das ding leer ist
if(IsEmpty){
//blende BauMenu ein
BauMenu.Visible = true;
//sende an alle Plätze: unselektieren...
var Platz : GameObject;

for(Platz in Bauplaetze){
Platz.SendMessage("SetUnSelected",0.0,SendMessageOptions.DontRequireReceiver);
}
//...im naechsten frame aber teile den angeklickten mit: Du bist selektiert!
yield;
SetSelected();
}
[/CODE]

Die Frage ist doch, welche besseren Lösungen oder Vorschläge es gibt, das zu lösen.
Bin auf Kommentare gespannt...

Xenes