Proposal: Tilemap changes

I really appreciate your help

I’ve merged TilemapData into Tilemap, I agree that a tighter API (in general) is better overall.

I just need to finish the CairoTilemap and do a little work for DOM mode, and I think we’ll be in business :shipit:

As long as you store your Tilesets in an array, then I guess you could make that work. Instead of a Tile object, you’d have a set of floats, one of which would be an index in the Tileset array, and one of which would be a tile ID. (Then you’d have x, y, width, height, etc.)

You certainly could use abstracts here, but you’d still need a separate array to store the abstracts. Each abstract would need a reference to the Float32Array, plus an index in that array, so you’re still looking at an array of objects.

If you’re really willing to complicate the code, each abstract could store only the index, which would mean the array of abstracts would be an Array<Int>. The drawback is that you’d have to pass the Float32Array to every single one of the abstract’s functions.

All in all, I think it would be one big headache without much benefit. Is the array of Tile objects really that much of a bottleneck?

Nice!

Bit slow on my reply, but here are my thoughts on the OP… (will read through replies and post again)

Overall: Is there a need to have a fixed size for the Tilemap on creation? and could it be amended on the fly ? ((var tilemap = new Tilemap (800, 600);))
I’m used to using the Sprite displayobject on stage when using tilesheets, Sprite isn’t restricted by bounds, and makes managing window size changes and device orientation change fairly trivial, rather than having to recreate the Tilemap object whenever a viewing method/window changes.

option 1, ok, couldn’t a tileset be assumed to be a tilelayer ? or are they currently seperated so that two layers could be created from the same bitmapData (at a loss of performance) ?
option 2, imho the developer should retain control over the draw order of the tiles, rather than them being resorted internally, if tiles overlap and are internally moved to earler or later draw calls, their may be undesired order effects.
Also, Setting the tile ID manually seems unnecessary when unique ID’s are required and their actual value isn’t important. imho it would be better to have the tileset control the ID’s when tiles are added.
option 3, am not sure i understand when the bitmapdata would be passed to the GPU during this method? i understood it required passing early, rather than if a new bitmapdata was used for the first time mid program flow.
option 4, i would say unneeded complexity, the point of the API is batch rendering and performance. anything which unneccesarily slows the redering should be ditched.

edit: ok caught up now

My thoughts as i read through,

Personaly i dont like the idea of the TileData object.
It could in future include offset, centrepoint, hit rectangle information etc. But unless the Tilemap requires use of these variables, they’re unnecessay within the class, and could easily be added by the developer when they extend the class if required within their code.
I also dont like the appearance of encouraging new developers to use multiple bitmapdata, and Tilemap rendering, if they’re unaware that it’s a batch process, and dont know what that is, and when it should be used.

If Tileset is being kept, Option B
The incluusion of Tileset.getRect(id:Int):Rectangle; appears instantly very useful for me!. very useful when falling back to manually rendering using bitmapdata.draw calls on targets that cant or i dont want using GPU batch rendering.

1 Like

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