syndicate-2017/examples/platformer/game.rkt

302 lines
10 KiB
Racket
Raw Normal View History

2015-10-20 15:36:37 +00:00
#lang prospect
(require racket/set)
(require racket/match)
2015-10-22 21:04:17 +00:00
(require prospect/drivers/timer)
2015-10-22 21:59:31 +00:00
(require plot/utils) ;; for vector utilities
(require prospect-gl/2d)
2015-10-22 21:04:17 +00:00
2015-10-20 15:36:37 +00:00
#|
Layers:
2015-10-20 19:57:04 +00:00
- External I/O
as arranged by prospect-gl/2d
including keyboard events, interface to rendering, and frame timing
2015-10-20 19:57:04 +00:00
2015-10-20 15:36:37 +00:00
- Ground
2015-10-20 19:57:04 +00:00
corresponds to computer itself
device drivers
2015-10-22 20:48:18 +00:00
applications (e.g. in this instance, the game)
2015-10-20 19:57:04 +00:00
2015-10-20 15:36:37 +00:00
- Game
2015-10-20 19:57:04 +00:00
running application
per-game state, such as score and count-of-deaths
process which spawns levels
regular frame ticker
2015-10-20 15:36:37 +00:00
- Level
2015-10-20 19:57:04 +00:00
model of the game world
actors represent entities in the world, mostly
misc actors do physicsish things
## Common Data Definitions
2015-10-22 20:48:18 +00:00
A Vec is a (vector Number Number)
A Point is a (vector Number Number)
(See vector functions in plot/utils)
2015-10-20 19:57:04 +00:00
## Ground Layer Protocols
- Scene Management
2015-10-20 19:57:04 +00:00
- assertion: ScrollOffset
- role: SceneManager
Displays the scene backdrop and adjusts display coordinates via ScrollOffset.
2015-10-20 19:57:04 +00:00
A ScrollOffset is a (scroll-offset Vec), indicating the vector to *subtract*
from world coordinates to get device coordinates.
## Game Layer Protocols
- Scoring
- message: AddToScore
- assertion: CurrentScore
- role: ScoreKeeper
Maintains the score as private state.
Publishes the score using a CurrentScore.
Responds to AddToScore by updating the score.
- Level Spawning
- assertion: LevelRunning
- message: LevelCompleted
- role: LevelSpawner
Maintains the current level number as private state.
Spawns a new Level when required.
Monitors LevelRunning - when it drops, the level is over.
Receives LevelCompleted messages. If LevelRunning drops without
a LevelCompleted having arrived, the level ended in failure and
should be restarted. If LevelComplete arrived before LevelRunning
dropped, the level was completed successfully, and the next level
should be presented.
- role: Level
Running level instance. Maintains LevelRunning while it's still
going. Sends LevelCompleted if the player successfully completed
the level.
An AddToScore is an (add-to-score Number), a message
which signals a need to add the given number to the player's
current score.
A CurrentScore is a (current-score Number), an assertion
indicating the player's current score.
A LevelRunning is a (level-running), an assertion indicating that the
current level is still in progress.
2015-10-20 15:36:37 +00:00
2015-10-20 19:57:04 +00:00
A LevelCompleted is a (level-completed), a message indicating that
the current level was *successfully* completed before it terminated.
2015-10-20 15:36:37 +00:00
2015-10-20 19:57:04 +00:00
## Level Layer Protocols
- Movement and Physics
- message: JumpRequest
- assertion: Impulse
- assertion: Velocity
- assertion: Position
- assertion: Massive
- assertion: Attribute
- assertion: InitialPosition
- role: PhysicsEngine
Maintains positions, velocities and accelerations of all GamePieces.
Uses InitialPosition to place a piece at its creation.
Publishes Velocity and Position to match.
Listens to FrameDescription, using it to advance the simulation.
Takes Impulses as the baseline for moving GamePieces around.
For Massive GamePieces, applies gravitational acceleration.
Computes collisions between GamePieces.
Uses Attributed Aspects of GamePieces to decide what to do in response.
Sometimes, this involves sending Damage.
Responds to JumpRequest by checking whether the named piece is in a
jumpable location, and sets its upward velocity negative if so.
Consumer of LevelSize to figure out regions where, if a GamePiece
crosses into them, Damage should be dealt to the piece.
When the player touches a goal, sends LevelCompleted one layer out and
then kills the world. When the player vanishes from the board, kills
the world.
- role: GamePiece
Maintains private state. Asserts Impulse to move around, asserts Massive
and Attribute as required, asserts InitialPosition to get things
started. May issue JumpRequests at any time. Represents both the player,
enemies, the goal(s), and platforms and blocks in the environment.
Asserts a Sprite two layers out to render itself.
- Player State
- message: Damage
- assertion: Health
- role: Player
Maintains hitpoints, which it reflects using Health.
Responds to Damage.
When hitpoints drop low enough, removes the player from the board.
- World State
- assertion: LevelSize
- role: DisplayControl
Maintains a LevelSize assertion.
Observes the Position of the player, and computes and maintains a
ScrollOffset two layers out, to match.
An ID is a Symbol; the special symbol 'player indicates the player's avatar.
Gensyms from (gensym 'enemy) name enemies, etc.
A JumpRequest is a (jump-request ID), a message indicating a *request* to jump,
not necessarily honoured by the physics engine.
An Impulse is an (impulse ID Vec), an assertion indicating a contribution to
the net *requested* velocity of the given gamepiece.
A Velocity is a (velocity ID Vec), an assertion describing the net *actual*
velocity of the named gamepiece.
A Position is a (position ID Point), an assertion describing the current actual
position of the named gamepiece.
A Massive is a (massive ID), an assertion noting that the named gamepiece
should be subject to the effects of gravity.
An Attribute is an (attribute ID Aspect), an assertion describing some aspect of
the named gamepiece
An Aspect is either
2015-10-22 20:48:18 +00:00
- 'player - the named piece is a player avatar
2015-10-20 19:57:04 +00:00
- 'enemy - the named piece is an enemy
2015-10-22 20:48:18 +00:00
- 'solid - the named piece can be stood on / jumped from
- 'goal - the named piece, if touched, causes the level to
2015-10-20 19:57:04 +00:00
End The Game In Victory
An InitialPosition is an (initial-position ID Point), an assertion specifying
not only the *existence* but also the initial position (in World coordinates)
of the named gamepiece.
A Damage is a (damage ID Number), a message indicating an event that should
consume the given number of health points of the named gamepiece.
A Health is a (health ID Number), an assertion describing the current hitpoints
of the named gamepiece.
A LevelSize is a (level-size Vec), an assertion describing the right-hand and
bottom edges of the level canvas (in World coordinates).
-----------
Interaction Diagrams (to be refactored into the description later)
================================================================================
2015-10-20 15:36:37 +00:00
title Jump Sequence
Player -> Physics: (jump 'player)
note right of Physics: Considers the request.
note right of Physics: Denied -- Player is not on a surface.
Player -> Physics: (jump 'player)
note right of Physics: Considers the request.
note right of Physics: Accepted.
note right of Physics: Updates velocity, position
Physics -> Subscribers: (vel 'player ...)
Physics -> Subscribers: (pos 'player ...)
2015-10-20 19:57:04 +00:00
================================================================================
2015-10-20 15:36:37 +00:00
title Display Control Updates
Physics -> DisplayCtl: (pos 'player ...)
note right of DisplayCtl: Compares player pos to level size
DisplayCtl -> Subscribers: (at-meta (at-meta (scroll-offset ...)))
2015-10-20 19:57:04 +00:00
================================================================================
title Movement Sequence
Moveable -> Physics: (massive ID)
Moveable -> Physics: (attr ID ...)
Moveable -> Physics: (impulse ID vec)
note right of Physics: Processes simulation normally
Physics -> Subscribers: (pos ID ...)
Physics -> Subscribers: (vel ID ...)
================================================================================
title Keyboard Interpretation
Keyboard -> Player: (press right-arrow)
Player -->> Physics: assert (impulse ID (vec DX 0))
2015-10-20 15:36:37 +00:00
2015-10-20 19:57:04 +00:00
note right of Physics: Processes simulation normally
2015-10-20 15:36:37 +00:00
2015-10-20 19:57:04 +00:00
Keyboard -> Player: (press left-arrow)
Player -->> Physics: assert (impulse ID (vec 0 0))
2015-10-20 15:36:37 +00:00
2015-10-20 19:57:04 +00:00
Keyboard -> Player: (release right-arrow)
Player -->> Physics: assert (impulse ID (vec -DX 0))
2015-10-20 15:36:37 +00:00
2015-10-20 19:57:04 +00:00
Keyboard -> Player: (press space)
Player -> Physics: (jump)
2015-10-20 15:36:37 +00:00
2015-10-20 19:57:04 +00:00
|#
2015-10-20 15:36:37 +00:00
2015-10-22 21:04:17 +00:00
;;---------------------------------------------------------------------------
2015-10-22 21:59:31 +00:00
;; Keyboard and Display
2015-10-22 21:04:17 +00:00
;; A KeyStateChangeEvent is either
;; - (key-press KeyEvent) ;; from 2htdp
;; - (key-release KeyEvent)
;; signalling a key state change.
(struct key-press (key) #:prefab)
(struct key-release (key) #:prefab)
2015-10-22 21:59:31 +00:00
;; A ScreenSize is a (screen-size Vec), indicating the size of the device.
(struct screen-size (vec) #:prefab)
;; The canvas here both delivers keyboard events and serves as a
;; display medium.
(define game-canvas%
(class canvas%
(init-field key-handler)
(super-new)
(define/override (on-char event)
(match (send event get-key-code)
['release (key-handler (key-release (send event get-key-release-code)))]
[other (key-handler (key-press other))]))))
;; Construct, show and return a game-canvas%.
;; Keypresses will result in ground-messages.
(define (make-frame width height)
(parameterize ((current-eventspace (make-eventspace)))
(define frame (new frame%
[label "Prospect Platformer"]
[width width]
[height height]))
(define canvas
(new game-canvas%
[parent frame]
[key-handler send-ground-message]))
(send canvas focus)
(send frame show #t)
canvas))
2015-10-22 21:04:17 +00:00
;; -> KeyboardIntegrator
(define (spawn-keyboard-driver)
(spawn (lambda (e s)
...)
(void)
(sub (
))
2015-10-22 21:59:31 +00:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2015-10-22 21:04:17 +00:00
(spawn-timer-driver)
(spawn-keyboard-driver)
2015-10-22 21:59:31 +00:00
;;(spawn-display-driver)
(let ((canvas (make-frame 600 400)))
;; Retrieve the actual displayed size of the canvas, which differs
;; from the requested frame size because of window chrome etc.
(define the-screen-size
(let-values (((x-max y-max) (send canvas get-client-size)))
(screen-size (vector x-max y-max))))
(define the-dc (send canvas get-dc))
;;
;; So equipped, we may spawn the renderer.
(spawn-renderer the-screen-size the-dc))