10

想想Canvas你可能拥有的任何 Unity.UI。

Panel想象一下那个画布上的典型。假设它恰好包含一些图像,也许是一些文本等等。

如果您可以将该面板(仅面板)转换为屏幕截图,那将非常方便:Texture2D 或 PNG。

我唯一能想到的就是在屏幕上使用ReadPixels并找出有Panel问题的区域(实际上这很棘手);并且仅当面板恰好是方形并且没有以一定角度旋转时才有效。

你会想,应该有一种方法来渲染一个面板,或者至少,一个完整的画布?我什么也找不到。

在此处输入图像描述

在此处输入图像描述

在示例中,将粉色面板设为 PNG 图像。哎哟。

(显然,如果有人有一个仅执行“整个画布”而不是一个面板的解决方案,那当然也是令人钦佩的。)

4

3 回答 3

11

下面的代码可以拍摄 Canvas 的图片。画布必须附加您传递给它的对象。唯一要调用的函数是 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
}
于 2016-04-12T20:39:08.873 回答
1

这只是一个快速的尝试,因为我找不到任何官方的简单方法来做到这一点。

我不知道这对性能有多大影响,因为我还没有尝试过。

添加一个摄像头,指向 UI 面板以直接拍摄。将相机渲染为纹理,仅在相机上渲染该面板,销毁相机。

然后将纹理编码为 png

  1. 这可能非常昂贵。
  2. 它还会渲染屏幕的其余部分,除非您将相机调整到面板的大小,即使面板不是正方形或矩形,您也会在镜头中获得部分游戏/天空盒/背景颜色。
  3. 它可能根本行不通
于 2016-04-11T17:59:20.110 回答
1

我没有深入研究它,但我认为如果不渲染其父 Canvas 就不可能渲染 Panel。我还假设您不想在每一帧都这样做,而只是在特定场合。

在这种情况下,这是我会尝试的:

  • 使用 RenderMode“屏幕空间 - 相机”创建第二个画布。这允许您指定用于渲染此 Canvas 的相机。
  • 有一个专用的相机来渲染你的第二个画布。
  • 给相机一个处理 OnPreCull 和 OnPostRender 的脚本
  • OnPreCull,将目标面板附加到您的辅助 Canvas。OnPostRender,将其重新附加到之前的位置(我很……很抱歉)
  • 让辅助相机渲染到 RenderTexture
  • ReadPixels,应用,EncodeToPNG,瞧

当然还有很多细节没有说明,比如尺寸和位置等等。但是拥有专用的画布和相机应该可以让所有这些都被弄清楚并正确设置而不会弄乱东西。

于 2016-04-12T11:58:02.700 回答