如何创建透明 Unity 的屏幕截图 window + OS

How to create a screenshot of a transparant Unity window + OS

因此,对于我们正在尝试创建的应用程序,我们希望将独立应用程序的 Unity window 透明化(除了几个按钮之外的所有内容)并让用户对其 view/OS+统一层在一起。

所以例子: 用户打开我们的应用程序,点击一个按钮,整个单元 window 除了几个按钮变成透明的。然后用户可以像平常一样使用它的 OS,而前面提到的按钮保持在顶部。然后用户可以单击按钮创建他们的 OS 的屏幕截图,然后我们会将其保存到他们的系统中。例如,通过屏幕截图,我们可以在用户 OS 上方显示 Unity 中的任何内容(3D 模型、图像)。

目前,我们可以使用如下类似设置将 window 变为透明:https://alastaira.wordpress.com/2015/06/15/creating-windowless-unity-applications/

效果很好,点击 windows 等也很好。但是,我们现在想创建一个屏幕截图并将其保存在某个地方。为此,我们尝试了多种方法,几年前,在我们将这个项目搁置一旁之前,我们通过使用我们从 unity 内部调用的 "using System.Drawing" 代码的自定义 dll 让它工作。请参阅下面此 dll 和代码的示例。

using System.Drawing;

namespace ScreenShotDll
{
public class ScreenShotClass
{
    public static void TakeScreenShotRect(int srcX, int srcY, int dstX, int dstY) //both fullscreen screenshot and cropped rectangle screenshot
    {

        int width = Math.Abs(srcX - dstX);
        int height = Math.Abs(srcY - dstY);

        Bitmap memoryImage;
        memoryImage = new Bitmap(width, height);
        Size s = new Size(memoryImage.Width, memoryImage.Height);

        Graphics memoryGraphics = Graphics.FromImage(memoryImage);

        memoryGraphics.CopyFromScreen(srcX, srcY, 0, 0, s);

        string str = "";

        try
        {
            str = string.Format(AppDomain.CurrentDomain.BaseDirectory + @"Screenshot.png");
        }
        catch (Exception er)
        {
            Console.WriteLine("Sorry, there was an error: " + er.Message);
            Console.WriteLine();
        }

        memoryImage.Save(str);
    }

然而,这似乎不再奏效了。我们在 Unity 的 IL2CPP 后端上得到错误:NotSupportedException: System.Drawing.Bitmap

我们也在尝试使用 Unity 中的 user32.dll 并使用它的 GetPixel、ReleaseDC 和 GetActiveWindow 函数,正如在几个论坛上发布的那样,但我们得到的只是一张白色图像.

我们将不胜感激调整我们的自定义 dll 的任何方法或任何其他方法。如果您需要更多信息,请告诉我。

几天后,我设法在 Unity 中解决了这个问题。

我使用了以下代码,制作了 window 的屏幕截图,并将其保存为位图。然后将其保存在我的磁盘上为.png。

[DllImport("user32.dll", SetLastError = true)] static extern int GetSystemMetrics(int smIndex);
[DllImport("user32.dll", SetLastError = false)] static extern IntPtr GetDesktopWindow();
[DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC", SetLastError=true)] static extern IntPtr CreateCompatibleDC([In] IntPtr hdc);
[DllImport("gdi32.dll", EntryPoint = "DeleteDC")] public static extern bool DeleteDC([In] IntPtr hdc);
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")] public static extern bool DeleteObject([In] IntPtr hObject);
[DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")] static extern IntPtr CreateCompatibleBitmap([In] IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll", EntryPoint = "SelectObject")] public static extern IntPtr SelectObject([In] IntPtr hdc, [In] IntPtr hgdiobj);
[DllImport("gdi32.dll", EntryPoint = "BitBlt", SetLastError = true)] static extern bool BitBlt([In] IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, [In] IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);

private void screenShot()
{
    Debug.Log("In screenShot!");
    int nScreenWidth = GetSystemMetrics(0);
    int nScreenHeight = GetSystemMetrics(1);
    IntPtr hDesktopWnd = GetDesktopWindow();
    IntPtr hDesktopDC = GetDC(hDesktopWnd);
    IntPtr hCaptureDC = CreateCompatibleDC(hDesktopDC);
    IntPtr hCaptureBitmap = CreateCompatibleBitmap(hDesktopDC, nScreenWidth, nScreenHeight);
    SelectObject(hCaptureDC, hCaptureBitmap);
    BitBlt(hCaptureDC, 0, 0, nScreenWidth, nScreenHeight, hDesktopDC, 0, 0, 0x00CC0020 | 0x40000000);

    Bitmap bmp = Image.FromHbitmap(hCaptureBitmap);

    ImageConverter converter = new ImageConverter();
    byte[] bytes = (byte[])converter.ConvertTo(bmp, typeof(byte[]));

    string path = Application.dataPath;
    if (Application.platform == RuntimePlatform.OSXPlayer) {
        path += "/../../";
    }
    else if (Application.platform == RuntimePlatform.WindowsPlayer) {
        path += "/../";
    }

    File.WriteAllBytes(path + "Screenshot" + ".png", bytes);

    ReleaseDC(hDesktopWnd, hDesktopDC);
    DeleteDC(hCaptureDC);
    DeleteObject(hCaptureBitmap);
}

我能够通过创建 class 库来获取屏幕截图。我将我的项目设置为 .Net 4.7.2 库,因此这可能对您不起作用 (IL2CPP)。我将我的统一项目脚本版本设置为 .Net 4.x 我不确定是否有必要。我只是将我的库 dll 文件复制到资产文件夹中。

这些是我需要的 using 语句,CapturePng 从 Unity 获取内存流。

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

public static void CapturePng(MemoryStream memoryStream, int width, int height, int resolutionX, int resolutionY)
{
    //Create a new bitmap.
    using (var bmpScreenshot = new Bitmap(
        width,
        height,
        PixelFormat.Format32bppArgb))
    {
        // Create a graphics object from the bitmap.
        using (var gfxScreenshot = Graphics.FromImage(bmpScreenshot))
        {
            // Take the screenshot from the upper left corner to the right bottom corner.
            gfxScreenshot.CopyFromScreen(
                new Point(0, 0),
                new Point(0, 0),
                new Size(width, height),
                CopyPixelOperation.SourceCopy);

            // Save the screenshot to the specified path that the user has chosen.
            new Bitmap(bmpScreenshot, new Size(resolutionX, resolutionY)).Save(memoryStream, ImageFormat.Png);
        }
    }
}

这就是我的 Unity 代码的样子。

Texture2D tex = new Texture2D(200, 300, TextureFormat.RGB24, false);
        
MemoryStream ms = new MemoryStream();

Screenshot.CapturePng(ms, Screen.currentResolution.width, Screen.currentResolution.height);

ms.Seek(0, SeekOrigin.Begin);
        
tex.LoadImage(ms.ToArray());

MeshRenderer.material.mainTexture = tex;

我希望这对您有所帮助,至少它不需要任何“user32.dll”导入或“gdi32.dll”导入。