diff options
author | Ricardo Wurmus <rekado@elephly.net> | 2021-03-01 00:08:40 +0100 |
---|---|---|
committer | Ricardo Wurmus <rekado@elephly.net> | 2021-03-09 11:40:28 +0100 |
commit | bc2ecb951a837db673b13def15f2c31f7134415a (patch) | |
tree | 6a27f6aefe9660ec73d6b6747e45a3c1178c530b /scenes |
WIP
Diffstat (limited to 'scenes')
-rw-r--r-- | scenes/death.scm | 333 | ||||
-rw-r--r-- | scenes/game.scm | 259 | ||||
-rw-r--r-- | scenes/intro.scm | 137 |
3 files changed, 729 insertions, 0 deletions
diff --git a/scenes/death.scm b/scenes/death.scm new file mode 100644 index 0000000..6d83637 --- /dev/null +++ b/scenes/death.scm @@ -0,0 +1,333 @@ +;;; The Inevitable Game +;;; Copyright © 2018, 2021 Ricardo Wurmus <rekado@elephly.net> +;;; +;;; This program is free software: you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License as +;;; published by the Free Software Foundation, either version 3 of the +;;; License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;;; General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program. If not, see +;;; <http://www.gnu.org/licenses/>. + +(define-module (scenes death) + #:use-module (chickadee) + #:use-module (chickadee audio) + #:use-module (chickadee math) + #:use-module (chickadee math vector) + #:use-module ((chickadee graphics color) #:select (make-color)) + #:use-module (chickadee graphics font) + #:use-module (chickadee graphics sprite) + #:use-module (chickadee graphics texture) + #:use-module (chickadee graphics tiled) + #:use-module (chickadee scripting) + #:use-module (config) + #:use-module (engine assets) + #:use-module (characters lorenzo) + #:use-module (characters reaper) + #:use-module (utils) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:use-module (oop goops) + #:export (scene)) + +(define-asset death-map + (load-tile-map "assets/maps/death.tmx")) +(define-asset vignette-image + (load-image "assets/images/vignette.png")) +(define-asset fade-image + (load-image "assets/images/fade.png")) +(define-asset game-font + (load-bitmap-font "assets/fonts/good_neighbors_starling.xml")) +(define-asset music + ;; TODO: no sound if mode is 'static! + (load-audio "assets/music/death.ogg" #:mode 'stream)) + + +(define agenda (make-agenda)) +(define fade-map-fill (make-color 0 0 0 0.0)) +(define fade-box-fill (make-color 0 0 0 0.0)) +(define death-text #false) +(define pretend-walking? #false) +(define *labels* '()) + +(define texts + '("\ +Life waits for no one, but +death is patient." + + "\ +You have died. Your points +don't matter." + + "\ +Death is inevitable." + + "\ +The void resumes." + + "\ +It did not last." + + "\ +You were busy, but none of that +matters any more." + + "\ +You were mortal after all." + + "\ +Nobody outruns the clock." + + "\ +Life goes on, but you end here." + + "\ +This is no longer your battle +to fight. Let go." + + "\ +Life is hard, but thankfully +quite short.")) + +(define credits + '(("The Inevitable Game" + "Made by Ricardo Wurmus" + "For Lorenzo") + ("Sky background" + "by Paulina Riva (CC-BY 3.0)") + ("Reaper character graphics" + "based on \"Lil Reaper Pet\" by Tracy") + ("Lorenzo character graphics" + "Based on \"Small 3/4 RPG character base\"" + "by Stephen Challener (Redshrike)") + ("Map tiles" + "taken from \"Zelda-like tilesets and sprites\"" + "by ArMM1998") + ("Food graphics" + "taken from \"The Humble Food Pack\"" + "by \"The Wise Hedgehog\"") + ("Font" + "\"Good Neighbors Starling\"" + "by PROWNE and Clint Bellanger") + ("All music" + "composed and recorded" + "by Ricardo Wurmus") + ("Bird sounds" + "taken from the public domain") + ("Made with 100% Free Software including" + "- GNU Guile" + "- Chickadee" + "- Guile OpenGL" + "- SDL") + ("Thanks for enduring this game!"))) + +(define camera-position + (vec2 40.0 90.0)) + +(define world-position + (vec2 0.0 0.0)) +(define last-world-position + (vec2 0.0 0.0)) + +(define *player* #false) +(define *reaper* #false) + + +(define (load-scene) + (set! death-text (list-ref texts (random (length texts)))) + (source-stop) + (source-play + (make-source #:audio + (asset-ref music) + #:loop? #false ; TODO: loops only first chunk! + )) + + (set! *player* (lorenzo #:start-position + (vec2 (+ (/ %width 2) 25) + (- (/ %height 2) 20)) + #:dead? #true)) + (update-animated-sprite *player* 0) + (set! (walk-speed *player*) 0.3) + + (set! *reaper* (reaper #:start-position + (vec2 (+ (/ %width 2) 110) + (- (/ %height 2) 20)))) + (update-animated-sprite *reaper* 0) + (set! (walk-speed *reaper*) 0.3) + + (with-agenda + agenda + (spawn-script + (lambda () + (wait-until (any key-pressed? '(escape q return space))) + + ;; Fade out + (tween 60 0.0 1.0 + (lambda (alpha) + (set! fade-box-fill + (make-color 0 0 0 alpha)))) + (abort-game))) + + (script + ;; Fade in + (tween 15 1.0 0.0 + (lambda (alpha) + (set! fade-box-fill + (make-color 0 0 0 alpha)))) + + (tween 300 0.0 0.5 + (lambda (alpha) + (set! (tint *player*) + (make-color 1.0 1.0 1.0 alpha)))) + + (walk *player* '(right)) + (sleep 3000) + (walk *player* '(right) 'stop) + (walk *player* '(idle-front)) + (sleep 1900) + (walk *player* '(up)) + (walk *reaper* '(up)) + (sleep 2000) + + ;; Slowly fade out the map and the top text. + (tween 600 0.0 1.0 (lambda (value) + (set! fade-map-fill + ;; That's the colour of the "black" cave tile. + (make-color 0.125 0.09 0.161 value)))) + + (set! pretend-walking? #true) + (sleep 1000) + + ;; Roll credits + (for-each + (lambda (lines) + (let ((labels (map (lambda (line i) + (make <label> + #:font game-font + #:text line + #:visible? #true + #:position + (vec2 16.0 (- 180.0 (* 12 i))))) + lines + (iota (length lines))))) + (set! *labels* (append *labels* labels)) + (sleep 3000) + (for-each (lambda (label) + (set! (visible? label) #false)) + labels))) + credits) + (sleep 6000) + + (for-each (lambda (text) + (let ((label + (make <label> + #:font game-font + #:text text + #:visible? #true + #:origin (vec2 (/ %width 2) + (/ %height 2)) + #:position + (vec2 16.0 180)))) + (set! *labels* (cons label *labels*)) + (sleep 3000) + (set! (visible? label) #false) + (sleep (+ (random 10000) 4000)))) + `("There is nothing more to see here." + "I'm serious." + "What are you waiting for?" + "Don't waste your time!" + "Have you learned nothing from this game?" + "I've got all day, but how about you?" + "Look, I'm programmed to just keep going." + "Ugh... why are you even reading this?" + "Let me tell you a story..." + "Once upon a time, there was..." + ,@(let ((what '(("...a squirrel." "It had a nut allergy and died.") + ("...a dolphin." "It swam too far and never returned.") + ("...a stupid fish." "It drowned.") + ("...a long war." "Everybody died.")))) + (list-ref what (random (length what)))) + "End of story." + "Good night." + "Please just turn it off." + "I'm begging you." + "You're not listening, huh?" + "Well, I guess I'll just stop talking then.")))) + (current-agenda agenda)) + +(define (draw-scene alpha) + (define world-position* + (vec2 (round (lerp (vec2-x last-world-position) + (vec2-x world-position) + alpha)) + (round (lerp (vec2-y last-world-position) + (vec2-y world-position) + alpha)))) + + (draw-tile-map (asset-ref death-map) + #:position world-position* + #:camera camera-position) + + ;; Dead lorenzo + (draw-sprite + (texture-atlas-ref + (asset-ref (atlas *player*)) 23) + (vec2+ world-position* + (vec2 (/ %width 2) (- (/ %height 2) 20)))) + + ;; Wisdom + (let ((parts (string-split death-text #\newline))) + (map (lambda (part i) + (draw-text part + (vec2 16.0 (- 190.0 (* 12 i))) + #:font (asset-ref game-font))) + parts (iota (length parts)))) + + ;; Box for fading out map + (draw-sprite + (asset-ref fade-image) + (vec2 0 0) + #:tint fade-map-fill) + + ;; Ghost + (draw-animated-sprite *player* (vec2 0 0)) + + ;; Reaper + (draw-animated-sprite *reaper* world-position*) + + ;; Credits + (for-each (lambda (label) + (draw-label label alpha)) + *labels*) + + (draw-sprite (asset-ref vignette-image) (vec2 0 0)) + + ;; Box for fading in/out everything + (draw-sprite + (asset-ref fade-image) + (vec2 0 0) + #:tint fade-box-fill)) + +(define (update-scene dt) + (update-agenda dt) + (update-animated-sprite *player* dt) + (update-animated-sprite *reaper* dt) + + ;; Move the world + (unless pretend-walking? + (vec2-copy! world-position last-world-position) + (vec2-add! (position *reaper*) (velocity *reaper*)) + (vec2-sub! world-position (velocity *player*)))) + + +(define scene + `(#:name "death" + #:load ,load-scene + #:draw ,draw-scene + #:update ,update-scene)) diff --git a/scenes/game.scm b/scenes/game.scm new file mode 100644 index 0000000..9f89172 --- /dev/null +++ b/scenes/game.scm @@ -0,0 +1,259 @@ +;;; The Inevitable Game +;;; Copyright © 2018, 2021 Ricardo Wurmus <rekado@elephly.net> +;;; +;;; This program is free software: you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License as +;;; published by the Free Software Foundation, either version 3 of the +;;; License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;;; General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program. If not, see +;;; <http://www.gnu.org/licenses/>. + +(define-module (scenes game) + #:use-module (chickadee) + #:use-module (chickadee audio) + #:use-module (chickadee math) + #:use-module (chickadee math easings) + #:use-module (chickadee math grid) + #:use-module (chickadee math rect) + #:use-module (chickadee math vector) + #:use-module ((chickadee graphics color) #:select (make-color)) + #:use-module (chickadee graphics font) + #:use-module (chickadee graphics path) + #:use-module (chickadee graphics sprite) + #:use-module (chickadee graphics texture) + #:use-module (chickadee graphics tiled) + #:use-module (chickadee scripting) + #:use-module (config) + #:use-module (engine assets) + #:use-module (characters lorenzo) + #:use-module (characters reaper) + #:use-module (utils) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:export (scene)) + +(define-asset game-map + (load-tile-map "assets/maps/01.tmx")) +(define-asset vignette-image + (load-image "assets/images/vignette.png")) +(define-asset fade-image + (load-image "assets/images/fade.png")) +(define-asset game-font + (load-bitmap-font "assets/fonts/good_neighbors_starling.xml")) +(define-asset music + ;; TODO: no sound if mode is 'static! + (load-audio "assets/sounds/birds.mp3" #:mode 'stream)) + +(define location + (let ((positions + (object-layer-objects + (tile-map-layer-ref (asset-ref game-map) "positions"))) + (offset (vec2 0 0))) + (lambda (name) + "Look up the location for an object with the given NAME in the +map's positions layer." + (or (and=> (find (lambda (obj) + (equal? (map-object-name obj) name)) + positions) + (lambda (obj) + (let ((shape (map-object-shape obj))) + (vec2 (- (+ (vec2-x offset) (rect-x shape)) + (/ (rect-width shape) 2)) + (- (+ (vec2-y offset) (rect-y shape)) + (/ (rect-height shape) 2)))))) + (vec2 0.0 0.0))))) + +(define fade-box-fill (make-color 0 0 0 1.0)) + +(define agenda (make-agenda)) +(define last-player-position + (location "player")) +(define *player-previous-keys* (list)) +(define *player* #false) +(define *reaper* #false) +(define *layers* #false) +(define grid (make-grid 16)) +(define player-grid-x-offset 8) +(define player-grid-y-offset 0) + + + +(define (load-scene) + (source-stop) + (source-play + (make-source #:audio + (asset-ref music) + #:loop? #false ; TODO: loops only first chunk! + )) + (set! *player* (lorenzo #:start-position + (location "player"))) + (update-animated-sprite *player* 0) + (grid-add grid 'player + (+ (vec2-x (position *player*)) player-grid-x-offset) + (+ (vec2-y (position *player*)) player-grid-y-offset) + 16 16) + (set! (walk-speed *player*) 1.5) + + (set! *reaper* (reaper #:start-position + (location "reaper"))) + (update-animated-sprite *reaper* 0) + + ;; XXX: Chickadee parses the object layer incorrectly, so all + ;; objects are flipped vertically. We use this to compute the + ;; corrected shapes of layer objects only once. + (set! *layers* + (map (lambda (layer-name) + (cons layer-name + (map (lambda (obj) + (let ((r-wrong (map-object-shape obj))) + (cons obj + (make-rect (rect-x r-wrong) + (- (rect-y r-wrong) (rect-height r-wrong)) + (rect-width r-wrong) + (rect-height r-wrong))))) + (object-layer-objects + (tile-map-layer-ref (asset-ref game-map) layer-name))))) + '("food" "positions" "actions" "collision"))) + + (for-each (lambda (barrier) + (let ((r (cdr barrier))) + (grid-add grid (map-object-id (car barrier)) + (rect-x r) + (rect-y r) + (rect-width r) + (rect-height r)))) + (assoc-ref *layers* "collision")) + + (with-agenda + agenda + (spawn-script + (lambda () + (wait-until (any key-pressed? '(escape))) + + ;; Fade out + (tween 160 0.0 1.0 + (lambda (alpha) + (set! fade-box-fill + (make-color 0 0 0 alpha)))) + (throw 'switch-scene + (@ (scenes death) scene)))) + (spawn-script + (lambda () + (forever + (sleep 1) + ;; Stop any motion in a direction when the matching key has just + ;; been released. + (let ((released (filter key-released? *player-previous-keys*))) + (unless (null? released) + (walk *player* released 'stop))) + + ;; Detect newly pressed keys. + (let ((active (filter key-pressed? '(left right up down)))) + (walk *player* (match active + (() '(idle)) + (_ active))) + (set! *player-previous-keys* active)) + + ;; Update player position and respond to position-dependent + ;; events. + (let ((vel (velocity *player*))) + (vec2-copy! world-position last-world-position) + (vec2-add! world-position vel) + + ;; TODO + ;; ;; Reset when the new position is invalid. + ;; (when (collides? player game "collision") + ;; (vec2-sub! pos vel)) + ;; (and=> (collides? player game (items game)) + ;; ;; TODO: do something to the item + ;; (match-lambda + ;; (() #f) + ;; (items (pk items)))) + )))) + + ;; Handle background noise fade in + (spawn-script + (lambda () + (tween 60 0.0 1.0 + (lambda (a) + (set-source-volume! background-music a) + (sleep 100)) + #:ease ease-out-sine))) + + ;; Fade in + (script + (tween 60 1.0 0.0 + (lambda (alpha) + (set! fade-box-fill + (make-color 0 0 0 alpha)))))) + (current-agenda agenda)) + +(define (draw-scene alpha) + (define player-render-position + (vec2 (round (lerp (vec2-x last-player-position) + (vec2-x (position *player*)) + alpha)) + (round (lerp (vec2-y last-player-position) + (vec2-y (position *player*)) + alpha)))) + + ;; Always centered on the player + (define camera + (let ((player-half 16)) + (vec2- (position *player*) + (vec2 (- (/ %width 2) player-half) + (- (/ %height 2) player-half))))) + + ;; The Void + (draw-sprite + (asset-ref fade-image) + (vec2 0 0) + #:tint (make-color 0.125 0.09 0.161 1.0)) + + ;; The lower layers of the world + (draw-tile-map (asset-ref game-map) + #:position (vec2 0 0) + #:camera camera + #:layers (list 0 1 2)) + + ;; Reaper + (draw-animated-sprite *reaper* + (vec2* camera -1.0)) + + ;; Player + (draw-animated-sprite *player* + (vec2* camera -1.0)) + + ;; The upper layer hiding items and the player. + (draw-tile-map (asset-ref game-map) + #:position (vec2 0 0) + #:camera camera + #:layers (list 3)) + + ;; Vignette + (draw-sprite (asset-ref vignette-image) (vec2 0 0)) + + ;; Box for fading in/out everything + (draw-sprite + (asset-ref fade-image) + (vec2 0 0) + #:tint fade-box-fill)) + +(define (update-scene dt) + (update-agenda dt) + (update-animated-sprite *player* dt) + (update-animated-sprite *reaper* dt)) + + +(define scene + `(#:name "game" + #:load ,load-scene + #:draw ,draw-scene + #:update ,update-scene)) diff --git a/scenes/intro.scm b/scenes/intro.scm new file mode 100644 index 0000000..739abed --- /dev/null +++ b/scenes/intro.scm @@ -0,0 +1,137 @@ +;;; The Inevitable Game +;;; Copyright © 2018, 2021 Ricardo Wurmus <rekado@elephly.net> +;;; +;;; This program is free software: you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License as +;;; published by the Free Software Foundation, either version 3 of the +;;; License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;;; General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program. If not, see +;;; <http://www.gnu.org/licenses/>. + +(define-module (scenes intro) + #:use-module (chickadee) + #:use-module (chickadee audio) + #:use-module (chickadee math vector) + #:use-module (chickadee math rect) + #:use-module ((chickadee graphics color) #:select (make-color)) + #:use-module (chickadee graphics font) + #:use-module (chickadee graphics path) + #:use-module (chickadee graphics sprite) + #:use-module (chickadee graphics texture) + #:use-module (chickadee scripting) + #:use-module (config) + #:use-module (engine assets) + #:export (scene)) + +(define-asset game-font + (load-bitmap-font "assets/fonts/good_neighbors_starling.xml")) +(define-asset intro-bg + (load-image "assets/images/intro-bg.png")) +(define-asset music + (load-audio "assets/music/intro.ogg" #:mode 'stream)) + + +(define agenda (make-agenda)) +(define fade-box-fill (make-color 1 1 1 1.0)) +(define fade-box (fill (rectangle (vec2 0 0) %width %height))) +(define texts + '("\ +The void has been suspended for +a little while. You exist now." + "\ +Confusion gives way to a brief +burst of consciousness. +Here you are. What now?" + "\ +The haze clears and you can see +that you are here now. You did not +ask for this." + "\ +You rise from deepest waters and +take your first breath. You wonder: +will this last?" + "\ +History yields to the present. +The present crumbles underfoot. +You better move." + "\ +A leaf has turned. +The sunshine warms and blinds. +The leaf begins to dry.")) +(define welcome-text #false) + + + +(define (load-scene) + (set! *random-state* (random-state-from-platform)) + (set! welcome-text (list-ref texts (random (length texts)))) + (source-play + (make-source #:audio + (asset-ref music) + #:loop? #false)) + + (with-agenda + agenda + (spawn-script + (lambda () + (wait-until (key-pressed? 'return)) + + ;; Fade out + (tween 60 0.0 1.0 + (lambda (alpha) + (set! fade-box-fill + (make-color 0 0 0 alpha)))) + (throw 'switch-scene + (module-ref (resolve-interface '(scenes game)) 'scene)))) + (script + ;; Fade in + (tween 120 1.0 0.0 + (lambda (alpha) + (set! fade-box-fill + (make-color 1 1 1 alpha)))))) + (current-agenda agenda)) + +(define (draw-scene alpha) + ;; Background + (draw-sprite (asset-ref intro-bg) (vec2 0 0)) + + ;; Text + (let ((text "Press enter to start.")) + (draw-text text + (let ((width (font-line-width (asset-ref game-font) text))) + (vec2 (- (/ %width 2) + (/ width 2)) + 30.0)) + #:font (asset-ref game-font))) + + (let ((parts (string-split welcome-text #\newline))) + (for-each (lambda (part i) + (draw-text part + (vec2 (- (/ %width 2) + (/ (font-line-width (asset-ref game-font) part) 2)) + (- 120.0 (* 15 i))) + #:font (asset-ref game-font))) + parts (iota (length parts)))) + + ;; Fade box + (draw-canvas + (make-canvas + (with-style + ((fill-color fade-box-fill)) + fade-box)))) + +(define update-scene update-agenda) + + +(define scene + `(#:name "intro" + #:load ,load-scene + #:draw ,draw-scene + #:update ,update-scene)) |