The long-awaited foreground appears in part 5 of the Hooray! series. It doesn't do much, but it lays the foundation for future changes. Thus, patch 8 deserves its own episode.
1 commit f2b85ea6892ddcf4d590efdbab9046e162a910cb 2 Author: Thien-Thi Nguyen <ttn@gnuvola.org> 3 Date: 2022-01-29 00:20:32 -0500 4 5 Add simple foreground 6 7 * hooray.scm: Import ‘(sdl misc-utils) exact-truncate
’. 8 Import ‘(sdl gfx)
’ procs ‘blit-rgba
’, ‘imfi-sub-c
’. 9 (FG-EDGE-LENGTH): New constant. 10 (make-fg-rect): New proc. 11 (hooray!): New proc. 12 <top-level>: Add call to ‘hooray!
’. 13 --- 14 hooray.scm | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 15 1 file changed, 117 insertions(+), 2 deletions(-) 16 17 diff --git a/hooray.scm b/hooray.scm 18 index ef226b2..cd3da51 100644 19 --- a/hooray.scm 20 +++ b/hooray.scm 21 @@ -15,10 +15,12 @@ 22 23 ;;; Commentary: 24 25 -;; Usage: guile -s hooray.scm THEME 26 +;; Usage: guile -s hooray.scm THEME [THRONG] 27 ;; 28 ;; This opens a window, loads THEME (a stylized PNG file), 29 ;; displays some random tiles from it placed in random locations, 30 +;; displays a THRONG (integer, default 9) of "hooray!" animations 31 +;; (in parallel, repeating a number of times), 32 ;; waits a moment, then exits. 33 ;; The window has title "hooray!!!", maybe. 34 35 @@ -26,7 +28,10 @@ 36 37 (use-modules 38 ((srfi srfi-11) #:select (let-values)) 39 - ((sdl misc-utils) #:select (copy-surface)) 40 + ((sdl misc-utils) #:select (copy-surface 41 + exact-truncate)) 42 + ((sdl gfx) #:select (blit-rgba 43 + imfi-sub-c)) 44 ((sdl sdl) #:prefix SDL:)) 45 46 (SDL:init 'video) 47 @@ -133,9 +138,119 @@ (define smokin 48 (SDL:delay millisecs)) 49 (newline)))) 50 51 +(define FG-EDGE-LENGTH 32) 52 + 53 +(define (make-fg-rect) 54 + (SDL:make-rect 0 0 55 + FG-EDGE-LENGTH 56 + FG-EDGE-LENGTH)) 57 + 58 +(define hooray! 59 + (let ((throng (cond ((pair? (cddr (command-line))) 60 + (string->number (caddr (command-line)))) 61 + (else 62 + 9))) 63 + (step-count 42) 64 + (src (make-fg-rect))) 65 + 66 + (define (svec) 67 + (make-vector step-count)) 68 + 69 + (define (make-put win) 70 + ;; Avoid ‘copy-surface
’; it uses ‘blit-surface
’ which disregards alpha. 71 + ;;- (copy-surface win src) 72 + (let ((surface (SDL:create-rgb-surface 73 + '(src-alpha) 74 + FG-EDGE-LENGTH ; w 75 + FG-EDGE-LENGTH ; h 76 + 32 ; depth 77 + #x000000FF 78 + #x0000FF00 79 + #x00FF0000 80 + #xFF000000))) 81 + ;; We can use ‘src
’ for dest rect only because its xy stays 0 0. 82 + ;; Some day ‘blit-rgba
’ will accept ‘#f
’ for 2nd, 4th args to mean 83 + ;; "full surface", and we will be able to change this accordingly. 84 + (blit-rgba win src surface src) 85 + surface)) 86 + 87 + (define (fg-step put fraction) 88 + 89 + (define (dim! surface) 90 + (let ((c (exact-truncate (* 256 fraction)))) 91 + ;; Ideally we would operate only on the alpha channel 92 + ;; by using ‘(ash c 24)
’ instead of ‘c
’ here. 93 + ;; Unfortunately Guile-SDL 0.5.3 ‘imfi-sub-c
’ has a bug 94 + ;; where values ‘(ash 128 24)
’ and up signal range error. 95 + ;; When next Guile-SDL is released, we will update this. 96 + (imfi-sub-c surface surface c) 97 + surface)) 98 + 99 + (dim! put)) 100 + 101 + (define (/2 n) 102 + (ash n -1)) 103 + 104 + (define (half aspect) 105 + ;; rv 106 + (lambda (surface) 107 + (/2 (aspect surface)))) 108 + 109 + (define (tvec) 110 + (make-vector throng)) 111 + 112 + (define (random-c offset) 113 + (let ((origin (+ (/2 FG-EDGE-LENGTH) 114 + (offset (* TILE-EDGE-LENGTH 2) 115 + (/2 FG-EDGE-LENGTH))))) 116 + ;; rv 117 + (lambda () 118 + (+ origin (random 29) -14)))) 119 + 120 + (define (place-fg! fg w/2 h/2) 121 + ;; rv 122 + (lambda (dst x y) 123 + (SDL:rect:set-x! dst (- x w/2)) 124 + (SDL:rect:set-y! dst (exact-truncate (- y h/2))) 125 + (SDL:blit-surface fg #f #f dst))) 126 + 127 + (let ((fg (svec)) 128 + (w/2 (svec)) 129 + (h/2 (svec)) 130 + (x (tvec)) ; location 131 + (y (tvec)) 132 + (dst (tvec))) 133 + 134 + (array-index-map! fg (lambda (i) 135 + (fg-step (make-put (surf 'win)) 136 + (/ i step-count)))) 137 + (array-map! w/2 (half SDL:surface:w) fg) 138 + (array-map! h/2 (half SDL:surface:h) fg) 139 + (array-map! dst make-fg-rect) 140 + 141 + ;; hooray! 142 + (lambda () 143 + (array-map! x (random-c +)) 144 + (array-map! y (random-c -)) 145 + (array-for-each (lambda (rect x y) 146 + (SDL:rect:set-x! rect x) 147 + (SDL:rect:set-y! rect y)) 148 + dst x y) 149 + (array-for-each 150 + (lambda (fg w/2 h/2) 151 + (refresh-scenery!) 152 + (array-for-each (place-fg! fg w/2 h/2) 153 + dst x y) 154 + (SDL:flip) 155 + (SDL:delay 42)) 156 + fg w/2 h/2))))) 157 + 158 (set! *random-state* (seed->random-state (let ((now (gettimeofday))) 159 (* (car now) (cdr now))))) 160 (smokin 9) 161 +(do ((i 0 (1+ i))) 162 + ((= 5 i)) 163 + (hooray!)) 164 165 (SDL:delay 420) 166 (exit (SDL:quit))
First off, patch 8 introduces another (optional) command-line argument: the ‘THRONG
’ (line 26, lines 59-62).
The behavior description (lines 30-31) tells us the nature of this arg and points out the direction of development: towards parallelism.
We'll have more to say on the matter in a later Trip Log.
Next, we notice a familiar pattern, the addition of constant ‘FG-EDGE-LENGTH
’ (line 51) and the convenience procedure ‘make-fg-rect
’ (an abstraction) that uses it (lines 53-56).
Remember how we
noticed
that the ‘win
’ tile was special?
Well, that's now codified in ‘FG-EDGE-LENGTH
’ and ‘make-fg-rect
’.
Now for the big ‘hooray!
’ procedure.
It has many internal procedures (lines 66-125), which we will describe next.
Before doing that, a small explanation of the overall strategy that ‘hooray!
’ takes is in order.
There are two axes to think about for this animation.
The first is the parallelism — the ‘throng
’ value; the second is the step count (line 63) — how fine-grained the animation is to be.
The strategy ‘hooray!
’ takes is to present ‘throng
’ fg images (some people call them “sprites”) in parallel, a ‘step-count
’ number of times.
For each step, something varies in the presentation — that's how the illusion of motion is created from still frames.
To organize the data on these axes, we use regular Scheme vectors.
That's what ‘svec
’ (lines 66-67) and ‘tvec
’ (lines 109-110) provide.
Thus, we use ‘svec
’ for things that might change every step, such as ‘fg
’, ‘w/2
’, and ‘h/2
’ (lines 127-129), and ‘tvec
’ for thronging things, such as location (lines 130-132).
Speaking of location, there is a difference between the coordinate system we use for the scenery and the one for the foreground. Because scenery dimensions and placement are relatively static, it's enough to refer to their location by the tile images' regular (upper-left-hand corner) coordinates. Not so for the foreground! Because we anticipate that the foreground image will change (spoiler: that doesn't happen in patch 8 — sorry), it's better to refer to the foreground by its “center” coordinates. Here's a picture that illustrates the difference:
The empty circle marks the upper-left-hand corner coordinate of ‘win
’, while the filled circle marks the center coordinate.
This helps explain the existence of ‘w/2
’ and ‘h/2
’ and the hair surrounding the computation of ‘random-c
’ (lines 112-118) local variable ‘origin
’ (line 113).
Note that ‘place-fg!
’ (lines 120-125) must eventually translate the center coordinate when it sets up the ‘dst
’ rect (lines 123-124), since SDL underestands only upper-left-hand corner coordinates.
As for what comprises the set of foreground images, hopefully the comments in ‘make-put
’ (lines 69-85) and ‘fg-step
’ (lines 87-99) are self-explanatory.
If not, feel free to
let me know
(hint for the Italiano-phobes: “URL non è chiaro per niente!”).
The main thing to note there is that each step gets its own image, and that the steps are parameterized by their ‘fraction
’ (line 87).
So, what is the result of this parameterization? Here's what I see when I run the command “guile -s hooray.scm Hellsfire.png”:
It appears the only change is the transparency of the throng. They aren't even moving! Well, that, dear reader, is for a future Trip Log. For now, we'll just check off the aspects of our emulation that we've achieved thus far:
* PipeWalker fg behavior (to reproduce if possible) - [X] starts near top of center tile - [ ] mostly upward velocity - [ ] rotates clockwise slightly - [ ] starts smaller than 32x32 - [ ] grows quickly - [ ] non synchronized (all aspects) - [x] fades to transparent
Actually, “fades to transparent” is debatable (hence the small “x”). There is indeed fading, but the way we did it is not ideal. In other words, it looks okay for Hellsfire, but not so nice for other themes (such as Network) where the fg image is lighter-colored. Perhaps with the next release of Guile-SDL, we can fix things and change that small “x” to a big “X” in good conscience.
See you next time!
Copyright (C) 2022 Thien-Thi Nguyen