Posts Tagged ‘development’

Our solution to handle multiple screen sizes in Android – Part three

Thursday, September 12th, 2013

In the previous posts of this series we talked about our solution to handle multiple screen sizes for game menus, in particular we showed the main menu of the game Clash of the Olympians. In this post we are going to talk about what we did inside the game itself. As a side note, the solution we used here is simple and specific for this game, hope it could help as example but don't expect a silver bullet.

Scaling to match the physics world

As we use Box2D in Clash of the Olympians, the first step was to use a proper scale between Box2D bodies and our assets. The basic approach was to consider that 1m (meter in MKS system) was 32px, so in our target resolution of 800x480 could show 25m x 15m. We picked that scale because it gives pretty numbers both in terms of the game area and in terms of our assets, for example, a character of 64px of height is 2m tall. In particular, Achilles has a height of approx 60px which is equivalent to 1.875m using our scale, that sounds pretty reasonable for that character.

clashoftheolympians-800x480
The image shows the relation between screen size in pixels (800x480 in this case) and the game world in meters.

Defining a virtual area to show

We previously said that we could show 25m x 15m, in fact, the height is not so important in Clash of the Olympians since the game mainly depends in the horizontal distance. So, if we had an imaginary with a resolution of 800x400 (really wide, an aspect ratio of 2) we would show in that case 12.5m of height, we could assume that if we show at least that height the game balance would be not affected at all (enemies are never spawned too high). However, in terms of horizontal distance we want to show always the same area across all devices to avoid changing the game balance (for example, if you could see less area you couldn't react in the proper time to some waves), that is why we decided to show always 25m in terms of width.

clashoftheolympians-800x600

The image shows how we still show the same game world width of 25m on a 800x600 device.

Scaling the world back to match the screen size

Finally, in order to show this virtual area of 25m x H (with H >= 12.5m), we have to calculate the proper scale to set our game camera in each device. For example, in the case of having a Nexus 7 (1280x720 resolution device) the scale to show 25m of horizontal size is 51.2x since we know that 1280 / scale = 25, then 1280 / 25 = 51.2. In the case of a Samsung Galaxy Y (480x320 resolution device) the scale would be 19.2x since 480 / 25 = 19.2. Translating this inside the game would be something as easy as:

camera.scale = screen.width / 25

Final thoughts

This is not a general solution, it depends a lot in the game we were making and the things we could assume like the game height doesn't matter.

Even though the solution is specific and not so cool as the previous posts, we hope it could be of help when making your own game.

VN:F [1.9.22_1171]
Rating: 4.2/5 (15 votes cast)

Our resources manager and how it helped in localizing Clash of the Olympians

Wednesday, May 1st, 2013

In this post we want to share how our resources manager implementation helped us when we had to localize Clash of the Olympians.

Our resources manager

Some time ago we started a small java project named jresourcesmanager (yeah, the most creative name in the world) which provides a simple abstraction of what a resource is and how it is loaded. It consists in some basic concepts named Resource, DataLoader and ResourceManager.

Resource

Is the concept of an application resource/asset, and provides an API to know if the resource is loaded or not and to load and unload it. Here is the code:

public class Resource<T> {

	T data = null;

	DataLoader<T> dataLoader;

	protected Resource(DataLoader<T> dataLoader) {
		this(dataLoader, true);
	}

	protected Resource(DataLoader<T> dataLoader, boolean deferred) {
		this.dataLoader = dataLoader;
		if (!deferred)
			reload();
	}

	/**
	 * Returns the data stored by the Resource.
	 */
	public T get() {
		if (!isLoaded())
			load();
		return data;
	}
	
	public void set(T data) {
		this.data = data;
	}

	public DataLoader<T> getDataLoader() {
		return dataLoader;
	}
	
	public void setDataLoader(DataLoader<T> dataLoader) {
		unload();
		this.dataLoader = dataLoader;
	}

	/**
	 * Reloads the internal data by calling unload and load().
	 */
	public void reload() {
		unload();
		load();
	}

	/**
	 * Loads the data if it wasn't loaded yet, it does nothing otherwise.
	 */
	public void load() {
		if (!isLoaded())
			data = dataLoader.load();
	}

	/**
	 * Unloads the data by calling the DataLoader.unload(t) method.
	 */
	public void unload() {
		if (isLoaded()) {
			dataLoader.unload(data);
			data = null;
		}
	}

	/**
	 * Returns true if the data is loaded, false otherwise.
	 */
	public boolean isLoaded() {
		return data != null;
	}

	public Resource<T> clone() {
		return new Resource<T>(dataLoader);
	}

}

DataLoader

Provides an API to define a way to load and unload a Resource, here is the code:

public abstract class DataLoader<T> {

	/**
	 * Implements how to load the data.
	 */
	public abstract T load();

	/**
	 * Implements how to unload the data, if it is nod automatic and you need to unload stuff by hand.
	 */
	public void unload(T t) {

	}

	/**
	 * Provides a way to return custom information about the data loader.
	 * 
	 * @return An object with the custom information.
	 */
	public Object getMetaData() {
		return null;
	}

}

ResourceManager

It is just a map with all the resources identified by a key, which can be a String for example, and an API to store and get resources to and from, respectively.

That is our resources management code, it is really simple and fulfilled our needs in assets loading/unloading for Vampire Runner and Clash of the Olympians.

The interesting part of this library is that it allows you to store whatever you want to consider as a resource/asset, and that feature is what we used in order to solve the localization issue.

Declaring resources

Another important pillar is how we declare resources in an easy way through the code, and that is by using some builders that simplified the resource declaration. As we were using LibGDX library, we created a resource builder for LibGDX assets and more. This resource builder allowed us to do stuff like this:

splitLoadingTextureAtlas("MainTextureAtlas", "data/images/screens/mainmenu/pack");
resource("MainBackgroundTop", sprite2().textureAtlas("MainTextureAtlas", "mainmenu-bg", 1).center(0.5f, 0.5f).trySpriteAtlas());

That declares a texture atlas with the name of MainTextureAtlas and then a resource which is a sprite with the name of MainBackgroundTop from a texture atlas resource identified by the previous name. Also, allow us to do stuff like declare that the sprite is centered in the middle (the anchor point) and more.

This code is not important, just an example of some of our builders.

Declaring localized resources

As we have the power to create complex resource builders and we needed a simple way to switch between assets given the language selected, we created a resource builder which allowed us to declare different resources, depending the current locale, using the same identifier. So, for example, we can do something like this:

resource("CreditsButton", new MultilanguageResourceBuilder<Sprite>() //
	.defaultLocale(new Locale("en")) //
	.resource(new Locale("en"), sprite2().textureAtlas(TextureAtlases.MeinMenu, "mainmenu-but-credits-en", 1).trySpriteAtlas()) //
	.resource(new Locale("es"), sprite2().textureAtlas(TextureAtlases.MeinMenu, "mainmenu-but-credits-es", 1).trySpriteAtlas()) //
			);

That declares a resource named CreditsButton which returns a sprite with index 1 and name “mainmenu-but-credits-en” from a texture atlas in case the locale is English and a sprite with index 1 and name “mainmenu-but-credits-es” in case the locale is Spanish. It also declares that the default locale (in case a resource for the current locale wasn’t found) is English.

This resource builder was very handy because it allowed us to declare any type of resource for different locales, and from the application side it was transparent, we just ask for the resource CreditsButton.

In case you are interested, the code of the MultilanguageResoruceBuilder is:

public class MultilanguageResourceBuilder<T> implements ResourceBuilder<T> {

	private Map<Locale, ResourceBuilder<T>> resourceBuilders = new HashMap<Locale, ResourceBuilder<T>>();
	private Locale defaultLocale;

	@Override
	public boolean isVolatile() {
		if (defaultLocale == null)
			throw new IllegalStateException("Multilanguage resource builder needs a default locale");
		ResourceBuilder<T> resourceBuilder = resourceBuilders.get(defaultLocale);
		if (resourceBuilder == null)
			throw new IllegalStateException("Multilanguage resource builder needs a default resource builder");
		return resourceBuilder.isVolatile();
	}

	public MultilanguageResourceBuilder<T> defaultLocale(Locale locale) {
		this.defaultLocale = locale;
		return this;
	}

	public MultilanguageResourceBuilder<T> resource(Locale locale, ResourceBuilder<T> resourceBuilder) {
		resourceBuilders.put(locale, resourceBuilder);
		return this;
	}

	@Override
	public T build() {
		Locale locale = Locale.getDefault();

		if (defaultLocale == null)
			throw new IllegalStateException("Multilanguage resource builder needs a default locale");

		if (!resourceBuilders.containsKey(locale))
			locale = defaultLocale;

		ResourceBuilder<T> resourceBuilder = resourceBuilders.get(locale);

		if (resourceBuilder == null)
			throw new IllegalStateException("Multilanguage resource builder needs a default resource builder");

		return resourceBuilder.build();
	}

}

Conclusion

That was the way we used to support multiple languages in a transparent way for the application, we just need to change the current locale and reload the assets.

Hope this blog post idea helps you in case you are about to support multiple languages in your game, and see you next time.

VN:F [1.9.22_1171]
Rating: 3.9/5 (14 votes cast)

Our solution to handle multiple screen sizes in Android – Part two

Wednesday, February 13th, 2013

Continuing with the previous blog post, in this post we are going to talk about the code behind the theory. It consists in three concepts, the VirtualViewport, the OrthographicCameraWithVirtualViewport and the MultipleVirtualViewportBuilder.

VirtualViewport

It defines a virtual area where the game stuff is contained and provides a way to get the real width and height to use with a camera in order to always show the virtual area. Here is the code of this class:

public class VirtualViewport {

	float virtualWidth;
	float virtualHeight;

	public float getVirtualWidth() {
		return virtualWidth;
	}

	public float getVirtualHeight() {
		return virtualHeight;
	}

	public VirtualViewport(float virtualWidth, float virtualHeight) {
		this(virtualWidth, virtualHeight, false);
	}

	public VirtualViewport(float virtualWidth, float virtualHeight, boolean shrink) {
		this.virtualWidth = virtualWidth;
		this.virtualHeight = virtualHeight;
	}

	public float getWidth() {
		return getWidth(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
	}

	public float getHeight() {
		return getHeight(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
	}

	/**
	 * Returns the view port width to let all the virtual view port to be shown on the screen.
	 * 
	 * @param screenWidth
	 *            The screen width.
	 * @param screenHeight
	 *            The screen Height.
	 */
	public float getWidth(float screenWidth, float screenHeight) {
		float virtualAspect = virtualWidth / virtualHeight;
		float aspect = screenWidth / screenHeight;
		if (aspect > virtualAspect || (Math.abs(aspect - virtualAspect) < 0.01f)) {
			return virtualHeight * aspect;
		} else {
			return virtualWidth;
		}
	}

	/**
	 * Returns the view port height to let all the virtual view port to be shown on the screen.
	 * 
	 * @param screenWidth
	 *            The screen width.
	 * @param screenHeight
	 *            The screen Height.
	 */
	public float getHeight(float screenWidth, float screenHeight) {
		float virtualAspect = virtualWidth / virtualHeight;
		float aspect = screenWidth / screenHeight;
		if (aspect > virtualAspect || (Math.abs(aspect - virtualAspect) < 0.01f)) {
			return virtualHeight;
		} else {
			return virtualWidth / aspect;
		}
	}

}

So, if we have a virtual area of 640x480 and want to show it on a screen of 800x480 we can do the next steps in order to get the proper values that we have to use as the camera viewport for that screen:

VirtualViewport virtualViewport = new VirtualViewport(640, 480);
float realViewportWidth = virtualViewport.getWidth(800, 480);
float realViewportHeight = virtualViewport.getHeight(800, 480);
// now set the camera viewport values
camera.setViewportFor(realViewportWidth, realViewportHeight);

OrthographicCameraWithVirtualViewport

In order to simplify the work when using LibGDX library, we created a subclass of LibGDX's OrthographicCamera with specific behavior to update the camera viewport using the VirtualViewport values. Here is its code:

public class OrthographicCameraWithVirtualViewport extends OrthographicCamera {

	Vector3 tmp = new Vector3();
	Vector2 origin = new Vector2();
	VirtualViewport virtualViewport;
	
	public void setVirtualViewport(VirtualViewport virtualViewport) {
		this.virtualViewport = virtualViewport;
	}

	public OrthographicCameraWithVirtualViewport(VirtualViewport virtualViewport) {
		this(virtualViewport, 0f, 0f);
	}

	public OrthographicCameraWithVirtualViewport(VirtualViewport virtualViewport, float cx, float cy) {
		this.virtualViewport = virtualViewport;
		this.origin.set(cx, cy);
	}

	public void setPosition(float x, float y) {
		position.set(x - viewportWidth * origin.x, y - viewportHeight * origin.y, 0f);
	}

	@Override
	public void update() {
		float left = zoom * -viewportWidth / 2 + virtualViewport.getVirtualWidth() * origin.x;
		float right = zoom * viewportWidth / 2 + virtualViewport.getVirtualWidth() * origin.x;
		float top = zoom * viewportHeight / 2 + virtualViewport.getVirtualHeight() * origin.y;
		float bottom = zoom * -viewportHeight / 2 + virtualViewport.getVirtualHeight() * origin.y;

		projection.setToOrtho(left, right, bottom, top, Math.abs(near), Math.abs(far));
		view.setToLookAt(position, tmp.set(position).add(direction), up);
		combined.set(projection);
		Matrix4.mul(combined.val, view.val);
		invProjectionView.set(combined);
		Matrix4.inv(invProjectionView.val);
		frustum.update(invProjectionView);
	}

	/**
	 * This must be called in ApplicationListener.resize() in order to correctly update the camera viewport. 
	 */
	public void updateViewport() {
		setToOrtho(false, virtualViewport.getWidth(), virtualViewport.getHeight());
	}
}

MultipleVirtualViewportBuilder

This class allows us to build a better VirtualViewport given the minimum and maximum areas we want to support performing the logic we explained in the previous post. For example, if we have a minimum area of 800x480 and a maximum area of 854x600, then, given a device of 480x320 (3:2) it will return a VirtualViewport of 854x570 which is a good match of a resolution which contains the minimum area and is smaller than the maximum area and has the same aspect ratio of 480x320.

public class MultipleVirtualViewportBuilder {

	private final float minWidth;
	private final float minHeight;
	private final float maxWidth;
	private final float maxHeight;

	public MultipleVirtualViewportBuilder(float minWidth, float minHeight, float maxWidth, float maxHeight) {
		this.minWidth = minWidth;
		this.minHeight = minHeight;
		this.maxWidth = maxWidth;
		this.maxHeight = maxHeight;
	}

	public VirtualViewport getVirtualViewport(float width, float height) {
		if (width >= minWidth && width <= maxWidth && height >= minHeight && height <= maxHeight)
			return new VirtualViewport(width, height, true);

		float aspect = width / height;

		float scaleForMinSize = minWidth / width;
		float scaleForMaxSize = maxWidth / width;

		float virtualViewportWidth = width * scaleForMaxSize;
		float virtualViewportHeight = virtualViewportWidth / aspect;

		if (insideBounds(virtualViewportWidth, virtualViewportHeight))
			return new VirtualViewport(virtualViewportWidth, virtualViewportHeight, false);

		virtualViewportWidth = width * scaleForMinSize;
		virtualViewportHeight = virtualViewportWidth / aspect;

		if (insideBounds(virtualViewportWidth, virtualViewportHeight))
			return new VirtualViewport(virtualViewportWidth, virtualViewportHeight, false);
		
		return new VirtualViewport(minWidth, minHeight, true);
	}
	
	private boolean insideBounds(float width, float height) {
		if (width < minWidth || width > maxWidth)
			return false;
		if (height < minHeight || height > maxHeight)
			return false;
		return true;
	}

}

In case the aspect ratio is not supported, it will return the minimum area.

Floating elements

As we explained in the previous post, there are some cases where we need stuff that should be always at fixed positions in the screen, for example, the audio and music buttons in Clash of the Olympians. In order to do that we need to make the position of those buttons depend on the VirtualViewport. In the next section where we explain how to use all together we show an example of how to do a floating element.

Using the code together

Finally, here is an example showing how to use these concepts in a LibGDX application:

public class VirtualViewportExampleMain extends com.badlogic.gdx.Game {

	private OrthographicCameraWithVirtualViewport camera;
	
	// extra stuff for the example
	private SpriteBatch spriteBatch;
	private Sprite minimumAreaSprite;
	private Sprite maximumAreaSprite;
	private Sprite floatingButtonSprite;
	private BitmapFont font;

	private MultipleVirtualViewportBuilder multipleVirtualViewportBuilder;

	@Override
	public void create() {
		multipleVirtualViewportBuilder = new MultipleVirtualViewportBuilder(800, 480, 854, 600);
		VirtualViewport virtualViewport = multipleVirtualViewportBuilder.getVirtualViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		
		camera = new OrthographicCameraWithVirtualViewport(virtualViewport);
		// centers the camera at 0, 0 (the center of the virtual viewport)
		camera.position.set(0f, 0f, 0f);
		
		// extra code
		spriteBatch = new SpriteBatch();
		
		Pixmap pixmap = new Pixmap(64, 64, Format.RGBA8888);
		pixmap.setColor(Color.WHITE);
		pixmap.fillRectangle(0, 0, 64, 64);
		
		minimumAreaSprite = new Sprite(new Texture(pixmap));
		minimumAreaSprite.setPosition(-400, -240);
		minimumAreaSprite.setSize(800, 480);
		minimumAreaSprite.setColor(0f, 1f, 0f, 1f);
		
		maximumAreaSprite = new Sprite(new Texture(pixmap));
		maximumAreaSprite.setPosition(-427, -300);
		maximumAreaSprite.setSize(854, 600);
		maximumAreaSprite.setColor(1f, 1f, 0f, 1f);
		
		floatingButtonSprite = new Sprite(new Texture(pixmap));
		floatingButtonSprite.setPosition(virtualViewport.getVirtualWidth() * 0.5f - 80, virtualViewport.getVirtualHeight() * 0.5f - 80);
		floatingButtonSprite.setSize(64, 64);
		floatingButtonSprite.setColor(1f, 1f, 1f, 1f);
		
		font = new BitmapFont();
		font.setColor(Color.BLACK);
	}
	
	@Override
	public void resize(int width, int height) {
		super.resize(width, height);
		
		VirtualViewport virtualViewport = multipleVirtualViewportBuilder.getVirtualViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		camera.setVirtualViewport(virtualViewport);
		
		camera.updateViewport();
		// centers the camera at 0, 0 (the center of the virtual viewport)
		camera.position.set(0f, 0f, 0f);
		
		// relocate floating stuff
		floatingButtonSprite.setPosition(virtualViewport.getVirtualWidth() * 0.5f - 80, virtualViewport.getVirtualHeight() * 0.5f - 80);
	}
	
	@Override
	public void render() {
		super.render();
		Gdx.gl.glClearColor(1f, 0f, 0f, 1f);
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
		camera.update();
		
		// render stuff...
		spriteBatch.setProjectionMatrix(camera.combined);
		spriteBatch.begin();
		maximumAreaSprite.draw(spriteBatch);
		minimumAreaSprite.draw(spriteBatch);
		floatingButtonSprite.draw(spriteBatch);
		font.draw(spriteBatch, String.format("%1$sx%2$s", Gdx.graphics.getWidth(), Gdx.graphics.getHeight()), -20, 0);
		spriteBatch.end();
	}

	public static void main(String[] args) {
		LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();

		config.title = VirtualViewportExampleMain.class.getName();
		config.width = 800;
		config.height = 480;
		config.fullscreen = false;
		config.useGL20 = true;
		config.useCPUSynch = true;
		config.forceExit = true;
		config.vSyncEnabled = true;

		new LwjglApplication(new VirtualViewportExampleMain(), config);
	}

}

In the example there are three colors, green represents the minimum supported area, yellow the maximum supported area and red represents the area outside. If we see red it means that aspect ratio is not supported. There is a floating element colored white, which is always relocated in the top right corner of the screen, unless we are on an unsupported aspect ratio, in that case it is just located in the top right corner of the green area.

The next video shows the example in action:

UPDATE: you can download the source code to run on Eclipse from here.

Conclusion

In these two blog posts we explained in a simplified way how we managed to support different aspect ratios and resolutions for Clash of the Olympians, a technique that could be used as an acceptable way of handling different screen sizes for a wide range of games, and it is not hard to use.

As always, we hope you liked it and that it could be useful for you when developing your games. Opinions and suggestions are always welcome if you want to comment :) and also share it if you liked it and think other people could benefit from this code.

Thanks for reading.

VN:F [1.9.22_1171]
Rating: 4.9/5 (30 votes cast)

Our solution to handle multiple screen sizes in Android - Part one

Tuesday, January 22nd, 2013

Developing games for multiple devices is not an easy task. Given the variety of devices, one of the most common problem is having to handle multiple screen sizes, which means different resolutions and aspect ratios.

In this blog post we want to share what we did to minimize this problem when making Ironhide's Clash of the Olympians for Android.

In the next sections we are going to show some common ways of handling the multiple screens problem and then our way.

Stretching the content

One common approach when developing a game is making the game for a fixed resolution, for example, making the game for 800x480.

Based on that, you can have the next layout in one of your game's screens:


Main screen of Clash of the Olympians in a 800x480 device.

Then, to support other screen sizes the idea is to stretch the content to the other device screen:


Main screen on a 800x600 device, stretched from 800x480.

The main problem is that the aspect ratio is affected and that is visually unacceptable.

Stretching + keeping aspect ratio

To solve part of the previous problem, one common technique is stretching but keeping the correct aspect ratio by adding dead space to the borders of the screen so the real game area aspect ratio is the same on different devices. For example:


Main screen in a 800x600 device with borders.


Main screen in a 854x480 device with borders.

This is an easy way to attack this multiple screen size problem, you can even create some nice borders instead of the black borders shown in the previous image to improve how it looks.

However, in some cases this is not acceptable either since it doesn't look so good or it feels like the game wasn't made for that device.

Our solution: Using a Virtual Viewport

Our approach consists in adapting what is shown in the game screen area to the device screen size.

First, we define a range of aspect ratios we want to support, for example, in the case of clash we defined 4:3 (800x600) and 16:9 (854x480) as our border case aspect ratios, so all aspect ratios in the middle of those two should be supported.

Given those two aspect ratios, we defined our maximum area as 854x600 and our minimum area as 800x480 (the union and intersection between 800x600 and 854x480, respecively). The idea is to cover the maximum area with stuff, but the important stuff (buttons, information, etc) should be always included in the minimum area.


The red rectangle shows the minimum area while the blue rectangle shows the maximum area.

Then, given a device resolution we calculate an area that matches the device aspect ratio and is included in the virtual area. For example, given a device with a resolution of 816x544 (4:3), this is what is shown:


The green rectangle shows the matching area for 816x544.


This is how the main screen is shown in a 816x544 device.

In case we are on a bigger or lower resolution than the maximum or minimum area we defined, respectively, for example a screen of 480x320 (3:2), what we do is calculate the aspect ratio and find a corresponding match for that aspect ratio in the area we defined. In the case of the example, one match could be 800x534 since it is 3:2 aspect ratio and it is inside our virtual area. Then we scale down to fit the screen.


The green rectangle shows the calculated area for a resolution of 800x534 (matching the aspect of the 480x320 device).


This is what is shown of the main screen in a 480x320 device (click to enlarge the image).

Floating elements

For some elements of the game, such as buttons, maintaining their fixed world position for different screen sizes doesn't look good, so what we do is making them floating elements. That means they are always at the same screen position, the next images shows an example with the main screen buttons:


Main screen's buttons distribution for a 854x480 device.


Main screen's buttons distribution for a 800x600 device. As you can see, buttons are relocated to match the screen size.

Finally, we want to show a video of this multiple screen sizes auto adjustment in real time:


Adjusting the game to the screen size in real time.

Some limitations

As we are scaling up/down in some cases to match the corresponding screen, some devices could perceive some blur since we are using linear filtering and the final position of the elements after the camera transformations could be not integer positions. This problem is minimized with better density devices and assets.

Layouts could change between different devices, for example, the layout for a phone could be different to the layout of a tablet device.

Text is a special case, when rendering text just downscaling it is not a correct solution since it could be not readable. You may have to re-layout text for lower resolution devices to show it bigger and readable.

Conclusion

If you design your game screens follow this approach, it is not so hard to support multiple screen sizes in an acceptable way. However there is still a lot of detail to take care of, like the problems we talked in the previous section.

In the next part of this blog post we will show some code based on LibGDX for those interested in how we implemented all this.

Thanks for reading and hope you liked it.

VN:F [1.9.22_1171]
Rating: 4.6/5 (35 votes cast)

Decoupling game logic from input handling logic

Thursday, August 23rd, 2012

In this post we want to share how we are decoupling our game logic from the input handling as we explained briefly in a previous post about different controls we tested for Super Flying Thing.

Introduction

There are different ways to handle the input in a game. Basically, you could have a framework that provides a way to define event handlers for each input event, or to poll input values from the API. LibGDX provides both worlds so it is up to you what you consider best for your game. We prefer to poll for input values for the game logic itself.

When starting to make games, you probably feel tempted to add the input handling logic in one or more base concepts of your game, for example, if you were making Angry Birds you probably would add it to the Slingshot class to detect when to fire a bird or not. That is not totally bad if you are making a quick prototype but it is not recommended for long term because it would be harder to add or change between different control implementations.

Abstracting the input

To improve a bit this scenario in our games, we are using an intermediary class named Controller. That class provides values more friendly and related with the game concepts. A possible Controller class for our example could be:

class SlingshotController {
	boolean charging;
	Vector2 direction;
}

Now, we could process the input handling in one part of the code and update a common Controller instance shared between it and the game logic. Something like this:

class SlingshotMouseControllerLogic extends InputListener {

	Slingshot slingshot;
	SlingshotController controller;

	public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
		slingshotPosition = slingshot.getPosition();
		controller.charging = slingshotPosition.isNear(x,y);
		return controller.charging;
	}

	public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
		if (!controller.charging) 
			return;
		controller.charging = false;
	}

	public void touchDragged (InputEvent event, float x, float y, int pointer) {
		if (!controller.charging) 
			return;
		slingshotPosition = slingshot.getPosition();
		controller.direction.set(slingshotPosition);
		controller.direction.sub(x,y);
	}	
}

Or if you are polling the input:

class SlingshotMouseControllerLogic implements Updateable {

	Slingshot slingshot;
	SlingshotController controller;

	boolean touchWasPressed = false;

	public void update(float delta) {
		boolean currentTouchPressed = Gdx.input.isPressed();

		slingshotPosition = slingshot.getPosition();
		x = Gdx.input.getX();
		y = Gdx.input.getY();

		if (!touchWasPressed && currentTouchPressed) {
			controller.charging = slingshotPosition.isNear(x,y);
			touchWasPressed = true;
		} 

		if(touchWasPressed && !currentTouchPressed) {
			controller.charging = false;
			touchWasPressed = false
		}

		if (!controller.charging)
			return;

		controller.direction.set(slingshotPosition);
		controller.direction.sub(x,y);	
	}
	
}

Now, the Slingshot implementation will look something like this:

class Slingshot { 

	// multiple fields
	// render logic 

	Controller controller;
	boolean wasCharging = false;

	update() {
		if (controller.charging && !wasCharging) {
			// starts to draw stuff based on the state we are now charging...
			
			// charges a bird in the slingshot.

			wasCharging = true;
		} else if (!controller.charging && wasCharging) {
			// stops drawing the slingshot as charging

			// fires the bird!!
	
			wasCharging = false;
		}
		// more stuff...
	}
}

As you can see, the game concept Slingshot doesn't know anything about input anymore and we could switch to use the keyboard, Xbox 360 Controller, etc, and the game logic will not notice the change.

Conclusion

Decoupling your game logic from the input by abstracting it in a class is a good way to keep your game logic depending mainly on game concepts making it easier to understand and improving its design. Also, it is a good way to create several controls for the game (input, AI, network, recorded input, etc), while the game logic don't even notice the change.

This post provides a really simple concept, the concept of abstraction, it is nothing new and probably most of the game developers are already doing this, even though we wanted to share it, maybe it is helpful for someone.

We tried to use simple and direct code in this post to increase understandability, however in our games, as we use an entity system framework, we do it a bit different using components, scripts and systems instead of direct classes for concepts like the class Slingshot we presented in this post, but that's food for another blog post.

Finally, we use another abstraction layer over the framework input handling which provides us a better and simplified API to poll info from, that's why we prefer to poll values, food for another post as well.

Hope you like it, as always.

VN:F [1.9.22_1171]
Rating: 4.6/5 (15 votes cast)

Modifying textures using libGDX Pixmap in runtime - Explained

Wednesday, January 4th, 2012

We have previously shown a bit how we were using LibGDX Pixmap to modify textures in runtime here and here for a game prototype we were doing. In this post I want to share more detail of how we do that. The objective was to make destructible terrain like in Worms 2.

Introduction

When you work with OpenGL textures, you can't directly modify their pixels whenever you want since they are on OpenGL context. To modify them you have to upload an array of bytes using glTexImage2D or glTexSubImage2D. The problem is you have to maintain on the application side an array of bytes representing the modifications you want to do.

To simplify working with byte arrays representing images, LibGDX provides a useful class named Pixmap which is a map of pixels kept in local memory with some methods to interact with a native library to perform all modifications with better performance.

Moving data from Pixmap to OpenGL Texture

In our prototypes, we wanted to remove part of the terrain whenever a missile touches it, like a Worms 2 explosion. That means we need some way to detect the collisions between the missile and the terrain and then a way to remove pixels from a texture.

We simplified the first problem by getting the color of the pixel only for the missile's position and checking if it was transparent or not. A more correct solution could be using a bitmap mask to check collisions between pixels but we wanted to simplify the work for now.

For the second problem, given a radius of explosion of the missile, we used the pixmap fillCircle method by previously setting the color to (0,0,0,0) (fully transparent) and disabled Pixmap blending to override those pixels.

But that only modified the pixmap data, now we needed to modify the OpenGL texture. To do that, we called OpenGL glTexImage2D using the bytes of the pixmap as the new texture data and that worked correctly.

Transforming from world coordinates to Pixmap coordinates

One problem when working with pixmaps is we have to map world coordinates (the position of the missile for example) to coordinates inside the Pixmap.


This image shows the coordinate system of the Pixmap, it goes from 0 to width in x and 0 to height in y.


This image shows how we normally need to move, rotate and resize the Pixmap in a game.

To solve this, we are using a LibGDX Sprite to maintain the Pixmap transformation, so we can easily move, rotate and scale it. Then, we can use that information to project a world coordinate to Pixmap coordinate by applying the inverse transform, here is the code:

	public void project(Vector2 position, float x, float y) {
		position.set(x, y);

		float centerX = sprite.getX() + sprite.getOriginX();
		float centerY = sprite.getY() + sprite.getOriginY();

		position.add(-centerX, -centerY);

		position.rotate(-sprite.getRotation());

		float scaleX = pixmap.getWidth() / sprite.getWidth();
		float scaleY = pixmap.getHeight() / sprite.getHeight();

		position.x *= scaleX;
		position.y *= scaleY;

		position.add( //
				pixmap.getWidth() * 0.5f, //
				-pixmap.getHeight() * 0.5f //
		);

		position.y *= -1f;
	}

(note: it is the first version at least, it could have bugs and could be improved also)

To simplify our work with all this stuff, we created a class named PixmapHelper which manage a Pixmap, a Texture and a Sprite, so we could move the Sprite wherever we wanted to and if we modify the pixmap through the PixmapHelper then the Texture was automatically updated and hence the Sprite (since it uses internally the Texture).

The next video shows how we tested the previous work in a prototype were we simulated cluster bombs (similar to Worms 2):

Some adjustments to improve performance

Instead of always working with a full size Pixmap by modifying it and then moved to the OpenGL texture, we created smaller Pixmaps of fixed sizes: 32x32, 64x64, etc. Then, each time we needed to make an explosion, we used the best Pixmap for that explosion and then we called glTexSubImage2D instead glTexImage2D to avoid updating untouched pixels. One limitation of this modification is we have to create and maintain several fixed size pixmaps depending on the modification size. Our current greater quad is 256x256 (almost never used).

Then, we changed to store each modification instead performing them in the moment the PixmapHelper erase method was called, and we added an update method which performs all modifications together. This improvement allow us to call Pixmap update method when we wanted, maybe one in three game updates or things like that.

Conclusion

Despite using LibGDX Pixmap for better performance, moving data to and from OpenGL context is not a cheap operation, on Android devices this could means some pauses when refreshing the modified textures with the new data. However, there is a lot of space for performance improvement, some ideas are to work only with pixmap of less bits instead RGBA8888 and use that one as the collisions context and as the mask of the real image (even using shaders), between other ideas.

Finally, the technique looks really nice and we believe that it could be used without problems for a simple game but it is not ready yet to manage a greater game like Worms 2.

Hope you like the technique and if you use it, maybe even also share your findings.

P.S.: In case you were wondering: yes, I love Worms 2.

VN:F [1.9.22_1171]
Rating: 4.0/5 (4 votes cast)

Scripting with Artemis Entity System Framework

Sunday, November 13th, 2011

Artemis is a component based entity system framework made in Java by Arni Arent (appel) and Tiago Costa (spiegel).

The core of Artemis is small and it is based on four main concepts. The Component, where all information about an Entity is stored. The Entity, which represents a game object. The EntitySystem, where custom logic, for entities with one or more component, is processed. And finally the World, where Entities and EntitySystems live. The idea is to grow over those concepts by making your own Components and EntitySystems.

The problem

There are some cases where it depends a lot how are you using Artemis, to feel it is helping you or not. For example, when you want an Entity to have some custom behavior on a specific level you could follow the next approach.

First, create an SpecificBehaviorForLevelComponent to declare that an Entity with that component will have that custom behavior and then create the SpecificBehaviorForLevelSystem which performs the logic.

The problem with this approach is that you will have an empty Component used only as a mark to let the EntitySystem work over it. Also, you will have an EntitySystem that will perform logic only in a specific moment of the game and then it will be useless.

If you have to create more than just one custom behavior, then you are a bit doomed and you will hate Artemis. However, it is not Artemis, or any component based entity framework, fault.

Script Framework, a possible solution

To simplify working with cases like the one mentioned before, we created a scripting framework over Artemis, composed by three main concepts: the Script, the ScriptComponent and the ScriptSystem.

Script

This is an interface which provides the following three methods:

  • init(world, entity) : called once when the entity enters the world.
  • update(world, entity) : called in each world update
  • dispose(world, entity) : called once when the entity leaves the world.

It should be implemented and added to the ScriptComponent of an Entity in order to perform the behavior.

ScriptComponent

This is an Artemis Component which contains a collection of Scripts and it is used by the ScriptSystem in order to know which Scripts to process on each Entity.

ScriptSystem

This is an Artemis EntitySystem responsible of calling, for each Entity, the init/update/dispose methods of each Script declared in the ScriptComponent of that Entity.

For the previous example, where we wanted a specific behavior for an entity in one level, we could create a SpecificBehaviorForLevelScript and, when loading that level, add that script to the ScriptComponent. So, instead creating Components and EntitySystems, we can create only Scripts.

Pros

  • Clear and simple API.
  • Easy to add/remove a Script vs add/remove a System.
  • Scripts are easily reusable (when you want two or more entities with a custom behavior).
  • Logic localized in one place.
  • Allow different implementations of the Script interface, for example, a Groovy implementation.

Cons

  • Not good for global stuff like rendering, an EntitySystem keeps being better for those cases.
  • Hard to know the right balance between which code should be on Scripts and which one on Systems.

Conclusion

The Script Framework is some point in the middle between Entities with no logic at all and Entities with all the logic. It should be used with care, learning to balance which code should be on Scripts and which code on Systems is not easy.

Artemis is only a small core, a foundation of classes to work with. The real power comes from the custom Components and Systems you create over it. For example, in our case we created several Systems that we reuse between games, some of them are the RenderSystem responsible of rendering the entities with the RenderComponent using libGDX and the PhysicsSystem responsible of processing the Box2d world.

References

There are a lot of resources about components based entity systems, however the best/correct solution between all these references is not so clear.

Hope you like it.

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

Quick prototyping and sharing FTW!!

Monday, October 17th, 2011

Since the beginning of times (of Gemserk), I had one or more local projects where I could test stuff related to the libraries we were using (Slick2D, LibGDX, Artemis), graphical effects, game ideas or just algorithms. One example is the lighting bolt effect I talked about in a previous post, I have also made some videos of some other tests but never uploaded the code.

As these kind of prototypes could be useful for other developers we have started a new project named prototypes (yeah, super creative name) where we (at least I) are going to add some of the new tests and prototypes we make.

To be easily tested by anyone, we created a Webstart and an Android application with a launcher to select which test or prototype you want to run.

Hope you like it.

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

Super Flying Thing - Update 09

Wednesday, October 5th, 2011

There is a new version of Super Flying Thing available, here is the change log:

  • Changed to count the best time to end a level
  • Modified random level generation and changed random mode to be named training
  • Removed practice mode, go to training now
  • Removed Exit button, you can exit the game pressing back button on main menu
  • Added sound for the explosions
  • Added about us screen with links to blog and more games

Remember, you can play it right now, just follow the next links:

PLAY ON PC or PLAY ON ANDROID:

Play on Android

Hope you enjoy it.

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

First version of new random level generator for Super Flying Thing

Friday, September 30th, 2011

Two weeks ago I was working to improve random level generation of Super Flying Thing, the objective was to make the levels a bit more interesting.

Previous generator was really simple, the idea was to define where the starting and destination planet will be and then generate a lot of "garbage" in the way, sometimes with no possible path between both planets.

New level generator does more work to generate a bit more interesting level. Basically the idea is to use some pre generated tiles:

Tiles are defined by SVG groups in a SVG file with custom meta data defining with which tile can be paired.

Based on that information, the generator creates a pattern and based on that pattern and the tiles, it creates the level.

For example, if we have four tiles {A, B, C, D} where A is the where the start planet should be and D where the destination planet should be, and we have the possible links {A -> B, A -> C, B -> B, B -> C, B -> D, C -> B, C -> C, C -> D}, then a possible level could be ABBCBCD (using a regular expression could be defined with something like A(B|C)+D).

So, a very basic pseudo algorithm could be something like this:

	tiles.add(startTile)
	currentTile = startTile
	while (currentTile != endtile) {
		tile = getPossibleNextTile(currentTile);
		tiles.add(tile);
		currentTile = tile;
	}

Where getPossibleNextTile() method uses the meta data of the tile to define randomly a possible next tile.

Here is how a random generated level looks like:

As the title says, this is the first version of the generator, there are a lot of possible improvements to be made and/or create a new generator and throw this one to the trash can.

After writing the post I realized that there is not so much information and the algorithm is pretty basic but I hope it could be of help anyway.

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