Главная » Silverlight » Элемент UserControl игры с бомбами

0

Следующий этап разработки приложения — создание графического представле­ния бомбы. Можно было бы использовать статическое изображение с прозрачным фо­ном, однако такое решение слишком негибкое. Лучше применить более гибкие фигуры

Silverlight. Фигуры позволяют изменять размеры и поворачивать бомбы, анимировать детали бомбы и т.д. Честно говоря, для такой простой игры, как в данном примере, было бы достаточно статического изображения, однако тогда при любом усложнении структуры игры (например, при добавлении фона или дополнительных правил) вы столкнетесь с неразрешимыми проблемами. Бомба, использованная в данном примере, извлечена непосредственно из сетевой коллекции клипов Microsoft Word. Для преобра­зования клипа в разметку XAML он был вставлен в документ Word и сохранен как файл XPS, как описано в главе 8. В полученной таким образом разметке XAML используется набор элементов Path.

Затем разметка XAML бомбы (класс Bomb) была немного упрощена: удалены лишние элементы Canvas, обрамляющие бомбу, и объекты преобразований, масштабирующие бомбу. Разметка XAML вставлена в новый пользовательский элемент управления Bomb. В результате этих манипуляций для вывода бомбы на экран достаточно создать экзем­пляр элемента Bomb и добавить его в контейнер Canvas.

Размещение графики в отдельном пользовательском элементе управления UserControl облегчает создание многих экземпляров бомбы на поверхности игры и позволяет инкапсулировать функциональность бомбы в фоновом коде элемента UserControl. Таким образом, клип, извлеченный из Word, дополнен лишь одним ком­понентом: булевым свойством isFalling, позволяющим отслеживать текущее состоя­ние бомбы (а именно — падает ли она в данный момент времени или уже уничтожена).

public partial class Bomb : UserControl

{

public Bomb() {

InitializeComponent();

}

public bool IsFalling {

get; set;

}

}

Разметка бомбы содержит элемент преобразования RotateTransform, который в коде анимации можно использовать для покачивания бомбы во время падения. Преобразование RotateTransform можно было бы создать и добавить программно, од­нако в данной ситуации лучше определить его в файле XAML.

<UserControl х:Class="BombDrop.Bomb" xmlns="http://schemas.microsoft.com/client/2007"

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

<UserControl.RenderTransform> <TransformGroup> <RotateTransform Angle="20" CenterX="50" CenterY="50"> </RotateTransform>

<ScaleTransform ScaleX="0.5" ScaleY="0.5"> </ScaleTransform> </TransformGroup> </UserControl.RenderTransform>

<Canvas>

<!— Здесь нужно вставить определение элементов Path, рисующих бомбу —>

</Canvas> </UserControl>

Вставить бомбу в окно можно с помощью элемента <bomb:Bomb>, так же как элемент Title в главное окно (см. выше). Однако в данном случае лучше создавать бомбы про­граммно, чтобы легче было закодировать их бросание.

Бросание бомб

Для бросания бомб используется встроенный в Silverlight таймер DispatcherTimer, генерирующий события в потоке пользовательского интерфейса и позволяющий изба­виться от проблем, связанных с диспетчеризацией и блокировками (о многопоточном программировании см. в главе 19). При использовании таймера DispatcherTimer нужно лишь задать интервал времени, определяющий периодичность генерирования событий Tick таймером. Ниже приведен код, подключающий событие Tick к странице.

private DispatcherTimer bombTimer = new DispatcherTimer();

public Paget) {

InitializeComponent() ; bombTimer .Tick += bombTimer_Tick;

}

Сначала таймер генерирует события Tick каждые 1,3 секунды. Когда пользователь щелкает на кнопке Пуск (см. рис. 10.12), запускается таймер.

// Отслеживание количества упавших и уничтоженных бомб private int droppedCount = 0; private int savedCount = 0;

// Сначала бомбы сбрасываются каждые 1,3 секунды и // падают на землю через 3,5 секунды private double initialSecondsBetweenBombs = 1.3; private double initialSecondsToFall = 3.5; private double secondsBetweenBombs; private double secondsToFall;

private void cmdStart_Click(object sender, RoutedEventArgs e)

{

cmdStart.IsEnabled = false; droppedCount = 0; savedCount = 0;

secondsBetweenBombs = initialSecondsBetweenBombs; secondsToFall = initialSecondsToFall; // Запуск таймера bombTimer. Interval -

TimeSpan.FromSeconds(secondsBetweenBombs); bombTimer.Start();

}

При генерации события Tick код создает новый объект Bomb и позиционирует его в контейнере Canvas. Сначала бомба не видна, потому что она находится непосред­ственно над рамкой области игры, однако немедленно после события Click она начи­нает появляться на экране. Горизонтальная позиция бомбы определяется с помощью генератора случайных чисел Random.

private void bombTimer_Tick(object sender, EventArgs e) {

// Создание бомбы Bomb bomb = new Bomb () ; bomb.IsFalling = true;

// Позиционирование бомбы Random random = new Random(); bomb.SetValue(Canvas.LeftProperty, (double)(random.Next(0,

(int)(canvasBackground.ActualWidth – 50)))); bomb.SetValue(Canvas.TopProperty, -100.0) ;

// Добавление бомбы в контейнер Canvas canvasBackground.Children.Add(bomb);

Затем код динамически создает раскадровку для анимации бомбы. Создаются две раскадровки: одна — бросает бомбу путем изменения подключенного свой­ства Canvas.Тор, а другая — покачивает бомбу, изменяя угол с помощью объекта

RotateTransform. Свойства StoryBoard.TargetPlement и Storyboard.TargetProperty являются подключенными, поэтому изменять их нужно с помощью методов Storyboard. SetTargetElement() и Storyboard.SetTargetProperty().

// Подключение щелчка мыши, уничтожающего бомбу bomb.MouseLeftButtonDown += bomb_MouseLeftButtonDown;

// Создание анимации, перемещающей бомбу Storyboard storyboard = new Storyboard(); DoubleAnimation fallAnimation = new DoubleAnimation (); fallAnimation.To = canvasBackground.ActualHeight; fallAnimation.Duration =

TimeSpan.FromSeconds(secondsToFall) ;

Storyboard.SetTarget(fallAnimation, bomb); Storyboard.SetTargetProperty(fallAnimation,

new PropertyPathC (Canvas.Top) ") ) ; storyboard.Children.Add(fallAnimation);

// Создание анимации, покачивающей бомбу DoubleAnimation wiggleAnimation = new DoubleAnimation (); wiggleAnimation.To = 30;

wiggleAnimation.Duration = TimeSpan.FromSeconds (0.2); wiggleAnimation.RepeatBehavior = RepeatBehavior.Forever; wiggleAnimation.AutoReverse = true; Storyboard.SetTarget(wiggleAnimation, ((TransformGroup)bomb.RenderTransform).Children[0]); Storyboard.SetTargetProperty(wiggleAnimation,

new PropertyPath("Angle")); storyboard.Children.Add(wiggleAnimation);

В обеих анимациях можно применить функции смягчения для имитации более реа­листичного поведения. В данном примере используется линейная интерполяция.

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

private Dictionary<Bomb, Storyboard>

storyboards =new Dictionary<Bomb, Storyboard>(); private Dictionary<Storyboard, Bomb>

bombs = new Dictionary<Storyboard, Bomb>();

Приведенный ниже код добавляет бомбу и раскадровку в две коллекции.

bombs.Add(storyboard, bomb); storyboards.Add(bomb, storyboard);

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

storyboard.Duration = fallAnimation.Duration; storyboard.Completed += storyboard_Completed; storyboard.Begin() ;

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

// Коррекция каждые 15 секунд

private double secondsBetweenAdjustments = 15;

private DateTime lastAdjustmentTime = DateTime.MinValue;

// После каждой коррекции обе переменные нужно уменьшить, // иначе интервал таймера и время падения быстро // станут равными нулю

private double secondsBetweenBombsReduction = 0.1; private double secondsToFallReduction = 0.1;

Ниже приведен код, размещенный в конце обработчика события DispatcherTimer. Tick. Код проверяет, нужна ли коррекция, и, если да, выполняет ее. Кроме того, приве­денный код обновляет надписи на правой панели.

// Выполнение коррекции при необходимости

if ((DateTime.Now.Subtract(lastAdjustmentTime).TotalSeconds> secondsBetweenAdjustments))

{

lastAdjustmentTime = DateTime.Now;

secondsBetweenBombs -= secondsBetweenBombsReduction; secondsToFall -= secondsToFallReduction;

// Технически следовало бы проверять переменные

//на равенство нулю или наличие отрицательных значений,

// однако эти события никогда не должны наступить;

// поэтому выполняется установка таймера

// для бросания следующей бомбы

bombTimer.Interval =

TimeSpan.FromSeconds(secondsBetweenBombs);

// Обновление надписей на правой панели

lblRate.Text = String.Format( "Бомбы сбрасываются каждые {0} секунд", secondsBetweenBombs);

IblSpeed.Text = String.Format( "Каждая бомба падает {0} секунды", secondsToFall);

}

}

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

Перехват бомб

Пользователь уничтожает бомбу, щелкнув на ней до того, как она упадет на дно кон­тейнера Canvas. Каждая бомба является отдельным экземпляром пользовательского эле­мента управления Bomb, поэтому отреагировать на щелчок на ней мышью несложно. Для этого достаточно подключить обработчик к событию MouseLeftButtonDown, которое генерируется при щелчке на любом компоненте бомбы, но не генерируется при щелчке на фоне бомбы, например, за пределами окружности.

При щелчке на бомбе в первую очередь нужно извлечь ее объект и установить его свойство isFalling, чтобы отметить, что бомба больше не падает. Свойство isFalling используется также в обработчике завершения анимации. Ниже приведено начало об­работчика щелчка на бомбе.

private void bomb_MouseLeftButtonDown(object sender,

MouseButtonEventArgs e)

{

/ / Извлечение объекта бомбы Bomb bomb = (Bomb)sender; bomb.IsFalling = false;

// Сохранение текущей позиции бомбы double currentTop = Canvas.GetTop(bomb) ;

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

// Прекращение падения бомбы

Storyboard storyboard = storyboards[bomb];

storyboard.Stop();

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

// Повторное использование существующей раскадровки с новыми

// анимациями; бомбе присваивается новая траектория путем // анимирования свойств Canvas.Тор и Canvas.Left, storyboard.Children.Clear();

DoubleAnimation riseAnimation = new DoubleAnimation(); riseAnimation.From = currentTop; riseAnimation.To = 0;

riseAnimation.Duration = TimeSpan.FromSeconds(2);

Storyboard.SetTarget(riseAnimation, bomb); Storyboard.SetTargetProperty(riseAnimation,

new PropertyPath("(Canvas.Top)")); storyboard.Children.Add(riseAnimation);

DoubleAnimation slideAnimation = new DoubleAnimation(); double currentLeft = Canvas.GetLeft(bomb);

/ / Бомба убирается с экрана

if (currentLeft < canvasBackground.ActualWidth / 2)

slideAnimation.To = -100;

}

else {

slideAnimation.To = canvasBackground.ActualWidth + 100;

}

slideAnimation.Duration = TimeSpan.FromSeconds(1); Storyboard.SetTarget(slideAnimation, bomb);

Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(Canvas.Left)")) ; storyboard.Children.Add(slideAnimation);

// Запуск новой анимации

storyboard.Duration = slideAnimation.Duration;

storyboard.Begin(); }

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

Подсчет бомб и очистка игры

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

// Завершение игры после падения 5 бомб private int maxDropped = 5;

private void storyboard_Completed(object sender,

EventArgs e)

{

Storyboard completedStoryboard = (Storyboard)sender; Bomb completedBomb = bombs [completedStoryboard] ;

// Проверка бомбы (упала или уничтожена)

if (completedBomb.IsFalling)

droppedCount++ ; else savedCount++;

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

// Обновление надписей lblStatus.Text = String.Format ( "Упало {0} и уничтожено {1} бомб", droppedCount, savedCount);

/ / раскадровка completedStoryboard.Stop();

canvasBackground.Children.Remove(completedBomb);

// Очистка коллекций

storyboards.Remove(completedBomb);

bombs.Remove(completedStoryboard);

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

// Проверка, завершена ли игра

if (droppedCount >= maxDropped) {

bombTimer.Stop ();

lblStatus.Text += "\r\n\r\nGame over.";

// Извлечение всех, действительных раскадровок foreach (KeyValuePair<Bomb, Storyboard> item in storyboards)

{

Storyboard storyboard = item.Value; Bomb bomb = item.Key; storyboard.Stop();

canvasBackground.Children.Remove(bomb);

)

// Очистка коллекций storyboards.Clear(); bombs.Clear ();

// Разрешение пользователю начать новую игру cmdStart.IsEnabled = true;

)

Этим завершается код игры BombDropper. Однако вы можете внести в нее ряд усо­вершенствований.

•       Добавление визуального эффекта взрыва бомбы. Вокруг бомбы можно выве­сти пламя. Можно также расчленить бомбу на ряд осколков, летящих в разные стороны.

•       Анимация фона. Можете добавить перемещающийся пейзаж, облака взрывов, движущиеся объекты (танки, грузовики, фигурки солдат) и т.д.

•       Добавление эффекта глубины. Сделать это легче, чем кажется на первый взгляд. Например, можно выводить бомбы разных размеров. Большим бомбам присвойте большее значение ZIndex, чтобы они закрывали меньшие бомбы. При­свойте им также более высокую скорость падения.

•       Добавление шумового эффекта взрыва. Добавление в приложение Silverlight звука и других медийных средств рассматривается в главе 11. Другой звук до­бавьте при уничтожении бомбы.

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

В Интернете можно найти много примеров программирования игр в среде Silverlight. Сайт сообщества Silverlight предоставляет коды примеров по такому адресу:

www.silverlight.net/themes/silverlight/community/

gallerydetail.aspx?cat=6

Посетите также сайт Энди Болье (Andy Beaulieu), содержащий ряд игр и великолеп­ных имитаторов физических эффектов (www. andybeaulieu. com).

Источник: Мак-Дональд, Мэтью. Silverlight 3 с примерами на С# для профессионалов. : Пер. с англ. —- М. : ООО «И.Д. Вильяме», 2010. — 656 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии