Forum rules - please read before posting.

2D Tint/lightmap?

edited October 2014 in Engine development
Maybe there's already a way to do this, somehow, but I am missing an easy way to control the colour and brightness of the characters in 2D. 3D can do this easily with lights and lightprobes, and I was thinking that maybe lightprobes would work here, too, but they are based on lights and wouldn't give much fine control?

Perhaps something like a tint map from which you sample the colour at the character's pivot and it would be applied to any object that has the tint map script attached to it, much like objects with the sorting map scripts?

image
«1

Comments

  • I would imagine the solution to this would be more to do with Unity and custom scripts, rather than AC specifically.

    I'll open this one to the floor - there're many 2D games in development, anyone tried anything like this?
  • Not me, but I'd love to (and I might do if I get the chance)

    I did spot a relevant post recently to do with the lighting technique used in Broken Age so maybe that'll be useful to you Eastman

  • Thanks for the input, guys. I'd love to do something described in that article, @oxyscythe, but I'm not sure where I'd begin. I did the next best thing which will have to work in the meantime: I changed the material of the character sprite to the Sprites/Diffuse material, allowing it to receive light and put out a few point lights, and while the result isn't perfect it's good enough for the time being. Video here.
  • Woah, that looks awesome! Good job! :)
  • Thanks! I think it works acceptably for what it is but I still like the solutions in the Gamasutra article you posted much better so I'm going to look into seeing what solutions already exist for Unity :)
  • i've managed to do this by raycasting the material/texture of the ground collider and then use the color value to drive the color of the shader....

    i've saved a description and the code how to do it (found on the web), but i didn't save where i found it, and who actually wrote it.. so sorry for not providing proper credit...

    i hope this helps!
      --stephan

    ----
    Here's one way ...

    associate a Collider with your textured plane
    use Collider.Raycast to cast vertical rays (direction = Vector3.down) down into the plane from the world position of the character
    use the textureCoord of the raycast hit and feed it into Texture2D.GetPixel to read the RGB value from the texture
    EDIT: The following script seems to do the trick.

    To use it ...

    Save the script as DataField.cs in your project.
    Select Game Object > Create Other > Plane from the Unity menu
    Attach a material to your plane, and a texture to the material.
    Check the texture importer settings on the texture and make sure Read/Write enable is set.
    Attach the DataField component to your Plane object.
    Find an object in your scene, or create an empty one, that will move across the plane. Attach it to the ExampleCharacterObject property of the DataField component.
    Run your game, and watch the ColorUnderCharacter property of the DataField as the object moves across it. It should match the texture color under the character.
    Note that another component in your scene can now have a property of type DataField, and then call the GetColorData method on it directly -- the ExampleCharacterObject lookup in the Update method is just for demonstration purposes.

    ------

    using UnityEngine;
     
    public class DataField : MonoBehaviour
    {
        // Attach something to this in the inspector and move it around
        // to demonstrate the data field lookup in action.
        public Transform ExampleCharacterObject = null;
     
        // Don't modify this, it will be updated as the character object moves around.
        public Color ColorUnderCharacter = Color.black;
     
        private Texture2D mTexture = null;
     
        // Check for required components on startup, and find the texture to be
        // used for the data field.
        void Start ()
        {
            if (collider == null)
            {
                Debug.LogError("collider required for DataField to perform texture lookups", gameObject);
            }
            if ((renderer == null) || (renderer.material == null) || (renderer.material.mainTexture == null))
            {
                Debug.LogError("renderer with a material and a main texture required for DataField to perform texture lookups", gameObject);
            }
     
            mTexture = renderer.material.mainTexture as Texture2D;
            if (mTexture == null)
            {
                Debug.LogError("Texture2D required for DataField to perform texture lookups", gameObject);
            }
     
            // Note that you must turn on Read/Write enable in the import settings for the
            // texture or else GetPixel will fail.
        }
     
        // Update demonstrates the use of GetColorData, but you can call
        // it from elsewhere too (this behaviour doesn't need an Update
        // method, this is just for illustration).
        void Update ()
        {
            if (ExampleCharacterObject != null)
            {
                ColorUnderCharacter = GetColorData(ExampleCharacterObject.position);
            }
        }
     
        // Find the color data under a given position.
        public Color GetColorData(Vector3 position)
        {
            // Default to black if we find no data.
            Color colorData = Color.black;
     
            // Create a down-pointing ray at the position.
            Ray ray = new Ray(position, Vector3.down);
            RaycastHit hit;
     
            // Check for a hit, using some arbitrarily long ray length.
            if (collider.Raycast(ray, out hit, 10000.0f))
            {
                colorData = mTexture.GetPixelBilinear(hit.textureCoord.x, hit.textureCoord.y);
            }
     
            return colorData;
        }
    }

  • edited October 2014
    For dinamic 2D lighting my characters I was thinking about something like Sprite Lamp (already a backer), but as it creates normal maps, for mobile it could be costly (but maybe by the time I finish anything it will be no more). And you also have to draw/render every pose with diferent lighting direction, but they look great.

    For full sprite tint solutions you can go with what @deroesi proposed, raycasting a texture. For this I would use a lower res texture the size (in unity units) of the background art... and test character position against it.

    You could even avoid the raycasting just translading character world x/y to light texture x/y.

    And If you only want to use it for a few zones, you can just use an AC trigger zone and call a custom action for tinting the character' sprite.

    Ah... and @Eastman, by the way... that looks AWESOME :)
  • Switching the material to Diffuse is a really good tip, thanks, I'm going to use that! And your game looks rad, very Blade Runner!
  • @deroesi, thanks! After some fiddling I got it working, but since I don't really know how to program I only have the main character sample the color value from the texture and I use it to tint the mesh renderer instead of the material itself. You don't happen to have a good solution of how to apply it to multiple characters/objects? Oh, and I found the original post here.

    @MaaS, I had a look at solutions similar to Sprite Lamp for Unity, but there seems to be issues when the sprites scale. I don't know if that's still the case since the posts I read were fairly old. I'm not overly concerned about the extra work with normal maps since I render 3D characters to sprites, but I'd have to figure out how to bake the existing normal maps that are on the 3D model into the overall tangent normal map I'd use for the effect. Don't know if that makes sense, I confused myself a bit writing it..

    As for the solution @deoresi posted, it works nicely, aside from the issue mentioned above. I'm handicapped by not having any talent for programming at all :)

    You could even avoid the raycasting just translading character world x/y to light texture x/y.

    Ah yes, good idea!

    And If you only want to use it for a few zones, you can just use an AC trigger zone and call a custom action for tinting the character' sprite.

    That's a solution I used in old Adventure Game Studio, but I had trouble getting smooth transitions. But for stepping into a shadow with a hard edge it would work fine.

    Ah... and @Eastman, by the way... that looks AWESOME  :)
    --
    And your game looks rad, very Blade Runner!

    Thanks :) Not part of a game though, just a test scene that I cram as much as I can into.
  • Ahh, another AGS veteran, nice. Hey I just came across this tool, maybe that would be ideal for you:


    It's called SpriteLamp and it generates a normal map based on (hand-drawn) lighting maps you feed it for your characters. That gives you full dynamic lighting on any 2D sprite.

    I found the pixel zombie they show the most impressive one, looks like there is a pretty good algorithm behind it.
  • I used AGS on and off for over 10 years, but I've never actually completed a project with it. Here's to another 10+ years of not completing anything!

    I had a look at Sprite Lamp (and similar solutions) but since I render all my character out to 2D sprites from 3D models I don't think it's something I have use for, since I can just assign a tangent normal shader to the model and render them all out at once :)

    Also, from what I've read there's problems when scaling sprites in Unity that causes issues when using a sprite material with support for normal maps. I haven't read anywhere about this being addressed, so I think it may still be the case...
  • I just wrote an article about how we're doing lighting in our 2D game:

    Maybe it's helpful for somebody.
  • edited May 2015
    Thanks for sharing this script @deroesi and everyone for all the ideas. I firstly tried to use normal maps or just the "sprite-diffuse" material on sprites to have dynamic lights as it sounded awesome and as I didn't know these technics. But in the end I'm not a big fan of these solutions as I find it very diffucult to have consistent lights this way even if I find it cool to have more "realistic" lights with the ability to move the sources anytime.

    So in the end I came back to the "basic" lightmap solution by using the script shared by @deroesi. But I modified it to make it work the other way around. I mean I put the script on any Player/NPC sprite and I add the lightmap Plane as a parameter. This way I can easily apply the lightmap on all moving NPCs or any sprite, and you can even use a different lightmap for two characters if you want.


    Here is my modified script for those interested (I put my lightmaps at Z=1 to but it behind the scene which is at Z=0, but could be higher depending of the max indicated at line 49) :


    using UnityEngine; public class LightMap2D : MonoBehaviour { public MeshFilter LightMap = null; private Texture2D mTexture = null; // Check for required components on startup, and find the texture to be // used for the data field. void Start () { if (LightMap.GetComponent< Collider >() == null) { Debug.LogError("collider required for LightMap2D to perform texture lookups", gameObject); } if ((LightMap.GetComponent< Renderer >() == null) || (LightMap.GetComponent< Renderer >().material == null) || (LightMap.GetComponent< Renderer >().material.mainTexture == null)) { Debug.LogError("renderer with a material and a main texture required for LightMap2D to perform texture lookups", gameObject); } mTexture = LightMap.GetComponent< Renderer >().material.mainTexture as Texture2D; if (mTexture == null) { Debug.LogError("Texture2D required for LightMap2D to perform texture lookups", gameObject); } // Note that you must turn on Read/Write enable in the import settings for the // texture or else GetPixel will fail. } void Update () { GetComponent< SpriteRenderer >().color = GetColorData(GetComponent< Transform >().position); } // Find the color data under a given position. public Color GetColorData(Vector3 position) { // Default to black if we find no data. Color colorData = Color.black; // Create a down-pointing ray at the position. Ray ray = new Ray(new Vector3(position.x, position.y, 0), Vector3.forward); RaycastHit hit; // Check for a hit, using some arbitrarily long ray length. if (LightMap.GetComponent< Collider >().Raycast(ray, out hit, 10.0f)) { colorData = mTexture.GetPixelBilinear(hit.textureCoord.x, hit.textureCoord.y); } return colorData; } }


    I'm new to unity and game dev in general so if what I did doesn't make sense I'm sorry, but it seems to work well for me :)

    And it's still possible to combine this with the "Sprites-Diffuse" solution proposed by @Eastman
  • That's an elegant solution @Gog0 :)

    I haven't done any development in a few months, and now I recently bought SpriteIlluminator because I wanted my sprites to blend in better with the environment, using dynamic lights, but I just found it too difficult to control the lights as precisely as I needed them to. It just doesn't look right most of the time.

    With your script it's very easy to just drag the plane with the light map into the slot, but with a main character that's a prefab how do you go about making sure the right plane is assigned in the right scene? Do you have a working solution for this? I know, I'm being lazy :)
  • edited May 2015
    Good thinking.

    I personaly include my player prefab in every scene because when you want to change something on it in one scene it might cause inventory troubles between scenes if you include the player prefab in one scene but not the others as I described in this thread.

    So as my player prefab is everywhere I can easily associate the current scene lightmap. To me it doesn't sound like a lot of work, but it depends how lazy you are :D
  • @ChrisIceBox, I'm paging you about this again (sorry!). I think the above solution would make a great addition to AC's 2D features, officially integrated, and I'm sure all 2D games that don't have a flat-shaded style to them would end up using it.

    Something along the lines of:
    • Under Scene> Scene settings you would specify the LightMap2D you are using.
    • Player/NPC components would have an Affected by LightMap2D? checkbox.
    • Able to switch the LightMap2D texture being used on-the-fly using actions (for example when you flip a lightswitch in the scene, making it darker/lighter/more colourful).
    Again, sorry for asking for more stuff, you spoil us enough as it is!
  • Gog0

    thanks for sharing your script aswell.. I took the path of putting it on the ground and not on the character because i wanted the character prefab to work in every scene without any hassle, but i'm a unity noob, so maybe this is flawed thinking...

    how did you solve that? do you pass a parameter to the character when every scene starts?

  • @deroesi

    Just try to add the script I shared to your character, you'll see how it works. I think it's simple, you just have to associate the lightmap to your player/NPCs in each scene. It's just few seconds of work for each scene, it is simple enough for me and having the ability to apply a lightmap to multiple characters/objects is essential in my case so I don't matter doing this small amount of extra work.
  • I so also want to use this.
  • Hi peeps!

    So @Gog0 Could you clarify what you mean by linking the 'lightmap plane' to your script? It seems to me you mean create a 3D Plane object and link that to the scrip, but trying that hasn't worked for me. Where can I find this mysterious lightmap plane?
Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.