В предыдущем номере журнала «СИ» нами был сделан рисунок космического корабля, который мы успешно загрузили в игру и затем нарисовали на экране. Теперь пришло время добавить в игру новый исходный код, который поможет нам реализовать движения корабля в пространстве. Дополнительно в сегодняшнем материале мы также рассмотрим один из способов вывода на экран фонового рисунка (звездное небо) и его циклическое перемещение в игровом процессе.
Движение объектов в пространстве
Рисуя на экране телевизора двухмерное графическое изображение, вы задаете для изображения координаты по двум осям X и Y. Возьмем, например, наш космический корабль. Выводя корабль на экран, мы указываем точку вывода спрайта с помощью переменной shipPosition.
Vector2 shipPosition = new Vector2(600, 400);
// или
Vector2 shipPosition;
shipPosition.X = 600;
shipPosition.Y = 400;
Таким образом, позиция корабля в двухмерной плоскости телевизора задана по оси X в 600 пикселей (от левого верхнего угла экрана) и по оси Y в 400 пикселей вниз все от того же верхнего левого угла. В данном случае корабль находится в статическом состоянии. Если вы желаете переместить корабль на экране, например, сверху вниз (ось Y), то вам необходимо на каждой итерации игрового цикла или в один проход исходного кода метода Update() класса Game1 увеличивать позицию корабля на заданное количество пикселей, например:
shipPosition.Y = shipPosition.Y + 10;
// или
shipPosition.Y += 10;
Как вы помните, метод Update() функционирует в игре в цикличном режиме и вызывается за одну секунду как минимум тридцать раз (те самые 30-60… кадров за одну секунду). В приведенном блоке исходного кода, на каждой итерации игрового цикла позиция корабля будет увеличиваться на 10 пикселей по оси Y, а значит наш корабль будет перемещаться по экрану сверху вниз. В итоге получается, что значение в 10 пикселей – это скорость движения объекта в пространстве. Если задать меньшее значение скорости, то, соответственно, мы уменьшим скорость движения корабля, а большее значение, наоборот, увеличит скорость объекта. Задавая скорость только по одной оси координат, вы будете передвигать объект только вдоль этой выбранной оси. Для реализации движения по двум осям координат нужно изменять позицию объекта уже по двум осям одновременно. Допустим, скорость по каждой из осей X и Y равна +10 пикселей, тогда корабль будет перемещаться по экрану под углом в 45 градусов, одновременно сверху вниз и слева направо. Ко всему прочему, вы можете также использовать и отрицательные значения скорости, в этом случае позиция корабля будет уменьшаться, например:
Vector2 shipPosition = new Vector2(600, 400);
...
protected override void Update(GameTime gameTime)
{
shipPosition.Y -= 10;
}
В этом блоке кода движение корабля происходит по оси Y, но уже снизу вверх. То есть мы отнимаем от текущего местоположения корабля те самые 10 пикселей по оси Y. Посмотрите на рис. 1, где представлены различные значения скорости корабля по двум осям координат. Этот рисунок поможет вам наглядно разобраться с движением объектов в пространстве в различных направлениях. Теперь давайте перейдем к изучению джойпада и рассмотрим методику управления объектами в играх.
Рис. 1рис. 2
Как работать с джойпадом
Джойпад приставки Xbox 360 содержит набор кнопок и рычажков, но мы изучим только те, которые будем использовать в игре. С правой стороны джойпада располагаются четыре кнопки с буквами A, B, X и Y. Дополнительно еще имеются две кнопки Back, Start и два рычажка, которые используются для управления главным персонажем игры или игровым меню. Обычно рычаг на джойпаде, который находится с левой стороны, служит для передвижения героя по игровой местности. Рычажок, который находится на джойпаде несколько правее относительно первого рычага, чаще всего представляет в играх камеру или глаза главного героя. Чтобы программисту было легче работать с элементами управления джойпада, кнопки и рычаги рассортированы по категориям (рис. 2). Приведенные на рис. 2 названия кнопок и рычагов на самом деле представляют названия структур XNA Framework, с помощью которых программист может организовать управление игрой. Чтобы получать события с джойпада, необходимо постоянно опрашивать джойпад или следить за действиями пользователя. Для этих целей в играх используется следующая конструкция кода, которая вызывается в методе Update():
В этой строке кода создается объект currentState структуры GamePadState, который обновляет свое состояние каждую долю секунды игры с помощью метода GetState() класса GamePad. Метод GetState() использует параметр PlayerIndex.One. Этот параметр назначает, к какому именно из джойпадов принадлежит объект currentState или какой из джойпадов в данный момент мы контролируем.
В Xbox 360 вы можете одновременно подключить до четырех джойпадов, то есть одновременно в игре могут участвовать сразу четыре игрока. Если вы хотите создать в игре механизм получения данных от всех четырех джойпадов сразу, то можно записать следующий блок кода:
Здесь в каждом новом вызове метода GetState() присутствует новый индекс, который соответствует англоязычному исчислению; один, два, три и четыре. Но если в игре используется только один джойпад, то всегда необходимо начинать именно с первого индекса. Сейчас давайте рассмотрим технику работы с кнопками и рычажками джойпада, которые мы будем использовать в игре.
GamePadButtons
Эта структура позволяет определить, нажата одна из кнопок джойпада или отпущена. Чтобы определить, нажата определенная кнопка или нет, необходимо использовать следующую запись:
if(currentState.Buttons.A == ButtonState.Pressed)
// действия
Эта строка кода определяет нажатие кнопки с буквой А. Для определения отпускания нажатой ранее кнопки используется уже другая запись:
Аналогичные действия можно назначить на любую кнопку джойпада, в этом случае вместо буквы А необходимо подставить буквы кнопок B, X, Y, Back или Start.
GamePadThumbSticks
Механизм получения событий с двух рычажков может на первый взгляд показаться весьма необычным. Дело в том, что структура GamePadThumbSticks основана на определении степени нажатия рычажков в одну из сторон. Посмотрите на пример исходного кода:
if(currentState.ThumbSticks.Left.X > 0.5f)
// действия
if(currentState.ThumbSticks.Right.X < -0.9f)
// действия
Поскольку у джойпада два рычага, то имеется два способа записи ThumbSticks.Left и ThumbSticks.Right. Также можно определять, в какую из сторон или в какую часть плоскости координатной системы перемещать своего главного героя, например ThumbSticks.Left.X или ThumbSticks.Left.Y. Для сравнения степени нажатия используются значения в пределах от -1.0f до 1.0f. Определение степени нажатия рычажков влияет на скорость реакции объекта или камеры в пространстве. Например, если назначить на вращение камеры значение 0.99f, то камера будет медленно и несколько заторможенно реагировать на нажатие рычага джойпада, если же назначить значение 0.1f, то скорость реакции будет очень быстрой. То есть фактически значение степени нажатия рычага в одну из сторон назначает скорость реакции выполнения той или иной задачи.
Рис. 3рис. 4
Движение корабля по экрану
Сейчас нам предстоит объединить знания, полученные из двух предшествующих разделов, и написать исходный код, позволяющий управлять кораблем в игре. Как вы уже знаете, для перемещения корабля в пространстве нам необходимо просто изменять его местоположение на экране, по одной или двум осям координат одновременно. Само изменение позиции корабля в игре должно происходить в методе Update(), и есть несколько способов решения этой задачи. Можно прямо в исходном коде метода Update() прописать блок кода, описывающий получения событий с джойпада. Также можно создать отдельный класс или прямо в исходном коде класса Game1 создать дополнительный метод, например с названием MoveShip(). Тогда в этом методе нужно описать всю механику работы с джойпадом, а затем вызвать метод непосредственно в игровом цикле или в методе Update(). Мы именно так и поступим. Откройте с компакт-диска новый проект Backround и перейдите в классе Game1 к методу MoveShip().
else if (shipPosition.X > screenWidth /*ширина экрана*/ -
ship.Width /*ширина корабля*/)
shipPosition.X = screenWidth - ship.Width;
// не даем выйти кораблю за края экрана по оси Y
if (shipPosition.Y < 0)
shipPosition.Y = 0;
else if (shipPosition.Y > screenHeight /*высота экрана*/ -
ship.Height /*высота корабля*/)
shipPosition.Y = screenHeight - ship.Height;
}
В начале исходного кода метода MoveShip() создается новый объект currentState структуры GamePadState для получения событий с джойпада. Оставшаяся часть кода метода MoveShip() состоит из двух независимых блоков исходного кода. В первом блоке происходит обработка нажатий левого рычажка GamePadThumbSticks.Left влево и вправо. Для обработки получаемых событий используется конструкция кода if/else. В переводе на русский язык эта конструкция кода обозначает следующее:
Если GamePadThumbSticks.Left нажат в левую сторону, то:
позиция космического корабля по оси Х уменьшается на 10 пикселей.
А если GamePadThumbSticks.Left нажат в правую сторону, то:
позиция космического корабля по оси Х увеличивается на 10 пикселей.
Уменьшение или увеличение позиции по оси X приводит к движению корабля по горизонтали влево и вправо. Заданная скорость в размере 10 пикселей за один кадр тестировалась много раз и оказалась наиболее удачным значением для данной игры. Что касается движения корабля по оси Y, то оно аналогично движению по оси X.
Второй блок исходного кода обрабатывает ситуацию выхода космического корабля за пределы экрана телевизора с левой, правой, верхней и нижней части экрана. Если не обрабатывать ситуацию выхода корабля за пределы зоны видимости, то вы элементарно потеряете корабль где-то в виртуальном пространстве. А так корабль при столкновении с крайними точками экрана просто остается на месте без движения. После того как мы создали новый метод MoveShip(), его нужно вызвать в игровом цикле, следующим образом:
protected override void Update(GameTime gameTime)
{
// Получаем события с джойпада
if (GamePad.GetState(PlayerIndex.One).Buttons.Back==ButtonState.Pressed)
this.Exit();
// движение корабля
MoveShip();
base.Update(gameTime);
}
Теперь на каждой итерации игрового цикла мы будем получать с джойпада события и следить за действиями пользователя в игре. Заметьте, что у нас по-прежнему осталась конструкция кода, получающая события с кнопки Back. Эту конструкцию кода можно также перенести в метод MoveShip() с небольшой модификацией, а можно оставить без изменений.
Фоновый рисунок
Фоновый рисунок в играх может быть реализован по-разному, например в одних играх фоновый рисунок находится в статическом состоянии и изменяется только с переходом на новый уровень. В других играх фон может двигаться в разные стороны или постоянно перемещаться в одном направлении, здесь все зависит от тех задач, которые необходимо решать в игре. Самый простой способ создания фона в игре – это использование большого графического изображения, размером в экран телевизора. В этом случае вам нужно просто вывести рисунок на экран позади всей игровой графики. Точка вывода рисунка – это два нулевых значения по обеим осям координат (рис. 3). Другой, более сложный способ формирования фонового рисунка заключается в создании полноценной игровой карты. В этом случае художник рисует большой набор разнородных элементов карты (например камни, строительные блоки, колонны, песок, трава…) и соединяет весь рисунок воедино, но так, чтобы все элементы были одинакового размера. Потом он создает в одном из редакторов игровую местность (карту), которая строится по принципу мозаики. В свою очередь, программист использует эту карту по назначению и добавляет в исходный код механизм вывода фонового рисунка на экран (рис. 4). На сайте клуба разработчиков игр по адресу http://creators.xna.com, можно найти пример реализации этого способа представления фонового рисунка на экране. Еще один интересный способ создания фонового рисунка заключается в заполнении всего пространства экрана телевизора одним-единственным графическим изображением (рис. 5). В этом случае нужно нарисовать кусок определенной местности, да так, чтобы, когда вы соберете все кусочки воедино, на стыках фона не было заметно, что он сделан из кусков. Мы как раз и будем использовать этот озвученный способ. Для этих целей в графическом редакторе было сделано простое изображение куска черного космического неба со звездами размером 256х256 пикселей. Я все-таки не художник, поэтому нарисовать нечто большое мне просто не под силу. Само изображение неба мы размножим в игре по всему экрану и заставим двигаться весь сформированный таким образом фон, в цикличном режиме сверху вниз, что позволит нам создать иллюзию движения корабля в космическом пространстве.
Рис. 5
Звездное небо
Итак, займемся космическим пространством. Перейдем к исходному коду класса Game1 и в области глобальных переменных добавим три новые строки кода:
public Texture2D background;
float scroll = 0;
int screenWidth, screenHeight;
Объект background класса Texture2D будет представлять в игре изображение космического неба background.png. Здесь механизм работы с 2D-графикой абсолютно идентичен механизму работы с кораблем, который мы рассмотрели в прошлом номере журнала «СИ». Во второй строке кода создается дополнительная переменная scroll. С помощью этой переменной мы будем перемещать фоновое изображение в пространстве, о чем поговорим чуть позже. В последней строке этого блока кода создаются две дополнительные переменные screenWidth и screenHight, которые будут содержать текущие размеры экрана. Объявив эти переменные в глобальной области класса Game1, мы далее в конструкторе этого класса инициализируем их значениями ширины и высоты экрана телевизора:
Впоследствии эти переменные будут использоваться для правильного отображения звездного неба на экране телевизора. Переходим в классе Game1 к методу LoadGraphicsContent() и загружаем в игру рисунок черного космического неба со звездами:
Так же как и в случае с космическим кораблем, для загрузки фонового рисунка используется метод Load(). В параметре этого метода указывается полный путь к графическому файлу background.png. Загрузив в игру рисунок звездного неба, можно переходить к методу Draw(). Как вы помните, он предназначен для рисования игровой графики на экране, причем все действия должны выполняться между вызовами Begin() и End(). Добавляем между этими двумя методами следующие строки кода:
for (int x = 0; x <= screenWidth/background.Width; x++)
{
for (int y = -1; y <= screenHeight/background.Height + 1; y++)
{
Vector2 positionBackground = new Vector2(x * background.Width, y * background.Height +((int)scroll) % background.Height);
Для начинающего программиста эта конструкция кода может на первый взгляд показаться несколько сложной. Вкратце суть работы этого кода заключается в том, чтобы с помощью циклов for заполнить экран телевизора изображением background.png по горизонтали и вертикали, согласно той методике, которая была озвучена ранее. Если вы сразу не разберетесь с этим кодом, то ничего страшного нет, отнеситесь к этому блоку кода как к черному ящику. Единственное, хочу обратить ваше внимание на два очень важных аспекта в использовании этого блока кода. Первый важный момент связан с очередностью вывода графики на экран. Если вы внимательно посмотрите на полный исходный код класса Game1 проекта Background, то в методе Draw() заметите, что приведенный блок кода звездного неба располагается до рисования корабля на экране – это очень важно! Здесь имеет место правило очередности рисования графики на экране. Помните: все то, что вызывается в методе Draw() позже, всегда накладывается поверх уже имеющейся графики. Если представить себе графику игры в виде слоев, то получится такая многослойная луковица, где каждая последующая строка кода перекрывает собой предыдущую. Поэтому очень важно рисовать космический корабль поверх звездного неба, а не наоборот, поскольку звездное небо тогда просто закроет собой корабль и его не будет видно на экране. Второй нюанс связан с переменной scroll. Эта переменная необходима нам для цикличного перемещения фона по экрану, поэтому дополнительно в методе Update() мы будем изменять ее значение на каждой итерации игрового цикла следующим образом:
protected override void Update(GameTime gameTime)
{
// Получаем события с джойпада
if (GamePad.GetState(PlayerIndex.One).Buttons.Back==ButtonState.Pressed)
К перемещению фона по экрану в методе Update() имеют отношение две строки кода, следующие после комментариев «Перемещаем фон сверху вниз». В этих строках мы сначала создаем дополнительную переменную speed, которая задает скорость движения звездного неба, а затем будем увеличивать эту переменную на каждой новой итерации игрового цикла. В итоге переменная scroll у нас постоянно обновляется или увеличивает свое значение, а поскольку эта переменная используется для задания точки вывода фона на экране в методе Draw(), то сам фон будет постоянно перемещаться по экрану сверху вниз с цикличной периодичностью. Таким вот нехитрым способом мы добиваемся иллюзии движения звездного неба в игре. Откомпилируйте проект Background, передайте его на Xbox 360 и посмотрите, как работает программа. На сегодня это все, ждите следующего номера журнала, в котором мы добавим в игру летящие навстречу кораблю метеориты и другие дополнительные элементы игровой графики. <<3-я часть «Игры своими руками»5-я часть «Игры своими руками»>>
1397 Прочтений • [Игра для Xbox 360 своими руками. Часть 4] [12.04.2012] [Комментариев: 0]