Опубликован: 11.01.2013 | Доступ: свободный | Студентов: 623 / 124 | Длительность: 12:06:00
Лекция 6:

Курсовые работы

< Лекция 5 || Лекция 6: 12 || Лекция 7 >
Аннотация: Игровое приложение средствами Silverlight и XNA.

Курсовая работа №1. Игровое приложение средствами Silverlight

Задание

Используя технологию Silverlight, написать игру "Пятнашки" для Windows Phone 7.

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

Требования к работе:

  • использовать анимацию
  • использовать событие Manipulation
  • сохранять лучший результат в изолированном хранилище

Описание

Создадим новый проект Silverlight for Windows PhoneWindows Phone Application.

Рисование квадратиков с цифрами будем делать динамически в коде приложения. В роли квадратиков будут выступать элементы управления Border и TextBlock. Размещаться они будут в элементе управления Canvas. Нарисуем xaml-дизайн приложения:

  <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot">
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="Black" Offset="0" />
                <GradientStop Color="White" Offset="1" />
            </LinearGradientBrush>
        </Grid.Background>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="ПЯТНАШКИ" 
            Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="ПЯТНАШКИ" Margin="9,-7,0,0"
            
             Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
 
        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="100"/>
            </Grid.RowDefinitions>
 
            <Border Name="brdrField"  BorderBrush="Gray" BorderThickness="3" 
            Background="Black" Grid.Row="0">
                <Canvas Name="cnvField" ManipulationDelta="cnvField_ManipulationDelta" />
            </Border>
 
            <StackPanel Orientation="Horizontal" Grid.Row="1">
                <TextBlock Name="tbTimer" Text="" FontSize="30" Foreground="Black" 
                VerticalAlignment="Center" Width="230" MouseEnter="tbTimer_MouseEnter" />
                <Button Name="btnStart" Content="Сначала" Width="240" Height="90" 
                Background="Black" Click="btnStart_Click" />
            </StackPanel>
        </Grid>
    </Grid>

На эмуляторе это будет выглядеть примерно следующим образом Рис. 6.1 :

Внешний вид приложения

Рис. 6.1. Внешний вид приложения

Для улучшения дизайна приложения свойству Background элемента LayoutRoot мы задали значение:

  <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="Black" Offset="0" />
                <GradientStop Color="White" Offset="1" />
            </LinearGradientBrush>
        </Grid.Background>

Объекту Canvas мы задали событие ManipulationDelta. Обработчик этого события будет срабатывать при изменении положения прикоснувшегося пальца. Таким образом мы будем перемещать квадратики движением пальца по экрану по горизонтали или по вертикали.

Перейдем к коду.

Определим метод Draw(), который будет вызываться в начале игры, случайно генерировать цифры, вырисовывать их на поле. Каждый квадратик представляет собой текстовый блок внутри элемента Border.

Для генерации цифр в самом начале заполним список цифрами от 0 до 15 (0 – пустой квадратик). Затем, при расположении квадратика, будем случайно извлекать элементы этого списка и удалять их, чтобы обеспечить неповторяемость значений.

  private void Draw()
        {
            cnvField.Children.Clear();
 
            //заполняем список чисел от 0 до FIELD_SIZE*FIELD_SIZE
            List<int> listNumbers = new List<int>();
            for (int i = 0; i < (FIELD_SIZE * FIELD_SIZE); i++)
            {
                listNumbers.Add(i);
            }
 
            //будем брать случайный элемент этого списка
            Random u = new Random(DateTime.Now.Millisecond);
 
            for (int j = 0; j < FIELD_SIZE; j++)
            {
                for (int i = 0; i < FIELD_SIZE; i++)
                {
                    Border brdElement = new Border();
                    TextBlock tbElement = new TextBlock();
 
                    double tmpX = i * (CELL_SIZE + FIELD_BORDER);
                    double tmpY = j * (CELL_SIZE + FIELD_BORDER);
 
                    int r = u.Next(0, listNumbers.Count - 1);
                    string strText = listNumbers[r].ToString();
                    listNumbers.RemoveAt(r);
 
                    if (strText.Equals("0"))
                    {
                        strText = "";
                    }
 
                    tbElement.VerticalAlignment = System.Windows.VerticalAlignment.Center;
                    tbElement.TextAlignment = TextAlignment.Center;
                    tbElement.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
                    tbElement.FontSize = 30;
                    tbElement.Text = strText;
 
                    brdElement.VerticalAlignment = System.Windows.VerticalAlignment.Center;
                    brdElement.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
                    brdElement.Width = CELL_SIZE;
                    brdElement.Height = CELL_SIZE;
                    brdElement.SetValue(Canvas.LeftProperty, tmpX);
                    brdElement.SetValue(Canvas.TopProperty, tmpY);
                    brdElement.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 128, 128, 128));
                    brdElement.BorderThickness = new Thickness(3);
                    brdElement.Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
                    brdElement.Child = tbElement;
 
                    listElements[i, j] = brdElement;
                    cnvField.Children.Add(listElements[i, j]);
                }
            }
        }

На эмуляторе это будет выглядеть примерно следующим образом Рис. 6.2 :

Внешний вид конечного приложения

Рис. 6.2. Внешний вид конечного приложения

Теперь напишем обработчик нажатия. Поскольку квадратик – это элемент TextBlock внутри элемента Border, то нажатие может произойти как по одному, так и по другому элементу. В самом начале обработчика предусмотрим такую возможность. В случае, если нажатии на текстовый блок, будет брать его родительский элемент (т.е. Border) и дальше работать с ним. В случае если нажатие произошло по какому-либо другому элементу – выходим из обработчика.

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

Далее определяем координаты нажатой клетки, выполняем перемещение (с анимацией) и проверяем на окончание игры:

 private void cnvField_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            if (m_IsPlay)
            {
                Border brdr;
 
                if (e.OriginalSource.GetType() == (new TextBlock()).GetType())
                {
                    TextBlock tb = e.OriginalSource as TextBlock;
                    brdr = tb.Parent as Border;
                }
                else if (e.OriginalSource.GetType() == (new Border()).GetType())
                {
                    brdr = e.OriginalSource as Border;
                }
                else
                {
                    return;
                }
 
                //задержка выполнения анимации (предотвращает баг с неплоным перемещением)
                if ((DateTime.Now - m_dtSleep).Milliseconds > (ANIMATION_DURATION * 1000))
                {
                    //получаем координаты нажатого контрола
                    int left = (int)(((double)brdr.GetValue(Canvas.LeftProperty)) / CELL_SIZE);
                    int top = (int)(((double)brdr.GetValue(Canvas.TopProperty)) / CELL_SIZE);
 
                    //если движение по горизонтали
                    if (Math.Abs(e.DeltaManipulation.Translation.X) > Math.Abs(e.DeltaManipulation.Translation.Y))
                    {
                        if ((e.DeltaManipulation.Translation.X < 0) && (left > 0))
                        {
                            MoveCell(left, top, left - 1, top);
                            m_dtSleep = DateTime.Now;
 
                            if (CheckForWin()) Win();
                        }
 
                        if ((e.DeltaManipulation.Translation.X > 0) && (left < FIELD_SIZE - 1))
                        {
                            MoveCell(left, top, left + 1, top);
                            m_dtSleep = DateTime.Now;
 
                            if (CheckForWin()) Win();
                        }
                    }
                    //если движение по вертикали
                    else if (Math.Abs(e.DeltaManipulation.Translation.X) < Math.Abs(e.DeltaManipulation.Translation.Y))
                    {
                        if ((e.DeltaManipulation.Translation.Y < 0) && (top > 0))
                        {
                            MoveCell(left, top, left, top - 1);
                            m_dtSleep = DateTime.Now;
 
                            if (CheckForWin()) Win();
                        }
 
                        if ((e.DeltaManipulation.Translation.Y > 0) && (top < FIELD_SIZE - 1))
                        {
                            MoveCell(left, top, left, top + 1);
                            m_dtSleep = DateTime.Now;
 
                            if (CheckForWin()) Win();
                        }
                    }
                }
            }
            
            e.Handled = true;
        }

При перемещении клетки необходимо учесть свойство Z-индекс, которое определяет, какой элемент на Canvas находится ниже, какой выше.

Создадим два объекта типа DoubleAnimation для анимации перемещения левой (на случай, если перемещение по горизонтали) и верхней границы (на случай, если перемещение по вертикали). Зададим им диапазон изменения и задержку. Затем установим, что анимация будет происходить над элементом listElements[fromX, fromY] (в этом массиве типа Border[,] хранятся квадратики), а именно над его свойствами Canvas.LeftProperty и Canvas.TopProperty. Затем добавим анимацию в элемент Storyboard и запустим ее на выполнение:

//создаем и описываем анимацию - перемещение
                    DoubleAnimation anima1 = new DoubleAnimation();
                    anima1.From = (double)listElements[fromX, fromY].GetValue(Canvas.LeftProperty);
                    anima1.To = leftTo;
                    anima1.Duration = new Duration(TimeSpan.FromSeconds(ANIMATION_DURATION));
                    DoubleAnimation anima2 = new DoubleAnimation();
                    anima2.From = (double)listElements[fromX, fromY].GetValue(Canvas.TopProperty);
                    anima2.To = topTo;
                    anima2.Duration = new Duration(TimeSpan.FromSeconds(ANIMATION_DURATION));
 
                    Storyboard.SetTarget(anima1, listElements[fromX, fromY]);
                    Storyboard.SetTargetProperty(anima1, new PropertyPath(Canvas.LeftProperty));
                    Storyboard.SetTarget(anima2, listElements[fromX, fromY]);
                    Storyboard.SetTargetProperty(anima2, new PropertyPath(Canvas.TopProperty));
 
                    Storyboard storyboard = new Storyboard();
                    storyboard.Children.Add(anima1);
                    storyboard.Children.Add(anima2);
                    storyboard.Begin();

Полностью функция MoveCell() выглядит следующим образом:

private void MoveCell(int fromX, int fromY, int toX, int toY)
        {
            listElements[fromX, fromY].Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
 
            if (IsCellFree(toX, toY))
            {
                //если клетки соседние
                if (NeighbourCell(fromX, fromY, toX, toY))
                {
                    //у кого z-индекс больше, тот рисуется выше
                    listElements[fromX, fromY].SetValue(Canvas.ZIndexProperty, 2);
                    listElements[toX, toY].SetValue(Canvas.ZIndexProperty, 1);
 
                    //запоминаем координаты пустой клетки
                    double leftTo = (double)listElements[toX, toY].GetValue(Canvas.LeftProperty);
                    double topTo = (double)listElements[toX, toY].GetValue(Canvas.TopProperty);
 
                    //перемещаем пустую клетку
                    listElements[toX, toY].SetValue(Canvas.LeftProperty, listElements[fromX, fromY].GetValue(Canvas.LeftProperty));
                    listElements[toX, toY].SetValue(Canvas.TopProperty, listElements[fromX, fromY].GetValue(Canvas.TopProperty));
 
                    //создаем и описываем анимацию - перемещение
                    DoubleAnimation anima1 = new DoubleAnimation();
                    anima1.From = (double)listElements[fromX, fromY].GetValue(Canvas.LeftProperty);
                    anima1.To = leftTo;
                    anima1.Duration = new Duration(TimeSpan.FromSeconds(ANIMATION_DURATION));
                    DoubleAnimation anima2 = new DoubleAnimation();
                    anima2.From = (double)listElements[fromX, fromY].GetValue(Canvas.TopProperty);
                    anima2.To = topTo;
                    anima2.Duration = new Duration(TimeSpan.FromSeconds(ANIMATION_DURATION));
 
                    Storyboard.SetTarget(anima1, listElements[fromX, fromY]);
                    Storyboard.SetTargetProperty(anima1, new PropertyPath(Canvas.LeftProperty));
                    Storyboard.SetTarget(anima2, listElements[fromX, fromY]);
                    Storyboard.SetTargetProperty(anima2, new PropertyPath(Canvas.TopProperty));
 
                    Storyboard storyboard = new Storyboard();
                    storyboard.Children.Add(anima1);
                    storyboard.Children.Add(anima2);
                    storyboard.Begin();
 
                    //меняем индексы
                    Border tmp = listElements[fromX, fromY];
                    listElements[fromX, fromY] = listElements[toX, toY];
                    listElements[toX, toY] = tmp;
                }
            }
        }
< Лекция 5 || Лекция 6: 12 || Лекция 7 >