D
DocJunioR
Ambitioniertes Mitglied
- 7
Im letzten Teil haben wir gesehen, wie man einen kleinen Controller bastelt, der die einzelnen Ansichten des Spieles, bzw. Spielmenüs steuert.
Jetzt möchte ich gern zeigen, wie man auf einfache Weise seinen Spielstand vorhalten, laden und speichern kann.
Gehen wir mal davon aus, dass wir ein Spiel haben (wie das dann funktioniert, folgt in einem weiteren Teil) in dem z.B. wir die Informationen Spielername, Level, Punktzahl und Lebensenergie speichern wollen.
Wenn man sich nicht noch über Dateiformate Gedanken machen möchte, gibt es eine sehr einfache Möglichkeit in Java, Daten vorzuhalten: die Klasse Properties.
Sie besteht prinzipiell aus einer Map, die Schlüssel-Wertpaare beinhaltet, hat aber noch eine entscheidene Eigenschaft: Sie kann ihren Inhalt in Text- oder XML-Dateien schreiben und diesen wieder auslesen. Sicherlich wären Spieldaten im Zugriff schneller, wenn wir sie als Klassenhierarchie aufbewahren, aaber dann müssten wir dieses selbst tun.
Wie arbeite ich jetzt aber damit?
Zuallererst einmal müssen meine Spieleviews wissen, dass es einen Spielstand gibt. Dieser wird zentral verwaltet und den Views übergeben. Dazu erweitern wir unser IGameView
Meine Activity wird Wirt meiner Spieledaten. Dort werden sie instanziiert und beim Erstellen der Views über das Interface übergeben. Dies macht übrigens auch da sinn, wo eigentlich nicht gespielt wird. Beispielsweise braucht ein Menü kein Save anbieten, wenn der Spielstand leer ist.
Wieder müssen wir das Interface ständig neu implementieren, bzw. pro Viewtyp vererben. Mein Hauptmenü sieht dann so aus:
Als kleines Schmankerl hier noch meine loadgame und savegame-Klassen. Diese sind abgeleitet von einer FileView-Klasse.
Mein LoadGame sieht dann ca. so aus:
und das SaveGame
Soo, jetzt haben wir eigentlich alles, was wir benötigen, um eine Spiele-View zu schreiben. Nur, hat diese am Anfang gar keine Daten. Ich habs mir da einfach gemacht. Wenn mein GameView am Anfang keine Datem jat, die gameDate.isEmpty() also true liefern, wird einfach ein initialer Spielstand aus den assets geladen:
Der nächste Teil zeigt dann, wie man eine View mit GameLoop baut, um z.b. KI-Spieler für sich arbeiten zu lassen..
Ich hoff, ich hab euch ein wenig was Interessantes erzählen können. Kritik und Anregungen sind wie immer erwünscht.
Jetzt möchte ich gern zeigen, wie man auf einfache Weise seinen Spielstand vorhalten, laden und speichern kann.
Gehen wir mal davon aus, dass wir ein Spiel haben (wie das dann funktioniert, folgt in einem weiteren Teil) in dem z.B. wir die Informationen Spielername, Level, Punktzahl und Lebensenergie speichern wollen.
Wenn man sich nicht noch über Dateiformate Gedanken machen möchte, gibt es eine sehr einfache Möglichkeit in Java, Daten vorzuhalten: die Klasse Properties.
Sie besteht prinzipiell aus einer Map, die Schlüssel-Wertpaare beinhaltet, hat aber noch eine entscheidene Eigenschaft: Sie kann ihren Inhalt in Text- oder XML-Dateien schreiben und diesen wieder auslesen. Sicherlich wären Spieldaten im Zugriff schneller, wenn wir sie als Klassenhierarchie aufbewahren, aaber dann müssten wir dieses selbst tun.
Wie arbeite ich jetzt aber damit?
Zuallererst einmal müssen meine Spieleviews wissen, dass es einen Spielstand gibt. Dieser wird zentral verwaltet und den Views übergeben. Dazu erweitern wir unser IGameView
Code:
public interface IGameView {
/**
* @return game data object
*/
public Properties getGameData();
/**
* @param data game data object
*/
public void setGameData(Properties data);
/**
* @param setter Instance of the program setting the active view
*/
public void setViewSetter (IViewSetter setter);
}
Meine Activity wird Wirt meiner Spieledaten. Dort werden sie instanziiert und beim Erstellen der Views über das Interface übergeben. Dies macht übrigens auch da sinn, wo eigentlich nicht gespielt wird. Beispielsweise braucht ein Menü kein Save anbieten, wenn der Spielstand leer ist.
Code:
public class MainActivity extends Activity implements IViewSetter {
// Die Spielstandsdaten- Neudeutsch: Geschäftsobjekt
Properties gameData = new Properties();
public void setView(String, viewName) {
...
// mache den ViewSetter bekannt
view.setViewSetter(this);
// mache der View den Spielstand bekannt.
view.setGameData(gameData);
// setze die neue View
setContentView ((View) view);
}
Wieder müssen wir das Interface ständig neu implementieren, bzw. pro Viewtyp vererben. Mein Hauptmenü sieht dann so aus:
Code:
public class MainMenu extends LinearLayout implements IGameView,
OnClickListener {
IViewSetter setter = null;
Properties gameData = new Properties();
Button startGame = null;
Button loadGame = null;
Button saveGame = null;
Button exit = null;
private Button createMenuButton(String name) {
Button b = new Button(getContext());
b.setText(name);
b.setVisibility(VISIBLE);
b.setPadding(2, 2, 2, 2);
b.getBackground().setAlpha(0x80);
b.setClickable(true);
b.setOnClickListener(this);
this.addView(b);
return b;
}
public MainMenu(Context context) {
super(context);
try {
// set visibility
this.setVisibility(VISIBLE);
// gravity is bottom
this.setOrientation(VERTICAL);
this
.setGravity(Gravity.CENTER_VERTICAL
| Gravity.CENTER_HORIZONTAL);
startGame = this.createMenuButton("Start Game");
loadGame = this.createMenuButton("Load Game");
// wenn Daten im Spieleobjekt enthalten sind,
// stelle den save-button dar
if (!this.gameData.isEmpty()) {
saveGame = this.createMenuButton("Save Game");
}
exit = this.createMenuButton("Exit");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public Properties getGameData() {
return gameData;
}
@Override
public void setGameData(Properties data) {
this.gameData = data;
}
@Override
public void setViewSetter(IViewSetter setter) {
this.setter = setter;
}
@Override
public void onClick(View v) {
Log.d("TraderGame", "item click");
if (v == startGame) {
this.setter.setView("gameloop");
} else if (v == loadGame) {
this.setter.setView("loadgame");
} else if (v == saveGame) {
this.setter.setView("savegame");
} else if (v == exit) {
this.setter.setView("exit");
}
}
}
Als kleines Schmankerl hier noch meine loadgame und savegame-Klassen. Diese sind abgeleitet von einer FileView-Klasse.
Code:
public abstract class FileListView extends LinearLayout implements IGameView,
OnItemClickListener, OnClickListener {
protected IViewSetter setter = null;
protected Properties gameData = new Properties();
protected File directory = null;
protected ListView list = null;
protected EditText filename = null;
protected Button go = null;
/**
* @param context
*/
public FileListView(Context context) {
super(context);
// Savegame-Verzeichnis erstellen
createDirectory(context);
// set visibility
this.setVisibility(VISIBLE);
// gravity is bottom
this.setOrientation(VERTICAL);
this.setGravity(Gravity.TOP | Gravity.LEFT);
LinearLayout view2 = new LinearLayout(context);
view2.setOrientation(HORIZONTAL);
view2.setGravity(Gravity.LEFT);
// add filename textfield
this.filename = new EditText(context);
this.filename.setWidth(250);
view2.addView(filename);
// add run button
this.go = new Button(context);
this.go.setText("go");
this.go.setOnClickListener(this);
view2.addView(go);
this.addView(view2);
// add ListView
this.list = new ListView(context);
this.list.setId(R.id.ListView01);
this.list.setOnItemClickListener(this);
String fileList[] = directory.list();
this.list.setAdapter(new ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, fileList));
this.addView(this.list);
}
/**
* create data directory
*
* @param context
*/
private void createDirectory(Context context) {
directory = new File("/sdcard/."
+ context.getResources().getString(R.string.app_name));
// Erstelle das Unterverzeichnis für die Savegames, falls nötig
if (!directory.isDirectory()) {
directory.mkdir();
}
}
/*
* (non-Javadoc)
*
* @see de.docjunior.gameengine.IGameView#getGameData()
*/
@Override
public Properties getGameData() {
return this.gameData;
}
/*
* (non-Javadoc)
*
* @see de.docjunior.gameengine.IGameView#setGameData(java.util.Properties)
*/
@Override
public void setGameData(Properties data) {
this.gameData = data;
}
/*
* (non-Javadoc)
*
* @see
* de.docjunior.gameengine.IGameView#setViewSetter(de.docjunior.gameengine
* .IViewSetter)
*/
@Override
public void setViewSetter(IViewSetter setter) {
this.setter = setter;
}
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int item, long arg3) {
this.filename.setText(this.list.getItemAtPosition(item).toString());
}
/**
* gets the created file
* @return
*/
protected File getFile() {
File f = null;
if (!this.filename.getText().equals("")) {
String dir = this.directory.getAbsolutePath();
dir += "/" + this.filename.getText();
dir.replace(" ", "_");
Log.d(getContext().getString(R.string.app_name), "File:" + dir );
f = new File (dir);
}
return f;
}
}
Mein LoadGame sieht dann ca. so aus:
Code:
public class LoadGame extends FileListView {
/**
* @param context
*/
public LoadGame(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
/* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick(View v) {
if (v == this.go) {
InputStream is;
try {
is = new FileInputStream(getFile());
this.gameData.load(is);
this.setter.setView("gameloop");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
und das SaveGame
Code:
public class SaveGame extends FileListView {
/**
* @param context
*/
public SaveGame(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
/*
* (non-Javadoc)
*
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick(View v) {
if (v == this.go) {
OutputStream os;
try {
os = new FileOutputStream(getFile());
this.gameData.store(os, "save game at" + new Date());
this.setter.setView("mainmenu");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Soo, jetzt haben wir eigentlich alles, was wir benötigen, um eine Spiele-View zu schreiben. Nur, hat diese am Anfang gar keine Daten. Ich habs mir da einfach gemacht. Wenn mein GameView am Anfang keine Datem jat, die gameDate.isEmpty() also true liefern, wird einfach ein initialer Spielstand aus den assets geladen:
Code:
public void setGameData(Properties gameData) {
// TODO Auto-generated method stub
this.gameData = gameData;
if (gameData.isEmpty()) {
InputStream in;
try {
in = getContext().getAssets().open("newgame.properties");
gameData.load(in);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Der nächste Teil zeigt dann, wie man eine View mit GameLoop baut, um z.b. KI-Spieler für sich arbeiten zu lassen..
Ich hoff, ich hab euch ein wenig was Interessantes erzählen können. Kritik und Anregungen sind wie immer erwünscht.