Creator 原生无感截图

接到需求,需要在原生中生成带二维码的海报,于是有了这篇记录
原生中不像微信小游戏那样,有离屏画布,所以必须要采用其他的方案来做

思路如下,使用双相机实现无感截图
主相机展示 UI界面,新增截图相机来做截图
具体实现,在需要截图的界面新增一个相机,同时新增一个sceneShot的层,截图相机渲染Group为sceneShot的元素
主相机渲染剔除sceneShot层,同时设置相机深度,sceneShot优先级比主相机优先级高即可

截图相机属性


image.png

主相机属性


image.png

后续就使用 cc.RenderTexture 组件,将截图相机中的内容送进来,然后使用RenderTexture实例 调用readPixels方法完成截图,截图时需要把二维码挂在上面所以,有一个等待机制,假设code 没加载出来,那么这截图没有任何意义,因为截图时间长,所以我做了缓存,做了缓存以后,发现需要更新截图的时候不方便(比如海报变动,或者code变动),我又做了版本比对,游戏启动会去读取配置信息,根据配置信息中的shareBgVersion 与 本地存储的shareBgVersion 进行对比,若服务器版本高且图片存在,那么会删除图片重新截图,同时把服务器版本保存到本地,
以备下一次更新,代码如下


const {ccclass, property} = cc._decorator;

@ccclass
export default class UISharePanel extends UI 
{

    @property(cc.Node) btn_close : cc.Node = null;

    @property(cc.Node) btn_friend : cc.Node = null;

    @property(cc.Node) btn_circle : cc.Node = null;

    @property(cc.Sprite) bg_shot : cc.Sprite = null;

    @property(cc.Camera) camera : cc.Camera = null;

    @property(cc.Sprite) headIcon : cc.Sprite = null;

    @property(cc.Sprite) inviteCodeImage : cc.Sprite = null;

    @property(cc.Node) content : cc.Node = null;

    @property(cc.Node) item : cc.Node = null;

    @property(cc.Node) loading : cc.Node = null;

    @property(cc.PageView) pageView : cc.PageView = null;

    private texture : cc.RenderTexture = null;

    private prefixPath = "";

    private width = 720;

    private height = 1280;

    private curIdx = 0; //当前索引

    private shotIdx = 1;

    private shotComplete : Function = null;

    private picDataList = [];

    private isCanShot = false; //是否可以截图

    private isNeedShot = false; //是否需要截图

    private pageSize = null;

    private shareBgList:string[] = 
    [
        "share_1.png",
        "share_2.png",
    ]

    public Init()
    {
        if(!CC_JSB)
        {
            return;
        }
        this.prefixPath = jsb.fileUtils.getWritablePath();
        if(cc.sys.os == cc.sys.OS_ANDROID)
        {
            this.prefixPath = '/sdcard/';
        }
        this.pageSize = this.item.getContentSize();
        this.texture = new cc.RenderTexture();
        let gl = cc.game._renderContext;
        this.texture.initWithSize(this.width,this.height,gl.STENCIL_INDEX8);
        this.camera.targetTexture = this.texture;
        this.UpdateShareInfo();
        this.pageView.setCurrentPageIndex(this.curIdx);
        this.curIdx ++;
    }

    //更新邀请信息
    public UpdateShareInfo()
    {
        //this.inviteCodeLabel.string = "<b><color=#000000>我的邀请码:"+ Player.Instance.playerInfo.invitationCode +"</c>";
        this.LoadImage(this.inviteCodeImage,Player.Instance.playerInfo.codeUrl,this.LoadFinished.bind(this));  
        this.LoadImage(this.headIcon,Player.Instance.playerInfo.hearImg,this.LoadFinished.bind(this));
    }

    private idx = 0;
    public LoadFinished()
    {
        this.idx ++;
        console.log("LoadFinished : " + this.idx);
        if(this.idx>=2)
        {
            console.error("图片加载完毕 可以截图");
            this.isCanShot = true;
            if(this.isNeedShot)
            {
                console.error("检测要需要截图,开始截图");
                this.ShotFunc();
            }
        }
    }    

    public LoadImage(image:cc.Sprite, url:string,callback:Function)
    {
        if(!url || url == "")
        {
            console.log("URL 为 null");
            return;
        }
        console.log("开始下载图片 : " + url);
        cc.loader.load({url: url, type: 'png'},(err,res)=>{
            if(err != null)
            {
                console.error("加载Image Error : " +JSON.stringify(err));
                return;
            }
            console.log("加载图片成功 : URL : " + url);
            image.spriteFrame = new cc.SpriteFrame(res);
            if(callback)
            {
                callback();
            }
        });
    }

    // 监听事件
    public OnPageEvent(sender, eventType) 
    { 
        // 翻页事件
        if (eventType !== cc.PageView.EventType.PAGE_TURNING) {
            return;
        }
        console.log("当前所在的页面索引:" + sender.getCurrentPageIndex());
        this.curIdx = sender.getCurrentPageIndex() + 1;
    }

    public GetCurShareURL()
    {
        return this.prefixPath + this.shareBgList[this.curIdx-1];
    }

    //检查分享背景图版本
    //返回值是否需要重新截图
    public CheckUpdateShareBg():boolean
    {
        let curVersion = cc.sys.localStorage.getItem("shareBgVersion");
        let serverVersion = HeroGame.Instance.GameConfigInfo.shareBgVersion;
        if(!curVersion)
        {
            return true;
        }
        else
        {
            if(serverVersion >= curVersion)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }

    public DeleteShareBg()
    {
        for(let i = 0;i<this.shareBgList.length;++i)
        {
            if (jsb.fileUtils.isFileExist(this.prefixPath + this.shareBgList[i])) 
            {
                console.error("删除图片 Path: " + this.prefixPath + this.shareBgList[i]);
                jsb.fileUtils.removeFile(this.prefixPath + this.shareBgList[i]);
                //cc.loader.releaseRes(this.prefixPath + this.shareBgList[i]);      
            }
        }
    }

    public CheckShareBgExist()
    {
        let isExist = true;
        for(let i = 0;i<this.shareBgList.length;++i)
        {
            if (!jsb.fileUtils.isFileExist(this.prefixPath + this.shareBgList[i])) 
            {
                isExist = false;
                break;
            }
        }
        return isExist;
    }

    //开始截图
    public StartSceneShot()
    {
        if(!CC_JSB)
        {
            this.loading.active = false;
            UIItem.InitList(this.content);
            return;
        }

        this.loading.active = true;
        UIItem.InitList(this.content);
        //首先检测一下是否图片已经存在 若存在直接读取
        let isExist = this.CheckShareBgExist();
        let isUpdate = this.CheckUpdateShareBg(); //检测是否需要更新
        if (isUpdate && isExist) 
        {
            console.error("图片存在且需要更新");
            this.DeleteShareBg();
            isExist = false;
        }
        
        if(isExist)
        {
            console.log("图片已经存在,直接读取");
            UIItem.InitList(this.content);
            this.pageView.removeAllPages();
            //读取展示
            for(let i = 0;i<this.shareBgList.length;++i)
            {   
                let sprite :cc.SpriteFrame = null;
                console.log(this.prefixPath+this.shareBgList[i]);
                cc.loader.load(this.prefixPath+this.shareBgList[i],(err,res)=>
                {
                    if(err)
                    {
                        console.error(err);
                        return;
                    }
                    else
                    {
                        console.log("加载成功");
                        sprite = new cc.SpriteFrame();
                        sprite.setTexture(res);
                        let node = cc.instantiate(this.item);
                        console.log(node.getContentSize());
                        node.name = "share_"+i;
                        this.pageView.addPage(node);
                        let Image = node.getComponent(cc.Sprite);
                        Image.spriteFrame = sprite;
                        node.active = true;
                    }
                })
            }
            let size = this.content.getContentSize();
            this.content.setContentSize(cc.size(200 + this.shareBgList.length* 534,size.height));
            this.loading.active = false;
        }
        else
        {
            console.log("图片不存在,需要重新截图");
            //图片不存在重新截图保存
            this.shotComplete = ()=>
            {
                console.log("截图全部完成");
                cc.sys.localStorage.setItem("shareBgVersion",HeroGame.Instance.GameConfigInfo.shareBgVersion+1); //更新背景标识
                UIItem.InitList(this.content);
                this.pageView.removeAllPages();
                //读取展示
                for(let i = 0;i<this.picDataList.length;++i)
                {
                    let picData = this.picDataList[i];
                    let texture = new cc.Texture2D();
                    
                    texture.initWithData(picData,32,this.width,this.height);
                    //创建主相机列表展示
                    let sprite = new cc.SpriteFrame(texture);
                    let node = cc.instantiate(this.item);
                    node.setContentSize(this.pageSize);
                    console.log("node size : " + node.getContentSize());
                    node.name = "share_"+i;
                    this.pageView.addPage(node);
                    let Image = node.getComponent(cc.Sprite);
                    Image.spriteFrame = sprite;
                    node.active = true;
                }
                let size = this.content.getContentSize();
                this.content.setContentSize(cc.size(200 + this.shareBgList.length* 534,size.height));
                this.loading.active = false;
            }
            this.isNeedShot = true;
            if(this.isCanShot)
            {
                console.error("调用截图时 图片已经加载完毕,可以截图");
                this.ShotFunc();
            }
            else
            {
                console.error("调用截图时 图片未加载完毕,需要等待");
            }
        }
    }

    public ShotFunc()
    {
        this.scheduleOnce(()=>
        {
            console.log("开始截图");
            let picData = this.InitImage();
            this.picDataList.push(picData);
            this.SaveFile(picData,this.shotIdx); //保存
            this.shotIdx++;
            if(this.shotIdx<this.shareBgList.length+1)
            {
                console.log("换背景继续截图 : " + this.shotIdx);
                cc.loader.loadRes("UI/Static/texture/share_"+this.shotIdx,cc.SpriteFrame,(err,res)=>
                {
                    if(err)
                    {
                        console.error("load share bg err : " + err);
                    }
                    else
                    {
                        this.bg_shot.spriteFrame = res;
                        this.ShotFunc();
                    }
                })
            }
            else
            {
                if(this.shotComplete)
                {
                    this.shotComplete();
                }
            }
        },0); //等一帧再次截图
    }

    private _width = 0;
    private _height = 0;

    public InitImage() : Uint8Array
    {
        let data = this.texture.readPixels();
        this._width =  this.texture.width;
        this._height = this.texture.height;
        let picData = this.filpYImage(data,this._width,this._height);
        return picData;
    }

    public filpYImage (data:Uint8Array, width:number, height:number) :Uint8Array
    {
        let picData = new Uint8Array(width * height * 4);
        let rowBytes = width * 4;
        for (let row = 0; row < height; row++) {
            let srow = height - 1 - row;
            let start = srow * width * 4;
            let reStart = row * width * 4;
            // save the piexls data
            for (let i = 0; i < rowBytes; i++) {
                picData[reStart + i] = data[start + i];
            }
        }
        return picData;
    }

    public SaveFile(picData:Uint8Array,idx:number)
    {
        if(CC_JSB)
        {
            let filePath = this.prefixPath + 'share_'+idx+'.png';
            let success = jsb.saveImageData(picData, this._width, this._height, filePath)
            if (success)
            {
                cc.log("save image data success, file: " + filePath);
            }
            else 
            {
                cc.error("save image data failed!");
            }
        }
    }
    
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335