Splash! – разработка игры для Windows Phone 7. Часть II

    В предыдущей части нашей статьи (часть I) мы рассмотрели базовые вопросы создания игр под Windows Phone 7, выбор инструментов и технологий. Сейчас мы детально расмотрим вопросы программирования и взаимодействия с телефоном.

    Игровая логика


    Всю игровую логику вынесем в отдельный класс GameController. Теперь функция Update в классе игры выглядит следующим образом:
    protected override void Update(GameTime gameTime)
    {
    	// Allows the game to exit
    	if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
    		this.Exit();
     
    	GameController.Update(gameTime);
     
    	base.Update(gameTime);
    }
    

    Обратите внимание, что мы передаем в GameController.Update объект класса GameTime. У него есть свойство ElapsedGameTime, которое показывает, сколько времени прошло с предыдущего вызова функции Update. Как правило, это время фиксировано и на телефоне равно 33 миллисекундам, но лучше на это не рассчитывать, а использовать переданное значение во всех расчетах.

    Мы будем использовать ElapsedGameTime везде, где нужно вычислять движения объектов игрового мира. Например, если нам нужно рассчитать новую позицию белого шарика (под воздействием наклона телефона), то код будет выглядеть примерно так:
    position += (float)(acceleration * gameTime.ElapsedGameTime.TotalSeconds);

    Радиус шариков и клякс (в момент их появления) будет вычисляться так:
    radius += (float)(GrowSpeed * gameTime.ElapsedGameTime.TotalSeconds);

    Естественно, все эти вычислены мы будем проделывать в отдельных классах – у каждого из них будет своя аналогичная функция Update(gameTime). А GameController.Update будет только вызывать их.

    Рисуем графику


    Рисование тоже перенесем в класс GameController. Функция Draw в классе игры теперь выглядит следующим образом:
    protected override void Draw(GameTime gameTime)
    {
    	GraphicsDevice.Clear(Color.Black);
     	
    	spriteBatch.Begin();
    	GameController.Draw(spriteBatch);
    	spriteBatch.End();
    
    	base.Draw(gameTime);
    }
    

    Обратите внимание, что мы передаем в GameController.Draw только объект класса SpriteBatch – он предназначен исключительно для рисования. GameTime нам здесь не нужен, так как все необходимые вычисления уже сделаны в функции Update.

    Далее, GameController.Draw будет вызывать аналогичные функции Draw(spriteBatch) у классов, которые реализуют логику игрового мира.

    Само рисование выглядит довольно просто:
    spriteBatch.Draw(texture, position, color);
    То есть, мы можем выводить на экран только готовые текстуры – рисование 2D-примитивов (линий, прямоугольников, эллипсов и т.п.) в XNA не предусмотрено.

    Текстуру можно получить, загрузив ее из контента:
    Texture2D texture = Content.Load<Texture2D>(assetName);
    Текстуры у нас уже загружены, все позиции вычислены. А вот с цветом можно проделать кое-что интересное. Если нам нужен оригинальный цвет текстуры, то надо передать Color.White.

    Но если передать другой цвет, то текстура нарисуется с наложением этого цвета. Если же задать цвет с альфа-каналом, то текстура нарисуется полупрозрачной. Таким образом, мы можем управлять плавным появлением и исчезновением объектов и изменением их цвета (все это как раз требуется в нашей игре).

    Для смешивания двух цветов применяется функция Color.Lerp. А для добавления альфа-канала – следует использовать функцию Color.FromNonPremultiplied.

    Есть и другие варианты функции spriteBatch.Draw, которые позволяют поворачивать текстуру и масштабировать ее. Они нам тоже пригодятся.

    Выводим текст


    Вывод на экран текста производиться так же просто, как и вывод графики:
    spriteBatch.DrawString(spriteFont, text, position, color);

    Объект класса SpriteFont можно получить, загрузив его из контента:
    SpriteFont spriteFont = Content.Load<SpriteFont>(assetName);

    Здесь точно так же можно указывать цвет текста. И если же задать цвет с альфа-каналом, то текст нарисуется полупрозрачным.

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

    Есть и другие варианты функции spriteBatch.DrawString, которые позволяют поворачивать текст и масштабировать его. Но необходимо помнить, что любые подобные манипуляции вызывают искажения текста. Это вызвано тем, что XNA работает не с оригинальным векторным шрифтом, а его растровым представлением, которое создается в момент компиляции проекта и добавляется в контент.

    Тач-скрин


    Чтобы определить, где пользователь нажал на экран, необходимо получить данные от класса Microsoft.Xna.Framework.Input.Touch.TouchPanel:
    foreach (var item in TouchPanel.GetState())
    {
    	if (item.State == TouchLocationState.Pressed
    		|| item.State == TouchLocationState.Moved)
    	{
    		// Get item.Position
    	}
    }
    

    Таким образом, мы получим все точки на экране, к которым пользователь просто прикоснулся пальцами.

    Однако нам потребуется еще и данные о том, где пользователь произвел однократное нажатие на экран (нажал и отпустил). Это необходимо, чтобы отслеживать нажатия на экранные кнопки, например Pause. Для получения этих данных воспользуемся поддержкой жестов.

    В начале, при старте игры укажем, что нам нужна поддержка именно таких жестов (tap):
    TouchPanel.EnabledGestures = GestureType.Tap;

    Затем мы можем получать жесты в каждой итерации игрового цикла:
    while (TouchPanel.IsGestureAvailable)
    {
    	GestureSample gesture = TouchPanel.ReadGesture();
     
    	if (gesture.GestureType == GestureType.Tap)
    	{
    		// Get guesture.Position
    	}
    }
    

    Акселерометр


    Для получения данных о наклоне телефона, мы воспользуемся классом Microsoft.Devices.Sensors.Accelerometer. К сожалению, с акселерометра нельзя получить данные напрямую (как мы делали это с TouchPanel) – он поддерживает только событийную модель. Поэтому придется делать вспомогательный класс, который создает объект и подписывается на его событие:
    accelerometer = new Microsoft.Devices.Sensors.Accelerometer();
    accelerometer.ReadingChanged += AccelerometerChanged;
    accelerometer.Start();
    

    В обработчике события будем запоминать значение ускорения и сохранять его для дальнейшего использования:
    private void AccelerometerChanged(object sender, AccelerometerReadingEventArgs e)
    {
    	vector = new Vector3((float)e.X, (float)e.Y, (float)e.Z);
    }
    

    Вектор ускорения содержит информацию обо всех трех осях (X, Y и Z), но нам требуется только первые две – причем относительно портретной ориентации телефона. Поэтому свойство, возвращающее ускорение в нашей системе координат, будет выглядеть так:
    public Vector2 Acceleration
    {
    	get
    	{
    		return new Vector2(vector.X, -vector.Y);
    	}
    }
    

    Именно это ускорение мы и будем придавать нашему белому шарику.

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

    Проигрываем звук


    Проигрывание звуков в XNA делается при помощи функции Play у объекта класса SoundEffect. Объект этого класса можно загрузить из контента:
    SoundEffect sound = Content.Load<SoundEffect>(assetName);
    

    Собственно, это и все – больше нам ничего со звуками делать не надо.

    Окна


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

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

    Окон в XNA для Windows Phone нет совсем, поэтому придется делать их самостоятельно. Это не так сложно, как может показаться на первый взгляд.

    Достаточно сделать базовый контрол с основными свойствами: Parent, Children, Bounds, Visible, Enabled и функциями Update и Draw. Затем делаем несколько наследников: Window, Button, Label и т.п.

    После этого можно уже легко размещать элементы внутри окон.

    Сохранение состояния


    Игра на телефоне может быть прервана в любой момент – нажатием на аппаратную кнопку Home или по другому внешнему событию. Поэтому придется озаботиться сохранением состояния игры в любой момент и последующим восстановлением этого состояния при загрузке игры.

    Для хранения состояния (а заодно и настроек) воспользуемся классом System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings. Этот класс реализует интерфейс IDictionary<string, object>, поэтому работать с ним очень просто. Сложим текущее состояние всех объектов игрового мира (к счастью, у нас их немного) в этот dictionary и вызовем функцию IsolatedStorageSettings.ApplicationSettings.Save().

    Сохранение будем производить в момент выхода из приложения, для этого перекроем функцию OnExiting в классе игры:
    protected override void OnExiting(object sender, EventArgs args)
    {
    	GameController.SaveState();
    
    	base.OnExiting(sender, args); 
    }
    

    Восстановление состояния производится аналогично – при загрузке приложения получаем данные из IsolatedStorageSettings.ApplicationSettings и восстанавливаем все объекты игрового мира.

    Активация и деактивация приложения


    Наше приложение не всегда будет находиться в активном состоянии – иногда оно может быть деактивировано (по входящему звонку, например), а затем снова активировано.

    Для отслеживания этих событий перекроем функции OnActivated и OnDeactivated в классе игры.

    При деактивации приложения мы будем переводить игру в режим паузы – чтобы вернувшись к игре, пользователь обнаружил диалог с предложением продолжить игру.

    Кроме того, чтобы не тратить вычислительные ресурсы телефона в неактивном состоянии, добавим следующий код в начало функции Update в классе игры:
    if (!IsActive)
    {
    	SuppressDraw();
    	return;
    }
    

    Splash Screen


    Наша игра (а точнее игровой контент) загружается несколько секунд, и при этом не подает никаких признаков жизни – виден только черный экран. Надо бы как-то показать пользователю, что приложение работает. Для этого нарисуем красивый splash-screen, чтобы показывать его при старте игры:


    В Silverlight приложении для Windows Phone достаточно добавить в проект файл SplashScreenImage.jpg – и он автоматически покажется при загрузке. Однако это не работает для XNA проектов.

    Придется переделывать загрузку контента – вначале загрузим текстуру для splash-screen и нарисуем ее при первом вызове функции Draw. А затем загрузим весь остальной контент и запустим игру. И пока будет происходить загрузка остального контента, пользователь сможет наблюдать на экране наш splash- screen.

    Теперь старт приложения выглядит гораздо приятнее.

    Расположение игры на телефоне


    Чтобы наша игра появилась на телефоне в секции Games (туда можно добраться, нажав кнопку с надписью XBOX LIVE на стартовом экране телефона), необходимо отредактировать файл WMAppManifest.xml в проекте. В этом файле вместо строчки Genre=«Apps.Normal» надо написать Genre=«Apps.Games».

    Заодно укажем название и описание игры в этом же файле (в атрибутах Title и Description). И уберем лишние требования (секция ). В этой секции оставим только /> — для поддержки акселерометра. Все остальные в этой версии игры нам пока не нужно.

    В проекте должны быть две картинки: GameThumbnail.png и Background.png.

    Первая картинка нужна для показа игры в секции Games, а вторая – на стартовом экране телефона. Обе картинки должны быть одинакового размера: 173x173 пиксела.

    Trial-режим


    Так как наша игра будет платной, то надо добавить поддержку триального режима. Проверка триала уже встроена в платформу и производится при помощи класса Microsoft.Phone.Marketplace.LicenseInformation:
    var license = new Microsoft.Phone.Marketplace.LicenseInformation();
    return license.IsTrial();
    

    Так как эта функция работает довольно медленно, мы не станем вызывать ее на каждой итерации игрового цикла. Вместо этого, вызовем ее только при активации приложения – в функции OnActivated класса игры, а результат сохраним в переменной.

    Если приложение запущено в триальном режиме, то после нескольких минут игры мы покажем пользователю диалог с предложением купить приложение или начать новую игру.

    При нажатии на кнопку Buy вызовем показ нашего приложения в Windows Phone Marketplace:
    var market = new Microsoft.Phone.Tasks.MarketplaceDetailTask();
    market.Show();
    

    После покупки пользователь сможет вернуться в приложение и продолжить игру.

    Кнопка Back


    Наконец все готово и мы отправляем приложение в Windows Phone Marketplace. Перед публикацией его тщательно тестируют в Microsoft и через несколько дней выдают вердикт: не прошло. В чем же дело?

    Проблема оказалась в том самом коде, который мы оставили в функции Update:
    protected override void Update(GameTime gameTime)
    {
    	// Allows the game to exit
    	if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
    		this.Exit();
     
    	...
    }
    

    Аппаратная кнопка Back везде должна работать определенным образом – возвращать пользователя на предыдущий экран. И выход из приложения по ней должен производиться, только если пользователь находится на стартовом экране приложения (в главном меню игры). Если же мы находимся в диалоге паузы, то кнопка Back должна вернуть нас в игру.

    У нас же кнопка Back всегда вызывала выход из приложения. Убираем этот код, делаем правильную обработку кнопки Back и снова отдаем на проверку. Повторное рассмотрение занимает уже гораздо меньше времени.

    Результат


    И вот, наконец, приняли! Теперь игра находится в Windows Phone Marketplace.

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

     

    Редакция сайта Best WP7 Games провела обзор этой игры: www.bestwp7games.com/splash-fun-accelerometer-game-for-wp7-review.html



    И уже через неделю после появления, игра попала в Top 100 платных игр в Windows Phone Marketplace.

    Скачать игру можно здесь

    Какой же вывод можно сделать из опыта разработки этой игры? Создание игр для Windows Phone 7 – это действительно очень просто!
    • +31
    • 7.1k
    • 5
    Enterra
    37.76
    Company
    Share post

    Comments 5

      +2
      >>Скачать игру можно здесь
      Огорчили, думал здесь XAP :)
        0
        Можно реализовать GameController как GameComponent и добавлять его в список компонентов игры. Тогда GameController.Update будет вызываться автоматически. Хотя, приложение достаточно простое, так что это придирка.

        position += (float)(acceleration * gameTime.ElapsedGameTime.TotalSeconds);

        тут наверное правильнее не «acceleration», а «velocity»? Ведь dS = V * dT.
          0
          GameComponent не делали, так как автоматический вызов был как раз не нужен – чтобы при первом Update/Draw загрузился и показался только сплеш-скрин.

          А потом уже (на втором Update) загрузится контент и состояние игры – и только тогда начнет вызываться GameController.Update.

          Velocity, да более уместно будет.
          0
          Очень интересно.

          Хотелось бы узнать о финансовой состовляющей :)

          Кстати, сколько времени ушло на разработку?
            0
            В общей сложности на разработку потрачено около двух недель.

          Only users with full accounts can post comments. Log in, please.