How to bundle external assets?

I have the jme project structure with some minimal assets (icons and splash screen), then most of the assets are on a folder outside the project (where I actually run the game).

I did this mainly for having the assets on a separate repository.

Now I’d like to have these external assets bundled with the application, and also with a JVM. Any advice is welcome, thanks!

how is your update concept and game size?

does packing all assets into a jar work for you? then the first part is solved. (create a small gradle tasks/ or whatever tool you like to just zip them and rename to .jar, use classpath locator then to load them)

For bundling there is the the offical javafx way (it works even if you have 0% javafx)
There are handmade but quite reliable solutions that involve using multiple java zip releases,
put them all to a folder. Then use a small start script to use the correct one and start your application with it.
Eg. for windows something like this:

:CheckOS
IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT)

:64BIT
echo 64-bit...
./win64_oracle_jre/bin/java -cp myjarFilea.jar;myJarFileb.jar com.example.ClassWithMain
GOTO END

:32BIT
echo 32-bit...
./win32_oracle_jre/bin/java -cp myjarFilea.jar;myJarFileb.jar com.example.ClassWithMain
GOTO END

:END
3 Likes

I’d like to pack the jars from 2 folders into one jar:

project(":assets") {
    apply plugin: "java"
    
    buildDir = rootProject.file("build/assets")
    
    sourceSets {
        main {
            resources {
                srcDir '.'
            }
        }
    }    
}

project(":assetsm") {
    apply plugin: "java"
    
    buildDir = rootProject.file("build/assets")
    
    sourceSets {
        main {
            resources {
                srcDir 'run'
            }
        }
    }    
}

OK solved:

task myCopy(type: Copy)

myCopy {
    from 'assets','run/assets'
    into 'pack'
    exclude('**/*.blend','**/*.j3odata')
}

This implies that the other assets are in a run subdirectory unser build/assets… but then they would have been packaged already in the regular assets.jar.

Can you describe at a higher level what you are trying to do? I know you solved it but sometimes there are more elegant ways.

My tree hierarchy looks like this:

/src
/assets
/run/assets

And I want to merge the contents of “assets” and “run/assets” into a single asset.jar.
This is my gradle

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'idea'

mainClassName='com.pesegato.p8s.Main'

repositories {
    mavenLocal()
    jcenter()
}

ext.jmeVersion = "[3.1,)" 

project(":assets") {
    apply plugin: "java"
    
    buildDir = rootProject.file("build/assets")
    
    sourceSets {
        main {
            resources {
                srcDir '.'
            }
        }
    }    
}
/*
task myCopy(type: Copy)

myCopy {
    from 'assets','run/assets'
    into 'pack'
    exclude('**/*.blend','**/*.j3odata')
}
*/

task myPack(type: Jar) {
    from 'assets','run/assets'
    exclude('**/*.blend','**/*.j3odata')
    baseName 'assets'
}

//myPack.dependsOn myCopy

sourceSets {
    main {
        java { srcDir 'src'}
    }
}


dependencies {
 
    compile "org.jmonkeyengine:jme3-core:$jmeVersion"
    compile "org.jmonkeyengine:jme3-desktop:$jmeVersion"
    compile "org.jmonkeyengine:jme3-lwjgl:$jmeVersion"
    compile 'com.simsilica:zay-es:1.2.1'
    compile 'com.simsilica:lemur:1.8.1'
    compile 'com.google.guava:guava:19.0'
    compile 'org.slf4j:slf4j-api:1.7.21'
    compile "com.pesegato:GoldMonkey:[0.1,)"
    compile "com.pesegato:MonkeySheet:[0.1,)"

    compile files('../commons-math3-3.5.jar')
    compile files('../tonegod.emitter.jar')
    compile files('../tonegodgui-0.0.2.jar')
    compile files('../Mermaid-0.0.1.jar')
    
    runtime ":myPack"
//    runtime project(':assets')    
}



My current problem is that I’d like to declare the task myPack to be executed by the assets project… or something like that :stuck_out_tongue:

So, it might help to know why you want the separate directories and why you want them in one jar.

Is there a reason two jars would be bad?

  1. Some assets must reside on src/assets (icon+splashscreen), so I can’t put them on the separate assets repo.
  2. This also allow me to have a production logback.xml that overwrite the development one

This would prevent 2) above but I can live with it.

So I get that you suggest to have 2 subprojects, one for each asset folder?

Anyway, if you don’t to have them as two jars (though that seems like it would make your life easier just to have two assets projects or put everything in one directory tree since they are effectively treated that way) then you might try…

But my syntax may be a bit off and/or the project may have issues with going outside it’s own root.

Personally, if I wanted two different directories for some reason then I’d just treat them like two different assets projects. And if I didn’t want them as two different assets projects then that strongly implies that I should be just keeping them together anyway.

Edit: had typed this before seeing your reply… yes, two assets projects named appropriately for why they are different.

1 Like

I don’t know how logback resolves it’s XML… does it take the first one or the last one or try to load all of them?

It’s not the same thing but you want something similar to the “provided” idea in maven… in that you want your runtime build to depend on something that you don’t want to package. And I only say that because if you lookup the gradle solutions for ‘provided’ then they basically show you how to have your runtime development depend on something that you don’t package… in which case you might be able to just depend on a directory of local resources for your logback.xml that you want to be dev only.

1 Like

Note: icons and splash screens should be able to exist anywhere on your classpath… if you mean the ones settable in AppSettings. I do this all the time.

Edit: and by “anywhere on your classpath” I mean in any jar that you depend on… whether src/main/resources or assets or other.

1 Like

I’m posting here my latest gradle. Bundles both the game and the JVM, and I think is a good starting point for publishing games:

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'idea'

version = "0.1.0"
mainClassName='com.pesegato.p8s.Main'

buildscript {
    dependencies {
        classpath group: 'de.dynamicfiles.projects.gradle.plugins', name: 'javafx-gradle-plugin', version: '8.5.1'
    }

    repositories {
        mavenCentral()
    }
}

repositories {
    mavenLocal()
    jcenter()
}

ext.jmeVersion = "[3.1,)" 

project(":assets") {

    apply plugin: "java"
    
    buildDir = rootProject.file("build/assets")
    
    sourceSets {
        main {
            resources {
                srcDirs = [ '.', '../run/assets' ]
            }
        }
    }    

}

task myPack(type: Jar) {
    from 'assets','run/assets'
    exclude('**/*.blend','**/*.j3odata')
    baseName 'assets'
}

//myPack.dependsOn myCopy

task versionInfo(type:Exec){
    commandLine 'hg id -i -b -t'.split()
    ext.versionfile = new File('assets/buildinfo.properties')
    standardOutput = new ByteArrayOutputStream()

    doLast {
        versionfile.text = 'build.revision=' + standardOutput.toString()
    }
}

sourceSets {
    main {
        java { srcDir 'src'}
    }
}


dependencies {
 
    compile "org.jmonkeyengine:jme3-core:$jmeVersion"
    compile "org.jmonkeyengine:jme3-desktop:$jmeVersion"
    compile "org.jmonkeyengine:jme3-lwjgl:$jmeVersion"
    compile "org.jmonkeyengine:jme3-jogg:$jmeVersion"
    compile 'com.simsilica:zay-es:1.2.1'
    compile 'com.simsilica:lemur:1.8.1'
    compile 'com.google.guava:guava:19.0'
    compile 'org.slf4j:slf4j-api:1.7.21'
    compile "com.pesegato:GoldMonkey:[0.1,)"
    compile "com.pesegato:MonkeySheet:[0.1,)"

    compile files('../commons-math3-3.5.jar')
    compile files('../tonegod.emitter.jar')
    compile files('../tonegodgui-0.0.2.jar')
    compile files('../Mermaid-0.0.1.jar')
    
    runtime project(':assets')    
}

apply plugin: 'javafx-gradle-plugin'

// configure javafx-gradle-plugin
jfx {
    verbose = true
    mainClass = "com.pesegato.p8s.Main"
    jfxAppOutputDir = "build/jfx/app"
    jfxMainAppJarName = "Simonetta.jar"
    deployDir = "src/main/deploy"

    // gradle jfxJar
    css2bin = false
    preLoader = null
    updateExistingJar = false
    allPermissions = false
    manifestAttributes = null // Map<String, String>
    addPackagerJar = true

    // gradle jfxNative
    identifier = null // setting this for windows-bundlers makes it possible to generate upgradeable installers (using same GUID)
    vendor = "some serious business corp."
    nativeOutputDir = "build/jfx/native"
    bundler = "ALL" // set this to some specific, if your don't want all bundlers running, examples "windows.app", "jnlp", ...
    jvmProperties = null // Map<String, String>
    jvmArgs = null // List<String>
    userJvmArgs = null // Map<String, String>
    launcherArguments = null // List<String>
    nativeReleaseVersion = "1.0"
    needShortcut = false
    needMenu = false
    bundleArguments = [
        // dont bundle JRE (not recommended, but increases build-size/-speed)
        runtime: "C:/Program Files/Java/jre1.8.0_91"
    ]
    appName = "simonetta" // this is used for files below "src/main/deploy", e.g. "src/main/deploy/windows/project.ico"
    additionalAppResources = ["package/windows/simonetta.ico"] // path to some additional resources when creating application-bundle
    //secondaryLaunchers = [[appName:"somethingDifferent"], [appName:"somethingDifferent2"]]
    fileAssociations = null // List<Map<String, Object>>
    noBlobSigning = false // when using bundler "jnlp", you can choose to NOT use blob signing
    customBundlers // List<String>
    skipNativeLauncherWorkaround205 = false

    skipNativeLauncherWorkaround124 = false
    skipNativeLauncherWorkaround167 = false
    skipJNLPRessourcePathWorkaround182 = false
    skipSigningJarFilesJNLP185 = false
    skipSizeRecalculationForJNLP185 = false
/*
    // gradle jfxGenerateKeyStore
    keyStore = "src/main/deploy/keystore.jks"
    keyStoreAlias = "myalias"
    keyStorePassword = "password"
    keyPassword = null // will default to keyStorePassword
    keyStoreType = "jks"
    overwriteKeyStore = false

    certDomain = null // required
    certOrgUnit = null // defaults to "none"
    certOrg = null // required
    certState = null // required
    certCountry = null // required
*/
}
/*
task myPublish(dependsOn: [build, jfxNative]) << {
    println "done"
}
*/

/*
 set JAVA_HOME="C:\Program Files\Java\jdk1.8.0_77
 gradlew clean
 gradlew build jfxNative
 gradlew myBundle
*/

task myBundle(type: Zip) {
    from 'build/jfx/native/simonetta'
    baseName 'Simonetta-bundle-Windows-x64.zip'
}

Question: to avoid errors, I have to invoke:

gradlew build jfxNative myBundle

…why is “build” needed? I’d also like to simply invoke gradlew myBundle…

1 Like

Because myBundle doesn’t depend on anything.

Added

myBundle.dependsOn jfxNative
jfxNative.dependsOn build

Still have no idea why jfxNative requires build, however… it works.

This is my last version of the script http://pastebin.com/BVGEtTvQ

I have a .properties file on the source dir (that gets bundled on the jar with jme sdk) which is not included with the gradle build. What am I missing? Thanks!

(the file is actually the default settings for the game, which are copied if not already present).

Tried to look at the file but it was this giant video playing at the top… so I closed it again.

Most likely, you’ve put the properties file in the java directory… but it’s not a java file, it’s a resource… so should go in src/main/resources.

Sorry about that. here it is again:

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'idea'

version = "0.1.0"
mainClassName='com.pesegato.p8s.Main'

buildscript {
    dependencies {
        classpath group: 'de.dynamicfiles.projects.gradle.plugins', name: 'javafx-gradle-plugin', version: '8.5.1'
    }

    repositories {
        mavenCentral()
    }
}

repositories {
    mavenLocal()
    jcenter()
}

ext.jmeVersion = "[3.1,)" 

project(":assets") {

    apply plugin: "java"
    
    buildDir = rootProject.file("build/assets")
    
    sourceSets {
        main {
            resources {
                srcDirs = [ '.', '../run/assets' ]
                exclude('**/*.blend','**/*.j3odata')
            }
        }
    }    

}

task myPack(type: Jar) {
    from 'assets','run/assets'
    exclude('**/*.blend','**/*.j3odata')
    baseName 'assets'
}


def buildDate
def buildCommit

task dateInfo(type:Exec){
    commandLine 'powershell get-date -format "{dd-MM-yyyy HHmm}"'.split()
    standardOutput = new ByteArrayOutputStream()

    doLast {
        //versionfile.text = 'build.date=' + standardOutput.toString()
        buildDate=standardOutput.toString().trim()
    }
}

task dateInfo2(type:Exec){
    commandLine 'hg id -i -b -t'.split()
    //ext.versionfile = new File('assets/buildinfo2.properties')
    standardOutput = new ByteArrayOutputStream()

    doLast {
        //versionfile.text = 'build.revision=' + standardOutput.toString()
        buildCommit=standardOutput.toString().trim()
    }
}

task versionInfo << {
   def props = new Properties()
/*
   ['assets/buildinfo1.properties','assets/buildinfo2.properties'].each {
      props.load(new FileReader(file(it)))
   }
*/
    props.put('build.date',buildDate)
    props.put('build.revision',buildCommit)
   def writer = new FileWriter(file('assets/buildinfo.properties'))
   try {
      props.store(writer, 'Automatically generated by gradle')
      writer.flush()
   } finally {
      writer.close()
   }
}
versionInfo.dependsOn dateInfo
versionInfo.dependsOn dateInfo2

/*
task versionInfo(type:Exec){
    def props = new Properties()
    File propsFile = new File('assets/buildinfo.properties')
    commandLine 'hg id -i -b -t'.split()
    standardOutput = new ByteArrayOutputStream()
    props.setProperty('build.revision', 'aaa' )
    commandLine 'powershell get-date'.split() // -format "{dd-MM-yyyy HH:mm}"'
    props.setProperty('build.date', 'bbb' )
    props.store(propsFile.newWriter(), null)
    ext.versionfile = new File('assets/buildinfo.properties')

    doLast {
        versionfile.text = 'build.revision=' + standardOutput.toString() + 'build.date='
    }
}
*/
sourceSets {
    main {
        java { srcDir 'src'}
    }
}


dependencies {
 
    compile "org.jmonkeyengine:jme3-core:$jmeVersion"
    compile "org.jmonkeyengine:jme3-desktop:$jmeVersion"
    compile "org.jmonkeyengine:jme3-lwjgl:$jmeVersion"
    compile "org.jmonkeyengine:jme3-jogg:$jmeVersion"
    compile 'com.simsilica:zay-es:1.2.1'
    compile 'com.simsilica:lemur:1.8.1'
    compile 'com.google.guava:guava:19.0'
    compile 'org.slf4j:slf4j-api:1.7.21'
    compile "com.pesegato:GoldMonkey:[0.1,)"
    compile "com.pesegato:MonkeySheet:[0.1,)"

    compile files('../commons-math3-3.5.jar')
    compile files('../tonegod.emitter.jar')
    compile files('../tonegodgui-0.0.2.jar')
    compile files('../Mermaid.jar')
    
    runtime project(':assets')    
}

apply plugin: 'javafx-gradle-plugin'

// configure javafx-gradle-plugin
jfx {
    verbose = true
    mainClass = "com.pesegato.p8s.Main"
    jfxAppOutputDir = "build/jfx/app"
    jfxMainAppJarName = "Simonetta.jar"
    deployDir = "src/main/deploy"

    // gradle jfxJar
    css2bin = false
    preLoader = null
    updateExistingJar = false
    allPermissions = false
    manifestAttributes = null // Map<String, String>
    addPackagerJar = true

    // gradle jfxNative
    identifier = null // setting this for windows-bundlers makes it possible to generate upgradeable installers (using same GUID)
    vendor = "some serious business corp."
    nativeOutputDir = "build/jfx/native"
    bundler = "ALL" // set this to some specific, if your don't want all bundlers running, examples "windows.app", "jnlp", ...
    jvmProperties = null // Map<String, String>
    jvmArgs = null // List<String>
    userJvmArgs = null // Map<String, String>
    launcherArguments = null // List<String>
    nativeReleaseVersion = "1.0"
    needShortcut = false
    needMenu = false
/*
    bundleArguments = [
        // dont bundle JRE (not recommended, but increases build-size/-speed)
    ]
*/
    appName = "simonetta" // this is used for files below "src/main/deploy", e.g. "src/main/deploy/windows/project.ico"
    additionalAppResources = null // path to some additional resources when creating application-bundle
    //secondaryLaunchers = [[appName:"somethingDifferent"], [appName:"somethingDifferent2"]]
    fileAssociations = null // List<Map<String, Object>>
    noBlobSigning = false // when using bundler "jnlp", you can choose to NOT use blob signing
    customBundlers // List<String>
    skipNativeLauncherWorkaround205 = false

    skipNativeLauncherWorkaround124 = false
    skipNativeLauncherWorkaround167 = false
    skipJNLPRessourcePathWorkaround182 = false
    skipSigningJarFilesJNLP185 = false
    skipSizeRecalculationForJNLP185 = false
/*
    // gradle jfxGenerateKeyStore
    keyStore = "src/main/deploy/keystore.jks"
    keyStoreAlias = "myalias"
    keyStorePassword = "password"
    keyPassword = null // will default to keyStorePassword
    keyStoreType = "jks"
    overwriteKeyStore = false

    certDomain = null // required
    certOrgUnit = null // defaults to "none"
    certOrg = null // required
    certState = null // required
    certCountry = null // required
*/
}

/*
 PER 32bit
 set JAVA_HOME="C:\Program Files (x86)\Java\jdk1.8.0_91"

 PER 64bit
 set JAVA_HOME="C:\Program Files\Java\jdk1.8.0_77
 gradlew clean
 gradlew release
*/

task makePretty(type: Delete) {
  delete 'build/jfx/native/simonetta/simonetta.ico' //, 'uglyFile'
}

task release(type: Zip) {
    from 'build/jfx/native'
    baseName 'Simonetta-Windows-x64.zip'
}

release.dependsOn makePretty
makePretty.dependsOn jfxNative
jfxNative.dependsOn build
jar.dependsOn versionInfo

OK

Is this still good?

1 Like