Série d'articles : Développement d'un jeu occasionnel (« casual game ») avec Silverlight 2Author : Joel Neubeck – Director of Technology / Silverlight MVP Blog :
http://joel.neubeck.net/ This article appeared in the Expression Newsletter;
subscribe to get the next issue. · Module 1 : Prise en main – Architecture/infrastructure · Module 2 : Détection de mouvement et de collision · Module 3 : Conception – Sprites, tableaux et boîtes de dialogue · Module 4 : Animations et son · Module 5 : Initialisation et déploiement · Module 6 : Concepts avancés (physique, multijoueur, optimisation) Dans cette série d’articles, nous allons étudier le processus de conception et de création d’un jeu occasionnel en ligne (ou encore « casual game ») dans Silverlight 2 (SL2). Il n'est pas surprenant que les jeux en ligne ne cessent de gagner en popularité. Les jeux occasionnels répondent à notre soif de loisirs interactifs au cours des heures de plus en plus nombreuses que nous passons sur Internet. Adobe Flash était jusqu'à présent la plateforme privilégiée pour les jeux occasionnels. Avec l'arrivée imminente de la version RTM de SL2, vous allez pouvoir utiliser Silverlight pour créer votre prochain jeu en ligne. Dans cette série d'articles, nous allons nous intéresser au développement interactif et construire notre propre version du jeu « Sabotage », un classique des années 1980. Si vous possédez un iPod, vous avez peut-être joué à sa version Apple appelée « Parachute », où vous abattez des parachutistes avant qu'ils n'atterrissent et ne détruisent votre bunker. De nombreuses versions de ce grand classique se sont succédées au fil des années : la Figure 1 illustre quelques présentations visuelles de ce jeu. .jpg)
Figure 1 - Exemples de jeux Module 1 : Prise en main – Architecture/infrastructureDans ce premier module, nous allons conceptualiser notre jeu et créer l'infrastructure architecturale qui servira de base à notre projet. Notre code sera intégralement écrit en Microsoft.NET C# et mettra à profit les infrastructures et les outils suivants : · Visual Studio 2008 · Kit de développement Silverlight 2 Beta 2 et outils pour VS2008 · Microsoft .Net Framework 3.5 · Microsoft Expression Blend 2.5 (présortie en juin 2008) · Microsoft Expression Design 2 ConceptL'objectif de ce jeu est simple : obtenir le maximum de points en abattant des parachutistes et des hélicoptères avant que l'ennemi ne détruise votre bunker. Conditions requises- L'utilisateur doit se servir de la souris pour diriger sa tourelle. Il doit cliquer avec le bouton droit pour tirer.
- La distance entre le réticule du canon et la tourelle détermine la vitesse initiale de l'obus.
- Chaque obus décélère pendant la distance de tir.
- Lorsqu'un parachute est touché, le parachutiste tombe.
- Lorsqu'un parachutiste est touché, l'ennemi est éliminé.
- Lorsqu'un hélicoptère est touché, il est détruit. Ses débris peuvent éliminer d'autres parachutistes.
- Chaque objet détruit rapporte 2 points. Chaque tir d'obus vous fait perdre 1 point.
InfrastructureÀ l'automne dernier, Microsoft a sorti sa première version de l'infrastructure Model-View-Controller (MVC) pour ASP.NET. Elle a pour objectif d'aider les développeurs à implémenter ce modèle de séparation éprouvé en divisant l'implémentation d'une application en trois composants : les modèles, les vues et les contrôleurs. Cette « séparation » optimise la capacité d'un développeur à dissocier et à isoler la visualisation des éléments, quelle que soit la logique métier impliquée. Dans le cadre d'un développement Silverlight, MVC est un excellent moyen de prendre en charge des outils propres à des rôles, comme Visual Studio pour les développeurs et Expression Blend pour les concepteurs et les animateurs. L'utilisation de MVC dans un jeu permet d'obtenir une base qui isole la capacité d'un développeur à contrôler l'état (position, mouvement et collisions), tout en laissant aux concepteurs le contrôle complet du rendu des éléments du jeu. La Figure 2 illustre la relation entre chaque composant MVC. Les lignes pleines indiquent les associations directes, alors que les lignes en pointillé représentent les associations indirectes. .jpg)
Figure 2 - Relations entre les composants MVC MVC dans Silverlight 2VuesChaque élément visuel du jeu représente une vue. Cela inclut le shell qui contient la scène, les boîtes de dialogue utilisées pour charger, démarrer et terminer le jeu et chaque élément du jeu (parachutiste, hélicoptère, tourelle et obus). ModèlesPour obtenir une véritable séparation MVC, nous devons faire en sorte que nos modèles soient complètement indépendants de nos vues et de nos contrôleurs. Aucun modèle ne doit comporter de variables d'instance vers l'un de ces composants. Deux méthodologies peuvent être employées lors de la conception d'un modèle : Modèle passif – Dans cette implémentation, une vue est notifiée d'un changement par le contrôleur, uniquement après que le contrôleur a mis à jour le modèle approprié. Modèle actif – Dans cette implémentation, chaque modèle notifie la vue appropriée d'un changement à l'aide d'un modèle observateur. Dans .NET, l'implémentation la plus simple d'un modèle observateur consiste à utiliser des délégués et des événements. Dans le cadre du développement de jeux, cette technique fonctionne plutôt bien, car elle permet à chaque vue de s'abonner à des événements déclenchés par un modèle en cas de changement d'état (nouvelle position ou collision). ContrôleurLe dernier type de composant est le contrôleur. Nous allons utiliser un seul contrôleur qui jouera le rôle de système nerveux central pour le jeu. Le contrôleur sera chargé d'assurer la principale boucle de jeu utilisée pour déplacer les sprites et surveiller les collisions. Le contrôleur est le seul composant qui insérera un élément visuel dans le jeu. Les Figures 3 et 4 montrent la construction et l'initialisation du contrôleur par la page.xaml. Une fois le contrôleur construit, nous pouvons appeler la méthode Initialize du contrôleur pour transmettre toutes les commandes suivantes à ce composant. Dans cette méthode Initialize, nous allons définir le minuteur et charger la boîte de dialogue de démarrage. De nombreux débats ont porté sur l'utilisation de DispaterTimer ou de Storyboard et sur leur efficacité. Contre toute attente, une table de montage séquentiel vide de courte durée constitue le meilleur choix. Elle fonctionnera de manière plus efficace et cohérente. public partial class Page : UserControl { private Controllers.Controller _controller; public Page() { InitializeComponent(); this.Loaded += new RoutedEventHandler(Page_Loaded); } void Page_Loaded(object sender, RoutedEventArgs e) { _controller = new Controllers.Controller(this); _controller.Initialize(); } }
Figure 3 - Code issu de Page.xaml.cs public Controller(Page pageView) { _pageView = pageView; _shellView = new Game.Views.Shell(new Models.Shell()); _pageView.LayoutRoot.Children.Add(_shellView); } private Storyboard _sbTick = new Storyboard(); public void Initialize() { //setup my game timer _sbTick.Duration = TimeSpan.FromMilliseconds(10); _sbTick.Completed += new EventHandler(_sbTick_Completed); //place the start dialog into the shell _startView = new Views.Start(); //attach to the start event of the view _startView.Begin += new Game.Views.Start.BeginHandler(_startView_Begin); //add the view to the shell _shellView.LayoutRoot.Children.Add(_startView); }
Figure 4 - Initialisation du contrôleur Étudions plus en détail les Figures 3 et 4 . Une déclaration d'objet SL2 chargera par défaut « page.xaml » en tant que point d'entrée par défaut. Nous allons mettre cela à profit pour instancier le contrôleur et utiliser l'injection de dépendance pour transmettre au contrôleur une référence à Page.xaml. L'injection de dépendance est couramment utilisée pour fournir une dépendance externe à une classe ou à un composant. Une fois la référence à ce UserControl toujours au premier plan (« top most »), nous pouvons ajouter le shell en tant qu'enfant à son LayoutRoot (nom attribué par défaut à la principale grille d'un UserControl). Le shell comportera la barre d'état, l'arrière-plan et un conteneur qui servira de scène à chacun des éléments du jeu. La Figure 5 montre la construction de cette vue en langage XAML. <UserControl x:Class="Game.Views.Shell" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="450" Height="450"> <Grid x:Name="LayoutRoot" Background="WhiteSmoke" Cursor="None"> <Grid Width="450" Height="400" x:Name="Container" VerticalAlignment="Top" Background="Transparent"/> <Rectangle Margin="0,0,0,50" Height="30" Width="50" Fill="Black" VerticalAlignment="Bottom" HorizontalAlignment="Center"></Rectangle> <Grid Width="450" Height="50" x:Name="Statusbar" Background="Black" HorizontalAlignment="Center" VerticalAlignment="Bottom"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="60" /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Margin="0,0,0,0" FontSize="20" Text="Score:" TextAlignment="Right" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Center"/> <TextBlock Grid.Column="1" Margin="0,0,15,0" x:Name="tbScore" FontSize="20" Text="0" TextAlignment="Right" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Center"/> </Grid> </Grid> </UserControl>
Figure 5 - Shell Xaml De la même manière que le contrôleur a obtenu une référence à Page.xaml, Views.Shell utilise l'injection de dépendance pour recevoir une référence au modèle approprié (voir Figure 7). Le modèle du shell permet d'actualiser le score du jeu et de déclencher un événement qui provoque la mise à jour du shell. La Figure 6 montre en détail la transmission de ce modèle à la vue, son stockage et son utilisation pour être attaché à l'événement Models.Shell.UpdateScore. public partial class Shell : UserControl { private Models.Shell _model; public Models.Shell Model { get { return _model; } } public Shell(Models.Shell model) { InitializeComponent(); _model = model; _model.UpdateScore += new Game.Models.Shell.UpdateScoreHandler(_model_UpdateScore); } void _model_UpdateScore(object sender, int points) { tbScore.Text = points.ToString(); } }
Figure 6 - Views.Shell.xaml.cs public class Shell { public delegate void UpdateScoreHandler(object sender, int points); public event UpdateScoreHandler UpdateScore; private int _score = 20; public int Score { get { return _score; } set { _score = value; OnUpdateScore(); } } protected void OnUpdateScore() { if (UpdateScore != null) { UpdateScore(this, Score); } } }
Figure 7 - Models.Shell Démarrage du jeuMaintenant que nous savons comment organiser les choses, penchons-nous sur l'interaction de la vue avec le contrôleur. Lorsqu'un utilisateur télécharge le jeu pour la première fois, la première vue qu'il verra sera le Start.xaml. Cette vue contient des instructions et un bouton pour démarrer le jeu. La Figure 4 montre comment nous allons insérer cette vue dans le shell et attacher un gestionnaire à son événement Begin. Perspective : Dans le module 5, nous montrerons comment créer un projet Introduction qui est chargé avant le jeu et qui sert à télécharger de manière asynchrone le plus gros assembly du jeu. Une fois qu'un utilisateur a cliqué sur le bouton Démarrer, le contrôleur se préparera à commencer le jeu en attachant les événements Mouse du shell. Nous utiliserons la souris pour contrôler la direction et la vitesse des obus tirés par le canon. Lorsque le curseur pénètre dans le périmètre du shell, nous commencerons à suivre l'angle produit par le canon et le curseur. L'utilisateur doit cliquer avec le bouton gauche de la souris pour tirer un obus dans cette direction. Pour visualiser l'emplacement visé, nous placerons une image de réticule au même emplacement que le curseur. La vitesse sera calculée en fonction de la distance entre le réticule et le corps du canon. Dans le module 2, nous vous montrerons comment suivre ce mouvement et calculer l'angle de la trajectoire. La Figure 7 montre le code utilisé pour suivre les mouvements de la souris et y répondre. À mesure que nous développerons le jeu, nous étendrons cette méthode StartGame pour placer le joueur, entamer la boucle et lancer le vol des hélicoptères. Public void StartGame() { .... _shellView.MouseEnter += new MouseEventHandler(_shellView_MouseEnter); _shellView.MouseLeave += new MouseEventHandler(_shellView_MouseLeave); _shellView.MouseMove += new MouseEventHandler(_shellView_MouseMove); _shellView.MouseLeftButtonDown += new MouseButtonEventHandler(_shellView_MouseLeftButtonDown); //add the crosshair Crosshair cross = new Crosshair(new Vector(0.0, 0.0)); _crosshair = new Game.Views.Crosshair(cross); _shellView.Container.Children.Add(_crosshair); .... } void _shellView_MouseMove(object sender, MouseEventArgs e) { Point m = e.GetPosition(_shellView.Container); _crosshair.Model.Move(_shellView.Container, m); _player.Model.ChangeAngle(m); } void _shellView_MouseLeave(object sender, MouseEventArgs e) { _shellView.CaptureMouse(); } void _shellView_MouseEnter(object sender, MouseEventArgs e) { _shellView.CaptureMouse(); } void _shellView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { . . . . }
Figure 8 - Suivi du mouvement de la souris Maintenant que nous avons défini cette infrastructure de base, nous pouvons commencer à créer les éléments du jeu. Dans le prochain article, nous nous intéresserons au mouvement et nous déterminerons comment obtenir la chute des parachutistes, le vol des hélicoptères et le tir du canon. Attendez‑vous à quelques leçons de trigonométrie et de physique de base. Rien de très compliqué, mais cela s'avère indispensable pour que le mouvement et les collisions paraissent réels et que le jeu soit agréable à jouer. Pour obtenir la source complète ou commenter cet article, accédez à mon blog à l'adresse suivante :
http://joel.neubeck.net/2008/09/casual-game-m1-expression-newsletter/ Merci et à bientôt
|