【问题标题】:Run-time loading of external assets and re-using preloaded assets in actionscript 3?运行时加载外部资产并在 actionscript 3 中重新使用预加载的资产?
【发布时间】:2009-08-11 21:55:27
【问题描述】:

我正在创建一个 2d Flash 游戏(在 flex/actionscript 3 中编码),在需要时下载资源。目前我有这样的设置:

AssetLoader.as

package
{
    import flash.display.Loader;
    import flash.net.URLRequest;

    public class AssetLoader extends Loader
    {
        //set vars
        private var url:String = "http://test.com/client/assets/";

        public function AssetLoader(url:String)
        {
            Logger.log("AssetLoader request: " + this.url + url);
            var request:URLRequest = new URLRequest(this.url + url);
            this.load(request);
        }
    }
}

然后,在我要加载资产的地方,我执行以下操作:

var asset:AssetLoader = new AssetLoader("ships/" + graphicId + ".gif");
asset.contentLoaderInfo.addEventListener(Event.COMPLETE, onShipAssetComplete, false, 0, true);

private function onShipAssetComplete(event:Event):void
{
    var loader:Loader = Loader(event.target.loader);
        shipImage = Bitmap(loader.content);
        shipImage.smoothing = true;
        addChild(shipImage);
}

问题是,此方法不会检查已下载的资源,因此它会在第二次请求相同资源时重新下载它们(我认为)。

所以,我需要一个存储所有下载资产的数组,并根据请求检查该资产的名称是否存在于数组中。因此,如果它已经下载,则必须返回内存中的资产,而不是重新下载。

我可以让assetloader成为一个静态类,但是我必须等待事件在图像下载完成后触发——所以我不能简单地让静态函数返回相应的图像。知道我应该怎么做吗?

编辑在 cmets 之后尝试:

package
{
    import flash.display.Loader;
    import flash.events.Event;
    import flash.net.URLRequest;

    public final class AssetManager
    {
        private static var assets:Object = {};
        private static var preUrl:String = Settings.ASSETS_PRE_URL;

        public static function load(postUrl:String):*
        {
            if (assets[postUrl])
            { //when the asset already exists
                //continue
            }
            else
            { //the asset still has to be downloaded
                var request:URLRequest = new URLRequest(preUrl + postUrl);
                var loader:Loader = new Loader();
                loader.load(request);
                loader.contentLoaderInfo.addEventListener(Event.COMPLETE, 
                function(event:Event):void
                {
                    var loader:Loader = Loader(event.target.loader);
                    assets[postUrl] = loader.content;
                }, false, 0, true); 
            }
        }
    }
}

EDIT2:再次尝试

package
{
    import flash.display.Loader;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.net.URLRequest;

    public final class AssetManager
    {
        private static var assets:Object = {};
        private static var preUrl:String = Settings.ASSETS_PRE_URL;

        public static function load(postUrl:String):*
        {
            if (assets[postUrl])
            { //the asset already exists
                var dispatcher:EventDispatcher = new EventDispatcher();
                dispatcher.dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, assets[postUrl]));
            }
            else
            { //the asset still has to be downloaded
                var request:URLRequest = new URLRequest(preUrl + postUrl);
                var loader:Loader = new Loader();
                loader.load(request);
                loader.contentLoaderInfo.addEventListener(Event.COMPLETE, 
                function(event:Event):void
                {
                    var loader:Loader = Loader(event.target.loader);
                    assets[postUrl] = loader.content;
                    var dispatcher:EventDispatcher = new EventDispatcher();
                    dispatcher.dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, assets[postUrl]));
                }, false, 0, true); 
            }
        }
    }
}

然后,我尝试以下操作:

var asset:AssetManager = AssetManager.load("ships/" + graphicId + ".gif");
            asset.addEventListener(CustomEvent.LOAD_COMPLETE, onShipAssetComplete, false, 0, true);

但是得到一个错误,“未定义的方法 addEventListener by a reference of type static AssetManager”(粗略翻译)。

【问题讨论】:

    标签: actionscript-3 flex3 asset-management


    【解决方案1】:

    您可以在 AssetLoader 类中添加一个静态对象(用作字典,其中资产的 url 作为键,资产的内容作为值),同时继续以正确的方式使用该类现在。

    private static var assets:Object = {};
    

    不同之处在于,如果之前已经请求过内容的 URL,则您的类需要检查该静态对象。如果有,立即分派完成事件。如果没有,请按照正常程序进行操作,不要忘记使用新加载的资源填充您的静态对象。



    更新:

    这是我的意思的一个简单示例。我没有时间对此进行测试,但它应该可以工作。

    注意: 您必须调用您创建的 AssetLoader 实例的 loadAsset() 方法才能实际加载资产。这与我们扩展的 Loader 类的工作方式是一致的。

    在调用 loadAsset() 方法之前,您应该始终添加所有事件侦听器。在您的问题中,您从构造函数中调用 load() 方法,然后才为 Event.COMPLETE 添加事件侦听器。这可能会产生奇怪的结果。

    代码如下:

    package
    {
      import flash.display.Loader;
      import flash.events.Event;
      import flash.net.URLRequest;
    
    
      public class AssetLoader extends Loader
      {
        private static const BASE_URL:String = 'http://test.com/client/assets/';
    
        public static var storedAssets:Object = {};
    
        private var assetURL:String;
        private var urlRequest:URLRequest;
        private var cached:Boolean = false;
    
    
        public function AssetLoader(url:String):void
        {
          trace('Loading: ' + url);
          assetURL = url;
    
          if (storedAssets[assetURL] != null)
          {
            cached = true;
            trace('Cached');
          }
          else
          {
            trace('Loading uncached asset');
            urlRequest = new URLRequest(BASE_URL + assetURL);
            contentLoaderInfo.addEventListener(Event.COMPLETE, OnAssetLoadComplete);
          }
        }
    
        public function loadAsset():void
        {
          if (cached)
            loadBytes(storedAssets[assetURL]);
          else
            load(urlRequest);
        }
    
        private function OnAssetLoadComplete(event:Event):void
        {
          storedAssets[assetURL] = contentLoaderInfo.bytes;
          trace('Loaded ' + contentLoaderInfo.bytesLoaded + ' bytes');
        }
    
      }
    
    }
    


    更新 2:

    下面是如何使用上面的类:

    var assetLdr:AssetLoader = new AssetLoader("ships/" + graphicId + ".gif");
    assetLdr.contentLoaderInfo.addEventListener(Event.COMPLETE, onShipAssetComplete);
    assetLdr.loadAsset();
    
    private function onShipAssetComplete(event:Event):void
    {
        var shipImage:Bitmap = Bitmap(event.target.loader.content);
        // Do stuff with shipImage
    }
    

    【讨论】:

    • Lior 可能建议您有一个包含资源 url -> 加载资源的地图。类似assets[url] = loadedContent
    • 啊——我明白了。加载时使用闭包。我会用一个例子写一个答案。
    • 我已经修改了代码来解决这两个问题。使用此类加载资产时,请使用 loadAsset() 而不是 load()。详情请查看代码。
    • 再次更新了代码。添加了几个 cmets 以尽可能澄清问题,并提供了如何使用该类的示例。
    • 没有时间彻底测试这个,但这不应该发生。静态变量属于定义它们的类的上下文,而不是实例。可能存在范围界定问题,但不应该是这种情况。我会仔细研究一下这个tmw。凌晨 3 点,该睡觉了 :)
    【解决方案2】:

    也许你应该看看Bulk Loader。它会做你想做的事情。如果您真的想使用自定义解决方案,这将是一个很好的参考点,但为什么要重新发明轮子呢?

    泰勒。

    【讨论】:

    • 嗯,我想要的功能似乎有点太多了。它可能会导致额外的资源使用和负载。
    • 对于我们的游戏开发人员,我们构建了一个非静态加载器,可以在关卡加载时预加载每个关卡的所有图形。因为我们知道所有的加载都发生在那里,所以我们知道当我们访问它们时这些资产是可用的。我们只需将所有 URL 存储在一个数组中,然后一次加载一个直到全部完成,然后调度一个完整的事件。我们还实现了资产“老化”,因此如果资产在 X 级加载中未使用,我们会通过它来节省内存资源。
    • 不幸的是,我需要在运行时加载大多数资产,所以这不适合我。此外,您推荐的课程似乎是为批量加载而设计的,这并不是我真正想要的。不过还是谢谢。
    【解决方案3】:

    这是您的加载命令的更改以捕获 resourceId

    public function load(postUrl:String):*
    {
        var index:int;
        if ((index = assetExists(postUrl)) != -1)
        {
            dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, asset[postUrl]));
        }
        else
        { 
            //the asset still has to be downloaded
            var request:URLRequest = new URLRequest(preUrl + postUrl);
            var loader:Loader = new Loader();
            loader.load(request);
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, 
            function(event:Event)
            {
                // NOTE: not sure on scoping here ...
                // pretty sure you're this will be the Loader
                // if not just read it off the event like you were before
                assets[postUrl] = content;
                dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, asset[postUrl]));
            }, false, 0, true);
        }
    }
    
    /* In a new file */
    public class CustomEvent extends Event
    {
        public static const LOAD_COMPLETE:String = "CustomEvent_LoadComplete";
    
        // If you know the type you should use it (e.g. Sprite/DisplayObject)
        public var content:*;
    
        public function CustomEvent(type:String, _content:*)
        {
            content = _content;
            super(type);
        }
    }
    

    注意:当您编写 Event 后代时,您还应该覆盖 toStringclone 方法。我也欺骗了构造函数,因为你可能想通过weakReferences之类的东西。

    【讨论】:

    • 谢谢,更新了第一篇文章。现在我不确定如何继续。认为我需要触发资产可获得的事件(在两种情况下)。但是如何使用静态公共类来做到这一点?
    • 你继承自 Loader。 load 方法不需要是静态的。 Loader 继承自 EventDispatcher,因此它可以调度事件。我建议为加载的内容创建一个带有属性的自定义事件并调度它。 dispatchEvent(new CustomLoadEvent(Event.COMPLETE, content))
    • 抱歉,什么是 CustomLoadEvent?我不知道那个物体。我知道 Event 对象,但如何将内容传递给它?如果这不是太多的要求,你能举个例子吗?谢谢。
    • 更新了我的回复以展示如何编写自定义事件。您可以通过子类 Event 来制作自己的事件。
    • 谢谢,得到“未定义的方法 dispatchEvent”,即使使用“import flash.events.*;”。有什么想法吗?
    猜你喜欢
    • 1970-01-01
    • 2014-03-14
    • 2011-11-23
    • 1970-01-01
    • 2016-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多