2013-10-13

The libgdx Simple Game Demo in Clojure

Hello world! As a first post, I'd like to share my Clojure implementation of the simple game demo found here:
https://github.com/libgdx/libgdx/wiki/A-simple-game

I'm using Nightcode as my editor (https://github.com/oakes/Nightcode) and it gave me a nice template where to start. I'm not going through all the set up and directory structures of the program here, using nightcode makes it all very easy. The only thing I changed for the launcher code (desktop_launcher.clj) was the resolution of the program.

So without any further blaa blaa, here's the main code (for now, I'll paste it without fancy formatting. Have to check what SyntaxHighlighter has to offer later). I left out the sounds though, because I didn't feel like registering to the page from where I would have had to download them...

(ns com.demo-game.core
  (:import [com.badlogic.gdx Game Gdx Graphics Screen Input$Keys]
           [com.badlogic.gdx.graphics GL10 Color Texture OrthographicCamera]
           [com.badlogic.gdx.graphics.g2d BitmapFont SpriteBatch]
           [com.badlogic.gdx.math Rectangle Vector3 MathUtils]
           [com.badlogic.gdx.scenes.scene2d Stage]
           [com.badlogic.gdx.scenes.scene2d.ui Label Label$LabelStyle]
           [com.badlogic.gdx.utils Array TimeUtils]))

(declare ^Stage stage)


(def asset_path "../android/assets/")


(def droplet-img nil)

(def bucket-img nil)
(def ortocamera nil)
(def batch nil)
(def bucket nil)
(def droplet nil)
(def raindrops (Array.))
(def last-drop nil)

(defn spawn-raindrop []

  (let [raindrop (Rectangle.)]
    (set! (. raindrop x) (.nextInt MathUtils/random (- 800 64)))
    (set! (. raindrop y) 480)
    (set! (. raindrop width) 64)
    (set! (. raindrop height) 64)
    (.add raindrops raindrop)
    (def last-drop (TimeUtils/nanoTime))))

(def main-screen

  (proxy [Screen] []
    (show [] )
    (render [delta]
      (.glClearColor (Gdx/gl) 0 0 0.2 1)
      (.glClear (Gdx/gl) GL10/GL_COLOR_BUFFER_BIT)
      
      (if (> (- (TimeUtils/nanoTime) last-drop) 1000000000)
        (spawn-raindrop))
      
      (if (.isKeyPressed Gdx/input Input$Keys/LEFT)
        (set! (. bucket x)
              (- (. bucket x)(* 200 (.getDeltaTime Gdx/graphics)))))
      (if (.isKeyPressed Gdx/input Input$Keys/RIGHT)
        (set! (. bucket x)
              (+ (. bucket x) (* 200 (.getDeltaTime Gdx/graphics)))))  
      
      (if (.isTouched Gdx/input)
        (let [touch-pos (Vector3.)]
          ; convert the touch coordinates (from input) into the
          ; coordinate system of the camera
          (.set touch-pos (.getX Gdx/input) (.getY Gdx/input) 0)
          (.unproject ortocamera touch-pos)
          (set! (. bucket x) (- (. touch-pos x) (/ 64 2)))))
      
      (let [iter (.iterator raindrops)]
        (while (.hasNext iter)
          (let [rdrop (.next iter)]
            (set! (. rdrop y)
                  (- (. rdrop y) (* 200 (.getDeltaTime Gdx/graphics))))
            (if (< (+ (. rdrop y) 64) 0)
              (.remove iter))
            (if (.overlaps rdrop bucket)
              (.remove iter)))))
      
      (if (< (. bucket x) 0)
        (set! (. bucket x) 0))
      (if (> (. bucket x) (- 800 64))
        (set! (. bucket x) (- 800 64)))
      
      (.update ortocamera)
      (.setProjectionMatrix batch (. ortocamera combined))
      (.begin batch)
      (.draw batch bucket-img (. bucket x) (. bucket y))
      
      (doseq [rdrop (seq raindrops)]
        (.draw batch droplet-img (. rdrop x) (. rdrop y)))
      (.end batch))
    
    (dispose[]
      (.dispose droplet-img)
      (.dispose bucket-img)
      (.dispose batch))
    (hide [])
    (pause [])
    (resize [w h])
    (resume [])))


(gen-class

  :name com.demo-game.core.Game
  :extends com.badlogic.gdx.Game)

(defn -resize [this w h]

  (println "resized to" w "x" h))

(defn -create [this]

  (.setScreen this main-screen)
  (def droplet-img
    (Texture. (.internal (Gdx/files) (str asset_path "droplet.png"))))
  (def bucket-img
    (Texture. (.internal (Gdx/files) (str asset_path "bucket.png"))))
  (def ortocamera (OrthographicCamera.))
  (.setToOrtho ortocamera false 800 480)
  (def batch (SpriteBatch.))
  (def bucket (Rectangle.))
  (set! (. bucket x) (- (/ 800 2) (/ 64 2)))
  (set! (. bucket y) 20)
  (set! (. bucket width) 64)
  (set! (. bucket height) 64)
  (spawn-raindrop))

4 comments:

  1. Hi, Jussi. The assets that you define in -create are first 'def'ed at the top of the source. Why is that? Should they be 'declare'ed instead? Great tutorial BTW!

    ReplyDelete
  2. Hi Kris! Thanks for the comment, it's good to know that the blog is sometimes read by others than just me and google crawler. :) Concerning the defs, you're completely right, "declare" would be the correct way to introduce symbols there. I'm still very new to Clojure so some things in the blog might be better done some other way.

    ReplyDelete
  3. I found this post very helpful when I was translating the simple game tutorial myself into Clojure. I used yours to double-check my mistakes :) Based on your code, I tried to make it a little more Clojure-y. Here is what I came up with:

    http://pastebin.com/Znpb2h6M

    Note the use of an atom for last-drop. This way, I don't have to re-def last-drop every time spawn-raindrop is called.

    Another thing I did was wrap all the setting of attributes of Rectangle objects in a doto and using the setter methods of Rectangle instead of calling set! .

    It could still be better, I'm sure. I'm no Clojure expert either. In particular, I think there is a more Clojure-y way to iterate through the raindrops array by using doseq somehow, but I couldn't figure it out :/

    And one more thing. I don't know what version of nightcode you used for this, but for versions >= 0.1.3 if you put your assets in desktop/resources/ then you don't need to specify your asset path when loading them. Works when deploying to Android too!

    ReplyDelete
  4. Thanks for pasting the code, I have to take a deeper look at some point to improve my way of doing things. I haven't really had much time to work on this lately (as the delay in answering might imply...). I don't know anymore which version of Nightcode I was working with earlier (I reinstalled my system a little while ago) but the assets I put into the android folders (which were supposed to work with desktop as well) weren't found. So I used the hardcoded path as a quick way around it because I wanted to concentrate on the functionality itself. Have to try again now with a never version, maybe it works as expected. :)

    ReplyDelete