Proposal: Tilemap changes

Could there be a faster method to create TileMap? Like this

var myTileMap = new Tilemap(_params1,_params2,...,"tiles.png");

and then add it to the stage like this

addChild(myTileMap);

myTileMap.x = this.location.in.X;
myTileMap.WhichTileImage[2ndTile.Or.So];
myTileMap.others = others;

No need to do this manually. Tilemap automatically falls back when necessary.

This also explains why the size is fixed. The Flash target copies tiles onto one big bitmap, and the big bitmap needs a fixed size. On other targets, you can draw beyond the bounds of the Tilemap, so you can just ignore width and height if you aren’t targeting Flash.

You’re right that it doesn’t need any hit rectangle information, but it would definitely use the offset and rotation point.

Agreed, but as loudo pointed out, sometimes you can’t avoid using multiple. I think the best solution is to update BunnyMark to demonstrate how to batch tiles:

//Like wabbit_alpha.png, except it contains two rabbits.
var bitmapData = Assets.getBitmapData ("assets/wabbits_alpha.png");

var tileset = new Tileset (bitmapData);
tileset.addRect (new Rectangle(0, 0, 26, 37));
tileset.addRect (new Rectangle(26, 0, 26, 37));

var tilemap = new Tilemap (800, 600, tileset);
addChild (tilemap);

for (i in 0...100) {
    var bunnyID:Int = Std.int (Math.random () * tileset.tileCount));
    var bunny:Bunny = new Bunny (bunnyID);
    bunny.x = 0;
    bunny.y = 0;
    bunny.speedX = Math.random () * 5;
    bunny.speedY = (Math.random () * 5) - 2.5;
    bunnies.push (bunny);
    tilemap.addTile (bunny);
}

Actually, I guess it’s still pretty obvious how to use multiple bitmaps. Ideally, we want to force users to look it up. Then when they ask about it, we can explain why it’s slower, and maybe help them do it the right way.

Maybe what we need to do is add the Tileset functions to Tilemap, so that you never have to interact with the Tileset class as long as you only have one tileset.

var bitmapData = Assets.getBitmapData ("assets/wabbits_alpha.png");

//When you pass a BitmapData object to Tilemap's constructor,
//it creates a Tileset object from that data, and sets it as the default.
var tilemap = new Tilemap (800, 600, bitmapData);

//The addRect and getRect functions are passed through to the
//default Tileset.
tilemap.addRect (new Rectangle(0, 0, 26, 37));
tilemap.addRect (new Rectangle(26, 0, 26, 37));

addChild (tilemap);

Hello. Thanks for your work in the Tilemap api. I’ve made some test and it’s ok on flash but there are some bugs in other targets.

I’m currently working on a Tileset extension that allow to automatically create rectangles for each tiles in the atlas.
For now I have Sparrow and Spriter atlases working. I will create a git repo soon.

It allows to positionate correctly the Tile depending on the original image and not on the trimmed one (Spriter works like that) :

Tile.x = x + tileset.getSize(Tile.id).x;

Hi, thanks for your work and feedback!

Are the Sparrow and Spriter parsers very large? I wonder if we should include any of that in the core Tileset implementation?

We should consider an offset value within Tileset, which we could apply internally when rendering tiles, although this means we will need to process each tile.matrix value

No that’s only 30 lines of code each.

Here is the code so that you can take a look (mainly based on tilelayer lib actually) :

openfl.display.TilesetEx.hx (base class to add map of names and offsets for all parsers but we can merge it with Tileset.hx)

package openfl.display;
import openfl.geom.Rectangle;

class TilesetEx extends Tileset
{
	var defs:Map<String, Int>;
	var sizes:Array<Rectangle>;
	
	public function new(bitmapData:BitmapData) 
	{
		super(bitmapData);
		defs = new Map<String, Int>();
		sizes = [];
	}
	public function getImageID(name:String):Int
	{
		if (defs.exists(name))
		{
			return defs.get(name);
		}
		return -1;
	}
	public function getSize(id:Int):Rectangle
	{
		if (id < sizes.length)
			return sizes[id];
		else 
			return null;
	}
}

openfl.display.TilesetSparrow.hx

package openfl.display;
import openfl.geom.Rectangle;

class SparrowTileset extends TilesetEx
{

	/**
	 * 
	 * @param	img texture atlas
	 * @param	xml
	 */
	public function new(img:BitmapData, xml:String) 
	{
		super(img);
		
		var x = new haxe.xml.Fast( Xml.parse(xml).firstElement() );
		for (texture in x.nodes.SubTexture)
		{
			var name = texture.att.name;
			var rect = new Rectangle(
				Std.parseFloat(texture.att.x), Std.parseFloat(texture.att.y),
				Std.parseFloat(texture.att.width), Std.parseFloat(texture.att.height));
			
			var size = if (texture.has.frameX) // trimmed
					new Rectangle(
						Std.parseInt(texture.att.frameX), Std.parseInt(texture.att.frameY),
						Std.parseInt(texture.att.frameWidth), Std.parseInt(texture.att.frameHeight));
				else 
					new Rectangle(0, 0, rect.width, rect.height);
			
			var id = addRect(rect);
			defs.set(name, id);
			sizes[id] = size;
		}
	}
	
}

I should probably raid this

Hi, friends!

var rect = _tile.tileset.getRect(_tile.id);

Error 1009 null object ref - cause tileset doesn’t sets in Tilemap methods addTile, addTiles and addTileAt - check this bug plz.

And I would be happy if you add tunable center-point for rotations and scales. It really is a huge pain in the ass)

Hi!

tileset.getRect should be implemented, tile.tileset is an optional property (if you use a unique tileset for one specific tile). The other method of getting a Tileset reference might be using tilemap.tileset, but it’s all in how you set up your code

We’ll consider center points, the issue is that we don’t want to burden the renderer with too much matrix manipulation, but I understand how this would make things more convenient. Happy if someone is interested in helping

You need this instead :
var rect = tilemap.tileset.getRect(tile.id);

I suppose you have set the default Tileset in Tilemap but you have not set any Tileset for the Tile. This is a correct implementation when you use only one Tileset. With this implementation, the renderer will use the default Tileset from Tilemap. If you want to use more than one Tileset, you will have to set a Tileset for each Tiles (or at least for each Tiles that don’t use the default Tileset in Tilemap). So the error you get is normal because Tile.tileset is null (openfl does not copy/paste the default Tileset here, it’s up to you to set it). Hope it’s clear enough.

So is there a guide to using the tilemap yet? I know theres a bunnymark, however it doesnt really specify how to use it for tiling as it only uses a single bunny image. Also does anybody know if it is bound to change again?

I don’t think it will change anymore.

And here is my library to import texture atlas format (Sparrow, Spriter, etc.) : https://github.com/loudoweb/openfl-atlas

Here is the how to:

//create a Tileset and add rectangles for all your tiles inside your atlas
var tileset:Tileset = new Tileset(Assets.getBitmapdata('atlas.png'));
var tileId:Int = tileset.addRect(new Rectangle(0,0,100,100));
//or you could import one using my openfl-atlas library
var tilesetEx:TilesetEx = new SparrowTileset(Assets.getBitmapdata('atlas.png'), Assets.getText('atlas.xml'));
//then create a Tilemap (this the displayObject)
var tilemap = new Tilemap(width, height, tileset, smoothing);
addChild(tilemap);
//add a tile using the tileId given by the addRect method
var tile:Tile = new Tile(tileId, x, y, scaleX, scaleY, rotation)
//or retrieve the tileID by name using my openfl-atlas library
var tile:Tile = new Tile(tilesetEx.getImageID(name), x, y, scaleX, scaleY, rotation);
//add the Tile into Tilemap
tilemap.addTile(tile);
//if you want to specify a different tileset for a Tile you can do that:
tile.tileset = tileset2;

Thanks for the help, I’ve got tiles going again now. What is the best way to do a scrolling map from this, it seems to take a lot of resources to continuously empty the array reload them every frame, though I still have to load new tiles in as the player moves. Do you guys use some form of binary search tree or something?

It seems rather difficult.

First: have you tried just rendering the entire map, including everything offscreen? If you can do that without much lag, maybe that’s good enough.

Otherwise…

Let’s say the screen is 15 tiles wide and 15 tiles tall. This means you can see a maximum of 256 (16*16) tiles at a time. My advice is to allocate an array of exactly 256 tiles, and use those.

Each frame, you’d check where the top-left corner of the camera is. Then you’d take the first tile, and set its id to whatever should show up in the top-left corner. Also, set its x and y coordinates to place it in the right spot. Repeat for the other tiles.

In case you’re confused, here’s a demo that I spent way too long on. Each of the 256 tiles are numbered so that you can see how they move around. You can also check out the source code if you’re curious.

This is great, thanks very much for the help.

Hi everyone,

I give suggestion if you have very large bitmap and loads very slow or game launcher will freeze.

Why not we use OpenGlView because bitmapdata draws very slow. From SimpleOpenGLView you can try OpenGLTile, OpenGLTileMap, …

Okay I will write own TileMap under OpenGL. After my work I will try. If it works faster than normal bitmapdata TileMap.

I give suggestion:

  • Animator
  • Effectiver
  • Ammo
  • Enemy
  • Movable
    And more functions

Easy to develop if they don’t know how do they develop TileMap games.
Thanks for taking care with my suggestions!

2 Likes

Sorry for stupid question, but how can I rotate tile about arbitrary pivot point?

Also what about color transform support?

rotate around point -> http://www.siggraph.org/education/materials/HyperGraph/modeling/mod_tran/2drota.htm (the picture at bottom;)

may this formular helps:

positionVector = (positionVector - pivotVector) * matrix2D(
                                                            cos(alpha),  -sin(alpha) ,
                                                            sin(alpha),   cos(alpha)
                                                           )
                                 + pivotVector
1 Like

You need to compute the final position by yourself like @maitag suggested.

But be careful I think there is some bug when you use rotation after scaling or vice versa with specific values. Anybody has the same bug?

Currently tile.matrix is what the renderer uses, the get/set x, y, rotation (etc) are really convenience methods over the matrix, but as you know, the order of scale vs rotation affects this.

We don’t support a center or offset on tilesets, but perhaps for ergonomics, we need to. I wanted to avoid doing matrix math like this in the tile render, but perhaps this is important to usability

What do you mean? That my issue is not a bug? So how can I use negative scale for example? Should I only use matrix instead of get x,y,rotation and scale?