【Unity开发】局部区域截图功能实现

🐾 个人主页 🐾

阿松爱睡觉,横竖醒不来 🏅你可以不屠龙,但不能不磨剑🗡


文章目录

一、前言

上一篇文章讲的长截图,有长截图就有短截图,或者区域截图,这篇文章就是说的区域截图,而且比长截图简单得多------不用滚动、不用拼接、不用算零头,渲染还是完整渲染一遍,只是读取像素的时候指定一个区域就行。比如只截游戏画面中间 500*400 的区域,把边缘的 UI 完美避开,用的就是这一招。

那么话不多说,直接开始今天的内容吧!

二、思路

(一)直接开一张小的 RenderTexture?不行

肯定有同学会想:截 500400 的区域,那我把 RenderTexture 直接开成 500400 不就行了?

不行👇👇👇。相机渲染到一张小的 RenderTexture 上,是把整个视野缩放着塞进去,相当于给画面打了个缩小镜头,并不是裁剪。不信的话可以自己试,截出来该有的东西一样不少,只是全都变小了。

所以裁剪这件事,只能在读像素这一步做。

(二)整屏渲染,读取的时候挑地方

正解分两步:

  1. RenderTexture 该开多大还开多大,和屏幕保持一致,相机完整渲染一遍;
  2. ReadPixels 的时候传一个 Rect,只读中间那块区域的像素,读出来的纹理就只有这么大。

一句话概括:渲染要完整,读取挑地方。下面直接上代码。

三、完整代码

csharp 复制代码
using System.IO;
using UnityEngine;

public class ShotPartScreen : MonoBehaviour
{
    public Camera camera1;      // 用来截图的相机
    public GameObject button1;  // 截图期间要藏起来的按钮

    /// <summary>
    /// 截取屏幕中间 500*400 的区域
    /// </summary>
    public void StartShotPart()
    {
        button1.SetActive(false);
        StartCoroutine(ShotPartTexture());
    }

    private IEnumerator ShotPartTexture()
    {
        // 和上一期长截图一个道理:等到帧结束,画面渲染完了再动手
        yield return new WaitForEndOfFrame();

        // 创建一张和屏幕一样大的 RenderTexture,作为相机的渲染目标
        RenderTexture renderTexture = new RenderTexture(Screen.width, Screen.height, 24);
        camera1.targetTexture = renderTexture;
        camera1.Render();

        // 计算屏幕中间 500*400 区域的坐标(注意坐标原点在左下角)
        int centerX = Screen.width / 2;
        int centerY = Screen.height / 2;
        int halfWidth = 250;  // 500的一半
        int halfHeight = 200; // 400的一半
        Rect middleRect = new Rect(centerX - halfWidth, centerY - halfHeight, 500, 400);

        // 把 RenderTexture 设为激活状态,然后只读取中间区域的像素
        RenderTexture.active = renderTexture;
        Texture2D texture2D = new Texture2D(500, 400, TextureFormat.RGB24, false);
        texture2D.ReadPixels(middleRect, 0, 0);
        texture2D.Apply();

        // 收尾:激活状态和渲染目标都还原,RenderTexture 该释放就释放
        RenderTexture.active = null;
        camera1.targetTexture = null;
        renderTexture.Release();

        // 保存成 PNG
        string path = Path.Combine(Application.dataPath, "SavedScreen.png");
        File.WriteAllBytes(path, texture2D.EncodeToPNG());
        Debug.Log($"xiao=====:{path}");

        // 把藏起来的按钮恢复回去
        button1.SetActive(true);
    }
}

四、代码讲解

(一)中间区域的坐标怎么算

思路很直白:屏幕中心点减去半个宽度和高度,拿到矩形的左下角。

csharp 复制代码
int centerX = Screen.width / 2;
int centerY = Screen.height / 2;
int halfWidth = 250;  // 500的一半
int halfHeight = 200; // 400的一半
Rect middleRect = new Rect(centerX - halfWidth, centerY - halfHeight, 500, 400);

这里有一个坑 要特别注意:屏幕和 RenderTexture 的坐标原点在左下角 ,y 轴向上。不少同学习惯性地按左上角为原点去算,算出来的位置直接就偏了。自检方法很简单:算出来的 x、y 应该都是正数,且 x + 500、y + 400 不超过屏幕宽高,那就对了。

(二)ReadPixels 只读一块

ReadPixels 的三个参数:从哪读、贴到哪、贴多大区域。

csharp 复制代码
texture2D.ReadPixels(middleRect, 0, 0);

意思就是:从当前激活的 RenderTexture 里 middleRect 这块区域读像素,贴到纹理的 (0, 0) 处。纹理本身开的是 500*400,和 middleRect 的尺寸一一对应,刚好贴满。

这里要强调原话:ReadPixels 里的坐标和尺寸是相对于 RenderTexture 的,不是相对于屏幕的。本例中 RenderTexture 和屏幕一样大,所以直接用屏幕坐标没问题;如果你的 RenderTexture 是别的尺寸(比如为了性能开了一半分辨率),坐标就要按比例换算,不能直接套屏幕坐标。

另外,Texture2D 的尺寸必须和 Rect 的尺寸一致,不一致 ReadPixels 会直接给你脸色看(读取异常或者花屏),这个没有商量。

(三)收尾工作

截图这活,重点都在收尾,三件事漏一件就出事:

  1. RenderTexture.active = null。不清掉的话,后面所有正常渲染都会写到这张 RenderTexture 上,游戏画面直接消失,这个坑踩过的人都懂。
  2. renderTexture.Release()。RenderTexture 占的是 GPU 内存,不释放就一直攒着,截得越多爆得越快。
  3. button1.SetActive(true)。截图前把按钮藏了,截完记得放出来,不然用户对着空气点半天。

哦对了,原来代码里保存路径是这么写的:

csharp 复制代码
File.WriteAllBytes(Application.dataPath + " /SavedScreen.png", bytes);

引号里面多打了一个空格,存出来的文件名前面带空格,路径看着没毛病,找文件的时候能给你找懵。所以优化版换成了 Path.Combine,分隔符也不用自己操心了:

csharp 复制代码
string path = Path.Combine(Application.dataPath, "SavedScreen.png");

五、注意事项

  1. 坐标原点在左下角,算中间区域的时候别按左上角算,这是最常见的错。
  2. Texture2D 尺寸必须和 Rect 尺寸一致 ,都是 500400,不能一个 500400 一个 600*400。
  3. 截取区域别超出屏幕范围。现在机型分辨率五花八门,小屏机上 500*400 也可能贴边,需要的话按屏幕尺寸做个 clamp,把宽高夹一下。
  4. RenderTexture 用完必须释放,active 状态必须还原,上面说过了,这里再啰嗦一遍。
  5. 路径按平台区分 。Editor 里 Application.dataPath 指向 Assets 目录,真机上更建议用 Application.persistentDataPath。
  6. 老生常谈:如果 UI 是 Screen Space - Overlay,相机渲染抓不到 UI,得换成等帧结束直接读屏幕后备缓冲的思路,这个在长截图那期说过,这里不重复了。

六、结语

局部截图比长截图简单得多------不用滚、不用拼,RenderTexture 完整渲染一遍,ReadPixels 指定个 Rect 就完事了。真正的坑全在细节里:坐标原点、尺寸匹配、释放还原,这三件事忘了任何一件,轻则截图截歪了,重则画面全黑。

建议大家照着代码自己敲一遍,顺手把 500*400 改成别的尺寸试试,看看截出来的位置对不对。

如果在实践的过程中遇到什么问题,欢迎在评论区留言,我看到的话会及时回复。这期的内容就到这里,下期见,拜拜!

相关推荐
zhanghaha13143 小时前
C# 系列教程_1 基础语法篇(面向新手系统教程)
开发语言·c#
昂好的day4 小时前
固高运动控制卡实战(下):点位运动与直线/圆弧插补 API 详解
开发语言·c#
淡海水8 小时前
08-02-优化-LINQ与函数式编程的GC代价
unity·c#·.netcore·linq·gc
sali-tec9 小时前
C# 基于OpenCv的视觉工作流-章114-视觉与http通讯
图像处理·人工智能·opencv·计算机视觉·c#
波力海苔夹心脆6759 小时前
C# 委托与事件详解:委托声明、Action/Func、多播、event 与 EventHandler 入门教程
开发语言·c#·.net·上位机·委托·事件
警醒与鞭策1 天前
Codex桌面版与Claude桌面版使用设置
android·unity·ai·性能优化
xcLeigh1 天前
Unity基础:Destroy与DestroyImmediate——正确销毁游戏物体的方式
游戏·unity·游戏引擎·教程
He BianGu1 天前
【笔记】C# 中BindingFlags的详细作用
笔记·c#
?abc!1 天前
(贪心)621. 任务调度器
java·前端·c#