Rick Barraza

Silverlight UX Development

20090324 Tuesday March 24, 2009

Sketching in C#: Legos versus Clay



The reason Flash 5 was so disruptive when it first appeared a decade ago is that for the very first time, a popular piece of software presented us with a lump of clay and said, "Here, be creative and smart at the same time." Now being expressive in clay is easy. You can produce a high level of detail and have many creative options available to you in terms of how and what you want to produce. However, if your model starts getting too large or robust, your model can start falling over if you don't have a strong infrastructure in place. Microsoft, on the other hand, comes from a heritage of "Battleship Gray" development; very large, stable and enterprise strength with reusable components all the way down. Think of this as Legos. The Microsoft development community collectively asks for new functionality, Microsoft then asks what use case they are trying to solve, and they then go out and build a component to fill that need. Very much like a Lego factory. And as with Legos, the creativity is usually manifest on how you put the pieces together and maybe some aesthetic enhancements on the surface. However, being creative with Legos and being creative with clay are two very different things.

I have met and admired the work of artists in both mediums and I enjoy playing with the different modalities as well. However, there is a messy quality to clay. An ability to have happy accidents, what I like to call reckless creativity, which is harder to duplicate when working in a Lego world. So one of the first things I did when coming into the Microsoft UX world was to figure out how to have fun with Silverlight. How to code in such a way that I'm not thinking of Design Patterns or OOP or reusability, but how to capture that same fast, sketch like quality I used to enjoy when coding dynamic Flash interactions.

So last Tuesday, on the Pre-Mix conferences, a portion of my talk was about "sketching in C#". It was a pleasant surprise that most of the MIX09 conference also shared this theme of sketching, though focusing on other areas of our discipline. However, the principle was the same: fast, fun sketches that focus on ideas and exploration first, without worrying too much at this first stage on reusability and air tight architecture.

Since several came up to me afterward and asked me to share the files online, I decided to go ahead and also document the mental process I demonstrated while onstage.

General Sketch Idea: Snowy Particles

Click here to download all project files

The starter project is a Silverlight 2.0 project with a default Page.xaml and code behind and a User Control named Snow.xaml. Snow.xaml only contains an Ellipse with a white Gradient fill. Both Page.xaml and Snow.xaml have been converted from Grid to Canvas, since I'm used to interacting with procedural animation sketches by positioning them explicitly rather than with Margins or Padding. Also note that Page.xaml has an inital onLoaded() function that will be called when the Silverlight movie first starts.

Setting Up the Visual Element

So the first thing I do after defining the look of the element in XAML, and this is true for virtually every sketch I start, is go to the code behind in the User Control I'll be attaching dynamically, in this case Snow.xaml, and prep it for more intuitive positioning by defining a public x and y property like so:

public double x { 
            get { return (double)this.GetValue(Canvas.LeftProperty); } 
            set { this.SetValue(Canvas.LeftProperty, value); } }

        public double y { 
            get { return (double)this.GetValue(Canvas.TopProperty); } 
            set { this.SetValue(Canvas.TopProperty, value); } }

        public double vx = 1.0;
        public double vy = 1.0;



The vx and vy public variables are used for storing a velocity for the x and y directions as well. I'm not sure if I'll be using this, but I usually add them both at the same time. Worst case scenario, I use them temporarily to test the animation loop as we'll see later.

Adding the Elements to the Page

Now that I have my visual element Snow.xaml and it is wired with x and y positioning, I add a function to Page.xaml.cs to add multiple instances of this element to the visual tree. Note that I add a call to the setupSnow() function in the onLoaded handler. I also tend to create a Random variable to help with placing and when I want some visual variety.

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            setupSnow();
        }

        double totalSnow = 400;
        Random r = new Random(DateTime.Now.Millisecond);
        List snows = new List();

        private void setupSnow()
        {
            for (int i = 0; i < totalSnow; i++)
            {
                Snow s = new Snow();
                s.x = r.NextDouble() * 500;
                s.y = r.NextDouble() * 300;
                s.Opacity = .1 + r.NextDouble() * .5;
                s.vx = -2 + r.NextDouble() * 4;
                s.vy = 1 + r.NextDouble() * 2;
                snows.Add(s);
                LayoutRoot.Children.Add(s);
            }
        }



I'm assigning random numbers for the x and y velocities. VX will range from -2 to +2, while vy will range from 1 to 3. This means that the particles will only move downward.

Adding a Timer and Testing

Now that I am sure that my visual element is being displayed on the screen and it all works so far, I add a Timer() event and add some simple test code to make sure its working. This is the most similar to the onEnterFrame() mindset I grew up with in Flash. In order to use a DispatcherTimer in C#, though, you need to first assigned a reference to System.Windows.Threading. The modifications in Page.xaml.cs to define a timer tick() event looks like this:



[...]
using System.Windows.Threading;
[...]

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            setupSnow();
            setupTimer();
        }

        DispatcherTimer dt;

        private void setupTimer()
        {
            dt = new DispatcherTimer();
            dt.Interval = TimeSpan.FromMilliseconds(30);
            dt.Tick += new EventHandler(dt_Tick);
            dt.Start();
        }

        void dt_Tick(object sender, EventArgs e)
        {
        }


At every milestone, I press F5 to test the project. To make sure everything is working, we can add some temporary lines into the tick() event. This helps to make sure that nothing is broken so far:

        void dt_Tick(object sender, EventArgs e)
        {
            foreach (Snow s in snows)
            {
                s.x += s.vx;
                s.y += s.vy;
            }
        }



Tweaking the Original Visual

Now that I know I have created my visual, can place instances on stage, and have an animation loop I can exploit to control them, I want to start iterating through my project and hacking things creatively. The first thing I want to do is beef up my Snow.xaml particle to give it rotation. Here is the render transform added under the Canvas tag:

    <Canvas.RenderTransform>
        <RotateTransform x:Name="rot" Angle="0"/>
    </Canvas.RenderTransform>

And here is what I add to the Snow.xaml.cs code behind to take advantage of this:

        public double baseSize = 10.0;
        public double scale = 1.0;

        public double angularVelocity = 1.0;
        public double angle = 0.0;

        public void setScale(double _scale)
        {
            if (_scale > (3)) _scale = 3;
            if (_scale < 0.1) _scale = 0.1;
            scale = _scale;
            bkg.Width = baseSize * scale;
            bkg.Height = baseSize * scale;
            gs.Offset = .5 + ((scale / 2) * .4);
            rot.Angle = ((scale/2.0) * 90) - 180;
        }   


And finally, I add a line of code in the Page.xaml.cs setupSnow() function to take advantage of this like so:

        private void setupSnow()
        {
            for (int i = 0; i < totalSnow; i++)
            {
                Snow s = new Snow();
                s.x = r.NextDouble() * 500;
                s.y = r.NextDouble() * 300;
                s.setScale(r.NextDouble() * 2.0);
		[...]                



Back to the Timer

Now that I've added more detail to my visual element, I want to rewrite the Timer_tick() function to get closer to what I have in mind. Since we added some code to vary the size of the Snow particles, it makes sense to have each particle's size affect their speed to create a faux parallax trick. If bigger particles move faster than smaller particles, an implicit perspective is created for the viewer. Kinda cool, huh?

        void dt_Tick(object sender, EventArgs e)
        {
            foreach (Snow s in snows)
            {
                s.x += s.vx + 5 * s.scale;
                s.y += s.vy;

                if (s.x > 550) s.x = -50;
                if (s.x < -50) s.x = 550;
                if (s.y > 350) s.y = -50;
            }
        }



Tweaking the Animation Timer further

While at this point you could stop, it would still be underwhelming. The animation is very linear and I'm sure we can spice it up more. Let's see what happens if we shift the Snow.xaml Ellipse to Canvas.Left="-50" off center and then rotate it during the Timer using a Brownian motion style algorithm. The adjusted Snow.xaml looks like this:


        <Ellipse x:Name="bkg" Width="10" Height="10" Canvas.Left="-50" Canvas.Top="-5">



If you recall, we added an angle and angularVelocity to the Snow.xaml.cs code behind that we're not currently using. Let's add a Brownian motion algorithm to our animation loop in Page.xaml.cs to take advantage of this rotation now:

            foreach (Snow s in snows)
            {
                s.x += s.vx + 5 * s.scale;
                s.y += s.vy;
                s.angularVelocity *= .97;
                s.angularVelocity += (r.NextDouble() - r.NextDouble());
                s.rot.Angle += s.angularVelocity;
		[...]



If we add a random number from -1 to +1 to each Particle's angular velocity, then add this value to the angle value while applying friction, the motion will be much more organic than simple stuttering but will wax and wane with an undefined but graceful motion. Since our rotation will rotate the offset Ellipse around its origin point, the visual effect will be a tremendous increase in apparent visual complexity with only a little tweaking in the actual xaml and code.

The last 5%

A lot of interaction sketches start to gain that magic in the last 5% of the project. While everything we've done so far provides a compelling visual, it would be nice if we could make it interactive so the users can play with it as well. The first thing I do when having my animations interact with the cursor is create a Point object that stores the current position of the mouse like so:

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            setupSnow();
            setupTimer();
            this.MouseMove += new MouseEventHandler(Page_MouseMove);
        }

        Point mouse = new Point(0, 0);

        void Page_MouseMove(object sender, MouseEventArgs e)
        {
            mouse = e.GetPosition(LayoutRoot);
        }



Since I only really care about the vertical movement for this sketch, I can extract it within the timer() function have it affect the x position like this:

        void dt_Tick(object sender, EventArgs e)
        {
            double xmovement = (mouse.X / 500.0)-.5;
            foreach (Snow s in snows)
            {
                s.x += s.vx + 5 * s.scale*xmovement;
		[...]

That simple algorithm for xmovement converts the x position of the cursor to a range of -.5 to +.5 so that as you move your mouse more to the left, the wind blows one way, and more to the right the wind blows the other way.

Now we're starting to have something fun...

Variations on a Theme

It wouldn't be a sketch if you didn't do variations! Here is a variation that shows how tweaking some of the styling, positioning and animation logic can give us a different mood. Creating multiple versions will let you pick and choose the effect you like most.



Click here to download all project files

While this tutorial went a little more in depth than usual, many of the tutorials I will be adding will follow a similar pattern so it's good to present it now. Create the Visual Elements, add them to the stage, create a Timer, add animation logic and tweak to fit. My next code tutorial will cover a similar approach for developing interactions for the Microsoft Surface, so come back soon to check it out!

\rb

Posted by rickbarraza | Mar 24 2009, 12:00:00 AM PST
XML