40. HaxeFlixel RPG tutorial: Part 3

2014-10-11

HaxeFlixel RPG tutorial camera

Last time we added a map to our game, but only a small portion of it fits on the screen. Let's add a moveable camera this time.

HaxeFlixel employs a special FlxCamera class, which displays a portion of the game world to the player. This class has a follow() method, which makes it automatically focus on and follow an entity, such as the player character.

In this case I don't want the camera to follow a character. Instead, I want to be able to move the camera around with arrow keys or WASD as far as the map goes.

For this purpose I will create an invisible object which the camera will follow; so we will actually move the invisible entity that the camera is following, and not the camera it self.

Start by going to the PlayState class and adding 5 new variables:

static var LEVEL_WIDTH:Int = 50;

static var LEVEL_HEIGHT:Int = 50;

static var CAMERA_SPEED:Int = 8;

private var camera:FlxCamera;

private var cameraFocus:FlxSprite;

In the create() function we'll instantiate the cameraFocus object. We can use the built-in FlxSprite method makeGraphic() to make a dummy image for the entity. Create a transparent 1x1 rectangle:

cameraFocus = new FlxSprite();

cameraFocus.makeGraphic(1, 1, FlxColor.TRANSPARENT);

add(cameraFocus);

Next we'll instantiate the camera itself. Use the follow() method to make the camera follow the entity around. There are several built-in following styles, defined using FlxCamera's static properties. In this case I'm using STYLE_LOCKON, which will always center the followed object.

We can create a new camera, or use the default camera which already exists. Since we are covering the entire screen anyway, we can use the default camera.

Note that if you're creating a brand new camera object, you'll need to add it to the FlxG.cameras array for it to be visible.

camera = FlxG.camera;

camera.follow(cameraFocus, FlxCamera.STYLE_LOCKON);

When that is done, go to the update() function, which is called once every frame, and add 4 if statements that move the invisible entity around.

In HaxeFlixel, keyboard input can be detected using the FlxG.keys object very easily.

// Camera movement

if (FlxG.keys.anyPressed(["DOWN", "S"])) {

	cameraFocus.y += CAMERA_SPEED;

}

if (FlxG.keys.anyPressed(["UP", "W"])) {

	cameraFocus.y -= CAMERA_SPEED;

}

if (FlxG.keys.anyPressed(["RIGHT", "D"])) {

	cameraFocus.x += CAMERA_SPEED;

}

if (FlxG.keys.anyPressed(["LEFT", "A"])) {

	cameraFocus.x -= CAMERA_SPEED;

}

With a bit of math, let's add the world bounds to our invisible cameraFocus object.

// Camera bounds

if (cameraFocus.x < FlxG.width / 2) {

	cameraFocus.x = FlxG.width / 2;

}

if (cameraFocus.x > LEVEL_WIDTH * TILE_WIDTH - FlxG.width / 2) {

	cameraFocus.x = LEVEL_WIDTH * TILE_WIDTH - FlxG.width / 2;

}

if (cameraFocus.y < FlxG.height / 2) {

	cameraFocus.y = FlxG.height / 2;

}

if (cameraFocus.y > LEVEL_HEIGHT * TILE_HEIGHT - FlxG.height / 2) {

	cameraFocus.y = LEVEL_HEIGHT * TILE_HEIGHT - FlxG.height / 2;

}

Full PlayState.hx code:

package ;



import flixel.FlxCamera;

import flixel.FlxG;

import flixel.FlxSprite;

import flixel.FlxState;

import flixel.text.FlxText;

import flixel.tile.FlxTilemap;

import flixel.ui.FlxButton;

import flixel.util.FlxColor;

import flixel.util.FlxMath;

import openfl.Assets;



/**

 * A FlxState which can be used for the actual gameplay.

 */

class PlayState extends FlxState

{

	private var tileMap:FlxTilemap;

	static var TILE_WIDTH:Int = 16;

	static var TILE_HEIGHT:Int = 16;

	static var LEVEL_WIDTH:Int = 50;

	static var LEVEL_HEIGHT:Int = 50;

	static var CAMERA_SPEED:Int = 8;

	private var camera:FlxCamera;

	private var cameraFocus:FlxSprite;

	/**

	 * Function that is called up when to state is created to set it up.

	 */

	override public function create():Void

	{

		super.create();

		

		tileMap = new FlxTilemap();

		tileMap.loadMap(Assets.getText("assets/data/map.csv"), "assets/images/tileset.png", TILE_WIDTH, TILE_HEIGHT, 0, 1);

		add(tileMap);

		

		cameraFocus = new FlxSprite();

		cameraFocus.makeGraphic(1, 1, FlxColor.TRANSPARENT);

		add(cameraFocus);

		

		camera = FlxG.camera;

		camera.follow(cameraFocus, FlxCamera.STYLE_LOCKON);

	}



	/**

	 * Function that is called when this state is destroyed - you might want to

	 * consider setting all objects this state uses to null to help garbage collection.

	 */

	override public function destroy():Void

	{

		super.destroy();

	}



	/**

	 * Function that is called once every frame.

	 */

	override public function update():Void

	{

		super.update();

		

		// Camera movement

		if (FlxG.keys.anyPressed(["DOWN", "S"])) {

			cameraFocus.y += CAMERA_SPEED;

		}

		if (FlxG.keys.anyPressed(["UP", "W"])) {

			cameraFocus.y -= CAMERA_SPEED;

		}

		if (FlxG.keys.anyPressed(["RIGHT", "D"])) {

			cameraFocus.x += CAMERA_SPEED;

		}

		if (FlxG.keys.anyPressed(["LEFT", "A"])) {

			cameraFocus.x -= CAMERA_SPEED;

		}

		

		// Camera bounds

		if (cameraFocus.x < FlxG.width / 2) {

			cameraFocus.x = FlxG.width / 2;

		}

		if (cameraFocus.x > LEVEL_WIDTH * TILE_WIDTH - FlxG.width / 2) {

			cameraFocus.x = LEVEL_WIDTH * TILE_WIDTH - FlxG.width / 2;

		}

		if (cameraFocus.y < FlxG.height / 2) {

			cameraFocus.y = FlxG.height / 2;

		}

		if (cameraFocus.y > LEVEL_HEIGHT * TILE_HEIGHT - FlxG.height / 2) {

			cameraFocus.y = LEVEL_HEIGHT * TILE_HEIGHT - FlxG.height / 2;

		}

	}

}

If you test the game now, you'll see that you're now able to move the camera around using WASD or arrow keys.

We'll start implementing mouse based movement in the next part!