Silverlight Game Programming: Playing an Audio File

image In my recent Silverlight game, there are a variety of sound effects that occur from bullet fires to explosions. Most of the audio files are brief, lasting only for a second or less on average.

I had a few options — put a MediaElement directly on the UI (in XAML) and reuse each as needed, or create new MediaElements on demand. Rather than live with the artificial restrictions that would be inherent in a design which potentially limited the total number of simultaneous audio streams playing at one time, I decided on the second option — create them on demand.

Playing a supported audio file in Silverlight is easy as:

MediaElement me = new MediaElement();
me.Source = new Uri(fileName, UriKind.Relative);
me.Volume = volume;
me.AutoPlay = true;
Game.Sky.Children.Add(me);

 

Boom. Explode. The audio plays (thanks to AutoPlay–which I’ve explicitly set here though the default is true). The part that annoys me though is that once these audio files are completed, there’s no way to have them be automatically removed from the control tree. A MediaElement is a UIElement — so it’s not zero impact to leave them around. So, they build up, and build up.

My simple technique is to attach to the individual file’s CurrentStateChanged event.

public static void PlayAudio(string fileName, double volume)
{
    MediaElement me = new MediaElement();
    me.Source = new Uri(fileName, UriKind.Relative);
    me.Volume = volume;
    me.AutoPlay = true;
    me.CurrentStateChanged += new RoutedEventHandler(Audio_CurrentStateChanged);

    Game.Sky.Children.Add(me);
}

public static void Audio_CurrentStateChanged(object sender, RoutedEventArgs e)
{
    if (sender is MediaElement)
    {
        MediaElement me = sender as MediaElement;
        if (me.CurrentState == MediaElementState.Paused)
        {
            me.CurrentStateChanged -= new RoutedEventHandler(Audio_CurrentStateChanged);
            Game.Sky.Children.Remove(me);
        }
    }
}

In the CurrentStateChanged event handler, the code verifies that the Media is a Paused state (which happens when the media has finished playing) and then removes the UIElement from the control tree. (By the way, the Game.Sky object is a technique that I’ve used to expose globally the base UI element used throughout the game).

It’s cool that Silverlight can play WAV, MP3, and WMA files (my game uses all three types!)

How to Develop Games in Silverlight

There’s an interesting series starting in the latest issue of the Expression newsletter exploring the process of designing and creating a casual online game in Silverlight 2. Find more info here.

I’ll be following the series to see how I could improve my recent game attempt in Silverlight which I just released.

With all of the graphics and audio, my game’s download size is annoyingly large — if you’re on a fast Internet connection, you’ll likely not notice, but I would have liked to have done more to stage the download more efficiently (or to present a quick “splash” screen while the game continues to download in the background.

Evil Space Alien Attack (A Silverlight Game)

I just completed the final touches on a simple Silverlight game I’d been working on last night.

image

image

The game can be found here.

The game play and rules are simple:

  • Use the up and down arrows to control the angle/pitch of the small plane. (or you can control the angle using the red stick/ball on the right of the screen with a mouse).
  • Use the space bar to fire the weapon.
  • Don’t get hit by aliens or alien weapons.
  • You lose points if you don’t destroy each alien.
  • You can get extra bullets, but no extra “health”.

I’ll be posting some of the code, etc. soon.

With the exception of using Garage Band for a few sounds, I created everything using Microsoft products (Expression Design, Expression Blend, and Visual Studio 2008).

This version of the game uses Silverlight 2.0 Beta 2, as there isn’t a go-live license for the RC.

Silverlight 2, RC available

image

Via ScottGu, the Silverlight 2 Release Candidate is now available for download.

Low-lights:

  • No new go-live license for until the final version is released (the RC Silverlight binaries won’t be made available to the general public)
  • It has some breaking changes for code (details here).
  • No RTF control (one can dream … :))

 

High-lights:

  • The layout system in Silverlight has been modified to round final measure and arrange values to integers when placing elements on the screen (“pixel snapping”). The result is crisper lines, more consistent rendering look, and fewer rendering artifacts.
    Layout rounding will be *on* by default, but there is a new inherited property on UIElement called UseLayoutRounding that can be set to false if the old layout behavior is desired.
    Note   It is possible that this change will affect how your animations render, in which case you might want to set UseLayoutRounding to false.
    Note   This change does not affect transforms. If you apply a transform to an element, it may still be rendered at a sub-pixel location.

    :) What this means is that the fuzzy anti-aliased look of the old version should finally be gone — and instead you’ll get crisper text/lines/everything without resorting to pixel snapping transform hacks.

  • Combo-box
  • Password text box
  • Better looking default skins.

The big question — will it get the adoption rate needed by web companies and ISVs in order to make it a viable platform? (I wonder if it will be installed by default on a Windows 7 box?)

Flash 10 and the associated tools look really good … Blend (and Silverlight) is years behind. I love the new simple 3D features of Flash CS4 Professional.

Data Binding and Tooltips in Silverlight

Have you ever wanted to databind a tooltip in Silverlight (or WPF for that matter), and found that the DataContext isn’t available for tooltips (the datacontext is null)? It’s very annoying. Tooltips, unfortunately, aren’t connected to their parents in anyway when they’re created, so they loose the ability to connect to the proper data context that is being provided to their parent. Listboxes, textboxes, all suffer from this problem.

In order to combat that problem, I’ve come up with a simple workaround for this data context problem.

I’ve created a simple attached property which augments the standard tooltip property and intervenes before it is created so that the proper data context can be established. This code hasn’t been tested thoroughly, but it works in my limited testing. It could be extended to handle more edge cases, but if you’re creating advanced tooltips outside of XAML, there’s little reason to use this code.

public class DataBindingTooltip {
     public static DependencyProperty TooltipProperty;

     static DataBindingTooltip()
     {
         TooltipProperty = DependencyProperty.RegisterAttached
             ("Tooltip", typeof(object), 
             typeof(DataBindingTooltip), 
             new PropertyMetadata(TooltipChanged));
     }

     public static void SetTooltip(DependencyObject d, object value)
     {
         d.SetValue(DataBindingTooltip.TooltipProperty, value);
     }

     public static object GetToolTip(DependencyObject d)
     {
         return d.GetValue(DataBindingTooltip.TooltipProperty);
     }

     private static void TooltipChanged(DependencyObject sender, 
         DependencyPropertyChangedEventArgs e)
     {
         if (sender is FrameworkElement)
         {
             FrameworkElement owner = sender as FrameworkElement;
             // wait for it to be in the visual tree so that // context can be established owner.Loaded += new RoutedEventHandler(owner_Loaded);
         }

     }

     static void owner_Loaded(object sender, RoutedEventArgs e)
     {
         if (sender is FrameworkElement)
         {
             FrameworkElement owner = sender as FrameworkElement;
             // remove the event handler owner.Loaded -= new RoutedEventHandler(owner_Loaded);

             DependencyObject tooltip = 
                 owner.GetValue(DataBindingTooltip.TooltipProperty) as DependencyObject;
             if (tooltip != null)
             {
                 // assign the data context of the current owner control to the tooltip's datacontext tooltip.SetValue(FrameworkElement.DataContextProperty, 
                     owner.GetValue(FrameworkElement.DataContextProperty));
             }
             ToolTipService.SetToolTip(owner, tooltip);
         }            
     }
 }

 

Here’s how to use it:

<Grid x:Name="LayoutRoot" Background="White"> <ListBox x:Name="lb" ItemsSource="{Binding Mode=OneWay}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <local:DataBindingTooltip.Tooltip> <StackPanel Orientation="Vertical" > <TextBlock Text="Tooltip!" FontWeight="Bold" /> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Age}" /> </StackPanel> </local:DataBindingTooltip.Tooltip> <TextBlock Text="{Binding Name}" Margin="4" /> <TextBlock Text="{Binding Age}" Margin="4"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>

 

And, a small test sample:

public partial class Page : UserControl {
    public Page()
    {
        InitializeComponent();

        lb.DataContext = new List<Person>() { 
            new Person(){Name="John", Age=23},
            new Person(){Name="Hank", Age=37},
            new Person(){Name="Sally", Age=31}};
    }
}

public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
}

Which, when you put it all together results in:

image

Hopefully, you can spot the trick — as soon as the host control has been created and assigned a datacontext (in the Loaded event for the element), I set the datacontext onto the tooltip and assign it to the REAL tooltip property (via the TooltipService.Tooltip attached property).

Hope you find this useful.