Developing a Casual Game with Silverlight 2 – Part 3Author: Joel Neubeck – Director of Technology / Silverlight MVP Blog: http://joel.neubeck.net/ Expression Newsletter, subscribe now to get yours. In this series of articles, we are exploring the process of designing and building a casual online game in Silverlight 2 (SL2). Throughout the series we target the interactive developer, as we construct our own version of the classic 1980s game Sabotage. The premise of this game is quite simple: get as many points as possible by shooting down parachuters and helicopters before the enemy destroys your bunker. Module 3: Design – Sprites, boards and dialogsIn our first two modules we built the architectural framework that will serve as the foundation for our game, and then started moving objects around the screen. In this third article, we will begin to illustrate those objects and design the boards and dialogs we will use throughout the game. Now please keep in mind that I am a developer, and as hard as I try, I am not a designer. Please forgive the simplicity of my game elements. I hope that when the game is completed, one of my talented illustrator friends can lend me some real design expertise. Game BoardsThose that have played the original Sabotage will remember that the entire game consists of a single board. This game has no concept of levels or screens, so the entire experience will take place on a single screen. Back in article 1 we created our primary view called “Shell”; this is the UserControl which will contain each of our game elements. Our shell is comprised of two grids. The first we will call “Container” and the second “Menu.” The container will define a location for which we can add our helicopters, paratroopers, and bullets. When the game is finished, we will simply clear this container’s children to remove everything from our stage. Our menu grid will sit below our container, and will contain our ToggleButton used to mute sound, and our TextBlocks used to display the player’s score. Any other elements that we would like to add to our main game screen will be added to this shell’s Layout root. These elements will include such things as the ground, gun, a background image and any of our dialogs. Figures 1 & 2 illustrate the XAML used to create our shell. Take note of the use of columns in our menu grid. This allows us to have the mute button left-aligned, while the score is right-aligned. Think of a grid with explicit column or row definitions just as you would an HTML table. .png)
Figure 1. Shell View <UserControl x:Class="Game.Views.Shell" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x:="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" Width="400" Height="400" Cursor="None"> <Grid x:Name="LayoutRoot" Background="Black"> <Grid.Resources> <Grid Width="400" Height="350" x:Name="Container" VerticalAlignment="Top" Background="Azure"/> <Grid Background="Transparent" x:Name="Menu" Height="50" VerticalAlignment="Bottom"> <Grid.ColumnDefinitions> <ColumnDefinition Width="70" /> <ColumnDefinition Width="*"/> <ColumnDefinition Width="60" /> </Grid.ColumnDefinitions> <ToggleButton Grid.Columns="0" Height="18" HorizontalAlignment="Left" IsChecked="True" Margin="10,0,0,0" x:Name="btnSpeaker" Template="(StaticResource speakerControlTemplate)"/>
<TextBlock Grid.Column="1" Margin="0,0,0,0" FontSize="20" Text="Score:" TextAlignment="Right" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Center"/> <TextBlock Grid.Column="2" Margin="0,0,15,0" x:Name="tbScore" FontSize="20" Text="100" TextAlignment="Right" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Center"/> </Grid> </Grid> </UserControl> Figure 2. Shell XAML DialogsWhen a user first loads our game, we will start with a dialog that gives a user three choices: start a new game, view the top scores, or learn how to play the game. Our dialog is nothing more than a simple UserControl we have defined and added to our Views namespace. When page.xaml calls our controller’s “initialize” method , this dialog will be placed on top of our main game shell. If a user clicks “Start New Game,” then our dialog will capture the button’s Click event and bubble this up to our controller, which will hide the dialog and begin game play. Figure 3 is an excerpt of the code I used to insert the start dialog (Figure 4) into my shell and how the controller will react when the start button is pressed. This same approach will be used when I insert the top scores and help dialog. Look Ahead: In Module 6, we will demonstrate how we leverage isolated storage to keep track of a player’s 10 top scores and display these scores in the high scores dialog.
public void Initialize() { //place the start dialog in 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); } void _startView_Begin(Game.Views.Start dialog) { CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); //we will use this timer to place our helicopters _timerInsertSpheres.Interval = TimeSpan.FromSeconds(5); _timerInsertSpheres.Tick += new EventHandler(_timerInsertHelicopter_Tick); _shellView.LayoutRoot.Children.Remove(dialog); _startView = null; StartGame(); } Figure 3. Controller inserts start dialog .png)
Figure 4. Start Dialog Recycling SpritesOne of the things that all game developers need to be cautious of is the number of sprites being stored on the display stack. One way to mitigate the potential issues that can arise from a large number of instances and the time it takes to instantiate each is to recycle. In this game we will recycle both our bullets and troopers (No need to do helicopters since there is only one on stage at a time). Each time that our gun fires a bullet, we check a private collection containing each bullet that has been previously instantiated and added to our Shell’s Container grid. If any of the bullet models in this collection are set to “Moving=False,” then it can be recycled by setting its new starting position, direction, and velocity. If for some reason there are no non-moving bullets, then we can declare one, and add it to our display stack. Using this technique we ensure that only the very minimum number of bullets is ever declared, improving the efficiency of our game and keeping our collections to a minimum. This identical technique is used when inserting paratroopers. Figure 5 is the code we use to recycle bullets. void _shellView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Views.Bullet bullet = null;
//we have to add 90 deg to our angle since we are shooting upwards double angle = _player.Model.Angle+90;
//as the barrel rotates it creates a radius of 30 pixels minus //the radius of the bullet (3) double radius = 27;
double sin = Math.Sin(Helper.ConvertToRadians(angle)); double cosine = Math.Cos(Helper.ConvertToRadians(angle));
//determin the exit point of the bullet from the torret double exitX = (_player.Model.Midpoint.X-3) + sin * radius; double exitY = (_player.Model.Midpoint.Y-3) + cosine * radius; Vector exitV = new Vector(exitX, exitY);
//calculate the direction the bullet will be traveling. Vector directionV = new Vector(sin, cosine);
if(_debug) _tb.Text = _tb.Text + string.Format(" - vX: {0} vY: {1}", exitV.X.ToString("0.0##"), exitV.Y.ToString("0.0##"));
//lets recycle bullets that have been used and are now inactive foreach(Views.Bullet b in _bullets) { if (!b.Model.Moving) { bullet = b; bullet.Model.Position = exitV; bullet.Model.Velocity = Vector.Multiply(directionV, _player.Model.Firepower); //very important since we are recycling bullets Models.Collision.BoundingSphere bounds = bullet.Model.Boundaries[0] as Models.Collision.BoundingSphere; bounds.Center = exitV + new Vector(bounds.Radius, bounds.Radius); break; } } //if we could not find any inactive bullets we have to add one to the display stack if (bullet == null) { Bullet b = new Models.Bullet(exitV, directionV, _player.Model.Firepower); bullet = new Game.Views.Bullet(b); b.ExlodeCollision += new CollisionHandler(bullet_ExlodeCollision); _shellView.Container.Children.Add(bullet); _bullets.Add(bullet); }
bullet.Visibility = Visibility.Visible; bullet.Model.Moving = true;
//firing a bullet subtracts 1 point from your score _shellView.Model.Score--; //get the bullet moving bullet.Model.Move(_shellView.Container); TrackModel(bullet.Model, ref _gridMoving);
} Figure 5. Bullet Recycling Sprites # 1 - HelicopterThe first sprite we had to create for this game was the helicopter. Now, since my artistic skills are quite limited, I decided to cheat a bit and find an existing helicopter illustration and trace the drawing with the Pen tool in Expression Design. If you are not familiar with Expression Design, it’s a lot like Adobe Illustrator and is perfect when you want to create simple vector illustrations. The technique I used was quite simple; I placed my helicopter image into design, created a new layer, and started tracing the image with the Pen tool. I made sure that each element that I wanted to color separately was an individual path, paying special attention to any elements that will need to be animated in the future. Figures 6 and 7 show the original and my end result. .gif)
Figure 6. Original Helicopter Image .png)
Figure 7. Helicopter drawing Once we are happy with the drawing, we can give it some color and export the XAML to Blend as the Helicopter.Xaml UserControl. In Module 4 we will show you how we begin to animate our sprites. In the case of the helicopter we will make the rotors move, giving the effect of the helicopter flying across the screen. Using a combination of Expression Design and Blend, we were able to create each of the additional game elements with relative ease. In the next module we will focus on animating each of our sprites. In some cases we can simply loop a storyboard to achieve the appropriate effect (Helicopter rear rotor) while others will require some additional illustrations to construct a frame-by-frame animation. Thank you and see you next time. For complete source or to make a comment on this article, drop by my blog at: http://joel.neubeck.net/2009/01/casual-game-m3-expression-newsletter/ Joel Neubeck, Director of Technology, Terralever |