Texture Editing on built project not working

I made a game where you peel apples. It paints to a mask texture to reveal the meat below the apples skin. It works just fine in the editor, but when I build the project for Win or WebGL the apple does not peel.

At the start the apple makes a copy of the mask texture and replaces it the mask with copy. The mask copy is created programmatically so the texture is set to readable/writable. I’m not sure what could be going on. Any insights would be appreciated.

Peeler Code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PeelBrush : MonoBehaviour
{
    public Transform anchor;
    public Transform peelerTransform;
    public Transform peelTransform;
    public Texture2D mask;
    public Texture2D peelerShape;

    public Color[] peelPixels;
    public int brushWidth, brushHeight;

    public GameObject peelTemplate;
    public GameObject peelObject;
    public float blend;
    public float blendX;
    public float blendScale = 5;
    public LayerMask layerMask;

    Vector3 start;
    // Start is called before the first frame update
    void Start()
    {
        peelPixels = peelerShape.GetPixels();
        brushWidth = peelerShape.width;
        brushHeight = peelerShape.height;
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        Vector3 mouse = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0);
        Ray ray = Camera.main.ScreenPointToRay(mouse);

        Debug.DrawRay(ray.origin, ray.direction);
        bool hit = Physics.Raycast(ray, out RaycastHit hitData, float.MaxValue, layerMask);


        if (hit)
        {
            transform.position = hitData.point + hitData.normal / 4;
            transform.forward = -hitData.normal;
            Debug.DrawRay(hitData.point, hitData.normal,Color.red);
            Fruit fruit = null;
            Transform parent = hitData.collider.transform.parent;
            while (parent != null)
            {
                parent.TryGetComponent<Fruit>(out fruit);
                if (fruit)
                    break;
                parent = parent.parent;
            }

            if (Input.GetMouseButtonDown(0))
            {
                blendX = hitData.point.x;
                start = hitData.point;
            }
            if (Input.GetMouseButton(0))
            {

                Material mat = hitData.collider.GetComponent<MeshRenderer>().material;
                mask = (Texture2D)hitData.collider.GetComponent<MeshRenderer>().material.GetTexture("_MaskTex");
                Vector2Int textureCoord = new Vector2Int((int)(mask.width * hitData.textureCoord.x), (int)(mask.height * hitData.textureCoord.y));


                if (hitData.point.x - blendX < 0.1f)
                {
                    if (!peelObject)
                    {
                        peelObject = GameObject.Instantiate(peelTemplate, peelerTransform.position, Quaternion.Euler(Vector3.right + Vector3.up + Vector3.forward));
                        peelObject.transform.parent = peelTransform;
                    }


                    if (fruit)
                    {
                        fruit.peeling = true;
                    }
                    Material mat2 = peelObject.GetComponentInChildren<MeshRenderer>().material;
                    blend += Mathf.Abs(hitData.point.x - blendX) / blendScale;
                    blendX = hitData.point.x;
                    mat2.SetFloat("_Blend", blend);

                    try
                    {
                        DrawCut(textureCoord.x - brushWidth / 2, textureCoord.y);
                        hitData.collider.GetComponent<MeshRenderer>().material.SetTexture("_MaskTex", mask);
                    }
                    catch (System.Exception e)
                    {
                        blend = 0;
                        peelObject.AddComponent<Rigidbody>();
                        peelObject = null;
                    }
                }

            }
            if (Input.GetMouseButtonUp(0) || blend >= 1)
            {
                blend = 0;
                peelObject.AddComponent<Rigidbody>();
                peelObject = null;
                if (fruit)
                {
                    fruit.peeling = false;
                }
            }
        }
        else
        {
            transform.position = anchor.position;
            transform.rotation= anchor.rotation;
        }

    }

    void DrawPixel(int x, int y, Color color)
    {
        mask.SetPixel(x, y, color);
        mask.Apply();
    }

    void DrawCut(int x, int y)
    {

        mask.SetPixels(x, y, brushWidth, brushHeight, peelPixels);
        mask.Apply();
    }


    /* public float layers;
     [Range(0.1f, 1f)]
     public float step = 1/(2*Mathf.PI);

     public float scale = 1;
     private void OnDrawGizmos()
     {
         Gizmos.color = Color.green;
         if (step < 0.08f)
             step = 0.08f;

         List<Vector3> points = new List<Vector3>();
         for(float i = 0; i < 2* Mathf.PI; i += step)
         {
             float angle = scale * Mathf.Pow(1.1f, i);
             Vector3 polarPoint = new Vector3(Mathf.Sin(angle),i,Mathf.Cos(angle));
             Gizmos.DrawSphere(polarPoint,.1f);
             points.Add(polarPoint);
         }

         for(int i =0; i < points.Count-1; i++)
         {
             Vector3 forward = (points[i+1]-points[i]).normalized;
             Vector3 right = CalcRight(forward);
             Vector3 up = CalcUp(right,forward);
             Gizmos.color = Color.blue;
             Gizmos.DrawRay(points[i], forward);
             Gizmos.color = Color.green;
             Gizmos.DrawRay(points[i], right);
             Gizmos.color = Color.red;
             Gizmos.DrawRay(points[i], up);
         }


     }
    */
    public Vector3 CalcRight(Vector3 forward)
    {
        return Vector3.Cross(Vector3.up, forward);
    }

    public Vector3 CalcUp(Vector3 right, Vector3 forward)
    {
        return -Vector3.Cross(right, forward);
    }
}

Fruit/Apple Code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Fruit : MonoBehaviour
{
    public MeshRenderer meshRenderer;
    public Material mat;
    public Texture2D mask;


    public MeshRenderer eyeRenderer;
    public Material eyeMat;
    public Texture normal, wince, dead;


    public MeshRenderer mouthRenderer;
    public Material mouthMat;
    public Texture smile, oh, frown,deadMouth;

   
    public bool peeling,witnessing,selected,alive=true;
    public float peelPercentage;
    public float deadPercent =.70f;
    Animator animator;
    // Start is called before the first frame update
    void Start()
    {
        mat = meshRenderer.material;
        mask = (Texture2D)Instantiate(mat.GetTexture("_MaskTex"));
        mat.SetTexture("_MaskTex", mask);

        eyeMat = eyeRenderer.material;
        mouthMat= mouthRenderer.material;
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
       
        if (peeling && alive)
        {
            UpdatePercentage();
            alive = peelPercentage > deadPercent;
        }

        if(peeling && alive)
        {

            eyeMat.mainTexture = wince;
            mouthMat.mainTexture = oh;
        }
        else if (alive)
        {
            eyeMat.mainTexture = normal;
            if (peelPercentage == 0 || peelPercentage > 98f)
                mouthMat.mainTexture = smile;
            else
                mouthMat.mainTexture = frown;
        }

        animator.SetBool("peeling",peeling);

       
    }

    void UpdatePercentage()
    {
        int totalWhite = 0;
        float total = 0;
        int density = mask.width * mask.height;
        Color[] colors = mask.GetPixels(0);
        foreach(Color k in colors)
        {
            float color = k.r + k.g + k.b;
            color /= 3;
            total += color;
            bool white = color > .8f; ;
            if (white)
            {
                totalWhite++;
            }
        }

        peelPercentage = total / colors.Length;

    }
}

Here’s the game:

Also lemme know what you think it, if you don’t mind.

It’s important to prove where this is going wrong, otherwise it’s impossible to fix.

I would first try and write a little blob of code (and put it in its own scene), some code that shows a texture, waits for user input (like a tap), the modifies that texture, perhaps by sprinkling a few hundred magenta pixels on it, then shows it again.

If you get that working in the Win and WebGL builds then you know it’s just a plain old bug somewhere in your code. Otherwise you have to isolate that what you want to do is possible on the targets you’re targeting.

Ok so I did what you suggested and made a scene with a texture and a script that modifies it when space is pressed. For both the Win and Web versions it worked just fine. So there’s probably a bug somewhere in my code. But its weird why would my code work in the editor but not in the build?

One possibility with this is your code relying on which order different objects get initialized. There’s many other possibilities too, but you gotta get ahold of the logging first.

See if you can get ahold of the logs from the targets that are failing, see if there is a null reference being thrown. I forget the details of grabbing them off Win or WebGL builds but google will tell you.

I got a hold of the logs. It doesn’t appear to be an out of order initlization problem. Whenever I attempt to cut there’s a log entry for a ‘texture rectangle is out of bounds’ error. I placed some debugs log around it to grab the position of where its writing pixels and the dimensions of the texture its grabbing. It’s writing to the pixel coordinate -4,0. When I run this code in the editor I get various valid coordinates, but in the Win build its -4,0 only.

So it turns out that the mesh collider used to detect texture coordinates of the apple mask was scaled down a bit and was also the child of an object that was also scaled down a bit. According to the unity manual:

For a Mesh Collider to work properly, the Mesh must be read/write enabled in any of these circumstances:

  • The Mesh Collider’s Transform has negative scaling (for example, (–1, 1, 1)) and the Mesh is convex.
  • The Mesh Collider’s transform is skewed or sheared (for example, when a rotated transform has a scaled parent transform).
  • The Mesh Collider’s **Cooking Options** flags are set to any value other than the default

Enabling read/write on the apple mesh fix this issue. It now peels properly on Win and WebGL.

Thanks for walking through this with me Kurt-Dekker.

1 Like

Ha! That’s awesome… Happy New Year… always glad to help.