diff options
author | Colin Okay <colin@cicadas.surf> | 2022-07-08 12:13:16 -0500 |
---|---|---|
committer | Colin Okay <colin@cicadas.surf> | 2022-07-08 12:13:16 -0500 |
commit | 1cba62c355602528890ecf8f669f5362e4c4d9a7 (patch) | |
tree | d953fc00289048f61ea396b147c234dd548c0f0b | |
parent | 8bf0e572ee084e6659f56b2fecd44a54e2775b6c (diff) |
[add] [doc] README; [refactor] rendering of containers
-rw-r--r-- | README.org | 196 | ||||
-rw-r--r-- | examples/02-moving-bitmp.lisp | 4 | ||||
-rw-r--r-- | examples/08-pong.lisp | 11 | ||||
-rw-r--r-- | src/core/container.lisp | 2 |
4 files changed, 182 insertions, 31 deletions
@@ -7,7 +7,7 @@ /A Sprite System in Common Lisp for Games and GUIs/ ** Installation - + Ensure that sdl2 is installed on your system. *** Fetch Systems That Are Not In Quicklisp @@ -41,52 +41,204 @@ Then load one of the example files and call its "start" function: #+end_src -** TODO Basic Use +** Basic Use + + The best introduction is to look at and play with the examples. + What follows is a cursory explanation of how pieces are meant to + fit together. + *** The Application + +When you want to use Wheelwork, you must make a sublcass of ~application~. The ~application~ is the main container supplied by wheelwork. It is the root of the [[*The Display Tree Protocol: Units and Containers][display tree]], holds references to [[*The Asset Protocol][loaded assets]], and handles [[*Events & Event Handling][events]] from the user. - The best introduction is to look at and play with the examples. - What follows is a cursory explanation of how pieces are meant to - fit together. +For example, from the pong example in =examples/08-pong.lisp=, the application class looks like + +#+begin_src lisp +(defclass/std solo-pong (ww:application) + ((paddle ball game-over intro-text))) +#+end_src -**** Your Application +/note: I'm using defclass-std:defclass/std to define the above, see [[https://quickdocs.org/defclass-std][here]] for more./ +This defines a subclass of appliation along with some state needed for the pong game. + **** The Boot Method +It isn't enough to define a subclass, you must also implement a ~ww:boot~ method for your application class. The boot method is called right after the opengl context becomes available. Inside boot, you are expected to do everything necessary to start your game: load assets, create some display units, add them to the scene, and add event handlers. Here is what the boot method looks like for the pong game. + +#+begin_src lisp + +(defmethod ww::boot ((app solo-pong)) + "Adds the intro text and sets up the start button handler." + (sdl2:hide-cursor) + (let ((intro-text + (make-instance + 'ww:text + :content "Press any key to start" + :font (ww::get-asset "Ticketing.ttf") + :x 160 + :y 300 + :scale-x 3.0 + :scale-y 3.0))) + (setf (intro-text app) intro-text) + (ww:add-unit app intro-text)) ; add to scene + (ww:add-handler app #'press-to-start)) + +#+end_src + +You can see that it does very little. It first creates an instance of [[*Text][text]], with the appropriate scale and starting position. Then it just sets the app's ~intro-text~ slot to the newly created text before adding to the scene. + +It ends by adding a handler to the app called ~press-to-start~, where presumably, the rest of the game is set up. + **** The Shutdown Method -*** Display Units +The shutdown method is optional and is called right before the application exits. If supplied, it is a good place to do things like: make save files, close network connections, clean up foreign memory resources that are not already managed by the [[*The Asset Protocol][asset protocol]]. + + +*** The Display Tree Protocol: Units and Containers + +Objects that render to the screen are organized into a display tree. There are two basic kinds of objects: units and containers. Units are, roughly, "what you want to display" and containers are "when and where you want to display it". Additionally, containers hold a list of units. -**** Bitmaps +In general, the last thing added to a container will be the last thing rendered - i.e. it will be "on top". Containers are themselves units, so they too can be added to other containers. Nesting of containers allows you to render a one set of units before or after some other set of units. -**** Text +The application is a container, and is the only unit that does not need to be added to a scene. Nothing else will display until it becomes part of the display tree rooted at the application. -**** Sprites +Containers have "bounds", which are screen coordinates for the left, right, top, and bottom of the region inside of which units will be displayed. If a unit moves out of bounds, it will not show up on the screen, and will not receive mouse events. (It may, however, still be focused - and hence receive key events.) The bounds of the application are the visible window itself. Other containers may have custom bounds. + +See =examples/07-scrollarea.lisp= for an example that uses a container. **** Containers +The main thing you can do with containers is add and remove units: + +: (add-unit app my-unit) +: (drop-unit my-unit) ;; it knows its container + +You can also adjust their bounds, for example: + +: (setf (container-top my-container) 100) + +**** The Affine Units + +Most units of any interest implement the "affine protocol". I.e they have orientation and scale in the 2d plane of the game window. + +More specifically, you can use the following accessor functions on them: + + : x + : y + : width + : height + : scale-x + : scale-y + : rotation + +There are a few convenience functions also defined that use the above functions under-the-hood. I'm not including them here because the API is still stabilizing. + +The affine units are things like: + ++ ~bitmap~: display an image that has been loaded from a file asset (currently only png is supported) ++ ~text~: display text ++ ~frameset~: display an animated sequence of images ++ ~sprite~: display a "bundle" of framesets + *** Events & Event Handling -**** User Input Events vs Psuedo-Events +Anything that "happens" in a wheelwork application happens through the course of handling some event. + +Wheelwork defines an ~event-handler~ class that is used to to create functions for handling events. Instances of ~event-handler~ are funcallable objects with a slot that specifies the kind of event being handled. + +Event handlers are added to instances of ~interactive~, which includes most kinds of units and the application itself. Notably, the base ~container~ class is not a subclass of ~interactive~ and so cannot handle events. -**** Event Handler Macros +Wheelwork provides a macro called ~defhandler~ that can be used easily create instances of ~event-handler~ and bind them to a name. Most event handlers are created using a macro that looks like ~on-EVENTNAME~, which are discussed below. -**** Focus Events +Here is a simple example where a handler is defined, from the pong game: -**** Perframe Events +#+begin_src lisp +(ww:defhandler pong-mousemove + (ww:on-mousemotion (app) + (setf (ww:x (paddle app)) + (- x (* 0.5 (ww:width (paddle app)))) + (dx (paddle app)) xrel))) +#+end_src + +See the example for details. + +There are two kinds of events: User Interaction Events and Psuedoevents. + +**** User Interaction Events + +User Interaction events are, curiously enough, generated by user interaction. These include + +: keydown +: keyup +: mousewheel +: mousedown +: mouseup +: mousemotion + +The first three will fire on whichever object has focus. The last three will fire on the first visible object that intersects with the cursor. + +**** Psuedoevents + +Psuedoevents are generated by the wheelwork itself, and include the following: + +Display tree events: + +: after-added +: before-added +: before-dropped + +Focus events + +: focus +: blur + +Frame events -**** Display Tree Events +: perframe -**** Mouse Events +See the documentation for the ~on-*~ forms for these events to get a sense of how to handle them. -**** Key Events -*** Extending Wheelwork -**** The Display Tree Protocol - -**** The Asset Protocol +*** The Asset Protocol + +Assets are resources loaded from disk. The application's ~asset-classifiers~ list associates file extensions (like "ttf", and "png") with classes (like ~font~ and ~png~) that load and prepare assets for use in an application. + +Every asset has a "key", which is just a string path name that is relative to the application's ~asset-root~. These keys are used by ~get-asset~ to fetch assets, possibly loading them for the first time if they have not been previously fetched. + +Some classes (like ~text~ or ~bitmap~) require an instance of an asset class to fill one of their instance slots (like ~font~ or ~texture~) in order to work properly. + +E.g. In ~examples/03-font-render.lisp~ you see + +#+begin_src lisp + +(make-instance + 'ww::text + :content (format nil "Hell!~%Oh World...") + :font (ww::get-asset "Ticketing.ttf" + :asset-args '(:oversample 2))) +#+end_src + +which fetches the ~"Ticketing.ttf"~ font asset for use in rendering the text content. + +The asset root relative to which ~"Ticketing.ttf"~ is resolved can be set during instantiation of the application. + +e.g., for the same example: + +#+begin_src lisp +(make-instance + 'font-display + :fps 60 + :refocus-on-mousedown-p nil + :width 800 + :height 600 + :title "Wheelwork Example: Font display" + :asset-root (merge-pathnames + "examples/" + (asdf:system-source-directory :wheelwork))) +#+end_src -**** The Affine Protocol diff --git a/examples/02-moving-bitmp.lisp b/examples/02-moving-bitmp.lisp index bf05a7c..b07f1aa 100644 --- a/examples/02-moving-bitmp.lisp +++ b/examples/02-moving-bitmp.lisp @@ -125,8 +125,8 @@ (ww::add-handler bm2 #'look-away) (ww::add-handler bm2 #'wheelie) - (ww::add-unit app bm2) - (ww::add-unit app bm))) + (ww::add-unit app bm) + (ww::add-unit app bm2))) (defun start () diff --git a/examples/08-pong.lisp b/examples/08-pong.lisp index 51de28d..6fc9faa 100644 --- a/examples/08-pong.lisp +++ b/examples/08-pong.lisp @@ -173,19 +173,18 @@ on which boundary VAL is outside of." (defmethod ww::boot ((app solo-pong)) "Adds the intro text and sets up the start button handler." (sdl2:hide-cursor) - (ww::add-unit - app - (setf (intro-text app) + (let ((intro-text (make-instance - 'ww::text + 'ww:text :content "Press any key to start" :font (ww::get-asset "Ticketing.ttf") :x 160 :y 300 :scale-x 3.0 :scale-y 3.0))) - - (ww::add-handler app #'press-to-start)) + (setf (intro-text app) intro-text) + (ww:add-unit app intro-text)) + (ww:add-handler app #'press-to-start)) (defun start () (ww::start diff --git a/src/core/container.lisp b/src/core/container.lisp index d332cb8..a9e6caf 100644 --- a/src/core/container.lisp +++ b/src/core/container.lisp @@ -75,7 +75,7 @@ necessary." (with-slots (left right top bottom) container (gl:scissor (* left scale) (* scale bottom) (* scale (- right left)) (* scale (- top bottom))) (unwind-protect - (dolist (u (container-units container)) + (dolist (u (reverse (container-units container))) (if (visible-in-container-p u) (render u))) (gl:scissor (aref current 0) (aref current 1) (aref current 2) (aref current 3)))))) |