OndrejNepozitek / Edgar-Unity

Unity Procedural Level Generator
https://ondrejnepozitek.github.io/Edgar-Unity/docs/introduction
MIT License
817 stars 70 forks source link

Shared tilemaps' material reset when build the project #67

Closed longtran2904 closed 4 years ago

longtran2904 commented 4 years ago

I had a 2d scene with some 2d global lights and free form lights (from the URP package). When i had it in the editor and the game view, it looked perfect but when i built the project suddenly it would remove all the lights (i didn't see any lights). After a few days of debugging i managed to know why it happend. I think that when i build the project all the shared tilemaps' material switched from "Sprite-lit-default" to "Sprites-default" which didn't work with 2d lightings. If i didn't choose the "Intialize Shared Tilemaps" and disable all the room templates rendering and collider it would work perfectly. If i intialize shared tilemaps but didn''t disable the render and collider it would still show the room templates perfectly with all the lightings. So i thought that it must have been because the shared tilemap's material has been reset to default when been built. Why? How to fixed this?

OndrejNepozitek commented 4 years ago

The shared tilemaps all recreated every time you run the generator. How do you assign the material to the tilemaps? The correct approach would be to add this logic to your custom tilemap layers handler (or to a custom post-processing task). I would probably add a public field to the custom tilemap layers handler with the material that you want to use and then when you initialize the tilemaps I would assign this material to all the tilemap layers.

longtran2904 commented 4 years ago

When i install the URP package it had an automate option to switch all the materials in the project and in the editor it all looked fine but maybe when I built it it suddenly got reseted. How to assign the material through script? In the custom tilemap script there is a function called CreateTilemapGameObject which add and setup the tilemap renderer to each tilemap so i need to assign the material in here right?

longtran2904 commented 4 years ago

OMG i finally managed to make it worked by your way! Thank you so much! You have no ideas how long i spend debugging this! Thank you!

longtran2904 commented 4 years ago

Okay so I've just debugged the material that I assigned it through the inspector and it appears to be null which is weird because I assigned it through the inspector. image image Why did it null?

OndrejNepozitek commented 4 years ago

Hey! So custom tilemap layers handlers are used for two things - creating the structure of tilemap in room templates and creating the structure in generated levels. I would guess that you have a problem with room templates. The problem is that even though you assign the material to the instance of the handler, you probably do not use that instance when creating a room template.

You probably have something like this:

public class CustomRoomTemplateInitializer : BaseRoomTemplateInitializer
{
    protected override void InitializeTilemaps(GameObject tilemapsRoot)
    {
        // Create an instance of your tilemap layers handler
        var tilemapLayersHandler = ScriptableObject.CreateInstance<CustomTilemapsLayerHandler>();

        // Initialize tilemaps
        tilemapLayersHandler.InitializeTilemaps(tilemapsRoot);
    }
}

The problem is that you create a new instance of the CustomTilemapsLayerHandler instead of using the instance with your material. So in order to fix it, you would have to explicitly load the saved instance of the scriptable object. Or maybe a simple solution is to create an empty room template, fix the material manually and then duplicate the room template.

However, as I'm looking into that, there should be an even easier fix. I have something like this for the universal render pipeline:

image

And you can see that the Default Material Type is set to Lit. With this setting, I don't have to handle materials manually - they are lit by default if I don't change it.

longtran2904 commented 4 years ago

Thanks for replying! That is exactly what I've just thought about! Do I still need to have the line: tilemapRenderer.sharedMaterial = material; in the CustomTilemapsLayersHandler.cs?

OndrejNepozitek commented 4 years ago

If you have the default value correctly set, then you do not need to set the material manually so I'd expect that the line is no longer needed (it seems to work in my project even without the line).

longtran2904 commented 4 years ago

The problem is when I built the project the material would keep reset it back. This is the problem in the first place. If I added that line, the problem would be gone but the Material field in Tilemap Renderer would become "None (Material)" and if I drew something in the editor mode it would have pink color (because "None (Material)"). So anytime I want to draw something I had to manually assign the material to the Material field in the Tilemap Renderer.

longtran2904 commented 4 years ago

Okay, I know how to fix it. Just put an if check to whether it is null or not. Something like: if (material) { tilemapRenderer.sharedMaterial = material; } will do the trick. But I still don't know why when the game wasn't played the material would be null?

OndrejNepozitek commented 4 years ago

Ok, so here is how it works in my project:

I managed to fix that with the following code:

public class CustomTilemapLayersHandler: TilemapLayersHandlerBase
{
    public override void InitializeTilemaps(GameObject gameObject)
    {
        gameObject.AddComponent<Grid>();

        // Create the material here
        var material = new Material(Shader.Find("Universal Render Pipeline/2D/Sprite-Lit-Default"));

        var floorTilemapObject = CreateTilemapGameObject("Floor", gameObject,0, material);
        var wallsTilemapObject = CreateTilemapGameObject("Walls", gameObject, 1, material);
        var collideableTilemapObject = CreateTilemapGameObject("Collideable", gameObject, 2, material);
    }

    protected GameObject CreateTilemapGameObject(string name, GameObject parentObject, int sortingOrder, Material material)
    {
        var tilemapObject = new GameObject(name);
        tilemapObject.transform.SetParent(parentObject.transform);
        var tilemap = tilemapObject.AddComponent<Tilemap>();
        var tilemapRenderer = tilemapObject.AddComponent<TilemapRenderer>();
        tilemapRenderer.sortingOrder = sortingOrder;

        // Set the material here
        tilemapRenderer.material = material;

        return tilemapObject;
    }

Note that I create a new material with the correct shader and then assign it to all the tilemap renderers. This seems to work even when I build the game.

longtran2904 commented 4 years ago

It will work with your way when you built the project (It worked for me). But if you have a public material variable and assign it through the inspector of the scriptable object, in the editor that variable will become null (The build version would be okay but when you are editing the tile in editor mode it would be null). How you assign your material variable?

longtran2904 commented 4 years ago

This can easily be fixed by having an if statement to check whether it is null or not. But I wonder why it is null even though I assign its value through the inspector?

OndrejNepozitek commented 4 years ago

Hey! So custom tilemap layers handlers are used for two things - creating the structure of tilemap in room templates and creating the structure in generated levels. I would guess that you have a problem with room templates. The problem is that even though you assign the material to the instance of the handler, you probably do not use that instance when creating a room template.

You probably have something like this:

public class CustomRoomTemplateInitializer : BaseRoomTemplateInitializer
{
    protected override void InitializeTilemaps(GameObject tilemapsRoot)
    {
        // Create an instance of your tilemap layers handler
        var tilemapLayersHandler = ScriptableObject.CreateInstance<CustomTilemapsLayerHandler>();

        // Initialize tilemaps
        tilemapLayersHandler.InitializeTilemaps(tilemapsRoot);
    }
}

The problem is that you create a new instance of the CustomTilemapsLayerHandler instead of using the instance with your material. So in order to fix it, you would have to explicitly load the saved instance of the scriptable object. Or maybe a simple solution is to create an empty room template, fix the material manually and then duplicate the room template.

If you use this line var tilemapLayersHandler = ScriptableObject.CreateInstance<CustomTilemapsLayerHandler>(); then it will create a new instance of the "CustomTilemapsLayerHandler". That means that it is not the instance where you have your material set, therefore the material will have the default value - null. Unfortunately, the create room template menu item does not have access to your saved instance of the handler. You would have to somehow manually load it and then you would see your material set.

longtran2904 commented 4 years ago

Okay, so I understand it now. Thanks!