我正在动作脚本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)。
答案 0 :(得分:4)
GC是一项繁重的操作,因此Flash Player不会经常执行此操作。如果你已经确定BitmapData能够成为GC,那么就让它成为。 FP将找到合适的GC时间。
实际上不建议手动控制或尝试控制GC,因为您永远无法在每个CPU / RAM组合上测试您自己的GC方法的性能结果(结果可能非常不同)。
<强>更新强>
顺便说一句,你可以试试对象池,它将:答案 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.
新的flash播放器在flash.system.System类中有一个静态gc()方法。但这仅适用于调试版
答案 3 :(得分:1)
不要担心内存消耗。所有这些图形都会占用大量空间,无论实际文件有多小,它们都会在加载时解压缩。
这意味着32位的500x500图像将占用500x500x32 = 7.6mb的内存。所以是的,你会经历很多记忆。
问题是编写垃圾收集器和内存管理的人比我们聪明。他们比我们更了解flash播放器的复杂性,所以请相信他们更好的判断。
Flash会自然地使用尽可能多的内存,毕竟它是内存的所有内容,如果它一直是免费的,它并没有太多用处。因此,拥有8GB内存将使GC运行频率降低。