Пишем «Змейку» для Windows Phone 7

в 7:48, , рубрики: nokia, windows phone 7, Блог компании Nokia, разработка под windows phone, разработка под wp7

Об авторе: Паси Маннинен разрабатывает мобильные и веб-приложения, является Чемпионом Nokia Developer и Профессионалом Adobe Community, сертифицированным экспертом и преподавателем Adobe по Flex, Flash и Flash Mobile. Окончил магистратуру Университета Ювяскюля (одного из крупнейших вузов Финляндии) по специальностям «Прикладная математика» и «Компьютерные науки».

Введение

В этой статье мы покажем вам как создать простую версию Змейки для Windows Phone. В ней можно будет перемещать змейку в разных направлениях с помощью жестов. Приложение состоит из экрана меню, самой игры и финального экрана с результатами игры.

Пишем «Змейку» для Windows Phone 7

Windows Phone 7.1 SDK

Для разработки приложений для устройств под управлением Windows Phone 7, вам нужно установить Windows Phone 7.1 SDK. Вы можете загрузить его последнюю версию здесь.

Windows Phone Game

Чтобы начать разработку новой Windows Phone Game, запустите Microsoft Visual studio, создайте новый проект и выберите Windows Phone Game (4.0) Template. Мы будем использовать в качестве имени проекта SnakeIt.

image

В этом примере мы будем пользоваться C# для разработки нашей игры.

Подготовка изображений для игры

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

Еда

Щёлкните правой кнопкой мыши по Content в обозревателе решения проекта SnakeIt и выберите Add > New Item…
Выберите Bitmap File и назовите его FootTile.bmp

image

Нарисуйте зелёный прямоугольник размером 25x25 для обозначения еды. Не забудьте сохранить файл.

image

Змейка

Используйте ту же самую технику для рисования красного квадратика самой змейки. Назовите изображение SnakeTile.bmp.

Игровой шрифт

Мы можем создать шрифт для игры тем же способом, каким мы пользовались для создания изображений. Щёлкните правой кнопкой мышки по содержимому проекта в обозревателе решения, выберите Add > New Item…
Выберите Sprite Font и назовите его Segoe20.spritefont.

image

Дважды щёлкните по шрифту в обозревателе проектов и установите его размер в значение 20. Не забудьте сохранить шрифт.

Создание класса Food для еды

Щёлкните правой кнопкой мышки по содержимому проекта в обозревателе решения, выберите Add > New Item… Выберите Code в списке установленных шаблонов и создайте класс Food.

image

Данный класс хранит информацию о Texture (текстуре, типе) и Position (положении). Размеры width и height используются для определения столкновений в классе Game1.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
 
namespace SnakeIt
{
    class Food
    {
        // Texture representing the food
        public Texture2D Texture;
 
        // Position of the food relative to the upper left side of the screen
        public Vector2 Position;
 
        // Get the width of the food
        public int Width
        {
            get { return Texture.Width; }
        }
 
        // Get the height of the food
        public int Height
        {
            get { return Texture.Height; }
        }
 
        // Set Food Texture and Position
        public void Initialize(Texture2D texture, Vector2 position)
        {
            Texture = texture;
            Position = position;
        }
 
        // Draw Food to the Screen
        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(Texture, Position, null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);
        }
    }
}

Создание класса Snake

Создайте класс Snake так же, как вы создавали класс Food.

Используемые пространства имён

Мы будем использовать XNA Framework и класс List из Collections для обработки всех плиток для нашей змейки.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
Свойства

Для определения змейки зададим несколько её свойств. Мы будем загружать изображение змейки в качестве текстуры Texture и устанавливать начальное положение змейки в середине экрана (это мы реализуем позже в классе Game1). Все ячейки для змейки будут храниться в списке snakeTiles. Направление движения змейки будет управляться в классе Game1 с помощью жестов.

// Текстура змейки
public Texture2D Texture;
 
// Начальное положение
public Vector2 Position;
 
// Ячейки для замейки
public List<Tile> snakeTiles;
 
// Инкремент и скорость змейки
public float speed;
const float speedIncrement = 0.2f;
 
// Направление змейки // 1==влево, 2==вверх, 3==вправо, 4==вниз
public int direction;
 
// определено пересечение змейки с едой -> нужно добавить новую ячейку к змейке
public bool newTile;
Инициализация

Game1 будет инициализировать объект Snake. Текстура змейки будет загружена, её начальное положение будет установлено. Все свойства змейки будут сброшены в свойства по умолчанию при первом запуске и тогда, когда игрок захочет пройти игру снова. По умолчанию змейка движется на 5 пикселей влево и создаётся её головная ячейка.

// Инициализация змейки
 public void Initialize(Texture2D texture, Vector2 position)
{
       Position = position;
       Texture = texture;
       Reset();
}
 
// Сброс (новая игра)
public void Reset()
{
      speed = 5f;
      direction = 1;
      Tile tile = new Tile(Texture, Position);
      snakeTiles = new List<Tile>();
      snakeTiles.Add(tile);
      newTile = false;
}
Обновление

Игра будет вызывать метод для обновления змейки каждый новый игровой шаг. В этом методе мы находим положение головы змейки (это первая ячейка, содержащаяся в списке snakeTiles). Если нам нужно добавить новую ячейку к змейке (то есть, когда еда была съедена), мы создаём новую ячейку для headPostion и добавляем её к списку snakeTiles. Далее мы перемещаем только голову нашей змейки, увеличивая таким образом её размер. Если больше не предвидится новых ячеек для змейки, то мы будем перемещать только последнюю ячейку к «голове» змейки, а саму змейку переместим на новое положение.

public void Update() 
{
            // Положение головы змейки
            Vector2 headPosition = snakeTiles[0].Position;
 
            // Добавляем новую ячейку
            if (newTile)
            {
                // Создаём новую ячейку
                Tile tile = new Tile(Texture, headPosition);
                // Вставляём её в SnakeTiles (после HeadPosition, но с теми же координатами на экране)
                snakeTiles.Insert(1, tile);
            }
            // Перемещаем последнюю ячейку туда, где была "голова" змейки
            else if (snakeTiles.Count > 1)
            {
                Tile last = snakeTiles[snakeTiles.Count - 1];
                last.Position = headPosition;
                snakeTiles.RemoveAt(snakeTiles.Count-1);
                snakeTiles.Insert(0, last);
            }
 
            // Увеличиваем скорости при поедании вкусняшки
            if (newTile)
            {
                speed += speedIncrement;
                newTile = false;
            }
 
            // Перемещаем "головы" змейки на новое местоположение
            switch (direction)
            {
                case 1: snakeTiles[0].Position.X -= speed; break;
                case 2: snakeTiles[0].Position.Y -= speed; break;
                case 3: snakeTiles[0].Position.X += speed; break;
                case 4: snakeTiles[0].Position.Y += speed; break;
            }        
 }
Рисование

Каждая ячейка змейки рисуется в методе Draw(). Данный метод вызывается из класса Game1.

public void Draw(SpriteBatch spriteBatch)
{
            for (int i = 0; i < snakeTiles.Count; i++)
            {
                spriteBatch.Draw(snakeTiles[i].Texture, snakeTiles[i].Position, null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);
            }
}
Класс Tile (вложенный класс по отношению к классу Snake)

Создадим вложенный класс Tile. Данный класс будет определять ячейку змейки. У каждой ячейки есть положение Position и текстура Texture.

class Tile
{
        public Vector2 Position;
        public Texture2D Texture;
 
        public Tile(Texture2D texture, Vector2 position) {
            Position = position;
            Texture = texture;
        }
 
        // Ширина змейки
        public int Width
        {
            get { return Texture.Width; }
        }
 
        // Высота змейки
        public int Height
        {
            get { return Texture.Height; }
        }
}

Класс Game1

Класс Game1 является основным для игр на основе Windows Phone XNA Games. Здесь происходит инициализация и загрузки содержимого нашей игры. Жесты и определение столкновений используется в методе игры под названием Update method. Draw проверяет режим игры по свойству GameMode и рисует соответствующий экран.

Пространства имён

Используются обычные классы XNA и Touch для определения жестов.

using System;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
Свойства

Мы используем класс GraphicsDeviceManage для получения информации о текущем дисплее. Класс SpriteBatch используется для отображения информации на экран. Мы решили использовать разные игровые режимы для контролирования происходящего через метод Draw(): для рисования меню, самой игры или режима завершения игры. Это очень простой способ для обработки рисования разных режимов на экран. Помимо этого, возможен покомпонентный подход для решения этой задачи (см. ссылку: Game State Management). Текущий игровой режим хранится в свойстве Mode, всевозможные режимы определены как перечисление GameMode.

// Информация о текущем экране
private GraphicsDeviceManager graphics;
// Объекты для вывода на экран
private SpriteBatch spriteBatch;
// Змейка
private Snake snake;
// Еда для змейки
private Food food;
// Шрифт
private SpriteFont segoe20;
// Положение счёта на экране
private Vector2 scorePosition;
// Счёт
private float score;
private const float FOOD_POINTS = 20;
// Текущий игровой режим
 GameMode Mode;
// Все игровые режимы
enum GameMode {Menu, Running, Complete}
Конструктор

Для начала мы соединяемся с классом GraphicsDeviceManage и сообщаем нашу директорию со всеми спрайтами и шрифтами Content. Частота фреймов установлена по умолчанию в значение 30 кадров в секунду. Также нам нужно включить жесты Tap и Flick. Разные игровые режимы сменяются с помощью жеста Tap, движение змейки контролируется жестом Flick.

public Game1()
{
            // Менеджер графики
            graphics = new GraphicsDeviceManager(this);
 
            // Директория со спрайтами и шрифтами
            Content.RootDirectory = "Content";
 
            // 30 кадров в секунду по умолчанию для Windows Phone.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
 
            // Увеличиваем время жизни библиотеки при блокировании устройства.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
 
            // Определение жестов Tap и Flick
            TouchPanel.EnabledGestures = GestureType.Tap | GestureType.Flick;
 }
Инициализация игры

Метод Initialize позволяет проводить любые действия для инициализации игры и всех её ресурсов перед запуском самой игры. Здесь мы установим режим игры и создадим объекты для еды и самой змейки.

protected override void Initialize()
{
            // Устанавливаем режим игры в Menu, экран Menu будет нарисован методом Draw()
            Mode = GameMode.Menu;
            // Создаём змейку и еду для неё 
            snake = new Snake();
            food = new Food();
            // Начальные 0 очков
            score = 0f;
 
            base.Initialize();
}
Загрузка спрайтов и шрифтов

LoadContent вызывается один раз для загрузки всех файлов. Сначала мы соединяемся с графическим устройством с помощью SpriteBatch, чтобы позже мы могли рисовать объекты на экране. Затем мы загружаем и создаём текстуры для еды и змейки. В этом месте вызываются метод Initialize у соответствующих классов Snake и Food. Также мы создаём и наш шрифт и вычисляем положение для вывода количества очков.

protected override void LoadContent()
{
            // Создаём SpriteBatch для вывода текстур
            spriteBatch = new SpriteBatch(GraphicsDevice);
 
            // Текстура змейки и её расположение по центру экрана
            Vector2 snakePosition = new Vector2(
                GraphicsDevice.Viewport.TitleSafeArea.X + GraphicsDevice.Viewport.TitleSafeArea.Width / 2, 
                GraphicsDevice.Viewport.TitleSafeArea.Y + GraphicsDevice.Viewport.TitleSafeArea.Height / 2);
            snake.Initialize(Content.Load<Texture2D>("SnakeTile"), snakePosition);
 
            // Текстура еды и расположение её случайным образом на экране
            Vector2 foodPosition = RandPosition();
            food.Initialize(Content.Load<Texture2D>("FoodTile"), foodPosition);
 
            // Шрифт
            segoe20 = this.Content.Load<SpriteFont>("Segoe20");
 
            // Счётчик очков в левом верхнем углу
            scorePosition = new Vector2(20, 20);
}
Обновление игры

Update реализует логику обновления игры: проверка столкновений, реагирование на ввод и воспроизведение аудио. Здесь же мы обновляем положение змейки и проверяем на её столкновение с едой, если игра запущена. Все жесты проверяются каждый раз при вызове данного метода независимо от того, запущена игра или нет.

protected override void Update(GameTime gameTime)
{
            // Возможность выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
 
            if (Mode == GameMode.Running)
            {
                snake.Update();
                CheckCollision();
            }
 
            CheckGestures();
 
            base.Update(gameTime);
}
Проверяем столкновения

Положение еды и «головы» змейки вычисляется в соответствующие объекты Rectangle. После мы можем использовать встроенный метод intersect для определения пересечения прямоугольников. Мы увеличиваем в этом случае число очков и создаём спрайт еды в случайном новом положении, если определено поедание змейкой еды (также мы подсказываем змейке, что ей нужно обновиться и добавить себе новую ячейку). Игра считается оконченной, если голова змейки уходит за пределы экрана.

private void CheckCollision()
{
            // Использование встроенной функции для проверки пересечения
            int snakeHeadX = (int)snake.snakeTiles[0].Position.X;
            int snakeHeadY = (int)snake.snakeTiles[0].Position.Y;
            Rectangle rectangle1 = new Rectangle(snakeHeadX, snakeHeadY, snake.snakeTiles[0].Width,  snake.snakeTiles[0].Height);
            Rectangle rectangle2 = new Rectangle((int)food.Position.X, (int)food.Position.Y, food.Width, food.Height);
 
            // Змейка и её еда
            if (rectangle1.Intersects(rectangle2))
            {
                // Добавление очков
                score += FOOD_POINTS;
                // Меняем положение спрайта еды
                food.Position = RandPosition();
                // Новая ячейка для змейки
                snake.newTile = true;
            }
 
            // Змейка относительно границ экрана
            if (snakeHeadX < 0 || 
                snakeHeadY < 0 ||
                snakeHeadX + snake.snakeTiles[0].Width > GraphicsDevice.Viewport.Width ||
                snakeHeadY + snake.snakeTiles[0].Height > GraphicsDevice.Viewport.Height)
            {
                Mode = GameMode.Complete;
            }
}

Метод ниже используется для перемещения спрайта еды на новое случайное положение:

private Vector2 RandPosition()
{
            // Случайное положение для еды
            Random random = new Random();
            Vector2 position = new Vector2(
            GraphicsDevice.Viewport.TitleSafeArea.X + random.Next(GraphicsDevice.Viewport.TitleSafeArea.Width - 45) + 20,
            GraphicsDevice.Viewport.TitleSafeArea.Y + random.Next(GraphicsDevice.Viewport.TitleSafeArea.Height - 45) + 20);
            return position;
}
Проверка жестов

Жесты будут проверятся в методе Update каждый раз. Для начала мы проверяем, доступны ли новые жесты, и, если доступны, считываем их. Также получаем значение Delta, если жест типа Flick был определён. Змейка тогда будет перемещена в соответствующем направлении. Игрок не может переместить змейку в противоположное направление (игра будет окончена). Режим игры изменяется, если определяется жест Tap (начинает новую игру из меню или перезапускает игру).

private void CheckGestures()
{
            // Доступны ли жесты?
            while (TouchPanel.IsGestureAvailable)
            {
                // Считываем жест
                GestureSample gesture = TouchPanel.ReadGesture();
 
                // Flick или Tap? 
                switch (gesture.GestureType)
                {
                    case GestureType.Flick:
                        // В какую сторону двигали пальцем?
                        Single x = gesture.Delta.X, y = gesture.Delta.Y;
 
                        // По горизонтали или по вертикали?
                        if (Math.Abs(x) > Math.Abs(y))
                        {
                            // left or right
                            if (x < 0)
                            {
                                if (snake.direction == 3 && snake.snakeTiles.Count() > 1) Mode = GameMode.Complete;
                                else snake.direction = 1; // Налево
                            }
                            else
                            {
                                if (snake.direction == 1 && snake.snakeTiles.Count() > 1) Mode = GameMode.Complete;
                                else snake.direction = 3; // Направо
                            }
                        }
                        else
                        {
                            // Вверх или вниз?
                            if (y < 0)
                            {
                                if (snake.direction == 4 && snake.snakeTiles.Count() > 1) Mode = GameMode.Complete;
                                else snake.direction = 2; // Вверх
                            }
                            else
                            {
                                if (snake.direction == 2 && snake.snakeTiles.Count() > 1) Mode = GameMode.Complete;
                                else snake.direction = 4; // Вниз
                            }
                        }
                        break;
                    case GestureType.Tap:
                        // Режим меняется с Menu на Running
                        if (Mode == GameMode.Menu) 
                        {
                            Mode = GameMode.Running;
                        }
                        // Режим меняется с Complete на Running снова
                        else if (Mode == GameMode.Complete)
                        {
                            snake.Reset();
                            score = 0f;
                            Mode = GameMode.Running;
                        }
                        break;
                }
            }
}
Вывод игры на экран

Метод Draw вызывается тогда, когда игра себя рисует. В данном примере используется только синий фон. Данный метод проверяет режим игры и рисует соответствующий экран. Текст «FreeSnake, Tap the Screen to Start» выводится на экран, когда игра загружается первый раз. Змейка, еда для змейки и число очков выводится, когда пользователь играет в игру. Информация о набранных очках и предложение сыграть снова выводится при окончании игры.

protected override void Draw(GameTime gameTime)
{
            GraphicsDevice.Clear(Color.CornflowerBlue);
 
            // Начинаем рисовать
            spriteBatch.Begin();
 
            if (Mode == GameMode.Menu)
            {
                string GameStart = "FreeSnakenTap the Screen to Start";
                Vector2 stringSize = segoe20.MeasureString(GameStart);
                spriteBatch.DrawString(segoe20, 
                    GameStart, 
                    new Vector2((GraphicsDevice.Viewport.Width - stringSize.X) / 2, (GraphicsDevice.Viewport.Height - stringSize.Y) / 2), Color.White);
            }
            else if (Mode == GameMode.Running)
            {
                // Рисуем змейку
                snake.Draw(spriteBatch);
                // Рисуем еду
                food.Draw(spriteBatch);
                // Рисуем число очков
                spriteBatch.DrawString(segoe20, "Score:"+score, scorePosition, Color.White);
            }
            else if (Mode == GameMode.Complete)
            {
                snake.Draw(spriteBatch);
                string GameOver = "Game OvernScore : " + score +"nTap the Screen to Restart";
                Vector2 stringSize = segoe20.MeasureString(GameOver);
                spriteBatch.DrawString(segoe20,
                    GameOver,
                    new Vector2((GraphicsDevice.Viewport.Width - stringSize.X) / 2, (GraphicsDevice.Viewport.Height - stringSize.Y) / 2), Color.White);
            }
            // Прекращаем рисовать
            spriteBatch.End(); 
 
            base.Draw(gameTime);
}

Заключение

На этом примере мы показали основную информацию, которая требуется для создания игр с жестами для Windows Phone.

Исходный код получившейся игры можно найти здесь.

И ещё кое-что

Также мы напоминаем, что до 20 мая происходит приём заявок на конкурс «Лучшим приложениям бесплатное продвижение» от компаний Nokia и Microsoft. Отправляйте свои приложения и игры для Windows Phone 7, чтобы получить шанс выиграть смартфон Nokia Lumia 800 и бесплатное продвижение вашего приложения на площадках Nokia и Microsoft.

Автор: nokiaman

Поделиться

* - обязательные к заполнению поля