Unity3D, построить PNG из панели единства.Пользовательского интерфейса?
подумайте о любом единстве.UI Canvas
возможно.
представьте себе типичный Panel
на этом холсте. Скажем, он содержит некоторые изображения, возможно, текст и так далее.
было бы очень удобно, если бы вы могли превратить эту панель (только панель) в скриншот: Texture2D или PNG.
единственное, что я могу придумать, это просто использовать ReadPixels
и выяснить площадь Panel
в вопрос на экране (и на самом деле это довольно сложно); и что работает только в том случае, если панель квадратная и не повернута под углом.
вы думаете, должен быть способ визуализации одной панели или, по крайней мере, одного целого холста? Я ничего не могу найти.
в Примере сделайте розовую панель 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.
- это может быть ужасно дорогой.
- он также будет отображать остальную часть экрана, если вы не размер камера к панели, и даже тогда, если панель не квадрат или прямоугольник вы получите часть игры / skybox / цвет фона в выстрел.
- он может просто не работать
Я не заглядывал в него глубоко, но я предполагаю, что невозможно визуализировать панель без визуализации ее родительского холста. Я также предполагаю, что вы не хотите делать это каждый кадр, но только по особым случаям.
в этом контексте, вот что я бы попробовал:
- есть второй холст с RenderMode"экран пространство - камера". Это позволяет указать камеру, используемую для визуализации этого холста.
- есть специальная камера для визуализации второй Холст.
- дайте камере скрипт, который обрабатывает OnPreCull и OnPostRender
- OnPreCull, прикрепите целевую панель к вторичному холсту. OnPostRender, прикрепите его туда, где он был раньше (я так... так жаль)
- пусть вторичная камера рендеринга на RenderTexture
- ReadPixels, Apply, EncodeToPNG, вуаля
конечно, есть много деталей неуказанных, таких как размеры и позиции и что угодно. Но посвятив Холст и камера должны позволить все это быть выяснены и правильно настроить, не испортив вещи.