Глава 3. Разработка гаджета рабочего стола с использованием технологии Silverlight

3.1 Создание Silverlight приложения для гаджета «Shooter»

Гаджет (Sidebar Gadget) – название класса вспомогательных мини-программ – графических модулей, которые размещаются в рабочем пространстве родительской программы и служат для украшения рабочего пространства, развлечения, решения отдельных рабочих задач или быстрого получения информации из интернета без помощи Web-браузера.

Для создания гаджета необходимо установить программное обеспечение:

ü Microsoft Visual Studio 2010

ü Silverlight4 Tools

ü Silverlight4 SDK

После установки Microsoft Visual Studio, а также всех необходимых компонент можно создавать Silverlight-приложение.

1. Выбираем «Создать проект»

2. Откроется диалоговое окно Создания проекта. Следует убедиться, что в верхнем правом углу диалога выбран пункт .NET Framework4, и в списке Типы проекта выбираем Приложение Silverlight.

3. После нажатия кнопки «OK» Visual Studio запустит Мастер нового приложения Silverlight. Этот мастер предлагает несколько вариантов создания и управления Silverlight-приложением. Все Silverlight-приложения создаются как пользовательские элементы управления, экземпляры которых затем могут быть созданы и размещены на странице. Принимаем настройки по умолчанию.
Мастер создаст новое решение Visual Studio, содержащее элемент управления Silverlight.

Теперь можно приступать непосредственно к разработке.

Общая идея игры

Создаем игровое поле и размещаем на нем спрайты – класс, содержащий базовые объекты игры: корабль, пришельцы, снаряды. Для каждого из них нам надо знать его координаты и как он выглядит. Координаты мы будем отслеживать с помощью Point, а свойство типа Canvas будем использовать для отображения XAML каждого объекта. Управлять движением спрайтов будем с помощью класса Vector. Начать надо с выбора клавиш, которыми будет управляться движение. Затем нам надо будет отслеживать их нажатие и выполнять соответствующие действия. В каждом цикле надо отслеживать, не столкнулась ли та или иная ракета с одним из пришельцев и не вылетела ли за пределы поля, не коснулась ли бомба нашего корабля и не исчезла ли с экрана, а также нет ли нового выстрела пришельцев по нашему кораблю.

Все это будет в теле цикла игры. Для каждой коллекции мы вызываем методы, просматривающие ее содержимое и выполняющие действия в зависимости от координат и столкновений спрайтов. После выполнения цикла каждой основной коллекции, мы проходим по соответствующей ей коллекции с удаленными элементами, удаляем ненужные изображения с экрана и из основной коллекции. В конце проверяем, не настало ли время пришельцам сбросить очередные бомбы.

Более подробное описание и программный код игры представлены в приложении [17].

Задачей является создание виджета, поэтому в папке, где хранится приложение открываем: Bin\Debug и содержимое этой папки добавляем в ZIP архив. У получившегося архива меняем расширение с .zip на .gadget. Гаджет готов. Выполняем его установку на компьютер и размещаем на рабочий стол.


Заключение

Silverlight является следующим шагом в направлении к улучшению взаимодествия с пользователем через веб. Цель Silverlight - обеспечить в веб-приложениях точность отображения и качество пользовательского интерфейса доступное в настольных приложениях, позволяя веб-разработчикам и дизайнерам реализовывать в создаваемых решениях особые требования заказчиков. Silverlight заполняет технологический вакуум между дизайнерами и разработчиками, предоставляя им общий формат для работы. Этот формат обрабатывается браузером и основывается на XML, что упрощает создание шаблонов и автоматическое формирование представления. Таким форматом является XAML - Extensible Application Markup Language (расширяемый язык разметки приложений).

Microsoft Silverlight - это подключаемый модуль, который может использоваться в разных браузерах и на разных платформах, разработанный для обеспечения возможности создания сложных сценариев воспроизведения мультимедиа и создания насыщенных интерактивных интернет-приложений с использованием современных технологий.

В ходе исследования изучена литература описывающая технологию Silverlight. Большинство литературы представлено на английском языке, а именно Silverlight 4: Problem – Design – Solution, автор Nick Lecrenski, Microsoft Silverlight 4 Data and Services Cookbook от Gill Cleeren, Kevin Dockx. На русском языке представлена переведенная книга Лоуренса Морони - Введение в Microsoft® Silverlight™ 3. Найдено несколько блогов, российских популяризаторов Silverlight, в которых описываются новые введения в данной технологии. Особенно интересным является блог Михаила Черномордикова.

В исследовании выявлено, что гаджет рабочего стола - это небольшие инструменты (программы), выполняющие одну определенную функцию и требующие для своей работы специальной среды — виджет-движка (widget engine). Они могут показывать на рабочем столе вашего компьютера последние новости, слайд-шоу из фотографий, позволять делать заметки на виртуальных стикерах, вести учёт рабочего времени и многое другое.

В исследовании были изучены особенности строения гаджета рабочего стола операционной системы Windows 7. Выявлено, что простейший гаджет это zip архив с расширением .gadget состоящий из двух файлов: файл манифест, который включает в себя все настройки и информацию о гаджете, описанную на языке XML, и файл в котором описывается внешний вид гаджета, его основная функциональность, а так же поведение при определенных действиях пользователя. Дополнительно у гаджета могут быть файл настроек, файлы картинок и др.

В ходе работы был создан гаджет рабочего стола «shooter». Особое внимание уделялось применению технологии Silverlight для создания гаджета рабочего стола, рассмотрен способ создания, приведен код получившегося приложения, таким образом цель исследования была достигнута.


 

 

Список литературы

1. Байдачный С.С. Silverlight 4: Создание насыщенных Web-приложений – М.: СОЛОН-ПРЕСС, 2010.

2. Блог Михаила Черномордикова [Электронный ресурс]. - Режим доступа: http://blogs.msdn.com/b/mikcher/

3. Журнал MSDN Magazine [Электронный ресурс]. - Режим доступа: http://msdn.microsoft.com/ru-ru/magazine/cc163370.aspx

4. Крис Андерсон , Основы Windows Presentation Foundation. Пер. с англ. А. Слинкина — М.: ДМКПресс, 2008.

5. Леонтьев В.П. Новейший самоучитель. Компьютер + интернет 2013. –М.: ОЛМА Медиа Групп, 2013.

6. Лоуренс Морони. Введение в Microsoft Silverlight 3. — 2-е изд. — R.: Microsoft Press, 2009.

7. Мэтью Мак-Дональд. Silverlight 3 с примерами на C# для профессионалов = Pro Silverlight 3 in C#. — 3-е изд. — М.: Вильямс, 2010.

8. Настройки гаджетов [Электронный ресурс]. - Режим доступа: http://volginartem.wordpress.com/2011/02/14

9. Ник Рендольф, Дэвид Гарднер, Майкл Минутилло, Крис Андерсон Visual Studio 2010 для профессионалов — М.: «Диалектика», 2011.

10. Раздел «Microsoft Visual Studio» на сайте Майкрософт [Электронный ресурс]. - Режим доступа: http://msdn.microsoft.com/ru-ru/vstudio/default.aspx

11. Разрабатываем свой Sidebar gadget [Электронный ресурс]. - Режим доступа: http://habrahabr.ru/post/71958/

12. Свободная энциклопедия [Электронный ресурс]. - Режим доступа: http://ru.wikipedia.org/wiki/Adobe_Flash

13. Свободная энциклопедия [Электронный ресурс]. - Режим доступа: http://ru.wikipedia.org/wiki/HTML5

14. Свободная энциклопедия [Электронный ресурс]. - Режим доступа: http://ru.wikipedia.org/wiki/Silverlight

15. Свободная энциклопедия [Электронный ресурс]. - Режим доступа: http://ru.wikipedia.org/wiki/Виджет

16. Создание гаджетов для Windows Sidebar [Электронный ресурс]. - Режим доступа: http://designformasters.info/posts/windows-sidebar-gadget/

17. Создание простой игры на Silverlight [Электронный ресурс]. - Режим доступа: http://blogs.msdn.com/b/rucoding4fun/archive/2009/07/30/e-silverlight.aspx

 


 

Приложение

Компоновка экрана. Добавим полотно на сетку внутри Page.xaml. Его фон сделаем черным и назовем gameRoot.

<UserControl x:Class="SimpleShooter.Page"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Width="400" Height="300">

<Grid x:Name="LayoutRoot">

<Canvas x:Name="gameRoot" Width="500" Height="400" Background="Black">

</Canvas>

</Grid>

</UserControl>

Затем добавим элементы управления для объектов игры и отображения данных. Также добавим в проект пользовательские элементы управления Info, LivesRemaining, Score и WaveInfo. Они включают текстовые блоки для вывода данных о состоянии игры. Мы же оставили простое полотно с непосредственным указанием координат Canvas.Top и Canvas.Left. Свои элементы управления поместили в Page.xaml, задав для каждого x:Name. К этому моменту каждый элемент управления содержал лишь TextBlock.

<UserControl x:Class="SimpleShooter.Page"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:SimpleShooter="clr-namespace:SimpleShooter"

Width="500" Height="400">

<Grid x:Name="LayoutRoot">

<Canvas x:Name="gameRoot" Width="500" Height="400" Background="Black">

<SimpleShooter:RemainingLives x:Name="ctlLives" Canvas.Top="380" Canvas.Left="10" />

<SimpleShooter:Score x:Name="ctlScore" Canvas.Top="10" Canvas.Left="10" />

<SimpleShooter:WaveInfo x:Name="ctlWaveInfo" Canvas.Left="440" Canvas.Top="10" />

<SimpleShooter:Info x:Name="ctlInfo" Canvas.Top="10"/>

</Canvas>

</Grid>

</UserControl>

 

Добавим к нашему экрану звездное поле. Для этого напишем функцию генерации случайных чисел, а также другую, которая будет случайным образом располагать эллипсы на нашем базовом полотне. Нам потребуется наследование от System.Security.Cryptography:

public partial class Page : UserControl

{

public Page()

{

InitializeComponent();

GenerateStarField(350);

}

void GenerateStarField(int numberOfStars)

{

for (int i = 0; i < numberOfStars; i++)

{

Ellipse star = new Ellipse();

double size = GetRandInt(10, 800) * .01;

star.Width = size;

star.Height = size;

star.Opacity = GetRandInt(1, 5) * .1;

star.Fill = new SolidColorBrush(Colors.White);

int x = GetRandInt(0, (int)Math.Round(gameRoot.Height, 0));

int y = GetRandInt(0, (int)Math.Round(gameRoot.Width, 0));

star.SetValue(Canvas.TopProperty, (double)x);

star.SetValue(Canvas.LeftProperty, (double)y);

gameRoot.Children.Add(star);

}

}

 

public int GetRandInt(int min, int max)

{

Byte[] rndBytes = new Byte[10];

RNGCryptoServiceProvider rndC = new RNGCryptoServiceProvider();

rndC.GetBytes(rndBytes);

int seed = BitConverter.ToInt32(rndBytes, 0);

Random rand = new Random(seed);

return rand.Next(min, max);

}

}

 

Теперь у нас есть функция GenerateStarField. Обратите внимание на то, что каждый эллипс добавляется к коллекции Children нашего базового полотна gameRoot, а также на способ задания координат этих эллипсов посредством свойств Top и Left. Теперь у нас есть фон и базовая структура экрана нашей игры.

Далее мы добавим в нашу программу классы, представляющие спрайты и векторы:

public abstract class Sprite

{

public double Width { get; set; }

public double Height { get; set; }

public Vector Velocity { get; set; }

public Canvas SpriteCanvas { get; set; }

private Point _position;

public Point Position

{

get

{

return _position;

}

set

{

_position = value;

SpriteCanvas.SetValue(Canvas.TopProperty, _position.Y - (Height / 2));

SpriteCanvas.SetValue(Canvas.LeftProperty, _position.X - (Width / 2));

}

}

 

public Sprite(Double width, Double height, Point position)

{

Width = width;

Height = height;

 

SpriteCanvas = RenderSpriteCanvas();

 

SpriteCanvas.Width = width;

SpriteCanvas.Height = height;

// Примечание: поскольку в установщике для Position используются и Height, и Width, важно, что это следует после их установки.


 

Position = position;

}

public abstract Canvas RenderSpriteCanvas();

public Canvas LoadSpriteCanvas(string xamlPath)

{

System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream(xamlPath);

return (Canvas)XamlReader.Load(new System.IO.StreamReader(s).ReadToEnd());

}

public virtual void Update(TimeSpan elapsedTime)

{

Position = (Position + Velocity * elapsedTime.TotalSeconds);

}

}

Класс Sprite будет содержать такие базовые объекты игры, как корабль, пришельцы и снаряды. Для каждого из них нам надо знать местонахождение и как он выглядит. Координаты мы будем отслеживать с помощью Point, а свойство типа Canvas будем использовать для отображения XAML каждого объекта. Начальные параметры этих свойств задаются в конструкторе, а в каждом производном классе будет реализован метод RenderSpriteCanvas. Этот метод позволит производному классу устанавливать содержимое полотна, управляя внешним видом спрайта. У нас есть также класс, описывающий векторы, с помощью которого мы будем управлять движением спрайтов.

public struct Vector

{

public double X;

public double Y;

public Vector(double x, double y)

{

X = x;

Y = y;

}

public double Length

{

get

{

return Math.Sqrt(LengthSquared);

}

}

 

public double LengthSquared

{

get

{

return X * X + Y * Y;

}

}

public void Normalize()

{

double length = Length;

X /= length;

Y /= length;

}

public static Vector operator -(Vector vector)

{

return new Vector(-vector.X, -vector.Y);

}

public static Vector operator *(Vector vector, double scalar)

{

return new Vector(scalar * vector.X, scalar * vector.Y);

}

public static Point operator +(Point point, Vector vector)

{

return new Point(point.X + vector.X, point.Y + vector.Y);

}

static public Vector CreateVectorFromAngle(double angleInDegrees, double length)

{

double x = Math.Sin(DegreesToRadians(180 - angleInDegrees)) * length;

double y = Math.Cos(DegreesToRadians(180 - angleInDegrees)) * length;

return new Vector(x, y);

}

static public double DegreesToRadians(double degrees)

{

double radians = ((degrees / 360) * 2 * Math.PI);

return radians;

}

}

Теперь можем реализовать конкретный спрайт с помощью класса Ship. Добавьте в свой проект класс с именем Ship и файл Ship.xaml. Не забудьте установить свойства Ship.xaml в «Embedded Resource». Нам потребуется наследование от класса Sprite:

public class Ship : Sprite

{

public Ship(double width, double height, Point firstPosition)

: base(width, height, firstPosition)

{

}

public override Canvas RenderSpriteCanvas()

{

return LoadSpriteCanvas("SimpleShooter.Sprites.Ship.xaml");

}

}

При создании экземпляра Ship вызывается конструктор его базового класса, Sprite. В классе Ship также реализован метод RenderSpriteCanvas и определен XAML (просто белый квадрат) для загрузки в полотно данного спрайта. Теперь мы можем добавить спрайт к нашей главной странице. В этой несложной игре у нас есть всего один корабль (остальные — пришельцы), так что добавим к странице свойство и функцию, с помощью которой будет создаваться экземпляр корабля:

<Canvas x:Name="LayoutRoot" Width="30" Height="30"

xmlns="http://schemas.microsoft.com/client/2007"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >

<Rectangle Height="30" Width="30" Fill="White" />

</Canvas>

 

void InitializeGame()

{

PlayerShip = new Ship(10, 10, new Point(100, 300));

gameRoot.Children.Add(PlayerShip.SpriteCanvas);

}

Этот метод мы можем вызывать из конструктора страницы, и при запуске проекта в левой нижней части страницы будет появляться белый квадрат, представляющий наш корабль. Если посмотреть внимательно, можно заметить, что размер страницы равен 500×400, а метод InitializeGame располагает корабль на расстоянии 100 пикселов от левой границы полотна gameRoot и на 300 пикселов от его верхней границы.

Настало время привести некоторые предметы в движение. Начать надо с выбора клавиш, которыми будет управляться движение. Затем нам надо будет отслеживать их нажатие и выполнять соответствующие действия. Обработчик клавиатуры улавливает все события нажатия и отпускания клавиш. Это позволяет нам узнать о нажатии некоторой клавиши в любой момент. Цикл игры — это просто непрерывный цикл. В него входит раскадровка, которая запускается и тут же останавливается. Класс генерирует событие и раскадровка запускается снова. Подписчики события Update предоставляют значение, указывающее интервал времени в миллисекундах с момента последнего обновления. Это значение может использоваться в векторах для реализации плавного движения спрайтов. Нам надо добавить в класс Page экземпляры KeyHandler и GameLoop. Изменим конструкторы InitializeGame и Page, а также добавим обработчик для GameLoop:

public Page()

{

InitializeComponent();

keyHandler = new KeyHandler(this);

GenerateStarField(350);

InitializeGame();

}

 

void InitializeGame()

{

gameLoop = new GameLoop(this);

gameLoop.Update += new GameLoop.UpdateHandler(gameLoop_Update);

 

PlayerShip = new Ship(10, 10, new Point(100, 360));

gameRoot.Children.Add(PlayerShip.SpriteCanvas);

 

gameLoop.Start();

}

 

void gameLoop_Update(TimeSpan elapsed)

{

// Очистим текущий вектор, чтобы спрайт не двигался, когда не нажаты те или иные клавиши

PlayerShip.Velocity = new Vector(0, 0);

if (keyHandler.IsKeyPressed(Key.Left))

{

PlayerShip.Velocity = Vector.CreateVectorFromAngle(270, 125);

}

if (keyHandler.IsKeyPressed(Key.Right))

{

PlayerShip.Velocity = Vector.CreateVectorFromAngle(90, 125);

}

PlayerShip.Update(elapsed);

}

Теперь у нас есть работающий цикл игры. Если запустить приложение и щелкнуть элемент управления Silverlight, чтобы он получил фокус, то те­перь мы сможем управлять кораблем кнопками со стрелками. Есть, правда, одна проблема — мы можете совсем сместить корабль за пределы экрана. Чтобы исправить ситуацию, добавим в класс, описывающий корабль, свой­ства MinX и MaxX и переопределим метод Update, который он наследует от Sprite. Надо также прибавить эти свойства для минимального и максимального значений в методе InitializeGame класса Page после создания экземпляра Ship:

public class Ship : Sprite

{

public double MaxX { get; set; }

public double MinX { get; set; }

 

public Ship(double width, double height, Point firstPosition)

: base(width, height, firstPosition)

{

 

}

 

public override Canvas RenderSpriteCanvas()

{

return LoadSpriteCanvas("SimpleShooter.Sprites.Ship.xaml");

}

 

public override void Update(System.TimeSpan elapsedTime)

{

// Проверить, что к этой точке можно переместиться

if (Position.X > MaxX)

{

Position = new Point(MaxX, Position.Y);

Velocity = new Vector(0, 0);

}

if (Position.X < MinX)

{

Position = new Point(MinX, Position.Y);

Velocity = new Vector(0, 0);

}

base.Update(elapsedTime);

}

}

У нас есть все необходимое для добавления других спрайтов, таких как пришельцы и снаряды. Сначала добавим производные от Sprite классы: Alien, Missle и Bomb, а также Alien.xaml, Missle.xaml и Bomb.xaml (для всех этих .xaml-файлов должен быть установлен параметр Embedded Resources). Эти xaml-файлы аналогичны Ship.xaml и отличаются лишь размерами и цветом. Сделаем захватчиков (Aliens) и их бомбы красными, уменьшив длину и ширину бомб и ракет до пяти. Файлы xaml очень похожи, а классы имеют несколько особенностей. Классы Bomb и Missile отличаются лишь загружаемыми xaml. Вот класс Bomb:

public class Bomb : Sprite

{

public double MaxX { get; set; }

public double MinX { get; set; }

 

public Bomb(double width, double height, Point firstPosition)

: base(width, height, firstPosition)

{

 

}

 

public override Canvas RenderSpriteCanvas()

{

return LoadSpriteCanvas("SimpleShooter.Sprites.Bomb.xaml");

}

 

public override void Update(System.TimeSpan elapsedTime)

{

base.Update(elapsedTime);

}

}

Добавим класс Alien (пришелец), аналогичный классу Ship. Оба эти класса требуют дополнительных возможностей. Прежде всего, они должны уметь стрелять один в другого. Alien будет сбрасывать бомбы:

public class Alien : Sprite

{

public double fireRateMilliseconds = 2000;

public double fireVelocity = 250;

public double wayPointMin;

public double wayPointMax;

public double speed = 100;

public bool spawnWait;

public DateTime spawnComplete;

public double MaxX { get; set; }

public double MinX { get; set; }

 

public Alien(double width, double height, Point firstPosition)

: base(width, height, firstPosition)

{

 

}

 

public void CheckDirection()

{

if (Position.X > wayPointMax)

{

Velocity = Vector.CreateVectorFromAngle(270, speed);

}

if (Position.X < wayPointMin)

{

Velocity = Vector.CreateVectorFromAngle(90, speed);

}

}

 

public override Canvas RenderSpriteCanvas()

{

return LoadSpriteCanvas("SimpleShooter.Sprites.Alien.xaml");

}

public override void Update(TimeSpan elapsedTime)

{

CheckDirection();

base.Update(elapsedTime);

}

 

public Bomb Fire()

{

Bomb bomb = new Bomb(5, 5, Position);

bomb.Velocity = Vector.CreateVectorFromAngle(180, fireVelocity);

return bomb;

}

}

Нам необходим механизм отслеживания столкновений снарядов с другими спрайтами. Добавим в класс спрайта метод обнаружения столкновений. Будем использовать зону, определяемую радиусом CollisionRadius, измеряемым от центра спрайта. Для простоты будем считать радиус равным половине ширины спрайта. С помощью вектора, созданного из этих двух точек, мы сможем определять, не превышает ли сумма этих двух радиусов длины нашего вектора. Если это так, значит было столкновение:

public static bool Collides(Sprite s1, Sprite s2)

{

Vector v = new Vector((s1.Position.X) - (s2.Position.X), (s1.Position.Y) - (s2.Position.Y));

if (s1.CollisionRadius + s2.CollisionRadius > v.Length)

{

return true;

}

else

{

return false;

}

}

Теперь мы можем расширить наш цикл игры возможностями, превосходящими отслеживание движения нашего корабля. Прежде всего, добавим обработку дополнительных клавиш, не только стрелок. Для стрельбы будет использоваться клавиша пробела. Добавим перечисление, описывающее состояния игры. Также добавим класс, реализующий стрельбу. Он будет достаточно простым, с большим потенциалом усовершенствования:

public enum GameState

{

Ready = 0,

Running = 1,

Paused = 2,

BetweenWaves = 3,

GameOver = 4

}

 

if (keyHandler.IsKeyPressed(Key.Space))

{

switch (status)

{

case GameState.Ready:

break;

case GameState.Running:

EntityFired(PlayerShip);

break;

case GameState.Paused:

break;

case GameState.BetweenWaves:

status = GameState.Running;

ctlInfo.GameInfo = "";

StartWave();

break;

case GameState.GameOver:

break;

default:

break;

}

}

 

void EntityFired(Sprite shooter)

{

Debug.WriteLine(shooter);

switch (shooter.ToString())

{

case "SimpleShooter.Ship":

if (missles.Count == 0)

{

Missle missle = ((Ship)shooter).Fire();

missles.Add(missle);

gameRoot.Children.Add(missle.SpriteCanvas);

 

}

break;

case "SimpleShooter.Alien":

Bomb bomb = ((Alien)shooter).Fire();

bombs.Add(bomb);

gameRoot.Children.Add(bomb.SpriteCanvas);

break;

default:

break;

}

}

Чтобы состояние игры было проще отслеживать, добавим к нашим элементам управления открытые свойства. Например, добавим свойство Lives в элемент управления ReamainingLives, причем установщик этого свойства будет обновлять TextBlock этого элемента управления, чтобы пользователь видел, сколько осталось жизней. Аналогично поступим с остальными тремя элементами управления:

public partial class RemainingLives : UserControl

{

private int _lives;

public int Lives

{

get { return _lives; }

set

{

_lives = value;

string livesString = string.Empty;

for (int i = 0; i < _lives - 1; i++)

{

livesString = string.Format("{0}{1}", livesString, "A");

}

txtRemainingLives.Text = livesString;

}

}

 

public RemainingLives()

{

InitializeComponent();

}

}

Пришло время добавить в наш основной класс и другие вещи. В цикле игры нам придется иметь дело со множеством объектов. У нас будет свой корабль, некоторое число пришельцев, сколько-то бомб, а также наши ракеты, которые можно будет выпускать по одной с помощью метода EntityFired. В каждом цикле надо отслеживать, не столкнулась ли та или иная ракета с одним из пришельцев и не вылетела ли за пределы поля, не коснулась ли бомба нашего корабля и не исчезла ли с экрана, а также нет ли нового выстрела пришельцев по нашему кораблю. В классе Page уже есть свойство Ship, но Aliens (пришельцы), Bombs (бомбы) и Missiles (ракеты) должны быть коллекциями. Например, просматривая по очереди бомбы, нам будет удобно удалять те из них, которые попали в корабль или вышли за пределы игрового поля. Поскольку мы собираемся производить итерацию этих коллекций, мы не можем удалять из них элементы в цикле. Есть множество решений этой задачи, но мы поступим просто: введем коллекции с удаленными элементами, соответствующие основным коллекциям Bomb, Alien и Missile. В дальнейшем, в цикле игры удаляемые элементы будут добавляться в коллекцию с удаленными элементами и оставаться в основной коллекции:

List<Alien> aliens;

List<Alien> aliensRemove;

List<Alien> alienShooters;

List<Bomb> bombs;

List<Bomb> bombsRemove;

List<Missle> missles;

List<Missle> misslesRemove;

Нам также потребуется класс для отслеживания волн пришельцев, которые мы будем посылать по одной. Кроме того, добавим коллекцию для хранения этих волн. В каждой волне будет отслеживаться число пришельцев, появившихся за раз, общее число пришельцев в волне, количество пришельцев, сбросивших бомбы, а также частоту, с которой они могут метать бомбы.

public class WaveData

{

public WaveData(int count, double fireRate, int atOnce, int fireatonce)

{

EnemyCount = count;

fireRateMilliseconds = fireRate;

enemiesAtOnce = atOnce;

fireAtOnce = fireatonce;

waveEmpty = false;

}

public int EnemyCount { get; set; }

public double fireRateMilliseconds { get; set; }

public int enemiesAtOnce { get; set; }

public int fireAtOnce { get; set; }

public bool waveEmpty { get; set; }

}

Нам надо установить инициализационный метод нашей игры, все коллекции для спрайтов и добавить последовательно усложняющиеся волны. Все это будет в теле цикла игры. Для каждой коллекции мы вызываем методы, просматривающие ее содержимое и выполняющие действия в зависимости от координат и столкновений спрайтов. После выполнения цикла каждой основной коллекции, мы проходим по соответствующей ей коллекции с удаленными элементами, удаляем ненужные изображения с экрана и из основной коллекции. В конце проверяем, не настало ли время пришельцам сбросить очередные бомбы:

void gameLoop_Update(TimeSpan elapsed)

{

// Очистим текущий вектор, чтобы спрайт не двигался, когда не нажаты те или иные клавиши

PlayerShip.Velocity = new Vector(0, 0);

if (keyHandler.IsKeyPressed(Key.Left))

{

PlayerShip.Velocity = Vector.CreateVectorFromAngle(270, 125);

}

if (keyHandler.IsKeyPressed(Key.Right))

{

PlayerShip.Velocity = Vector.CreateVectorFromAngle(90, 125);

}

if (keyHandler.IsKeyPressed(Key.Space))

{

switch (status)

{

case GameState.Ready:

break;

case GameState.Running:

EntityFired(PlayerShip);

break;

case GameState.Paused:

break;

case GameState.BetweenWaves:

status = GameState.Running;

ctlInfo.GameInfo = "";

StartWave();

break;

case GameState.GameOver:

break;

default:

break;

}

}

PlayerShip.Update(elapsed);

 

BombLoop(elapsed);

MissleLoop(elapsed);

AlienLoop(elapsed);

 

foreach (Alien alien in aliensRemove)

{

aliens.Remove(alien);

gameRoot.Children.Remove(alien.SpriteCanvas);

AlienShot(alien);

}

aliensRemove.Clear();

 

foreach (Missle missle in misslesRemove)

{

missles.Remove(missle);

gameRoot.Children.Remove(missle.SpriteCanvas);

}

misslesRemove.Clear();

 

if (nextShot <= DateTime.Now)

{

nextShot = DateTime.Now.AddMilliseconds(enemyShootMilliseonds).AddMilliseconds(elapsed.Milliseconds * -1);

 

shotsThisPass = shotsAtOnce;

if (shotsThisPass > aliens.Count)

{

shotsThisPass = aliens.Count;

}

 

if (aliens.Count > 0)

{

foreach (Alien alien in aliens)

{

alienShooters.Add(alien);

}

}

 

while (alienShooters.Count > shotsThisPass)

{

alienShooters.RemoveAt(GetRandInt(0, alienShooters.Count - 1));

}

 

foreach (Alien alien in alienShooters)

{

EntityFired(alien);

}

 

alienShooters.Clear();

}

}