Files
play-clj/src/play_clj/core_utils.clj
2014-05-21 16:11:25 -04:00

192 lines
6.3 KiB
Clojure

(in-ns 'play-clj.core)
(defmacro on-gl
"Runs the macro body on the GL thread.
(on-gl (set-screen! my-game main-screen))"
[& body]
`(app! :post-runnable (fn [] ~@body)))
(defn bundle
"Returns an entity containing other entities. This is a useful way to keep
related entities together. They will be drawn in the order they appear in the
internal :entities vector. Any keys in the bundle, such as :x and :y, will
override the equivalent keys in each entity when being drawn.
(bundle (shape :filled) (shape :line))
(assoc (bundle (shape :filled) (shape :line))
:x 100 :y 100)"
[& entities]
(BundleEntity. entities))
(defn bundle?
"Returns true if `entity` is a `bundle`."
[entity]
(isa? (type entity) BundleEntity))
(defn screenshot!
"Captures a screenshot and either returns it as a `pixmap` or saves it to the
specified path.
(screenshot!)
(screenshot! \"out.png\")
(screenshot! (files! :external \"out.png\"))"
([]
(let [^Pixmap$Format pic-f (Pixmap$Format/RGBA8888)
^Pixmap pic (Pixmap. ^long (game :width) ^long (game :height) pic-f)
pixel-data (ScreenUtils/getFrameBufferPixels true)
pixels (.getPixels pic)]
(doto pixels
(.clear)
(.put pixel-data)
(.position 0))
pic))
([path]
(let [pic (screenshot!)
handle (if (string? path)
(files! :local path)
path)]
(PixmapIO/writePNG handle pic)
(pixmap! pic :dispose))))
(defmacro pref!
"Retrieves and stores preferences. The `name` should be a unique string.
; define the name we'll be using
(def ^:const pref-name \"my-game.settings\")
; store a single preference
(pref! pref-name :put-float \"player-health\" 40)
; store multiple preferences
(pref! pref-name :put {\"player-health\" 40
\"player-x\" 20
\"player-y\" 50})
; save the changes to the disk
(pref! pref-name :flush)
; retrieve a single preference
(pref! pref-name :get-float \"player-health\")"
[name k & options]
`(let [^Preferences p# (if (string? ~name)
(app! :get-preferences ~name)
~name)]
(u/call! p# ~k ~@options)))
; static fields
(defmacro scaling
"Returns a static field from [Scaling](http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/utils/Scaling.html).
(scaling :fill)"
[k]
`~(u/gdx-field :utils :Scaling k))
(defmacro usage
"Returns a static field in [VertexAttributes.Usage](http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/graphics/VertexAttributes.Usage.html)."
[k]
`~(u/gdx-field :graphics "VertexAttributes$Usage" (u/key->pascal k)))
; timers
(defn ^:private task*
[{:keys [execute-fn! on-timer]} id]
(proxy [Timer$Task] []
(run []
(execute-fn! on-timer :id id))))
(defn ^:private timer*
[]
(some-> (Class/forName "com.badlogic.gdx.utils.Timer")
(try (catch Exception _))
.newInstance))
(defn ^:private create-and-add-timer!
[{:keys [update-fn!] :as screen} id]
; remove timer if it already exists
(when-let [old-timer (get-in screen [:timers id])]
(.stop old-timer)
(some-> u/*timers* (swap! disj old-timer)))
; create timer, add to screen map, and return it
(let [new-timer (timer*)]
(update-fn! assoc-in [[:timers id] new-timer])
(some-> u/*timers* (swap! conj new-timer))
new-timer))
(defn add-timer!
"Returns a [Timer](http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/utils/Timer.html)
which runs which will fire the :on-timer function one or more times (depending
on the given arguments). In the :on-timer function, the id will be passed in the
screen map. If a timer with that id already exists in the screen, it will be
stopped and replaced with a new timer.
; wait 2 seconds and run once
(add-timer! screen :spawn-enemy 2)
; wait 2 seconds and run forever at 10 second intervals
(add-timer! screen :spawn-enemy 2 10)
; wait 2 seconds, run once, and then run 3 more times at 10 second intervals
(add-timer! screen :spawn-enemy 2 10 3)"
([screen id delay]
(doto (create-and-add-timer! screen id)
(.scheduleTask (task* screen id) delay)))
([screen id delay interval]
(doto (create-and-add-timer! screen id)
(.scheduleTask (task* screen id) delay interval)))
([screen id delay interval repeat]
(doto (create-and-add-timer! screen id)
(.scheduleTask (task* screen id) delay interval repeat))))
(defn remove-timer!
"Stops and removes the timer associated with `id`, returning it or nil if not
found."
[{:keys [update-fn!] :as screen} id]
(when-let [timer (get-in screen [:timers id])]
(.stop timer)
(update-fn! update-in [[:timers] dissoc id])
timer))
; assets
(defn ^:private set-loaders!
([am]
(set-loaders! am (InternalFileHandleResolver.)))
([^AssetManager am res]
(.setLoader am TiledMap (TmxMapLoader. res))
(.setLoader am ParticleEffect (proxy [ParticleEffectLoader] [res]
(load [am file-name fh param]
(doto (ParticleEffect.)
(.load fh (.parent fh))))))))
(defn asset-manager*
([]
(doto (AssetManager.) set-loaders!))
([resolver]
(doto (AssetManager. resolver) (set-loaders! resolver))))
(defmacro asset-manager
"Returns an [AssetManager](http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/assets/AssetManager.html).
(asset-manager)"
[& options]
`(let [^AssetManager object# (asset-manager*)]
(u/calls! object# ~@options)))
(defmacro asset-manager!
"Calls a single method in an `asset-manager`.
(asset-manager! object :clear)"
[object k & options]
`(let [^AssetManager object# ~object]
(u/call! object# ~k ~@options)))
(defn set-asset-manager!
"Sets a global asset manager, which will keep track of objects that need to
be manually disposed, such as `texture` entities and `pixmap` objects. The
asset manager will then allow you to dispose them all at once.
; create an asset manager
(defonce manager (asset-manager))
; set it to be used by play-clj
(set-asset-manager! manager)
; dispose all assets at once
(asset-manager! manager :clear)"
[am]
(intern 'play-clj.utils '*asset-manager* am))