Posts Tagged ‘java’

Android and Desktop games internationalization using Java and LibGDX

Wednesday, May 23rd, 2012

Recently, we had to add multiple language support for a game we are developing. As you may know, Java provides classes to simplify the task of making your application available in multiple languages. In this post we want to share a bit our experience when using Java localization classes in a LibGDX application to provide multiple language support for both Android and desktop platforms.

Quick introduction

Java provides a class named ResourceBundle which provides a way to store resources (mainly strings) for a given locale, so you can ask for a string identified by a key and it will return the text depending the current locale. You can read the article Java Internationalization: Localization with ResourceBundles if you want to know more about how to use Java classes for internationalization. The rest of the post assumes you know something about Locale and ResourceBundle classes.

Why we don't use Android resources

Android provides also a way to support multiple locale resources but it depends on Android API, so we prefer to use the Java API instead which should work on all platforms.

Our experience when using Java internationalization on Android

When letting ResourceBundle to automatically load resources bundles from properties files, Java expects them to be in ISO-8859-1 encoding. However, it seems Android behaves in a different way and expects another encoding by default. So, when resource bundles are automatically loaded in Android from an ISO-8859-1 properties file with special characters, it loads them wrong.

The first try

The first solution we tried to fix this was to call ResourceBundle.getBundle() method using a custom Control implementation which creates PropertyResourceBundles using an InputReader with the correct encoding. Here is a code example to achieve that:

public class EncodingControl extends Control {

	String encoding;

	public EncodingControl(String encoding) {
		this.encoding = encoding;
	}

	@Override
	public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) 
			throws IllegalAccessException, InstantiationException, IOException {
		String bundleName = toBundleName(baseName, locale);
		String resourceName = toResourceName(bundleName, "properties");
		ResourceBundle bundle = null;
		InputStream inputStream = null;
		try {
			inputStream = loader.getResourceAsStream(resourceName);
			bundle = new PropertyResourceBundle(new InputStreamReader(inputStream, encoding));
		} finally {
			if (inputStream != null)
				inputStream.close();
		}
		return bundle;
	}
}

After that, we customized the Control class to work with LibGDX FileHandle in order to place the properties files in the assets folder. Here is the final code for our Control implementation:

public class GdxFileControl extends Control {

	private String encoding;
	private FileType fileType;

	public GdxFileControl(String encoding, FileType fileType) {
		this.encoding = encoding;
		this.fileType = fileType;
	}
	
	public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) 
			throws IllegalAccessException, InstantiationException, IOException {
		// The below is a copy of the default implementation.
		String bundleName = toBundleName(baseName, locale);
		String resourceName = toResourceName(bundleName, "properties");
		ResourceBundle bundle = null;
		FileHandle fileHandle = Gdx.files.getFileHandle(resourceName, fileType);
		if (fileHandle.exists()) {
			InputStream stream = null;
			try {
				stream = fileHandle.read();
				// Only this line is changed to make it to read properties files as UTF-8.
				bundle = new PropertyResourceBundle(new InputStreamReader(stream, encoding));
			} finally {
				if (stream != null)
					stream.close();
			}
		}
		return bundle;
	}
}

And that can be called in this way:

	ResourceBundle.getBundle("messages", 
		new GdxFileControl("ISO-8859-1", FileType.Internal))

That worked really well until we discovered that Android API sucks and doesn't support ResourceBundle.Control before API level 9, that means our solution works only for users with Android 2.3+. That's a problem since we want to support 2.0+, so we had to think another way to solve this.

The second try

After some tests, we discovered that if we construct a PropertyResourceBundle using an InputStream, the expected encoding is ISO-8859-1 for both desktop and Android. That means that, if we use that specific PropertyResourceBundle constructor, we don't have to force the encoding. So, the new solution consists in building a PropertyResourceBundle for each locale and configuring the hierarchy ourselves by setting their parent ResourceBundle. Here is an example of what we do now:

	FileHandle rootFileHandle = Gdx.files.internal("data/messages.properties");
	FileHandle spanishFileHandle = Gdx.files.internal("data/messages_es.properties");
	ResourceBundle rootResourceBundle = new PropertyResourceBundle(rootFileHandle.read());
	ResourceBundle spanishResourceBundle = new PropertyResourceBundle(spanishFileHandle.read()) {{
		setParent(rootResourcebundle);
	}};

After that we created a map of ResourceBundles for each Locale we support, so we can call something like:

	ResourceBundle resourceBundle = getResourceBundle(new Locale("es"));

The good part is this solution works well for both Android and desktop despite the Android API level (PropertyResourceBudndle seems to be supported from API Level 1). The bad part is that we lost the ResourceBundle logic to automatically build the hierarchy of resources and we had to do that manually now.

UPDATE: The class we use for this stuff is available in our commons-gdx project, resources module with the name of ResourceBundleResourceBuilder.

Conclusion

Supporting multiple languages in an application is a way to say users of all around the world you care about them but translating text to several languages is not cheap at all, however, Java provides a good framework to simplify the job if you decide to support internationalization.

And as a side conclusion: never assume all the Java classes you are using are implemented for the minimum Android API you are targeting.

References

Animation4j - Interpolation Functions

Saturday, March 26th, 2011

Interpolation Functions are a key concept in animation4j project, they define how to a variable should change from one value to another. To define interpolation functions in animation4j, InterpolatorFunction interface should be implemented, the API looks like this:

public interface InterpolatorFunction {

	/**
	 * @param t
	 *            A real number in the interval [0,1]
	 * @return The interpolated value.
	 */
	float interpolate(float t);

}

So basically, functions works for values of t between interval [0,1]. An example implementation of this function is a linear Bézier:

/**
 * Linear Bézier curve implementation of an InterpolatorFunction.
 * 
 */
public class LinearBezierInterpolatorFunction implements InterpolatorFunction {

	private final float p0, p1;

	public LinearBezierInterpolatorFunction(float p0, float p1) {
		this.p0 = p0;
		this.p1 = p1;
	}

	@Override
	public float interpolate(float t) {
		if (t < 0)
			return p0;
		if (t > 1)
			return p1;
		return (1 - t) * p0 + t * p1;
	}

}

The library already provides some implementations, you could instantiate them using InterpolatorFunctionFactory factory class. The API of the factory looks like this:

	// returns a cubic Bézier interpolation function based on the four specified points.
	InterpolatorFunction cubicBezier(float p0, float p1, float p2, float p3);

	// returns a quadratic Bézier interpolation function based on the four specified points.
	InterpolatorFunction quadratic(float p0, float p1, float p2);

	// returns a cubic Bézier interpolation function with presetted values.
	InterpolatorFunction ease();

	// returns a linear Bézier interpolation function.
	InterpolatorFunction linear();

	// returns a cubic Bézier interpolation function with presetted values.
	InterpolatorFunction easeIn();

	// returns a cubic Bézier interpolation function with presetted values.
	InterpolatorFunction easeOut();

	// returns a cubic Bézier interpolation function with presetted values.
	InterpolatorFunction easeInOut();

In the previous post we talked about transitions of variables from one value to another using Transition interface. When building a transition using the Transitions factory, you could specify the interpolation functions you want to use, if you don't, then linear Bézier functions are used by default. One example of how to create a Transition specifying the interpolation functions looks like this:

Transition<Vector2f> transition = Transitions.transition(
	new Vector2f(0f, 0f), 					// the starting value
	vector2fConverter, 						// the type converter (using two variables)
	InterpolatorFunctionFactory.easeOut(),	// the interpolation function for the first variable
	InterpolatorFunctionFactory.easeIn());	// the interpolation function for the second variable

One thing to mention is that Transitions factory methods use a varargs to specify interpolation functions parameters because we don't know beforehand how many variables you need. In case you specify less functions than variables, then linear Bézier functions will be used for the variables without functions specified.

As we said on the previous post, animation4j API could change since this blog post was written. API used for this post is the one of the current released version 0.0.8 (already uploaded to maven central).

VN:F [1.9.22_1171]
Rating: 0.0/5 (0 votes cast)

Animation4j - Transitions

Friday, March 25th, 2011

As we said on a previous post, we are going to talk a bit more about the current released version (0.0.8) of animation4j, remember that the API could change since we wrote this post.

In this case, we are going to talk about making a transition from one value to another in some time. Animation4j provides you one interface to make transitions, the Transition interface, the API by now looks like this:

public interface Transition<T> {

	/**
	 * Returns the current value of the transition.
	 * 
	 */
	T get();

	/**
	 * Start an interpolation from a to b in the specified default time.
	 * 
	 * @param t
	 *            The wanted new value.
	 */
	void set(T t);

	/**
	 * Start an interpolation from a to b in the specified time.
	 * 
	 * @param t
	 *            The wanted new value.
	 * @param time
	 *            The time to set the new value. If time is zero, then value is applied directly.
	 */
	void set(T t, int time);
}

The library uses generics so you can create transitions of any type. To do this, you can use the Transitions factory class, where you can create transitions for your types. The API of the factory class looks like this:

<T> Transition<T> transition(T startValue, TypeConverter<T> typeConverter);

The current Transition generic implementation internally work with a float[] in order to optimize memory and to simplify internal work. To use it, you will have to create a converter from your type to float[] and vice versa by implementing the TypeConverter<T> interface. Current API of TypeConverter<T> looks like this:

/**
 * Provides a way to convert an object in a float[] array and vice versa, for interpolation purposes.
 * 
 * @param <T>
 *            The type to convert.
 * @author acoppes
 */
public interface TypeConverter<T> {

	/**
	 * Returns the quantity of variables are used to convert the object to the float[] and vice versa.
	 * 
	 * @return the quantity of variables used.
	 */
	int variables();

	/**
	 * Copy the values of the object to the specified float array, if null it will create a new float array.
	 * 
	 * @param object
	 *            The object from where to get the values to fulfill the float array.
	 * @param x
	 *            The float array to copy the values of the object. If null it will create a new float array.
	 * @return The float array with the values of the object.
	 */
	float[] copyFromObject(T object, float[] x);

	/**
	 * Copy the values of the float array to the specified object.
	 * 
	 * @param object
	 *            The object which the float array values will be copied to. If null or object immutable, it will create a new object.
	 * @param x
	 *            The float array to get the values to fulfill the object.
	 * @return An object with the values of the float array.
	 */
	T copyToObject(T object, float[] x);

}

Type converters should be stateless, so you can reuse a single type converter for all your transitions of the same type. For the next Vector2f class example:

public class Vector2f {

	public float x,y;

	public Vector2f(float x, float y) {
		set(x,y);
	}

	public void set(float x, float y) {
		this.x = x;
		this.y = y;
	}

}

We could create the next type converter:

public class Vector2fConverter implements TypeConverter<Vector2f> {

	@Override
	public float[] copyFromObject(Vector2f v, float[] x) {
		if (x == null) 
			x = new float[variables()];  // don't worry about garbage generation, the transition implementation will cache these values.
		x[0] = v.x;
		x[1] = v.y;
		return x;
	}

	@Override
	public Vector2f copyToObject(Vector2f v, float[] x) {
		if (v == null)
			v = new Vector2f(0, 0); // don't worry about garbage generation, the transition implementation will cache these values.
		v.x = x[0];
		v.y = x[1];
		return v;
	}

	@Override
	public int variables() {
		// we are only using two variables.
		return 2;
	}
}

So, to create a transition, your code would look like:

TypeConverter<Vector2f> converter = new Vector2fConverter(); // could be reused
Transition<Vector2f> transition = Transitions.transition(new Vector2f(100, 100), converter);

// now, set a transition to (500,500) in five seconds.
transition.set(new Vector2f(500, 500), 5000);

// wait some time, and get the value interpolated
Vector2f v = transition.get();

For more information, there is an transitions example in the examples module.

The idea is to provide different TypeConverter implementations for different libraries as project modules so you don't have to implement a TypeConverter for a Slick2D vector2f, or libgdx Vector2. However it is really easy to implement a type converter and you only have to do it once. Also, you will probably use transitions only for some types.

In one of the next posts, we want to talk about interpolation functions (and how are they used for transitions) as they are key concepts in animation4j project.

VN:F [1.9.22_1171]
Rating: 0.0/5 (0 votes cast)

Animation4j Project Introduction

Thursday, March 17th, 2011

Some time ago I started an internal project to simplify working with animations and transitions when making games in Java.

It was used in some of our games, for example ZombieRockers to make different kind of effects like fade in/out of screens or the points messages when scoring, or in JSnakes to make camera effects like zoom in/out, camera movement, etc.

After using it on our games, it became more clear what kind of things are possible with it.

Now that we are using Github for our source code, we started a dedicated project named animation4j with all the stuff related with animations.

This video shows some working examples of the project:

You can download a runnable jar to test the features shown in the video.

The project is on development, so right now there are a lot of things to improve starting by making easy to understand examples.

I plan to add more posts explaining some design decisions and showing how to use the library.

Hope Java developers could find some use for it.

VN:F [1.9.22_1171]
Rating: 5.0/5 (1 vote cast)

JNLP Downloader Tool

Wednesday, March 9th, 2011

Some time ago, Rubén wrote a Java based tool to download all JNLP resources and prepare executable files to run it based on the JNLP file values.

It downloads all the resources specified by the JNLP file and save them into /libs and /natives for jar resources and nativelib resources respectively, in the second case it creates sub folders for each platform (Windows, Mac and Linux). After that, it creates one executable script file to run the application for each platform, configuring classpath and java.library.path inside it.

For example, running the tool:

 
 java -jar jnlpdownloader.jar example http://www.example.com/application.jnlp

where the JNLP contents are:

 
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" codebase="http://www.example.com/" href="application.jnlp">
    <information>
            <title>Some Title</title>
            <vendor>Some Vendor</vendor>
            <description>Some Description</description>
    </information>
    <resources>
            <jar href="slf4j-api-1.5.8.jar" />
            <jar href="google-collections-1.0.jar" />
            <jar href="lwjgl-2.4.2.jar" />
    </resources>
    <resources os="Windows">
            <nativelib href="lwjgl-2.4.2-natives-win.jar" />
    </resources>
    <resources os="Linux">
            <nativelib href="lwjgl-2.4.2-natives-linux.jar" />
    </resources>
    <resources os="Mac">
            <nativelib href="lwjgl-2.4.2-natives-mac.jar" />
    </resources>  
    <application-desc main-class="Main" />
</jnlp>

will create the next file structure:

 
./example
./example/natives
./example/natives/Linux
./example/natives/Linux/liblwjgl.so
./example/natives/Windows
./example/natives/Windows/lwjgl.dll
./example/natives/Mac
./example/natives/Mac/liblwjgl.jnilib
./example/libs
./example/libs/google-collections-1.0.jar
./example/libs/slf4j-api-1.5.8.jar
./example/libs/lwjgl-2.4.2.jar
./example/run-windows.bat
./example/run-macosx.sh
./example/run-linux.sh

More info at the project's home page.

We used this tool mainly to take a snapshot of a deployed Java Web Start application to make it run offline, for demo purposes.

VN:F [1.9.22_1171]
Rating: 5.0/5 (1 vote cast)

SVG Path Traversal in Java

Thursday, March 3rd, 2011

Because some people asked, in this post we are going to explain in some detail how we do the balls movement in Zombie Rockers. All this work could be useful for other kind of games using paths like, for example, a tower defense.

For Zombie Rockers (we have to change that name), we defined a Path as an ordered collection of points. Simplifying our Path class, it could be something like this:

 
class Path {
    ArrayList<Vector2f> points;
}

note: we are using Vector2f provided by Slick2D

To easily traverse a path, we created the class PathTraversal, which let us move forward or backward through the path. For example, we could start in the beginning of a path built by three points: (100,100), (200, 100) and (300, 100). Then, if we advance 50 units, then we are going to be on the coordinate (150, 100), between first and second point.

note: when we say units, could be anything you need, pixels, meters, etc, it is not important for the example.

The idea of the PathTraversal in the future is to support any kind of paths, for example, one made using Bézier curves instead only a list of points.

This shows the API of the PathTraversal:

 
class PathTraversal {
    // the path we are traversing
    Path path; 
    // the distance to the origin we are right now
    float getCurrentDistance();
    // go back the specified distance
    void backward(float distance);
    // go forward the specified distance
    void forward(float distance);
    // returns a point based on the current distance
    Vector2f getPoint();
}

You can create the Path in the way you want, for Zombie Rockers we are using SVG files to store the paths for each level and we are loading it using SVG Salamander project. First we create a AWT Shape using the Path class provided by the SVG Salamander, then we use a PathIterator to build the points we need to create our Path.

 
public List<Vector2f> loadPointsFromSVG(URI fileUri) {
	ArrayList<Vector2f> points = new ArrayList<Vector2f>();
	SVGDiagram diagram = SVGCache.getSVGUniverse().getDiagram(fileUri);
	SVGElement element = diagram.getElement(pathName);
	List vector = element.getPath(null);
	com.kitfox.svg.Path pathSVG = (com.kitfox.svg.Path) vector.get(1); 
	// get the AWT Shape
	Shape shape = pathSVG.getShape();	 
	// iterate over the shape using a path iterator discretizing with distance 0.001 units	 
	PathIterator pathIterator = shape.getPathIterator(null, 0.001d);
	float[] coords = new float[2];
	while (!pathIterator.isDone()) {
	pathIterator.currentSegment(coords);
	points.add(new Vector2f(coords[0], coords[1]));
	pathIterator.next();
	}
}

To create the SVG path, you can use any program you want, in our case we are using mainly Gimp, we tried using Inkscape too but as we are creating the levels using Gimp it was easy to do all the work using the same application. So, open Gimp, create a path and then export it to a SVG file. Then, it should be ready to be loaded by SVG salamander inside your code.

A working example of this technique is shown in the next video of Zombie Rockers:

Conclusion

This solution works well while you avoid making the angle of two consecutive segments too sharp. For example, right now when balls traverse between two segments where the angle is too sharp, they will change the render direction too quickly and doesn't looks so good.

The problem of SVG Path when the angle of two consecutive segments is too sharp.
Figure 1: The problem of SVG Path when the angle of two consecutive segments is too sharp.

One solution to reduce this, is to increment the points of the path when making a discrete version of the path, so you will have more intermediate points, then less angle brusque changes.

Another solution is to use a smooth change for the rendering direction, so when you change the value of the render direction, it will change smoothly over time.

Hope it could be of help.

Update: Added figure for the problem of the angles between segments.

Update2: Added links to the current implementation of Path and PathTraversal classes.

Update3: Fixed the path example.

VN:F [1.9.22_1171]
Rating: 4.5/5 (2 votes cast)

Signing JARs for Applet and Webstart

Sunday, February 7th, 2010

We are developing our games using Java and deploying them as Applets and Webstart applications.

By default, applications launched with Java Webstart or as an Applet run in a restricted environment. We are using some technologies which require unrestricted access like Lwjgl, Jinput and custom ClassLoaders. In order to have access to these features, every jar must be signed with a certificate.

Once the jars have been downloaded on the client machine and the signature is validated, the user is requested whether he trust or not the provider of the certificate and if he wants to accept it permanently.

In the beginning, one option is to generate a new certificate every time we sign the jars of an application but this means that although the user might have accepted a certificate permanently, he will have to accept the new one.

A better option is to generate a certificate once and use it every time we sign an application, so that whenever the user accepts our certificate permanently he won’t be bothered again.

A way to create a certificate and manually sign an application with it is explained at Sun's Java Documentation.

In our case, we are using maven as our build tool with maven-webstart-plugin to automatically sign our jars. This plugin allow us to use both options.

In order to easily choose between them, we configure the plugin using properties instead of fixed values, so we can override them with profiles. Using the default values of these properties, the plugin generates a new certificate each time.
If we want to make a public build, we activate a maven profile overriding these properties to use an existent certificate used by all of our applications.

Here are some snippets of our configuration files:

pom.xml - maven-webstart-plugin configuration

  
<configuration>
	<sign>
		<keystore>${gemserk.keystore}</keystore>
		<keypass>${gemserk.keypass}</keypass>
		<storepass>${gemserk.storepass}</storepass>
		<alias>${gemserk.alias}</alias>

		<!-- default values if gen is true -->
		<validity>3560</validity>
		<dnameCn>Gemserk</dnameCn>
		<dnameOu>Gemserk</dnameOu>
		<dnameO>Gemserk</dnameO>
		<dnameL>Montevideo</dnameL>
		<dnameSt>Montevideo</dnameSt>
		<dnameC>UY</dnameC>

		<verify>true</verify>

		<keystoreConfig>
			<delete>${gemserk.keystore.delete}</delete>
			<gen>${gemserk.keystore.gen}</gen>
		</keystoreConfig>
	</sign>
</configuration>

We use our company name as a prefix for the property keys in order to have a common scope when setting the values.

pom.xml - default values

  
<properties>
	<!-- Properties for keystore generation  -->
	<gemserk.keystore>/tmp/keystore-gemserk</gemserk.keystore>
	<gemserk.keypass>m2m2m2</gemserk.keypass>
	<gemserk.storepass>m2m2m2</gemserk.storepass>
	<gemserk.alias>gemserk.com</gemserk.alias>
	<gemserk.keystore.delete>true</gemserk.keystore.delete>
	<gemserk.keystore.gen>true</gemserk.keystore.gen>
</properties>

settings.xml - profile declaration

  
<profile>
	<id>useDeploymentCertificate</id>
	<properties>
		<gemserk.keystore>/opt/gemserk-keystore</gemserk.keystore>
		<gemserk.keypass>password</gemserk.keypass>
		<gemserk.storepass>password</gemserk.storepass>
		<gemserk.alias>gemserk.com</gemserk.alias>
		<gemserk.keystore.delete>false</gemserk.keystore.delete>
		<gemserk.keystore.gen>false</gemserk.keystore.gen>
	</properties>
</profile>

In order to build an application for deployment, you can activate this profile from the command line using:

mvn package -PuseDeploymentCertificate

If you have any questions leave a comment.

VN:F [1.9.22_1171]
Rating: 0.0/5 (0 votes cast)