Monthly Archives: June 2004

Why don’t I care about story?

I just finished watching Spirited Away for the third time. I was just wondering why I have such different taste in movies from people around me? Some of my favorite movies are: Kill Bill (and I liked volume 1 the best), Spirited Away, Ocean’s Eleven, …. All these movies have shallow or simple stories, but I could watch them over and over again. I think it’s the quality of the production: The picture and the sound, the visual contrast, the photography. These are the things that give a movie a long “replay value” for me.

This makes sense, since for a while I’ve known that my liking for a piece of music largely depends on its performance. No, Rach 3 isn’t my favorite piece; Feltsman’s Rach 3 is.

Hmp.

Animations and Transient Callbacks

Animations in games are too hard. Usually they consist of creating some convoluted state machine, where the state is what they’re supposed to be doing, and the data is for how long they’ve been doing it. Now this is a fine model, but most of the time it isn’t formalized, and thus becomes a large switch or cascade of ifs. Hard to read, hard to maintain. Something needs to be done here.

Glop solves the problem using a queue of transient callbacks, basically a callback that knows when it’s not supposed to be called anymore. There’s a level of indirection around the queue, so some object can take it, store it away, and replace it with its own queue. You’ll see why this is important in a minute.

For example, let’s say you’re writing tetris. You’re dealing with a piece that, when “k” is pressed, you want to rotate clockwise. But you want it to rotate smoothly and not “jump” like many tetris implementations do. In the traditional “obscure state machine” model, you put it in a rotating state and record when it started rotating. You rotate it by the difference in seconds between the current time and the time it started, and when the difference exceeds π/2 you put it back into the rest state.

That seems to work. But what happens if the user presses the rotate button again while it’s rotating? Then you’ve got a problem. You can either ignore it, or set the initial time to “now” so that it jumps back and starts over. But there’s not a good way to make it rotate again after it’s done, which is the behavior I’d want as a game designer (an unskilled one, but I figure at least some of my ideas are sane).

With the transient callback queue it’s easy. You enqueue a “timeslice” functor whenever the button is pressed. The timeslice functor keeps calling the closure given to it (with the amount of time passed since its starting) every frame, and then quits when it’s done. Sounds familiar, but the interesting part is that you can enqueue it. Once it’s done, the next one starts and rotates it again. It looks something like this in code:

    method rotate_cw => sub {
        my ($self) = @_;
        enqueue $self Glop::Transient::timeslice(
                               pi/2, sub { $theta += $STEP });
    }

That’s it! No complicated state machines; just a simple closure and a length of time. $_[0] inside the closure would refer to the amount of time passed since the timeslice reached the front of the queue, but we turned out not to need it here.

Now, what happens if you say “if they push down while it’s rotating, I want it to smoothly move down by one immediately.” To boot, you probably want the downs to queue independently of the rotates. It’s easy to do that, if a little more abstract.

    method BUILD => sub {
        my ($self) = @_;
        ($downq, $rotq) = $self->queue->fork;
    }

That splits the queue into two different ones (you can split it into three just the same way, using the magic of Want.pm). Now you just use the two different queues and it all works nicely.

There are more kinds of transients than just the timeslice, as you may have guessed. There’s Transient::single, which just executes it once. There’s Transient::forever which, well, isn’t really transient. There’s Transient::indef (for indefinite; I may need to come up with a better name[1]), which goes until you say last TRANSIENT (which can be nicely shortened to just last in the absence of an inner loop). There may be others, and you can certainly add your own.

And that’s Glop’s take on animation. Now I have all the necessary information to start implementing the Actor base class.

[1] There needs to be a standardization of interface at some point. I figure that can wait, however, as I’m not sure all the spiffy interfaces I’m going to do. Nobody’s using Glop yet, and if they do, it’s their funeral since it’s very early in development. By OSCON it should be in a fairly interface-stable state. After all, I have the Glop::Draw->Cirlce style; the Glop::Transient::timeslice style; the just plain drawing {...} style. There needs to be some rhyme and reason to why I use each of these, and I may toss the second one altogether, so it becomes Glop::Transient->Timeslice.

Glop Input Design

Into the plethora of modules included in Glop I add POE. I’m going to use it under the hood, and try to hide it from the user as much as possible (well, in that postmodern way of exposing it when people want it). You’ll note that I’m coupling the design with all the libraries I’m including with it. I’m not certain that I’m making the right decision in doing so, but building abstraction layers around these kind of modules is not a simple task, and sometimes wouldn’t accomplish anything. Especially for things like POE, where the module basically is its abstraction layer.

Anyway, on to the title of this post, the input design. By “design” here, for the first time, it doesn’t mean “just an idea.” It means what I’m doing, and what I’ll be coding soon. Also, in this input design we begin to silhouette the rest of the design, which is quite exciting.

Mouse Interaction

First, every screen object implements an interface that the Glop kernel knows how to use. Among other things, this will include update (called every frame), draw, and select_draw. The latter method is the one we’re interested in, and it by default just registers a picker id and calls draw.

Whenever the mouse is clicked, it runs through all the objects in the scene and calls select_draw on them. It then finds the root of the select stack and calls select on it. This method should, and if you inherit from Glop::Actor (or whatever it turns out to be), does just call select on the next item in the stack. Then, presumably, the leaf objects will override this behavior and do something specific.

If you don’t need to do something as complex as the select_draw stuff, you can put it in a mode where it just reports all mouse click positions to a callback. In fact, the select_draw mode is just the default callback to this routine.

Keyboard Interaction

Keyboard interaction is completely different. All you do is register a callback with the global keyboard handler for a specific event. Give it an option whether you want to be called every time it changes, or every frame reporting the state. The latter mode means that you don’t have to keep track of the keystate when you’re doing an acceleration-driven object. It does that for you (well, more accurately, SDL does that for you, and it just passes along the information). The keycodes are strings with a familiar syntax: "x" (the key x), "C-x" or "C_x" (control-x), "Alt" (the obvious), etc.

Example

    package Missile;
    use Glop;
    use Class::Closure;   # if you please
    sub CLASS {
        extends Glop::Actor;
        has(my $p) = v;   # v is short for v(0,0), which is short for v(0,0,0);
        has(my $v) = v;

        register Glop::Input::Keyboard
            'report',  # report every frame
            LEFT    => sub { $v += $STEP * v(-1,0) if $_[0] },
            RIGHT   => sub { $v += $STEP * v(1,0) if $_[0] },
            UP      => sub { $v += $STEP * v(0,1) if $_[0] },
            DOWN    => sub { $v += $STEP * v(0,-1) if $_[0] },
        ;
        # or for this insanely common case, there's a special semantic
        register Glop::Input::Keyboard
            qw(report keypad) => sub { $v += $STEP * $_[1] if $_[0] },
        ;

        method update => sub {  # This shouldn't be necessary if you're using ODE masses
                                # more on that later
            $p += $v;
        };
        method draw => sub {
            drawing {    # Just a nicer way of saying glPush/PopMatrix
                glColor(1,0,0);
                glTranslate(@$p);
                Glop::Draw->Circle;    # default unit radius
            };
        };
        method select => sub {
            KABOOM;
            # see the animations post later this month for how this will work
        };
    }

Sonic Rendering

I’ve had a project in mind for a while, that I may start after I’m done with all my other projects (when I’m 50 years old :-)

I use MIDI to compose all my music, and I frequently am annoyed by its limitations. In fact, the limitations of MIDI may be one of the main reasons I am a piano composer. MIDI’s framework works well for piano, but it fails in the presence of strings, woodwinds, or basically anything that can crescendo in the middle of a note, or change timbre without changing volume.

I want to create a new file format that can handle these things, and a corresponding renderer. Here are some basic things I want:

  • The ability to couple a soundfont with a piece, without including it directly in the file. That is, the music file would include a URL at which the player could find a soundfont to use. Then the player could cache this, and avoid downloading it again for another similar piece. It would also encourage sharing of soundfonts.
  • The ability to move smoothly between two timbres, for example, a violin playing a note fortepiano. You would use the marcato attack sample from the violin, and then interpolate to the piano sample.
  • The ability to glissando between any two notes, not just ones up to a whole step away.

Things like that. I have an idea about the implementation of the soundfonts as well. Each instrument would be a table of state references, each of which a sample and some attributes on that sample. Therefore, an entire instrument could be a single sample referenced from each state (unfilled states would be interpolated in), or it could fill in each state with a different sample (which would likely end up taking a few hundred megs, but has the possibility of sounding extremely realistic).

It would be possible to interpolate between any two states. A glissando would be an interpolation between two different notes; a timbre change would interpolate over the timbre states while keeping the notes the same; a crescendo would interpolate over two volume states. You could even interpolate all three at once.

This is the kind of thing that warrants a lot of experimenting before the real implementation. So, I’ll a-be experimentin’, probably when I’m procrastinating on something else. Watch for updates.

Network Family Annoyance Center

I’ve been going for the past two months to Network Spinal Care, a form of chiropractice, from Network Family Wellness Center here in Boulder. I have now decided to stop going, even though I’ve already paid for one more month (we paid a big lump sum at the beginning).

My mom and I found out about it through a “community day,” naturally a marketing and expansion technique. They wouldn’t say that it was an expansion technique. Instead, they said Dr. Dannny (the main chiropractor there, with such a sickeningly cute nomer) did it because he “cares so much about the community.” She left off the important suffix to that quote “‘s money.”

We got two free sessions from that venture, and somehow they sold us into doing it. They were going to have us pay $1000 a piece. We said that that wasn’t exactly affordable, so they whip out a second plan, without a second thought, where we pay $750 a piece. That was a dead giveaway that they could go a lot lower. We finally talked them down to $1000 for the both of us.

When I started care, somehow it wasn’t the same as the “sample” on the community day. I would lie down on the table next to three other people, and I would spend 10 minutes in the room. He’s working on four people at once, so each person gets about 2.5 minutes of his time. We got three sessions a week for three months, which means 36 sessions, which means 90 minutes. Wait—I’m paying $500 for 90 minutes of care total?. That Dr. Danny is making $330 per hour off me!

As if that weren’t bad enough, they have cult-like tendencies within their system. First, they bombard you with two “how it works” talks, exactly the same, which tell you none “how it works,” but instead “that it works.” And today I was required to go to a basic care workshop, to which I didn’t actually go, but in which I’ve heard they do the exact same thing a third time.

Also, they require that if you attend care, your entire family do. They won’t even accept you otherwise. This scares me.

Finaly, their marketing is sickeningly agressive. They constantly hand you newsletters and pamphlets (which I have been making a point of crumpling up and throwing in the trash right in front of them). While you’re in the waiting room, they don’t have magazines, but a TV that is continuously advertizing their care.

This is the kind of service that makes me too uncomfortable to attend even if it will supposedly help me (even though I don’t know how). My mom will continue to the end of her 90 days, but we’ve been practicing declining their every attempt to move us to “intermediate care” for another $1000. And boy will they try.