Compare commits
6 Commits
0e24e171da
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bd7ad1a28 | |||
| 507faf8875 | |||
| befcdfc352 | |||
| a179e453b1 | |||
| ba10d04fa2 | |||
| f11ce94adf |
@@ -4,8 +4,6 @@
|
||||
["pixi.js" :as pixi]
|
||||
[goog.dom :as dom]
|
||||
[cljs.core.async :as async :refer [go go-loop <! >!]]
|
||||
[il.common.race :as race]
|
||||
[il.common.class :as class]
|
||||
[frontend.il.entity :as entity]
|
||||
[frontend.il.update :as update]))
|
||||
|
||||
@@ -25,7 +23,7 @@
|
||||
right :a
|
||||
up :w
|
||||
down :s} (get-in @state [:player :keymap])
|
||||
speed (get-in @state [:player :speed] 5)]
|
||||
speed (get-in @state [:player :speed] 2.33)]
|
||||
(swap! state update-in [:player :velocity] assoc
|
||||
:x (* speed (+ left right))
|
||||
:y (* speed (+ up down))))))
|
||||
@@ -38,12 +36,12 @@
|
||||
(when (count outbox)
|
||||
(go
|
||||
(doseq [msg outbox]
|
||||
(println state)
|
||||
(println "Writing event: " msg)
|
||||
(js/console.log (clj->js state))
|
||||
(js/console.log "Writing event: " (clj->js msg))
|
||||
(>! tx-chan (js/JSON.stringify (clj->js msg))))))
|
||||
(assoc state :outbox []))
|
||||
|
||||
(defn ticker [app state layers rx-chan tx-chan t]
|
||||
(defn ticker [app state layers sb rx-chan tx-chan t]
|
||||
(let [dt (.-deltaTime t)
|
||||
{world-layer :world-layer
|
||||
entity-layer :entity-layer} layers
|
||||
@@ -51,9 +49,10 @@
|
||||
entity-relative-mouse-pos (.toLocal ^js entity-layer global-mouse-pos)
|
||||
send-interval 25 ; Send every 25ms (10 tps)
|
||||
elapsed (.-elapsedMS t)
|
||||
{:keys [player entities]} (-> (update @state :elapsed + elapsed)
|
||||
(update/update-entities dt layers)
|
||||
(update/update-player dt send-interval layers entity-relative-mouse-pos)
|
||||
{:keys [player entities players]} (-> (update @state :elapsed + elapsed)
|
||||
(update/update-state :entities dt layers)
|
||||
(update/update-state :players dt layers)
|
||||
(update/update-player dt send-interval layers entity-relative-mouse-pos sb)
|
||||
(update/update-from-server rx-chan)
|
||||
(drain-outbox! tx-chan)
|
||||
(->> (swap! state merge)))
|
||||
@@ -62,35 +61,35 @@
|
||||
entity-renderables (->> (vals entities)
|
||||
(remove #(nil? (% :renderable)))
|
||||
(vec))
|
||||
other-players-renderables (->> (vals players)
|
||||
(remove #(nil? (% :renderable)))
|
||||
(vec))
|
||||
world-renderables (vector {:renderable world-layer :x sx :y sy})
|
||||
player-renderables (vector player
|
||||
(assoc player :renderable (:shootbox player)))
|
||||
things-to-update (concat player-renderables entity-renderables world-renderables)]
|
||||
things-to-update (concat player-renderables
|
||||
other-players-renderables
|
||||
entity-renderables
|
||||
world-renderables)]
|
||||
(doseq [thing things-to-update]
|
||||
; TODO: Add lerp here, we shouldn't just launch things around with their X/Y
|
||||
; instead if the difference between curr-pos and tar-pos is > than vel try to move it to their every frame via vel
|
||||
(let [{renderable :renderable
|
||||
x :x
|
||||
y :y} thing]
|
||||
(when renderable
|
||||
(js/console.log "Rendering thing: " x y)
|
||||
(set! (.. renderable -x) x)
|
||||
(set! (.. renderable -y) y)))))
|
||||
(set! (.. renderable -y) y))))))
|
||||
|
||||
(defn init []
|
||||
(let [app (doto (pixi/Application.)
|
||||
(.init {:resizeTo js/window :autoDensity true :resolution (.-devicePixelRatio js/window)}))
|
||||
state (atom {:player {:velocity {:x 0 :y 0}
|
||||
:keymap {:w 0 :a 0 :s 0 :d 0 :lmb 0}
|
||||
:last-attack -1
|
||||
:attack-speed 0.66
|
||||
:meta {:name "Player"
|
||||
:race race/human
|
||||
:class class/priest}
|
||||
:sprite "https://pixijs.com/assets/bunny.png"
|
||||
:speed 10
|
||||
:renderable nil}
|
||||
:last-attack -1}
|
||||
:elapsed 0
|
||||
:outbox []
|
||||
:entities {}})
|
||||
:entities {}
|
||||
:players {}})
|
||||
width (.-width js/window.screen)
|
||||
height (.-height js/window.screen)
|
||||
stage (.-stage app)
|
||||
@@ -102,6 +101,11 @@
|
||||
layers {:entity-layer entity-layer
|
||||
:world-layer world-layer
|
||||
:ui-layer ui-layer}
|
||||
sb-callback (fn [v _] (swap! state update-in [:player :keymap] assoc :lmb v))
|
||||
sb (entity/player-shoot-box
|
||||
entity-layer
|
||||
(partial sb-callback true)
|
||||
(partial sb-callback false))
|
||||
websocket (doto (js/WebSocket. (str "ws://" js/location.host "/ws"))
|
||||
(.addEventListener "open" #(go-loop []
|
||||
(when-let [msg (<! tx-chan)]
|
||||
@@ -121,15 +125,4 @@
|
||||
(.addChild world-layer) ; Bottom of the layer stack
|
||||
(.addChild entity-layer) ; Middle of the layer stack
|
||||
(.addChild ui-layer)) ; Top of the layer stack
|
||||
(let [e (merge {:x (-> width (/ 2) (int))
|
||||
:y (-> height (/ 2) (int))}
|
||||
(@state :player))
|
||||
p (entity/render entity-layer e)
|
||||
sb-callback (fn [v _] (swap! state update-in [:player :keymap] assoc :lmb v))
|
||||
sb (entity/player-shoot-box
|
||||
entity-layer
|
||||
(partial sb-callback true)
|
||||
(partial sb-callback false))
|
||||
player (assoc e :renderable p :shootbox sb)]
|
||||
(swap! state update-in [:player] merge player)
|
||||
(.. app -ticker (add (partial ticker app state layers rx-chan tx-chan)))))))))
|
||||
(.. app -ticker (add (partial ticker app state layers sb rx-chan tx-chan))))))))
|
||||
|
||||
@@ -2,33 +2,41 @@
|
||||
(:require
|
||||
[frontend.il.entity :as entity]
|
||||
[cljs.core.async :as async]
|
||||
[il.common.velocity :as v]))
|
||||
[il.common.math :as ilmath]))
|
||||
|
||||
(defn apply-velocity [e dt]
|
||||
(if (contains? e :velocity)
|
||||
(let [{x :x y :y vel :velocity} e
|
||||
{vx :x vy :y} vel
|
||||
dtvx (* dt vx)
|
||||
dtvy (* dt vy)]
|
||||
dtvx (Math/floor (* dt vx))
|
||||
dtvy (Math/floor (* dt vy))]
|
||||
(assoc e :x (+ x dtvx) :y (+ y dtvy)))
|
||||
e))
|
||||
|
||||
(defn apply-renderable [e layers]
|
||||
(if-not (contains? e :renderable)
|
||||
(if (and (nil? (e :renderable)) (e :sprite))
|
||||
(let [{entity-layer :entity-layer} layers
|
||||
renderable (entity/render entity-layer e)]
|
||||
(assoc e :renderable renderable))
|
||||
e))
|
||||
|
||||
|
||||
(defn apply-server-position [e]
|
||||
(let [{:keys [x y tx ty]} e
|
||||
speed (get e :speed 15)
|
||||
min-distance-for-update 1
|
||||
has-server-pos (not (and (nil? tx) (nil? ty)))
|
||||
has-actual-pos (not (and (nil? x) (nil? y)))]
|
||||
has-actual-pos (not (and (nil? x) (nil? y)))
|
||||
velocityless-entity (dissoc e :velocity)]
|
||||
(cond
|
||||
(and has-server-pos (not has-actual-pos)) (assoc e :x tx :y ty)
|
||||
(and has-server-pos has-actual-pos) (assoc e :velocity (v/velocity-for x y tx ty speed))
|
||||
; If the position given by the server is the same as the current position, do nothing
|
||||
(and has-server-pos (= x tx) (= y ty)) velocityless-entity
|
||||
; When the entity has a rendered position, and a new server position
|
||||
(and has-server-pos has-actual-pos) (let [delta (ilmath/euclidean-distance x y tx ty)]
|
||||
(if (> delta min-distance-for-update)
|
||||
(assoc velocityless-entity :velocity (ilmath/velocity-for x y tx ty speed))
|
||||
velocityless-entity))
|
||||
; When the entity has no rendered position, and a new server position
|
||||
has-server-pos (assoc velocityless-entity :x tx :y ty)
|
||||
:else e)))
|
||||
|
||||
(defn apply-player-shoot [state mouse-pos]
|
||||
@@ -36,11 +44,13 @@
|
||||
(< (+ (get-in state [:player :last-attack] -1)
|
||||
(* 1000 (get-in state [:player :attack-speed] 0.75)))
|
||||
(js/Date.now)))
|
||||
(let [{x :x y :y} (state :player)]
|
||||
(-> state
|
||||
(update :outbox conj {:type "shoot"
|
||||
:player {:x x :y y}
|
||||
:x (.-x mouse-pos)
|
||||
:y (.-y mouse-pos)})
|
||||
(assoc-in [:player :last-attack] (js/Date.now)))
|
||||
(assoc-in [:player :last-attack] (js/Date.now))))
|
||||
state))
|
||||
|
||||
(defn apply-player-position [state send-interval]
|
||||
@@ -54,23 +64,26 @@
|
||||
(update :outbox conj {:type "position" :x x :y y}))
|
||||
state)))
|
||||
|
||||
;; TODO: Cleanup entities somehow
|
||||
(defn update-entities [state dt layers]
|
||||
(let [{entities :entities} state
|
||||
new-entities (update-vals entities #(-> %
|
||||
(defn update-state [state key dt layers]
|
||||
(let [{v key} state
|
||||
_ (js/console.log (clj->js v))
|
||||
new-values (update-vals v #(-> %
|
||||
(apply-server-position)
|
||||
(apply-renderable layers)
|
||||
(apply-velocity dt)))]
|
||||
(assoc state :entities new-entities)))
|
||||
(assoc state key new-values)))
|
||||
|
||||
(defn update-player [state dt send-interval layers mouse-pos]
|
||||
(defn update-player [state dt send-interval layers mouse-pos sb]
|
||||
(let [{player :player} state
|
||||
updated-player (-> player
|
||||
(apply-renderable layers)
|
||||
(assoc :shootbox sb)
|
||||
(apply-velocity dt))]
|
||||
(if-not (and (nil? player) (nil? (player :renderable)))
|
||||
(-> (assoc state :player updated-player)
|
||||
(apply-player-position send-interval)
|
||||
(apply-player-shoot mouse-pos))))
|
||||
(apply-player-shoot mouse-pos))
|
||||
(update state :outbox conj {:type "request-init"}))))
|
||||
|
||||
(defn- deep-merge [& maps]
|
||||
(apply merge-with
|
||||
@@ -83,14 +96,27 @@
|
||||
(defn update-from-server [state rx-chan]
|
||||
(loop [state-acc state
|
||||
{:keys [type] :as msg} (async/poll! rx-chan)]
|
||||
(if (and (not (nil? msg)) (= type "sync"))
|
||||
(let [entities (msg :entities)
|
||||
(if-not (nil? msg)
|
||||
(cond
|
||||
(= type "init") (update state-acc :player merge (dissoc msg :type))
|
||||
(= type "sync") (let [entities (msg :entities)
|
||||
players (if-not (nil? (get-in state-acc [:player :id]))
|
||||
(dissoc (msg :players) (get-in state-acc [:player :id]))
|
||||
{})
|
||||
state-entities (state-acc :entities)
|
||||
state-players (state-acc :players)
|
||||
tx-entities (update-vals entities (fn [e] {:sprite (:sprite e)
|
||||
:tx (:x e)
|
||||
:ty (:y e)}))
|
||||
merged-entities (deep-merge state-entities tx-entities)]
|
||||
tx-players (update-vals players (fn [e] {:sprite (:sprite e)
|
||||
:tx (:x e)
|
||||
:ty (:y e)}))
|
||||
merged-entities (deep-merge state-entities tx-entities)
|
||||
merged-players (deep-merge state-players tx-players)]
|
||||
(recur
|
||||
(assoc state-acc :entities merged-entities)
|
||||
(assoc state-acc
|
||||
:entities merged-entities
|
||||
:players merged-players)
|
||||
(async/poll! rx-chan)))
|
||||
:else state-acc)
|
||||
state-acc)))
|
||||
|
||||
1
src/il/common/constant.cljc
Normal file
1
src/il/common/constant.cljc
Normal file
@@ -0,0 +1 @@
|
||||
(ns il.common.constant)
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns il.common.velocity)
|
||||
(ns il.common.math)
|
||||
|
||||
(defn velocity-for [x y tx ty speed]
|
||||
(let [dx (- tx x)
|
||||
@@ -9,3 +9,6 @@
|
||||
(if (zero? h)
|
||||
{:x 0.0 :y 0.0}
|
||||
{:x vx :y vy})))
|
||||
|
||||
(defn euclidean-distance [x1 y1 x2 y2]
|
||||
(Math/sqrt (+ (Math/pow (- x2 x1) 2) (Math/pow (- y2 y1) 2))))
|
||||
@@ -1 +0,0 @@
|
||||
(ns il.constant)
|
||||
@@ -5,33 +5,41 @@
|
||||
[compojure.route :as route]
|
||||
[clojure.core.async :as async :refer [<! >! go-loop]]
|
||||
[hiccup.core :as h]
|
||||
[il.player :as p]
|
||||
[il.state :as s]
|
||||
[il.json :as j]
|
||||
[il.handler :as handler]
|
||||
[il.system.movement :as movement]))
|
||||
|
||||
(defn ws-handler [state state-chan player-id]
|
||||
(println "Starting session for " player-id)
|
||||
(go-websocket [in out err]
|
||||
(s/change state-chan update :players assoc player-id {:out out :in in :err err :x 0 :y 0})
|
||||
(go-loop []
|
||||
(when-let [e (<! err)]
|
||||
(println (.getMessage e))
|
||||
(recur)))
|
||||
(go-loop []
|
||||
(if-not (contains? (@state :players) player-id)
|
||||
(do (s/change state-chan update :players assoc player-id {:out out :in in :err err
|
||||
:x 0 :y 0
|
||||
:initialized false
|
||||
:data (merge p/default-player {:id player-id})})
|
||||
(recur))
|
||||
(if-let [raw (<! in)]
|
||||
(when (contains? (@state :players) player-id) ; Only if player has been initialized do we accept messages
|
||||
(let [msg (j/from-json raw)
|
||||
type (get msg :type "invalid")]
|
||||
type (get msg :type "invalid")
|
||||
state @state]
|
||||
(cond
|
||||
(= type "position") (handler/handle-position @state state-chan player-id msg)
|
||||
(= type "shoot") (handler/handle-shoot @state state-chan player-id msg)
|
||||
(= type "invalid") (>! out "Hello World!"))
|
||||
(= type "position") (handler/handle-position state state-chan player-id msg)
|
||||
(= type "shoot") (handler/handle-shoot state state-chan player-id msg)
|
||||
(= type "request-init") (handler/handle-request-init state state-chan player-id msg)
|
||||
(= type "invalid") (>! out "Invalid command!"))
|
||||
(println msg)
|
||||
(println @state))
|
||||
(println state)
|
||||
(recur))
|
||||
(do
|
||||
(s/change state-chan update :players dissoc player-id)
|
||||
(println "Closed: " player-id))))))
|
||||
(println "Closed: " player-id)))))))
|
||||
|
||||
(defn index-handler [_]
|
||||
(h/html
|
||||
@@ -53,7 +61,7 @@
|
||||
(defn game-loop [state state-chan]
|
||||
(go-loop []
|
||||
(let [start-time (System/currentTimeMillis)
|
||||
max-inputs-per-tick 15000 ; Stops the game from spinning for too long with events endlessly, dropping ticks
|
||||
max-inputs-per-tick 15000 ; Stops the game from spinning for too long with events endlessly dropping ticks
|
||||
events-to-apply (loop [inputs []
|
||||
count 0]
|
||||
(if (>= count max-inputs-per-tick)
|
||||
@@ -68,15 +76,32 @@
|
||||
system-state (reduce (fn [state system] (system state))
|
||||
event-state
|
||||
(event-state :systems))
|
||||
entities (:entities @state)
|
||||
players (:players @state)]
|
||||
(swap! state conj system-state)
|
||||
(doseq [{out :out} (vals players)]
|
||||
(>! out (j/to-json {:type "sync"
|
||||
:entities entities}))))
|
||||
updated-state (swap! state conj system-state)
|
||||
entities (updated-state :entities)
|
||||
players (updated-state :players)]
|
||||
(doseq [{:keys [initialized data out] :as player} (vals players)]
|
||||
(let [player-id (data :id)]
|
||||
(println player)
|
||||
(when-not (nil? out)
|
||||
(when-not initialized
|
||||
(let [coords (select-keys player [:x :y])
|
||||
data-to-send (conj data coords)
|
||||
msg (assoc data-to-send :type "init")]
|
||||
(println "Initializing " player-id)
|
||||
(async/put! out (j/to-json msg))
|
||||
(s/change state-chan update-in [:players player-id] assoc :initialized true)))
|
||||
(let [players (update-vals players (fn [e] (-> e
|
||||
(select-keys [:x :y :data])
|
||||
(#(merge (dissoc % :data) (get % :data))))))
|
||||
players-without-player-id (dissoc players player-id)] ; Strip the selected player from the update
|
||||
(async/put! out (j/to-json {:type "sync"
|
||||
:entities entities
|
||||
:players players-without-player-id})))))))
|
||||
|
||||
(let [elapsed (- (System/currentTimeMillis) start-time)
|
||||
sleep-time (max 0 (- 50 elapsed))]
|
||||
(<! (async/timeout sleep-time)))
|
||||
|
||||
(recur))))
|
||||
|
||||
(defn -main []
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
(ns il.handler
|
||||
(:require [il.state :as s]
|
||||
[il.common.velocity :as v]))
|
||||
[il.common.math :as ilmath]))
|
||||
|
||||
(defn handle-position [_ state-chan player-id {x :x y :y}]
|
||||
(s/change state-chan update-in [:players player-id] assoc :x x :y y))
|
||||
|
||||
(defn handle-shoot [state state-chan player-id msg]
|
||||
(defn handle-request-init [_ state-chan player-id _]
|
||||
(s/change state-chan update-in [:players player-id] assoc :initialized false))
|
||||
|
||||
(defn handle-shoot [_ state-chan player-id msg]
|
||||
(let [bullet-id (random-uuid)
|
||||
player (get-in state [:players player-id])
|
||||
; player (get-in state [:players player-id])
|
||||
{tx :x ty :y player :player} msg
|
||||
{x :x y :y} player
|
||||
{tx :x ty :y} msg
|
||||
speed 25 ; TODO: Get projectile speed from player weapon
|
||||
velocity (v/velocity-for x y tx ty speed)
|
||||
speed 50 ; TODO: Get projectile speed from player weapon
|
||||
velocity (ilmath/velocity-for x y tx ty speed)
|
||||
bullet {:sprite "/sprite/projectile.png" ; TODO: Render sprite based on player weapon
|
||||
:x x
|
||||
:y y
|
||||
:speed speed
|
||||
:player-id player-id
|
||||
:velocity velocity}]
|
||||
(s/change state-chan update :entities assoc bullet-id bullet)))
|
||||
|
||||
12
src/il/player.clj
Normal file
12
src/il/player.clj
Normal file
@@ -0,0 +1,12 @@
|
||||
(ns il.player
|
||||
(:require
|
||||
[il.common.race :as race]
|
||||
[il.common.class :as class]))
|
||||
|
||||
(def default-player
|
||||
{:attack-speed 0.66
|
||||
:meta {:name "Player"
|
||||
:race race/human
|
||||
:class class/priest}
|
||||
:sprite "https://pixijs.com/assets/bunny.png"
|
||||
:speed 10})
|
||||
Reference in New Issue
Block a user