Procedural Door/Trigger generation

I’m working on a script which lets you create your level in paint/photoshop. Every pixel in the .png document is being used to spawn a prefab in-game at runtime.
Now I got to a point where I want to spawn a door with a trigger. It’s not a problem when the trigger is on the door, but when it’s further away, I’m getting confused on how to make it possible.
One solution I have is to create a prefab with a trigger on a specific door prefab, not realistic for multiple doors though.
I know there is a solution with instances of GameObjects, I wouldn’t know how to get that to work though. I don’t ask for any script (even though that would be nice), but mostly for implementation ideas.

How do I tell a door prefab that a specific trigger has to be used while being generated at runtime?

Are you talking about Trigger as in collider triggers?

How do you know a trigger and door are related? Is this aspect solved?

If so, you just need a script on the trigger with a field for the door it references. And when you create it, and create the door, you give it the reference to said door.

that’s the problem, they don’t know the are related

So basically at editor time (in paint) you need to define a way for the creator to relate the trigger and door.

2 Likes

One way would be to give your triggers a script that exposes the physics events as UnityEvents. Then when you spawn the door and trigger, make the door listen for the trigger class’s event firing and handle it.

This way you can have the trigger be a completely separate object, and still catch the physics events from any other objec

Example:

public class ExampleTrigger : MonoBehaviour
{
    public class UnityEvent_Collider : UnityEvent<Collider> { }

    public UnityEvent_Collider OnTriggerEntered;

    private void Awake()
    {
        OnTriggerEntered = new UnityEvent_Collider();
    }

    public void OnTriggerEnter(Collider collider)
    {
        OnTriggerEntered.Invoke(collider);
    }
}

public class ExampleDoor : MonoBehaviour
{
    public void TriggerEnterHandler(Collider collider)
    {
        // do stuff
    }
}

public class Spawner : MonoBehaviour
{
    ExampleTrigger triggerPrefab;
    ExampleDoor doorPrefab;

    public void SpawnDoorTriggerPair()
    {
        ExampleTrigger trigger = Instantiate(triggerPrefab);
        ExampleDoor door = Instantiate(doorPrefab);
        trigger.OnTriggerEntered.AddListener(door.TriggerEnterHandler);
    }
}

This would of course be the approach after you know which trigger goes with which door, and have references to both objects.

1 Like

Imagine the problem when you have 20 doors all using the same prefab…gets kinda complicated unless you have 20 different prefabs

yes. I’m searching a way to tell the trigger which door it has to open.
Besides having multiple prefabs, I didn’t find any solution.

I’ve never done this sort of thing before, but I may have an idea. How does your current system know that the pixel(s) you draw represents a door, vs. a wall, trigger, etc?

Edit: Below, Jeffrey already hit on exactly what I was thinking… either different color representing the objects with alpha perhaps linking relationship, or multiple files. This would work, but if there’s an end user using this editor, I could see it being a bit cumbersome to use though.

1 Like

It shouldn’t be any more complicated. Spawn a door, spawn the cooresponding trigger and link them with code. Do that a hundred times and it’s still the same process. Prefabs exist to be duplicated and reused, and can be scripted in a way that works for dynamic generation.

However your problem is that a single color-coded image can’t hold enough data to define relationships without a much more convoluted solution potentially using multiple color channels, which wouldn’t be nice to edit.

If you’ve ever used Tiled, you know that it’s not just an image per tile. It’s an image, plus another file with data about that tile. Collision mesh, values, naming, layers, etc.

So you just need more data. If you really want to go the route of mapping via pixels in a png, at the very least you’ll need one more image to resolve it. Say for instance you created a second map which is all black except you mark each door and trigger pair as a unique color. That would be your door-to-trigger map. You can spawn all your stuff, and look up on that map which door goes with which trigger based on the matching colors.

Creating those two images could be a pain depending on which image editing software you use, and the resolution of the map, which is why most map editors are custom programmed to make it easier to mark tiles and create data maps behind the scenes.

1 Like

Yeah, it sounds to me like you’re using the pixel values to represent information.

So maybe break this up.

We have 32-bits of colour data per pixel. I’m assuming you’re using colour information to define what each thing. Lets say you reserve R and G for prefab information… giving you a potential of 65k prefabs (overkill? maybe, you could reduce this to like 12 bits if you wanted). Then the other channels B and A are auxililary data, whose rules are related to the prefab type the R and G say it is.

So lets say 0x000A means ‘wooden door’, and doors auxiliary data is defined as being and identifier (a value from 1 to 65536). Then 0x000A0001 (rgba) might mean “wooden door with id 1”.

Then lets say 0x000B means “door trigger”, and trigger’s auxiliary data is defined as being the identifier of the door it opens. So you would say 0x000B0001 means “door trigger that opens the door with id 1”.

Then when reading your files you’ll read those first 2 channels to get the prefab, then based on the prefab type’s rule you do what is necessary.

So you’d end up looping over your bitmap and being like:

found door, create door, assign id to it
found trigger, create trigger, assign target id to it
found rest… do this with rest

then you go back and you loop over the created prefabs and you’re like

found door, it has id X, nothing else needs to be done
found trigger, it targets door of id X… search map for that door, assign that trigger that door
so on, so forth

Of course, I don’t see how this would be at all enjoyable to create in photoshop or paint… it’d be rather tedious to be honest.

But I honestly don’t know how you’re even attempting to represent your data in these things.

1 Like

This part here strikes me as the best way of doing it. I know it’s extra work, but build a very basic paint tool yourself in c# (outside unity). Have a palette of tools representing the objects in the game and these are what are placed in a zoomed in representation of your map. Then have a small set of colors (let’s say 256) to represent the extra data needed that can be applied to these “pixels” (for lack of a better term), such as matching a door and its trigger(s). Actually, other special data would probably be handled in a more visual, intuitive way depending on what you need. Behind the scenes, the tool automatically builds the two images for you. That’s the part that worries about what color = what. Heck you might even be able to do it with just ASCII characters even.

This all probably works way more smoothly in my head than in practice, however :slight_smile:

At least this way is a way that someone would be able to use without a lot of aggravation, I think.

Here is the whole script I use to transform a specific color to a prefab
The color/prefab is set in the Inspector

As you can see, I’m able to use several maps if needed, but having a different map for every door/trigger pair is a bit overkill I think. Might be the only solution though.
I had no problems with moving platforms and so on since it’s all scripted to the prefab, but doors with triggers is like impossible without either multiple maps or multiple prefabs.
I could still put the trigger on the door, but I want to be able to put a trigger on the other side of the level if i want.

The reason for all this is to make it possible for the player to create its own maps by using paint/photoshop and a simple legend with color/prefab information.

using UnityEngine;

[System.Serializable]
public class ColorToPrefab {
    public Color32 color;
    public GameObject prefab;
}

public class LevelLoader : MonoBehaviour {

    public string levelFileName;

    public ColorToPrefab[] colorToPrefab;

    private void Awake()
    {
        levelFileName = PlayerPrefs.GetString("Level");
    }

    // Use this for initialization
    void Start () {
        LoadMap();
    }

    void EmptyMap() {
        while(transform.childCount > 0) {
            Transform c = transform.GetChild(0);
            c.SetParent(null);
            Destroy(c.gameObject);
        }
    }

    //Use this to load the Background
    void LoadMap() {
        EmptyMap();

        // Read the image data from the file in StreamingAssets
        string filePath = Application.dataPath + "/StreamingAssets/" + levelFileName;
        byte[] bytes = System.IO.File.ReadAllBytes(filePath);
        Texture2D levelMap = new Texture2D(2, 2);
        levelMap.LoadImage(bytes);


        // Get the raw pixels from the level imagemap
        Color32[] allPixels = levelMap.GetPixels32();
        int width = levelMap.width;
        int height = levelMap.height;

        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {

                SpawnTileAt( allPixels[(y * width) + x], x, y );

            }
        }

        LoadObjectMap();

    }

    //Use this as a second map to load Objects
    void LoadObjectMap()
    {
        string[] objectFileNames = levelFileName.Split('.');
        string objectFileName = objectFileNames[0] + "a.png";

        // Read the image data from the file in StreamingAssets
        string filePath = Application.dataPath + "/StreamingAssets/" + objectFileName;
        byte[] bytes = System.IO.File.ReadAllBytes(filePath);
        Texture2D levelMap = new Texture2D(2, 2);
        levelMap.LoadImage(bytes);


        // Get the raw pixels from the level imagemap
        Color32[] allPixels = levelMap.GetPixels32();
        int width = levelMap.width;
        int height = levelMap.height;

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {

                SpawnTileAt(allPixels[(y * width) + x], x, y);

            }
        }
    }

    void SpawnTileAt( Color32 c, int x, int y ) {

        // If this is a transparent pixel, return.
        if(c.a <= 0) {
            return;
        }

        // Find the right color in our map
        foreach(ColorToPrefab ctp in colorToPrefab) {
           
            if( c.Equals(ctp.color) ) {
                // Spawn the prefab at the right location
                GameObject go = (GameObject)Instantiate(ctp.prefab, new Vector3(x, y, 0), Quaternion.identity );
                go.transform.SetParent(this.transform);
                return;
            }
        }

        // no matching color in our array. Check the colors in Inspector or Image File.

        Debug.LogError("No color to prefab found for: " + c.ToString() );

    }
   
}

It’s not the only solution though.

A few were suggested here already:

  1. You can use one of the color channels (or alpha) to indicate door/trigger pairings

  2. You could use two different maps (and only two should be needed), with your 2nd map being the map that has color data to indicate pairings for doors/triggers on the 1st map…

  3. My own oddball thought, if you don’t have a whole lot of prefabs to work with, you could just have the user create a file with ASCII text. There’s probably other ways to do it with this, but you could have an easy set up of 36 door/trigger combos just by using lower case letters for either a door or trigger, and uppercase for the other, plus numbers/special chars above them (!@#$%, etc…). Wouldn’t look anywhere near as pretty, maybe a bit clunky, and possibly doesn’t give you enough flexibility (read: enough door/trigger combos), but another idea.

  4. Build your own graphical tool in Unity, or just C# outside of Unity. Have a tool palette which represents all your prefabs… you drag them into a grid. When it comes to doors/triggers, you pick your color from a palette of 256 colors (to be more simple). Upon save, the tool will scan through and build the first map, indicating prefabs and save 1 map. Then it passes through again, looking for the additional information (door/trigger color matching) to build a second map.

I personally think #4 is a great idea (credit to @LiterallyJeff for mentioning the idea of building a tool) and a rough version of a tool like that maybe you could do in a week or so, but it would go a long way in giving the best user experience.

Oh and with most of these solutions your users can even build their own tools if they want if there’s an easy understanding of what is needed!