bomberpac/game/entities/pacman.fnl

121 lines
5.4 KiB
Fennel

(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