Работа с джойстиком Ни одно устройство не может сравниться с джойстиком и ему подобными устройствами по степени комфортности и удобства управления игрой. К исключениям можно отнести только руль, но это узконаправленное устройство, предназначенное для управления гоночными играми и различными леталками (рулем удобнее управлять самолетом или космическим кораблем). Клавиатура и мышь чаще всего используются в компьютерных играх. Если пользователь не заядлый игрок и специально не приобрел джойстик для своей компьютерной системы, то с большой долей вероятности на компьютере он будет применять только клавиатуру и мышь. В консольных приставках ни мышь, ни клавиатура, естественно, не используются, и здесь для управления игровым процессом пользователь имеет отличный и очень удобный джойстик. К Xbox 360 можно подключить клавиатуру, а в играх есть возможность использовать соответствующий код, но вряд ли пользователь захочет играть в игру на клавиатуре вместо джойстика. В первом разделе этой главы мы отвлечемся от создания игры и остановимся на изучении работы с джойстиком. В двух оставшихся разделах главы рассматриваются два проекта – Platform и PauseGame. В первом проекте под названием Platform в игру добавится ковер-самолет, который будет ловить объекты, падающие с неба. Для движения ковра-самолета влево или вправо используется джойстик. Само название проекта Platform было взято из второй (или первой) книги по XNA Game Studio Express. Второй проект этой главы PauseGame реализует механизм паузы в игре. Требования к этому механизму чрезвычайно просты: необходимо по нажатии одной из кнопок джойстика сначала остановить работу игры, а потом по нажатии все той же кнопки запустить игру вновь. При этом игра должна продолжать работать именно с того места, на котором была остановлена. 9.1. Кнопки и рычаги джойстика На джойстике приставки Xbox 360 имеется определенный набор кнопок и рычажков. Посмотрите на рис. 9.1, на котором представлена передняя часть джойстика. С правой стороны джойстика располагаются четыре кнопки с буквами A, B, X, Y. Эти кнопки с буквами используются для назначения на них различных команд. Например, кнопка с буквой А очень часто выполняет роль компьютерной клави ши Enter. Все четыре кнопки можно использовать в меню игры, где вы можете арисовать красочное руководство пользователя и написать игроку, например, следующее:
Рис. 9.1. Передняя часть джойстика
Для возврата в главное меню нажмите кнопку B или Нажмите кнопку А для начала игры За годы существования приставок Xbox и Xbox 360 выработались определенные правила, которые в какой-то мере регулируют назначение тех или иных кнопок на определенные команды. Если вы позапускаете на приставке несколько различных игр, то поймете, о чем идет речь. Кроме четырех кнопок с буквами, джойстик имеет кнопки Back, Start и так называемую кнопку Xbox Guide (большая круглая кнопка в центре джойстика с цифрами 1, 2, 3 и 4). Кнопки Back и Start могут назначаться соответственно на возврат в предыдущее окно программы и для старта игры. Кнопка Xbox Guide вызывает основное меню приставки, а также запускает или выключает консоль. Вам как программисту эта кнопка не доступна для назначения на нее различных команд. На джойстике также располагаются два рычажка и большая круглая полукнопка-полурычажок, которая в чем-то аналогична клавишам компьютерной клавиатуры со стрелочками, или командами Вверх, Вниз, Влево и Вправо. Два рычага применяются для управления главным персонажем игры, или если это меню, то возможно их использование в переходе по командам меню. В меню также можно использовать тот самый полурычажок-полукнопку. Два рычажка при управлении главным персонажем игры делят между собой две функции. Рычаг на джойстике, который находится с левой стороны (он и называется левым рычагом), служит для передвижения героя по игровой местности. Рычажок, который находится на джойстике несколько правее относительно первого рычага, чаще всего представляет в играх камеру или глаза, голову главного героя, которая поворачивается и смотрит в разные стороны (вверх, вниз, влево, вправо…). Дополнительно у джойстика имеются еще четыре кнопки, на рис. 9.1 они не попадают в наше поле зрения. На назначении этих кнопок мы остановимся подробнее в конце этого раздела. Как видите, кнопок и рычагов на джойстике хватает. Чтобы программисту было легче работать с имеющимися элементами управления, все кнопки и рычаги джойстика были рассортированы по категориям. Посмотрите на рис. 9.2, где показано устройство джойстика с точки зрения программиста.
Рис. 9.2. Устройство джойстика с точки зрения программиста
Названия на рис. 9.2, приведенные для кнопок и рычагов, – это на самом деле названия структур в XNA Framework, с помощью которых программист должен управлять работой джойстика. Как видно из этого рисунка, в XNA Faramework имеются следующие структуры: * GamePadButtons – большинство кнопок джойстика попадают в юрисдикцию этой структуры. С помощью этой структуры можно определять нажатие и отпускание одной из кнопок джойстика; * GamePadDPad – это та самая полукнопка-полурычажок; * GamePadTriggers – две кнопки (левая и правая), похожие на спусковой механизм оружия, которые, как правило, для этих и подобных целей и используются; * GamePadThumbSticks – это два рычажка (левый и правый) для управления движением главного героя и камерой. Очевидно, что для получения событий с джойстика нам необходимо его постоянно опрашивать, или следить за тем, что пользователь делает с элементами управления. Для этих целей в XNA используется следующая конструкция кода, которая должна прямо или косвенно вызываться в игровом цикле. GamePadState currentState = GamePad.GetState(PlayerIndex.One); В этой строке кода создается объект currentState структуры GamePadState, который в каждый такт игры обновляет свое состояние с помощью метода GetState() класса GamePad. Таким образом, мы получаем возможность постоянно следить за всеми элементами управления джойстика непосредственно в игровом цикле или каждую долю секунды игры. В методе GetState() используется параметр PlayerIndex.One. Этот параметр назначает, к какому именно из джойстиков принадлежит объект currentState или какой из джойстиков в данный момент мы контролируем. В Xbox 360 вы можете одновременно подключить до четырех джойстиков, то есть одновременно в игре могут участвовать сразу четыре игрока. Соответственно в коде нам необходим механизм прослушивания каждого из четырех джойстиков. Если вы хотите создать в игре механизм прослушивания четырех джойстиков, то можно записать следующий блок кода: GamePadState currentState1 = GamePad.GetState(PlayerIndex.One); GamePadState currentState2 = GamePad.GetState(PlayerIndex.Two); GamePadState currentState3 = GamePad.GetState(PlayerIndex.Thre); GamePadState currentState4 = GamePad.GetState(PlayerIndex.Four); Думается, тут все ясно: PlayerIndex в каждом новом вызове имеет новый индекс, который соответствует англоязычному исчислению; один, два, три и четыре. Если вы в игре используете только один джойстик, то всегда необходимо начинать с первого индекса. Нельзя вызывать второй или третий джойстик без вызова первого, а точнее можно, но если к приставке подключен только один джойстик, то он по умолчанию PlayerIndex.One. В следующих подразделах рассматриваются способы работы с элементами управления джойстика. На диске в папке Chapter9\GamePadControl вы найдете проект, который показывает на практике работу всех кнопок и рычажков джойстика. В этом проекте я вывел на экран телевизора пять спрайтов. Каждый спрайт выполнен в виде таблички с названием той или иной структуры. Нажимая кнопки или двигая рычаги джойстика, вы в программе по экрану этими действиями будете перемещать таблички с соответствующими названиями. Изучите следующие четыре подраздела, а потом запустите этот проект на приставке. 9.1.1. GamePadButtons Структура GamePadButtons позволяет определить, нажата кнопка или опущена. Если вы хотите определить, нажата кнопка или нет, то используется следующая запись: if(currentState.Buttons.A == ButtonState.Pressed) // заданные события Эта строка кода предназначена для кнопки с буквой А. Если вам нужно опре- делить, отпущена ранее нажатая кнопка А или нет, то необходимо использовать другую конструкцию кода: if(currentState.Buttons.A == ButtonState.Released) // заданные события В этой строке происходит уже проверка на отпускание нажатой ранее кнопки. Далее перечислены все имеющиеся кнопки структуры GamePadButton. Запус- тите с диска проект GamePadControl и убедитесь, что вы понимаете, о каких именно кнопках идет речь. if(currentState.Buttons.A == ButtonState.Pressed) // заданные события if(currentState.Buttons.B == ButtonState.Pressed) // заданные события if(currentState.Buttons.X == ButtonState.Pressed) // заданные события if(currentState.Buttons.Y == ButtonState.Pressed) // заданные события if(currentState.Buttons.Back == ButtonState.Pressed) // заданные события if(currentState.Buttons.Start == ButtonState.Pressed) // заданные события if(currentState.Buttons.LeftShoulder == ButtonState.Pressed) // заданные события if(currentState.Buttons.RightShoulder == ButtonState.Pressed) // заданные события if(currentState.Buttons.LeftStick == ButtonState.Pressed) // заданные события if(currentState.Buttons.RightStick == ButtonState.Pressed) // заданные события 9.1.2. GamePadDPadЧасто структуру GamePadDPad используют для перехода по меню или для других подобных операций. Как и в предыдущем разделе, вы можете определять для GamePadDPad как нажатие в одну из сторон, так и отпускание нажатия. В GamePadDPad всего имеются четыре направления: влево, вправо, вверх и вниз, которые соответствуют одноименным английским командам. if(currentState.DPad.Left == ButtonState.Pressed) // заданные события if(currentState.DPad.Right == ButtonState.Pressed) // заданные события if(currentState.DPad.Up == ButtonState.Pressed) // заданные события if(currentState.DPad.Down == ButtonState.Pressed) // заданные события 9.1.3. GamePadTriggers Структура GamePadTriggers представляет две кнопки (левую и правую), похожие на спусковой механизм оружия (рис. 9.2). Механизм получения событий с этих двух кнопок может на первый взгляд показаться необычным. Посмотрите на блок кода, приведенный ниже. if(currentState.Triggers.Left > 0.1f) // заданные события if(currentState.Triggers.Right > 0.9f) // заданные события Все станет на свои места, когда вы поймете механизм работы этих кнопок. Возьмем, к примеру, игру Need for Speed. В этой игре на правую кнопку Triggers.Right назначен акселератор, или, как мы говорим, педаль газа в машине. В машине степень нажатия на эту педаль может определять степень разгона или текущую скорость машины на дороге. Более того, у каждой машины свои возможности при разгоне. Одна машина может стартовать быстрее и за несколько секунд набрать большую скорость, другая, наоборот, набирает скорость медленнее. Так вот, принцип работы этих двух кнопок джойстика сводится к определению степени нажатия на эту самую кнопку. Для сравнения степени нажатия используются значения в пределах от –1.0f до 1.0f. В блоке кода в первой строке для левой кнопки Triggers.Left было определено значение больше 0.1 f. В этом случае малейшее прикосновение к этой кнопке инициирует действия, которые заданы на выполнение этой команды. Во втором случае, где if (currentState.Triggers.Right > 0.9f) пользователю придется уже посильнее нажать на кнопку, чтобы те или иные действия возымели место. Такая система определения степени нажатия кнопок позволяет добавлять в игру реализма, связанного, например, с той же педалью газа в машине. 9.1.4. GamePadThumbSticks Структура GamePadThumbSticks основана на том же принципе работы, что и рассмотренная выше структура GamePadTriggers, с той лишь разницей, что используются не кнопки джойстика, а рычаги. Поскольку у джойстика два рычага, то имеются два способа записи – ThumbSticks.Left и ThumbSticks.Right. Дополнительно вы можете определять, в какую из сторон, или в какую часть плоскости координатной системы, перемещать своего главного героя, например ThumbSticks.Left.X или ThumbSticks.Left.Y. Для рычажков степень нажатия в одну из сторон – не менее важная часть, как и для кнопок. Если, например, назначить на вращение камеры значение 0.99f, то камера будет медленно и несколько заторможенно реагировать на нажатие рычага джойстика. Если назначить значение 0.1f, то скорость реакции будет очень быстрой. В следующем примере дается блок кода для левого рычага ThumbSticks.Left. if(currentState.ThumbSticks.Left.X 0.3f) // заданные события if(currentState.ThumbSticks.Left.Y > 0.3f) // заданные события if(currentState.ThumbSticks.Left.Y 0.9f) // заданные события if(currentState.ThumbSticks.Right.Y > 0.9f) // заданные события if(currentState.ThumbSticks.Right.Y 9.2. Проект Platform По задумке, в нашей игре мы обязаны ловить всех людей, падающих с неба в пропасть, с помощью ковра-самолета. Дополнительно с неба, кроме людей, также падают различные запчасти сгоревшего самолета, и от этих частей нам необходимо уворачиваться. Ковер-самолет находится в нижней части экрана и парит горизонтально на одном уровне. Чтобы управлять ковром-самолетом, нам необходимо использовать джойстик приставки Xbox 360. Изображение ковра-самолета состоит из одного фрейма, а это значит, что применяется неанимированный спрайт (рис. 9.3). Сам рисунок располагается в рабочем каталоге проекта в папках Content\Textures, а механизм добавления графического изображения в проект остается прежним.
Рис. 9.3. Ковер/самолет
Начнем работу над проектом Platform и добавим в исходный код класса Game1 в область глобальных переменных объявление нового объекта platform класса Sprite. Sprite platform; Объект platform не будет анимированным, поэтому при создании объекта используется неанимированный конструктор класса Sprite. platform = new Sprite(); Затем в методе LoadGraphicsContent() класса Sprite происходит загрузка изображения ковра-самолета в игру. platform.Load(content, «Content\\Textures\\platform»); Далее ковру-самолету в методе Initialize() класса Sprite задается позиция на экране телевизора. platform.spritePosition = new Vector2(screenWidth / 2, screenHeight - 90); По осям X и Y выбирается место примерно посередине экрана и в его нижней части, но в целом это положение по оси Х может быть любым. Для реализации движения ковра-самолета на экране в классе Game1 создается метод MovePlatform(), код которого выглядит следующим образом: public void MovePlatform() { GamePadState currentState = GamePad.GetState(PlayerIndex.One); if (currentState.IsConnected) { if (currentState.ThumbSticks.Left.X 0.5f) platform.spritePosition.X += 15; if (platform.spritePosition.X screenWidth – platform.spriteTexture.Width -30) platform.spritePosition.X = screenWidth – platform.spriteTexture.Width – 30;} } В начале исходного кода метода MovePlatform() мы создаем новый объект currentState структуры GamePadState для получения текущего состояния джойстика. Метод GetState() в качестве параметра получает значение PlayerIndex.One, что означает получение событий с одного, а точнее с первого подключенного джойстика к приставке. Как вы помните, к приставке можно подключить одновременно четыре джойстика, но какой бы по счету лично для вас джойстик ни был, всегда первый подключенный джойстик к приставке идет под индексом PlayerIndex.One, второй – под индексом PlayerIndex.Two, третий – PlayerIndex.Three и четвертый – PlayerIndex.Four. Здесь важна очередность подключения джойстиков к приставке. Затем в методе MovePlatform() следует строка кода. if(currentState.IsConnected) Эта простейшая конструкция кода позволяет определить, подключен джойстик к приставке или нет. Своего рода подстраховка для системы, на случай если джойстик по каким-то причинам отключится от приставки, но код будет работать и без этой проверочной строки. Оставшаяся часть кода метода MovePlatform() состоит из двух независимых блоков исходного кода. В первом блоке происходит обработка нажатий левого рычажка GamePadThumbSticks.Left влево и вправо. Для обработки получаемых событий используется конструкция кода if/else. В переводе на русский язык эта конструкция кода обозначает следующее: Если GamePadThumbSticks.Left нажат в левую сторону, то: Позиция ковра-самолета по оси Х уменьшается на 10 пикселей А если GamePadThumbSticks.Left нажат в правую сторону, то: Позиция ковра-самолета по оси Х увеличивается на 10 пикселей Уменьшение или увеличение позиции по оси X приводит к движению коврасамолета по горизонтали влево и вправо. Заданная скорость в размере 10 пикселей за один кадр тестировалась много раз и оказалась наиболее удачным значением. Второй блок исходного кода обрабатывает ситуацию выхода ковра-самолета за пределы экрана с левой и правой сторон. Если такое условие имеет место, то по оси X спрайту присваивается значение, равное соответственно левой или правой кромке экрана + 30 пикселей (для красоты). В этом случае спрайт при столкновении с двумя крайними точками экрана остается на месте без движения. Делается это для того, чтобы ковер не уезжал из нашей области видимости и все время оставался на экране. Метод MovePlatform() вызывается в методе Update() класса Game1, где происходит постоянное обновление состояния игры. В конце исходного кода класса Game1 в методе Draw() ковер-самолет рисуется на экране. Полный исходный код класса Game1 представлен в листинге 9.1. На компакт-диске рассмотренный пример находится в папке Code\Chapter9\ Platform. //========================================================================= /// /// Листинг 9.1 /// «Программирование игр для приставки Xbox 360 в XNA Game Studio Express» /// Автор книги: Горнаков С. Г. /// Глава 9 /// Проект: Platform /// Класс: Game1 /// Добавляем ковер-самолет /// //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace Platform { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; SpriteBatch spriteBatch; Sprite[] sprite = new Sprite[5]; private Texture2D background; Random rand = new Random(); int screenWidth, screenHeight; int j = 0; Sprite platform; /// /// Конструктор /// public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1280; graphics.PreferredBackBufferHeight = 720; screenWidth = graphics.PreferredBackBufferWidth; screenHeight = graphics.PreferredBackBufferHeight; for (int i = 0; sprite.Length > i; i++) { sprite = new Sprite(12, 10); } platform = new Sprite(); } /// /// Инициализация /// i; i++) { sprite.spritePosition = new Vector2(rand.Next(10, screenWidth - 150), j = j - 300); } platform.spritePosition = new Vector2(screenWidth / 2, screenHeight - 90); base.Initialize(); } /// /// Загрузка компонентов игры /// protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); background = content.Load(«Content\\Textures\\background»); platform.Load(content, «Content\\Textures\\platform»); sprite[0].Load(content, «Content\\Textures\\0»); sprite[1].Load(content, «Content\\Textures\\1»); sprite[2].Load(content, «Content\\Textures\\2»); sprite[3].Load(content, «Content\\Textures\\3»); sprite[4].Load(content, «Content\\Textures\\4»); } } /// /// Освобождаем ресурсы /// protected override void UnloadGraphicsContent(bool unloadAllContent) { … } /// /// Обновляем состояние игры /// protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); double elapsed = gameTime.ElapsedGameTime.TotalSeconds; for (int i = 0; sprite.Length > i; i++) { sprite.UpdateFrame(elapsed); } MovePlatform(); MoveSprite(); base.Update(gameTime); } /// /// Движение спрайта по вертикали /// public void MoveSprite() { for (int i = 0; sprite.Length > i; i++) { sprite.spritePosition += sprite.speedSprite; if (sprite.spritePosition.Y > screenHeight) { sprite.spritePosition = new Vector2(rand.Next(50, screenWidth - sprite.spriteTexture.Width / 12 - 50), -500); } } } /// /// Движение ковра-самолета по экрану /// public void MovePlatform() { GamePadState currentState = GamePad.GetState(PlayerIndex.One); if (currentState.IsConnected) { if (currentState.ThumbSticks.Left.X 0.5f) platform.spritePosition.X += 15; if (platform.spritePosition.X screenWidth – platform.spriteTexture.Width -30) platform.spritePosition.X = screenWidth – platform.spriteTexture.Width – 30; } } /// /// Рисуем на экране /// protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.Black); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(background, new Vector2(0, 0), Color.White); for (int i = 0; sprite.Length > i; i++) { sprite.DrawAnimationSprite(spriteBatch); } platform.DrawSprite(spriteBatch); spriteBatch.End(); base.Draw(gameTime); } } } После того как вы откомпилируете и запустите проект Platform на приставке, вы увидите в нижней части экрана телевизора или монитора ковер-самолет, который с помощью джойстика можно передвигать влево или вправо (рис. 9.4). С неба будут падать различные предметы и люди, которые вы должны ловить ковром, но на этом этапе, естественно, поймать вам ничего и никого не удастся, поскольку в игре еще не описан механизм игровых столкновений. Этим мы займемся в следующей главе, а в этой главе добавим еще несколько строк кода для того, чтобы реализовать в игре режим паузы. 9.3. Проект PauseGame Вводим в проект PauseGame две новые булевы переменные, объявление которых происходит в области глобальных переменных, чтобы переменные были доступны на любом участке исходного кода класса Game1.
Рис. 9.4. Движение ковра/самолета в игре
private bool paused = false; private bool pauseKeyDown = false; Эти две переменные будут отражать соответственно состояние паузы в игровом процессе и состояние нажатия заданной кнопки джойстика. На каждой новой итерации игрового цикла (метод Update()) мы будем опрашивать джойстик и узнавать, нажата определенная кнопка или нет. Для паузы избрана стандартная в этом случае кнопка с адписью «Start». Чаще всего в консольных играх на эту кнопку назначают паузу, после чего появляется дополнительное меню, через которое можно выйти в меню игры, настроить опции игры или вернуться снова в игровой процесс. Если кнопка с надписью «Start» нажата, то в исходном коде переменной paused присваивается значение true. В этом случае создается код на проверку условия, чему именно равна переменная paused, и если ее значение равно true, то, значит, выполнение игрового цикла пропускается, если false – то игра продолжает свою работу (рис. 9.5). Посмотрите на исходный код класса Game1 проекта PauseGame в листинге 9.2.
Рис. 9.5. Схема работы паузы в игре
//========================================================================= /// /// Листинг 9.2 /// Исходный код к книге: /// «Программирование игр для приставки Xbox 360 в XNA Game Studio Express» /// Автор книги: Горнаков С. Г. /// Глава 9 /// Проект: PauseGame /// Класс: Game1 /// Пауза в игре /// //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace PauseGame { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; SpriteBatch spriteBatch; Sprite[] sprite = new Sprite[5]; private Texture2D background; Random rand = new Random(); int screenWidth, screenHeight; int j = 0; Sprite platform; private bool paused = false; private bool pauseKeyDown = false; /// /// Конструктор /// public Game1() { … } /// /// Инициализация /// /// Загрузка компонентов игры /// protected override void LoadGraphicsContent(bool loadAllContent) { … } /// /// Освобождаем ресурсы /// protected override void UnloadGraphicsContent(bool unloadAllContent) { … } /// /// Обновляем состояние игры /// protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); Pause(); if (paused == false) { double elapsed = gameTime.ElapsedGameTime.TotalSeconds; for (int i = 0; sprite.Length > i; i++) { sprite.UpdateFrame(elapsed); } MoveSprite(); MovePlatform(); } base.Update(gameTime); } /// /// Движение спрайта по вертикали /// public void MoveSprite() { … } /// /// Движение платформы по экрану /// public void MovePlatform() { … } /// /// Пауза в игре /// public void Pause() { if (GamePad.GetState(PlayerIndex.One).Buttons.Start == ButtonState.Pressed) { pauseKeyDown = true; } else if (pauseKeyDown) { pauseKeyDown = false; paused = !paused; } } /// /// Рисуем на экране /// protected override void Draw(GameTime gameTime) { … } Из блока кода, направленного на обработку паузы в состоянии игры, видно, что мы сначала определяем состояние переменной paused, и если ее значение равно false (нет паузы), то выполняется содержимое блока if. Если состояние булевой переменной paused изменилось на true (пауза в игре), то выполнение блока if пропускается, что равносильно необновлению состояния игры, то есть игра замирает. Чтобы выйти из этого состояния, достаточно второй раз нажать кнопку «Start» и в соответствии с исходным кодом метода Pause() значение булевой переменной paused изменяется на противоположное значение. Заметьте, что вызов метода Pause() в методе Update() совершается до строки кода выхода из программы. if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); Делается это намеренно, чтобы пользователь в режиме паузы мог свободно выйти из игры и закрыть программу. Далее >>