Creator | 实现单个 Node 截图的两种方式

更舒适的阅读体验,请关注公众号:
qrcode_for_gh_5f59886669d1_258

实在是懒得整理格式了

截图是游戏中一个非常常见的需求,通过摄像机和 RenderTexture 我们可以快速实现一个截图功能

对于截图功能,在 example-cases 中有完整的测试用例,代码示例可参考 07_capture_texture

对于摄像机的介绍,请阅读官方文档:

https://docs.cocos.com/creator/manual/zh/render/camera.html

官方的测试用例中只提供了对屏幕的截图功能,但很多时候,我们只想对某个 node 进行截图

下面提供了两种方法,都可以实现该需求,两种实现方式虽然都是用 Camera 和 RenderTexture ,但还是有些区别,具体的优缺点在哪里,同学们可以自己思考

实现原理在代码中有详细的注释,这里就不再赘述

// 演示 //

// 实现 //

1 渲染屏幕,读取 node 区域的像素

captureNode(nodeCapture: cc.Node) {
    let nodeCamera = new cc.Node();
    nodeCamera.parent = cc.find("Canvas");
    let camera = nodeCamera.addComponent(cc.Camera);

    let width = nodeCapture.width;
    let height = nodeCapture.height;

    let texture = new cc.RenderTexture();
    texture.initWithSize(cc.visibleRect.width, cc.visibleRect.height, cc.gfx.RB_FMT_S8);

    camera.targetTexture = texture;

    let canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;

    let ctx = canvas.getContext('2d');
    camera.render();

    // 指定需要读取的区域的像素
    let size = nodeCapture.getContentSize();
    let pixels = new Uint8Array(size.width * size.height * 4);
    let x = texture.width / 2 - nodeCapture.width / 2;
    let y = texture.height / 2 - nodeCapture.height / 2;
    let w = nodeCapture.width;
    let h = nodeCapture.height;
    let data = texture.readPixels(pixels, x, y, w, h);

    // write the render data
    let rowBytes = width * 4;
    for (let row = 0; row < height; row++) {
        let srow = height - 1 - row;
        let imageData = ctx.createImageData(width, 1);
        let start = srow * width * 4;
        for (let i = 0; i < rowBytes; i++) {
            imageData.data[i] = data[start + i];
        }

        ctx.putImageData(imageData, 0, row);
    }

    let dataURL = canvas.toDataURL("image/png");
    let img = document.createElement("img");
    img.src = dataURL;

    let texture2D = new cc.Texture2D();
    texture2D.initWithElement(img);

    let spriteFrame = new cc.SpriteFrame();
    spriteFrame.setTexture(texture2D);

    let node = new cc.Node();
    let sprite = node.addComponent(cc.Sprite);
    sprite.spriteFrame = spriteFrame;

    return node;
}

// 插个小广告 //

江湖救急,因为业务需求,公司急招 creator 的小伙伴们,不出意外的话,咱们就一起写代码了~

为了让 渡鸦 少掉几根鸦毛,或者早点下班多点空余时间分享笔记,恳请大佬们将简历发我个人邮箱:valiancer@126.com 或者 投递到下方二维码

工作职责:

1.、负责作业帮核心业务游戏化内容开发;

2、 负责游戏内容的性能优化及体验优化;

3、使用Cocos Creator 开发教育游戏与互动课件

4、负责具体的系统功能与游戏课件的开发;

5、持续优化产品性能、功能逻辑和用户体验。

薪资范围好像是20K~50K(柠檬树上柠檬果,柠檬树下你和我[image])

2渲染 node

renderNode(nodeCapture: cc.Node) {
    let nodeCamera = new cc.Node();
    nodeCamera.parent = cc.find("Canvas");
    let camera = nodeCamera.addComponent(cc.Camera);

    let position = nodeCapture.getPosition();
    let width = nodeCapture.width;
    let height = nodeCapture.height;

    // 当 alignWithScreen 为 true 的时候,摄像机会自动将视窗大小调整为整个屏幕的大小。如果想要完全自由地控制摄像机,则需要将 alignWithScreen 设置为 false。(v2.2.1 新增)
    camera.alignWithScreen = false;
    // 设置摄像机的投影模式是正交(true)还是透视(false)模式
    camera.ortho = true;
    // 摄像机在正交投影模式下的视窗大小。该属性在 alignWithScreen 设置为 false 时生效。
    camera.orthoSize = height / 2;

    let texture = new cc.RenderTexture();
    // 如果截图内容中不包含 Mask 组件,可以不用传递第三个参数
    texture.initWithSize(width, height, cc.gfx.RB_FMT_S8);

    // 如果设置了 targetTexture,那么摄像机渲染的内容不会输出到屏幕上,而是会渲染到 targetTexture 上。
    camera.targetTexture = texture;

    // 创建画布
    let canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;

    let ctx = canvas.getContext('2d');

    nodeCapture.setPosition(cc.Vec2.ZERO);
    // 渲染一次摄像机,即更新一次内容到 RenderTexture 中
    camera.render(nodeCapture);
    nodeCapture.setPosition(position);

    // 从 render texture 读取像素数据,数据类型为 RGBA 格式的 Uint8Array 数组。
    // 默认每次调用此函数会生成一个大小为 (长 x 高 x 4) 的 Uint8Array。
    let data = texture.readPixels();
    // write the render data
    // PNG 中 1 像素 = 32 bit(RGBA),1 byte = 8 bit,所以 1 像素 = 4 byte
    // 每行 width 像素,即 width * 4 字节
    let rowBytes = width * 4;
    for (let row = 0; row < height; row++) {
        // RenderTexture 得到的纹理是上下翻转的
        let srow = height - 1 - row;
        let imageData = ctx.createImageData(width, 1);
        let start = srow * width * 4;
        for (let i = 0; i < rowBytes; i++) {
            imageData.data[i] = data[start + i];
        }

        ctx.putImageData(imageData, 0, row);
    }

    let dataURL = canvas.toDataURL("image/png");
    let img = document.createElement("img");
    img.src = dataURL;

    nodeCamera.destroy();

    let texture2D = new cc.Texture2D();
    texture2D.initWithElement(img);

    let spriteFrame = new cc.SpriteFrame();
    spriteFrame.setTexture(texture2D);

    let node = new cc.Node();
    let sprite = node.addComponent(cc.Sprite);
    sprite.spriteFrame = spriteFrame;

    return node;
}
8赞

好文章,顶一下

好文章,顶一下

mark,顺便问下,3.3版本怎么实现啊,render和readPixels都没有了

支持 好文章

3.3还没尝试
一直在用2.x···

1赞

我试了好久无解,GIT上有3.4和3.3.2两个版本,两种办法的截图都不行,截出来的图是白屏,不知道跟我的电脑有没有关系,你可以试试

这是3.4的DEMO:https://github.com/cocos-creator/CococsCreator-public-technology-solutions/tree/3.4.0-release/demo/Creator3.4.0_PartialScreenshot

这是3.3.2的DEMO:https://github.com/cocos-creator/CococsCreator-public-technology-solutions/tree/main/demo/Creator3.3.2_2D_DrawingBoard

你是要保存图片么
还是绘制到cocos里
哪个白屏?

用3.3.2那个demo跑了一下,绘制图片,保存出来的图片是一张白图

只是绘制的话,看下摄像机相关设置是否正确


相关代码:
let rt = new RenderTexture();
rt.reset({ width: width, height: height });
this.camera.targetTexture = rt;
director.root.frameMove(0);

2.4.1 渲染到精灵的时候
AlignWithScreen = false
渲染不到精灵,图片为黑

AlignWithScreen = true
正常
这个怎么解决?

标记一下, cc.gfx.RB_FMT_S8