How to share tileSets between different tiledMaps? - libgdx

If I have several .tmx files using the same tileSet, obviously I'd like to load the tileSet texture only once, but if I use the regular way to do it, the texture is loaded twice...
TmxMapLoader loader = new TmxMapLoader();
TiledMap tiledMap1 = loader.load("map-test.tmx");
TiledMap tiledMap2 = loader.load("map-test.tmx");
Texture texture1 = tiledMap1.getTileSets().getTile(1).getTextureRegion().getTexture();
Texture texture2 = tiledMap2.getTileSets().getTile(1).getTextureRegion().getTexture();
// texture1 is different than texture2
So my question is, is there any way to avoid the map loading the same assets several times?
Probably I'll end writing my own TmxLoader because I don't want it to load images from the Image layer but replace them with actual game objects... but I'd like to know the vanilla way...
Edit:
The solution provided by David Saltares was the one I needed, so I'll left here the proper code:
// supossing that both maps use the same tileset image...
TmxMapLoader loader = new TmxMapLoader();
assetManager.setLoader(TiledMap.class, loader);
assetManager.load("map-test1.tmx", TiledMap.class);
assetManager.load("map-test2.tmx", TiledMap.class);
assetManager.finishLoading();
TiledMap tiledMap1 = assetManager.get("map-test1.tmx");
TiledMap tiledMap2 = assetManager.get("map-test2.tmx");
Texture texture1 = tiledMap1.getTileSets().getTile(1).getTextureRegion().getTexture();
Texture texture2 = tiledMap2.getTileSets().getTile(1).getTextureRegion().getTexture();
// now texture1 == texture2 :)
What I'm wondering is, why this is not the default? I mean, assetManager has lots of loaders by default, but not the tmx one...

Use AssetManager, call its setLoader method passing a new instance of TmxMapLoader.
When loading a map via the asset manager, the tmx loader will try to handle it and tell the asset manager all its dependencies. One of these dependencies will be the texture for the tiles. The asset manager will satisfy the dependencies and then the map will actually get loaded.
All assets under the manager are referenced counted, so calling load() on the same handle, won't actually allocate more memory.
This means that, when the second map gets loaded and the manager tries to satisfy its dependencies, it will find the texture is already loaded. It will simply increment its reference count by one.

Related

Actionscript 3, reading two files then triggering a function, best practice

I need to read a part of two files, and once both parts of both files are loaded to trigger a function that does some work.
What is the best way to approach this problem.
I am currently triggering the second file load once the first is loaded, however it seems poor coding style, I think this because my OO code starts looking procedural.
EDIT: So its an Air based app so using filestream.
In the end I found I actually needed to read each file in a different way. Because I need the same part of both files, and I dont know the file size, I need to read one file first and once I have fileStream.bytesAvailable and position, I can then look for the same data from the second file. I found I must handle files smaller than my read size and the end of files beyond multiples of my read size.
You don't specified what file and from where you wont to load the file but you can actually load multiples files in parallel.
If you want to read only part of file from local machine you can use AIR's FileStream class - very easy and you don't have to load whole few hundreds MB file:
import flash.filesystem.*;
var file:File = File.documentsDirectory;
file = file.resolvePath("Apollo Test/test.txt");
var fileStream:FileStream = new FileStream();
fileStream.open(file, FileMode.READ);
var str:String = fileStream.readMultiByte(file.size, File.systemCharset);
trace(str);
fileStream.close();
Another option is to use URLStream and listen for ProgressEvent.PROGRESSevents to read data of partially loaded file.
You may also want to see NetStream class which is used to stream video.
there is many options, using File, FileStream is only available on air applications.
The File class extends the FileReference class. The FileReference
class, which is available in Flash® Player as well as Adobe® AIR®,
represents a pointer to a file, but the File class adds properties and
methods that are not exposed in Flash Player (in a SWF running in a
browser), due to security considerations.
as noted above, if you are creating a non-AIR application, FileReference should be used instead of FileStream and File classes, as you dont tagged AIR in your question.
FileReference does not provide any open("path") to you (due to security considerations), but a browse method will be available and ask's your client's for selecting a file. here is an example, which also explain how to trigger a function when opening is done:
var filereference:FileReference = new FileReference();
filereference.addEventListener(Event.SELECT, onFileSelected);
var text_files:FileFilter = new FileFilter("Text Files","*.txt; *.html;*.htm;*.php");
var all_files:FileFilter = new FileFilter("All Files (*.*)","*.*");
filereference.browse([text_files, all_files]);
// triggered when a file is selected by user
function onFileSelected(e:Event):void {
filereference.removeEventListener(Event.SELECT, onFileSelected);
filereference.addEventListener(Event.COMPLETE, onFileLoaded);
filereference.load();
}
// triggered when file loading is complete
function onFileSelected(e:Event):void {
var data:ByteArray = fileReference["data"];
filereference.removeEventListener(Event.COMPLETE, onFileSelected);
}
two more events to be listened for suddenly error's occurred and displaying a progress bar for loading progress (its sync):
IOErrorEvent.IO_ERROR and ProgressEvent.PROGRESS

Do I have to always have an .atlas file when making a skin?

I'm currently trying to build a simple loading screen for my game and am trying to get a skin for a font.
When I try to get the the skin though, with skin = game.manager.get("bin/ui/loading.json", Skin.class);, this error occurs:
Exception in thread "LWJGL Application" com.badlogic.gdx.utils.GdxRuntimeException: com.badlogic.gdx.utils.GdxRuntimeException: File not found: bin\ui\loadingSkin.atlas (Internal)
at com.badlogic.gdx.assets.AssetManager.handleTaskError(AssetManager.java:540)
at com.badlogic.gdx.assets.AssetManager.update(AssetManager.java:356)
at com.badlogic.gdx.assets.AssetManager.finishLoading(AssetManager.java:377)
at com.Sidescroll.game.LoadingScreen.show(LoadingScreen.java:32)
at com.badlogic.gdx.Game.setScreen(Game.java:61)
at com.Sidescroll.game.Sidescroll.create(Sidescroll.java:20)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:143)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:120)
Caused by: com.badlogic.gdx.utils.GdxRuntimeException: File not found: bin\ui\loadingSkin.atlas (Internal)
at com.badlogic.gdx.files.FileHandle.read(FileHandle.java:136)
at com.badlogic.gdx.graphics.g2d.TextureAtlas$TextureAtlasData.<init>(TextureAtlas.java:103)
at com.badlogic.gdx.assets.loaders.TextureAtlasLoader.getDependencies(TextureAtlasLoader.java:58)
at com.badlogic.gdx.assets.loaders.TextureAtlasLoader.getDependencies(TextureAtlasLoader.java:34)
at com.badlogic.gdx.assets.AssetLoadingTask.handleSyncLoader(AssetLoadingTask.java:98)
at com.badlogic.gdx.assets.AssetLoadingTask.update(AssetLoadingTask.java:87)
at com.badlogic.gdx.assets.AssetManager.updateTask(AssetManager.java:477)
at com.badlogic.gdx.assets.AssetManager.update(AssetManager.java:354)
... 6 more
In my current assets/ui folder are 5 files:
loading_0.png - BitmapFont image
loading.fnt - Font file
loading.pack - used TexturePacker to pack the single image
loading.png - TexturePacker image
loadingSkin.json - Skin.json, where BitmapFont and LabelStyle is described
The part where Im trying to use assetmanager:
//this is the beginning of the show method of my loadingScreen, nothing
//happened before
game.manager.load("bin/ui/loading.pack", TextureAtlas.class);
game.manager.finishLoading();
atlas = game.manager.get("bin/ui/loading.pack");
// do i need an atlas here ?
game.manager.load("bin/ui/loadingSkin.json", Skin.class);
game.manager.finishLoading();
skin = game.manager.get("bin/ui/loading.json", Skin.class);
Question is, do I always need an atlas? if yes, how do I create one, if not, why does the error occur?
EDIT: I'm using release 1.5.4 of libgdx and do not have android in my project
the Skin has many various contructors like:
Skin()
Skin(FileHandle skinFile)
Skin(FileHandle skinFile, TextureAtlas atlas)
Skin(TextureAtlas atlas)
If you are using the JSON version of constructor you are just passing the json configuration file to it. Skin needs to have some texture atlas so with this option by default it is looking for the .atlas file named exatcly like json file. It means that when you are using file name loadingSkin.json:
game.manager.load("bin/ui/loadingSkin.json", Skin.class);
it will look for the loadingSkin.atlas file and because there is nothing like this it causes the error.
Now you have two option:
create file named like your json but with atlas extension (.atlas is exactly like .pack - you can just change extension or create a file with .atlas instead of .pack in TexturePacker)
loading.pack -> loading.atlas
When changing names be aware of you texture .png file! (The .pack/.atlas file has its name inside - check with editor)
use another constructor of skin like:
Skin(FileHandle skinFile, TextureAtlas atlas)
You are passing filehandle to .json and TextureAtlas instance of the skin texture
TextureAtlas atlas = game.manager.get("bin/ui/loading.pack", TextureAtlas.class);
Skin skin = new Skin( Gdx.files.internal("bin/ui/loadingSkin.json"), atlas);
This line causes the error:
game.manager.load("bin/ui/loadingSkin.json", Skin.class);
When I looked into SkinLoader class' source I've seen that it loads the file by appending .atlas by default:
#Override
public Skin loadSync (AssetManager manager, String fileName, FileHandle file, SkinParameter parameter) {
String textureAtlasPath = file.pathWithoutExtension() + ".atlas";
...
So it seems like you need a TextureAtlas for your skin. You can create TextureAtlas by using https://libgdx-texturepacker-gui.googlecode.com/files/gdx-texturepacker-3.2.0.zip
You can create a TextureAtlas named loadingSkin.atlas and put it under bin\ui\loadingSkin.atlas. If you want to create an atlas with another name and tell the loader which TextureAtlas to use, you can pass SkinParameter to your load method:
SkinLoader.SkinParameter loadingSkinParameter = new SkinLoader.SkinParameter();
loadingSkinParameter.textureAtlasPath = "YOUR_TEXTURE_ATLAS_PATH";
game.manager.load("bin/ui/loadingSkin.json", Skin.class, loadingSkinParameter);

Embedded image assets and the memory consumption

I have a few contradicting misconceptions about how Flash handles image assets. I'm going to write them down here, please correct me where I'm wrong.
I have an embedded image
[Embed(source = "../../lib/images/myImage.png")]
public var embeddedImage:Class;
How do I use it in my app without using unreasonable amounts of operating memory?
I was going to do this:
var bData:BitmapData = (new embeddedImage() as Bitmap).bitmapData;
And from that point on use bData if I ever want to display this picture anywhere, ensuring that there is only one bitmapdata per image.
But what worries me is that embeddedImage still stays, doesn't it? There are basically two copies of this image in the operating memory, right?
What should I do instead? May be I should instantiate new embeddedImage() each time I want to use it? But that looks even worse, now I'm clearly instantiating a bunch of unneeded copies.
If I loaded the image using the Loader class I would end up with only one copy of the bitmapdata if I wanted to, provided GC did a good job clearing the loader after it was done.
The [Embed] block embeds the image into the application. This doesn't use RAM*, it increases the size of the application itself (the .swf or whatever format you export to).
When you use new EmbeddedImage(), you create an instance of the embedded image. This instance is what consumes RAM. Each instance of that image will consume additional RAM.
When you access the bitmapData property of that embedded image, it does not create a new instance or consume additional RAM, it refers to existing data which is already consuming RAM.
Because the BitmapData is the data which represents an image, it is obviously the largest. Knowing this, we look at ways to use a single instance of BitmapData for any graphics we want to display. Depending on the display architecture you've opted to use, there are different ways to go about referring to that BitmapData when drawing multiple instances of the same image.
As an example, we have your instance of EmbeddedImage created:
var embeddedImage:EmbeddedImage = new EmbeddedImage();
And we have a 'canvas', which is a single Bitmap added to the stage:
var canvas:Bitmap = new Bitmap();
canvas.bitmapData = new BitmapData(500, 500, false, 0xFFFFFF);
addChild(canvas);
Using the method copyPixels(), we can copy across a section of data from the embeddedImage, to the canvas without needing to make another instance of BitmapData, which would consume RAM equivalent to the source for each time we want to draw the graphics. Example:
var drawPosition:Point = new Point();
var sourceRect:Rectangle = embeddedImage.bitmapData.rect;
// Draw the embeddedImage 50 times.
canvas.bitmapData.lock();
for(var i:int = 0; i < 50; i++)
{
canvas.bitmapData.copyPixels(embeddedImage.bitmapData, sourceRect, drawPosition);
drawPosition.x += 10;
}
canvas.bitmapData.unlock();

Loader.as returning blank bitmap

I have an application which pulls in Bitmap resources from a server - currently I use the Loader class to do this, then, once they're loaded, generate a BitmapData based on the loader dimensions and draw the instance of Loader directly to it (the BitmapData is used for Away3D textures as well as Bitmap instances, so I have no need for the Loader once fetched).
This has always worked for me, but recently I started getting 0x0 Loaders, and invalid BitmapData as a result.
I stopped doing this:
this.imageBitmap = new BitmapData(this.imageLoader.width, this.imageLoader.height, true, 0);
..and started doing this:
this.imageBitmap = new BitmapData(event.target.content.width, event.target.content.height, true, 0);
Where event is the Event.COMPLETE event fired by the loader. This fixed the dimension problem, but the BitmapData is just a plain white bitmap (and it's set to transparent by default, so this is being drawn into it). Frustratingly, this doesn't happen every time, if I refresh the application it works as it should around 25% of the time, otherwise it plays up like this.
I've got a tight deadline and I'm really screwing about this, if anyone could help or suggest a better way of doing it you'd really be saving my neck!
Sounds like you need to adjust the image decoding policy for the loader - to ensure it decodes the image before COMPLETE fires - then the width and height etc should be reliable.
To do it, just add a suitable LoaderContext object to the Loader.load method:
var loaderContext:LoaderContext = new LoaderContext();
//set decode policy
loaderContext.imageDecodingPolicy = ImageDecodingPolicy.ON_LOAD;
//load image
loader.load(yourUrl, loaderContext);
The default decode policy is ImageDecodingPolicy.ON_DEMAND - which doesnt decode the image until it is actually required.
Lang docs: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/ImageDecodingPolicy.html
Fixed it, stupid oversight and bit of an obscure situation but I'll leave an answer in case anyone runs into something similar.
My loader is contained in an Asset class - when another object requires the internal bitmap, it queries this class - if the bitmap's present, it returns it, if not it loads it with a loader and registers a callback for a COMPLETE event which is fired when the Loader has loaded and transferred its contents to a BitmapData instance.
The stupid mistake I'd made was that, if several objects were querying the same (as-yet-unloaded) asset, it would start reloading the asset each time, creating a new Loader as it did so...so when the first Loader was complete, it would fire an event but no reference to it would exist, not only creating a memory leak but causing the Asset class to extract the BitmapData from the most-recently-created Loader, which was not complete! The asynchronous nature of Loader is the reason it worked sometimes, as on occasion the final Loader would be ready in time for BitmapData extraction.
Simple solution was to create an internal boolean, _isLoading, which is set to true the first time load() is called - any subsequent calls are ignored if it's true, but callbacks still registered, works a treat!

Loader.load and Loader.loadBytes differences

I'm loading as2 swf into AIR application. It works properly when loaded from file. But when loaded from bytes, it is broken in some way (it reacts to mouse, but some elements are inactive)
var bytes:ByteArray = ... //loaded from resources
var loader:Loader = new Loader();
var context:LoaderContext = new LoaderContext(false);
context.allowCodeImport = true; //this is neccessary
// Method 1 - blocks some scripts in loaded SWF
//context.applicationDomain = new ApplicationDomain();
// (application domain has no effect with as2 swf)
//context.securityDomain = SecurityDomain.currentDomain; //gives error 2114: securityDomain must be null
loader.loadBytes(bytes, context);
// Method 2 - loads properly
//loader.load(new URLRequest(file.url));
So why not just load it from file? My resources are protected with encryption and I can't dump them to disk - they must still be protected.
What tricks may exist to load from bytes properly?
There is similar question, but in my case as2 causes more problems.
AS2 and AS3 use different runtimes (bytecode is different) so you won't be able to properly execute any AS2 bytecode in the AS3 runtime. you are basically injecting AS2 code into your AS3 application, so it ain't gonna work :/
According the the documentation for LoaderContext you should only use the applicationDomain property only when loading ActionScript 3.0 SWFs. Try dropping that parameter (or setting it to null) and see what happens.
It seems that old SWF movies (AS1 and AS2, which require AVM1) loaded into an AIR app with load get put in their own domains, but those loaded with loadBytes share a domain. So if you have multiple AVM1 SWFs loaded with loadBytes their _global properties will clobber each other. This affects the Flash MX UI components (ca. 2002).
I can't be the only one trying to package ancient Flash files in AIR apps, so I figure this info may be useful to someone.