11RIA 闪客社区 - 最赞 Animate Flash 论坛

搜索
查看: 2064|回复: 0
上一主题 下一主题

[BUG & 解决方案] 解决flash后台运行缓慢的问题(帧频低的问题)

[复制链接] TA的其它主题
发表于 2018-12-6 09:22:56 | 显示全部楼层 |阅读模式

【游客模式】——注册会员,加入11RIA 闪客社区吧!一起见证Flash的再次辉煌……

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
概述:

    flash后台运行帧频低是源自flashplayer插件的问题,为了解决flash在后台运行时少占用些CPU的问题,但却为页游带来了一些麻烦,比如挂机战斗时玩家切换到别的页面去看东西了,就会造成如伤害输出不足,基本停止打怪这样的问题(除非你的战斗逻辑全写在服务端)。

解决方案思路:

   起初想利用js的时间间隔相关的方式去实现(setTimeout、setInterval、和支持H5的requestAnimationFrame帧渲染),不过目前很多浏览器对于当前页面最小化或切换了标签进行了优化,不再执行js的这些函数。

360 浏览器 - 非激活无法执行定期执行的函数
           - 切换标签无法执行定期执行的函数

IE         - 非激活无法执行定期执行的函数
           - 切换标签无法执行定期执行的函数


chrome/ff/opera - 非激活可以执行定期函数
                - 切换标签无法执行定期函数

所以,换了一种思路:

      -- 检测flash帧频低:在enterframe 事件里根据距离上一帧的时间间隔来检测(或者连续几帧时间间隔都很长)并切换至【虚拟渲染模式】
      -- 检测flash帧频正常:在js里通过监听页面激活来与flash交互,并切换至【正常渲染模式】,同时在虚拟渲染模式时如果发现距离上一帧时间很短,就恢复至【正常渲染模式】(起到双重保险的作用,确保能够恢复正常)

实现流程:
   1-写一个JS,监听页面激活状态并通知FLASH,FLASH会切入到【正常渲染模式】-- 普通的entreframe事件。
   2-初始化flash,以embed方式嵌入js文件,并以字节流方式解成字符串,然后通过调用js的方法eval来动态解析执行这段js代码。
   3-首先进入正常的渲染模式,enterframe事件
   4-在onEnterframe里,检测距离上一帧的时间是否超过一定范围(比如我的是设定为700),如果是的话就切换至【虚拟渲染模式】,关闭掉游戏的主循环
       同时以while形式来确保将一帧执行N次游戏循环,根据计算得来的正确的帧间隔时间。
   5-在【虚拟渲染模式】中利用与上一次while的间隔时间以及JS监控到页面被激活主动调用FLASH通知切换至【正常渲染模式】(优先)的双重保险下确保回归正常的enterframe事件中。


flash-AS3

package
{
        import flash.display.Sprite;
        import flash.display.Stage;
        import flash.events.Event;
        import flash.events.MouseEvent;
        import flash.external.ExternalInterface;
        import flash.text.TextField;
        import flash.utils.ByteArray;
        import flash.utils.clearTimeout;
        import flash.utils.getTimer;
        import flash.utils.setTimeout;
       
       
       
        [SWF(width="1024",height="576",frameRate="60")]
        public class testBackRender extends Sprite
        {
                // 嵌入的JS文件,以字节流方式
                [Embed(source = "render.js",mimeType = "application/octet-stream")]
                // 绑定的类
                private var jsClass:Class;
                /**
                 * 游戏主循环总执行次数
                 */               
                private var f:int = 0;
                /**
                 * 虚拟渲染时的方法
                 */               
                private var vf:Function;
                /**
                 * 虚拟渲染时用到的setTimeout记录值
                 */               
                private var vfi:*;
                /**
                 * 是否真实渲染模式(enterframe),否则的话采用while虚拟执行游戏主循环
                 */               
                private var isRealRender:Boolean = true;
                /**
                 * 每帧的起始时间
                 */               
                private var lastFtime:Number;
                /**
                 * 每30帧的起始时间
                 */               
                private var frameTime30:Number
                /**
                 * 构造函数
                 *
                 */               
                public function testBackRender()
                {
                        // 当此容器被添加到舞台时事件
                        addEventListener(Event.ADDED_TO_STAGE,init);
                }
               
                private function init(e:Event):void{
                        // 读取字节转化为文本(由于文件是使用ANSI储存的,即本地字符编码,大陆的一般英文字母+简体汉字的话 GB2312)
                        var jsBy:ByteArray = new jsClass() as ByteArray;
                        jsBy.position = 0;
                        var jsStr:String = jsBy.readMultiByte(jsBy.length,"gb2312");
                        // 与JS交互并使用EVAL动态编译执行代码
                        if(ExternalInterface.available)ExternalInterface.call("eval",jsStr);
                       
                    // 记录当前帧的时间,每一帧会重新记录的
                    lastFtime = getTimer();
                        // 记录每30帧的起始时间
                        frameTime30 = getTimer();
                        // 启用真实渲染模式:游戏主循环帧事件
                    addEventListener(Event.ENTER_FRAME,onFrame);
                        // 监听来自JS的页面激活事件来主动快速切换到真实渲染模式
                    if(ExternalInterface.available)
                    {
                          ExternalInterface.addCallback("jscall_useLoop",function(){
                                  realRenderStart();
                          });
                    }
                  
                // debug文本
                    var tf:TextField = new TextField();
                    tf.background = true;
                    tf.backgroundColor = 0x000000;
                    tf.textColor = 0xffffff;
                        tf.width = 200;
                    tf.height = 576;
                    addChild(tf);
                }
                /**
                 * 真实渲染开启
                 */               
                private function realRenderStart():void{
                        // 如果已处于真实渲染状态则忽略
                        if(isRealRender)return;
                        // 标识为真实渲染状态
                        isRealRender = true;
                        trace("■■■■■■■■■■■■■■■■■■■■■■■■");
                        // 游戏主循环开启
                        removeEventListener(Event.ENTER_FRAME,onFrame);
                        addEventListener(Event.ENTER_FRAME,onFrame);
                        // 清空可能存在的虚拟渲染模式中的setTimeout
                        if(vfi!=null){
                                clearTimeout(vfi);
                                vfi=null;
                        }
                }
                /**
                 * 虚拟渲染开启
                 * 原理:由于一帧为1秒左右的后台运行,可以在这一帧内通过while来分摊虚拟执行N次游戏主循环(根据帧间隔时间)
                 *
                 */               
                private function virtualRenderStart():void{
                        // 如果已处于虚拟渲染状态则忽略
                        if(!isRealRender)return;
                        // 标识为虚拟渲染状态
                        isRealRender = false;
                        trace("□□□□□□□□□□□□□□□□□□□□□□□□");
                        // 清除游戏主循环帧事件
                        removeEventListener(Event.ENTER_FRAME,onFrame);
                        // 虚拟执行游戏主循环,通过While来实现
                        vf = function():void
                        {
                                // 内部检测根据下一帧的间隔时间如果很短说明恢复正常帧频了,这时切换回真实渲染模式即可
                                // 但通常会被外部的JS通过页面激活事件主动让这里变为真实渲染模式,所以实际上这只是起到保险作用
                                if(lastFtime){
                                        // 此项是因为可能已经被JS激活了
                                        if(isRealRender==true){
                                           return;
                                        }
                                        //
                                        var ft:Number =  getTimer()-lastFtime;
                                        trace("虚拟中的间隔时间",ft)
                                        if(ft<100){
                                                // -- 正常帧频
                                                realRenderStart();
                                                return;
                                        }
                                }
                                // 记录while循环开始前的时间
                                var virtualFrameT1:Number = getTimer();
                                // 计算帧间隔时间
                                var frameInterval:Number = (1000/stage.frameRate);
                                // 记录while循环开始前的时间,并且在期间会没执行一次游戏主循环就刷新记录下
                                var virtualFrameT2:Number = getTimer();
                                // 测试用的执行次数
                                var times:int = 0;
                                while(true){
                                        // 取得当前的时间
                                        var virtualFrameT3:Number = getTimer();
                                        // 如果距离while起始时间已经超过1800ms的话则要退出等待到下一帧来执行了,总要让flash渲染一下否则就卡死-脚本超时了
                                        // 并且你JS主动切换至真实渲染模式也要等待这个while循环结束,所以我们定为1800ms就结束循环,
                                        // 这个数字比较合理,距离flash下一帧的时间间隔较短(测试了60fps和30fps的情况下)
                                        if(virtualFrameT3-virtualFrameT1>=1800){
                                                // 记录当前帧时间
                                                lastFtime = getTimer();
                                                // 下一帧再重新执行此方法进入新的while
                                                vfi = setTimeout(vf,0);
                                                break;
                                        }
                                        // 超过间隔时间说明要执行一次游戏主循环了
                                        if((virtualFrameT3-virtualFrameT2)>frameInterval)
                                        {
                                                // 执行游戏主循环
                                                onFrame(null);
                                                // 多出来的部分要计算为当前时间-多出来的时间
                                                virtualFrameT2 = virtualFrameT3-((virtualFrameT3-virtualFrameT2)-frameInterval);
                                                // for test
                                                times++;
                                                trace(times,(virtualFrameT3-virtualFrameT2),"ms");
                                        }
                                }
                        };
                        // 立即执行虚拟渲染方法
                        vf();
                }
                /**
                 *EnterFrame事件
                 * @param e
                 *
                 */               
                private function onFrame(e:Event):void{
                   // 计算与上一帧的间隔时间       
                   var ft:Number =  getTimer()-lastFtime;
                   // for test
                   var tf:TextField = getChildAt(0) as TextField;
                   f++;
                   if(f%30==0){
                           tf.appendText((getTimer()-frameTime30)+"ms "+(e?"[正常]":"[后台运行]")+"\n");
                           frameTime30 = getTimer();
                           tf.scrollV = tf.bottomScrollV;
                   }
                   // -- 如果处于真实渲染模式的话,检测帧频很低的话就使用切换至虚拟渲染模式
                   if(isRealRender){
                           // 记录帧时间
                           lastFtime = getTimer();
                           if(ft>=700){
                                   // 清除,以免执行虚拟渲染方法时冲突
                                   lastFtime = null;
                                   // 切换至虚拟渲染方法
                                   virtualRenderStart();
                           }
                   }
                }
        }
}
代码:JS

var flashID = "testBackRender";
window.onfocus = function(){
    var flashObj = getFlashObj(flashID);
    flashObj.jscall_useLoop();
};
function getFlashObj(name){
          var obj = document[name]==null?window[name]:document[name];
        return obj;
}

---------------------
作者:黑暗之神KDS
来源:CSDN
原文:https://blog.csdn.net/kdsrpg/article/details/52299228
版权声明:本文为博主原创文章,转载请附上博文链接!

评分

参与人数 1银子 +66 收起 理由
最后一批aser + 66 11RIA six six six.

查看全部评分

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐 上一条 /1 下一条

感谢所有支持论坛的朋友:下面展示最新的5位赞助和充值的朋友……更多赞助和充值朋友的信息,请查看:永远的感谢名单

SGlW(66139)、 anghuo(841)、 whdsyes(255)、 longxia(60904)、 囫囵吞澡(58054)

下面展示总排行榜的前3名(T1-T3)和今年排行榜的前3名的朋友(C1-C3)……更多信息,请查看:总排行榜今年排行榜

T1. fhqu1462(969)、 T2. lwlpluto(14232)、 T3. 1367926921(962)  |  C1. anghuo(147)、 C2. fdisker(27945)、 C3. 囫囵吞澡(58054)



快速回复 返回顶部 返回列表