Пишем игры для Windows Phone


    Не так давно публике была представлена платформа Windows Phone. Платформа очень интересная в плане разработки, т.к. присутствует поддержка .Net Framework, мультизадачность и XNA Framework, причем отличия у XNA от десктопной версии минимальны.

    Вначале ложка дегтя: выяснилось, что пока на Windows Phone не будет полноценной поддержки собственных шейдеров, но обещается ряд предустановленных с широкими возможностями настройками. Что-ж, не будем огорчаться, всему свое время.
    Для работы нам понадобится Windows Phone Developer Tools

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


    Итак, создаем новое решение:



    Начнем с вывода текста на экран, для этого добавим в проект новый файл шрифта:



    Откроем этот файл и добавим в секцию запись для кириллицы:
      <CharacterRegions>
       <CharacterRegion>
        <Start> </Start>
        <End>~</End>
       </CharacterRegion>
       <CharacterRegion>
        <Start>А</Start>
        <End>я</End>
       </CharacterRegion>
      </CharacterRegions>

    Также увеличим размер шрифта.

    Код загрузки шрифта и вывод текста на экран:
    protected override void LoadContent()
    {
      spriteBatch = new SpriteBatch(GraphicsDevice);
      font = Content.Load<SpriteFont>("Arial");
    }

    protected override void Draw(GameTime gameTime)
    {
      GraphicsDevice.Clear(Color.Black);
      spriteBatch.Begin();
      spriteBatch.DrawString(font, "Привет Хабрахабр!", new Vector2(120, 400), Color.White);
      spriteBatch.End();
      base.Draw(gameTime);
    }




    Поворот экрана



    Но вот какая незадача: ориентация экрана у нас не переключается при смене ориентации девайса в пространстве, а играть на экранчике с соотношением сторон 480/800 не очень удобно. Пока в XNA отсутствуют средства для разворота экрана, придется выкручиваться самим. В этом нам поможет RenderTarget2D. Фактически это текстура заданного нами размера, в которую мы можем рисовать, а потом так же выводить на экран. Изменим функцию Draw: теперь отрисовка спрайтов идет не на экран, а в renderTarget.

    protected override void Initialize()
    {
      base.Initialize();
      renderTarget = new RenderTarget2D(GraphicsDevice, 800, 480, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
    }

    private void DrawSprites(GameTime gameTime)  //В этой функции мы рисуем в renderTarget размером 800x480
    {
      spriteBatch.Begin();
      spriteBatch.DrawString(font, "Привет Хабрахабр!", new Vector2(40, 40), Color.White);
      spriteBatch.End();
    }

    protected override void Draw(GameTime gameTime)
    {
      GraphicsDevice.SetRenderTarget(renderTarget);
      GraphicsDevice.Clear(Color.Black);  //Очищаем renderTarget
      DrawSprites(gameTime);      //Рисуем в него
      GraphicsDevice.SetRenderTarget(null);  //Возвращаем стандартный
      GraphicsDevice.Clear(Color.Black);
      spriteBatch.Begin();  //И здесь рисуем повернутую и отраженную текстуру из нашего renderTarget
      spriteBatch.Draw((Texture2D)renderTarget, Vector2.Zero, null, Color.White, -MathHelper.PiOver2, new Vector2(800, 0), 1, SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically, 0);
      spriteBatch.End();
      base.Draw(gameTime);
    }


    Что мы получили:



    Наша «игра» определенно нуждается в стартовой заставке: добавим к проекту title.png. Мы будем выводить эту картинку на экран 4 секунды после запуска игры.
    Texture2D title;

    protected override void LoadContent()
    {
      spriteBatch = new SpriteBatch(GraphicsDevice);
      font = Content.Load<SpriteFont>("Arial");
      title = Content.Load<Texture2D>("Title");
    }

    private void DrawSprites(GameTime gameTime)
    {
      spriteBatch.Begin();
      if (gameTime.TotalGameTime.TotalSeconds > 4)
        spriteBatch.DrawString(font, "Привет Хабрахабр!", new Vector2(40, 40), Color.White);
      else
        spriteBatch.Draw(title, Vector2.Zero, Color.White);
      spriteBatch.End();
    }




    3D графика


    Нам понадобится класс камеры, отвечающий за работу с матрицами и обработку пользовательского ввода, про создание камеры можете почитать на gamedev.ru. В рамках этой статьи интерес представляет только функция UpdateTouch, отвечающая за работу с сенсорным вводом. Исходный код камеры целиком можно посмотреть по этой ссылке. Рекомендую выносить логику, классы для хранения данных в отдельную Dll. Для этого добавим к решению новый проект «WindowsPhoneGameDemoObjects»

    private void UpdateTouch(GameTime gameTime)
    {
      TouchCollection tc = TouchPanel.GetState();  //Получаем все касания
      if (tc.Count > 0)
      {
        TouchLocation current = tc[0]; //Получаем первое касание
        TouchLocation prev;
        if (current.TryGetPreviousLocation(out prev))  //Если это касание было и в прошлом цикле обновления
        {
        //То рассчитываем приращения углов
          Angle.X -= MathHelper.ToRadians((current.Position.X - prev.Position.X) * turnSpeed); // pitch
          Angle.Y += MathHelper.ToRadians((current.Position.Y - prev.Position.Y) * turnSpeed); // yaw

          while (Angle.Y > MathHelper.Pi * 2)
            Angle.Y -= MathHelper.Pi * 2;
          while (Angle.Y < -MathHelper.Pi * 2)
            Angle.Y += MathHelper.Pi * 2;

          if (Angle.X > maxPitch)
            Angle.X = maxPitch;

          if (Angle.X < -maxPitch)
            Angle.X = -maxPitch;

          float time = (float)gameTime.ElapsedGameTime.TotalSeconds;
          Vector3 forward = -Vector3.Normalize(new Vector3((float)Math.Sin(-Angle.Y),
            (float)Math.Sin(Angle.X),
            (float)Math.Cos(-Angle.Y)));
          if (DenyVerticalMovement)
          {
            forward = new Vector3(forward.X, 0, forward.Z);
            forward.Normalize();
          }
          Position += forward * movementSpeed * time;
        }
        else
          touchStartTime = gameTime;
      }
    }


    Отмечу, что камера унаследована от DrawableGameComponent — т.е. если мы добавим камеру в список компонентов нашей игры (this.Components.Add(camera);) то метод Update будет вызываться автоматически. Напишем классы объекта и сцены и добавим в проект модель, которую будем рендерить.

    public class Entity
    {
      public Matrix World //Матрица мира объкта
      {
        get
        {
          return Matrix.CreateScale(Scale)
               * Matrix.CreateRotationY(Rotation.Y)
               * Matrix.CreateRotationX(Rotation.X)
               * Matrix.CreateRotationZ(Rotation.Z)
               * Matrix.CreateTranslation(Position)
               ;
        }
      }
      public Model Model; //Модель объекта
      public Vector3 Scale = Vector3.One; //Названия переменных говорят сами за себя :)
      public Vector3 Rotation;
      public Vector3 Position;

      public void Draw(Camera camera)
      {
        Matrix[] transforms = new Matrix[Model.Bones.Count]; //Получаем матрицы трансформаций костей модели
        Model.CopyAbsoluteBoneTransformsTo(transforms);

        foreach (ModelMesh mesh in Model.Meshes)  //И в цикле отрисовываем все элементы модели
        {
          foreach (BasicEffect be in mesh.Effects)
          {
            be.EnableDefaultLighting();
            be.PreferPerPixelLighting = true;
            be.Projection = camera.Projection;
            be.View = camera.View;
            be.World = World * mesh.ParentBone.Transform;
          }
          mesh.Draw();
        }
      }
    }

    public class Scene
    {
      public List<Entity> Entities = new List<Entity>();
      public void Load(ContentManager content)
      {
        Entities.Add(new Entity() { Model = content.Load<Model>("Scene\\Scene") });
      }

      public void Draw(Camera camera)
      {
        foreach (Entity e in Entities)
          e.Draw(camera);
      }
    }


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



    Классно? Да, но не каждому игроку захочется ощущать себя бесплотной тенью, нам не хватает коллизий. А что нужно чтобы обрабатывать коллизии? Правильно, геометрия. Только брать ее на ходу из данных моделей не очень-то удобно, поэтому напишем свой ContentProcessor. Это штука, которая специальным образом обрабатывает во время сборки проекта игровые ресурсы.
    В этом ContentProcessor-е мы будем вычленять из моделей данные о геометрии и хранить их отдельно. Добавим к решению новый проект «Content Pipeline Extension Library», и напишем класс ModelCollisionProcessor, отвечающий за обработку 3D моделей, Данный код взят из msdn-овских примеров, вся работа которую он выполняет — добавление к модели списка полигонов. Список полигонов модели хранится в ее свойстве Tag. В этом свойстве мы можем хранить произвольные данные.
    Чтобы задействовать написанный нами ContentProcessor нужно добавить ссылку на CollisionContentProcessor для проекта ресурсов игры и на вкладке свойств для каждой 3Д-модели в проекте установить значение ContentProcessor в ModelCollisionProcessor.



    Теперь, когда у нас есть данные о геометрии надо с ними что-то делать. Напишем класс для обработки столкновений. Я писал данный класс руководствуясь этой статьей на gamedev.ru.
    Создадим новый класс GroundCamera и унаследуем его от Camera, принцип работы таков: по земле у нас катится шар для которого мы обрабатываем коллизии, а на некоторой дистанции от него сверху приделана наша камера.
    Заменим Camera на GroundCamera в Game1.cs, в методе Update будем вызывать функцию обработки столкновений. Для большей наглядности добавим в сцену еще один объект — детскую площадку. Ну и напоследок напишем простенький skybox.
    Что у нас получилось в итоге:



    Скачать исходники к статье.

    Что осталось за рамками этой статьи:
    • Звук
    • Шейдеры
    • Анимации
    • Мультитач
    • ИИ

    Если эта тема будет интересна хабрасообществу, обещаю написать еще парочку статей по XNA на Windows Phone.
    Что почитать:
    http://creators.xna.com
    http://blogs.msdn.com/shawnhar/default.aspx
    http://gamedev.ru

    Статья участвует в Конкурсе
    И пожалуйста, проголосуйте за статью Здесь
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      +14
      Вот это — СТАТЬЯ! Тема раскрыта полностью, показательных материалов в достатке. Сам топик — свежачок.

      Не то что понравилось, а вообще зацепило. Пошёл качать плагины. Жду ещё.
        +2
        да, очень интересно. качаю себе Visual Studio 2010 и хочу наконец-то вплотную заняться XNA и разработкой для WP7.
          0
          Жаль что в RTM версии VS2010 нету возможности создавать приложения под мобильные устройства.
          А CTP версия Windows Phone Developer Tools как я понял несовместима с VS2010 RTM
            0
            ну, я думаю, это быстро поправят. иначе будет много недовольных. мое предположение — в течение этой недели
          +1
          На данном этапе все таки думаю, что Windows Phone шейдеры пока не нужны, но на 100% можно сказать, что со временем они там будут)
          А статья отличная.
            0
            Хм… под iPhone писал, под Android писал, под WinPhone — не писал.
            Спасибо за статью (ушел качать студию).
              0
              все таки интересно, будут ли сопоставимы по производительности нативные приложения и на .Net Framework?)
                0
                Ходят слухи, что под wp7 будут запускаться только .net приложения.
                  +1
                  Это не слухи, это реальность.
                  0
                  Они и так сопоставимы, если смотреть по приложениям в мире Windows 7 (x86, x64).
                  +2
                  Статья очень интересная как по содержанию, так и по способу подачи. Пожалуйста, продолжайте!
                    –23
                    Смысл что-то под него писать? Мне кажется что виндовс фон будет столь же «успешен», как и zune.
                    Кстати XNЯ — че то мне в этом слове знакомо)))
                      +7
                      Вы аморал… и троль.
                        –8
                        Конечно, я моральный урод, ведь я не такой как вы, и не писаю от радости в штаны при упоминании MS )) А если серьезно, оценил ваш юмор)
                        +4
                        Кстати, zune hd — очень достойный аппарат. Жаль, что m$ не продаёт его в России.
                        0
                        Спасибо за статью.
                          0
                          Спасибо за статью, думаю стоит продолжить цикл.
                            0
                            Если кто-то из читателей-студентов пробовал программировать под Win Phone 7 — до 24 мая можно подать это приложение на конкурс. Подробности — habrahabr.ru/blogs/ms_for_students/93850/

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое