Serialization in java


package moc.em.mygdxgame;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;

public class Player implements Serializable {

	/**
	 * The "Serializable" interface is a class that lets you "read" or "write" 
	 * the object on to the disk, The TextureRegion is to show a certain area inside a sprite sheet
	 * Some of the objects which was initialized in this class is not serializable, so we can only save certain data
	 * 
	 */
	
	private static final long serialVersionUID = 1L;
	private String file;
	private Vector2 position;
	private static final int column = 4;
	private static final int row = 4;
	private transient Animation animation;
	private transient Texture texture;
	private transient TextureRegion[] frame;
	private static final String saveFile = "player.dat";
	private float stateTime;
	private transient TextureRegion currentFrame;
	private Rectangle bounds;
	
	public Player(String file, Vector2 position){
		this.file = file;
		this.position = new Vector2(position);
		texture = new Texture(Gdx.files.internal(file));
		TextureRegion[][] tr = TextureRegion.split(texture, texture.getWidth()/column, texture.getHeight()/row);
		frame = new TextureRegion[column * row];
		
		int index = 0;
		for(int i = 0; i < row; i++){
			for(int j = 0; j < column; j++){
				frame[index++] = tr[i][j]; // did not know you can have the index do that inside an array "index++"
			}
		}
		animation = new Animation(1,frame);
		stateTime = 0f;
		currentFrame = animation.getKeyFrame(0);
		bounds = new Rectangle(position.x, position.y, currentFrame.getRegionWidth(), currentFrame.getRegionHeight());

	}
		
	@SuppressWarnings("unused")
    private static byte[] serialize(Object obj) throws IOException {//This method converts an object to a byte array
            ByteArrayOutputStream b = new ByteArrayOutputStream();
            ObjectOutputStream o = new ObjectOutputStream(b);
            o.writeObject(obj);
            o.flush();
            return b.toByteArray();
    }
   
    public static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {//This converts byte array to an object
            ByteArrayInputStream b = new ByteArrayInputStream(bytes);
            ObjectInputStream o = new ObjectInputStream(b);
            return o.readObject();
    }	
	
	public static void savePlayer(Player player) throws IOException {
		FileHandle fh = Gdx.files.local(saveFile); //relative path
		OutputStream out = null;
        try {
            fh.writeBytes(serialize(player), false); //It is a byte array :)
        }catch(Exception e){
            System.out.println(e.toString());
        }finally{
            if(out != null) {
            	try{
            		out.close();
            	} catch (Exception ex){}
            }
        }
   
    System.out.println("Saving Player");
	}
	
	public static Player loadPlayer() throws IOException, ClassNotFoundException {
        Player player = null;
        FileHandle fh = Gdx.files.local("player.dat");
        player = (Player) deserialize(fh.readBytes()); // Its an object again :)
        return player;
    }
    
	public void update(){
		bounds.set(position.x, position.y, currentFrame.getRegionWidth(), currentFrame.getRegionHeight());
		if(stateTime < 4){
			stateTime += Gdx.graphics.getDeltaTime();
		} else {
			stateTime = 0;
		}
		if(Gdx.input.isKeyPressed(Keys.W)){
			position.y += 1f;
			currentFrame = animation.getKeyFrame(12 + stateTime);
		} else if(Gdx.input.isKeyPressed(Keys.A)){
			position.x -= 1f;
			currentFrame = animation.getKeyFrame(4 + stateTime);
		} else if(Gdx.input.isKeyPressed(Keys.S)){
			position.y -= 1f;
			currentFrame = animation.getKeyFrame(0 + stateTime);
		} else if(Gdx.input.isKeyPressed(Keys.D)){
			position.x += 1f;
			currentFrame = animation.getKeyFrame(8 + stateTime);
		}
	}
	
	public void draw(SpriteBatch sb){
		sb.draw(currentFrame, position.x, position.y);
	}	
	public String getPlayerImage(){
		return file;
	}
	
	public Texture getTexture(){
		return texture;
	}
	
	public Vector2 getPosition(){
		return position;
	}
		
	public void changePlayer(String file){
		this.file = file;
	}
	
	public Animation getAnimation() {
		return animation;
	}

	public void setAnimation(Animation animation) {
		this.animation = animation;
	}

	public TextureRegion[] getFrame() {
		return frame;
	}

	public void setFrame(TextureRegion[] frame) {
		this.frame = frame;
	}

	public TextureRegion getCurrentFrame() {
		return currentFrame;
	}

	public void setCurrentFrame(TextureRegion currentFrame) {
		this.currentFrame = currentFrame;
	}

	public float getStateTime() {
		return stateTime;
	}

	public void setStateTime(float stateTime) {
		this.stateTime = stateTime;
	}		
    public String getFile() {
		return file;
	}

	public void setFile(String file) {
		this.file = file;
	}

	public Rectangle getBounds() {
		return bounds;
	}

	public void setBounds(Rectangle bounds) {
		this.bounds = bounds;
	}
}

So I have three classes which are not serialize. Which are the: TextureRegion, Texture, and Animation class. I am trying to find a way to save the entire player class. I cannot use transient, because There is a render method in another class which always calls the update method. I would get a nullpointer from “bounds.set(position.x, position.y, currentFrame.getRegionWidth(), currentFrame.getRegionHeight());” If I use transient.

Since you seem to be using libGDX, I highly recommend the serialization solutions provided in the library, they should take care of your needs.
It is noted that Textures should not be serialized, but instead saved as a file path or similar and re-loaded when de-serialized.
If you don’t use JSON or Kryo, I would have the TextureRegions saved as a rectangle with a string referencing the texture file they are to be attached to.
The animations should probably be handled similarly to the textures.

Also consider what you’re saving, a copy of the player object, or information used to reconstruct that object? For instance, it might be simpler to write to a ‘saved_game’ file with something like the following:


Player:
   xPosition: 20
   yPosition: 68
   stateTime: 5693
   currentAnimation: "walking_3"
   texture: "/textures/player2.png"
   bounds: 23 45 43 45

And then simply load from that file. This is essentially what the JSON serialization is, and has the advantage over regular Java serialization in that if you change the Player class, it doesn’t break save files.

If you are doing something simple like saving basic coords and stuff there is no need to go so far as to use serialization. It would be more simple to just use libGDX’s XML capabilities. This way you can easily read and edit the file.

Check this out: http://code.google.com/p/libgdx/wiki/XmlParsing

Plus, if you don’t know much about XML style languages it would be a good chance to learn.

Thanks for the input, @Slyth2727 I do know how to work in xml. But I wanted to try out the serialization interface, since I never used it before. @BurntPizza Thanks for the references, I am going to look into it. I was also thinking of the thing, reloading the image when it is de-serialized.