简体   繁体   中英

clojure to java interop with libgdx

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.

My guess is you're trying to initialize them before they are allowed to be initialized by the libgdx framework. would be helpful if you posted a stack trace.

As another clojure newbie, maybe the thing to do is explore deftype? iirc that would give you methods and fields, which is what you need. let initializes a variable to the current scope only.

Another approach might be binding the values current thread, but i'm not sure what would be more correct. Good luck! (I'm also interested in libgdx and clojure)

That let would try to load the assets before an application instance is created. libGDX mimics the android application lifecycle so you shouldn't try loading assets before create is called.

Since libGDX is a Java framework we can't expect it to match Clojure's purely functional approach. There's got to be some state somewhere. You might want to take a look at the AssetManager .

In case people having trouble with Clojure and the example game end up here (like I did), here's a link to a working implementation (without the music / sounds though):

http://clojuregames.blogspot.com/2013/10/the-libgdx-simple-game-demo-in-clojure.html

I'm still only beginning to learn Clojure as well, so in case some things in the implementation are done in a bad way, let me know in the comments.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM