Unity3D, построить PNG из панели единства.Пользовательского интерфейса?

подумайте о любом единстве.UI Canvas возможно.

представьте себе типичный Panel на этом холсте. Скажем, он содержит некоторые изображения, возможно, текст и так далее.

было бы очень удобно, если бы вы могли превратить эту панель (только панель) в скриншот: Texture2D или PNG.

единственное, что я могу придумать, это просто использовать ReadPixels и выяснить площадь Panel в вопрос на экране (и на самом деле это довольно сложно); и что работает только в том случае, если панель квадратная и не повернута под углом.

вы думаете, должен быть способ визуализации одной панели или, по крайней мере, одного целого холста? Я ничего не могу найти.

enter image description here

enter image description here

в Примере сделайте розовую панель PNG-изображением. Ай.

(очевидно, если у кого-то есть решение, которое делает просто "весь холст", а не одну панель, конечно, даже это замечательный.)

3 ответов


код ниже может сфотографировать холст. Холст должен быть прикреплен к объекту, который вы передаете в него. Единственная функция для вызова void takeScreenShot(Canvas canvasPanel, SCREENSHOT_TYPE screenShotType = SCREENSHOT_TYPE.IMAGE_AND_TEXT, bool createNewInstance = true)

SCREENSHOT_TYPE.IMAGE_AND_TEXT параметр сфотографирует изображения и текст.

SCREENSHOT_TYPE.IMAGE_ONLY параметр будет только сфотографировать изображения. Все тексты на экране будут исключены. Вы можете использовать это для безопасности, чтобы удалить тексты и просто показать графику только.

SCREENSHOT_TYPE.TEXT_ONLY параметр сфотографирует текст только.

как использовать. Создайте GameObject, прикрепите к нему сценарий CanvasScreenShot. Подписаться на CanvasScreenShot.OnPictureTaken(byte[] pngArray);, тогда звоните screenShot.takeScreenShot(canvasToSreenShot, SCREENSHOT_TYPE.IMAGE_AND_TEXT, false);

Полный Код:

код test.cs сценарий:

public class test : MonoBehaviour
{
    public Canvas canvasToSreenShot;

    // Use this for initialization
    void Start()
    {
        //Subscribe
        CanvasScreenShot.OnPictureTaken += receivePNGScreenShot;
        CanvasScreenShot screenShot = GameObject.Find("GameObject").GetComponent<CanvasScreenShot>();

        //take ScreenShot(Image and Text)
        //screenShot.takeScreenShot(canvasToSreenShot, SCREENSHOT_TYPE.IMAGE_AND_TEXT, false);
        //take ScreenShot(Image only)
        screenShot.takeScreenShot(canvasToSreenShot, SCREENSHOT_TYPE.IMAGE_ONLY, false);
        //take ScreenShot(Text only)
        // screenShot.takeScreenShot(canvasToSreenShot, SCREENSHOT_TYPE.TEXT_ONLY, false);

    }

    public void OnEnable()
    {
        //Un-Subscribe
        CanvasScreenShot.OnPictureTaken -= receivePNGScreenShot;
    }

    void receivePNGScreenShot(byte[] pngArray)
    {
        Debug.Log("Picture taken");

        //Do Something With the Image (Save)
        string path = Application.persistentDataPath + "/CanvasScreenShot.png";
        System.IO.File.WriteAllBytes(path, pngArray);
        Debug.Log(path);
    }

}

на CanvasScreenShot.cs сценарий:

public class CanvasScreenShot : MonoBehaviour
{
    /*
 CanvasScreenShot by programmer.
 http://stackoverflow.com/questions/36555521/unity3d-build-png-from-panel-of-a-unity-ui#36555521
 http://stackoverflow.com/users/3785314/programmer
 */

    //Events
    public delegate void takePictureHandler(byte[] pngArray);
    public static event takePictureHandler OnPictureTaken;

    private GameObject duplicatedTargetUI;
    private Image[] allImages;
    private Text[] allTexts;

    //Store all other canvas that will be disabled and re-anabled after screenShot
    private Canvas[] allOtherCanvas;

    //takes Screenshot
    public void takeScreenShot(Canvas canvasPanel, SCREENSHOT_TYPE screenShotType = SCREENSHOT_TYPE.IMAGE_AND_TEXT, bool createNewInstance = true)
    {
        StartCoroutine(_takeScreenShot(canvasPanel, screenShotType, createNewInstance));
    }

    private IEnumerator _takeScreenShot(Canvas canvasPanel, SCREENSHOT_TYPE screenShotType = SCREENSHOT_TYPE.IMAGE_AND_TEXT, bool createNewInstance = true)
    {
        //Get Visible Canvas In the Scene
        allOtherCanvas = getAllCanvasInScene(false);

        //Hide all the other Visible Canvas except the one that is passed in as parameter(Canvas we want to take Picture of)
        showCanvasExcept(allOtherCanvas, canvasPanel, false);
        //Reset the position so that both UI will be in the-same place if we make the duplicate a child
        resetPosAndRot(gameObject);

        //Check if we should operate on the original image or make a duplicate of it
        if (createNewInstance)
        {
            //Duplicate the Canvas we want to take Picture of
            duplicatedTargetUI = duplicateUI(canvasPanel.gameObject, "ScreenShotUI");
            //Make this game object the parent of the Canvas
            duplicatedTargetUI.transform.SetParent(gameObject.transform);

            //Hide the orginal Canvas we want to take Picture of
            showCanvas(canvasPanel, false);
        }
        else
        {
            //No duplicate. Use original GameObject
            //Make this game object the parent of the Canvas
            canvasPanel.transform.SetParent(gameObject.transform);
        }

        RenderMode defaultRenderMode;

        //Change the duplicated Canvas to RenderMode to overlay
        Canvas duplicatedCanvas = null;
        if (createNewInstance)
        {
            duplicatedCanvas = duplicatedTargetUI.GetComponent<Canvas>();
            defaultRenderMode = duplicatedCanvas.renderMode;
            duplicatedCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
        }
        else
        {
            defaultRenderMode = canvasPanel.renderMode;
            canvasPanel.renderMode = RenderMode.ScreenSpaceOverlay;
        }


        if (screenShotType == SCREENSHOT_TYPE.IMAGE_AND_TEXT)
        {
            //No Action Needed
        }
        else if (screenShotType == SCREENSHOT_TYPE.IMAGE_ONLY)
        {
            if (createNewInstance)
            {
                //Get all images on the duplicated visible Canvas
                allTexts = getAllTextsFromCanvas(duplicatedTargetUI, false);
                //Hide those images
                showTexts(allTexts, false);
            }
            else
            {
                //Get all images on the duplicated visible Canvas
                allTexts = getAllTextsFromCanvas(canvasPanel.gameObject, false);
                //Hide those images
                showTexts(allTexts, false);
            }
        }
        else if (screenShotType == SCREENSHOT_TYPE.TEXT_ONLY)
        {
            if (createNewInstance)
            {
                //Get all images on the duplicated visible Canvas
                allImages = getAllImagesFromCanvas(duplicatedTargetUI, false);
                //Hide those images
                showImages(allImages, false);
            }
            else
            {
                //Get all images on the duplicated visible Canvas
                allImages = getAllImagesFromCanvas(canvasPanel.gameObject, false);
                //Hide those images
                showImages(allImages, false);
            }
        }

        //////////////////////////////////////Finally Take ScreenShot///////////////////////////////
        yield return new WaitForEndOfFrame();
        Texture2D screenImage = new Texture2D(Screen.width, Screen.height);
        //Get Image from screen
        screenImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
        screenImage.Apply();

        //Convert to png
        byte[] pngBytes = screenImage.EncodeToPNG();

        /*FOR TESTING/DEBUGGING PURPOSES ONLY. COMMENT THIS
        string path = Application.persistentDataPath + "/CanvasScreenShot.png";
        System.IO.File.WriteAllBytes(path, pngBytes);
        Debug.Log(path);*/

        //Notify functions that are subscribed to this event that picture is taken then pass in image bytes as png
        if (OnPictureTaken != null)
        {
            OnPictureTaken(pngBytes);
        }


        ///////////////////////////////////RE-ENABLE OBJECTS

        //Change the duplicated Canvas RenderMode back to default Value
        if (createNewInstance)
        {
            duplicatedCanvas.renderMode = defaultRenderMode;
        }
        else
        {
            canvasPanel.renderMode = defaultRenderMode;
        }
        //Un-Hide all the other Visible Canvas except the one that is passed in as parameter(Canvas we want to take Picture of)
        showCanvas(allOtherCanvas, true);
        if (screenShotType == SCREENSHOT_TYPE.IMAGE_AND_TEXT)
        {
            //No Action Needed
        }
        else if (screenShotType == SCREENSHOT_TYPE.IMAGE_ONLY)
        {
            //Un-Hide those images
            showTexts(allTexts, true);
        }
        else if (screenShotType == SCREENSHOT_TYPE.TEXT_ONLY)
        {
            //Un-Hide those images
            showImages(allImages, true);
        }

        //Un-hide the orginal Canvas we want to take Picture of
        showCanvas(canvasPanel, true);

        if (createNewInstance)
        {
            //Destroy the duplicated GameObject
            Destroy(duplicatedTargetUI, 1f);
        }
        else
        {
            //Remove the Canvas as parent 
            canvasPanel.transform.SetParent(null);
        }
    }

    private GameObject duplicateUI(GameObject parentUICanvasOrPanel, string newOBjectName)
    {
        GameObject tempObj = Instantiate(parentUICanvasOrPanel);
        tempObj.name = newOBjectName;
        return tempObj;
    }


    private Image[] getAllImagesFromCanvas(GameObject canvasParentGameObject, bool findDisabledCanvas = false)
    {
        Image[] tempImg = canvasParentGameObject.GetComponentsInChildren<Image>(findDisabledCanvas);
        if (findDisabledCanvas)
        {
            return tempImg;
        }
        else
        {
            System.Collections.Generic.List<Image> canvasList = new System.Collections.Generic.List<Image>();
            for (int i = 0; i < tempImg.Length; i++)
            {
                if (tempImg[i].enabled)
                {
                    canvasList.Add(tempImg[i]);
                }
            }
            return canvasList.ToArray();
        }
    }

    private Text[] getAllTextsFromCanvas(GameObject canvasParentGameObject, bool findDisabledCanvas = false)
    {
        Text[] tempImg = canvasParentGameObject.GetComponentsInChildren<Text>(findDisabledCanvas);
        if (findDisabledCanvas)
        {
            return tempImg;
        }
        else
        {
            System.Collections.Generic.List<Text> canvasList = new System.Collections.Generic.List<Text>();
            for (int i = 0; i < tempImg.Length; i++)
            {
                if (tempImg[i].enabled)
                {
                    canvasList.Add(tempImg[i]);
                }
            }
            return canvasList.ToArray();
        }
    }

    private Canvas[] getAllCanvasFromCanvas(Canvas canvasParentGameObject, bool findDisabledCanvas = false)
    {
        Canvas[] tempImg = canvasParentGameObject.GetComponentsInChildren<Canvas>(findDisabledCanvas);
        if (findDisabledCanvas)
        {
            return tempImg;
        }
        else
        {
            System.Collections.Generic.List<Canvas> canvasList = new System.Collections.Generic.List<Canvas>();
            for (int i = 0; i < tempImg.Length; i++)
            {
                if (tempImg[i].enabled)
                {
                    canvasList.Add(tempImg[i]);
                }
            }
            return canvasList.ToArray();
        }
    }

    //Find Canvas.
    private Canvas[] getAllCanvasInScene(bool findDisabledCanvas = false)
    {
        Canvas[] tempCanvas = GameObject.FindObjectsOfType<Canvas>();
        if (findDisabledCanvas)
        {
            return tempCanvas;
        }
        else
        {
            System.Collections.Generic.List<Canvas> canvasList = new System.Collections.Generic.List<Canvas>();
            for (int i = 0; i < tempCanvas.Length; i++)
            {
                if (tempCanvas[i].enabled)
                {
                    canvasList.Add(tempCanvas[i]);
                }
            }
            return canvasList.ToArray();
        }
    }

    //Disable/Enable Images
    private void showImages(Image[] imagesToDisable, bool enableImage = true)
    {
        for (int i = 0; i < imagesToDisable.Length; i++)
        {
            imagesToDisable[i].enabled = enableImage;
        }
    }

    //Disable/Enable Texts
    private void showTexts(Text[] imagesToDisable, bool enableTexts = true)
    {
        for (int i = 0; i < imagesToDisable.Length; i++)
        {
            imagesToDisable[i].enabled = enableTexts;
        }
    }


    //Disable/Enable Canvas
    private void showCanvas(Canvas[] canvasToDisable, bool enableCanvas = true)
    {
        for (int i = 0; i < canvasToDisable.Length; i++)
        {
            canvasToDisable[i].enabled = enableCanvas;
        }
    }


    //Disable/Enable one canvas
    private void showCanvas(Canvas canvasToDisable, bool enableCanvas = true)
    {
        canvasToDisable.enabled = enableCanvas;
    }

    //Disable/Enable Canvas Except
    private void showCanvasExcept(Canvas[] canvasToDisable, Canvas ignoreCanvas, bool enableCanvas = true)
    {
        for (int i = 0; i < canvasToDisable.Length; i++)
        {
            if (!(canvasToDisable[i] == ignoreCanvas))
            {
                canvasToDisable[i].enabled = enableCanvas;
            }
        }
    }

    //Disable/Enable Canvas Except
    private void showCanvasExcept(Canvas[] canvasToDisable, Canvas[] ignoreCanvas, bool enableCanvas = true)
    {
        for (int i = 0; i < canvasToDisable.Length; i++)
        {
            for (int j = 0; j < ignoreCanvas.Length; j++)
            {
                if (!(canvasToDisable[i] == ignoreCanvas[j]))
                {
                    canvasToDisable[i].enabled = enableCanvas;
                }
            }
        }
    }

    //Reset Position
    private void resetPosAndRot(GameObject posToReset)
    {
        posToReset.transform.position = Vector3.zero;
        posToReset.transform.rotation = Quaternion.Euler(Vector3.zero);
    }

}

public enum SCREENSHOT_TYPE
{
    IMAGE_AND_TEXT, IMAGE_ONLY, TEXT_ONLY
}

Это просто быстрый удар, так как я не могу найти официальный простой способ сделать это.

Я понятия не имею, насколько интенсивной будет производительность, так как я не пробовал.

добавьте камеру, направленную на панель пользовательского интерфейса, чтобы получить прямой снимок. Визуализируйте камеру в текстуру, при этом только эта панель отображается на камере, уничтожьте камеру.

затем Закодируйте текстуру в png.

  1. это может быть ужасно дорогой.
  2. он также будет отображать остальную часть экрана, если вы не размер камера к панели, и даже тогда, если панель не квадрат или прямоугольник вы получите часть игры / skybox / цвет фона в выстрел.
  3. он может просто не работать

Я не заглядывал в него глубоко, но я предполагаю, что невозможно визуализировать панель без визуализации ее родительского холста. Я также предполагаю, что вы не хотите делать это каждый кадр, но только по особым случаям.

в этом контексте, вот что я бы попробовал:

  • есть второй холст с RenderMode"экран пространство - камера". Это позволяет указать камеру, используемую для визуализации этого холста.
  • есть специальная камера для визуализации второй Холст.
  • дайте камере скрипт, который обрабатывает OnPreCull и OnPostRender
  • OnPreCull, прикрепите целевую панель к вторичному холсту. OnPostRender, прикрепите его туда, где он был раньше (я так... так жаль)
  • пусть вторичная камера рендеринга на RenderTexture
  • ReadPixels, Apply, EncodeToPNG, вуаля

конечно, есть много деталей неуказанных, таких как размеры и позиции и что угодно. Но посвятив Холст и камера должны позволить все это быть выяснены и правильно настроить, не испортив вещи.