Create and provide a timeline of user input events (keyboard / mouse)

Make the container accessible from the game object after initialization (mostly for debugging)
Implement some test code (moving a sprite around)
This commit is contained in:
Jeremy Penner 2013-03-14 11:13:44 -05:00
parent fcc86c782c
commit dcc5d430e3
3 changed files with 165 additions and 17 deletions

View file

@ -8,10 +8,28 @@
(slick/start-game "Test game" (slick/start-game "Test game"
{:state :game {:state :game
:gs (gs/with-gs gs/gs-empty :gs (gs/with-gs gs/gs-empty
(gs/add-index :name)
(gs/add-index :type) (gs/add-index :type)
(gs/set-entity (gs/gen-id) {:type :sprite :x 50 :y 50 :image "res/man.png"}))})) (gs/set-entity (gs/gen-id) {:type :sprite :x 50 :y 50
:image "res/man.png" :name :man}))}))
(defmethod slick/update-game :game [state input delta]
(let [gsnew
(gs/with-gs (:gs state)
(let [idman (first (gs/q :name :man))
man (gs/entity idman)]
(loop [timeline (slick/input-timeline input)]
(let [[ts events] (first timeline)
timelinenext (next timeline)]
(doseq [{:keys [type action x y]} events]
(if (and (= type :mouse) (= action :move))
(gs/set-entity idman (assoc man :x x :y y))))
(if timelinenext
(recur timelinenext)
(slick/input-clear input ts)))))
(gs/get-gs))]
(assoc state :gs gsnew)))
(defmethod slick/update-game :game [state delta])
(defmethod slick/render-game :game [state graphics] (defmethod slick/render-game :game [state graphics]
(gs/with-gs (:gs state) (gs/with-gs (:gs state)
(doseq [sprite (for [spriteid (gs/q :type :sprite)] (gs/entity spriteid))] (doseq [sprite (for [spriteid (gs/q :type :sprite)] (gs/entity spriteid))]
@ -20,9 +38,9 @@
(def ^:dynamic g nil) (def ^:dynamic g nil)
(def ^:dynamic s nil) (def ^:dynamic s nil)
(def ^:dynamic _container nil)
(defmethod slick/eval-with-bindings :game [state game container f] (defmethod slick/eval-with-bindings :default [state game f]
(binding [g game s state _container container] (binding [g game s state]
(let [gsnew (gs/with-gs (:gs state) (f) (gs/get-gs))] (let [gsnew (gs/with-gs (:gs state) (f) (gs/get-gs))]
(slick/game-setstate game (assoc state :gs gsnew))))) (slick/game-setstate game (assoc state :gs gsnew)))))

View file

@ -1,12 +1,114 @@
(ns hottub.slick (ns hottub.slick
(:import [org.newdawn.slick AppGameContainer]) (:import [org.newdawn.slick AppGameContainer
ControlledInputReciever
KeyListener
MouseListener
Input])
(:use ns-tracker.core (:use ns-tracker.core
[hottub.util :as u]
[hottub.repl :as repl] [hottub.repl :as repl]
[hottub.resource :as res])) [hottub.resource :as res]
[hottub.timeline :as tln]))
(defmulti update-game (fn [state delta] (:state state))) (defmulti update-game (fn [state input delta] (:state state)))
(defmulti render-game (fn [state graphics] (:state state))) (defmulti render-game (fn [state graphics] (:state state)))
(defmulti eval-with-bindings (fn [state game container f] (:state state))) (defmulti eval-with-bindings (fn [state game f] (:state state)))
(def slick-key-to-key
{Input/KEY_0 :0
Input/KEY_1 :1
Input/KEY_2 :2
Input/KEY_3 :3
Input/KEY_4 :4
Input/KEY_5 :5
Input/KEY_6 :6
Input/KEY_7 :7
Input/KEY_8 :8
Input/KEY_9 :9
Input/KEY_A :a
Input/KEY_B :b
Input/KEY_C :c
Input/KEY_D :d
Input/KEY_E :e
Input/KEY_F :f
Input/KEY_G :g
Input/KEY_H :h
Input/KEY_I :i
Input/KEY_J :j
Input/KEY_K :k
Input/KEY_L :l
Input/KEY_M :m
Input/KEY_N :n
Input/KEY_O :o
Input/KEY_P :p
Input/KEY_Q :q
Input/KEY_R :r
Input/KEY_S :s
Input/KEY_T :t
Input/KEY_U :u
Input/KEY_V :v
Input/KEY_W :w
Input/KEY_X :x
Input/KEY_Y :y
Input/KEY_Z :z
Input/KEY_UP :up
Input/KEY_DOWN :down
Input/KEY_LEFT :left
Input/KEY_RIGHT :right })
(def slick-mouse-to-mouse
{Input/MOUSE_LEFT_BUTTON :left
Input/MOUSE_MIDDLE_BUTTON :middle
Input/MOUSE_RIGHT_BUTTON :right})
; input
(defprotocol InputTimeline
(input-timeline [this])
(input-clear [this tsfirst]))
(defn- add-event [atln event]
(swap! atln tln/timeline-insert (u/timestamp) event))
(defn- add-key-event [atln skey action]
(if-let [key (get slick-key-to-key skey)]
(add-event atln {:type :key :action action :key key})))
(defn- add-mousebutton-event [atln sbutton action x y]
(if-let [button (get slick-mouse-to-mouse sbutton)]
(add-event atln {:type :mouse :action action :button button :x x :y y})))
(defn input-create []
(let [atln (atom (timeline-create))]
(reify
InputTimeline
(input-timeline [this] @atln)
(input-clear [this tsafter] (swap! atln timeline-after tsafter))
ControlledInputReciever
(inputEnded [this])
(inputStarted [this])
(isAcceptingInput [this] true)
(setInput [this input])
KeyListener
(keyPressed [this skey c] (add-key-event atln skey :pressed))
(keyReleased [this skey c] (add-key-event atln skey :released))
MouseListener
(mouseClicked [this sbutton x y count]
(add-mousebutton-event atln sbutton (if (= count 2) :double-click :click) x y))
(mouseDragged [this oldx oldy newx newy])
(mouseMoved [this oldx oldy newx newy] (add-event atln {:type :mouse
:action :move
:x newx
:y newy
:dx (- oldx newx)
:dy (- oldy newy)}))
(mousePressed [this sbutton x y] (add-mousebutton-event atln sbutton :down x y))
(mouseReleased [this sbutton x y] (add-mousebutton-event atln sbutton :up x y))
(mouseWheelMoved [this dy] (add-event atln {:type :mouse :action :wheel :distance dy})))))
(def modified-namespaces (ns-tracker ["src" "test"])) (def modified-namespaces (ns-tracker ["src" "test"]))
@ -16,19 +118,38 @@
:init create :init create
:constructors {[String clojure.lang.PersistentArrayMap] [String]} :constructors {[String clojure.lang.PersistentArrayMap] [String]}
:state state :state state
:extends org.newdawn.slick.BasicGame) :extends org.newdawn.slick.BasicGame
:implements [hottub.slick.InputTimeline])
(defn game-create [title state] (defn game-create [title state]
[[title] [[title]
{:state (ref state) {:state (ref state)
:repl (repl/repl-create 9999)}]) :repl (repl/repl-create 9999)
:container (atom nil)
:input (input-create)}])
(defn game-state-ref [this] (:state (.state this))) (defn game-state-ref [this] (:state (.state this)))
(defn game-container [this] @(:container (.state this)))
(defn game-install-input [this]
(let [input (:input (.state this))
sinput (.getInput (game-container this))]
(.addKeyListener sinput input)
(.addMouseListener sinput input)
input))
(defn game-uninstall-input [this]
(let [input (:input (.state this))
sinput (.getInput (game-container this))]
(.removeKeyListener sinput input)
(.removeMouseListener sinput input)))
(defn game-init [this container] (defn game-init [this container]
(reset! (:container (.state this)) container)
(game-install-input this)
(repl/repl-start (repl/repl-start
(:repl (.state this)) (:repl (.state this))
(fn [f] (eval-with-bindings @(game-state-ref this) this container f)))) (fn [f] (eval-with-bindings @(game-state-ref this) this f))))
(defn game-update [this container delta] (defn game-update [this container delta]
(doseq [ns-sym (modified-namespaces)] (doseq [ns-sym (modified-namespaces)]
@ -38,9 +159,9 @@
(repl/repl-update (:repl (.state this))) (repl/repl-update (:repl (.state this)))
(dosync (dosync
(try (try
(if-let [statenew (update-game @(game-state-ref this) delta)] (if-let [statenew (update-game @(game-state-ref this) (:input (.state this)) delta)]
(ref-set (game-state-ref this) statenew)) (ref-set (game-state-ref this) statenew))
(catch Exception e (prn "Couldn't update" (:state @(game-state-ref this)) (.getMessage e)))))) (catch Exception e (println "Couldn't update" (:state @(game-state-ref this)) (.getMessage e))))))
(defn game-render [this container graphics] (defn game-render [this container graphics]
(try (try
@ -51,9 +172,15 @@
(defn game-setstate [this statenew] (defn game-setstate [this statenew]
(dosync (ref-set (game-state-ref this) statenew))) (dosync (ref-set (game-state-ref this) statenew)))
(defmethod update-game :default [state delta]) (defn game-input_timeline [this]
(input-timeline (:input (.state this))))
(defn game-input_clear [this tsafter]
(input-clear (:input (.state this)) tsafter))
(defmethod update-game :default [state input delta])
(defmethod render-game :default [state graphics]) (defmethod render-game :default [state graphics])
(defmethod eval-with-bindings :default [state game container f] (f)) (defmethod eval-with-bindings :default [state game f] (f))
(defn start-game [title startstate] (defn start-game [title startstate]
(let [game (hottub.slick.Game. title startstate) (let [game (hottub.slick.Game. title startstate)

View file

@ -5,3 +5,6 @@
(if (compare-and-set! a oldval newval) (if (compare-and-set! a oldval newval)
oldval oldval
(recur @a)))) (recur @a))))
(defn timestamp []
(/ (System/nanoTime) 1000000))