#+TITLE: Wheelwork : Wheelwork \Wheel"work`\, n. (Mach.) : A combination of wheels, and their connection, in a machine : or mechanism. : [1913 Webster] /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 #+begin_src shell mkdir ~/wheelwork-playground cd ~/wheelwork-playground git clone https://cicadas.surf/cgit/colin/wheelwork.git wheelwork #+end_src *** Fire up Slime You'll want to let quickisp know about the =wheelwork-playground= directory #+begin_src lisp (pushnew #P"~/wheelwork-playground/" ql:*local-project-directories*) (ql:register-local-projects ) (ql:quickload :wheelwork) #+end_src *** Try the examples Then load one of the example files and call its "start" function: #+begin_src lisp (ql:quickload :wheelwork-examples) (ww.examples/6:start) #+end_src ** Basic Use The best introduction to wheelwork comes through looking at and playing with the examples. IN addition to playing with examples, consider what follows as a suppliment that helps explain how some of the pieces 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. 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 /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 Window & Scale & Coordinates Wheelwork uses SDL2 to create windows and generate events. The application includes a global scale factor that affects how the game interperets coordinates inside the window. A window, for example, can be 800x600 pixels on your computer monitor, but if the application's scale factor is 2.0, then it will only have a 400x300 logical space of coordinates. If you add a sprite that is 30x30 pixels big, it will appear twice as large, but it will still logically be 30x30. **** 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~ 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 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**. Roughly, units are the things you want to display and containers help to control when and where you to display them. Units are added to and removed from containers, and a unit will belong to at most one container at a time. In general, the last thing added to a container will be the last thing rendered - i.e. it will appear to 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. The application is itself a container, and is the only unit that does not need to be added to a scene. No unit will be displayed until it becomes part of the display tree rooted at the application. 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 =0,0= coordinate is the bottom left corner fo the game window, and the top right corner is =w,h=, the width and height of the screen, respectively. 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 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. 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. Here is a simple example where a handler is defined, from the pong game: #+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 : perframe See the documentation for the ~on-*~ forms for these events to get a sense of how to handle them. *** 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