121 lines
5.4 KiB
Fennel
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
|