I'm trying to reproduce the sample app from this libgdx tutorial in clojure and am having a hard time getting the java interop part working.
The way a desktop game is created in libgdx is by instantiating the LwjglApplication class with an object that implements the ApplicationListener interface, along with some configuration options, like so:
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
public class Main {
public static void main(String[] args) {
new LwjglApplication(new DropGame(), cfg);
}
public class DropGame implements ApplicationListener {
Texture dropImage;
Sound dropSound;
... rest of assets
public void create() {
dropImage = new Texture(Gdx.files.internal("droplet.png"));
dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
}
... rest of interface methods
}
}
After reading up on clojure/java interop, here is the clojure equivalent I came up with:
(ns dropgame.core
(:gen-class)
(:import (com.badlogic.gdx.backends.lwjgl LwjglApplication))
(defn app-listener []
(reify ApplicationListener
(create [this] (let [dropImage (Texture. (.internal Gdx/files "droplet.png"))
dropSound (.newSound Gdx/audio (.internal Gdx/files "drop.wav"))])
(defn App []
(LwjglApplication. (app-listener) cfg))
I left out a lot of the code, but that's fine because the app works using this structure. The problem is that this code is not exactly the same. The libgdx framework chooses to declare all assets globally (dropImage and dropSound in this case) so that code in other methods has easy access to them without needing to pass them around. If I try to restructure my code to do the same, by declaring assets in an outer let binding instead of inside a method, it no longer works.
Doing this gives me a nullPointerException:
(defn app-listener []
(let [dropImage (Texture. (.internal Gdx/files "droplet.png"))
dropSound (.newSound Gdx/audio (.internal Gdx/files "drop.wav"))]
(reify ApplicationListener
(create [this] ( ; try accessing dropImage or dropSound)))))
So the let binding is creating the same objects, but accessing these object from inside any of the methods no longer seems to work. Any ideas what I'm doing wrong here?
EDIT: So as mentioned, the reason why I was getting a nullPointerException is probably because the assets were being loaded before the app was ready. But anyway the proper way to load assets for anything beyond a simple game is to use the AssetManager, as muhuk suggested. So I ended up doing something like this for assets:
(def asset-manager (doto (AssetManager.)
(.load "rain.mp3" Music)
(.load "droplet.png" Texture))
However there are other things besides assets that need to be accessed globally, for example the camera. So what I did for these is this:
(defn app-listener []
(reify ApplicationListener
(create [this]
(def camera (-> (OrthographicCamera.) (.setToOrtho false 800 480))))))
While this would probably raise alarms in any normal clojure code, since you have a function defining global values, in this particular case it actually makes sense since 'app-listener' is only getting instantiated once, and the documentation specifies that the 'create' method only gets called once too during the app's life cycle. Though I'd still like to know if there's a better way to do this.
As for using 'deftype', my understanding is that 'defrecord' would be easier to use since it allows for passing a map of additional fields without having to declare them in the constructor. But in either case the code ended up looking pretty messy since you have to pass all your assets to the constructor. In addition to that, defining a named type kind of obscures what the code is doing and doesn't fit very nicely with the problem.
I'm gonna finish up the rest of the tutorial and post a link to the clojure code once I'm done. Thanks guys.