diff options
| -rw-r--r-- | camera.fnl | 45 | ||||
| -rw-r--r-- | game.fnl | 33 | ||||
| -rw-r--r-- | player.fnl | 61 | ||||
| -rw-r--r-- | world.fnl | 143 |
4 files changed, 225 insertions, 57 deletions
@@ -15,9 +15,46 @@ ;;;; You should have received a copy of the GNU General Public License along ;;;; with slime-the-world. If not, see <http://www.gnu.org/licenses/>. -;; Returns a new camera object initially focused at the given world coordinates, -;; (`x', `y'). -(fn new-camera [x y] - {:x-pos x :y-pos y}) +(local lume (require :lib.lume)) + +(local tile-sheet (. (require :world) :tile-sheet)) + +;; TODO: Document this +(fn focus-on-object [camera object dt] + (let [last-x (. camera :x-pos) + last-y (. camera :y-pos) + max-x (. camera :max-x) + max-y (. camera :max-y) + object-x (. object :x-pos) + object-y (. object :y-pos) + width (. object :width) + height (. object :height) + screen-width (. camera :screen-width) + screen-height (. camera :screen-height) + x-offset (math.floor (- (/ screen-width 2) (/ width 2))) + y-offset (math.floor (- (/ screen-height 2) (/ height 2))) + x (lume.lerp last-x (- object-x x-offset) (* 4 dt)) + x (lume.clamp x 0 max-x) + y (lume.lerp last-y (- object-y y-offset) (* 4 dt)) + y (lume.clamp y 0 max-y)] + (values x y))) + +;; Returns a new camera object, aware of the bounds of `world' and the camera +;; lock parameters entailed by the given screen dimensions. +(fn new-camera [world screen-width screen-height] + (let [tile-width (. tile-sheet :width) + tile-height (. tile-sheet :height) + max-x (- (* tile-width (. world :width)) screen-width) + max-y (- (* tile-height (. world :height)) screen-height)] + {:x-pos 0 + :y-pos 0 + + :screen-width screen-width + :screen-height screen-height + + :max-x max-x + :max-y max-y + + :focus-on-object focus-on-object})) {:new new-camera} @@ -15,7 +15,7 @@ ;;;; You should have received a copy of the GNU General Public License along ;;;; with slime-the-world. If not, see <http://www.gnu.org/licenses/>. -;; (local camera (require :camera)) +(local camera (require :camera)) (local player (require :player)) (local world (require :world)) @@ -50,7 +50,7 @@ ;; (fn update [dt set-mode] -;; (local gravity 128) +;; ;; (local camera-lock-goal-x (math.floor (- (/ screen-width 2) ;; (/ (. player-sheet :width) 2)))) ;; (local camera-lock-goal-y (math.floor (- (/ screen-height 2) @@ -156,20 +156,31 @@ (local current-player (player.new)) (local current-world (world.new "sandbox" current-player)) -(: current-player :position-at 128 128) - -(var camera-x 0) -(var camera-y 0) +(local current-camera (camera.new current-world screen-width screen-height)) +(: current-world :position-player-at 128 128) (fn draw [message] - (: current-world :draw camera-x camera-y screen-width screen-height) - (: current-player :draw 128 128)) + (let [camera-x (. current-camera :x-pos) + camera-y (. current-camera :y-pos)] + (: current-world :draw camera-x camera-y screen-width screen-height))) -(fn update [dt set-mode]) +(fn update [dt set-mode] + (: current-world :update dt) + (let [(camera-x camera-y) (: current-camera :focus-on-object current-player dt)] + (tset current-camera :x-pos camera-x) + (tset current-camera :y-pos camera-y))) -(fn keypressed [key set-mode]) +(fn keypressed [key set-mode] + (let [method (if (= key "right") :move-right + (= key "left") :move-left)] + (when method + (: current-player method)))) -(fn keyreleased [key set-mode]) +(fn keyreleased [key set-mode] + (let [method (if (= key "right") :stop-right + (= key "left") :stop-left)] + (when method + (: current-player method)))) {:draw draw :update update @@ -15,23 +15,65 @@ ;;;; You should have received a copy of the GNU General Public License along ;;;; with slime-the-world. If not, see <http://www.gnu.org/licenses/>. +(local lume (require :lib.lume)) + (local draw (require :draw)) +(local gravity 128) + +;; Returns a pair, (x, y) that represents the player's next position, assuming +;; that it did /not/ collide with anything. The role of collision detection, and +;; further, setting the player's position, is on the world. +(fn next-position [player dt] + (let [x (+ (. player :x-pos) (* dt (. player :x-vel))) + y (+ (. player :y-pos) (* dt (. player :y-vel)))] + (values x y))) + +(fn next-x-vel [player dt] + (let [last-x-vel (. player :x-vel) + moving-right (. player :action :right) + moving-left (. player :action :left) + abs-x-vel (. player :goal-x-vel) + goal-x-vel (if moving-right abs-x-vel moving-left (- abs-x-vel) 0)] + (lume.lerp last-x-vel goal-x-vel dt))) + +(fn next-y-vel [player dt] + (let [last-y-vel (. player :y-vel) + jumping (. player :action :jump) + grounded (. player :grounded) + jumping-y-vel (- last-y-vel (. player :goal-y-vel))] + (if (and jumping grounded) jumping-y-vel + grounded 0 + :else (+ (. player :y-vel) (* gravity dt))))) + +(fn impact-bottom [player] + (tset player :grounded true)) + +(fn impact-top [player]) + +(fn impact-left [player]) + +(fn impact-right [player]) + (fn move-right [player] (tset player :orientation :right) (tset player :action :right true) (tset player :action :left false) (tset player :x-vel (/ (. player :x-vel) 2))) +(fn stop-right [player] + (tset player :action :right false) + (tset player :x-vel (/ (. player :x-vel) 2))) + (fn move-left [player] (tset player :orientation :left) (tset player :action :left true) (tset player :action :right false) (tset player :x-vel (/ (. player :x-vel) 2))) -(fn position-at [player x y] - (tset player :x-pos x) - (tset player :y-pos y)) +(fn stop-left [player] + (tset player :action :left false) + (tset player :x-vel (/ (. player :x-vel) 2))) (local player-sheet {:img (love.graphics.newImage "art/swanky.png") @@ -40,7 +82,8 @@ :padding-x 8 :padding-y 10}) (fn draw-player [player x y] - (draw.tile x y player-sheet 0 0)) + (let [x-offset (if (= (. player :orientation) :left) 4 0)] + (draw.tile x y player-sheet x-offset 0))) ;; Returns a new player object. (fn new-player [] @@ -67,9 +110,17 @@ :right false :left false} - :position-at position-at + :next-position next-position + :next-x-vel next-x-vel + :next-y-vel next-y-vel + :impact-bottom impact-bottom + :impact-top impact-top + :impact-left impact-left + :impact-right impact-right :move-right move-right + :stop-right stop-right :move-left move-left + :stop-left stop-left :draw draw-player}) @@ -20,13 +20,22 @@ (local draw (require :draw)) +;; Updates the position of the player contained in `world' to the world +;; coordinates, (`x', `y'). +(fn position-player-at [world x y] + (let [player (. world :player) + collision-map (. world :collision-map)] + (tset player :x-pos x) + (tset player :y-pos y) + (: collision-map :update player x y))) + ;; Returns the tile at the given grid coordinates. -(fn tile-at [map x y] +(fn tile-at [world x y] (if (and (>= x 0) - (< x (. map :width)) + (< x (. world :width)) (>= y 0) - (< y (. map :height))) - (. map :tiles (+ 1 y) (+ 1 x)) + (< y (. world :height))) + (. world :tiles (+ 1 y) (+ 1 x)) nil)) (local tile-sheet @@ -47,12 +56,11 @@ (let [tile-offset (. slime-sheet :offsets orientation)] (draw.tile x y slime-sheet tile-offset 0))) - ;; Draws all slimed surfaces for a tile at screen coordinates, (`x', `y'). (fn draw-slime [tile x y] (each [orientation slimed (pairs (. tile :slimed))] (when slimed - (draw-slime-decal tile orientation x y)))) + (draw-slime-decal orientation x y)))) ;; Draws a single tile at the screen coordinates, (`x', `y'). (fn draw-tile [tile x y] @@ -61,25 +69,43 @@ (draw-slime tile x y))) ;; ;; Drawing routine for rendering the tiles visible from a given camera offset. -(fn draw-map [map camera-x camera-y screen-width screen-height] +(fn draw-map [world camera-x camera-y screen-width screen-height] (let [tile-width (. tile-sheet :width) tile-height (. tile-sheet :height) how-many-x (+ 1 (/ screen-width tile-width)) how-many-y (+ 1 (/ screen-height tile-height)) start-x (math.floor (/ camera-x tile-width)) start-y (math.floor (/ camera-y tile-height)) - width (. map :width) - height (. map :height)] + width (. world :width) + height (. world :height)] (for [x-offset 0 how-many-x] (for [y-offset 0 how-many-y] (when (and (and (>= (+ start-x x-offset) 0) (< (+ start-x x-offset) width)) (and (>= (+ start-y y-offset) 0) (< (+ start-y y-offset) height))) - (let [tile (tile-at map (+ start-x x-offset) (+ start-y y-offset)) + (let [tile (tile-at world (+ start-x x-offset) (+ start-y y-offset)) screen-x (- (* x-offset tile-width) (% camera-x tile-width)) - screen-y (- (* y-offset tile-height) (% camera-y tile-height))] + screen-x (lume.round screen-x) + screen-y (- (* y-offset tile-height) (% camera-y tile-height)) + screen-y (lume.round screen-y)] (when (~= :empty (. tile :type)) (draw-tile tile screen-x screen-y)))))))) +(fn draw-world [world camera-x camera-y screen-width screen-height] + (love.graphics.clear 0.1 0.1 0.1) + (draw-map world camera-x camera-y screen-width screen-height) + (let [player (. world :player) + player-x (. player :x-pos) + player-y (. player :y-pos) + x (- player-x camera-x) + y (- player-y camera-y)] + (: player :draw x y)) + ;; TODO: Refactor this into multiple functions + (love.graphics.print (string.format "%d/%d" (. world :surfaces-slimed) + (. world :surfaces-total)) + 0 0)) + + + ;; Returns whether or not `tile' exists in `checked'. (fn tile-checked [tile checked] (var res false) @@ -90,7 +116,7 @@ res) ;; Modified implementation of <https://en.wikipedia.org/wiki/Flood_fill>. -(fn count-surfaces-recur [map tile checked] +(fn count-surfaces-recur [world tile checked] (var surfaces 0) (when (not (tile-checked tile checked)) @@ -98,52 +124,52 @@ (let [x (/ (. tile :x-pos) (. tile-sheet :width)) y (/ (. tile :y-pos) (. tile-sheet :height)) - width (. map :width) - height (. map :height)] + width (. world :width) + height (. world :height)] (when (>= x 0) - (let [tile (tile-at map (- x 1) y)] + (let [tile (tile-at world (- x 1) y)] (if (= :empty (. tile :type)) - (set surfaces (+ surfaces (count-surfaces-recur map tile checked))) + (set surfaces (+ surfaces (count-surfaces-recur world tile checked))) (set surfaces (+ 1 surfaces))))) (when (< x width) - (let [tile (tile-at map (+ x 1) y)] + (let [tile (tile-at world (+ x 1) y)] (if (= :empty (. tile :type)) - (set surfaces (+ surfaces (count-surfaces-recur map tile checked))) + (set surfaces (+ surfaces (count-surfaces-recur world tile checked))) (set surfaces (+ 1 surfaces))))) (when (>= y 0) - (let [tile (tile-at map x (- y 1))] + (let [tile (tile-at world x (- y 1))] (if (= :empty (. tile :type)) - (set surfaces (+ surfaces (count-surfaces-recur map tile checked))) + (set surfaces (+ surfaces (count-surfaces-recur world tile checked))) (set surfaces (+ 1 surfaces))))) (when (< y height) - (let [tile (tile-at map x (+ y 1))] + (let [tile (tile-at world x (+ y 1))] (if (= :empty (. tile :type)) - (set surfaces (+ surfaces (count-surfaces-recur map tile checked))) + (set surfaces (+ surfaces (count-surfaces-recur world tile checked))) (set surfaces (+ 1 surfaces))))))) surfaces) -;; Returns some tile in `map' of type `tile-type', or nil if no such tile is +;; Returns some tile in `world' of type `tile-type', or nil if no such tile is ;; present. -(fn find-any [tile-type map] +(fn find-any [tile-type world] (var res nil) - (let [width (. map :width) - height (. map :height)] + (let [width (. world :width) + height (. world :height)] (for [x 0 (- width 1)] (for [y 0 (- height 1)] - (let [tile (tile-at map x y)] + (let [tile (tile-at world x y)] (when (= tile-type (. tile :type)) (set res tile)))))) res) ;; Returns the number of slime-able surfaces in the given grid of tiles. -(fn count-surfaces [map] - (let [seed (find-any :empty map)] +(fn count-surfaces [world] + (let [seed (find-any :empty world)] (when seed - (count-surfaces-recur map seed [])))) + (count-surfaces-recur world seed [])))) (fn add-player-to-collision-map [collision-map player] (let [x (. player :x-pos) @@ -157,17 +183,17 @@ y (. tile :y-pos) width (. tile :width) height (. tile :height)] - (: collision-map :add tile (* x width) (* y height) width height))) + (: collision-map :add tile x y width height))) ;; Returns a new collision map containing `player' and everything in `map'. -(fn new-collision-map [map player] +(fn new-collision-map [world player] (let [collision-map (bump.newWorld 32) - width (. map :width) - height (. map :height)] + width (. world :width) + height (. world :height)] (add-player-to-collision-map collision-map player) (for [x 0 (- width 1)] (for [y 0 (- height 1)] - (let [tile (tile-at map x y)] + (let [tile (tile-at world x y)] (when (~= :empty (. tile :type)) (add-tile-to-collision-map collision-map tile))))) collision-map)) @@ -221,17 +247,60 @@ (fn load-meta [path] (fennel.dofile path)) +(fn slime-tile [world tile orientation] + (when (not (. tile :slimed orientation)) + (let [surfaces-slimed (. world :surfaces-slimed)] + (tset world :surfaces-slimed (+ 1 surfaces-slimed)) + (tset tile :slimed orientation true)))) + +(fn update-world [world dt] + (let [player (. world :player) + collision-map (. world :collision-map) + (goal-x goal-y) (: player :next-position dt) + (x y collisions _) (: collision-map :move player goal-x goal-y)] + (tset player :x-pos x) + (tset player :y-pos y) + + (each [_ collision (ipairs collisions)] + ;; Touching top of surface. + (when (> 0 (. collision :normal :y)) + (: player :impact-bottom) + (slime-tile world (. collision :other) :top)) + + ;; Touching bottom of surface. + (when (< 0 (. collision :normal :y)) + (: player :impact-top) + (slime-tile world (. collision :other) :bottom)) + + ;; Touching left of surface. + (when (> 0 (. collision :normal :x)) + (: player :impact-right) + (slime-tile world (. collision :other) :left)) + + ;; Touching right of surface. + (when (< 0 (. collision :normal :x)) + (: player :impact-left) + (slime-tile world (. collision :other) :right))) + + (tset player :x-vel (: player :next-x-vel dt)) + (tset player :y-vel (: player :next-y-vel dt)) + (tset player :grounded false))) + ;; Loads the map of the given name into a new world containing `player'. Will ;; error out if either 'maps/${name}.png' or 'maps/${name}.fnl' do not exist. (fn new-world [name player] (let [tiles-path (.. "maps/" name ".png") meta-path (.. "maps/" name ".fnl") res (load-tiles (love.image.newImageData tiles-path)) + res (lume.extend res {:player player}) res (lume.extend res {:surfaces-slimed 0 :meta (load-meta meta-path)}) res (lume.extend res {:surfaces-total (count-surfaces res)}) res (lume.extend res {:collision-map (new-collision-map res player)}) - res (lume.extend res {:draw draw-map})] + res (lume.extend res {:position-player-at position-player-at}) + res (lume.extend res {:draw draw-world}) + res (lume.extend res {:update update-world})] res)) -{:tile-at tile-at +{:tile-sheet tile-sheet + :tile-at tile-at :new new-world} |