A major pain point while working with the 2D tilemap is that StartUp() of a tile seems to not be synchronous relative to its SetTile() function.
It seems that StartUp() is called the next frame, can anyone confirm this?
I can imagine this might have be done for performance reasons (would be nice if it’s documented), but it would be nice to have an option for the initialization to just be blocking/synchronous. Am I missing something obvious here?
The StartUp call should occur as part of SetTile during PlayMode. The only time it does not happen that way is if the Tilemap is loaded from a Scene. The StartUp call will occur in the first frame after the Tilemap is loaded.
Do let us know if that is not the case for you, thanks!
Sorry took me awhile to get back to this. I took another look at the behaviour and definitely seem two different patterns. One where StartUp is called synchronously after SetTile, and another one where StartUp called seems to becalled from Tilemap.Update. The two cases are two different scenarios in my game and I couldn’t figure out if it is causing the difference in behaviour. Here are the call stacks for the two cases.
Edit: apologies for the clutter, I marked interesting parts (SetTile and StartUp) using ###
CASE#1 StartUp NOT called synchronously – SetTile callstack
(Mono JIT Code) [ObjectTile.cs:57] pxcrpg.gameengine.visual.ObjectTile:GetTileData (UnityEngine.Vector3Int,UnityEngine.Tilemaps.ITilemap,UnityEngine.Tilemaps.TileData&)
(Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_void__this___Vector3Int_object_intptr& (object,intptr,intptr,intptr)
(mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
(mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
(mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
(Unity) scripting_method_invoke
(Unity) ScriptingInvocation::Invoke
(Unity) InvokeGetTileData
(Unity) Tilemap::RefreshTileAsset
(Unity) Tilemap::RefreshTileAsset
(Unity) Tilemap::RefreshTileAssetsInQueue<0>
(Unity) Tilemap::SetTileAsset
(Unity) Tilemap_CUSTOM_SetTileAsset_Injected
(Mono JIT Code) (wrapper managed-to-native) UnityEngine.Tilemaps.Tilemap:SetTileAsset_Injected (UnityEngine.Tilemaps.Tilemap,UnityEngine.Vector3Int&,UnityEngine.Object)
(Mono JIT Code) UnityEngine.Tilemaps.Tilemap:SetTileAsset (UnityEngine.Vector3Int,UnityEngine.Object)
### (Mono JIT Code) UnityEngine.Tilemaps.Tilemap:SetTile (UnityEngine.Vector3Int,UnityEngine.Tilemaps.TileBase) ###
(Mono JIT Code) [WorldTile.cs:236] pxcrpg.gameengine.presentation.WorldTile:AddWorldObjectToTileMap (UnityEngine.Vector2Int,string)
### a bunch of game script code ###
0x000002530f0b4243 (Mono JIT Code) pxcrpg.GameController:Update ()
0x000002531344b2e8 (Mono JIT Code) (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)
(mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
(mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
(mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
(Unity) scripting_method_invoke
(Unity) ScriptingInvocation::Invoke
(Unity) MonoBehaviour::CallMethodIfAvailable
(Unity) MonoBehaviour::CallUpdateMethod
(Unity) BaseBehaviourManager::CommonUpdate<BehaviourManager>
(Unity) BehaviourManager::Update
(Unity) `InitPlayerLoopCallbacks'::`2'::UpdateScriptRunBehaviourUpdateRegistrator::Forward
(Unity) ExecutePlayerLoop
(Unity) ExecutePlayerLoop
(Unity) PlayerLoop
CASE#1 StartUp NOT called synchronously – StartUp callstack
### (Mono JIT Code) [ObjectTile.cs:19] pxcrpg.gameengine.visual.ObjectTile:StartUp (UnityEngine.Vector3Int,UnityEngine.Tilemaps.ITilemap,UnityEngine.GameObject) ###
(Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_bool__this___Vector3Int_object_object (object,intptr,intptr,intptr)
(mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
(mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
(mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
(Unity) scripting_method_invoke
(Unity) ScriptingInvocation::Invoke
(Unity) InvokeStartUp
(Unity) Tilemap::StartUpTileAsset
(Unity) Tilemap::StartUpAllTileAssets
### (Unity) Tilemap::Update ###
(Unity) BaseBehaviourManager::CommonUpdate<BehaviourManager>
(Unity) BehaviourManager::Update
(Unity) `InitPlayerLoopCallbacks'::`2'::UpdateScriptRunBehaviourUpdateRegistrator::Forward
(Unity) ExecutePlayerLoop
(Unity) ExecutePlayerLoop
(Unity) PlayerLoop
CASE#2 StartUp called synchronously
### (Mono JIT Code) [ObjectTile.cs:19] pxcrpg.gameengine.visual.ObjectTile:StartUp (UnityEngine.Vector3Int,UnityEngine.Tilemaps.ITilemap,UnityEngine.GameObject) ###
(Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_bool__this___Vector3Int_object_object (object,intptr,intptr,intptr)
(mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
(mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
(mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
(Unity) scripting_method_invoke
(Unity) ScriptingInvocation::Invoke
(Unity) InvokeStartUp
(Unity) Tilemap::StartUpTileAsset
(Unity) Tilemap::RefreshTileAsset
(Unity) Tilemap::RefreshTileAsset
(Unity) Tilemap::RefreshTileAssetsInQueue<0>
(Unity) Tilemap::SetTileAsset
(Unity) Tilemap_CUSTOM_SetTileAsset_Injected
(Mono JIT Code) (wrapper managed-to-native) UnityEngine.Tilemaps.Tilemap:SetTileAsset_Injected (UnityEngine.Tilemaps.Tilemap,UnityEngine.Vector3Int&,UnityEngine.Object)
(Mono JIT Code) UnityEngine.Tilemaps.Tilemap:SetTileAsset (UnityEngine.Vector3Int,UnityEngine.Object)
### (Mono JIT Code) UnityEngine.Tilemaps.Tilemap:SetTile (UnityEngine.Vector3Int,UnityEngine.Tilemaps.TileBase) ###
(Mono JIT Code) [WorldTile.cs:236] pxcrpg.gameengine.presentation.WorldTile:AddWorldObjectToTileMap (UnityEngine.Vector2Int,string)
<<< a bunch of dame script code >>>
(Mono JIT Code) pxcrpg.command.CommandRunner:Execute (pxcrpg.command.ICommand)
(Mono JIT Code) [BlueprintInputHandler.cs:93] pxcrpg.ui.BlueprintInputHandler:PlaceBlueprint (UnityEngine.InputSystem.InputAction/CallbackContext)
(Mono JIT Code) UnityEngine.Events.InvokableCall`1<UnityEngine.InputSystem.InputAction/CallbackContext>:Invoke (UnityEngine.InputSystem.InputAction/CallbackContext)
<<< a bunch of InputSystem code >>>
(Mono JIT Code) UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr)
(Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_void_int_intptr (object,intptr,intptr,intptr)
(mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
(mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
(mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
(Unity) scripting_method_invoke
(Unity) ScriptingInvocation::Invoke
(Unity) ScriptingInvocation::Invoke<void>
(Unity) Scripting::UnityEngineInternal::Input::NativeInputSystemProxy::NotifyUpdate
(Unity) SendInputEventsToScript
(Unity) `InternalInitializeModule_Input'::`2'::PreUpdateNewInputUpdateRegistrator::Forward
(Unity) ExecutePlayerLoop
(Unity) ExecutePlayerLoop
(Unity) PlayerLoop
Yes, the two scenarios which you are seeing are correct.
- Startup called on SetTile
- Startup called when Tilemap is loaded. This is for users who have need this callback to set up their Tiles whenever the Tilemap is loaded (eg. instanced from a Prefab). As SetTile is not called for this by the user, this is done on the first update after the Tilemap is loaded.
@organick Could you share your use-case where one scenario works for you but the other does not? Thanks!
It’s not they don’t work entirely, it’s just that it adds complication in dealing with the difference in behaviour.
I see, I think now that you mention Tilemap loading, the call stack makes more sense to me, as I do create a new tilemap right before calling SetTile() on it. And before the first Update() call is made to the tilemap, any of its tiles’ StartUp won’t be called.
To elaborate on my specific use cases I mentioned above:
-
Open world chunking. As the player avatar moves around, new chunks (containing tilemap) are added to the scene, and immediately populate with objects (tiles). This is where I see the disjoint between SetTile() vs StartUp(), which as you explained, happened because the Tilemap was just created.
-
Player place objects (tiles) on an world chunk (containing tilemap) that has already been loaded. In this case, calling SetTile() is followed with a synchronous call to StartUp().
In either cases, I do bunch of setups and initialization for the newly created GameObject within StartUp(). And my intented code flow is something like this:
- SetTile()
- StartUp() // the new game objects are now ready
- more code that relies on new game objects being ready
But obviously, this won’t work if StartUp() is called only at some later point in time.
Again, I can write extra code to deal with this, but I rather have one single code path. It seems that if I can somehow call Update() on the Tilemap right after I created it, it might fix my problem. Not sure if that’s possible or if it’s even a good idea.
Thanks for taking a look at this.
@organick Ok, I am beginning to understand the issue here. If it is possible, could you share more details on how you create the Tilemap and set the Tiles? I assume that the flow that you are seeing is something like this:
-
Create Tilemap
-
SetTile
-
StartUp
-
First Update
-
StartUp
-
Second Update
-
…
On an arbitrary frame N:
- Something in the game triggered the instantiation of a prefab that has a Tilemap somewhere in its hierarchy. (The exact scenario is that player moving to an unloaded part of the world and triggers a new world chunk to get loaded into the scene).
- Immediately after the Tilemap instantiation, the script gets a reference to that Tilemap component and call SetTile() a bunch of times with some Tile T.
- At this point I expected Tile T to immediately call its StartUp() method, but as you explained in your previous post (and as I observed in the call stack), the StartUp() method for Tile T only gets called later in the frame when Tilemap.Update() gets called for the first time ever.
The game is almost entirely script-driven, so the tilemap is also created from a script during runtime.
I hope this is not too confusing 
Thanks for the clarification! We will try to resolve this case so that you do not need special handling for the two different StartUp cases.
Also, would it be possible to share the version of the Unity Editor you are using?
Here’s the Unity Editor version I’m using:
2020.2.7f1.4104
I do have one follow-up question I have that sort of stems from this.
Would you recommend using 2D Tilemap for a purely script-driven game?
A couple of folks on another post suggested that the 2D Tilemap might not be for my game, due to the lack of some hooks and therefore control (the topic of my other post was about object-pooling the instantiated tile GameObjects).
I’m curious about your opinion on this. Does the 2D Tilemap “vision” includes purely script-driven games? I’m trying to decide whether rolling out my own solution would be the better way to go.
Thanks!
Could you clarify what you mean by a purely-script driven game? I am not certain if the 2D Tilemap would suit all your needs and use-cases, but if you do share them, we would certainly try to improve it and add new features where possible! Thanks!
Yes, for sure.
The world in my game is not handcrafted, it’s procedurally generated. So after the world data gets generated (or loaded from a save file), my scripts will simply create whatever objects necessary and put them into the scene. One such object is the tilemap.
Since the world can be huge, I needed to divide the world into “world chunks” such that I only materialize – that is, putting GameObjects into the scene – when a world chunk is nearby the player. One of the main GameObject in a world chunk is a 2D Tilemap. So as player moves around in the world, new world chunks (Tilemap) gets loaded in, and the ones far away gets unloaded.
As you can see, the game project does not benefit much from the nice editor tools such as the various brushes and tilemap editor.
Here’s a snapshot of the game, each of the cyan squares is a 2D Tilemap. Well, more precisely there could be multiple 2D tilemaps in a world chunk. One for the ground, one for interactable objects (e.g. trees, rocks), and there might be more.
Edit: just adding a bit more info about which Tilemap API my game is using at the moment:
- Tilemap.SetTile(); to add new tile or clear a tile during runtime
- Tile.Startup(); put custom code for the GameObject instantiated by the Tile
- Tile.GetDataTile(); sets the prefab for the GameObject I want the tile to instantiate
- RuleTile; this was quite handy for handling walls and doors
- Tilemap.ClearAllTiles(); I used this when unloading a world chunk (tilemap), however clearing out plenty of tiles that have gameobjects seems to slow for one frame operation. I instead call SetTile(null) over multiple frames.
Let me know what you think. Happy fridays 
That sounds like the Unity 2D Tilemap would suit your needs.
For better performance, you could prefer to use Tilemap.SetTiles
or Tilemap.SetTilesBlock
, which reduces the API overhead compared to calling Tilemap.SetTile
individually.
If you have other suggestions or are lacking certain features for your use-case, do let us know! Thanks!