summaryrefslogtreecommitdiff
path: root/lisparuga
diff options
context:
space:
mode:
authorDavid Thompson <dthompson@vistahigherlearning.com>2020-04-12 21:59:35 -0400
committerDavid Thompson <dthompson@vistahigherlearning.com>2020-04-12 21:59:35 -0400
commit2bdb665cffff93721bbd38b3809a7c420dff2f1c (patch)
treede12f43ec1b8053f99f182d06d404a97ba62f64f /lisparuga
parent729f0b687b975e60f338831bcb0d59fad776f3e1 (diff)
Day 3 progress.
Diffstat (limited to 'lisparuga')
-rw-r--r--lisparuga/enemy.scm10
-rw-r--r--lisparuga/game.scm281
-rw-r--r--lisparuga/node-2d.scm26
-rw-r--r--lisparuga/player.scm51
4 files changed, 304 insertions, 64 deletions
diff --git a/lisparuga/enemy.scm b/lisparuga/enemy.scm
index 0589d16..aa5335e 100644
--- a/lisparuga/enemy.scm
+++ b/lisparuga/enemy.scm
@@ -21,6 +21,7 @@
;;; Code:
(define-module (lisparuga enemy)
+ #:use-module (chickadee audio)
#:use-module (chickadee math)
#:use-module (chickadee math rect)
#:use-module (chickadee math vector)
@@ -46,6 +47,9 @@
;;;
;;; Base Enemy
;;;
+
+(define-asset explosion-sound (load-audio (scope-asset "sounds/explosion.wav")))
+
(define-class <enemy> (<actor>)
(health #:accessor health #:init-keyword #:health)
(points #:getter points #:init-keyword #:points)
@@ -56,7 +60,11 @@
#t)
(define-method (damage (enemy <enemy>) x)
- (set! (health enemy) (max (- (health enemy) x) 0)))
+ (let ((new-health (max (- (health enemy) x) 0)))
+ (set! (health enemy) new-health)
+ (when (zero? new-health)
+ (audio-play (asset-ref explosion-sound)
+ #:volume 0.5))))
(define-method (dead? (enemy <enemy>))
(zero? (health enemy)))
diff --git a/lisparuga/game.scm b/lisparuga/game.scm
index 5b14edd..9e79898 100644
--- a/lisparuga/game.scm
+++ b/lisparuga/game.scm
@@ -22,9 +22,11 @@
;;; Code:
(define-module (lisparuga game)
+ #:use-module (chickadee)
#:use-module (chickadee math rect)
#:use-module (chickadee math vector)
#:use-module (chickadee render color)
+ #:use-module (chickadee render particles)
#:use-module (chickadee render texture)
#:use-module (chickadee scripting)
#:use-module (ice-9 format)
@@ -42,17 +44,36 @@
start-player-shooting
stop-player-shooting
toggle-player-polarity
- fire-player-homing-missiles))
+ fire-player-homing-missiles
+ spawn-enemies
+ start-stage
+ game-over?
+ complete?))
(define-asset clouds (load-image (scope-asset "images/clouds.png")))
(define-asset player-bullet-atlas
(load-tile-atlas (scope-asset "images/player-bullets.png") 16 16))
(define-asset enemy-bullet-atlas
(load-tile-atlas (scope-asset "images/enemy-bullets.png") 24 24))
+(define-asset explosion-texture
+ (load-image (scope-asset "images/explosion.png")))
;; nodes needed:
;; scrolling background
-(define-class <game> (<node-2d>))
+(define-class <game> (<canvas>)
+ (player-control? #:accessor player-control? #:init-value #f)
+ (complete? #:accessor complete? #:init-value #f))
+
+(define-method (initialize (game <game>) initargs)
+ (next-method)
+ (set! (views game)
+ ;; Game happens on a 160x240 pixel screen.
+ (list (make <view-2d>
+ #:camera (make <camera-2d>
+ #:width 160
+ #:height 240)
+ #:area (make-rect 80 0 160 240)
+ #:clear-color (make-color 0.0 0.0 0.0 1.0)))))
(define-method (on-boot (game <game>))
(let* ((player-bullets (make <bullet-field>
@@ -60,58 +81,79 @@
#:rank 2
#:capacity 500
#:texture-atlas player-bullet-atlas))
- (player (make-player player-bullets))
(enemy-bullets (make <bullet-field>
#:name 'enemy-bullets
- #:rank 4
+ #:rank 5
#:capacity 1000
#:texture-atlas enemy-bullet-atlas))
+ (explosions (make <particles>
+ #:name 'explosions
+ #:rank 3
+ #:particles
+ (make-particles 1000
+ #:texture (asset-ref explosion-texture)
+ #:end-color (make-color 1.0 1.0 1.0 0.0)
+ #:speed-range (vec2 0.5 5.0)
+ #:lifetime 12)))
(ui (make <node-2d>
#:name 'ui
#:rank 999)))
- (set! (rank player) 1)
(attach-to game
(make <sprite>
#:name 'clouds
#:rank 0
#:texture clouds)
- player
player-bullets
(make <node-2d>
#:name 'enemies
- #:rank 3)
+ #:rank 4)
+ explosions
enemy-bullets
ui)
;; Setup UI elements
+ ;; TODO: Move this out of here.
(attach-to ui
(make <label>
#:name 'score
- #:position (vec2 2.0 226.0))
+ #:position (vec2 2.0 242.0)
+ #:vertical-align 'top)
(make <label>
#:name 'chain
- #:position (vec2 2.0 210.0))
+ #:position (vec2 158.0 242.0)
+ #:align 'right
+ #:vertical-align 'top)
(make <label>
#:name 'energy
- #:position (vec2 2.0 18.0))
+ #:position (vec2 158.0 2.0)
+ #:align 'right)
(make <label>
#:name 'lives
#:position (vec2 2.0 2.0)))
+ (start-stage game)))
+
+(define-method (start-stage (game <game>))
+ (let ((player (make-player (& game player-bullets))))
+ (set! (rank player) 1)
+ (attach-to game player)
(update-ui game)
- ;; Test enemy
- (spawn-enemy game (make-utatsugumi 'white 10.0 180.0))
- (spawn-enemy game (make-utatsugumi 'white 30.0 180.0))
- (spawn-enemy game (make-utatsugumi 'white 50.0 180.0))
- (spawn-enemy game (make-utatsugumi 'black 70.0 180.0))
- (spawn-enemy game (make-utatsugumi 'black 90.0 180.0))
- (spawn-enemy game (make-utatsugumi 'black 110.0 180.0))
- (spawn-enemy game (make-utatsugumi 'white 130.0 180.0))
- (spawn-enemy game (make-utatsugumi 'white 150.0 180.0))))
-
-(define (update-ui game)
+ (play-stage-1 game)))
+
+(define-method (spawn-enemies (game <game>))
+ ;; Test enemies
+ (spawn-enemy game (make-utatsugumi 'white 10.0 180.0))
+ (spawn-enemy game (make-utatsugumi 'white 30.0 180.0))
+ (spawn-enemy game (make-utatsugumi 'white 50.0 180.0))
+ (spawn-enemy game (make-utatsugumi 'black 70.0 180.0))
+ (spawn-enemy game (make-utatsugumi 'black 90.0 180.0))
+ (spawn-enemy game (make-utatsugumi 'black 110.0 180.0))
+ (spawn-enemy game (make-utatsugumi 'white 130.0 180.0))
+ (spawn-enemy game (make-utatsugumi 'white 150.0 180.0)))
+
+(define-method (update-ui (game <game>))
(set! (text (& game ui score))
- (format #f "~9,'0d" (score (& game player))))
+ (format #f "~7,'0d" (score (& game player))))
(set! (text (& game ui chain))
- (format #f "CHAIN ~a: ~a"
+ (format #f "~a CHAIN (~a)"
(let ((n (chain (& game player))))
(if (< n 9) (number->string n) "MAX"))
(list->string
@@ -121,48 +163,189 @@
#\B))
(chain-progress (& game player))))))
(set! (text (& game ui energy))
- (format #f "E~d" (quotient (energy (& game player)) 10)))
+ (format #f "ENERGY ~d" (quotient (energy (& game player)) 10)))
(set! (text (& game ui lives))
- (format #f "x~d" (max (- (lives (& game player)) 1) 0))))
+ (format #f "SHIP x~d" (max (- (lives (& game player)) 1) 0))))
+
+(define-method (explode (game <game>) (actor <actor>))
+ (let* ((p (position actor))
+ (emitter (make-particle-emitter (make-rect (- (vec2-x p) 8.0)
+ (- (vec2-y p) 8.0)
+ 16.0 16.0)
+ 8 5)))
+ (add-particle-emitter (particles (& game explosions)) emitter)))
(define-method (update (game <game>) dt)
(let ((refresh-ui? #f)
(player (& game player)))
- ;; enemy -> player bullet collision
- ;; enemy -> player collision
- (for-each (lambda (enemy)
- (cond
- ((and (collide (& game player-bullets) enemy)
- (dead? enemy))
- (on-kill player enemy)
- (fire-parting-shots-maybe enemy player)
- (detach enemy)
- (set! refresh-ui? #t))
- ((collide player enemy)
- (set! refresh-ui? #t))))
- (children (& game enemies)))
- ;; player -> enemy bullet collision
- (when (collide (& game enemy-bullets) (& game player))
- (set! refresh-ui? #t))
- (when refresh-ui?
- (update-ui game))))
+ (when player
+ ;; enemy -> player bullet collision
+ ;; enemy -> player collision
+ (for-each (lambda (enemy)
+ (cond
+ ((and (collide (& game player-bullets) enemy)
+ (dead? enemy))
+ (on-kill player enemy)
+ (fire-parting-shots-maybe enemy player)
+ (explode game enemy)
+ (detach enemy)
+ (set! refresh-ui? #t))
+ ((collide player enemy)
+ (set! refresh-ui? #t))))
+ (children (& game enemies)))
+ ;; player -> enemy bullet collision
+ (when (collide (& game enemy-bullets) (& game player))
+ (set! refresh-ui? #t))
+ (when refresh-ui?
+ (update-ui game)))
+ (next-method)))
(define-method (spawn-enemy (game <game>) enemy)
(set! (bullet-field enemy) (& game enemy-bullets))
(attach-to (& game enemies) enemy))
(define-method (steer-player (game <game>) up? down? left? right?)
- (steer (& game player) up? down? left? right?))
+ (when (player-control? game)
+ (steer (& game player) up? down? left? right?)))
(define-method (start-player-shooting (game <game>))
- (start-shooting (& game player)))
+ (when (player-control? game)
+ (start-shooting (& game player))))
(define-method (stop-player-shooting (game <game>))
- (stop-shooting (& game player)))
+ (when (player-control? game)
+ (stop-shooting (& game player))))
(define-method (toggle-player-polarity (game <game>))
- (toggle-polarity (& game player)))
+ (when (player-control? game)
+ (toggle-polarity (& game player))))
(define-method (fire-player-homing-missiles (game <game>))
- (fire-homing-missiles (& game player) (children (& game enemies)))
- (update-ui game))
+ (when (player-control? game)
+ (fire-homing-missiles (& game player) (children (& game enemies)))
+ (update-ui game)))
+
+(define-method (game-over? (game <game>))
+ (let ((player (& game player)))
+ (and player (dead? player))))
+
+(define-method (play-stage-1 game)
+ (run-script game
+ (do-intro game)
+ (do-tutorial game)
+ (do-phase-1 game)
+ (do-win game)))
+
+(define-method (do-intro (game <game>))
+ (hide (& game ui))
+ (teleport (& game player) 80.0 -24.0)
+ (move-to (& game player) 80.0 32.0 50)
+ (steer (& game player) #f #f #f #f)
+ (set! (player-control? game) #t)
+ (show (& game ui)))
+
+(define *skip-tutorial?* #t)
+
+(define-method (do-tutorial (game <game>))
+ (define* (instruct text continue? #:optional (post-delay 60))
+ (let ((instructions (make <label>
+ #:text text
+ #:align 'center
+ #:vertical-align 'center
+ #:position (vec2 80.0 120.0))))
+ (attach-to (& game ui) instructions)
+ (while (not (continue?))
+ (sleep 10))
+ (sleep post-delay)
+ (detach instructions)
+ (sleep 60)))
+ (unless *skip-tutorial?*
+ (sleep 30)
+ (instruct "use arrow keys to move"
+ (let ((v (velocity (& game player))))
+ (lambda ()
+ (not (and (= (vec2-x v) 0.0)
+ (= (vec2-y v) 0.0))))))
+ (instruct "press Z to shoot"
+ (lambda ()
+ (shooting? (& game player))))
+ (instruct "press X to change color"
+ (let ((starting-polarity (polarity (& game player))))
+ (lambda ()
+ (not (eq? (polarity (& game player)) starting-polarity)))))
+ (instruct "avoid opposite energy" (const #t) 120)
+ (instruct "absorb same energy" (const #t) 120)
+ (add-energy (& game player) 120)
+ (update-ui game)
+ (instruct "press C to release energy"
+ (lambda ()
+ (zero? (energy (& game player)))))
+ (instruct "get ready!" (const #t) 120)))
+
+(define-method (do-phase-1 (game <game>))
+ (define (utatsugumi-sweep x polarity)
+ (let loop ((i 0))
+ (when (< i 6)
+ (let ((utatsugumi (make-utatsugumi polarity x 260.0)))
+ (spawn-enemy game utatsugumi)
+ (set-vec2! (velocity utatsugumi) 0.0 -3.0)
+ (script
+ (sleep (* 10 60))
+ (detach utatsugumi))
+ (sleep 10))
+ (loop (+ i 1)))))
+ (utatsugumi-sweep 140.0 'white)
+ (sleep 60)
+ (utatsugumi-sweep 20.0 'black)
+ (sleep 60)
+ (utatsugumi-sweep 140.0 'white)
+ (sleep 60)
+ (utatsugumi-sweep 20.0 'black)
+ (sleep (* 3 60)))
+
+(define-method (do-win (game <game>))
+ (set! (player-control? game) #f)
+ (steer (& game player) #f #f #f #f)
+ (stop-shooting (& game player))
+ (hide (& game ui))
+ (let ((battle-report (make <node-2d>
+ #:name 'battle-report
+ #:rank 999)))
+ (define (add-row y name value)
+ (attach-to battle-report
+ (make <label>
+ #:rank 999
+ #:text name
+ #:align 'left
+ #:position (vec2 16.0 y))
+ (make <label>
+ #:rank 999
+ #:text value
+ #:align 'left
+ #:position (vec2 96.0 y))))
+ (let ((backdrop (make <filled-rect>
+ #:region (make-rect 0.0 0.0 160.0 240.0))))
+ (attach-to battle-report backdrop)
+ (attach-to game battle-report)
+ (tween 45 (make-color 0.0 0.0 0.0 0.0) (make-color 0.0 0.0 0.0 0.8)
+ (lambda (c)
+ (set! (color backdrop) c))
+ #:interpolate color-lerp))
+ (attach-to battle-report
+ (make <label>
+ #:rank 999
+ #:text "BATTLE REPORT"
+ #:align 'center
+ #:position (vec2 80.0 180.0)))
+ (sleep 30)
+ (add-row 140.0 "SCORE" (number->string (score (& game player))))
+ (sleep 30)
+ (add-row 110.0 "MAX CHAIN" (number->string (max-chain (& game player))))
+ (sleep 30)
+ (attach-to battle-report
+ (make <label>
+ #:rank 999
+ #:text "press ENTER to play again"
+ #:position (vec2 80.0 60.0)
+ #:align 'center))
+ (set! (complete? game) #t)))
diff --git a/lisparuga/node-2d.scm b/lisparuga/node-2d.scm
index 0baef54..b6753ef 100644
--- a/lisparuga/node-2d.scm
+++ b/lisparuga/node-2d.scm
@@ -605,7 +605,31 @@
(define-class <label> (<node-2d>)
(font #:accessor font #:init-keyword #:font #:init-thunk default-font)
- (text #:accessor text #:init-form "" #:init-keyword #:text))
+ (text #:accessor text #:init-form "" #:init-keyword #:text)
+ (align #:accessor align #:init-value 'left #:init-keyword #:align)
+ (vertical-align #:accessor vertical-align #:init-value 'bottom
+ #:init-keyword #:vertical-align))
+
+(define-method (initialize (label <label>) initargs)
+ (next-method)
+ (realign label))
+
+(define-method ((setter text) (label <label>) s)
+ (slot-set! label 'text s)
+ (realign label))
+
+(define-method (realign (label <label>))
+ (let ((font (asset-ref (font label))))
+ (set-vec2! (origin label)
+ (match (align label)
+ ('left 0.0)
+ ('right (font-line-width font (text label)))
+ ('center (/ (font-line-width font (text label)) 2.0)))
+ (match (vertical-align label)
+ ('bottom 0.0)
+ ('top (font-line-height font))
+ ('center (/ (font-line-height font) 2.0)))))
+ (dirty! label))
(define-method (render (label <label>) alpha)
(draw-text* (asset-ref (font label)) (text label) (world-matrix label)))
diff --git a/lisparuga/player.scm b/lisparuga/player.scm
index ca57891..1265756 100644
--- a/lisparuga/player.scm
+++ b/lisparuga/player.scm
@@ -21,6 +21,7 @@
;;; Code:
(define-module (lisparuga player)
+ #:use-module (chickadee audio)
#:use-module (chickadee math)
#:use-module (chickadee math rect)
#:use-module (chickadee math vector)
@@ -42,17 +43,23 @@
energy
chain
chain-progress
+ max-chain
speed
+ invincible?
+ shooting?
steer
start-shooting
stop-shooting
toggle-polarity
fire-homing-missiles
kill-maybe
- on-kill))
+ on-kill
+ add-energy))
-(define-asset ship (load-image (scope-asset "images/player.png")))
(define-asset ship-atlas (load-tile-atlas (scope-asset "images/player.png") 24 24))
+(define-asset shoot-sound (load-audio (scope-asset "sounds/player-shoot.wav")))
+(define-asset missile-sound (load-audio (scope-asset "sounds/player-missile.wav")))
+(define-asset death-sound (load-audio (scope-asset "sounds/player-death.wav")))
(define kill-hitbox (make-hitbox 'kill (make-rect -2.0 -2.0 4.0 4.0)))
(define graze-hitbox (make-hitbox 'graze (make-rect -12.0 -12.0 24.0 24.0)))
@@ -63,16 +70,20 @@
(energy #:accessor energy #:init-value 0)
(chain #:accessor chain #:init-value 0)
(chain-progress #:accessor chain-progress #:init-form '())
+ (max-chain #:accessor max-chain #:init-value 0)
(speed #:accessor speed #:init-value 2.5)
(invincible? #:accessor invincible? #:init-value #f)
(shooting? #:accessor shooting? #:init-value #f)
(shoot-time #:accessor shoot-time #:init-value 0))
+(define-method (dead? (player <player>))
+ (zero? (lives player)))
+
(define (make-player bullet-field)
(make <player>
#:name 'player
#:hitboxes (list graze-hitbox kill-hitbox)
- #:position (vec2 80.0 24.0)
+ #:position (vec2 80.0 -24.0)
#:bullet-field bullet-field
#:polarity 'white))
@@ -121,11 +132,13 @@
(cond
;; single shot
((zero? t)
+ (audio-play (asset-ref shoot-sound))
(shoot player 0.0))
;; double shot. give a buffer of 4 frames so players can
;; reliably fire just a single shot.
((> t 4)
- (shoot player 5.0)
+ (audio-play (asset-ref shoot-sound))
+ (shoot player 6.0)
(shoot player -5.0))))
(set! (shoot-time player) (+ t 1))))
(next-method))
@@ -188,9 +201,10 @@
(if enemy
(let* ((ep (position enemy)))
(define (aim-at-enemy bp bv)
- (let ((dir (atan (- (vec2-y ep) (vec2-y bp))
- (- (vec2-x ep) (vec2-x bp)))))
- (set-vec2! bv (* (cos dir) speed) (* (sin dir) speed))))
+ (unless (dead? enemy)
+ (let ((dir (atan (- (vec2-y ep) (vec2-y bp))
+ (- (vec2-x ep) (vec2-x bp)))))
+ (set-vec2! bv (* (cos dir) speed) (* (sin dir) speed)))))
(run-script player
(let loop ((i 0))
(when (< i n)
@@ -206,6 +220,9 @@
(vec2-x p) (vec2-y p)
(* (cos theta) speed) (* (sin theta) speed))
(loop (+ i 1))))))))
+ (when (> e 10)
+ (audio-play (asset-ref missile-sound)
+ #:volume 0.5))
;; Distribute missiles amongst closest enemies
(let loop ((enemies enemies)
(missiles-remaining (quotient e 10))
@@ -229,15 +246,22 @@
(loop enemies 0 (+ missiles-used missiles-remaining)))))))
#t))
-(define-method (increment-energy (player <player>))
- (set! (energy player) (min (+ (energy player) 1) 120)))
+(define-method (add-energy (player <player>) n)
+ (set! (energy player) (min (+ (energy player) n) 120)))
(define-method (kill-maybe (player <player>))
(unless (invincible? player)
- (let ((new-lives (- (lives player) 1)))
+ (audio-play (asset-ref death-sound))
+ (let ((new-lives (max (- (lives player) 1) 0)))
(set! (lives player) new-lives)
+ (set! (energy player) 0)
(if (zero? new-lives)
- (hide player)
+ (begin
+ ;; to stop the death events from happening over and over
+ ;; after game over condition is reached.
+ (set! (invincible? player) #t)
+ (set-vec2! (velocity player) 0.0 0.0)
+ (hide player))
;; Give player invincibility for a bit while they recover.
(run-script player
(set! (invincible? player) #t)
@@ -258,7 +282,7 @@
;; Absorb bullets of the same polarity.
((and (eq? hitbox graze-hitbox)
(eq? bullet-polarity (polarity player)))
- (increment-energy player)
+ (add-energy player 1)
;; From what I can tell by watching youtube replays at .25 speed,
;; each bullet absorbed is worth 100 points.
(set! (score player) (+ (score player) 100))
@@ -270,13 +294,14 @@
#t)
(else #f)))
-(define (add-to-chain player polarity)
+(define-method (add-to-chain (player <player>) polarity)
(let ((current-chain (cons polarity (chain-progress player))))
(match current-chain
;; complete chain.
((or ('white 'white 'white)
('black 'black 'black))
(let ((new-chain (+ (chain player) 1)))
+ (set! (max-chain player) (max (max-chain player) new-chain))
(set! (chain player) new-chain)
(set! (chain-progress player) '())
(set! (score player)