Screen stack support

- rename "game-state" to "screen" (like "title screen", "options screen", etc)
- rename :state to :id
- provide helpers to load a screen given a "spec", deserialize a screen back to a spec, manage a screen stack
- rework test repl helper to allow setting the screen directly, instead of tweaking game state
- have start-game take a spec by default
This commit is contained in:
Jeremy Penner 2013-03-18 23:31:10 -04:00
parent cc62dfb054
commit bf41a54aec
3 changed files with 78 additions and 31 deletions

View file

@ -2,17 +2,21 @@
(:require [hottub.slick :as slick] (:require [hottub.slick :as slick]
[hottub.gs :as gs] [hottub.gs :as gs]
[hottub.resource :as res] [hottub.resource :as res]
[hottub.timeline :as tln])) [hottub.timeline :as tln]
[hottub.screen :as scr]))
(defn -main [& args] (defn -main [& args]
(res/start-resource-expiry-thread) (res/start-resource-expiry-thread)
(slick/start-game "Test game" (slick/start-game "Test game" {:id :game}))
{:state :game
:gs (gs/update-gs gs/gs-empty (defmethod scr/screen-from-spec :game [spec]
(gs/add-index :name) {:gs (gs/update-gs gs/gs-empty
(gs/add-index :type) (gs/add-index :name)
(gs/set-entity (gs/gen-id) {:type :sprite :x 50 :y 50 (gs/add-index :type)
:image "res/man.png" :name :man}))})) (gs/set-entity (gs/gen-id) {:type :sprite :x 50 :y 50
:image "res/man.png" :name :man}))})
(defmethod scr/spec-from-screen :game [screen] {:id :game})
(defmethod slick/update-game :game [state inputtln delta] (defmethod slick/update-game :game [state inputtln delta]
(assoc state :gs (gs/update-gs (:gs state) (assoc state :gs (gs/update-gs (:gs state)
@ -26,11 +30,14 @@
(if-let [image (res/get-image (:image sprite))] (if-let [image (res/get-image (:image sprite))]
(.draw image (:x sprite) (:y sprite)))))) (.draw image (:x sprite) (:y sprite))))))
;;; repl helpers
(def ^:dynamic g nil) (def ^:dynamic g nil)
(def ^:dynamic s nil) (def ^:dynamic s nil)
(def ^:dynamic *newscreen* nil)
(defn reload [] (reset! *newscreen* (scr/reload-screen s)))
(defmethod slick/eval-with-bindings :default [state game f] (defmethod slick/eval-with-bindings :default [state game f]
(binding [g game s state] (binding [g game s state *newscreen* (atom nil)]
(let [gsnew (gs/update-gs (:gs state) (f))] (let [gsnew (gs/update-gs (:gs state) (f))]
(slick/game-setstate! game (assoc state :gs gsnew))))) (slick/game-setstate! game (or @*newscreen* (assoc state :gs gsnew))))))

39
src/hottub/screen.clj Normal file
View file

@ -0,0 +1,39 @@
(ns hottub.screen)
(defmulti screen-from-spec (fn [spec] (:id spec)))
(defmulti spec-from-screen (fn [screen] (:id screen)))
(defn- -screen-from-spec [spec] (assoc (screen-from-spec spec) :id (:id spec)))
(defn- -spec-from-screen [screen] (assoc (spec-from-screen screen) :id (:id screen)))
(defn push-screen [screenold spec]
(let [specold (-spec-from-screen screenold)
screen (-screen-from-spec spec)]
(update-in [::stack] conj specold)))
(defn pop-screen [screenold]
(let [stackold (::stack screenold)
specnew (peek stackold)
stacknew (pop stackold)
screen (-screen-from-spec specnew)]
(assoc screen ::stack stacknew)))
(defn push-modal-screen [screenold spec]
(let [screen (-screen-from-spec spec)]
(assoc screen ::modal screenold)))
(defn pop-modal-screen [screenold]
(::modal screenold))
(defn screen-behind-modal [screen]
(pop-modal-screen screen))
(defn update-screen [screenold screennew]
(assoc screennew ::stack (::stack screenold)))
(defn goto-screen [screenold spec]
(let [screen (-screen-from-spec spec)]
(assoc screen ::stack (::stack screenold))))
(defn reload-screen [screen]
(goto-screen screen (-spec-from-screen screen)))

View file

@ -8,11 +8,12 @@
[hottub.util :as u] [hottub.util :as u]
[hottub.repl :as repl] [hottub.repl :as repl]
[hottub.resource :as res] [hottub.resource :as res]
[hottub.timeline :as tln])) [hottub.timeline :as tln]
[hottub.screen :as scr]))
(defmulti update-game (fn [state input delta] (:state state))) (defmulti update-game (fn [screen input delta] (:id screen)))
(defmulti render-game (fn [state graphics] (:state state))) (defmulti render-game (fn [screen graphics] (:id screen)))
(defmulti eval-with-bindings (fn [state game f] (:state state))) (defmulti eval-with-bindings (fn [screen game f] (:id screen)))
(def slick-key-to-key (def slick-key-to-key
{Input/KEY_0 :0 {Input/KEY_0 :0
@ -119,14 +120,14 @@
:state state :state state
:extends org.newdawn.slick.BasicGame) :extends org.newdawn.slick.BasicGame)
(defn game-create [title state] (defn game-create [title screen]
[[title] [[title]
{:state (atom state) {:screen (atom screen)
:repl (repl/repl-create 9999) :repl (repl/repl-create 9999)
:container (atom nil) :container (atom nil)
:input (input-create)}]) :input (input-create)}])
(defn game-state-atom [this] (:state (.state this))) (defn game-screen-atom [this] (:screen (.state this)))
(defn game-container [this] @(:container (.state this))) (defn game-container [this] @(:container (.state this)))
(defn game-install-input [this] (defn game-install-input [this]
@ -147,7 +148,7 @@
(game-install-input this) (game-install-input this)
(repl/repl-start (repl/repl-start
(:repl (.state this)) (:repl (.state this))
(fn [f] (eval-with-bindings @(game-state-atom this) this f)))) (fn [f] (eval-with-bindings @(game-screen-atom 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)]
@ -158,29 +159,29 @@
(.printStackTrace e)))) (.printStackTrace e))))
(repl/repl-update (:repl (.state this))) (repl/repl-update (:repl (.state this)))
(try (try
(if-let [statenew (update-game @(game-state-atom this) (input-timeline-clear! (:input (.state this))) delta)] (if-let [screennew (update-game @(game-screen-atom this) (input-timeline-clear! (:input (.state this))) delta)]
(reset! (game-state-atom this) statenew)) (reset! (game-screen-atom this) screennew))
(catch Exception e (catch Exception e
(println "Couldn't update" (:state @(game-state-atom this))) (println "Couldn't update" (:id @(game-screen-atom this)))
(.printStackTrace e)))) (.printStackTrace e))))
(defn game-render [this container graphics] (defn game-render [this container graphics]
(try (try
(res/gc-expired-resources) (res/gc-expired-resources)
(render-game @(game-state-atom this) graphics) (render-game @(game-screen-atom this) graphics)
(catch Exception e (catch Exception e
(println "Couldn't render" (:state @(game-state-atom this))) (println "Couldn't render" (:id @(game-screen-atom this)))
(.printStackTrace e)))) (.printStackTrace e))))
(defn game-setstate! [this statenew] (defn game-setscreen! [this screennew]
(reset! (game-state-atom this) statenew)) (reset! (game-screen-atom this) screennew))
(defmethod update-game :default [state input delta]) (defmethod update-game :default [screen input delta])
(defmethod render-game :default [state graphics]) (defmethod render-game :default [screen graphics])
(defmethod eval-with-bindings :default [state game f] (f)) (defmethod eval-with-bindings :default [screen game f] (f))
(defn start-game [title startstate] (defn start-game [title spec]
(let [game (hottub.slick.Game. title startstate) (let [game (hottub.slick.Game. title (scr/goto-screen nil spec))
container (AppGameContainer. game 640 480 false)] container (AppGameContainer. game 640 480 false)]
(.setAlwaysRender container true) (.setAlwaysRender container true)
(.start container) (.start container)