summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el4
-rw-r--r--Makefile.am3
-rw-r--r--chickadee/buffer.scm233
-rw-r--r--doc/api.texi177
-rw-r--r--examples/buffer.scm33
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)