Developing a Casual Game with Silverlight 2 – Part 1Author: 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: Getting Started – Architecture / framework
- Module 2: Movement and collision detection
- Module 3: Design – Sprites, boards and dialogs
- Module 4: Animations and sound
- Module 5: Initialization and Deployment
- Module 6: Advanced concepts (Physics, Multiplayer, Optimization)
In this series of articles, we will explore the process of designing and building a casual online game in Silverlight 2 (SL2). It should be no surprise that the popularity of online gaming continues to increase. Our desire to be interactively entertained makes casual games a great outlet for the increasing time we spend on the Internet. Traditionally, Adobe Flash has been the platform of choice for casual games. With the upcoming RTM release of SL2, now is the perfect time to use Silverlight to build your next online game. In this series, we will target the interactive developer and construct our own version of the classic 1980’s game “sabotage”. If you own an iPod, you may have played Apples version called “Parachute”, where you shoot down paratroopers before they land and destroy your bunker. Over the years we have seen numerous version of this classic: Figure 1 shows a few of the ways this games has been visualized over the years. .jpg)
Figure 1 - Game Examples Module 1: Getting Started – Architecture / FrameworkIn this first module we will conceptualize our game, and build the architectural framework that will serve as the foundation for our project. All of our code will be written in Microsoft.NET C#, and will leverage the following tools and frameworks. - Visual Studio 2008
- Silverlight 2 Beta 2 SDK and Tools for VS2008
- Microsoft.Net Framework 3.5
- Microsoft Expression Blend 2.5 June 2008 Preview
- Microsoft Expression Design 2
ConceptThis game has a simple premise: get as many points as possible by shooting down parachuters and helicopters before the enemy destroys your bunker. Requirements- The user must aim their turret using their mouse. Clicking the right button will fire a bullet.
- The distance between the crosshair of the gun and the turret will determine the initial velocity of the bullet.
- Each bullet will decelerate over the distance of the shot.
- Shooting the parachute will cause the parachuter to fall.
- Shooting the parachuter will destroy the enemy.
- Shooting the helicopter will destroy the helicopter creating debris, which might destroy other parachuters.
- Each object destroyed equals 2 points. Each bullet shot deducts 1 point from your score.
FrameworkLast fall Microsoft released its first version of the Model-View-Controller (MVC) framework for ASP.NET. The goal was to help developers implement this proven separation pattern, as a way to divide an application’s implementation into three components: models, views, and controllers. This “separation” improves a developer’s ability to decouple and isolate the visualization of elements, from any necessary business logic. In Silverlight development, MVC is an excellent way to support role specific tools such as Visual Studio for the developers and Expression Blend for designers and animators. Using MVC in a game ensures a foundation that isolates a developer’s ability to control state (position, movement and collisions) while allowing designers full control over how game elements are rendered. Figure 2 illustrates the relationship between each component in MVC. The solid lines represent direct associations where as dashed lines represent indirect associations. .jpg)
Figure 2 - MVC Relationships MVC in Silverlight 2ViewsEach visual element of our game represents a View. This will include our shell that contains our stage, dialogs used for loading, beginning and ending our game, and each game element such as the paratrooper, helicopter, turret and bullet. ModelsTo achieve true MVC separation we must make our models completely independent of our views and controllers. No model should hold instance variables to either of these components. There are two methodologies used when designing a model. Passive Model - In this implementation a view is notified of a change by the controller, only once the controller has updated the appropriate model. Active Model. In this implementation each model notify the appropriate view of a change by using an observer pattern. In .NET the easiest implementation of an observer pattern is through the use of delegates and events. In game development, this technique works quite well by allowing each view to subscribe to events fired by a model, when there is a change in state (new position or collision). ControllerThe last and final type of component is the controller. We will use a single controller that acts as the games central nervous system. The controller will be responsible for maintaining the primary game loop used to move sprites and monitor collisions. The controller is the only component that will insert a visual element into our game. Figure 3 and 4 shows how the controller is constructed and initialized by our page.xaml. Once the controller is constructed, we can call the controllers Initialize method to pass all further commands to this component. Within this “Initialize” method we will setup our timer and load our starting dialog. Much discussion has centered on the use of either a DispaterTimer or Storyboard, and which is more efficient. Contrary to what one might expect, an empty storyboard with a short duration, is the better choice. It will perform more efficiently and consistently. 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 from 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 - Controller initialization Let’s look at Figure 3 and 4 in more detail. By default, a SL2 object declaration will load “page.xaml” as its default entry point. We will leverage this as a way to instantiate our Controller, and use dependency injection to pass the controller a reference to Page.xaml. Dependency injection is a common way to supply an external dependency to a class or component. Once we have a reference to this top most UserControl, we can add our Shell as a child to its LayoutRoot (the default name given to a UserControls primary Grid). Our shell will contain our status bar, background and a container that will server as the stage for each of our game elements. Figure 5 is the XAML used to construct this view. <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 Similar to how our controller acquired reference to Page.xaml, our Views.Shell uses dependency injection to receive a reference to its appropriate model (Figure 7). The Shell’s model is used to maintain the games score, and fire an event that trigger the shell to update. Figure 6 details how this model is passed to the view, stored and used to attach to the Models.Shell.UpdateScore event. 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 Starting the GameNow that we have an idea of how to get things organized, lets take a look at how our view interacts with the controller. When a user first downloads the game the initial view they will see is our Start.xaml. This view contains some instructions and a button that begins the game. Figure 4 illustrates how we will insert this view into our shell, and attach a handler to its Begin event. Look Ahead: In module 5 we will demonstrate how to create an Introduction project that gets loaded before the game, and is responsible for asynchronously downloading the larger game assembly. Once a user has clicked the “Start” button, our controller will prepare to begin play by attaching to the Shell’s Mouse events. We will use the mouse to control the direction and velocity of bullets fired from our gun. When the cursor enters the perimeter of our Shell, we will begin to track the angle produced be the gun and the cursor. Clicking the left mouse button will fire a bullet in that direction. To visualize the location we are aiming, we will place an image of a crosshair at the same location as the cursor. Velocity will be calculated based on the distance from the crosshair to the gun barrel. In Module 2, we will show how to track this movement and calculate the angle of trajectory. Figure 7 is the code used to track and respond to mouse movements. As we continue our game development, we will expand this StartGame method to place our player, begin our loop and start flying our helicopters. 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 - Tracking Mouse movement Now that we have this essential framework defined, its time to start building our game elements! In the next article, we will focus on movement and determine how to get our parachuters to fall, helicopters to fly and gun to shot. Prepare yourself for a small dose of trigonometry and some basic physics. Nothing to complicated, but enough to ensure our movement and collisions are realistic, and game is fun to play. For complete source or to make a comment on this article, drop by my blog at: http://joel.neubeck.net/2008/09/casual-game-m1-expression-newsletter/ Thank you and see you next time. |