From dcc5d430e385e66cb990d2fd793ff33a496845bb Mon Sep 17 00:00:00 2001 From: Jeremy Penner Date: Thu, 14 Mar 2013 11:13:44 -0500 Subject: [PATCH] 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) --- src/hottub/core.clj | 28 ++++++-- src/hottub/slick.clj | 151 +++++++++++++++++++++++++++++++++++++++---- src/hottub/util.clj | 3 + 3 files changed, 165 insertions(+), 17 deletions(-) diff --git a/src/hottub/core.clj b/src/hottub/core.clj index 87a154a..b942d67 100644 --- a/src/hottub/core.clj +++ b/src/hottub/core.clj @@ -8,10 +8,28 @@ (slick/start-game "Test game" {:state :game :gs (gs/with-gs gs/gs-empty + (gs/add-index :name) (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] (gs/with-gs (:gs state) (doseq [sprite (for [spriteid (gs/q :type :sprite)] (gs/entity spriteid))] @@ -20,9 +38,9 @@ (def ^:dynamic g nil) (def ^:dynamic s nil) -(def ^:dynamic _container nil) -(defmethod slick/eval-with-bindings :game [state game container f] - (binding [g game s state _container container] + +(defmethod slick/eval-with-bindings :default [state game f] + (binding [g game s state] (let [gsnew (gs/with-gs (:gs state) (f) (gs/get-gs))] (slick/game-setstate game (assoc state :gs gsnew))))) diff --git a/src/hottub/slick.clj b/src/hottub/slick.clj index 8d3b40c..ca83568 100644 --- a/src/hottub/slick.clj +++ b/src/hottub/slick.clj @@ -1,12 +1,114 @@ (ns hottub.slick - (:import [org.newdawn.slick AppGameContainer]) + (:import [org.newdawn.slick AppGameContainer + ControlledInputReciever + KeyListener + MouseListener + Input]) (:use ns-tracker.core + [hottub.util :as u] [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 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"])) @@ -16,19 +118,38 @@ :init create :constructors {[String clojure.lang.PersistentArrayMap] [String]} :state state - :extends org.newdawn.slick.BasicGame) + :extends org.newdawn.slick.BasicGame + :implements [hottub.slick.InputTimeline]) (defn game-create [title state] [[title] {: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-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] + (reset! (:container (.state this)) container) + (game-install-input this) (repl/repl-start (: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] (doseq [ns-sym (modified-namespaces)] @@ -38,9 +159,9 @@ (repl/repl-update (:repl (.state this))) (dosync (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)) - (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] (try @@ -51,9 +172,15 @@ (defn game-setstate [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 eval-with-bindings :default [state game container f] (f)) +(defmethod eval-with-bindings :default [state game f] (f)) (defn start-game [title startstate] (let [game (hottub.slick.Game. title startstate) diff --git a/src/hottub/util.clj b/src/hottub/util.clj index ee8afdb..9df3a10 100644 --- a/src/hottub/util.clj +++ b/src/hottub/util.clj @@ -5,3 +5,6 @@ (if (compare-and-set! a oldval newval) oldval (recur @a)))) + +(defn timestamp [] + (/ (System/nanoTime) 1000000))