解决flash后台运行缓慢的问题(帧频低的问题)

概述:

    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事件中。


范例工程地址(flashbuilder工程):http://pan.baidu.com/s/1slvjnQX   密码:gtmf

   解决flash后台运行缓慢的问题(帧频低的问题)_第1张图片

代码 - 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;
}











你可能感兴趣的:(技术-语言-AS3)