Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On tileset click, go to layer #3256

Closed
djasnowski opened this issue Feb 1, 2022 · 14 comments
Closed

On tileset click, go to layer #3256

djasnowski opened this issue Feb 1, 2022 · 14 comments

Comments

@djasnowski
Copy link

Let's say you have a orthogonal 2D tile map. Two layers, background and foreground with two tilesets. One for each layer that will always stay in their respective layer.

Is there a way on Tiled to make it so when you click on, for example, the background tileset, for Tiled to automatically go to the "Background" layer? One less click. Less work.

@eishiya
Copy link
Contributor

eishiya commented Feb 1, 2022

This is not currently possible, since Tiled has no concept of associating tilesets with layers. It's also not currently possible with scripting, because scripting has no way to react to the selected tile/tileset changing, except as part of a scripted Tool.

If a signal like MapEditor.selectedTilesChanged (akin to the MapEditor.currentWangColorIndexChanged signal that was recently added) is added, it would be possible to write a script that checks the currently selected tiles' tileset and changes the selected layer(s) in the current map when it sees that signal.

@bjorn
Copy link
Member

bjorn commented Feb 1, 2022

If a signal like MapEditor.selectedTilesChanged (akin to the MapEditor.currentWangColorIndexChanged signal that was recently added) is added, it would be possible to write a script that checks the currently selected tiles' tileset and changes the selected layer(s) in the current map when it sees that signal.

I had a quick look, and actually there are undocumented currentTileChanged and stampCaptured signals on the TilesetsView. So you could use for example:

tiled.mapEditor.tilesetsView.currentTileChanged.connect(() => {
    tiled.log(tiled.mapEditor.tilesetsView.selectedTiles);
});

The feature might still make sense as a built-in, but it should be possible to select a layer by name based on a custom tile or tileset property.

@eishiya
Copy link
Contributor

eishiya commented Feb 1, 2022

Just how many undocumented signals are out there? I even checked TilesetsView hoping there was a signal like that. 🤣 My # 1 request for Tiled: document the signals available xP Ideally on a single page so they're easier to find.

@bjorn
Copy link
Member

bjorn commented Feb 1, 2022

document the signals available

Right, so I've actually tried to do this in adding the missing "changed" signals in 0c7b83d, but this one is kind of an "accidental signal", that wasn't intentionally exposed to the scripts. It just happens to be part of the TilesetDock class that I decided to expose directly (for better or worse, since this actually allows you to change its window title, floating status, etc...).

@djasnowski
Copy link
Author

djasnowski commented Feb 3, 2022

@bjorn Thank you. Promising. Is this where I can get started on scripting? https://doc.mapeditor.org/en/stable/reference/scripting/

From a cursory glance, I injected what you wrote and it seems like it would do trick ... but alas.. nothing?

image

@bjorn
Copy link
Member

bjorn commented Feb 3, 2022

From a cursory glance, I injected what you wrote and it seems like it would do trick ... but alas.. nothing?

Well, what I wrote was just a start, and it just logs to the Console some debug print of which tiles are selected each time the current tile changes. To be actually useful, you'd have to:

  • Set a custom property on your tiles, probably a string property called "layer", and give it the name of the layer. Note that you can assign a property to multiple tiles at once.
  • In the script, get the first tile from the selectedTiles and get its layer property, for example let layerName = selectedTiles[0].property("layer").
  • Iterate over all layers of the map, looking for one with the given name. That's less trivial than it sounds, but here's an example doing the same with the layer ID, which could be adjusted.
  • Set that layer to be the current one, with tiled.activeAsset.currentLayer = foundLayer.

@djasnowski
Copy link
Author

djasnowski commented Feb 4, 2022

tiled.mapEditor.tilesetsView.currentTileChanged.connect(() => {
  tiled.log(tiled.mapEditor.tilesetsView.selectedTiles);
  tiled.log(tiled.mapEditor.tilesetsView.selectedTiles.length);
  let layerName = tiled.mapEditor.tilesetsView.selectedTiles[0].property("layer");
  tiled.log(layerName);
  tiled.log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
});

output:

Tiled::EditableTile(0x55d6e651c400)
1

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

image

How can get the layer name if this custom property I am trying to fetch won't be returned? What am I doing wrong?

Update

Even doing tiled.log(Object.keys(tiled.mapEditor.tilesetsView.selectedTiles)); resulted in 0. Are these layers not objects when it comes to scripting?

@eishiya
Copy link
Contributor

eishiya commented Feb 4, 2022

tiled.mapEditor.tilesetsView.selectedTiles is not a layer, it's the array of tiles currently selected in the tilesets panel. Object.keys returns the keys of the values of that array, which are just the array indices in this case. If you're getting a 0, it's probably because the array contains just one index: index 0, containing the first selected tile.

tiled.mapEditor.tilesetsView.selectedTiles[0].property("layer"); returns the value of the "layer" property of the currently selected tile correctly for me. Your whole script, with the signal, works... somewhat correctly? It prints the correct value, but only after clicking off the tile in question. Seems that perhaps this signal triggers before the selected tile is actually updated...?

@djasnowski
Copy link
Author

AH. Ah. I see. You're getting the tile. I need to learn to read. I was doing the layers (tileset) property and getting nothing. I amended the tileset and did the same.. I see. I see.

I can just do tiled.activeAsset.currentLayer by looking at the type property on the tile and setting it there.

@eishiya
Copy link
Contributor

eishiya commented Feb 4, 2022

Yep! Just make sure you check that the activeAsset.isTileMap before you try to do anything with its layers.

Also, watch out for that issue I mentioned where the tiled.mapEditor.tilesetsView.currentTileChanged signal seems to trigger before tiled.mapEditor.tilesetsView.selectedTiles is updated, causing the script to be "behind" a tile. @bjorn Is this intentional? Am I running into a bug/regression when I test that script?

tiled.mapEditor.tilesetsView.currentTileChanged.connect(() => {
	tiled.log( tiled.mapEditor.tilesetsView.selectedTiles[0].id );
});

consistently shows me the ID of the tile I previously clicked rather than the tile I just clicked...

@djasnowski
Copy link
Author

djasnowski commented Feb 4, 2022

Yeah. I'm getting it now. I see. And yeah, that's interesting.

Look at the API, https://www.mapeditor.org/docs/scripting/classes/layer.html looks like I have to get the layer and select it, too. Hmm more work to do it seems for me.

I can also do this: tiled.mapEditor.tilesetsView.selectedTiles[0].tileset.name;

And I get the tileset of the tile so now all I have to do is go through the layer and select where the tileset and the layer name match... and select it! Fun.

@eishiya
Copy link
Contributor

eishiya commented Feb 4, 2022

Yes, you'll need to iterate all the layers until you find the one you want. If you use groups, this can get complicated. Here's a recursive function that'll take care of that, plus some extra code you may find useful.

let map = tiled.activeAsset;
if(!map.isTileMap)
	return;

let targetName = /* read the name from the selected tile property, as you figured out in earlier posts */;

function findLayer(curLayer) {
	if(curLayer.isGroupLayer || curLayer.isTileMap) {
		let numLayers = curLayer.layerCount;
		for(let layerID = 0; layerID < numLayers; layerID++) {
			let found = findLayer(curLayer.layerAt(layerID));
			if(found)
				return found;
		}
	} else if(curLayer.name == targetName)
		return curLayer;
	
	return null;
}

let layer = findLayer(map);
if(layer) //only change the layer if a match was found
	map.currentLayer = layer;

@djasnowski
Copy link
Author

Yeah. I was looking for tiled.activeAsset. Thank you. I got what I needed and this issue has opened a whole new world for me. Thank you!

You may close this issue if need be.

@bjorn
Copy link
Member

bjorn commented Feb 4, 2022

Also, watch out for that issue I mentioned where the tiled.mapEditor.tilesetsView.currentTileChanged signal seems to trigger before tiled.mapEditor.tilesetsView.selectedTiles is updated, causing the script to be "behind" a tile.

Right, something I hadn't realized (and this was an accidentally exposed signal, remember?). Maybe it works better if you connect to the other accidental signal, stampCaptured, instead.

You may close this issue if need be.

Alright, I'll close this issue.

The synchronization could also be implemented in the other direction, so that when you change the layer (TileMap.selectedLayersChanged) you would set tiled.mapEditor.tilesetsView.currentTileset to the one matching the selected layer's name.

Btw, since this kind of functionality seems useful to other people as well, you should consider publishing your extension. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants