Xnaml Component

I am very proud to announce the public beta of Xnaml, a Silverlight plugin for Xna on WIndows Phone 7, Xbox 360 and Pc. Making user interface with Xna it a too long and boring task. I’ve made a plugin that load Xaml page and c# sub code to reproduce the main feature of the Silverlight technology :


  • Dependency Properties
  • Inheritance DP (FontProperty, ForegroundProperty, DataContext, …)
  • Storyboards
  • Binding
  • Xaml parseur
  • Resources
  • Render Transform
  • Easing animation
  • Generics/Theme.xaml files
  • Style
  • Template
  • VisualStateManager
  • and so on …

And main known controls :


  • Border
  • Button
  • ContentControl
  • Grid
  • Image
  • ItemsControl
  • ItemsPresenter,
  • ControlPresenter
  • RootElement (similar to Window)
  • ScrollViewer
  • ScrollContentPresenter
  • StackPanel
  • TextBlock
  • Canvas
  • TextBox
  • Panel
  • Page

And specifc controls :


  • Iphone Scoller
  • Panaorama
  • WP7 Date picker

 


I respect all the WPF hierarchy : DispatcherObject-> DependencyObject-> Visual -> UIElement -> FrameworkElement/FrameworkContent


 


Just Xna !


 


My engine is designed to run in a pure Xna environement on all supported device. My engine is extensible, you can add your own controls, inherit from base classes (such as Control or Pane) to make your own behavior. The compatibility with Xaml is complete. Create your interface on Blend and make a simple copy/past action to add the Xaml file in your Xna project ! Extract a C# code from a Silverlight project and just add it to your own Xna project !


Getting started


The Xnaml Plugin is based on two Assemblies :


  • Arcane.Xna.Windows with contains the main Silverlight classes adapted to Xna
  • Arcane.Xna.Windows.Controls witch contains the main controls (Button, PanoramaControl, etc.)

Reference this two Dlls in you Xna project :



Add a using reference in your game class like this :


using Arcane.Xna.Windows;


Then create a new Arcane.Xna.Windows.XnamlGameComponent object and add it to the component list of your game class :


            XnamlGameComponent component = new XnamlGameComponent(this);
            this.Components.Add(component);


The Silverlight plugin is now ready !  And the Application.Current object is instanced and ready to use. We just have to fill its RootVisual property.


Lets try to add a first user interface. We will make the Application bar of the Windows Phone as a sample :




The first hing to do is to create the fonts we need here. Add to sprite font to the content project. The first will be called NormapSpriteFont and will create a sprteshhet for a Segoe UI with a size of 20. The second will be named SmallSpriteFont and will have the same font family with with a font size of 10.



Add a reference to the font identifier to the Application object to define the default font to use in TextBlock and ContentPresenter : For that, just add after the last instruction this line :


            Application.Current.DefaultFontAssetName = “NormalSpriteFont”;


Now create a sub directory to your project named Presentations. We will add in this folder all your xaml file and associed c# file. Right click on this directory and add a new Xml file named MainPage.xnaml :




Add this content :


<?xml version=”1.0″ encoding=”utf-8″ ?>
<Page
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
    x:Name=”Page”
  xmlns:phone=”clr-namespace:Arcane.TowerDefense.Controls.Phone;assembly=Arcane.TowerDefense”
  xmlns:interface=”clr-namespace:Arcane.TowerDefense.Controls.Interface;assembly=Arcane.TowerDefense”
    WindowTitle=”Page”
    FlowDirection=”LeftToRight”>

  <Grid x:Name=”LayoutRoot”>
   
  </Grid>
</Page>


That an easy to undertand Xaml code. The Page is a container control to host your interface. It can contain only one control. Here this orphan control is a grid, named LayoutRoot. Save this file and change its compilation mode to EmbeddedResource.



Now add a C# classic file named MainPage.xnaml.cs. Add the same using instruction to this file and change the code to :


   public class MainPage : Page
    {
        public MainPage()
        {
            Application.LoadComponent(this, new System.Uri(“/XnamlWindowsPhoneGame;component/Presentations/MainPage.xnaml”, System.UriKind.Relative));
        }
    }


Now the C# and xnaml file are linked. Add this instruction in the Initialize game method :


        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            Application.Current.RootVisual = new Presentations.MainPage();

            base.Initialize();
        }


And run the application. You will see nothing else than the cornflower blue screen. Lets add some color.


Change the MainPage.xnaml content to add a Background to the LayoutRoot grid :


  <Grid x:Name=”LayoutRoot” Background=”#FFFF0000″>
   
  </Grid>


Relaunch the application, now the screen is red :



Add Some margin to the grid to see once again le back blue screen :


  <Grid x:Name=”LayoutRoot” Background=”#FFFF0000″ Margin=”10″>
   
  </Grid>



Now add some children. Lets try a Canvas, StackPanel and Grid childrens :


  <Grid x:Name=”LayoutRoot” Background=”#FFFF0000″ Margin=”10″>

    <Canvas Margin=”-10″ Background=”#CCFFFFFF”></Canvas>
    <StackPanel Width=”50″ Height=”30″ Background=”Yellow”></StackPanel>
    <Grid Margin=”20″ Background=”#CCCCCC”></Grid>
   
  </Grid>


If you launch the application now, you get a dirty screen like this :



That’s normal : all children are layout uppon each others. Add some columns and row to layout this :


  <Grid x:Name=”LayoutRoot” Background=”#FFFF0000″ Margin=”10″>

    <Grid.ColumnDefinitions>
      <ColumnDefinition></ColumnDefinition>
      <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition></RowDefinition>
      <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>

    <Canvas Margin=”-10″ Background=”#CCFFFFFF”></Canvas>
    <StackPanel Grid.Row=”0″ Grid.Column=”1″ Width=”50″ Height=”30″ Background=”Yellow”></StackPanel>
    <Grid Grid.Row=”1″ Margin=”20″ Background=”#CCCCCC”></Grid>
   
  </Grid>


Relaunch the application you will have this :



Now lets try to add some texte and some pictures to this interface. I downloaded from web some picture like this :



to add to my interface. I put this icons in a folder named Icons in the content project :



 


Now lets begin to create an application bar.


Change the layoutroot Grid for this :


   <Grid Name=”LayoutRoot”>

      <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition Width=”Auto”></ColumnDefinition>
      </Grid.ColumnDefinitions>

      <Grid x:Name=”WorldZone” Background=”Transparent”>
    
      </Grid>

      <Grid x:Name=”MenuGrid” Grid.Column=”1″ Width=”72″ Background=”#1F1F1F”>

        <Grid  x:Name=”ThreePointButton”
               HorizontalAlignment=”Left” VerticalAlignment=”Top”
               Background=”Transparent” Margin=”0,10″
               Width=”72″ Height=”36″>
          <Image  Source=”Icons/ThreePoints” Width=”32″ Height=”16″ Margin=”20,5″/>
        </Grid>

        <StackPanel x:Name=”CliffButtons” Orientation=”Vertical” HorizontalAlignment=”Left” VerticalAlignment=”Center”>

          <Grid x:Name=”RiseButton” Margin=”11,5″ HorizontalAlignment=”Left” Background=”Transparent”>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width=”Auto”></ColumnDefinition>
              <ColumnDefinition Width=”12″></ColumnDefinition>
              <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Image  Source=”Icons/calend” VerticalAlignment=”Top” Width=”49″ Height=”50″ Margin=”0,5″/>
            <TextBlock Text=”Calendar” Foreground=”White” Grid.Column=”2″ FontFamily=”SmallSpriteFont”></TextBlock>
          </Grid>
          <Grid x:Name=”SunkButton”  Margin=”11,5″ HorizontalAlignment=”Left” Background=”Transparent”>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width=”Auto”></ColumnDefinition>
              <ColumnDefinition Width=”12″></ColumnDefinition>
              <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Image  Source=”Icons/compas” VerticalAlignment=”Top” Width=”49″ Height=”50″ Margin=”0,5″/>
            <TextBlock Text=”Compas” Foreground=”White” Grid.Column=”2″ FontFamily=”SmallSpriteFont”></TextBlock>
          </Grid>
          <Grid x:Name=”LevelingButton” Margin=”11,5″ HorizontalAlignment=”Left” Background=”Transparent”>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width=”Auto”></ColumnDefinition>
              <ColumnDefinition Width=”12″></ColumnDefinition>
              <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
                 <Image  Source=”Icons/info” VerticalAlignment=”Top” Width=”49″ Height=”50″ Margin=”0,5″ RenderTransformOrigin=”0.5,0.5″>
              <Image.RenderTransform>
                <ScaleTransform ScaleX=”0.5″ ScaleY=”0.8″></ScaleTransform>
              </Image.RenderTransform>
            </Image>


         <TextBlock Text=”In your face” Foreground=”White” Grid.Column=”2″ FontFamily=”SmallSpriteFont”></TextBlock>
          </Grid>
          <Grid x:Name=”RampButton”  Margin=”11,5″ HorizontalAlignment=”Left” Background=”Transparent”>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width=”Auto”></ColumnDefinition>
              <ColumnDefinition Width=”12″></ColumnDefinition>
              <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Image  Source=”Icons/info” VerticalAlignment=”Top” Width=”49″ Height=”50″ Margin=”0,5″/>
            <TextBlock Text=”info” Foreground=”White” Grid.Column=”2″ FontFamily=”SmallSpriteFont”></TextBlock>
          </Grid>

        </StackPanel>

        <StackPanel Orientation=”Vertical” Margin=”200,20″ HorizontalAlignment=”Left” VerticalAlignment=”Top”>

          <TextBlock Text=”Menu item 1″ Foreground=”White” HorizontalAlignment=”Left” Margin=”0,25″></TextBlock>
          <TextBlock Text=”Menu item 2″ Foreground=”White”  HorizontalAlignment=”Left” Margin=”0,25″></TextBlock>
          <TextBlock Text=”Menu item 3″ Foreground=”White”  HorizontalAlignment=”Left” Margin=”0,25″></TextBlock>
          <TextBlock Text=”Menu item 4″ Foreground=”White” HorizontalAlignment=”Left” Margin=”0,25″></TextBlock>

        </StackPanel>
      </Grid>


    </Grid>


Wow that’s bigger :) The LayoutRoot have to column, the first take all the place it can, the second take only the place asked by its children. The second children is the Application bar, it takes 72 pixel like the UI specification said. At the top with an VerticalAlignment tp top i put 3 points to expand, collapse the application bar. I use a StackPanel to layout my icons. And icon is an image and a small text at the left that is not visible in the collapsed state.Just for the fun you will notice that i put to the last icon a rendertransform to scale the Image. To finish, i put another StackPanel with menu items. If you launch it you will see this :



Are we on Silverlight or Xna here oO ? :) Yes that’s pure Xna !


Lets try to add the expanded state and use some storyboards. Open the MainPage.xnaml.cs file we’ve created before. Override the OnApplyTemplate and try to thet the UIElements named “ThreePointButton” and “MenuGrid” in the xnaml file :


        public Grid MenuGrid
        {
            get;
            set;
        }


        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();


            MenuGrid = this.GetTemplateChild(“MenuGrid”) as Grid;

            Grid grid = this.GetTemplateChild(“ThreePointButton”) as Grid;
            if (grid != null)
                grid.Tap += new RoutedEventHandler(grid_Tap);
        }

        void grid_Tap(object sender, RoutedEventArgs e)
        {
           
        }


We try to get the Grid and if it suceed we connect to the Tap event. Be sure to intercept tap event that your grid have a visible background (with an Alpha different of 0). Add a boolean to the MainScreenc lass named expanded. It will inform us if the application bar is expanded or not.


bool isExpanded;


Add this utility function to the end of the MainScreen class :


        public DoubleAnimation CreateAnimation(DependencyObject obj, DependencyProperty prop, double fromValue, double toValue, double milliseconds, EasingMode easing)
        {
            CubicEase ease = new CubicEase() { EasingMode = easing };
            DoubleAnimation animation = new DoubleAnimation
            {
                Duration = new Duration(TimeSpan.FromMilliseconds(milliseconds)),
                From = fromValue,
                To = Convert.ToDouble(toValue),
                FillBehavior = FillBehavior.HoldEnd,
                EasingFunction = ease
            };
            Storyboard.SetTarget(animation, obj);
            Storyboard.SetTargetProperty(animation, new PropertyPath(prop));

            return animation;
        }


I use this function inside my Silverlight project we no changes !


It create a DoubleAnimation on the specified object/property from the specified value to the specified value.


To correct errors add two more using :


using Arcane.Xna.Windows.Media;
using Arcane.Xna.Windows.Data;


Now fill the tap event method :


          void grid_Tap(object sender, RoutedEventArgs e)
        {
            e.Handled = true;
            Storyboard storyboard = new Storyboard();
            if (isExpanded)
            {
                storyboard.Children.Add(CreateAnimation(MenuGrid, FrameworkElement.WidthProperty, 500, 72, 200, EasingMode.EaseOut));
            }
            else
            {
                storyboard.Children.Add(CreateAnimation(MenuGrid, FrameworkElement.WidthProperty, 72, 500, 200, EasingMode.EaseOut));
            }
            storyboard.Begin();
            isExpanded = !isExpanded;
        }


We ask the engine that the event is handled. We change the expanded state and launch a new storyboard. Launch the application, you will have this :


[View:http://www.youtube.com/watch?v=nDP_iAAL9JQ:550:0]


 


As you can see, the Xnaml component is very simple and 100% compatible with Silvelright/WPF. In some case we can have better performance in Xna for UI than Silverlight with its compositor thread. We will see in next tutorial how to use template/style.


You can look at this video to see a sample of me that recreate the panorama control very easely by a simple copy past from an old codeproject about panorama and pivot control.


[View:http://www.youtube.com/watch?v=VgI9SCbURU4:550:0]


 


[soon]


 


Valentin


 

Anexe Windows Phone : Optimiser le chargement des assets

Le chargement des assets en Xna est un veritable casse tête pour les développeurs. Le multithreading n’était pas pas un solution pour l’heure, bon nombre de développeur s’orientent vers un chargement de type bulk (en masse) au lancement du jeu.


Malheureusement le lancement d’un jeu est soumis à plusieurs règles :


  • Elle doit afficher un premier écran en moins de 5 secondes
  • Au bout de 20 secondes l’application doit répondre aux interactions de l’utilisateur

Si ces deux règles ne sont pas respectées, le Watch Dog du système d’exploitation tuera le processus de votre jeu et l’utilisateur retournera à l’écran précédent.


Ajoutez à cela un accès mémoire relativement lent sur le hardware de base du WP et vous voilà confronté à un gros problème, surtout si votre jeu doit charger un grand nombre d’assets.


Voyons les différentes techniques qui s’offrent à nous pour améliorer le loading de vos jeux.


 


Premier moyen : réduire la taille des fichiers


Un seul règle ici : plus c’est gros, plus c’est long.


Gérer la taille d’un fichier pour la réduire, c’est sans doute le moyen le plus efficace d’améliorer les temps de chargement. Par default le content pipeline tente de générer à la compilation de son contenu les assets les plus fidèles en qualité au contenu original. Ce qui produit généralement un fichier aussi gros (sinon plus) que l’original ! Le développeur est heureusement libre de preciser l’encodage du fichier sous la forme d’un algorithme ou d’une qualité de compression. Trois types de fichiers se prêtent bien à cette optimisation : les fichiers sons, les fichiers images (textures), les fichiers Xml.


Son


Lorsque l’on expand les propriétés du processor lié à un asset sonore qu’il s’agisse d’un SoundEffect ou d’un Music il est possible de préciser la qualité de compression sonore. La fidélité de la qualité d’un son à l’original dépend bien entendu du son en lui même, mais il est possible de garder une qualité acceptable avec un taux de compression important. Le loading peut alors être considérablement réduit :



Certains tests peuvent montrer jusqu’à 20% de temps de chargement gagné. En outre il est possible de gagner encore 8% si le développeur utilise des sons en 44.1 kHz au lieu d’un 48kHz qui, il faut bien l’avouer n’apporte rien de plus sur un WP.


Textures


Les fichiers textures peuvent paradoxalement aboutir à un fichier bien plus important que la source, notamment si le choix du format de texture se porte sur “Color” :



L’utilisation du format Dxt (utilisant DXT1 pour des textures opaques et DXT5 pour les texture avec transparence) peut améliorer la taille du fichier et son chargement (les cartes graphiques peuvent décompresser une telle texture sur le hardware). Mais la qualité ici peut être radicalement différente. Il convient de toujours éviter une telle compression si votre texture contient des motifs fins, si elle contient du texte ou si elle est destinée à être visible de manière précise par le joueur. Au programmeur de faire un choix en fonction du contexte d’utilisation de la texture.


Enfin on peut être tenter de ne pas compiler (Build Action à None) une texture et de simplement autoriser sa copie dans le répertoire de destination (“Copy to Output Directory” à “Copy if newer“) afin de la charger avec un Texture2D.FromStream au runtime :



Effectivement, un png peut sembler plus petit que le Xnb généré par le content pipeline. Sachez juste que le DXT est utilisé nativement par le GPU qui les exploitent très facilement et rapidement. Utiliser une texture avec FromStream obligera une “decompression” du fichier au runtime et un chargement ddes données en 32 bits qui coutera du temps et surtout de la mémoire.


Pensez à toujours utiliser des textures ayant une taille puissance de 2. Quitte à les redimensionner. Leur chargement est plus rapide.


Utilisez aussi des SpriteSheet. Technique qui consiste à inclure dans une grosse texture plusieurs petites textures. Optimisant de fait le temps de chargement.


Xml


La sérialisation est d’une simplicité déconcertante aujourd’hui. L’utilisation du namespace System.Xml.Serialisation fait gagner un temps considérable. Malheureusement, comme précédemment, .Net ne fait pas dans la dentelle, il essaye de reproduire aussi bien que l’original. Ce qui dans le cas du Xml est très … verbeux.


Regardez la classe suivante que nous allons serialiser :


    public class DataOfMyGameWithAgreatNameForAClassIsntIt
    {

        public string ButYouKnowMyFriendMyPropertiesHaveGigNamesToo = “yes that’s true”;
        public int IntegerTakesPlaceToo;
       
    }


Elle possède des noms exagerement grand afin de mettre en exergue l’importance de maitriser la sérialisation de tout en bout. Serialisée cette classe donne :


  <?xml version=”1.0″?>
  <DataOfMyGameWithAgreatNameForAClassIsntIt xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=”http://www.w3.org/2001/XMLSchema”>
    <ButYouKnowMyFriendMyPropertiesHaveGigNamesToo>yes that’s true</ButYouKnowMyFriendMyPropertiesHaveGigNamesToo>
    <IntegerTakesPlaceToo>0</IntegerTakesPlaceToo>
  </DataOfMyGameWithAgreatNameForAClassIsntIt>


Lisible… mais gros. Donc long à charger. Surtout si la serialisation portable sur un ensemble conséquent de données. La solution est très simple, elle consiste à la fois à demander de sérialiser un champs en tant qu’attribut Xml et non en tant qu’élément et à donner à cet attribut un nom aussi court que possible :


    [XmlRoot("c")]
    public class DataOfMyGameWithAgreatNameForAClassIsntIt
    {

        [XmlAttribute(AttributeName = "a")]
        public string ButYouKnowMyFriendMyPropertiesHaveGigNamesToo = “yes that’s true”;
        [XmlAttribute(AttributeName = "b")]
        public int IntegerTakesPlaceToo;
       
    }


Le resultat se passe de commentaire :


  <?xml version=”1.0″?>
  <c xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” a=”yes that’s true” b=”0″/>


Plus de 50% de gain…




Second moyen : Adapter au besoin la ressource


Trois étapes ici. D’abord analyser la bonne utilisation de ses assets :


Pourquoi mettre un son wave dans un jeu qui n’est audible que sur 2s et pourtant dure 10 ?


Pourquoi utiliser une texture énorme pour un effet de particule là ou une simple image de 16*16 serait suffisante ?


Est il vraiment nécessaire d’utiliser autant de musiques dans son jeu ?


L’utilisation d’un modèle 3D de grande qualité est-elle nécessaire ? Elle prend du temps à charger… mais aussi à afficher.


En sommes il faut être sûr de ne pas gaspiller du temps de chargement avec des assets qui représentent un “luxe” bien trop important là ou une asset plus … “modeste” aurait été suffisante sans perte de qualité visuelle ou sonore.


Ensuite vérifier les “orphans” :


Un orphan (ou orphelin) est un fichier ajouté un jour au projet d’assets par le développeur et qui n’est plus utilisé.  Certes si ce fichier n’est plus utilisé il ne peut rien couter en temps de chargement. Mais bon nombre de développeurs (dont votre serviteur) chargent les assets au lancement du jeu en s’aidant d’une liste qui les énumère et permet de les charger de manière séquentielle. Le contenu de cette liste n’est pas forcement objectif par rapport a la réelle utilisation d’une asset dans un jeu. On peut se retrouver avec le chargement d’une ressource très importante qui paradoxalement n’est plus utilisée.


Enfin s’adapter au device :


Le besoin de qualité d’une texture (et sa taille) n’est pas forcement le même sur Windows Phone que sur Xbox 360. Les possibilités de cette dernière étant largement supérieure en hardware et en surface d’affichage les textures utilisées doivent forcement être plus importante en taille afin de garder un affichage aussi beau que possible. Le Xna Framework est un framework portable, un simple clique droit sur un fichier permet de passer une application d’un device vers un autre. Seul un projet ne change pas dans cette démarche : le projet de contenu. Il est alors possible de se retrouver avec un projet Windows Phone utilisant des assets destinées à la Xbox 360.La surface d’affichage d’un écran de télé ou d’ordinateur n’est pas la même que sur un WP ! Il est facile d’imaginer que le temps de chargement d’une texture en 2048*2048 n’est pas le même sur ces deux devices. Il existe deux solutions ici. Créer un nouveau projet de contenu reproduisant la même arborescence sur l’original mais avec des assets adaptée au device ciblé. Ou bien utiliser des directives pre processor pour modifier le code à la compilation en fonction de la cible de compilation. Un :


#if Windows_Phone
//code
#endif


ne compilera la partie “//code” que si le projet est compilé avec pour cible le Windows Phone. On peut alors imaginer que “//code” est une suite d’appel à la méthode Load du ContentManager avec des assets adaptées.


 


Troisième moyen : charger de manière intelligente


Charger de manière intelligent des assets c’est d’abord charger au besoin, c’est “se jouer du joueur”, c’est profiter du contexte du jeu, c’est, enfin, ménager le joueur. Voyons en détail ces trois points.


Regardez cette vidéo d’un jeu que j’ai réalisé avec quelques amis :


[View:http://www.youtube.com/watch?v=VgI9SCbURU4:550:0]


On y distingue 3etapes : l’affichage d’un logo animé, l’affichage d’un écran de trademark avec une progressbar, l’affichage du menu.


D’abord le logo animé. Vous avez sans doute remarqué qu’il y avait 3 parties ici :


  1. Le logo apparait de gauche vers la droite
  2. Il stoppe 3secondes
  3. Puis disparait de droite vers la gauche.

On ne le voit pas mais ici, je me joue du joueur en chargeant des assets durant les 3secondes ou le logo ne bouge plus. Je ne risque pas de faire laguer le jeu ou de deconcerter le joueur car cet écran est passif et n’affiche aucune animation. Le joueur n’y voit que du feu ! J’ai tout simplement profité d’un contexte ou le jeu n’avait rien d’autre à faire.


Seconde étape. Je ménage le joueur. Le trademark affiche une progressbar avec quelques messages amusants et du texte lire. L’écran profite là aussi pour charger un grand nombre d’assets mais ne déstabilise pas l’utilisateur puisqu’il lui donne des informations sur son état au travers de la progressbar. Sans cette dernière, un joueur non averti pourrait croire à un plantage ou rapidement se lasser de cet écran. En outre cet écran (tout comme le précédent) ne charge pas les assets demandée par une partie mais uniquement celles demandées par le menu. Lorsque le joueur lancera une partie, un nouvel écran chargera les données demandées par une partie. Le chargement réalisé par étape devient plus rapide et plus acceptable. Généralement le second écran dans un jeu (qui n’est pas visible dans la video ci-dessus) affiche des trucs et astuces ou bien les commandes pour apprendre au joueur à utiliser son jeu. D’autres jeux présentent le contexte dramatique du jeu par l’intermédiaire d’une carte ou le joueur voir sa progression dans le monde. Bref… de quoi passer le temps.


In game il est possible aussi de se jouer du joueur. Quand l’utilisateur clique sur un bouton provoquant une modification importante de la trame dramatique du jeu ou affiche un menu important, pkoi ne pas prendre une seconde pour charger quelques assets ? Le joueur verra un petit lag certes, mais il lui semblera “logique” eu égard à l’importance de l’action qu’il a réalisée.


Ceux qui connaissent Age of Empire ont sans doute remarqué qu’en changeant d’âge, toutes les assets du monde changent. Sur les vielles machines on peut sentir un leger lag là aussi. Mais ce lag semble logique au joueur. Pour le développeur c’est un moyen de ne charger ces nombreuses assets au moment de la partie avec le “consentement” tacite du joueur…


 


Quatrième moyen : Eviter le watchdog


Pour éviter le watch dog et un kill de son application due à un chargement trop long la méthode est simple.


Il faut tout d’abord tenter d’aller sur le premier update/draw le plus rapidement possible.


Ne mettez donc aucun load dans les méthode LoadContent ni aucun traitement lourd dans Initialise. Réalisez une liste des assets à charger pour le premier écran interractif du jeu (généralement l’écran titre). Voici par exemple la liste d’assets de mon jeu:


<Assets>
  <Asset Name=”Fonts\SubTitleSpriteFont” Type=”Microsoft.Xna.Framework.Graphics.SpriteFont, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842CF8BE1DE50553″ />
  <Asset Name=”Fonts\LegendSpriteFont” Type=”Microsoft.Xna.Framework.Graphics.SpriteFont, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842CF8BE1DE50553″ />
  <Asset Name=”Maps\homescreen” Type=”Arcane.TowerDefense.Terrain.LayeredTerrain, Arcane.TowerDefense, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null” />


  <!– …–>


  <Asset Name=”LogoATD” Type=”Microsoft.Xna.Framework.Graphics.Texture2D, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842CF8BE1DE50553″ />
  <Asset Name=”logo_gs” Type=”Microsoft.Xna.Framework.Graphics.Texture2D, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842CF8BE1DE50553″ />
  <Asset Name=”Textures\magicTrail” Type=”Microsoft.Xna.Framework.Graphics.Texture2D, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842CF8BE1DE50553″ />
  <Asset Name=”Textures\Summer\ground” Type=”Microsoft.Xna.Framework.Graphics.Texture2D, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842CF8BE1DE50553″ />
  <Asset Name=”Textures\Summer\badly” Type=”Microsoft.Xna.Framework.Graphics.Texture2D, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842CF8BE1DE50553″ />
  <Asset Name=”Textures\Summer\darkgrass” Type=”Microsoft.Xna.Framework.Graphics.Texture2D, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842CF8BE1DE50553″ />
  <Asset Name=”Textures\Summer\grass” Type=”Microsoft.Xna.Framework.Graphics.Texture2D, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842CF8BE1DE50553″ />
</Assets>


J’ai volontairement supprimé une bonne cinquantaine d’assets pour ne pas polluer cet article. En analysant ce fichier je peux récupérer le nombre d’assets à charger. Je peux donc tenir un indexeur à jour qui m’indique ma progression de chargement dans cette liste. Je suis donc capable de réaliser une progress bar.


Enfin je réalise un Load d’asset non pas à la suite afin de ne pas freezer le jeu mais séquentiellement à chaque update. Si j’ai 50 assets à charger, il me faudra 50 updates. Évidemment je ne ferait pas cela dans un écran demandant des interactions utilisateur ou affichant des animations. Le jeu freezera beaucoup et il ne faut pas le montrer. Les deux premiers écrans de la videos sont donc parfait pour ce type de traitement : dans le premier je charge les asset quand l’animaton s’arrête, dans le second j’affiche un écran statique où seule la progresse bar est animée.


En outre je ne charge que les assets nécessaires à l’écran que je veux afficher. Ceci est très important pour les problématiques de recover. Quand notre jeu est soumis à une interruption (quitter temproairement le jeu pour répondre à un sms, aller sur le web…) il libère toutes ses assets. Le joueur attends donc de pouvoir revenir au jeu très rapidement pour continuer sa partie Le développeur doit donc être capable de ne charger que les ressources qui seront utilisées pour le prochain écran interractif. Le meilleur moyen reste donc de grouper les assets part scènes de jeu.


Souvenez vous qu’une asset chargée ne sera jamais rechargée si elle n’est pas disposée par une interruption ou un appel à dispose. Et ce, même si vous appelez une nouvelle voit Load avec son identifiant.


Pour terminer soyez ami avec le Garbe Collector. Il est l’enfant terrible du WP. Le GC se lance à chaque fois qu’un nouveau méga de mémoire est alloué par l’application. Autant dire que pour le chargement d’assets il est souvent appelé. Il peut intervenir moins souvent si vous gerez au mieux la taille de vos assets et leur ordre de chargement en ayant en tête ce méga. Un freeze GC peut faire jusqu’à 200ms ce qui n’est pas rien…


Je mettrais en ligne sous peu mon système de chargement.


 


Etre inventif et astucieux


C’est les deux qualités à avoir pour réaliser un chargement rapide, transparent et pérenne pour ses jeux. N’oubliez pas que le chargement est la première impression qu’un joueur va avoir de votre appli… elle est donc la brique la plus sensible que vous aurez à gérer…


 


[Soon]


Valentin

Update Windows Phone 7 : Du bon pour les jeux

Plusieurs améliorations sympathiques en dehors du copier coller arrivent avec la prochaine update du Windows Phone 7. Elles concernent le jeu et sont attendues. Jugez plutôt :

Chargement plus rapide des applications


Le temps de chargement d’une application ou d’un jeu, ou le temps de résume sera réduit de manière importante. Il est vrai qu’aujourd’hui sur quelque modèles le chargement d’un jeu peut prendre plus d’une minute oO !

Amélioration de la recherche sur le Marketplace


Le recherche sur le marketplace sera facilité pour améliorer la qualité des résultats sur les jeux, les applications et la musique. Le bouton de recherche, activé dans la partie application ou jeu du marketplace ciblera uniquement les résultats portant sur ces types d’applications.

Press Search in the music section of Marketplace to search just the music catalog.