Hello Guest

Author Topic: Tinting tiles at runtime  (Read 5122 times)

danthat

  • Newbie
  • *
  • Posts: 4
    • View Profile
Tinting tiles at runtime
« on: September 16, 2016, 05:30:40 pm »
Hello! I'm looking for a way to tint tilemap (x, y) to Color c at runtime, so I can vary my tilesets a little without having huge atlases.

I looked at tileMap.ColorChannel.SetColor(x, y, Color.red) but nothing seems to change, even after a rebuild. I've run some tests and ColorChannel.GetColor(x, y) is returning 1, 0, 0, 1 so technically it's worked but my white tiles remain white. Any suggestions? Yes, tileMap is being rebuilt.

EDIT: TileMap->Settings->Layers has a 'Color' button I'd deselected, which explains why tinting didn't work.

However, I'm now aware that tinting the tile map tints all layers, where I'd only want one. I suppose I could keep rolling checks and changing the colour when the tile's changed, but that's potentially fiddly. Are there any other suggestions for ways to set individual tile tints? I'm happy to extend the core tk2d tilemap code if needs be...
« Last Edit: September 16, 2016, 06:49:47 pm by danthat »

danthat

  • Newbie
  • *
  • Posts: 4
    • View Profile
Re: Tinting tiles at runtime
« Reply #1 on: September 19, 2016, 12:33:38 pm »
Okay, I've worked out a system for this. I'm creating TileMaps at runtime for procedurally-generated levels. The code below uses 2Dtoolkit's existing SpriteBatching code to create custom SpriteBatches, allowing you to Instantiate a group of sprites, pass them to the SpriteBatchManager, and it'll turn them into a single draw call / whatever Unity5 is calling DrawCalls now.

Importantly: that means you can tint sprites when they're created, which'll carry over to the SpriteBatch, getting a little more variety out of your existing sprite sheets.

Posting it here in case others find it of use. It can be easily extended out to take advantage of tk2d's Build() functions so you can add/ remove at runtime, but for my purposes right now this'll do:

1. Create a 2dtoolkit sprite GameObject, attach a BatchableSprite.cs component, drag over the reference to the Sprite, and save it out as a Prefab

Code: [Select]
using UnityEngine;

public class BatchableSprite : MonoBehaviour {

    // this just stores a Sprite, but I've created a whole class for it so
    // it can be easily extended out to store other data:
    public tk2dBaseSprite theSprite;
}

2. Create a GameObject, and attach tk2D's StaticSpriteBatcher component, and this SpriteBatch script. Drag over the reference to to the StaticSpriteBatcher (so we don't have to use GetComponent), and convert it to a Prefab

Code: [Select]
using UnityEngine;
using System.Collections.Generic;

public class SpriteBatch : MonoBehaviour
{

    public tk2dStaticSpriteBatcher ourSpriteBatcher;

    public void Setup(List<BatchableSprite> bList)
    {
        // we need a list of BatchedSprites
        List<tk2dBatchedSprite> batchedSprites = new List<tk2dBatchedSprite>();

        // go through all the sprites in our list, and nab the pertinent information:
        foreach (BatchableSprite b in bList)
        {
            tk2dBatchedSprite bs = new tk2dBatchedSprite();
            bs.spriteCollection = b.theSprite.Collection;
            bs.spriteId = b.theSprite.spriteId;
            bs.relativeMatrix.SetTRS(b.transform.position, Quaternion.Euler(0, 0, 0), Vector3.one);
            bs.color = b.theSprite.color;

            batchedSprites.Add(bs);
        }

        // BUILD IT:
        BuildSpriteBatch(batchedSprites);

        // we can now safely destroy our source GameObjects:
        foreach(BatchableSprite b in bList)
            Destroy(b.gameObject);
    }

    private void BuildSpriteBatch(List<tk2dBatchedSprite> batchedSprites)
    {
        // get the batched sprites into tk2d's SpriteBatcher. First, get the array to the right size:
        ourSpriteBatcher.batchedSprites = new tk2dBatchedSprite[batchedSprites.Count];

        // go through them all, assigning to the appropriate slot
        for (int i = 0; i < batchedSprites.Count; ++i)
            ourSpriteBatcher.batchedSprites[i] = batchedSprites[i];// bs;
       
        // set flags:
        ourSpriteBatcher.SetFlag(tk2dStaticSpriteBatcher.Flags.GenerateCollider, false);
        ourSpriteBatcher.SetFlag(tk2dStaticSpriteBatcher.Flags.FlattenDepth, true);
        ourSpriteBatcher.SetFlag(tk2dStaticSpriteBatcher.Flags.SortToCamera, true);

        // Do the actual Build:
        ourSpriteBatcher.Build();
    }
}

3. In your scene, create a GameObject and call it something like SpriteBatchManager, and attach this to it:

Code: [Select]
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SpriteBatchManager : Manager {

    public SpriteBatch spriteBatchPrefab;

    public void BatchSprites(List<BatchableSprite> theBatchableSprites)
    {
        // this function takes a group of 2dtoolkit sprites, and batches them to a single drawcall.
        // first, we need to create a Sprite Batch to contain our supplied List of sprites
        SpriteBatch theSpriteBatch = Instantiate(spriteBatchPrefab, transform.position, Quaternion.identity) as SpriteBatch;
        theSpriteBatch.transform.SetParent(transform);

        // tell the sprite batcher to create a single Mesh Renderer based on the data from the List of sprites:
        theSpriteBatch.Setup(theBatchableSprites);
    }
}

4. Your SpriteBatchManager needs to understand the SpriteBatch prefab you made, so drag it over in the Inspector.

5. When you want to create a Batch of sprites, you can now do this from any script (as long as it has a link to the SpriteBatchManager:

Code: [Select]
// we need to pass the BatchableSprites to the SpriteBatcher, so let's keep a List
        List<BatchableSprite> theBatchableSprites = new List<BatchableSprite>();

        // TEST, 1000 sprites all laid out nicely in a single batch:
        for (int i = 0; i < 1000; i++)
        {
            BatchableSprite b = Instantiate(batchableSpritePrefab, new Vector3(i, 0, 0), Quaternion.identity) as BatchableSprite;
            b.theSprite.color = Color.red; // SET THE COLOUR!
            theBatchableSprites.Add(b);
        }

        spriteBatchMgr.BatchSprites(theBatchableSprites);


Um, it's not brilliantly tested but should work fine for small batches? Hopefully unikron can chime in if this is an astonishingly bad idea to do, but anecdotally it works, and allows you to tint sprites.


EDIT as an addendum to this: turns out you don't really need to Instantiate any game objects, since tk2dBatchedSprite doesn't derive from monobehaviour, so you can just call tk2dBatchedSprite = new tk2dBatchedSprite, add it to a  List<tk2dBatchedSprite> theBatchableSprites and edit SpriteBatchManager.BatchSprites accordingly to take that new List, and save a whole lot of Garbage Collection :)
« Last Edit: September 19, 2016, 02:35:45 pm by danthat »

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Tinting tiles at runtime
« Reply #2 on: September 21, 2016, 02:20:14 pm »
> add it to a  List<tk2dBatchedSprite> theBatchableSprites and edit SpriteBatchManager.BatchSprites accordingly to take that new List, and save a whole lot of Garbage Collection

Yup! Thats why it's structured the way it is.
Sounds like this'll work really well.