(local util (require :lib.util)) (local dim (require :game.dim)) (local {: defmethod} (util.require :lib.multimethod)) (local {: direct : draw : update} (util.require :game.entity)) (local {: vec* : vec+ : edge : edge-crosses : held-directions : dirs-from-dir : vec-from-dirs} (util.require :game.helpers)) (local map (require :game.tilemap)) (local pacman (util.hot-table ...)) (set pacman.keymap {:up :up :down :down :left :left :right :right}) (defmethod draw :pacman (fn [{:pos [x y] :vel [dx dy] : dt-animation}] (let [mouthsize-max (if (and (= dx 0) (= dy 0)) 0 (/ math.pi 4)) mouthsize-ratio (math.abs (math.sin (* (or dt-animation 0) 16))) mouthsize (* mouthsize-max mouthsize-ratio) anglestart (if (< dx 0) math.pi (> dx 0) 0 (> dy 0) (/ math.pi 2) (+ math.pi (/ math.pi 2))) angle1 (+ anglestart mouthsize) angle2 (+ anglestart math.pi math.pi (- mouthsize))] (love.graphics.setColor 1 1 0) (love.graphics.arc :fill x y dim.halftile angle1 angle2)))) ; Pacman movement and collision rules ; * Pacman moves in the direction he was told until stopped by a wall ; * If the player tells Pacman to change direction but there is a wall there, ; Pacman's direction does not change ; * Turning a corner is funky: ; https://www.gamasutra.com/view/feature/132330/the_pacman_dossier.php?page=4 ; "Whenever Pac-Man makes a pre-turn or post-turn, his orientation changes, and he starts to ; move one pixel in his new direction for every pixel traveled in his old direction, effectively ; doubling his speed as he moves at a 45 degree angle. Once he reaches the centerline of the new ; direction's path, he starts moving purely in that direction and his speed returns to normal." ; The simplest way to implement this appears to be to treat turns as having a "fudge factor" - ; Pacman enters a state where he changes direction _and_ is being pulled towards the center of ; the track. (The "post-turn" correction means we can't simply treat this as the corners having ; their edges filed off!) ; I think Bomberman actually does this too (fn tile-at [x y rules] (-?> [x y] (map.world-to-tile) (rules.tile-at))) (fn collides? [[x y] rules] (let [tile (tile-at x y rules)] (and tile (or tile.bomb tile.wall)))) (fn horizontal [c copp] [c copp]) (fn vertical [c copp] [copp c]) (fn perp-move? [dprev doppnext] (and (not= dprev 0) (not= doppnext 0))) (fn try-perp-turn [entity d make-xy rules] (let [[c copp] (make-xy (table.unpack entity.pos)) posperp (vec+ entity.pos (make-xy (if (> d 0) dim.tilesize (- dim.tilesize)) 0)) [xcenter ycenter] (vec+ (vec* (map.world-to-tile posperp) dim.tilesize) dim.halftile) [ccenter coppcenter] (make-xy xcenter ycenter) turn-allowed? (and (> copp (- coppcenter (/ dim.tilesize 4))) (< copp (+ coppcenter (/ dim.tilesize 4))))] (when (and turn-allowed? (not (collides? posperp rules))) (set entity.vel (make-xy d 0)) (set entity.opp-target coppcenter)))) (fn rev-move? [dprev dprevopp dnext] ; currently moving on this axis or not moving at all (and (or (not= dprev 0) (and (= dprev 0) (= dprevopp 0))) ; attempting to move in a new direction (not= dnext 0) (not= dprev dnext))) (fn update-direction [entity rules] (let [[dx dy] entity.vel [dxkey dykey] (vec* (vec-from-dirs (dirs-from-dir entity.dir)) (* dim.tilesize 4))] ; two possible movement cases: ; 1. Pacman wants to change direction perpendicular to how he is currently moving ; 2. Pacman wants to change direction on the axis he is currently moving on ; Case 1 takes priority over case 2 (if (perp-move? dy dxkey) (try-perp-turn entity dxkey horizontal rules) (perp-move? dx dykey) (try-perp-turn entity dykey vertical rules) (rev-move? dx dy dxkey) (set entity.vel [dxkey 0]) (rev-move? dy dx dykey) (set entity.vel [0 dykey])))) (fn move [entity dt rules] (let [[dx dy] (vec* entity.vel dt) make-xy (if (= dy 0) horizontal vertical) [c copp] (make-xy (table.unpack entity.pos)) [dc _] (make-xy dx dy) (edge-prev edge-next) (edge c dc dim.halftile) (crosses? edge-snap) (edge-crosses edge-prev edge-next dim.tilesize) cnext (if (and crosses? (collides? (make-xy edge-next copp) rules)) (+ c (- edge-snap edge-prev)) (+ c dc)) dcopp (if (= entity.opp-target nil) 0 (-> (- entity.opp-target copp) (math.min (* dt dim.tilesize 4)) (math.max (* dt dim.tilesize -4))))] (set entity.pos (make-xy cnext (+ copp dcopp))))) (fn handle-keys [entity] (let [prev-dirs (or entity.prev-dirs {}) next-dirs (held-directions pacman.keymap)] (each [dir _ (pairs next-dirs)] (when (not (. prev-dirs dir)) (set entity.last-dir dir))) (set entity.prev-dirs next-dirs) (set entity.dir (if (. next-dirs entity.last-dir) entity.last-dir (next next-dirs))))) (defmethod update :pacman (fn [entity dt rules] (handle-keys entity) (update-direction entity rules) (let [[x1 y1] entity.pos _ (move entity dt rules) [x2 y2] entity.pos] (when (or (not= x1 x2) (not= y1 y2)) (set entity.dt-animation (+ (or entity.dt-animation 0) dt)))))) (fn pacman.new [pos] {: pos :vel [0 0] :entity :pacman}) pacman.hot