game.worlds overhaul: add optional support for integrating audio.engine and make game.input integration optional too. add "tick-game-world" generic for extending game-world's tick* method

db4
Joe Groff 2010-01-19 15:02:47 -08:00
parent 3cfe2afa44
commit e5c44b95aa
7 changed files with 81 additions and 59 deletions

View File

@ -126,7 +126,7 @@ M: world request-focus-on ( child gadget -- )
[ T{ rgba f 1.0 1.0 1.0 1.0 } ] if ; [ T{ rgba f 1.0 1.0 1.0 1.0 } ] if ;
GENERIC# apply-world-attributes 1 ( world attributes -- world ) GENERIC# apply-world-attributes 1 ( world attributes -- world )
M: world apply-world-attributes ( world attributes -- world ) M: world apply-world-attributes
{ {
[ title>> >>title ] [ title>> >>title ]
[ status>> >>status ] [ status>> >>status ]

View File

@ -1,21 +1,28 @@
! (c)2009 Joe Groff bsd license ! (c)2009 Joe Groff bsd license
USING: game.loop help.markup help.syntax kernel math method-chains USING: audio.engine game.loop help.markup help.syntax kernel math method-chains
ui ui.gadgets.worlds words ; ui ui.gadgets.worlds words ;
IN: game.worlds IN: game.worlds
HELP: GAME:
{ $syntax """GAME: word { attributes }
attribute-code ;""" }
{ $description "Similar to " { $link POSTPONE: MAIN-WINDOW: } ", defines a main entry point " { $snippet "word" } " for the current vocabulary that opens a UI window with the provided " { $snippet "attributes" } ". In addition to the standard " { $link world-attributes } ", additional " { $link game-attributes } " can be specified to specify game-specific attributes. Unlike " { $link POSTPONE: MAIN-WINDOW: } ", the " { $snippet "attributes" } " for " { $snippet "GAME:" } " must provide values for the " { $snippet "world-class" } " and " { $snippet "tick-interval-micros" } " slots." } ;
HELP: game-attributes HELP: game-attributes
{ $class-description "Extends the " { $link world-attributes } " tuple class with extra attributes for " { $link game-world } "s:" } { $class-description "Extends the " { $link world-attributes } " tuple class with extra attributes for " { $link game-world } "s:" }
{ $list { $list
{ { $snippet "tick-interval-micros" } " specifies the number of microseconds between consecutive calls to the world's " { $link tick* } " method by the game loop." } { { $snippet "tick-interval-micros" } " specifies the number of microseconds between consecutive calls to the world's " { $link tick-game-world } " method by the game loop. An integer greater than zero must be provided." }
{ { $snippet "use-game-input?" } " specifies whether the game world should initialize the " { $vocab-link "game.input" } " library for use by the game. False by default." }
{ { $snippet "use-audio-engine?" } " specifies whether the game world should manage an " { $link audio-engine } " instance. False by default." }
{ { $snippet "audio-engine-device" } " specifies the string name of the OpenAL device the audio engine, if any, should try to open. The default value of " { $link POSTPONE: f } " attempts to open the default OpenAL device." }
{ { $snippet "audio-engine-voice-count" } " determines the number of independent voices the audio engine will make available. This determines how many individual audio clips can play simultaneously. This cannot exceed the OpenAL implementation's limit on supported voices." }
{ { $snippet "audio-engine-buffer-size" } " determines the size in bytes of the audio buffers the audio engine will stream to the sound card." }
{ { $snippet "audio-engine-buffer-count" } " determines the number of buffers the audio engine will allocate per audio clip played." }
} ; } ;
HELP: game-world HELP: game-world
{ $class-description "A subclass of " { $link world } " that automatically sets up and manages connections to the " { $vocab-link "game.loop" } " and " { $vocab-link "game.input" } " libraries. It does this by providing methods on " { $link begin-world } ", " { $link end-world } ", and " { $link draw* } ". Subclasses can provide their own world setup and teardown code by adding methods to the " { $link begin-game-world } " and " { $link end-game-world } " generic words." } ; { $class-description "A subclass of " { $link world } " that automatically sets up and manages connections to the " { $vocab-link "game.loop" } ", " { $vocab-link "game.input" } ", and " { $vocab-link "audio.engine" } " libraries. It does this by providing methods on " { $link begin-world } ", " { $link end-world } ", and " { $link draw* } ". Subclasses can provide their own world setup and teardown code by adding methods to the " { $link begin-game-world } " and " { $link end-game-world } " generic words."
$nl
"The game-world tuple has the following publicly accessible slots:"
{ $list
{ { $snippet "game-loop" } " contains the " { $link game-loop } " instance managed by the game world. If the world is inactive, this slot will contain " { $link POSTPONE: f } "." }
{ { $snippet "audio-engine" } " contains the " { $link audio-engine } " instance managed by the game world. If the world is inactive, or the " { $snippet "use-audio-engine?" } " slot of the " { $link game-attributes } " object used to initialize the world was false, this slot will contain " { $link POSTPONE: f } "." }
} } ;
HELP: begin-game-world HELP: begin-game-world
{ $values { "world" game-world } } { $values { "world" game-world } }
@ -25,26 +32,23 @@ HELP: end-game-world
{ $values { "world" game-world } } { $values { "world" game-world } }
{ $description "This generic word is called by the " { $link end-world } " method for " { $link game-world } " subclasses immediately after the game world stops the game loop." } ; { $description "This generic word is called by the " { $link end-world } " method for " { $link game-world } " subclasses immediately after the game world stops the game loop." } ;
{ game-world begin-game-world end-game-world } related-words HELP: tick-game-world
{ $values { "world" game-world } }
{ $description "This generic word is called by the " { $link tick* } " method for " { $link game-world } " subclasses every time the game loop's tick interval occurs." } ;
HELP: tick-interval-micros { game-world begin-game-world end-game-world tick-game-world } related-words
{ $values
{ "world" game-world }
{ "micros" integer }
}
{ $description "Subclasses of " { $link game-world } " can override this class to specify the number of microseconds between consecutive calls to the game world's " { $link tick* } " method by the game loop. Using the " { $link POSTPONE: GAME: } " syntax will define this method for you." } ;
ARTICLE: "game.worlds" "Game worlds" ARTICLE: "game.worlds" "Game worlds"
"The " { $vocab-link "game.worlds" } " vocabulary provides a " { $link world } " subclass that integrates with " { $vocab-link "game.loop" } " and " { $vocab-link "game.input" } " to quickly provide game infrastructure." "The " { $vocab-link "game.worlds" } " vocabulary provides a " { $link world } " subclass that integrates with " { $vocab-link "game.loop" } " and optionally " { $vocab-link "game.input" } " and " { $vocab-link "audio.engine" } " to quickly provide game infrastructure."
{ $subsections { $subsections
game-world game-world
game-attributes game-attributes
POSTPONE: GAME:
} }
"Subclasses of " { $link game-world } " can provide their own setup and teardown code by providing methods for these generic words:" "Subclasses of " { $link game-world } " can provide their own setup, teardown, and update code by providing methods for these generic words:"
{ $subsections { $subsections
begin-game-world begin-game-world
end-game-world end-game-world
tick-game-world
} ; } ;
ABOUT: "game.worlds" ABOUT: "game.worlds"

View File

@ -1,71 +1,86 @@
! (c)2009 Joe Groff bsd license ! (c)2009 Joe Groff bsd license
USING: accessors combinators fry game.input game.loop generic kernel math USING: accessors combinators fry game.input game.loop generic kernel math
parser sequences ui ui.gadgets ui.gadgets.worlds ui.gestures threads parser sequences ui ui.gadgets ui.gadgets.worlds ui.gestures threads
words ; words audio.engine destructors ;
IN: game.worlds IN: game.worlds
TUPLE: game-world < world TUPLE: game-world < world
game-loop game-loop
audio-engine
{ tick-interval-micros fixnum }
{ use-game-input? boolean }
{ use-audio-engine? boolean }
{ audio-engine-device initial: f }
{ audio-engine-voice-count initial: 16 }
{ audio-engine-buffer-size initial: 8192 }
{ audio-engine-buffer-count initial: 2 }
{ tick-slice float initial: 0.0 } ; { tick-slice float initial: 0.0 } ;
GENERIC: tick-interval-micros ( world -- micros )
GENERIC: begin-game-world ( world -- ) GENERIC: begin-game-world ( world -- )
M: object begin-game-world drop ; M: object begin-game-world drop ;
GENERIC: end-game-world ( world -- ) GENERIC: end-game-world ( world -- )
M: object end-game-world drop ; M: object end-game-world drop ;
GENERIC: tick-game-world ( world -- )
M: object tick-game-world drop ;
M: game-world tick*
[ tick-game-world ]
[ audio-engine>> [ update-audio ] when* ] bi ;
M: game-world draw* M: game-world draw*
swap >>tick-slice relayout-1 yield ; swap >>tick-slice relayout-1 yield ;
<PRIVATE
: open-game-audio-engine ( game-world -- audio-engine )
{
[ audio-engine-device>> ]
[ audio-engine-voice-count>> ]
[ audio-engine-buffer-size>> ]
[ audio-engine-buffer-count>> ]
} cleave <audio-engine>
[ start-audio* ] keep ; inline
PRIVATE>
M: game-world begin-world M: game-world begin-world
open-game-input dup use-game-input?>> [ open-game-input ] when
dup use-audio-engine?>> [ dup open-game-audio-engine >>audio-engine ] when
dup begin-game-world dup begin-game-world
dup [ tick-interval-micros ] [ ] bi <game-loop> [ >>game-loop ] keep start-loop dup [ tick-interval-micros>> ] [ ] bi <game-loop> [ >>game-loop ] keep start-loop
drop ; drop ;
M: game-world end-world M: game-world end-world
[ [ stop-loop ] when* f ] change-game-loop [ [ stop-loop ] when* f ] change-game-loop
end-game-world [ end-game-world ]
close-game-input ; [ audio-engine>> [ dispose ] when* ]
[ use-game-input?>> [ close-game-input ] when ] tri ;
TUPLE: game-attributes < world-attributes TUPLE: game-attributes < world-attributes
{ tick-interval-micros fixnum read-only } ; { tick-interval-micros fixnum }
{ use-game-input? boolean initial: f }
{ use-audio-engine? boolean initial: f }
{ audio-engine-device initial: f }
{ audio-engine-voice-count initial: 16 }
{ audio-engine-buffer-size initial: 8192 }
{ audio-engine-buffer-count initial: 2 } ;
<PRIVATE M: game-world apply-world-attributes
: verify-game-attributes ( attributes -- )
{ {
[ [ tick-interval-micros>> >>tick-interval-micros ]
world-class>> { f world } member? [ use-game-input?>> >>use-game-input? ]
[ "GAME: must be given a custom world-class" throw ] when [ use-audio-engine?>> >>use-audio-engine? ]
] [ audio-engine-device>> >>audio-engine-device ]
[ [ audio-engine-voice-count>> >>audio-engine-voice-count ]
tick-interval-micros>> 0 <= [ audio-engine-buffer-size>> >>audio-engine-buffer-size ]
[ "GAME: must be given a nonzero tick-interval-micros" throw ] when [ audio-engine-buffer-count>> >>audio-engine-buffer-count ]
] [ call-next-method ]
} cleave ; } cleave ;
: define-game-tick-interval-micros ( attributes -- )
[ world-class>> \ tick-interval-micros create-method ]
[ tick-interval-micros>> '[ drop _ ] ] bi
define ;
: define-game-methods ( attributes -- )
{
[ verify-game-attributes ]
[ define-game-tick-interval-micros ]
} cleave ;
: define-game ( word attributes quot -- )
[ define-main-window ]
[ drop nip define-game-methods ] 3bi ;
PRIVATE>
SYNTAX: GAME: SYNTAX: GAME:
CREATE CREATE
game-attributes parse-main-window-attributes game-attributes parse-main-window-attributes
parse-definition parse-definition
define-game ; define-main-window ;

View File

@ -306,6 +306,7 @@ GAME: bunny-game {
T{ depth-bits { value 24 } } T{ depth-bits { value 24 } }
} } } }
{ grab-input? t } { grab-input? t }
{ use-game-input? t }
{ pref-dim { 1024 768 } } { pref-dim { 1024 768 } }
{ tick-interval-micros $[ 60 fps ] } { tick-interval-micros $[ 60 fps ] }
} ; } ;

View File

@ -81,7 +81,7 @@ CONSTANT: fov 0.7
AFTER: raytrace-world resize-world AFTER: raytrace-world resize-world
dup dim>> dup first2 min >float v/n fov v*n >>fov drop ; dup dim>> dup first2 min >float v/n fov v*n >>fov drop ;
AFTER: raytrace-world tick* AFTER: raytrace-world tick-game-world
spheres>> [ tick-sphere ] each ; spheres>> [ tick-sphere ] each ;
M: raytrace-world draw-world* M: raytrace-world draw-world*
@ -102,6 +102,7 @@ GAME: raytrace-game {
double-buffered double-buffered
} } } }
{ grab-input? t } { grab-input? t }
{ use-game-input? t }
{ pref-dim { 1024 768 } } { pref-dim { 1024 768 } }
{ tick-interval-micros $[ 60 fps ] } { tick-interval-micros $[ 60 fps ] }
} ; } ;

View File

@ -121,7 +121,7 @@ CONSTANT: fov 0.7
: wasd-mouse-input ( world -- ) : wasd-mouse-input ( world -- )
read-mouse rotate-with-mouse ; read-mouse rotate-with-mouse ;
M: wasd-world tick* M: wasd-world tick-game-world
dup focused?>> [ dup focused?>> [
[ wasd-keyboard-input ] [ wasd-mouse-input ] bi [ wasd-keyboard-input ] [ wasd-mouse-input ] bi
reset-mouse reset-mouse

View File

@ -217,7 +217,7 @@ terrain-world H{
[ tick-player-reverse ] [ tick-player-reverse ]
[ tick-player-forward ] if ; [ tick-player-forward ] if ;
M: terrain-world tick* M: terrain-world tick-game-world
[ dup focused?>> [ handle-input ] [ drop ] if ] [ dup focused?>> [ handle-input ] [ drop ] if ]
[ dup player>> tick-player ] bi ; [ dup player>> tick-player ] bi ;
@ -295,6 +295,7 @@ GAME: terrain-game {
double-buffered double-buffered
T{ depth-bits { value 24 } } T{ depth-bits { value 24 } }
} } } }
{ use-game-input? t }
{ grab-input? t } { grab-input? t }
{ pref-dim { 1024 768 } } { pref-dim { 1024 768 } }
{ tick-interval-micros $[ 60 fps ] } { tick-interval-micros $[ 60 fps ] }