Categories: Java | Java ME | Games | How To | Code Examples
This page was last modified 18:07, 29 November 2007.
How to develop a game - Part 5
From Forum Nokia Wiki
After our last lesson we had a completely functional Arkanoid midlet but one important thing is still missing, can you guess what? To save the high scores! Until know our high scores were lost each time we exit the application (the same happens for the game settings). What's the point of playing our game if we cannot save the scores to show off to our friends!!!
To implement this functionality you need to understand how the Midlet Input Output works and then how to use RecordStores.
Streams
Let's start with the simple IO operations, the classes that implement them are in the java.io package. JavaMe only has a fraction of the classes available in JavaSE but they are the most useful:
- Input Stream, Output Stream: the base classes for binary bytes streams
- ByteAraryInputStream, ByteArrayOutputStream: streams to buffer arrays in memory
- DataInputStream, DataOutputStream: read and write primitive Java types(int, float, String, ...) to streams
- Reader, Writer: the base classes for character streams
- OutputStreamWriter, InputStreamReader: classes for read characters streams using encodings
To exemplify the use of these classes let's create a settings file were we store some of the game options:
- Number of lifes
- Ball speed
- Time to complete a level
- Number of points for each brick we hit
To implement this setting file we are going to create a simple text file "settings.txt" where each line represents a setting, the setting name and it's value are separated by a "=" character.
ball_speed=2 start_lifes=4 level_time=100 brick_points=10
After you create this file add it to your project resources and make sure this file will be stored inside the jar file along with your classes. Now how to access this file? You need use the method getResourceAsStream(String name) from the Class class. This method gives you access to any file stored inside your jar file. The root path "/" points to the root level of you jar file. For example to access the settings file we can use the following code:
InputStream is = this.getClass().getResourceAsStream("/settings.txt"); // read first byte byte c= is.read();
This code gives you the first byte of the file, but what you really need is to read each line as an character stream for that we can use the InputStreamReader and then parse each line for the settings key and value.
public class Settings { public Hashtable values; public Settings(String file) { values = new Hashtable(10); read(file); } public String getSetting(String key){ return (String)(values.get(key)); } /** * Opens the file and reaas all the settings to the hashtable * @param file, the name of the file to read */ public void read(String file){ // open file InputStream is = this.getClass().getResourceAsStream(file); InputStreamReader isr = new InputStreamReader(is); // create a buffer to store lines StringBuffer lineBuffer = new StringBuffer(); int c; try { c = isr.read(); while(c != -1){ lineBuffer.append((char)c); c = isr.read(); // checks for end of line character if ( c == 10 || c==-1){ // cleans extra spaces or end of lines chars String line = lineBuffer.toString().trim(); // splits the string using the = character int pos = line.indexOf("="); if (pos != -1){ // adds a new setting String key = line.substring(0,pos); String value = line.substring(pos+1); values.put(key, value); } // clean buffer lineBuffer.setLength(0); } } } catch (IOException e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }
Now you just need to use this class in our Canvas class in our init() method:
public void initSettings(){ Settings setting = new Settings("/settings.txt"); BALL_SPEED = Integer.parseInt(setting.getSetting("ball_speed")); MAX_LIFES = Integer.parseInt(setting.getSetting("start_lifes")); MAX_TIME = Integer.parseInt(setting.getSetting("level_time")); BRICK_SCORE = Integer.parseInt(setting.getSetting("brick_points")); } public void init(){ initSettings(); [...] }
Ok, know we have a configuration file, but still you ask how can i save my highscores!! The getResourceAsStream() method only allows you to read files stored in the jar not to write to them.
RecordStore
To write to files you need to use the RecordStore class, this class allows you to create mini-databases where you can store and retrieve data, that is persistent between evocations of the midlet.
This class is implemented in the javax.microedition.rms package and it provides the following static constructor methods:
- openRecordStore(String recordStoreName, boolean createIfNecessary)
- openRecordStore(String recordStoreName, boolean createIfNecessary, int authmode, boolean writable)
- openRecordStore(String recordStoreName, String vendorName, String suiteName)
All these methods allow to create and/or open an record store. The names of the record stores are case sensitive and have a maximum of 32 characters. One special feature to take in account is the authmode setting. This setting as two values:
- AUTHMODE_PRIVATE, in this mode only the Midlet that created the RecordStore can have access to it.
- AUTHMODE_ANY, in this mode any Midlet can open the record store, read data from it and if the writable setting is true write data to it. This allow you to share data between Midlets, so for example you can have two different games that share the high scores tables. The RecordStores are unique identified by their creation name and the Midlet-Name,Midlet-Vendor properties in the Jad file.
After you open the RecordStore you can add records to it, each record is made of an array of bytes and it's identified by a unique id at the moment of it's creation. After you create an record you can retrieve it's data, change the data and delete it. The following methods support these operations:
- addRecord(byte[] data, int offset, int numBytes)
- deleteRecord(int recordId)
- setRecord(int recordId, byte[] newData, int offset, int numBytes)
The maximum size of an RecordStore is device dependent, you can use the getSizeAvailable() method, but the value returned isn't accurate in the majority of the devices.
Now that we understand the RecordStore class better let's use it to save our high scores:
public void saveData() { try { // open records store options RecordStore options = RecordStore.openRecordStore("options", true); byte[] data = saveOptions(); // check if record store not empty if (options.getNumRecords() != 0) { // update the settings options.setRecord(1, data, 0, data.length); } else { // adds the settings options.addRecord(data, 0, data.length); } // closes the record store options.closeRecordStore(); } catch (RecordStoreException ex) { } } public byte[] saveOptions() { // create a byte array stream to store data temporarily ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); try { dos.writeBoolean(soundOn); // write scores for (int i =0; i < scores.length; i++){ dos.writeInt(scores[i].value); dos.writeUTF(scores[i].name); dos.writeLong(scores[i].when.getTime()); } // push all the data to the byte array stream dos.flush(); } catch (IOException ex) { } // returns bytes from stream return baos.toByteArray(); }
As you can see we are creating a "options" Record Store and use it to store our settings and the high scores. For the save process we are using an DataOutputStream with an ByteArrayOutputStream to facilitate our work to convert our data to an binary array.
Now we just need to implement the reading of data:
public void loadData() { try { RecordStore options = RecordStore.openRecordStore("options", true); // check if record store not empty if (options.getNumRecords() != 0) { loadOptions(options.getRecord(1)); } options.closeRecordStore(); } catch (RecordStoreException ex) { } } public void loadOptions(byte[] data) { // create a byte array stream to store data temporarily ByteArrayInputStream bais = new ByteArrayInputStream(data); // creates a data input stream to read from DataInputStream dis = new DataInputStream(bais); try { soundOn = dis.readBoolean(); // read scores for (int i = 0; i < scores.length; i++) { int value = dis.readInt(); String name = dis.readUTF(); Date date = new Date(dis.readLong()); scores[i] = new Score(value, name, date); } dis.close(); } catch (IOException ex) { } }
We simple open the record store and check if it has a record available. If this is the case retrieve the data and use the an DataInputStream combined with an ByteArrayInputStream.
Now we just need to add calls for these methods on our startup and shutdown of the Midlet.
public void initOptions() { soundOn = true; if (scores == null) { scores = new Score[10]; for (int i = 0; i < scores.length; i++) { scores[i] = new Score(0, "Empty", new Date()); } } // loads data in record stores if available loadData(); } public void exit() { // store high scores and setting to record store saveData(); notifyDestroyed(); }
After this last changes, we finally have our game saving the player high scores and settings. Next lesson we finally put to use our game settings, we are going to add sound to our game! See you soon.
Downloads:
| Related Discussions | ||||
| Thread | Thread Starter | Forum | Replies | Last Post |
| Sending mastermind game "question pegs" to another player to play | hong1981 | Mobile Java Networking & Messaging & Security | 0 | 2004-07-22 19:00 |
| Sending mastermind game "question pegs" to another player to play | hong1981 | Mobile Java General | 0 | 2004-07-22 19:02 |
| Making my game available for download | smb101 | Mobile Java General | 3 | 2004-06-24 14:00 |
| audio weirdness... help please | KlaarMobileEntertainment | Symbian Media (Graphics & Sounds) | 0 | 2006-06-30 14:29 |
| making your game available. | smb101 | Mobile Java General | 0 | 2003-12-24 10:32 |
