diff options
-rw-r--r-- | .dir-locals.el | 4 | ||||
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | chickadee/buffer.scm | 233 | ||||
-rw-r--r-- | doc/api.texi | 177 | ||||
-rw-r--r-- | examples/buffer.scm | 33 |
5 files changed, 448 insertions, 2 deletions
diff --git a/.dir-locals.el b/.dir-locals.el index 540a9b0..f5d370b 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -12,4 +12,6 @@ (eval . (put 'uniform-let 'scheme-indent-function 1)) (eval . (put 'call-with-surface 'scheme-indent-function 1)) (eval . (put 'with-new-rect 'scheme-indent-function 1)) - (eval . (put 'with-new-vec2 'scheme-indent-function 1))))) + (eval . (put 'with-new-vec2 'scheme-indent-function 1)) + (eval . (put 'with-buffer 'scheme-indent-function 1)) + (eval . (put 'with-current-buffer 'scheme-indent-function 1))))) diff --git a/Makefile.am b/Makefile.am index 50b8159..5bdd98d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -70,7 +70,8 @@ SOURCES = \ chickadee/scripting/coroutine.scm \ chickadee/scripting/channel.scm \ chickadee/scripting.scm \ - chickadee.scm + chickadee.scm \ + chickadee/buffer.scm EXTRA_DIST += \ COPYING \ diff --git a/chickadee/buffer.scm b/chickadee/buffer.scm new file mode 100644 index 0000000..793215d --- /dev/null +++ b/chickadee/buffer.scm @@ -0,0 +1,233 @@ +;;; Chickadee Game Toolkit +;;; Copyright © 2017 David Thompson <davet@gnu.org> +;;; +;;; Chickadee 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. +;;; +;;; Chickadee 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 (chickadee buffer) + #:use-module (chickadee) + #:use-module (chickadee scripting) + #:use-module (oop goops) + #:export (<buffer> + started? + start + stop + pause + resume + update + before-draw + draw + after-draw + abort + key-press + key-release + text-input + mouse-press + mouse-move + controller-add + controller-remove + controller-press + controller-release + controller-move + current-buffer + push-buffer! + pop-buffer! + replace-buffer! + use-buffers!)) + +(define-class <buffer> () + (started? #:init-value #f #:accessor buffer-started?) + (agenda #:init-thunk make-agenda #:getter buffer-agenda)) + +(define-method (start (buffer <buffer>)) + #t) + +(define-method (stop (buffer <buffer>)) + #t) + +(define-method (pause (buffer <buffer>)) + #t) + +(define-method (resume (buffer <buffer>)) + #t) + +(define-method (update (buffer <buffer>) dt) + #t) + +(define-method (abort (buffer <buffer>)) + (abort-game)) + +(define-method (before-draw (buffer <buffer>)) + #t) + +(define-method (draw (buffer <buffer>) alpha) + #t) + +(define-method (after-draw (buffer <buffer>)) + #t) + +(define-method (key-press (buffer <buffer>) key scancode modifiers repeat?) + #t) + +(define-method (key-release (buffer <buffer>) key scancode modifiers) + #t) + +(define-method (text-input (buffer <buffer>) text) + #t) + +(define-method (mouse-press (buffer <buffer>) button clicks x y) + #t) + +(define-method (mouse-release (buffer <buffer>) button x y) + #t) + +(define-method (mouse-move (buffer <buffer>) x y x-rel y-rel buttons) + #t) + +(define-method (controller-add (buffer <buffer>) controller) + #t) + +(define-method (controller-remove (buffer <buffer>) controller) + #t) + +(define-method (controller-press (buffer <buffer>) controller button) + #t) + +(define-method (controller-release (buffer <buffer>) controller button) + #t) + +(define-method (controller-move (buffer <buffer>) controller axis value) + #t) + + +;;; +;;; Buffer management +;;; + +(define *buffers* '()) + +(define (current-buffer) + (and (not (null? *buffers*)) + (car *buffers*))) + +(define-syntax-rule (with-buffer buffer body ...) + (with-agenda (buffer-agenda buffer) + body ...)) + +(define-syntax-rule (with-current-buffer name body ...) + (let ((name (current-buffer))) + (with-buffer name + body ...))) + +(define* (switch-buffers old new #:optional replace?) + (when (is-a? old <buffer>) + (with-buffer old + (if replace? + (stop old) + (pause old)))) + (with-buffer new + (if (buffer-started? new) + (resume new) + (begin + (start new) + (set! (buffer-started? new) #t))))) + +(define (push-buffer! buffer) + (let ((old (current-buffer))) + (set! *buffers* (cons buffer *buffers*)) + (switch-buffers old buffer))) + +(define (pop-buffer!) + (let ((old (current-buffer))) + (set! *buffers* (cdr *buffers*)) + (if (null? *buffers*) + (begin + (stop old) + (abort-game)) + (switch-buffers old (current-buffer) #t)))) + +(define (replace-buffer! buffer) + (let ((old (current-buffer))) + (set! *buffers* (cons buffer (cdr *buffers*))) + (switch-buffers old buffer #t))) + +(define (use-buffers! initial-buffer) + (add-hook! load-hook + (lambda () + (push-buffer! initial-buffer))) + (add-hook! update-hook + (lambda (dt) + (with-current-buffer buffer + (update-agenda 1) + (update buffer dt)))) + (add-hook! before-draw-hook + (lambda () + (with-current-buffer buffer + (before-draw buffer)))) + (add-hook! after-draw-hook + (lambda () + (with-current-buffer buffer + (after-draw buffer)))) + (add-hook! draw-hook + (lambda (alpha) + (with-current-buffer buffer + (draw buffer alpha)))) + (add-hook! quit-hook + (lambda () + (with-current-buffer buffer + (abort buffer)))) + (add-hook! key-press-hook + (lambda (key scancode modifiers repeat?) + (with-current-buffer buffer + (key-press buffer key scancode modifiers repeat?)))) + (add-hook! key-release-hook + (lambda (key scancode modifiers) + (with-current-buffer buffer + (key-release buffer key scancode modifiers)))) + (add-hook! text-input-hook + (lambda (text) + (with-current-buffer buffer + (text-input buffer text)))) + (add-hook! mouse-press-hook + (lambda (button clicks x y) + (with-current-buffer buffer + (mouse-press buffer button clicks x y)))) + (add-hook! mouse-release-hook + (lambda (button x y) + (with-current-buffer buffer + (mouse-release buffer button x y)))) + (add-hook! mouse-move-hook + (lambda (x y x-rel y-rel buttons) + (with-current-buffer buffer + (mouse-move buffer x y x-rel y-rel buttons)))) + (add-hook! controller-add-hook + (lambda (controller) + (with-current-buffer buffer + (controller-add buffer controller)))) + (add-hook! controller-remove-hook + (lambda (controller) + (with-current-buffer buffer + (controller-remove buffer controller)))) + (add-hook! controller-press-hook + (lambda (controller button) + (with-current-buffer buffer + (controller-press buffer controller button)))) + (add-hook! controller-release-hook + (lambda (controller button) + (with-current-buffer buffer + (controller-release buffer controller button)))) + (add-hook! controller-move-hook + (lambda (controller axis value) + (with-current-buffer buffer + (controller-move buffer controller axis value))))) diff --git a/doc/api.texi b/doc/api.texi index c3543b8..f3878f5 100644 --- a/doc/api.texi +++ b/doc/api.texi @@ -4,6 +4,7 @@ * Math:: Linear algebra and more. * Graphics:: Eye candy. * Audio:: Sound effects and music. +* Buffers:: Splitting games into logical components. * Scripting:: Bringing the game world to life. @end menu @@ -904,6 +905,182 @@ Return @code{#t} if music is currently playing. Return @code{#t} if music is currently paused. @end deffn +@node Buffers +@section Buffers + +Games are big state machines and they can get complex quickly, thus we +need tools to manage that complexity. One useful technique is to +separate the many ``screens'' of a game (such as the main menu, player +select screen, high score table, game over screen, etc.) from each +other, so that it is possible to program the logic for one section of +the game independent of another. In Chickadee, these ``screens'' are +called ``buffers''. In other game engines this same construct may be +called a ``scene'' or ``room''. + +A buffer knows how to update its state, render, handle input events, +etc. Only one buffer is ever active at a time, and the current buffer +can be changed with procedures like @code{push-buffer}, +@code{replace-buffer}, and @code{pop-buffer}. + +Buffers are implemented using Guile's object oriented programming +system (@pxref{GOOPS,,, guile, GNU Guile Reference Manual}). Each +type of buffer is represented as a subclass of the @code{<buffer>} +class. Once a new buffer class has been created, there are many +methods that can be specialized for that class. + +To install buffer support and set the initial buffer, call the +@code{use-buffers!} procedure. + +Let's take a look at a simple example to see how it all comes +together: + +@example +(use-modules (chickadee) + (chickadee buffer) + (chickadee math vector) + (chickadee render sprite) + (chickadee render texture) + (oop goops)) + +;; Define a new buffer type. +(define-class <splash-screen> (<buffer>) + (chickadee #:accessor chickadee #:init-value #f)) + +;; Define the logic for when the buffer is first activated. +(define-method (start (splash <splash-screen>)) + (set! (chickadee splash) (load-image "images/chickadee.png"))) + +;; Define how the buffer should be rendered. +(define-method (draw (splash <splash-screen>) alpha) + (draw-sprite (chickadee splash) (vec2 256.0 176.0))) + +;; Hook into the game engine and make the splash screen the initial buffer. +(use-buffers! (make <splash-screen>)) + +;; Start the game! +(run-game) +@end example + +@deffn {Class} <buffer> +The parent class for all buffers. +@end deffn + +@deffn {Method} started? @var{buffer} +Return @code{#t} if @var{buffer} has been activated by the game engine. +@end deffn + +@deffn {Method} start @var{buffer} +Called when @var{buffer} becomes the current buffer for the first time. +@end deffn + +@deffn {Method} stop @var{buffer} +Called when @var{buffer} is no longer in use. +@end deffn + +@deffn {Method} pause @var{buffer} +Called when @var{buffer} is no longer the current buffer but still in +use. +@end deffn + +@deffn {Method} resume @var{buffer} +Called when @var{buffer} becomes the current buffer after having been +suspended. +@end deffn + +The following methods are simply wrappers around the hooks described +in @xref{Kernel}, so see that section for more detail about the +arguments to these methods. The engine calls these methods with the +current buffer as the first argument. + +@deffn {Method} update @var{buffer} @var{dt} +Advance the simulation running in @var{buffer} by @var{dt} units of +time. +@end deffn + +@deffn {Method} abort @var{buffer} +Called when the user tries to close the game window. +@end deffn + +@deffn {Method} before-draw @var{buffer} +Called before @var{buffer} is rendered. +@end deffn + +@deffn {Method} after-draw @var{buffer} +Called after @var{buffer} is rendered. +@end deffn + +@deffn {Method} draw @var{buffer} @var{alpha} +Render @var{buffer}. +@end deffn + +@deffn {Method} key-press @var{buffer} @var{key} @var{scancode} @var{modifiers} @var{repeat?} +Handle key press event. +@end deffn + +@deffn {Method} key-release @var{buffer} @var{key} @var{scancode} @var{modifiers} +Handle key release event. +@end deffn + +@deffn {Method} text-input @var{buffer} @var{text} +Handle text input event. +@end deffn + +@deffn {Method} mouse-press @var{buffer} @var{button} @var{clicks} @var{x} @var{y} +Handle mouse press event. +@end deffn + +@deffn {Method} mouse-release @var{buffer} @var{button} @var{x} @var{y} +Handle mouse release event. +@end deffn + +@deffn {Method} mouse-move @var{buffer} @var{x} @var{y} @var{x-rel} @var{y-rel} @var{buttons} +Handle mouse move event. +@end deffn + +@deffn {Method} controller-add @var{buffer} @var{controller} +Handle controller add event. +@end deffn + +@deffn {Method} controller-remove @var{buffer} @var{controller} +Handle controller remove event. +@end deffn + +@deffn {Method} controller-press @var{buffer} @var{controller} @var{button} +Handle controller press event. +@end deffn + +@deffn {Method} controller-release @var{buffer} @var{controller} @var{button} +Handle controller release event. +@end deffn + +@deffn {Method} controller-move @var{buffer} @var{controller} @var{axis} @var{value} +Handle controller move event. +@end deffn + +The following procedures are used to manage the buffer stack: + +@deffn {Procedure} use-buffers! @var{initial-buffer} +Install buffers into the game engine and set the current buffer to +@var{initial-buffer}. +@end deffn + +@deffn {Procedure} push-buffer! @var{buffer} +Pause the current buffer and switch to @var{buffer}. +@end deffn + +@deffn {Procedure} pop-buffer! +Stop the current buffer and switch back to the previously active +buffer, or terminate the game loop if the buffer stack is empty. +@end deffn + +@deffn {Procedure} replace-buffer! @var{buffer} +Stop the current buffer and switch to @var{buffer} +@end deffn + +@deffn {Procedure} current-buffer +Return the current buffer. +@end deffn + @node Scripting @section Scripting diff --git a/examples/buffer.scm b/examples/buffer.scm new file mode 100644 index 0000000..4712ea4 --- /dev/null +++ b/examples/buffer.scm @@ -0,0 +1,33 @@ +(use-modules (chickadee) + (chickadee buffer) + (chickadee math vector) + (chickadee render font) + (chickadee render sprite) + (chickadee render texture) + (chickadee scripting) + (oop goops)) + +(define-class <splash-screen> (<buffer>) + (chickadee #:accessor chickadee #:init-value #f)) + +(define-method (start (splash <splash-screen>)) + (set! (chickadee splash) (load-image "images/chickadee.png")) + (after 120 (replace-buffer! (make <main-menu>)))) + +(define-method (draw (splash <splash-screen>) alpha) + (draw-sprite (chickadee splash) (vec2 256.0 176.0))) + +(define-class <main-menu> (<buffer>) + (font #:accessor menu-font #:init-value #f)) + +(define-method (start (menu <main-menu>)) + (set! (menu-font menu) (load-font "fonts/good_neighbors_starling.xml"))) + +(define-method (draw (menu <main-menu>) alpha) + (draw-text (menu-font menu) "press any key to exit" (vec2 200.0 240.0))) + +(define-method (key-press (menu <main-menu>) key scancode modifiers repeat?) + (pop-buffer!)) + +(use-buffers! (make <splash-screen>)) +(run-game) |