如何减少我的Flash游戏的后台引擎600MB内存使用率峰值?

时间:2009-09-26 10:24:28

标签: flash actionscript-3 memory memory-management background

我正在动作脚本3中创建一个具有无限宇宙的Flash游戏。由于Universe是无限的,因此使用以下后台引擎动态创建背景:

BackgroundEngine.as

package com.tommedema.background
{
    import br.com.stimuli.loading.BulkLoader;

    import com.tommedema.utils.Settings;
    import com.tommedema.utils.UtilLib;

    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.TimerEvent;
    import flash.utils.Timer;

    public final class BackgroundEngine extends Sprite
    {
        //general
        private static var isLoaded:Boolean = false;        
        private static var bulkLoader:BulkLoader = new BulkLoader("backgroundEngine");
        private static var assetsBitmapData:Array = [];
        private static var drawTimer:Timer;

        //objects
        private static var masterContainer:Sprite;
        private static var containers:Array = [];

        //stage
        private static var stageWidth:uint;
        private static var stageHeight:uint;
        private static var stageCenterX:Number;
        private static var stageCenterY:Number;

        //moves the background's X and Y coord
        public static function moveXY(xAmount:Number, yAmount:Number):void
        {
            if (!masterContainer) return;
            if (xAmount != 0) masterContainer.x += xAmount;
            if (yAmount != 0) masterContainer.y += yAmount;
        }

        //returns whether the background engine has been loaded already
        public static function loaded():Boolean
        {
            return isLoaded;
        }

        //loads the background engine
        public final function load():void
        {
            if (isLoaded) return;
            UtilLib.log("BackgroundEngine load.");

            //set stage width, height and center
            stageWidth = stage.stageWidth;
            stageHeight = stage.stageHeight;
            stageCenterX = stageWidth / 2;
            stageCenterY = stageHeight / 2;

            //load drawing timer
            drawTimer = new Timer(Settings.BG_DRAW_IV);
            drawTimer.addEventListener(TimerEvent.TIMER, updateBackground, false, 0, true);
            drawTimer.start();

            //retrieve background assets
            if ((bulkLoader.get("background/4.png")) && (bulkLoader.get("background/4.png").isLoaded))
            {
                loadAssets();
            }
            else
            {
                bulkLoader.add(Settings.ASSETS_PRE_URL + "background/1.gif", {id: "background/1.gif"});
                bulkLoader.add(Settings.ASSETS_PRE_URL + "background/2.png", {id: "background/2.png"});
                bulkLoader.add(Settings.ASSETS_PRE_URL + "background/3.png", {id: "background/3.png"});
                bulkLoader.add(Settings.ASSETS_PRE_URL + "background/4.png", {id: "background/4.png"});
                bulkLoader.addEventListener(BulkLoader.COMPLETE, assetsComplete, false, 0, true);
                bulkLoader.start();
            }

            //set isLoaded to true
            isLoaded = true;
        }

        //unloads the background engine
        public final function unload():void
        {
            if (!isLoaded) return;
            UtilLib.log("BackgroundEngine unload method has been called.");

            //unload drawTimer
            drawTimer.removeEventListener(TimerEvent.TIMER, updateBackground);
            drawTimer.stop();
            drawTimer = null;

            //clean the asset array
            assetsBitmapData = [];

            //remove containers
            for each (var container:Sprite in containers)
            {
                if (container)
                {
                    masterContainer.removeChild(container);
                    container = null;                   
                }
            }
            containers = [];

            //remove master container
            if (masterContainer)
            {
                removeChild(masterContainer);
                masterContainer = null; 
            }

            //set isLoaded to false
            isLoaded = false;
        }

        //updates the background
        private final function updateBackground(event:TimerEvent):void
        {
            if (masterContainer)
            {
                collectGarbage();
                drawNextContainer();    
            }   
        }

        //poller function for drawing next background squares
        private static function drawNextContainer():void
        {
            var curContainer:Sprite = hasBackground(stageCenterX, stageCenterY);
            if (curContainer)
            {
                if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY - stageHeight * 0.75)) //top left
                    drawNewSquare(curContainer.x - curContainer.width, curContainer.y - curContainer.height);
                if (!hasBackground(stageCenterX, stageCenterY - stageHeight * 0.75)) //top
                    drawNewSquare(curContainer.x, curContainer.y - curContainer.height);
                if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY - stageHeight * 0.75)) //top right
                    drawNewSquare(curContainer.x + curContainer.width, curContainer.y - curContainer.height);
                if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY)) //center left
                    drawNewSquare(curContainer.x - curContainer.width, curContainer.y);
                if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY)) //center right
                    drawNewSquare(curContainer.x + curContainer.width, curContainer.y);
                if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY + stageHeight * 0.75)) //bottom left
                    drawNewSquare(curContainer.x - curContainer.width, curContainer.y + curContainer.height);
                if (!hasBackground(stageCenterX, stageCenterY + stageHeight * 0.75)) //bottom center
                    drawNewSquare(curContainer.x, curContainer.y + curContainer.height);
                if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY + stageHeight * 0.75)) //bottom right
                    drawNewSquare(curContainer.x + curContainer.width, curContainer.y + curContainer.height);
            }
        }

        //draws the next square and adds it to the master container
        private static function drawNewSquare(x:Number, y:Number):void
        {
            containers.push(genSquareBg());
            var cIndex:uint = containers.length - 1;
            containers[cIndex].x = x;
            containers[cIndex].y = y;
            masterContainer.addChild(containers[cIndex]);
        }

        //returns whether the given location has a background and if so returns the corresponding square
        private static function hasBackground(x:Number, y:Number):Sprite
        {
            var stageX:Number;
            var stageY:Number;
            for(var i:uint = 0; i < containers.length; i++)
            {
                stageX = masterContainer.x + containers[i].x;
                stageY = masterContainer.y + containers[i].y;
                if ((containers[i]) && (stageX < x) && (stageX + containers[i].width > x) && (stageY < y) && (stageY + containers[i].height > y)) return containers[i];
            }
            return null;
        }

        //polling function for old background squares garbage collection
        private static function collectGarbage():void
        {
            var stageX:Number;
            var stageY:Number;
            for(var i:uint = 0; i < containers.length; i++)
            {
                if ((containers[i]) && (!isRequiredContainer(containers[i])))
                {
                    masterContainer.removeChild(containers[i]);
                    containers.splice(i, 1);
                }
            }
        }

        //returns whether the given container is required for display
        private static function isRequiredContainer(container:Sprite):Boolean
        {
            if (hasBackground(stageCenterX, stageCenterY) == container) //center
                return true;
            if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY - stageHeight * 0.75) == container) //top left
                return true;
            if (hasBackground(stageCenterX, stageCenterY - stageHeight * 0.75) == container) //top
                return true;
            if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY - stageHeight * 0.75) == container) //top right
                return true;
            if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY) == container) //center left
                return true;
            if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY) == container) //center right
                return true;
            if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY + stageHeight * 0.75) == container) //bottom left
                return true;
            if (hasBackground(stageCenterX, stageCenterY + stageHeight * 0.75) == container) //bottom center
                return true;
            if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY + stageHeight * 0.75) == container) //bottom right
                return true;
            return false;
        }

        //dispatched when all assets have finished downloading
        private final function assetsComplete(event:Event):void
        {
            loadAssets();
        }

        //loads the assets
        private final function loadAssets():void
        {
            assetsBitmapData.push(bulkLoader.getBitmapData("background/1.gif")); //stars simple
            assetsBitmapData.push(bulkLoader.getBitmapData("background/2.png")); //star bright
            assetsBitmapData.push(bulkLoader.getBitmapData("background/3.png")); //cloud white
            assetsBitmapData.push(bulkLoader.getBitmapData("background/4.png")); //cloud red
            init();
        }

        //initializes startup background containers
        private final function init():void
        {
            masterContainer = new Sprite(); //create master container

            //generate default background container
            containers.push(genSquareBg()); //top left
            containers[0].x = 0;
            containers[0].y = 0;
            masterContainer.addChild(containers[0]);

            //display the master container
            masterContainer.x = -(stageWidth / 2);
            masterContainer.y = -(stageHeight / 2);
            masterContainer.cacheAsBitmap = true;
            addChild(masterContainer);
        }       

        //generates a background square
        private static function genSquareBg():Sprite
        {
            var width:Number = stageWidth * 2;
            var height:Number = stageHeight * 2;
            var startX:Number = 0;
            var startY:Number = 0;

            var scale:Number;
            var drawAmount:uint;
            var tmpBitmap:Bitmap;
            var i:uint;

            //create container
            var container:Sprite = new Sprite();

            //show simple stars background
            tmpBitmap = UtilLib.copyDataToBitmap(assetsBitmapData[0], false, 0x000000);
            tmpBitmap.x = startX;
            tmpBitmap.y = startY;
            container.addChild(tmpBitmap);

            //draw bright stars
            drawAmount = UtilLib.getRandomInt(1, 2);
            for(i = 1; i <= drawAmount; i++)
            {
                tmpBitmap = UtilLib.copyDataToBitmap(assetsBitmapData[1], true, 0x000000);
                tmpBitmap.alpha = UtilLib.getRandomInt(3, 7) / 10;
                tmpBitmap.rotation = UtilLib.getRandomInt(0, 360);
                scale = UtilLib.getRandomInt(3, 10) / 10;
                tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
                tmpBitmap.x = UtilLib.getRandomInt(startX + tmpBitmap.width, width - tmpBitmap.width);
                tmpBitmap.y = UtilLib.getRandomInt(startY + tmpBitmap.height, height - tmpBitmap.height);
                container.addChild(tmpBitmap);
            }

            //draw white clouds
            drawAmount = UtilLib.getRandomInt(2, 4);
            for(i = 1; i <= drawAmount; i++)
            {
                tmpBitmap = UtilLib.copyDataToBitmap(assetsBitmapData[2], true, 0x000000);
                tmpBitmap.alpha = UtilLib.getRandomInt(3, 10) / 10;
                scale = UtilLib.getRandomInt(15, 40);
                tmpBitmap.scaleX = scale / 10;
                tmpBitmap.scaleY = UtilLib.getRandomInt(scale / 1.5, scale * 1.5) / 10;
                tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
                tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
                container.addChild(tmpBitmap);
            }

            //draw red clouds
            drawAmount = UtilLib.getRandomInt(0, 2);
            for(i = 1; i <= drawAmount; i++)
            {
                tmpBitmap = UtilLib.copyDataToBitmap(assetsBitmapData[3], true, 0x000000);
                tmpBitmap.alpha = UtilLib.getRandomInt(3, 10) / 10;
                scale = UtilLib.getRandomInt(5, 40) / 10;
                tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
                tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
                tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
                container.addChild(tmpBitmap);
            }

            //convert all layers to a single bitmap layer and return
            return container;
        }
    }
}

UtilLib.as copyDataToBitmap函数:

//copies bitmap data and returns a new bitmap
        public static function copyDataToBitmap(bitmapData:BitmapData, transparency:Boolean = false, flatBackground:uint = 0x000000):Bitmap
        {
            var width:Number = bitmapData.width;
            var height:Number = bitmapData.height;
            var tmpBitmapData:BitmapData = new BitmapData(width, height, transparency, flatBackground);
            tmpBitmapData.copyPixels(bitmapData, new Rectangle(0, 0, width, height), new Point(0, 0));
            return new Bitmap(tmpBitmapData);
        }

使用过的背景图片大约都是30千字节,但有些非常大: alt text http://feedpostal.com/client/assets/background/1.gif alt text http://feedpostal.com/client/assets/background/2.png alt text http://feedpostal.com/client/assets/background/3.png alt text http://feedpostal.com/client/assets/background/4.png

我曾经将所有容器层转换为1个平面位图,但这似乎会降低性能。内存使用量保持不变。

问题在于,当我在Flex 3中分析应用程序时,内存使用量从20MB(即1个容器)开始,当我开始移动时,正在加载更多容器,而旧的容器被设置为null垃圾收集器没有立即从内存中删除它们导致600MB内存峰值,之后它会回到20MB并重新启动。请注意,虽然我有8GB RAM和64位操作系统,但闪存可能会根据你的内存以不同的间隔运行垃圾收集器?

如果没有,我真的很感激如何减少它使用的内存。图像已经非常优化(使用Photoshop)。

4 个答案:

答案 0 :(得分:4)

GC是一项繁重的操作,因此Flash Player不会经常执行此操作。如果你已经确定BitmapData能够成为GC,那么就让它成为。 FP将找到合适的GC时间。

实际上不建议手动控制或尝试控制GC,因为您永远无法在每个CPU / RAM组合上测试您自己的GC方法的性能结果(结果可能非常不同)。


<强>更新

顺便说一句,你可以试试对象池,它将:

  • 增加最低RAM使用量
  • 降低RAM使用率的峰值
  • 降低GC频率(因此提高性能)

有关对象池的更多信息和开源类,请参阅herethere

答案 1 :(得分:4)

我可以提出其他答案中没有涉及的几点。

关于GC:导致GC的原因的确切触发器(我相信)没有公开发布,但我确实知道在某些环境中它们会自然地出现在计时器上(如果没有其他情况下触发) ,当Flash的内存使用量超过可用总内存的某个百分比时触发。因为你在评论中提到如果你强制使用GC,你的整体内存使用率不会太高,那么我的第一个答案是你应该停止担心内存使用,直到你发现它在这个或那个环境中引起问题的证据。一般来说,如果认为存储器有足够的内存,那么Flash会避免使用GC,因为如果你看到使用率攀升但不会影响系统性能,那么试图“修复”这听起来像是过早优化的可疑 - 你的时间可能就是更好地在别处度过。

关于你的代码:我只是半信半疑你在这里做什么,但有一件事我注意到你似乎没有在任何一个上调用dispose()方法你的bitmapData对象。如果没有必要忽略这一点,但是如果你要自动收集任何BMD,请确保你正在处理它们。

关于架构:如果您尝试使用“一个大位图”方法来解决此问题,我认为您会看到更低的内存使用率(可能会有更好的性能)。也就是说,你保留一个大的(屏幕大小的)位图,每个帧都将它空白并使用copyPixels()复制任何贴花(叠加)需要在任何位置。这比为游戏对象使用displayObjects(如sprite)要快,特别是当它们最初是位图时,不需要旋转等等(这似乎适用于你的情况)。

this question about the performance of using display objects vs. using a bitmap framebuffer比较。提问者最终发现使用帧缓冲更快,并且作为额外的奖励它应该有更多可预测的内存使用,因为你不会创建和销毁位图或显示对象。 (您创建和销毁的所有内容都是用于跟踪贴图所在位置的数据。)

答案 2 :(得分:2)

有一个强制垃圾收集器的技巧

 try {
    new LocalConnection().connect('foo');
    new LocalConnection().connect('foo');
 } catch (e:*) {}
 // the GC will perform a full mark/sweep on the second call.

full article why this works

新的flash播放器在flash.system.System类中有一个静态gc()方法。但这仅适用于调试版

答案 3 :(得分:1)

不要担心内存消耗。所有这些图形都会占用大量空间,无论实际文件有多小,它们都会在加载时解压缩。

这意味着32位的500x500图像将占用500x500x32 = 7.6mb的内存。所以是的,你会经历很多记忆。

问题是编写垃圾收集器和内存管理的人比我们聪明。他们比我们更了解flash播放器的复杂性,所以请相信他们更好的判断。

Flash会自然地使用尽可能多的内存,毕竟它是内存的所有内容,如果它一直是免费的,它并没有太多用处。因此,拥有8GB内存将使GC运行频率降低。