How do I create a launcher for a jMonkeyEngine project?

Hello,

I am attempting to build a launcher for my jMonkeyEngine game, but I am just completely lost.

I am looking to create something like, for example, this:

There are, of course, problems with a launcher. The main big one is that jMonkeyEngine exports all assets in an assets.jar, which would mean a single change to assets would require the user to re-download all of them.

The other issue, of course, is just implementing a GUI in general, and then downloading the files. I have very little experience with this, so if anyone could give me any help, I’d appreciate it.

Also, are there any existing technologies/code out there, instead of having to create my own?

Thanks,
Joe

Yes, one of them is called Steam. There are also several other platform-dependent ones I know of (ie. apt-get on Ubuntu) but I think you want to target Windows as well, so that probably isn’t an option for you.

You could use Java’s zip APIs (since jar files are really zip files) to only send the modified files. For instance, you could send a new assets.jar that only contains the files changed since the last version. Although, I recommend having your entire game client in a single jar file (classes, JME, assets, etc.) as that avoids any dependence problems since everything will be right there. As such, you can use this partial update/jar patch mechanism for everything – not just assets.

Just plain JavaFX or Swing should be fine for this. Downloading files is very simple, you just need to have a bit of knowledge about how to use Java’s streams. This is what Minecraft did until very recently.

1 Like

You may want to look at:

2 Likes

Nice topic!
I would very much appreciate a simple demo for something like this too.
Bundled with a tutorial in the wiki or in a pdf would be great.

What I need:
A platform launcher (executable for Windows / Linux / Mac).
Bundle a JRE with the game (so the user doesn’t need to install Java).
Mechanism to update the code (replace old code version by newest one).
Mechanism to update assets.
Mechanism to install mods.

The things I found so far:
SDK makes an exe for Windows but I don’t know how to configure it.
One user in this forum said, launch4j is a good tool but didn’t try it yet.
And

I didn’t know that Java has such a partial update mechanism. Do you have a good link for that?

1 Like

Most of this stuff is fairly easy but it would have minor differences depending on the game (ie. are mods applied as patch files, or loaded via a ClassLoder from a directory?).

As for the distributable, just make a zip file with a jre + your game in it which launch4j can then use to run the launcher. On Linux, I’m fine running a .sh, but, you could also package it as a .deb file or write a small C program to launch the JRE (I know only basic C and I can write it in a few seconds). For Windows, use this. I can’t comment on Macs since I don’t have one and I have no idea how they work beyond that they are based off BSD.

I wasn’t referring to an existing mechanism but rather a method to make ones own.

For instance, if your game has the following classes in version 1:

  1. yourgame.YourGameClient
  2. yourgame.AppState1
  3. yourgame.AppState2

Then, in version 2 you add AppState3 and modify AppState2, you would distribute the following in the “patch” jar:

  1. yourgame.AppState2
  2. yourgame.AppState3

Then, when your launcher connects to a web server it sees that a patch jar is available and downloads it. Then, it opens up the jar (remember that jars are zip files) and copies each file from the patch jar into the original jar while overwriting any files that exist in both jars with the one from the patch jar. You can do the same thing for every jar your project needs.

1 Like

What you’re suggesting seems like a lot of awkwardness, for example, having to distribute “patch JARs” with only specific files in them. It’s all fine and good saying “do this and this and this”, but neither of us (at least I don’t) understand much of this, which is why I’ve asked on the forum in the first place.

I’m looking for how to do it, not what I need to do.

You asked how to save on bandwidth, so I gave an abstract soltuion. Honestly, I think that just distributing a new jar for each version would be fine.

Note: the main jar can include claaspath entries that refer to relative paths. This is how the JME desktop deployment works (and how I wish more of the ‘application’ plugins in build tools work but anyway)…

Doing so lets one update just the jars needed and makes it easy to use something like getdown ( GitHub - threerings/getdown: Download, Install, Update ) which will do that automatically even. It even provides a launcher.

That way you don’t have to worry about patching jars (which is very expensive for larger jars as you cannot update a zip file, only rewrite it.)

Currently looking into GetDown, as per @pspeed’s recommendation. I’ve never known paul to be wrong yet, so if I manage to get anything implemented I’ll be sure to share whatever it is I’ve managed to do.

(Perhaps once it’s done I’ll release my games’ prototype/alpha for JME folks to try)

Just a note for clarification: getdown has been recommended to me probably 100 times by really smart people but it’s not something I’ve had a chance to use directly yet. I read the docs but I never got the time to apply it to one of my projects.

…else I would have posted a gradle example, too. I think @david_bernard_31 may have some examples online… but I don’t have the link handy.

Also, here is a quick and dirty launcher that roughly shows what I was talking about minus the jar patching (I didn’t test it, but it should compile):

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

public class BasicLauncher{
    private static File gameDir = new File(new File(System.getProperty("user.home")), ".yourgamename");
    private File versionFile = new File(gameDir, "version");
    private File gameFile = new File(gameDir, "yourgame.jar");

    public static void main(String[] args){
        new BasicLauncher().run();
    }

    private void run(){
        if(!gameDir.exists()){
            gameDir.mkdirs();
        }

        try{
            String downloadedVersion = getDownloadedVersion();
            String latestVersion = getLatestVersion();

            if(!downloadedVersion.equals(latestVersion)){
                downloadUpdates();
                setDownloadedVersion(latestVersion);
            }
        }catch(IOException e){
            System.err.println("Failed to check for new versions");
            e.printStackTrace();
        }

        try{
            URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(gameFile.getAbsolutePath())});
            Class<?> clazz = urlClassLoader.loadClass("com.yourgame.YourGame"); //put your main class here
            clazz.getMethod("main", String[].class).invoke(null, new String[0]);
        }catch(IOException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e){
            System.err.println("Failed to launch game");
            e.printStackTrace();
        }
    }

    private String getDownloadedVersion() throws IOException{
        BufferedReader bufferedReader = new BufferedReader(new FileReader(versionFile));
        String line, version = "";

        while((line = bufferedReader.readLine()) != null){
            version += line;
        }

        bufferedReader.close();

        return version;
    }

    private void setDownloadedVersion(String version) throws IOException{
        FileOutputStream fileOutputStream = new FileOutputStream(versionFile);
        fileOutputStream.write(version.getBytes()); //prevent newlines
        fileOutputStream.flush();
        fileOutputStream.close();
    }

    private String getLatestVersion() throws IOException{
        URL url = new URL("https://static.yourgame.com/latestversion");
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(url.openConnection().getInputStream()));
        String line, version = "";

        while((line = bufferedReader.readLine()) != null){
            version += line;
        }

        bufferedReader.close();

        return version;
    }

    private void downloadUpdates() throws IOException{
        File downloadFile = new File(gameDir, "yourgame-updated.jar"); //separate file so that the original is intact if the download fails
        URL url = new URL("https://static.yourgame.com/yourgame.jar"); //add some get paramaters here + a php script if you want to ensure that the user actually has a product key, but it isn't all that necessary in many case s(Minecraft doesn't do it for instance)
        InputStream inputStream = url.openConnection().getInputStream();
        FileOutputStream fileOutputStream = new FileOutputStream(downloadFile);
        byte[] buffer = new byte[1024];
        int bytesRead;

        while((bytesRead = inputStream.read(buffer)) > 0){
            fileOutputStream.write(buffer, 0, bytesRead);
        }

        inputStream.close();
        fileOutputStream.close();

        Files.move(downloadFile.toPath(), gameFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
    }

}

This is a very good point, I didn’t think of it.

1 Like

I provide the gradle-getdown-plugin.

A gradle plugin to bundle java app + jre with getdown support

I did few update last week into the plugin for LD. I use it for many of my JME game , tools. I hope the README contains enough info to start.

I started a new rebranded version gradle-appbundler-plugin that will have simpler configuration and default value / setup.

1 Like

I’ve got another stupid noob question: The bundled JRE means that it will start on-the-fly or will it install the JRE to the user’s OS?

No install of the jre at os level. the bundled jre is for the app only.
Content of an archive is:

mysuperproject
├── app
│   ├── digest.txt
│   ├── favicon.ico
│   ├── getdown-1.4.jar
│   ├── getdown.txt
│   └── lib
|       └── *.jar
├── jre
│   └── ...
├── latest-getdown.txt
├── launch
└── launch.exe

The plugin can also generate the content of the http server if you want to manage upload of update like requested in the top message.

EDIT: the plugin was made to create application ready to use with or without an http server to provide update. What is called offline mode.

1 Like

At the moment getDown just keeps re-downloading the files after “verifying”, and never actually runs the game. I suppose I’ll have to solve that. :confused:

Alright, so I have got it about 95% working.

  • Launcher opens, checks files, etc.
  • Game downloads files.
  • Game opens, jMonkey screen comes up:
  • I click “continue”. And the settings screen just closes. My game does not open.

Due to it being run from the launcher, there appears to be no console for possible errors.

The game runs correctly if I run it with jar -jar PlaneStorn.jar

If I could just overcome this last hurdle, then I’ll have done it: I’ll have a launcher.

1 Like

Theres tools to make deltas of folders, no matter if the actual files are large:

I don’t wanna be a dick, but isn’t that just the default launcher with an alternate image ?

I think there is something going on before the AppSettingsDialog (or whatever) comes up. But you are right, a launcher before a launcher is not so super cool. Guess we just need to go through all the other suggestions found in this thread - until someone has a good solution and a tutorial showing how to do it right. Perfect tutorial for me would be a step-by-step tutorial which I can follow (because I don’t know certain things yet).

wat? That question doesn’t make any sense. Of course it is the default getDown laucher, because why would I customize it before I even get it working?