0
\$\begingroup\$

I have been trying to implement a province system in Unity. I am trying to read the provinces from an image file, store their coordinates in a province, and use those coordinates to generate a texture and a 2D sprite for the province to be displayed on the screen at its appropriate world coordinates. Games like Risk, and many strategy games that rely on world maps to function use this approach. So far, I have the following code to parse the image from the province.png file I have created.

public class ProvinceReader()
{
    public static void Read()
    {
        Texture2D provinces = MapImageCache.province_tex;
        for(int x = 0; x < provinces.width; x++)
        {
            for(int y = 0; y < provinces.height; y++)
            {
                Color32 color = provinces.GetPixel(x,y);
                ProvinceData province = null;
                if(MapInstance.ColorCodedData.TryGetValue(color, out province))
                {
                    province.AddPoint(x, y);
                    //TODO: Check if the neighboring pixels are of different colors. If so, 
                    //add the pixel to the 'province border' pixels. The, get the province at 
                    //the checked pixel position and store the x,y coordinate in a dictionary 
                    //IDictionary<ProvinceData, List<Vector3>>.
                }
                else
                {
                    //MapInstance.Instance.ColorCodedData.Add(color, new ProvinceData());
                }
            }
        }
        foreach (ProvinceData prov in MapInstance.Instance.ColorCodedData.Values)
        {
            GameObject newProv = new GameObject();
            newProv.AddComponent<ProvinceScript>().SetData(prov); 
            newProv.AddComponent<SpriteRenderer>().sprite = prov.GenerateSprite();
        }
    }
}

public class ProvinceData
{
    private List<Vector2> points = new List<Vector2>();
    private List<int> xPoints = new List<int>();
    private List<int> yPoints = new List<int>();
    private Sprite image;

    public Sprite GenerateSprite()
    {
        int minX = xPoints[0], minY = yPoints[0];
        int maxX = xPoints[0], maxY = yPoints[0];
        for(int i = 0; i < xPoints.Count; i++)
        {
            int xval = xPoints[i], yval = yPoints[i];
            if(xval < minX) minX = xval;
            else if(xval > maxX) maxX = xval;
            
            if(yval < minY) minY = yval;
            else if(yval > maxY) maxY = yval;
        }
        int width = (maxX - minX) + 1, height = (maxY - minY) + 1;
        Texture2D m_tex = new Texture2D(width, height);
        
        for(int i = 0; i < xPoints.Count; i++)
        {
            int xval = xPoints[i] - minX, yval = yPoints[i] - minY;
            m_tex.SetPixel(xval, yval, new Color32(0, 123, 20, 255)); //I want to display the province on screen in green
        }
        m_tex.Apply();
        this.image = Sprite.Create(m_tex, new Rect(0, 0, width, height), Vector2.zero, 1f);
        return image;
    }
    public void AddPoint(int x, int y)
    {
        xPoints.Add(x);
        yPoints.Add(y);
        points.Add(new Vector2((float)x, (float)y)); 
        //Also save whether it is a border point.
    }
}

I have a black (0,0,0) province in the center of the texture in my png file:

enter image description here

And I am trying to read it. I loaded it into my MapInstance.Instance.ColorCodedData and ensured the data is valid. I loaded the png into a Texture2D and ensured the image was set up properly.

I am getting the following on my screen when I start the game:

enter image description here

I did some experimenting, and used Debug.Log to record a list of points. The largest point in the province that matches the province color is (x,y) => (643, 290). The smallest is (x,y) => (627, 281). I recorded the final value of "width" and "height", in the ProvinceReader.Read() method using Debug.Log(); The value was 1 when I explicitly subtracted the minimum of each value from the maximum of each value. So, I should have gotten (width, height) => (16, 9), but when I record these values I get 1. Which is weird.

I then changed the width and height values arbitrarily to 200,200 to see what happens, and I just get a 200x200 version of the above sprite.

What I am looking for:

Why is the sprite doing this? How can I solve it? If you need any more information, please let me know and I will include it.

\$\endgroup\$
8
  • \$\begingroup\$ You have put too much effort into writing this question but the question seems unrelated. The approach is right. GetPixel should work the way you wrote. The experiment though seems wrong and gave you wrong results \$\endgroup\$
    – Bizhan
    Feb 2, 2017 at 18:49
  • \$\begingroup\$ @Bijan That is pretty much the code of the experiment minus the MapManager and ProvinceData classes, the former of which simply calls MapReader.Read() while the latter does nothing but serve as a way to wrap the ProvinceData and SpriteRenderer in the same GameObject... GetPixel() seems to be working just fine, but SetPixel() is giving me problems. It is coloring the entire rectangle of the sprite in when it should just color the relevant pixels of the underlying texture. Do you need the sample texture I used? \$\endgroup\$
    – Jax
    Feb 2, 2017 at 19:41
  • \$\begingroup\$ You should update your question because it's unclear. What exactly do you need ? To know which region was clicked by the user ??? \$\endgroup\$
    – aybe
    Feb 2, 2017 at 19:55
  • \$\begingroup\$ That's what i was trying to say. instead of this long question you should ask why SetPixel behaves that way. \$\endgroup\$
    – Bizhan
    Feb 2, 2017 at 19:55
  • \$\begingroup\$ Hold on I'm looking at it ;) \$\endgroup\$
    – aybe
    Feb 2, 2017 at 19:57

2 Answers 2

0
\$\begingroup\$

To find what color has been clicked by user on an UI image:

Steps I've done on a new project:

  • add PNG to your project
    • tick Read/Write Enabled in its Import Settings
    • Alpha Source -> Input Texture Alpha
    • tick Alpha Is Transparency
    • apply changes
  • add a UI/RawImage component (this adds a Canvas and a RawImage inside it)
    • set Texture
    • click Set Native Size
  • in Canvas set Pixel Perfect
  • in RawImage add the component below

Code:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class FindColor : MonoBehaviour, IPointerClickHandler
{
    private Color _color;
    private RawImage _rawImage;

    #region IPointerClickHandler Members

    public void OnPointerClick(PointerEventData eventData)
    {
        // get the color of the pixel that has been clicked in image

        var rc = eventData.pointerCurrentRaycast;
        if (!rc.isValid)
            return;

        var rt = _rawImage.rectTransform;
        var sp = rc.screenPosition;
        var cam = eventData.pressEventCamera;
        Vector2 point;
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, sp, cam, out point))
            return;

        var d = _rawImage.mainTexture as Texture2D;
        if (d == null)
            return;

        var p = point + rt.rect.min;
        var x = (int) p.x;
        var y = (int) p.y;
        _color = d.GetPixel(x, y);

        // TODO do whatever you have to with the clicked color
    }

    #endregion

    private void Start()
    {
        _rawImage = FindObjectOfType<RawImage>();
    }

    private void OnGUI()
    {
        var color = GUI.color;
        GUI.color = _color;
        GUILayout.Label("Clicked color");
        GUI.color = color;
    }
}

So this will find the pixel color you've clicked, no matter where on your screen the map is, then do what you need to when the clicked color matches one you're watching.

(I assume your map is shown at 100% scale)

Result:

enter image description here

Original image:

enter image description here

NOTE:

Obviously for the technique to work your provinces should each have a distinct tone (no antialiasing)

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for taking the time to write an answer, but I think you misunderstood me. I am looking to correct a problem in the posted code... The sprite is rendering as a plain colored square and not as the shape I drew it as in gimp. Is the posted code working correctly for you? If not would you mind editing the answer to include the code that worked for you? The problem is with rendering, not detecting touches and clicks (though that will come in handy when I implement the input). Are you suggesting I use a RawTexture for rendering? \$\endgroup\$
    – Jax
    Feb 3, 2017 at 2:02
0
\$\begingroup\$
public Sprite GenerateSprite()
{
    int minX = xPoints[0], minY = yPoints[0];
    int maxX = xPoints[0], maxY = yPoints[0];

    for(int i = 0; i < xPoints.Count; i++)
    {
        int xval = xPoints[i], yval = yPoints[i];

        if(xval < minX) minX = xval;
        if(xval > maxX) maxX = xval;
        if(yval < minY) minY = yval;
        if(yval > maxY) maxY = yval;
    }
    int width = (maxX - minX) + 1, height = (maxY - minY) + 1;
    Texture2D m_tex = new Texture2D(width, height);

    for(int i = 0; i < xPoints.Count; i++)
    {
        //This is the modified code.
        int worldX = xPoints[i];
        int worldY = yPoints[i];
        int xval = maxX - worldX;
        int yval = maxY - worldY;

        m_tex.SetPixel32(xval, yval, new Color32(0, 123, 20, 255)); //I want to display the province on screen in green
    }
    m_tex.Apply();
    this.image = Sprite.Create(m_tex, new Rect(0, 0, width, height), Vector2.zero, 1f);
    return image;
}

This code fixes the error. For whatever reason, the else when I was setting my max and min values was screwing things up. I was also forgetting to convert the x and y values into the coordinates of the new texture.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .