1. Nimm jetzt an unserem 2. ADVENT-Gewinnspiel teil - Alle Informationen findest Du hier!

Meine Gedanken zur Spieleprogrammierung - Teil 2 - Spielstandsdaten

Dieses Thema im Forum "Android Spiele Entwicklung" wurde erstellt von DocJunioR, 16.07.2009.

  1. DocJunioR, 16.07.2009 #1
    DocJunioR

    DocJunioR Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    56
    Erhaltene Danke:
    6
    Registriert seit:
    25.06.2009
    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
    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.
     
  2. Temar, 16.07.2009 #2
    Temar

    Temar Erfahrener Benutzer

    Beiträge:
    214
    Erhaltene Danke:
    14
    Registriert seit:
    25.06.2009
    Kleine Anmerkung:
    Für Android gibt's auch die Klasse Preference: Preference | Android Developers Sie speichert ihre Daten in einer sqlite Datenbank. Vorteil ist, dass man von überall aus zugreifen kann, ohne sich Gedanken über Dateienamen oder Speicherorte machen zu müssen. Vermutlich ist sie auch ein wenig schneller, da nicht jedesmal ein XML file geladen und geparst werden muss.

    Man holt sich einfach eine SharedPreferences editor Instanz und kann dann ganz bequem alles reinschreiben.
     
  3. DocJunioR, 16.07.2009 #3
    DocJunioR

    DocJunioR Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    56
    Erhaltene Danke:
    6
    Registriert seit:
    25.06.2009
    ja, aaaber dann kannst du meines Wissens nur einen Spielstand laden. Es muss ja auch nicht xml sein. So, wie's im Code steht, speichert er einfache Schlüssel = Wert - Paar in Textdateien.
    Ansonsten ists aber auch Auffassungsfrage ;) hab ja gesagt, bin nicht der Weisheit letzter Schluss.

    Edit: Hab mir gerade die Preferences genauer angeschaut. Sie arbeiten auch über einen XML-Baum, sind aber im Gegensatz zu Properties hierarchisch aufbaubar. Sogar im- und exportieren kann man. Bei komplexeren Daten sicher garnicht so schlecht.
    Ich kenn halt die Android-API noch nicht soo gut und beschreibe eben auch nur meine Gedankengänge, als JavaSEler
     
    Zuletzt bearbeitet: 16.07.2009
  4. swordi, 16.07.2009 #4
    swordi

    swordi Gewerbliches Mitglied

    Beiträge:
    3,389
    Erhaltene Danke:
    441
    Registriert seit:
    09.05.2009
    kleiner tipp: von dateien ist generell abzuraten. ( in sehr seltenen fällen mögen sie ja sinnvoll sein, aber ganz ganz ganz selten )

    es gibt diese shared preferences und die sqlite db. die datenbank liegt ja direkt am händy, wodurch der zugriff ja extrem schnell ist ( im vergleich natürlich )
     
  5. DocJunioR, 16.07.2009 #5
    DocJunioR

    DocJunioR Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    56
    Erhaltene Danke:
    6
    Registriert seit:
    25.06.2009
    naaja, ich weiß nicht, ob es sich wirklich lohnt, für sowas ne datenbank zu entwickeln und eigenlich wird die datei ja nur zum lade- und speicherzeitpunkt angefasst, der rest läuft im ram ab . insofern ist performance eher unkritisch.
    prinzipiell ist es aber egal, wie persistiert wird. der transport des geschäftsobjekts- wie auch immer dieses aussieht- bleibt gleich ;)
    Ich persönlich bleibe vorerst bei dateien.
     
  6. swordi, 16.07.2009 #6
    swordi

    swordi Gewerbliches Mitglied

    Beiträge:
    3,389
    Erhaltene Danke:
    441
    Registriert seit:
    09.05.2009
    ja war auch nur ein hinweis, wie man es in der android welt eher bevorzugen sollte

    db entwickeln ist dafür schon ein wenig hoch gegriffen;) einfach eine tabelle mit paar werten und passt

    aber viele wege führen nach rom , das ist klar
     
  7. DocJunioR, 16.07.2009 #7
    DocJunioR

    DocJunioR Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    56
    Erhaltene Danke:
    6
    Registriert seit:
    25.06.2009
    eeben.und ich bin der letzte, der irgendwas dogmatisiert
     

Diese Seite empfehlen