How to update a secondary tilemap when a tilemap is being edited?

I want to implement a tilemap system inspired by Oskar Stålberg’s: dual-grid system.

TLDR: I have a ‘logical’ tilemap where I paint tiles to describe a logical representation of my world, and a secondary ‘display’ tilemap offset (.5,.5) units which displays different tiles based on whether or not there’s a tile in the 4 ‘neighbor’ tiles within the logical tilemap.

So far, I’ve set up a simple script that takes both my tilemaps, creates a lookup table to map the neighbor states to display tiles, and a function to update the display tiles, and a small custom editor with a button to call that function to update the display. This works but is janky and clunky to work with (either I’m looking at the ugly logical map while editing, or I don’t see my edits at all until I click that button).
I want to keep the display tilemap in sync with the logical map as it’s being edited, instead of needing to press a button (and ideally, only updating the 4 tiles that need to change instead of redrawing the entire display as I’m currently doing with the button).

I’ve looked a bit into the Tilemap 2d code trying to find the best place to hook in and handle this, but it’s a bit impenatrable to me.

Should I be looking at a custom brush? Custom TilemapEditorTool? Can RuleTiles be used to do something like this? Or is there some event i can listen for in my editor script to see when/how the logical tilemap is updated?

I’m new to this kind of Editor workflow tooling, so any help pointing me in the right direction would be very appreciated.

This editor only delegate is probably a good starting point: Unity - Scripting API: Tilemaps.Tilemap.tilemapTileChanged

Not sure what the best point to hook into this would be, potentially all the time with an [InitializeOnLoad] editor only static class, listening for changes to your data oriented tilemaps, and ensuring to update the visual tilemaps.

Props for taking the route of separating data from visuals. I honestly just wrote my own C# data structures to represent my data/logical grid, and keep the normal visual tilemap updated at runtime.

1 Like

Thanks! I’ll look into this and report back!

I honestly just wrote my own C# data structures to represent my data/logical grid, and keep the normal visual tilemap updated at runtime.

So do you just edit your map in a non-visual data way? I feel inclined to continue using the Tilemap to visually edit my map, but trying to improve the workflow.

My project involves procedurally generated worlds, so at present there isn’t a need to manually edit my maps.

At some point in the future I probably will need to hand edit some levels but I haven’t put much thought into how I might accomplish that.

Part of me wants to make my own level editor. But only because I enjoy editor/tools scripting…

1 Like

Eyyyyy!!! This works! A few things odd things to note:

Tilemap.tilemapTileChanged is static which means I need to do something like if (tilemap != logicalTilemap) return; in my handler, which is a bit weird but easy enough.

Still getting used to editor scripts, (this is a monobehaviour with [ExecuteAlways] and the whole file is wrapped in an #if UNITY_EDITOR… I’m not sure if this is good practice??). I subscribe to the event in Awake, unsubscribe in OnDestroy which I think works, but I need to leave prefab mode and reenter it to trigger Awake for the prefab with this component on it. I guess that works, but seems loosey goosey in a weird way that makes me suspect i’m doing something wrong?

I was thinking something using [InitializeOnLoad] on an editor-only static class, shown simply in the docs here: Unity - Manual: Running Editor Script Code on Launch

You can hook into the delegate there, and process all calls to the event without needing a Monobehaviour component to handle it.

Hmm, thanks for that lead, but I’m not sure about it… I could certainly hook into the delegate there, but it’s not clear to me how I’d reference various dual-grid tilemaps in my different map prefabs in that static class.

I mean, I could create a Dictionary<Tilemap, Tilemap> and a way to register tilemaps that need to be synced, but then registering/deregistering those tilemaps has to happen somewhere, and the way I know how to do that at the moment would still be an editor-only monobehaviour on those tilemaps that access that static class and register/deregister themselves. :sweat_smile:

I figure the your tilemaps exist on the same game objects as one of more of your custom monobehaviours that manage them. Then it’s a simple case of using Get/TryGetComponent to infer which one you’re dealing with. And ideally your custom components reference either one or both of your tilemaps to make it easy to modify the correct ones.

Yes indeed, it’s set up that way (GO with DualGridTilemap Monobehaviour, with two nested GOs with Tilemaps) but referencing the various prefabs of those gameobjects from the static class you propose is what doesn’t seem obvious to me. EG: When I’m editing “World1” I need world1’s logical map to be received into the handler, and updating world1’s display, but there are other tilemaps that aren’t ‘dual gridded’ that I want to ignore in handler.

FWIW, I realize I may have overcomplicated it with the Dictionary<Tilemap, Tilemap> and as a result misled you as to what part was confusing to me XD But I used Dictionary<Tilemap, Tilemap> because the tilemapTileChanged event returns the Tilemap that has changed, so it seems easier to store it this way to quickly go from changedTilemap to tilemapToBeSynced.

So… I guess I could (instead of registering/deregistering), on every handler call, do a GetComponentInParent to find DualGrid to determine if it’s a dualGrid and needs to be synced. I don’t like calling GetComponents on every update to a tilemap in my project, but I guess it works (and maybe it’s an over-optimization mindset to be resistant to this solution)?

I once did something a little bit like this with Custom Brushes. Though tilemapTileChanged probably could be pushed to do most anything.

Examples: 2d-extras/Editor/Brushes at master · Unity-Technologies/2d-extras · GitHub (also available as a Package)

It’s not going to be a performance concern. A single GetComponentInParent is barely a blip on the profiler. It’s not like you’re making the call dozens or hundreds of times every time it happens.

1 Like