Лабораторный практикум 2
Лабораторная работа №9. Игра "Загони шар в лузу"
Задание
Создать игру "Загони шар в лузу" для Windows Phone 7 с использованием акселерометра (шар меняет направление и скорость при наклоне телефона) и возможностью сохранения наилучшего времени в изолированном хранилище.
Освоение
- акселерометр
- работа с потоками
- изолированное хранилище
Описание
Создадим новый проект Silverlight for Windows Phone – Windows Phone Application.
Откроем файл разметки главной страницы MainPage.xaml. Разместим элемент Canvas, в котором будут находиться два эллипса (элемент управления Ellipse). В зависимости от положения телефона будем менять свойства Canvas.Left и Canvas.Top. Добавим также текстовый блок для вывода времени игры и 3 кнопки: новая игра, просмотр рекорда и сброс рекорда.
<!--ContentPanel - place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <StackPanel> <Canvas Name="cnvMain" Height="700" Width="450" Background="White"> <Ellipse Name="ellBall" Height="30" Width="30" Canvas.Left="50" Canvas.Top="200" Canvas.ZIndex="2" Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Fill="Red" /> <Ellipse Name="ellHole" Height="50" Width="50" Canvas.Left="200" Canvas.Top="50" Canvas.ZIndex="1" Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Fill="Black" /> </Canvas> <StackPanel Orientation="Horizontal"> <TextBlock Name="lblTime" Text="00:00" FontSize="26" VerticalAlignment="Center" /> <Button Name="btnNewGame" Content="Новая" Click="btnNewGame_Click" /> <Button Name="btnRecord" Content="Рекорд" Click="btnRecord_Click" /> <Button Name="btnResetRecord" Content="Сброс" Click="btnResetRecord_Click" /> </StackPanel> </StackPanel> </Grid>
Добавим константы для определения радиусов шарика и лузы:
private const double BALL_RAD = 15d; private const double HOLE_RAD = 20d;
В конструкторе класса проинициализируем размеры элепсов:
ellBall.Width = BALL_RAD * 2d; ellBall.Height = BALL_RAD * 2d; ellHole.Width = HOLE_RAD * 2d; ellHole.Height = HOLE_RAD * 2d;
Для работы с акселерометром добавим в код директиву:
using Microsoft.Devices.Sensors;
Создадим глобальную переменную акселерометра:
private Accelerometer myAccel;
В конструкторе класса страницы проинициализируем акселерометр и подпишемся на событие изменения положения телефона:
myAccel = new Accelerometer(); myAccel.ReadingChanged += new EventHandler<AccelerometerReadingEventArgs>(myAccel_ReadingChanged);
Для изменения скорости шарика определим глобальную константу и переменные в классе:
private const double KOEFF_ACCEL = 20d; private double nSpeedUpZ, nSpeedUpX; private bool bGameStopped;
При изменении положения телефона будем менять координаты шарика. Поскольку из отдельного потока нельзя напрямую работать с элементами управления, будем пользоваться диспетчерами. После изменения координат будем проверять игру на победу (попадание центра шарика в лузу).
void myAccel_ReadingChanged(object sender, AccelerometerReadingEventArgs e) { if (myAccel.IsDataValid && !bGameStopped) { nSpeedUpZ = e.Z * KOEFF_ACCEL; nSpeedUpX = e.X * KOEFF_ACCEL; if (e.Z < 0) { Dispatcher.BeginInvoke(() => HandleUp()); } else { Dispatcher.BeginInvoke(() => HandleDown()); } if (e.X < 0) { Dispatcher.BeginInvoke(() => HandleLeft()); } else { Dispatcher.BeginInvoke(() => HandleRight()); } Dispatcher.BeginInvoke(() => WinChecking()); } } private void HandleUp() { if ((double)ellBall.GetValue(Canvas.TopProperty) > 0d) { ellBall.SetValue(Canvas.TopProperty, (double)ellBall.GetValue(Canvas.TopProperty) + nSpeedUpZ); } } private void HandleDown() { if ((double)ellBall.GetValue(Canvas.TopProperty) < (cnvMain.Height - ellBall.Height)) { ellBall.SetValue(Canvas.TopProperty, (double)ellBall.GetValue(Canvas.TopProperty) + nSpeedUpZ); } } private void HandleLeft() { if ((double)ellBall.GetValue(Canvas.LeftProperty) > 0d) { ellBall.SetValue(Canvas.LeftProperty, (double)ellBall.GetValue(Canvas.LeftProperty) + nSpeedUpX); } } private void HandleRight() { if ((double)ellBall.GetValue(Canvas.LeftProperty) < (cnvMain.Width - ellBall.Width)) { ellBall.SetValue(Canvas.LeftProperty, (double)ellBall.GetValue(Canvas.LeftProperty) + nSpeedUpX); } } private void WinChecking() { if (IsWin()) { bGameStopped = true; dTimer.Stop(); myAccel.Stop(); if (IsRecord()) { nTimeRecord = nTime; MessageBox.Show("Победа!" + Environment.NewLine + "Новый рекорд: " + FormatTime(nTimeRecord) + "!"); SaveToIsolatedStorage(nTimeRecord.ToString()); } else { MessageBox.Show("Победа!"); } } } private bool IsWin() { bool bRes = false; if ((((double)ellBall.GetValue(Canvas.TopProperty) + BALL_RAD) > ((double)ellHole.GetValue(Canvas.TopProperty))) && (((double)ellBall.GetValue(Canvas.TopProperty) + BALL_RAD) < ((double)ellHole.GetValue(Canvas.TopProperty) + HOLE_RAD + HOLE_RAD))) { if ((((double)ellBall.GetValue(Canvas.LeftProperty) + BALL_RAD) > ((double)ellHole.GetValue(Canvas.LeftProperty))) && (((double)ellBall.GetValue(Canvas.LeftProperty) + BALL_RAD) < ((double)ellHole.GetValue(Canvas.LeftProperty) + HOLE_RAD + HOLE_RAD))) { bRes = true; } } return bRes; }
Введем в игру счетчик времени. Добавим константу и переменные:
private const int MAX_TIME = 3599; //59:59 private int nTimeRecord; private int nTime; System.Windows.Threading.DispatcherTimer dTimer;
В конструкторе класса объявим новый поток и подпишемся на событие срабатывания таймера (каждую секунду):
dTimer = new System.Windows.Threading.DispatcherTimer(); dTimer.Interval = new TimeSpan(0, 0, 0, 1, 0); //1 сек dTimer.Tick += new EventHandler(Timer_Tick);
По событию будем увеличивать таймер и с помощью диспетчера менять время в текстовом блоке на странице. А в случае, если максимальное время достигнуто, останавливаем игру.
private void Timer_Tick(object sender, EventArgs e) { nTime++; Dispatcher.BeginInvoke(() => ChangeTimerLabel()); if (IsLose()) { Dispatcher.BeginInvoke(() => Lose()); } } private void ChangeTimerLabel() { lblTime.Text = FormatTime(nTime); } private string FormatTime(int time) { return String.Format("{0:00}", (int)(time / 60)) + ":" + String.Format("{0:00}", (time % 60)); } private bool IsLose() { bool bRes = false; if (nTime >= MAX_TIME) { bRes = true; } return bRes; } private void Lose() { bGameStopped = true; dTimer.Stop(); myAccel.Stop(); MessageBox.Show("Скучно..."); }
В случае успешного попадания шарика в лузу, если достигнут рекорд по времени, сохраняем его в файле в изолированном хранилище:
private const string strIStorageName = "Wp7IUSLab12.txt";
При этому в конструкторе класса, в случае если файл с рекордом существует, будем загружать рекорд:
if (IsIsolatedStorageExist()) { nTimeRecord = int.Parse(LoadFromIsolatedStorage()); } else { nTimeRecord = MAX_TIME; }
При нажатии на кнопку "Сброс" будем очищать хранилище:
Для работы с хранилищем определим директивы:
using System.IO.IsolatedStorage; using System.IO;
Создадим функции сохранения рекорда в хранилище, загрузки, удаления и проверки файла на существование:
private void SaveToIsolatedStorage(string histText) { IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication(); IsolatedStorageFileStream fileStream = fileStorage.CreateFile(strIStorageName); StreamWriter sw = new StreamWriter(fileStream); sw.Write(histText); sw.Close(); fileStream.Close(); } private string LoadFromIsolatedStorage() { IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication(); IsolatedStorageFileStream fileStream = fileStorage.OpenFile(strIStorageName, System.IO.FileMode.Open); StreamReader sr = new StreamReader(fileStream); string strRes = sr.ReadToEnd(); sr.Close(); fileStream.Close(); return strRes; } private bool IsIsolatedStorageExist() { IsolatedStorageFile sileStorage = IsolatedStorageFile.GetUserStoreForApplication(); return sileStorage.FileExists(strIStorageName); } private void RemoveIsolatedStorage() { if (IsIsolatedStorageExist()) { IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication(); fileStorage.DeleteFile(strIStorageName); } }
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.