April, 2012

  • DaveDev

    Playing Music and Sound Effects in a Windows 8 Metro Style App using HTML and JavaScript

    • 0 Comments

    Overview

    I have recently been coding a Windows 8 Metro Style App using the new Windows 8 Release Preview bits and Visual Studio Express 2012 RC.  The app is going to be a retro shooter that takes advantage of HTML5 Canvas for the main game engine and then several Windows 8 Metro Style App Features. 

    What game would rock without Music and Sound Effects!  So how do we add that functionality to a Metro Style App?  For my game I decided to take two routes. 

    This is how I broke everything down:

    • HTML5 Audio is being used for the background music and is in a wav file format.  I have two royalty free music tracks that play one during my game’s menu screen and one during actual gameplay.
    • All Sound Effects are played using the free SoundJS Library. This JavaScript Library works straight off gitHub and no additional work was needed to use it inside our Windows Metro Style App!
    • The Lasers are three separate sound effects and each time a ship is destroyed a random one will play.  They are royalty free sounds I pulled from some of the XNA samples via AppHub’s Game Development Library.
    • The announcer voices were custom recorded by me and saved as mp3 files via the free Audacity Sound Editing Tool.  I got the retro voice sound (anyone remember AdLib and SoundBlaster back in the day?) by tweaking the stream.


    Declaration and Initialization

    I store all of my sound and music files in a /sounds folder off the root of the project and include the sound.js file in my normal /js folder.


    Sounds and JavaScript - sweet!

    Inside of my default.html file I then make a reference to the SoundJS library.

    <link href="/css/default.css" rel="stylesheet">
    <link href="/css/space.css" rel="stylesheet"/>
    <script src="/js/default.js"></script>
    <script src="/js/sound.js"></script>

    Our App is now ready to play music and sound streams.


    Music

    Inside of my default.js file I set up a couple of local variables to handle the streams and the sound effects we will pass to SoundJS.  I also set up a variable to detect if I have any music already playing.

    //Menu Music
    var musicPlaying = false;
    var musicMenu = new Audio("/sounds/hydrogen.mp3");
    var musicGame = new Audio("/sounds/crazy_comets.wav");
    musicMenu.loop = true;
    musicGame.loop = true;
     
    //soundeffects
    var lasers = new Array();
    lasers[0] = "laser1";
    lasers[1] = "laser2";
    lasers[2] = "laser3";

    During my initialize function I set up all of my game parameters and load the sound effects I will use into SoundJS.  SoundJS lets you optionally load multiple instances of the same stream but I’m choosing just to use the one right now.

    //Init Sounds
          SoundJS.addBatch([
           { name: "redalert", src: "../sounds/redalert.mp3", instances: 1 },
           { name: "newlevel", src: "../sounds/newlevel.mp3", instances: 1 },
           { name: "pulse", src: "../sounds/pulse.mp3", instances: 1 },
           { name: "laser1", src: "../sounds/laser1.mp3", instances: 1 },
           { name: "laser2", src: "../sounds/laser2.mp3", instances: 1 },
           { name: "laser3", src: "../sounds/laser3.mp3", instances: 1 }]);

    I then load up the UI elements for my Game Menu and begin playing my Menu music.  This is done by simply calling the pause() and play() methods of both the musicGame and musicMenu HTML5 Audio tag variables we declared previously.

    Here is the code that initializes the Menu UI and begins playing the Menu Music:

    //Set Up Menu Screen UI Elements
      function showMenu(event) {
          menuEnabled = true;
     
          txtPlayerName.style.visibility = "hidden";
          txtScore.style.visibility = "hidden";
          imgPlayer.style.visibility = "hidden";
          imgMenu.style.visibility = "visible";
          btnStart.style.visibility = "visible";
          txtVersion.innerHTML = GAME_VERSION;
          txtVersion.style.visibility = "visible";
          txtLevel.style.visibility = "hidden";
          
     
          var menuX, btnX, btnY;
          menuX = (SCREEN_WIDTH - imgMenu.width) / 2;
          btnX = (SCREEN_WIDTH - btnStart.clientWidth) / 2;
          btnY = (SCREEN_HEIGHT - btnStart.clientHeight) / 2;
     
          imgMenu.style.posLeft = menuX;
          btnStart.style.posLeft = btnX;
          btnStart.style.posTop = btnY;
     
          musicGame.pause();
          musicMenu.play();
       
      }

    Here is the code that initializes the Game UI and begins playing the Game Music:

    /Set up Game Screen UI Elements
        function startGame(event) {
            txtPlayerName.style.visibility="visible";
            txtScore.style.visibility="visible";
            imgPlayer.style.visibility = "visible";
            imgMenu.style.visibility = "hidden";
            btnStart.style.visibility = "hidden";
            txtVersion.style.visibility = "hidden";
            txtLevel.style.visibility = "visible";
     
            var lvlX = (SCREEN_WIDTH - txtLevel.clientWidth) / 2;
            txtLevel.style.posLeft = lvlX;
     
            musicMenu.pause();
            musicGame.play();
     
            menuEnabled = false;
            
        }

     

    Sound Effects

    Now that we have the music all set up let’s move onto the Sound Effects. 

    New Level and Gravity Pulse

    The first sound bite is when our user achieves a new level (currently set to every 2,000 points).  The second scenario is if they achieve a gravity pulse (previously covered here).  Both of these scenarios are handled in our updateScore function which gets called during our main game loop (more on game loops to come).

    //update player score
        function updateScore(points) {
     
            score += points;
            scoreGravity += points;
            txtScore.innerHTML = "  Score: " + score;
     
            if (scoreGravity === GRAVITY_WAVE_PTS_REQ) {
                accelerometer.addEventListener("shaken", onShakenAccel);
                txtScore.innerHTML = " > SHAKE THAT SCREEN <";
                scoreGravity = 0;
                SoundJS.play("pulse", SoundJS.INTERRUPT_ANY);
            }
     
            //new level
            lvlNextPts = (lvlCurrent + 1) * LEVEL_PTS_REQ;
            if (score >= lvlNextPts) {
                lvlCurrent++;
                txtLevel.innerHTML = "Level: " + lvlCurrent;
                lvlDifficulty = LEVEL_SPEED_INCREASE * lvlCurrent;
     
                SoundJS.play("newlevel",SoundJS.INTERUPT_ANY);
     
            }
     
     
        }

     

    Lasers Beams!

    All of the logic for the laser effects happens when I handle destroyed ships that have been correctly “tapped on” (more on handling Touch events to come).  I run through the laser sound array we previously created and pick a random sound.  This gives us the ability to have three different laser sounds when we are blowing up ships on the screen!

    function destroyShip(ship) {
     
        var r = randomAccel(0, 2);
        var laserSound = lasers[r];
        SoundJS.play(laserSound, SoundJS.INTERRUPT_ANY);
       
        //TODO: Animation Explosion and better sound
        var explosion = new Image();
        explosion.onload = function () {
            ctx.drawImage(explosion, ship.x, ship.y);
        }
        explosion.src = "/images/explosion.png";
        ship.img = explosion;
        ship.destroyed = true;
     
        updateScore(POINTS_SHIPHIT);
     
        return ship;
    }

    For those curious here is the randomizer function I used to pick a random number as well:

    //Random acceleration speed
        function randomAccel(a, b) {
            return (Math.floor(Math.random() * (1 + b - a))) + a;
        }

     

    One of the things I noticed when having many sound effects on screen was that they sometimes would cut off each other.  Remember that users can use all 10 fingers on a touch device so we could have 10 explosions at once!  This is where SoundJS handles things very well.  You may have noticed the INTERRUPT_ANY param being passed in.  This is where you can tell SoundJS how to handle currently playing sounds of the same exact stream.  You can also created multiple instances of the same sound effect.  If you are still  having issues with choppy sounds the SoundJS Documentation has some suggestions on using setTimeout to buffer your sounds.

    I wrote a little function that implements this which I’m not currently using but may need at some point in the future.  I’ve included it here in case you run into any sound clipping issues and want to give it a try.

    //Play Sound Effect
     function playSound(sound) {
         setTimeout(function () {
             SoundJS.play(sound, SoundJS.INTERUPT_NONE, 1, false);
         }, 100);
     }

     

    Conclusion

    I hope this post has given you an idea of how easy it is to include Music and Sound Effects in your Metro Style Apps using the built in HTML5 Audio tags and existing JavaScript Libraries.  If you are currently working on a Windows 8 app and want to get into the Windows Store I would love to hear about it

    You may also want to check out my previous Windows 8 Metro Style Development Tips:

  • DaveDev

    Illegal characters in path when deploying a Metro Style App

    • 1 Comments

    If you are getting an error similar to the one below when deploying your Metro Style App in Visual Studio 11 Beta it’s not you.

    Error    1    Error : DEP0600 : The following unexpected error occurred during deployment: 
    Illegal characters in path.
       at System.IO.Path.CheckInvalidPathChars(String path, Boolean checkAdditional)
       at System.IO.Path.Combine(String path1, String path2)
       at Microsoft.VisualStudio.ImmersiveProjectServices.Shared.AppxLayoutManager.CheckPackageLayoutState(DeployPackageName deployPackageName, String location)
       at Microsoft.VisualStudio.ImmersiveProjectServices.Shared.LocalDeployJob.GetLayoutState(DeployPackageName deployName, Boolean hasFrameworkDependencies)
       at Microsoft.VisualStudio.ImmersiveProjectServices.Shared.RegisterAppxLayout.Start(Boolean forceNewLayout, Boolean forceRegistration, NetworkLoopbackState desiredNetworkLoopbackState, Boolean refreshLayoutOnly, String& packageMoniker, String& firstUserAppID, Exception& deployException)    MetroInclinometerSteeringWheel

     

    This is a known issue when moving source files around to different folders without uninstalling the Metro Style App.  The good news is the fix is simple!

    Uninstall the application via it’s Start Screen Live Tile and then re-deploy it in Visual Studio 11.

    I have this happen to me frequently due to having different machines I present on and moving source code around between them.  I figured I would share the solution in case you come across the same issue.

  • DaveDev

    Using KnockoutJS in Windows 8 Metro Style Apps

    • 3 Comments

    What is it?

    If you have been messing around with JavaScript lately and come from a .Net Background you might have already heard of KnockoutJS.

    Knockout

    If you haven’t, KnockoutJS is a lightweight, free, JavaScript Library from Steve Sanderson that brings the MVVM pattern to the web world.  It’s also filled with #awesome sauce.

     

    Cats and Dogs Living Together – Mass Hysteria!

    It wasn’t too long ago that Windows app development and Web app development were two entirely different views of the world.  Thanks to the magic of WinRT language projection with Metro Style App development we no longer have to give up all the great libraries we are used from the web when  moving onto the desktop.  Most JavaScript libraries will run by just including it in your Metro Style App project.  Well at least local copies of those JavaScript Libraries.  If you want to call out to external libraries however you will need to start looking into the security model of how Windows Web Apps run (WWA). 

    The exception to this is jQuery if you are pulling from the public CDN’s it has already been whitelisted for you.  I wont dive into details here but you can check out the great Build Session from Chris Tavares to learn about the security model for third party JavaScript libraries.

     

    Ok Dave where's the code?

    Hang tight!  If you just want the Windows 8 Metro Style App soure code grab it now.

    Source Code

     

    Setting up KnockoutJS

    The first thing you will need to do is grab the latest version of KnockoutJS off GitHub.  I’ve included and tested with both the stable 2.0.0 release as well the 2.1.0rc update.

    Create a new project in Visual Studio 11 Express as usual and then add the Knockout.js file to your JS folder.

    KnockoutJS.js

    As with all JavaScript files if you want Visual Studio to give you Intellisense you will need to add the following to the current JavaScript source file you are working on:

    /// <reference path="knockout-2.0.0.js" />

    Note that Visual Studio will do this automatically for you if you simply drag the .js file over to your code window.

    Next, you will need to add a reference to the KnockoutJS file in the html source. While we are at it let’s go ahead and grab the latest jQuery Library as well.  Some of the KnockouJS Learning samples assume jQuery is included.  Your references should look like below:

    <script src="/js/knockout-2.0.0.js"></script>
    <script src="/js/jquery-1.7.2.min.js"></script>


    Now we should have working jQuery and Knockout JavaScript functionality but don’t forget that both of these libraries assume you have a DOM.  On the web we would normally start executing code after the onLoad event.  Fortunately for us this is super easy to do in Metro Style Apps by adding an event handler for DOMContentLoaded.  This should be the last line in your main function like so:

    app.start();
     
    //If Document fully loaded than begin processing
    document.addEventListener("DOMContentLoaded", initialize, false);
     
    })();

    This will ensure your DOM is loaded and now accessible inside of your app.

    The last step now is to ensure that all of our KnockoutJS bindings occur once our initialize function has been called.  Here is an example of what I mean:

    //Main Execution
    function initialize() {
     
        // Activates knockout.js
        ko.applyBindings(new AppViewModel());
     
    }

     

    Converting Learn.KnockoutJS samples to a Metro Style Apps

    There are some really great tutorials on Learn.KnockoutJS.com to help get you started.  I decided to go through each of those and bring them over into a Metro Style App.

    Learn KnockoutJS

    All five tutorials came over with minor tweaks except for Single Page applications.   I’ve named the tutorials using the following convention:

    • Introduction – default.html/default.js
    • Working with Lists and Collections – seats.html/seats.js
    • Single page applications – spa.html/spa.js
      • Didn’t work.  I’ve included the source so if anyone wants to hack on it some more and see what’s up please let me know. 
    • Creating custom bindings – custom.html/custom.js
    • Loading and saving data – data.html/data.js

    To change which sample you want to run just edit the startup page inside the project properties.

    Capture

     

    Introduction Example

    No tweaks needed this came over perfectly.  Here is what my html looks like:

    <body>
     
    <p>First name: <strong data-bind="text: firstName"></strong></p>
    <p>Last name: <strong data-bind="text: lastName"></strong></p>
     
    </body>

     

    Here is the JavaScript:

    (function () {
        "use strict";
     
        var app = WinJS.Application;
     
        app.onactivated = function (eventObject) {
            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
                if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
                    // TODO: This application has been newly launched. Initialize 
                    // your application here.
                } else {
                    // TODO: This application has been reactivated from suspension. 
                    // Restore application state here.
                }
                WinJS.UI.processAll();
            }
        };
     
        function AppViewModel() {
            this.firstName = "David";
            this.lastName = "Isbitski";
        }
     
        
     
        //Main Execution
        function initialize() {
     
            // Activates knockout.js
            ko.applyBindings(new AppViewModel());
     
        }
     
     
        app.oncheckpoint = function (eventObject) {
            // TODO: This application is about to be suspended. Save any state
            // that needs to persist across suspensions here. You might use the 
            // WinJS.Application.sessionState object, which is automatically
            // saved and restored across suspension. If you need to complete an
            // asynchronous operation before your application is suspended, call
            // eventObject.setPromise(). 
        };
     
        app.start();
     
        //If Document fully loaded than begin processing
        document.addEventListener("DOMContentLoaded", initialize, false);
     
    })();

     

    Working with Lists and Collections Example

    Worked right out of the box.  Here is the HTML:

    <body>
     
    <h2>Your seat reservations</h2>
     
    <table>
        <thead><tr>
            <th>Passenger name</th><th>Meal</th><th>Surcharge</th><th></th>
        </tr></thead>
        <tbody data-bind="foreach: seats">
            <tr>
                <td data-bind="text: name"></td>
                <td data-bind="text: meal().mealName"></td>
                <td data-bind="text: meal().price"></td>
            </tr>    
        </tbody>
    </table>
    </body>

     

    Here is the JavaScript:

     
    (function () {
        "use strict";
     
        var app = WinJS.Application;
     
        app.onactivated = function (eventObject) {
            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
                if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
                    // TODO: This application has been newly launched. Initialize 
                    // your application here.
                } else {
                    // TODO: This application has been reactivated from suspension. 
                    // Restore application state here.
                }
                WinJS.UI.processAll();
            }
        };
     
        // Class to represent a row in the seat reservations grid
        function SeatReservation(name, initialMeal) {
            var self = this;
            self.name = name;
            self.meal = ko.observable(initialMeal);
        }
     
        // Overall viewmodel for this screen, along with initial state
        function ReservationsViewModel() {
            var self = this;
     
            // Non-editable catalog data - would come from the server
            self.availableMeals = [
            { mealName: "Standard (sandwich)", price: 0 },
            { mealName: "Premium (lobster)", price: 34.95 },
            { mealName: "Ultimate (whole zebra)", price: 290 }
            ];
     
            // Editable data
            self.seats = ko.observableArray([
            new SeatReservation("Steve", self.availableMeals[0]),
            new SeatReservation("Bert", self.availableMeals[0])
            ]);
        }
     
       
     
        //Main Execution
        function initialize() {
     
            // Activates knockout.js
             ko.applyBindings(new ReservationsViewModel());
     
        }
     
     
        app.oncheckpoint = function (eventObject) {
            // TODO: This application is about to be suspended. Save any state
            // that needs to persist across suspensions here. You might use the 
            // WinJS.Application.sessionState object, which is automatically
            // saved and restored across suspension. If you need to complete an
            // asynchronous operation before your application is suspended, call
            // eventObject.setPromise(). 
        };
     
        app.start();
     
        //If Document fully loaded than begin processing
        document.addEventListener("DOMContentLoaded", initialize, false);
     
    })();

     

    Creating Custom Bindings Example

    I needed to do two things to get this example to work.  The first one was to include jQuery it assumes it would be present.  The second was to replace the alert() function call (this doesn’t exist in WinRT) with a native WinRT MessageDialog.  Here is what the HTML looks like:

    <body>
     
    <h3 data-bind="text: question"></h3>
    <p>Please distribute <b data-bind="text: pointsBudget"></b> points between the following options.</p>
     
    <table>
        <thead><tr><th>Option</th><th>Importance</th></tr></thead>
        <tbody data-bind="foreach: answers">
            <tr>
                <td data-bind="text: answerText"></td>
                <td><select data-bind="options: [1,2,3,4,5], value: points"></select></td>
            </tr>    
        </tbody>
    </table>
     
    <h3 data-bind="fadeVisible: pointsUsed() > pointsBudget">You've used too many points! Please remove some.</h3>
    <p>You've got <b data-bind="text: pointsBudget - pointsUsed()"></b> points left to use.</p>
    <button data-bind="enable: pointsUsed() <= pointsBudget, click: save">Finished</button>
    </body>

     

    Here is the JavaScript:

     
    (function () {
        "use strict";
     
        var app = WinJS.Application;
     
        app.onactivated = function (eventObject) {
            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
                if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
                    // TODO: This application has been newly launched. Initialize 
                    // your application here.
                } else {
                    // TODO: This application has been reactivated from suspension. 
                    // Restore application state here.
                }
                WinJS.UI.processAll();
            }
        };
     
        
     
        // ----------------------------------------------------------------------------
        // Page viewmodel
     
        function Answer(text) { this.answerText = text; this.points = ko.observable(1); }
     
        function SurveyViewModel(question, pointsBudget, answers) {
            this.question = question;
            this.pointsBudget = pointsBudget;
            this.answers = $.map(answers, function (text) { return new Answer(text) });
     
            //Dave Isbitski - 4/24/12 - Changed alert to native WinRT dialog
            this.save = function () {
                var dlg = new Windows.UI.Popups.MessageDialog("To do");
                 dlg.showAsync().done();
     
            };
     
            this.pointsUsed = ko.computed(function () {
                var total = 0;
                for (var i = 0; i < this.answers.length; i++)
                    total += this.answers[i].points();
                return total;
            }, this);
        }
     
        //Main Execution
        function initialize() {
     
            ko.bindingHandlers.fadeVisible = {
                init: function (element, valueAccessor) {
                    // Start visible/invisible according to initial value
                    var shouldDisplay = valueAccessor();
                    $(element).toggle(shouldDisplay);
                },
                update: function (element, valueAccessor) {
                    // On update, fade in/out
                    var shouldDisplay = valueAccessor();
                    shouldDisplay ? $(element).fadeIn() : $(element).fadeOut();
                }
            };
     
            // Activates knockout.js
            ko.applyBindings(new SurveyViewModel("Which factors affect your technology choices?", 10, [
       "Functionality, compatibility, pricing - all that boring stuff",
       "How often it is mentioned on Hacker News",
       "Number of gradients/dropshadows on project homepage",
       "Totally believable testimonials on project homepage"
            ]));
     
        }
     
     
        app.oncheckpoint = function (eventObject) {
            // TODO: This application is about to be suspended. Save any state
            // that needs to persist across suspensions here. You might use the 
            // WinJS.Application.sessionState object, which is automatically
            // saved and restored across suspension. If you need to complete an
            // asynchronous operation before your application is suspended, call
            // eventObject.setPromise(). 
        };
     
        app.start();
     
        //If Document fully loaded than begin processing
        document.addEventListener("DOMContentLoaded", initialize, false);
     
    })();

     

    Loading and Saving Data Example

    Only thing I had to add here was jQuery due to the dependency on it. Here is the HTML:

    <body>
     
    <h3>Tasks</h3>
     
    <form data-bind="submit: addTask">
        Add task: <input data-bind="value: newTaskText" placeholder="What needs to be done?" />
        <button type="submit">Add</button>
    </form>
     
    <ul data-bind="foreach: tasks, visible: tasks().length > 0">
        <li>
            <input type="checkbox" data-bind="checked: isDone" />
            <input data-bind="value: title, disable: isDone" />
            <a href="#" data-bind="click: $parent.removeTask">Delete</a>
        </li> 
    </ul>
     
    You have <b data-bind="text: incompleteTasks().length">&nbsp;</b> incomplete task(s)
    <span data-bind="visible: incompleteTasks().length == 0"> - it's beer time!</span>
     
    </body>

     

    Here is the JavaScript:

     
    (function () {
        "use strict";
     
        var app = WinJS.Application;
     
        app.onactivated = function (eventObject) {
            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
                if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
                    // TODO: This application has been newly launched. Initialize 
                    // your application here.
                } else {
                    // TODO: This application has been reactivated from suspension. 
                    // Restore application state here.
                }
                WinJS.UI.processAll();
            }
        };
     
        
        function Task(data) {
            this.title = ko.observable(data.title);
            this.isDone = ko.observable(data.isDone);
        }
     
        function TaskListViewModel() {
            // Data
            var self = this;
            self.tasks = ko.observableArray([]);
            self.newTaskText = ko.observable();
            self.incompleteTasks = ko.computed(function () {
                return ko.utils.arrayFilter(self.tasks(), function (task) { return !task.isDone() });
            });
     
            // Operations
            self.addTask = function () {
                self.tasks.push(new Task({ title: this.newTaskText() }));
                self.newTaskText("");
            };
            self.removeTask = function (task) { self.tasks.remove(task) };
     
            // Load initial state from server, convert it to Task instances, then populate self.tasks
            $.getJSON("/tasks", function (allData) {
                var mappedTasks = $.map(allData, function (item) { return new Task(item) });
                self.tasks(mappedTasks);
            });
        }
     
        //Main Execution
        function initialize() {
     
            // Activates knockout.js
            ko.applyBindings(new TaskListViewModel());
     
        }
     
     
        app.oncheckpoint = function (eventObject) {
            // TODO: This application is about to be suspended. Save any state
            // that needs to persist across suspensions here. You might use the 
            // WinJS.Application.sessionState object, which is automatically
            // saved and restored across suspension. If you need to complete an
            // asynchronous operation before your application is suspended, call
            // eventObject.setPromise(). 
        };
     
        app.start();
     
        //If Document fully loaded than begin processing
        document.addEventListener("DOMContentLoaded", initialize, false);
     
    })();

     

    Conclusion

    I hope this post has given you an idea of how easy it is to include existing JavaScript Libraries from the Web inside your own Metro Style Apps.  As Always - if you are currently working on a Windows 8 app I would love to hear about it!

    You may also want to check out my previous Windows 8 Metro Style Development Tips:

  • DaveDev

    Accessing the Camera in a Windows 8 Metro Style App using HTML and JavaScript

    • 2 Comments

    Overview

    I have recently been coding a Windows 8 Metro Style App using the new Windows 8 Release Preview bits and Visual Studio Express 2012 RC.  The app is going to be a retro shooter that takes advantage of HTML5 Canvas for the main game engine and then several Windows 8 Metro Style App Features.

    One of the things I wanted to do in the game was to keep Player Profiles.  These profiles track player high scores, player names and a player avatar.  Eventually these high scores could be used in a leaderboard running in the cloud. 

    Player Name and Avatar

     

    I thought it would be neat to let players take a picture of themselves for their Avatar by accessing the webcam.  Fortunately for us WinRT makes this super easy to use.  We can even pull up prebuilt camera capture UI by using the built-in Windows.Media.Capture API’s.  If you are an existing Windows Phone Developer this may remind you a lot of Windows Phone Development Tasks – something I absolutely loved. 

    Features like camera timer, settings, and  even picture cropping are all done for us via the Windows.Media.Capture.CameraCaptureUI.  For those looking for more control you also have the ability to call into Windows.Media.Capture.MediaCapture.  You won’t get a default UI with MediaCapture but you do get full control of the streams.  MediaCapture is also a way to record Audio streams in your app.

    Here is what the default Capture Capture UI looks like when using the cropping tool:

    Camera Capture Cropping Tool

    After cropping the photo to my liking and tapping the OK button my new Avatar is automatically set.  The new Avatar Image is automatically scaled correctly thanks to the predefined CSS styling we have created.

    New Avatar Picture

     

    Sweet!

     

    Declaration and Initialization

    One of the joys of doing Windows 8 Metro Style development with HTML and JavaScript is that it feels like Web Programming.  In fact my entire game header is done using normal <div> tags and CSS Styles.  Inside of my default.html file I defined the following header to display the player score, level, name and avatar picture:

    <div id="divGame">
            <div id="txtScore">Score: 0</div>    
            <div id="txtLevel">Level: 0</div>
            <div id="divPlayer">
                 <div id="txtPlayerName">Player1</div>
                 <img id="imgPlayer" src="/images/helmet.png" />
             </div>
    <div>  

     

    I then defined a CSS Style for the Avatar Picture using it’s imgPlayer id.

    #imgPlayer {
        max-width: 100px; 
        max-height: 50px;
        float:left;
        margin-right:10px;
     
    }

     

    The Windows Library for JavaScript (WinJS) gives us a way to create a Windows 8 AppBar easily by just a few lines of HTML (more on WinJS Controls later).  By opening the AppBar and tapping the cmdCamera button our players will get the option to take a new Avatar picture.

    <div id="AppBar" data-win-control="WinJS.UI.AppBar" data-win-options="">
           <button id="cmdHome" data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'cmdHome',label:'Menu',icon:'home',section:'selection',tooltip:'Menu'}">
           </button>
          <button id="cmdCamera" data-win-control ="WinJS.UI.AppBarCommand" data-win-options="{id:'cmdCamera',label:'Cadet Photo',icon:'camera',section:'global',tooltip:'Change Cadet Photo'}">
           </button>
         <button id="cmdName" data-win-control ="WinJS.UI.AppBarCommand" data-win-options="{id:'cmdName',label:'Cadet Name',icon:'url(images/accept.png)',section:'global',tooltip:'Change Cadet Name',type:'flyout',flyout:'nameFlyout'}">
           </button>
    </div>

     

    All of the AppBar buttons tap events are handled using standard JavaScript EventListeners:

    //AppBar Commands
            document.getElementById("cmdCamera").addEventListener("click", capturePhoto, false);
            document.getElementById("cmdName").addEventListener("click", showCadetNameUpdate, false);
            document.getElementById("cmdHome").addEventListener("click", showMenu, false);
            document.getElementById("submitButton").addEventListener("click", updateCadetName, false);

    That’s it! We now have a working Game Header and AppBar for our game.  When a player clicks on the Camera Button we will invoke a call to the capturePhoto function.

     

    Opening the Camera Capture UI

    capturePhoto is where the magic happens.  The first thing we do is make a call to WinRT using the Windows.Media.Capture namespace.  This gives us a reference to CameraCaptureUI where we can set default properties.  For my game I am telling the camera to use a 16x9 aspect ratio and lock to the photo mode.  If I wanted the user to capture video I could easily do this as well by using Windows.Media.Capture.CameraCaptureUIMode.video.

    //WinRT Camera API
        function capturePhoto() {
            try {
                var dialog = new Windows.Media.Capture.CameraCaptureUI();
                var aspectRatio = { width: 16, height: 9 };
                dialog.photoSettings.croppedAspectRatio = aspectRatio;
                dialog.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo).then(function (file) {
                    if (file) {
                        imgPlayer.src = URL.createObjectURL(file);
     
                    } else {
                        //No Photo captured
                    }
                }, function (err) {
                    displayError(err);
                });
            } catch (err) {
                displayError(err);
            }
        }

    This is where you will start to see all of the Async goodness inside of WinRT.  The Camera Capture UI  will run asynchronously while it awaits the user to take a picture.  I handle the callback by invoking a JavaScript Promise using the then keyword.  The promise will call a function when the user takes a photo and if there is an object it will set the source of my imgPlayer tag to be that file.  Those of you familiar with Web programming will recognize the URL.createObjectURL call so that my <img> tag knows where to reference the new source file.

     

    Conclusion

    Hopefully you have seen how easy it is to access the WebCam on Windows 8 now thanks to WinRT and the Windows.Media.Capture API’s.  As Always - if you are currently working on a Windows 8 app I would love to hear about it!

    You may also want to check out my previous Windows 8 Metro Style Development Tips:

Page 1 of 2 (7 items) 12