|
|
|
(ns topdown2d.core
|
|
|
|
(:require [topdown2d.demoscene :as demoscene]
|
|
|
|
[topdown2d.input :as input]
|
|
|
|
[topdown2d.dom :refer [by-id]]))
|
|
|
|
|
|
|
|
(enable-console-print!)
|
|
|
|
|
|
|
|
(def gamestate
|
|
|
|
{:canvas (by-id "gamecanvas")
|
|
|
|
:ctx (.getContext (by-id "gamecanvas") "2d")
|
|
|
|
:continue? true
|
|
|
|
:timing {;; msecs of previous frame
|
|
|
|
:prev 0
|
|
|
|
;; msecs of current frame
|
|
|
|
:now 0
|
|
|
|
;; fps resulting of prev and now
|
|
|
|
:fps 0
|
|
|
|
;; difference between prev and now in seconds
|
|
|
|
:elapsed 0}
|
|
|
|
;; width and height of the canvas
|
|
|
|
:dimensions {:w (* 16 11 3)
|
|
|
|
:h (* 16 9 3)}
|
|
|
|
:input {:dir :?}
|
|
|
|
;; currently active scene
|
|
|
|
:scene :demo
|
|
|
|
:scenes {:demo {:update demoscene/update-scene
|
|
|
|
:draw demoscene/draw-scene
|
|
|
|
:init demoscene/init}}})
|
|
|
|
|
|
|
|
(def reloaded (atom false))
|
|
|
|
|
|
|
|
(defn curr-fps
|
|
|
|
"calculates the current fps using the elapsed time"
|
|
|
|
[elapsed]
|
|
|
|
(/ 1 elapsed))
|
|
|
|
|
|
|
|
(defn elapsed-seconds
|
|
|
|
"calculates the elapsed seconds since the last frame"
|
|
|
|
[gamestate now]
|
|
|
|
(/ (- now (-> gamestate :timing :prev))
|
|
|
|
1000))
|
|
|
|
|
|
|
|
(defn curr-scene
|
|
|
|
"returns the current scene"
|
|
|
|
[gamestate]
|
|
|
|
(get-in gamestate [:scenes (:scene gamestate)]))
|
|
|
|
|
|
|
|
(defn run-scene-update
|
|
|
|
"updates the current scene using its udpate function"
|
|
|
|
[gamestate scene]
|
|
|
|
((:update scene) gamestate scene))
|
|
|
|
|
|
|
|
(defn continue-running?
|
|
|
|
"checks if the gameloop should keep running, based on input"
|
|
|
|
[prev-continue?]
|
|
|
|
(cond
|
|
|
|
(and prev-continue?
|
|
|
|
(input/keydown? :Digit2)
|
|
|
|
(input/keydown? :ControlLeft)) false
|
|
|
|
(and (not prev-continue?)
|
|
|
|
(input/keydown? :Digit3)
|
|
|
|
(input/keydown? :ControlLeft)) true
|
|
|
|
:else prev-continue?))
|
|
|
|
|
|
|
|
(defn update-step
|
|
|
|
"updates timing information and the current scene"
|
|
|
|
[gamestate]
|
|
|
|
(let [now (.now js/performance)
|
|
|
|
secs (elapsed-seconds gamestate now)
|
|
|
|
scene (curr-scene gamestate)
|
|
|
|
continue? (continue-running? (:continue? gamestate))]
|
|
|
|
(as-> gamestate $
|
|
|
|
(assoc-in $ [:input :dir] (input/dir))
|
|
|
|
(assoc $ :timing {:now now
|
|
|
|
:elapsed secs
|
|
|
|
:fps (curr-fps secs)})
|
|
|
|
(assoc-in $ [:scenes (:scene gamestate)]
|
|
|
|
(if continue?
|
|
|
|
(run-scene-update $ scene)
|
|
|
|
scene))
|
|
|
|
(assoc-in $ [:timing :prev] (.now js/performance)))))
|
|
|
|
|
|
|
|
(defn draw-fps
|
|
|
|
"draws the current fps"
|
|
|
|
[gamestate]
|
|
|
|
(let [ctx (:ctx gamestate)]
|
|
|
|
(aset ctx "fillStyle" "white")
|
|
|
|
(.fillRect ctx
|
|
|
|
0 0 13 13)
|
|
|
|
(aset ctx "fillStyle" "black")
|
|
|
|
(aset ctx "font" "10px monospace")
|
|
|
|
(.fillText (:ctx gamestate)
|
|
|
|
(int (get-in gamestate [:timing :fps]))
|
|
|
|
0 10)))
|
|
|
|
|
|
|
|
(defn draw-step
|
|
|
|
"clears the canvas, draws fps and invokes the scene draw function"
|
|
|
|
[gamestate]
|
|
|
|
(.clearRect (:ctx gamestate)
|
|
|
|
0 0
|
|
|
|
(get-in gamestate [:dimensions :w])
|
|
|
|
(get-in gamestate [:dimensions :h]))
|
|
|
|
(let [scenekey (:scene gamestate)
|
|
|
|
{:keys [draw] :as scene} (-> gamestate :scenes scenekey)]
|
|
|
|
(draw gamestate scene))
|
|
|
|
(draw-fps gamestate))
|
|
|
|
|
|
|
|
(defn mainloop
|
|
|
|
"transforms the given gamestate by invoking a series of update
|
|
|
|
functions and draws it using the 2d context of the gamestate.
|
|
|
|
then, it calls itself again using requestAnimationFrame"
|
|
|
|
[gamestate]
|
|
|
|
(let [newstate (update-step gamestate)]
|
|
|
|
(draw-step newstate)
|
|
|
|
(when-not @reloaded
|
|
|
|
(.requestAnimationFrame js/window
|
|
|
|
#(mainloop newstate)))))
|
|
|
|
|
|
|
|
(defn init-scenes
|
|
|
|
"initiates the scene data maps using their respective init functions"
|
|
|
|
[]
|
|
|
|
(set! (.-width (:canvas gamestate))
|
|
|
|
(-> gamestate :dimensions :w))
|
|
|
|
(set! (.-height (:canvas gamestate))
|
|
|
|
(-> gamestate :dimensions :h))
|
|
|
|
(set! (.-imageSmoothingEnabled (:ctx gamestate))
|
|
|
|
false)
|
|
|
|
(update gamestate
|
|
|
|
:scenes
|
|
|
|
#(reduce
|
|
|
|
(fn [carr [key scene]]
|
|
|
|
(assoc carr key ((:init scene) gamestate scene)))
|
|
|
|
{} %)))
|
|
|
|
|
|
|
|
(defn fig-reload []
|
|
|
|
(reset! reloaded true)
|
|
|
|
(.setTimeout js/window
|
|
|
|
(fn []
|
|
|
|
(reset! reloaded false)
|
|
|
|
(mainloop (init-scenes)))
|
|
|
|
1000))
|
|
|
|
|
|
|
|
(mainloop (init-scenes))
|