diff --git a/bin/hxswfml.n b/bin/hxswfml.n
new file mode 100644
index 00000000..398b0a61
Binary files /dev/null and b/bin/hxswfml.n differ
diff --git a/command/data/scaffold/flambe.yaml b/command/data/scaffold/flambe.yaml
index c3094938..ae0cbaec 100644
--- a/command/data/scaffold/flambe.yaml
+++ b/command/data/scaffold/flambe.yaml
@@ -23,6 +23,12 @@ default_platform: flash
orientation: portrait
fullscreen: true
+# Whether to embed assets in to SWF's. If not, assets will be loaded over HTTP.
+embed_assets: false
+
+width: 640
+height: 480
+
# Additional flags to pass to the Haxe compiler.
# haxe_flags: -lib nape -D foobar
diff --git a/command/index.js b/command/index.js
index cdbee2a8..d59dc25c 100644
--- a/command/index.js
+++ b/command/index.js
@@ -10,8 +10,10 @@ var os = require("os");
var path = require("path");
var spawn = require("child_process").spawn;
var wrench = require("wrench");
+var xmldom = require("xmldom");
var DATA_DIR = __dirname + "/data/";
+var BIN_DIR = __dirname + "/../bin/";
var CACHE_DIR = "build/.cache/";
var HAXE_COMPILER_PORT = 6000;
@@ -154,18 +156,88 @@ exports.build = function (config, platforms, opts) {
return Q();
};
- var prepareAssets = function (dest) {
+ var generateAssetXml = function (relAssetDir, hxswfmlDoc) {
+ forEachFileIn(relAssetDir, function (file) {
+ var filePath = relAssetDir + "/" + file;
+ if(fs.lstatSync(filePath).isDirectory()) {
+ generateAssetXml(filePath, hxswfmlDoc);
+ } else {
+ var dataType = "";
+ var extension = filePath.substring(filePath.lastIndexOf(".") + 1).toLowerCase();
+ switch (extension) {
+ case "png":
+ case "jpg":
+ case "jpeg":
+ case "jxr":
+ case "gif":
+ dataType = "bitmap";
+ break;
+ case "mp3":
+ dataType = "sound";
+ break;
+ default:
+ dataType = "bytearray";
+ break;
+ }
+
+ var el = hxswfmlDoc.createElement(dataType);
+ el.setAttribute("file", filePath);
+ // create a valid AS3 class name from the asset url by replacing all non digits, non word characters, and leading non-letters with dollar signs
+ var className = filePath.substring(filePath.indexOf("/") + 1, filePath.lastIndexOf(".")).replace(/[^\d|\w|\$]|^[^A-za-z]/g, '$');
+ el.setAttribute("class", className);
+ hxswfmlDoc.documentElement.appendChild(el);
+ }
+ });
+ };
+
+ var prepareEmbeddedAssetLibrary = function () {
+ var hxswfmlDoc = new xmldom.DOMParser().parseFromString("");
+ hxswfmlDoc.documentElement.setAttribute("width", get(config, "width"));
+ hxswfmlDoc.documentElement.setAttribute("height", get(config, "height"));
+ assetPaths.forEach(function (assetPath) {
+ generateAssetXml(assetPath, hxswfmlDoc);
+ });
+ hxswfmlDoc.documentElement.appendChild(hxswfmlDoc.createElement("frame"));
+
+ var xmlPath = CACHE_DIR+"swf/hxswfml_asset_lib_def.xml";
+ fs.writeFileSync(xmlPath, new xmldom.XMLSerializer().serializeToString(hxswfmlDoc));
+
+ return hxswfml(["xml2lib", xmlPath, "libs/library.swf"]);
+ };
+
+ var prepareAssets = function (dest, platform) {
+ var assetFlags = ["--macro", "flambe.platform.ManifestBuilder.use(\""+dest+"\")"];
+
wrench.rmdirSyncRecursive(dest, true);
+ if (fs.existsSync("libs/library.swf")) {
+ fs.unlinkSync("libs/library.swf");
+ }
+
// TODO(bruno): Filter out certain formats based on the platform
- return copyDirs(assetPaths, dest)
- .then(function () {
- return ["--macro", "flambe.platform.ManifestBuilder.use(\""+dest+"\")"];
+ var promise = copyDirs(assetPaths, dest);
+ if (platform == "flash" && get(config, "embed_assets")) {
+ wrench.mkdirSyncRecursive(CACHE_DIR+"swf");
+ assetFlags.push("-D", "embed_assets");
+
+ promise = promise.then(function () {
+ return prepareEmbeddedAssetLibrary()
+ });
+ }
+
+ return promise.then(function () {
+ return assetFlags;
});
};
var swfFlags = function (air) {
// Flags common to all swf-based targets (flash, android, ios)
- var flags = ["--flash-strict", "-swf-header", "640:480:60:000000"];
+ var flags = ["--flash-strict"];
+ if (!get(config, "embed_assets")) {
+ // when assets are embedded, we take the header from their swf library so that
+ // we can also absorb their timeline with -D flash-use-stage, which is incompatible with -swf-header
+ var swfHeader = get(config, "width") + ":" + get(config, "height") + ":60:000000";
+ flags.push("-swf-header", swfHeader);
+ }
if (debug) flags.push("-D", "fdb", "-D", "advanced-telemetry");
else flags.push("-D", "native_trace");
@@ -223,7 +295,7 @@ exports.build = function (config, platforms, opts) {
var outputDir = "build/web";
return prepareWeb(outputDir)
- .then(function () { return prepareAssets(outputDir+"/assets") })
+ .then(function () { return prepareAssets(outputDir+"/assets", "html") })
.then(function (assetFlags) {
console.log("Building: " + outputDir);
return buildJS({
@@ -236,29 +308,28 @@ exports.build = function (config, platforms, opts) {
var buildFlash = function () {
var swf = "build/web/targets/main-flash.swf";
- var flashFlags = swfFlags(false).concat([
- "-swf-version", SWF_VERSION, "-swf", swf]);
return prepareWeb()
- .then(function () { return prepareAssets("build/web/assets") })
+ .then(function () { return prepareAssets("build/web/assets", "flash") })
.then(function (assetFlags) {
console.log("Building: " + swf);
+ var flashFlags = swfFlags(false).concat([
+ "-swf-version", SWF_VERSION, "-swf", swf]);
return haxe(commonFlags.concat(assetFlags).concat(flashFlags));
});
};
- var buildAir = function (flags) {
+ var buildAir = function (flags, platform) {
var airFlags = swfFlags(true).concat(["-swf-version", "11.7", "-D", "air"]);
wrench.mkdirSyncRecursive(CACHE_DIR+"air");
- return prepareAssets(CACHE_DIR+"air/assets")
+ return prepareAssets(CACHE_DIR+"air/assets", platform)
.then(function (assetFlags) {
return haxe(commonFlags.concat(assetFlags).concat(airFlags).concat(flags));
});
};
var generateAirXml = function (swf, output) {
- var xmldom = require("xmldom");
var xml =
"\n" +
" "+get(config, "id")+"\n" +
@@ -349,7 +420,7 @@ exports.build = function (config, platforms, opts) {
var cert = CACHE_DIR+"air/certificate-android.p12";
var xml = CACHE_DIR+"air/config-android.xml";
- return buildAir(["-D", "android", "-swf", CACHE_DIR+"air/"+swf])
+ return buildAir(["-D", "android", "-swf", CACHE_DIR+"air/"+swf], "android")
.then(function () {
// Generate a dummy certificate if it doesn't exist
if (!fs.existsSync(cert)) {
@@ -387,7 +458,7 @@ exports.build = function (config, platforms, opts) {
var mobileProvision = "certs/ios.mobileprovision";
var xml = CACHE_DIR+"air/config-ios.xml";
- return buildAir(["-D", "ios", "-D", "no-flash-override", "-swf", CACHE_DIR+"air/"+swf])
+ return buildAir(["-D", "ios", "-D", "no-flash-override", "-swf", CACHE_DIR+"air/"+swf], "ios")
.then(function () {
var pathOptions = generateAirXml(swf, xml);
@@ -414,7 +485,7 @@ exports.build = function (config, platforms, opts) {
var outputDir = "build/firefox";
wrench.mkdirSyncRecursive(outputDir+"/targets");
- return prepareAssets(outputDir+"/assets")
+ return prepareAssets(outputDir+"/assets", "firefox")
.then(function (assetFlags) {
console.log("Building: " + outputDir);
return buildJS({
@@ -563,6 +634,11 @@ var haxelib = function (flags, opts) {
};
exports.haxelib = haxelib;
+var hxswfml = function (flags, opts) {
+ return exec("neko " + BIN_DIR + "hxswfml.n", flags, opts);
+};
+exports.hxswfml = hxswfml;
+
var adt = function (flags, opts) {
return exec("adt", flags, opts);
};
diff --git a/src/flambe/platform/flash/FlashEmbeddedAssetPackLoader.hx b/src/flambe/platform/flash/FlashEmbeddedAssetPackLoader.hx
new file mode 100644
index 00000000..bc4c2848
--- /dev/null
+++ b/src/flambe/platform/flash/FlashEmbeddedAssetPackLoader.hx
@@ -0,0 +1,78 @@
+//
+// Flambe - Rapid game development
+// https://github.com/aduros/flambe/blob/master/LICENSE.txt
+
+package flambe.platform.flash;
+
+import flambe.display.Texture;
+import flash.display.Bitmap;
+import flash.display.Loader;
+import flash.errors.Error;
+import flash.events.ErrorEvent;
+import flash.events.Event;
+import flash.events.IEventDispatcher;
+import flash.events.IOErrorEvent;
+import flash.events.ProgressEvent;
+import flash.events.SecurityErrorEvent;
+import flash.filesystem.File;
+import flash.media.Sound;
+import flash.net.URLLoader;
+import flash.net.URLRequest;
+import flash.system.Capabilities;
+import flash.system.LoaderContext;
+import flash.utils.ByteArray;
+
+import flambe.asset.AssetEntry;
+import flambe.asset.Manifest;
+import flambe.util.Assert;
+
+class FlashEmbeddedAssetPackLoader extends BasicAssetPackLoader
+{
+ public function new (platform :FlashPlatform, manifest :Manifest)
+ {
+ super(platform, manifest);
+ }
+
+ override private function loadEntry (url :String, entry :AssetEntry)
+ {
+ var asset : Dynamic;
+ // create a valid AS3 class name from the asset url, replace all non digits, non word characters, and leading non-letters with dollar signs
+ var className = ~/[^\d|\w|\$]|^[^A-za-z]/g.replace(url.substring(url.indexOf("/") + 1, url.lastIndexOf(".")), "$");
+
+ if (Type.resolveClass(className) != null) {
+ var resInst = Type.createInstance(Type.resolveClass(className), []); // an instance of the resource
+
+ switch (entry.format) {
+ case JXR, PNG, JPG, GIF:
+ asset = _platform.getRenderer().createTextureFromImage(resInst.bitmapData);
+ resInst.bitmapData.dispose();
+
+ case MP3:
+ var sound :Sound = cast resInst;
+ asset = new FlashSound(sound);
+
+ case Data:
+ var data :ByteArray = cast resInst;
+ asset = new BasicFile(data.toString());
+
+ default:
+ // Should never happen
+ Assert.fail("Unsupported format", ["format", entry.format]);
+ return;
+ }
+
+ if (asset != null) {
+ handleLoad(entry, asset);
+ } else {
+ // Assume this is a failed createTexture()
+ trace("failed to create texture...");
+ handleTextureError(entry);
+ }
+ }
+ }
+
+ override private function getAssetFormats (fn :Array -> Void)
+ {
+ fn([JXR, PNG, JPG, GIF, MP3, Data]);
+ }
+}
diff --git a/src/flambe/platform/flash/FlashPlatform.hx b/src/flambe/platform/flash/FlashPlatform.hx
index 60479408..cac88ea3 100644
--- a/src/flambe/platform/flash/FlashPlatform.hx
+++ b/src/flambe/platform/flash/FlashPlatform.hx
@@ -101,7 +101,11 @@ class FlashPlatform
public function loadAssetPack (manifest :Manifest) :Promise
{
- return new FlashAssetPackLoader(this, manifest).promise;
+ #if embed_assets
+ return new FlashEmbeddedAssetPackLoader(this, manifest).promise;
+ #else
+ return new FlashAssetPackLoader(this, manifest).promise;
+ #end
}
public function getStage () :StageSystem