如何在 Unity 中截圖
在 Unity 中要實現截圖的功能,我大體搜索了下,列舉如下幾種:
截全屏(使用 ScreenCapture 的API)void CaptureScreen() { ScreenCapture.CaptureScreenshot("Screenshot.png"); }根據截圖區域截圖(使用 Texture2D 的 API)
Texture2D CaptureScreenshot2(Rect rect) { // 先創建一個的空紋理,大小可根據實現需要來設置 Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24,false);
// 讀取屏幕像素信息並存儲為紋理數據, screenShot.ReadPixels(rect, 0, 0); screenShot.Apply();
// 然後將這些紋理數據,成一個png圖片文件 byte[] bytes = screenShot.EncodeToPNG(); string filename = Application.dataPath + "/Screenshot.png"; System.IO.File.WriteAllBytes(filename, bytes); Debug.Log(string.Format("截屏了一張圖片: {0}", filename));
// 最後,我返回這個Texture2d對象,這樣我們直接,所這個截圖圖示在遊戲中,當然這個根據自己的需求的。 return screenShot; }根據相機渲染內容截圖(使用 RenderTexture API)
Texture2D CaptureCamera(Camera camera, Rect rect) { RenderTexture rt = new RenderTexture((int)rect.width, (int)rect.height, 0); camera.targetTexture = rt; camera.Render();
RenderTexture.active = rt; Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24,false); screenShot.ReadPixels(rect, 0, 0); screenShot.Apply();
camera.targetTexture = null; RenderTexture.active = null; GameObject.Destroy(rt); byte[] bytes = screenShot.EncodeToPNG(); string filename = Application.dataPath + "/Screenshot.png"; System.IO.File.WriteAllBytes(filename, bytes); Debug.Log(string.Format("截屏了一張照片: {0}", filename));
return screenShot; }
以上 3 種方案應該都可以實現截圖功能,但是實際使用下來或多或少都還有些問題,具體哪些問題可以嘗試去寫寫代碼。
UI 截圖方案這裡介紹一種 UI 截圖的方案,其實也是上面截圖方案的一種而已,之所以單獨拿出來說,也是在使用過程中出現過一些問題,然後自己總結得到的一種通用功能接口。
UI 截圖用到的方案就是上面的第二種,通過給定具體的區域進行屏幕截圖,那這種方案實現下來會有怎樣的問題呢。其實答案也簡單,截圖嘛,最怕的不就是截圖截錯了,或者根本截不到圖。在 Unity 的 UGUI 中為啥會出現這些情況呢,這原因可能在於 UGUI 本身的設計,帶錨點和不用錨點的 UI 截圖會出現不一樣的情況,更何況不同解析度下的 UI 展示的效果也是不同的。
好了,話不多說,直接上代碼:
IEnumerator CaptureScreenshot(RectTransform rectTransform){ yield return new WaitForEndOfFrame(); int width = Mathf.Abs((int)rectTransform.rect.size.x); int height = Mathf.Abs((int)rectTransform.rect.size.y);
Texture2D tex = new Texture2D(width, height, TextureFormat.RGB24, false); int x = (int)((mainCam.WorldToScreenPoint(rectTransform.position).x) - (width * rectTransform.pivot.x)); int y = (int)((mainCam.WorldToScreenPoint(rectTransform.position).y) - (height * rectTransform.pivot.y));
tex.ReadPixels(new Rect(x, y, width, height), 0, 0); //執行讀取操作 tex.Apply(); byte[] bytes = tex.EncodeToPNG(); //保存 System.IO.File.WriteAllBytes(Application.dataPath + "/截圖.png", bytes);}
代碼中需要注意的有幾點:
首先就是獲取傳入 UI 組件的 RectTransform 得到組件的寬高,這裡採取的方法可能在某些組件上不起作用,這個後面再說;然後就是構建我們需要的 Texture2D 對象,設置好寬高,這裡需要了解 x 和 y 的值在截圖方法中起到什麼樣的作用。我們採用區域截圖的原理進行截圖,目的是構建一個 Texture2D 對象,目前寬高設置好了,但是截圖區域還沒說,截圖區域默認屏幕左下角為 (0,0) ,那麼 x 和 y 對應的就是這個起始截圖位置了。上面代碼針對 UI 組件進行了 x 和 y 值的獲取,需要將 UI 組件的世界坐標轉換成屏幕坐標,而且還要需要考慮 UI 組件的 pivot 點的位置,這樣才能精確計算到當前 UI 組件需要截取的區域是哪塊。目前我使用上述方案截取 UI 的圖片,沒啥問題了,但可能我測試不多,有問題再改吧~截長圖的實現過程這裡截長圖的方案也是基於上面 UI 截圖來的,在 Unity 中搭建的界面,涉及長圖的話其實應該是使用了 ScrollView 組件,本方案也是基於 UGUI 中的 ScrollView 組件來的。
基本原理:截圖的方案有了,但是截圖必須是能在屏幕上看到的內容才能截取,比如一頁顯示不完的 ScrollView 內容 ,是不能夠直接傳入 UI 組件的信息進行截圖的,所以這裡的想法是拖動 ScollView 一幀一幀來截圖,然後收集截取的 Texture2D 片段,最後拼接截取的片段變成長圖。
話不多說,還是直接上代碼:
using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;using FitMode = UnityEngine.UI.ContentSizeFitter.FitMode;
public class CaptureLongPic : MonoBehaviour{ public Button captureBtn; private Camera mainCam; public RectTransform targetTrans;
private ScrollRect scrollRect; // 計算 需要截取多少張圖 private int texLength = 0; // 收集到的 截圖片段 private List<Texture2D> texList; // 需要截取的區域信息 private RectTransform capturesRectTrans; // 是否可以分成整頁整頁這樣 private bool isAll = false; // 一頁的高度 private float pageHeight; // 所有頁面的高度(長圖的高度) private float allPageHeight;
void Start() { scrollRect = targetTrans.GetComponent<ScrollRect>(); captureBtn.onClick.AddListener(() => { Capture(); });
mainCam = Camera.main;
texList = new List<Texture2D>();
float height1 = scrollRect.transform.Find("Viewport").GetComponent<RectTransform>().rect.size.y; // float height2 = scrollRect.transform.Find("Viewport/Content").GetComponent<RectTransform>().rect.size.y; // 這樣獲取的是 0
// 組件 有 ContentSizeFitter 則需要這樣獲取組件高度 LayoutRebuilder.ForceRebuildLayoutImmediate(scrollRect.transform.Find("Viewport/Content").GetComponent<RectTransform>()); float height2 = HandleSelfFittingAlongAxis(1, scrollRect.transform.Find("Viewport/Content").GetComponent<RectTransform>(), scrollRect.transform.Find("Viewport/Content").GetComponent<ContentSizeFitter>());
// 說明滑動內容不夠一頁 if (height1 >= height2) { Debug.Log("內容不足1頁"); texLength = 1; // 只需要截取 content 中的內容即可 capturesRectTrans = scrollRect.transform.Find("Viewport/Content").GetComponent<RectTransform>(); isAll = false; } else { // 如果 滑動內容長度是一頁的整數倍 if ((height2 % height1) == 0) { texLength = (int)(height2 / height1); isAll = true; } else { texLength = (int)(height2 / height1) + 1; isAll = false; } capturesRectTrans = scrollRect.transform.Find("Viewport").GetComponent<RectTransform>(); }
pageHeight = height1; allPageHeight = height2;
}
// public Vector2 GetContentSizeFitterPreferredSize(this RectTransform rect, ContentSizeFitter contentSizeFitter) // { // LayoutRebuilder.ForceRebuildLayoutImmediate(rect); // return new Vector2(HandleSelfFittingAlongAxis(0, rect, contentSizeFitter), HandleSelfFittingAlongAxis(1, rect, contentSizeFitter)); // }
private float HandleSelfFittingAlongAxis(int axis, RectTransform rect, ContentSizeFitter contentSizeFitter) { FitMode fitting = (axis == 0 ? contentSizeFitter.horizontalFit : contentSizeFitter.verticalFit); if (fitting == FitMode.MinSize) { return LayoutUtility.GetMinSize(rect, axis); } else { return LayoutUtility.GetPreferredSize(rect, axis); } }
void Capture() { StartCoroutine(CaptureLonPic()); }
IEnumerator CaptureLonPic() { //上移 scrollview 繼續截圖 // 判斷當前長圖頁面是否是整數倍 if (isAll) { for (int i = 0; i < texLength; i++) { yield return new WaitForEndOfFrame();
StartCoroutine(CaptureScreenshot(capturesRectTrans));
scrollRect.verticalNormalizedPosition = 1 - (1 * i / texLength); } } else { float offset = pageHeight / (allPageHeight - pageHeight); for (int i = 0; i < texLength; i++) { yield return new WaitForEndOfFrame(); if (i == texLength - 1) { StartCoroutine(CaptureScreenshot(capturesRectTrans, (int)(allPageHeight - pageHeight * (texLength - 1)))); scrollRect.verticalNormalizedPosition = 0; } else { StartCoroutine(CaptureScreenshot(capturesRectTrans)); scrollRect.verticalNormalizedPosition = 1 - (offset * i); } } } yield return new WaitForSeconds(1f); // 拼接長圖 MergeImage(texList); }
/// <summary> /// Captures the screenshot2. /// </summary> /// <returns>The screenshot2.</returns> /// <param name="rect">Rect.截圖的區域,左下角為o點</param> IEnumerator CaptureScreenshot(RectTransform rectTransform, int h = 1280) { // 必須是 Camera 渲染模式 yield return new WaitForEndOfFrame(); int width = Mathf.Abs((int)rectTransform.rect.size.x); int height = Mathf.Abs((int)rectTransform.rect.size.y);
Texture2D tex; int x = (int)((mainCam.WorldToScreenPoint(rectTransform.position).x) - (width * rectTransform.pivot.x)); int y = (int)((mainCam.WorldToScreenPoint(rectTransform.position).y) - (height * rectTransform.pivot.y));
// 默認 解析度 720 * 1280 此處可以設置為動態獲取當前需要設置的高度 if (h != 1280) { tex = new Texture2D(width, h, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(x, y, width, h), 0, 0); } else { tex = new Texture2D(width, height, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(x, y, width, height), 0, 0); } //執行讀取操作 tex.Apply(); texList.Add(tex);// 收集 Texture2D }
/// <summary> /// 多張Texture2D合成一張Texture2D /// </summary> /// <param name="tex"></param> /// <returns></returns> public Texture2D MergeImage(List<Texture2D> tex) { if (tex.Count == 0) return null; //定義新圖的寬高 int width = 0, height = 0;
for (int i = 0; i < tex.Count; i++) { //新圖的寬度 width = tex[i].width; height += tex[i].height; }
//初始Texture2D Texture2D texture2D = new Texture2D(width, height);
int x = 0, y = 0; for (int i = tex.Count - 1; i >= 0; i--) { //取圖 Color32[] color = tex[i].GetPixels32(0); //賦給新圖 if (i < tex.Count - 1) { texture2D.SetPixels32(x, y += tex[i + 1].height, tex[i].width, tex[i].height, color); } else { texture2D.SetPixels32(x, y, tex[i].width, tex[i].height, color); } }
//應用 texture2D.Apply();
byte[] bytes = texture2D.EncodeToPNG(); //保存 System.IO.File.WriteAllBytes(Application.dataPath + "/截圖.png", bytes); return texture2D; }}代碼注意:
ScrollView 組件中 Content 一般會添加 ContentSizeFitter ,一旦添加了這個的 UI 組件時不能像上述截圖方案中那樣獲取組件寬高信息,具體可以參考上面代碼的方式獲取寬高。
截長圖還是需要考慮截圖內容的長度問題,不足一頁的和超過一頁的情況,同時得到一頁高度和總高度,為後面移動 ScrollView 做準備,還有就是基於目前的情況得到能夠截取多少個 Texture2D。
代碼的截圖代碼大體和上述的方案差不多,不過不同的是添加了尾頁不足一頁長度的特殊處理。
最後就是拼接圖片,遍歷 Texture2D 的時候採取的從後向前遍歷,因為發現這樣合成的圖片才是正常的;SetPixels32 方法可以理解為 給長圖 Texture2D 的框框添加內容,參數就是具體的位置信息而已。
綜上差不多了~