netlib.narod.ru< Назад | Оглавление | Далее >

Класс Input

В классе Input есть много свойств для облегчения доступа к состояниям всех наиболее используемых кнопок клавиатуры, мыши и игрового пульта (рис. 10.1). Класс также предоставляет несколько вспомогательных методов для управления вводом с клавиатуры и печати текста, но, скорее всего, большая часть кода класса Input в первых игровых проектах вам не понадобится. Он становится более полезным при увеличении размера ваших игр и появлении большего количества кода UI. Наиболее важный метод здесь Update, вызываемый в каждом кадре (это делается автоматически из метода Update класса BaseGame в начале каждого кадра).


Рис. 10.1

Рис. 10.1


Обратите внимание, что вся, относящаяся к мыши функциональность отсутствует, если вы запускаете XNA на Xbox 360, и вы даже не можете получить состояние мыши когда компилируете проект для Xbox 360 (классы просто отсутствуют). Таким образом, чтобы игра запускалась на Xbox 360, вы должны закомментировать весь относящийся к мыши код. Это не доставит удовольствия, особенно если у вас сотни вызовов функций мыши, разбросанных по всему вашему проекту. Намного лучшее решение — полностью скрыть состояния мыши (сделав их закрытыми) и обращаться к параметрам мыши только через свойства класса Input. Как видно на рис. 10.1 вы можете напрямую обращаться к состояниям игрового пульта и клавиатуры, если хотите самостоятельно проверить другую кнопку или клавишу, но для мыши вы можете обратиться только к предоставленным вам свойствам класса Input. Таким образом все эти свойства возвращают то, что вы ожидали бы на PC, а на Xbox 360 все они возвращают false или виртуальную позицию мыши. Вы можете получать и устанавливать положение мыши точно так же, как на PC, но пользователь не может управлять им напрямую.

Теперь один и тот же код для ввода с мыши, клавиатуры и игрового пульта может использоваться на обоих платформах. Цель XNA в том, чтобы 98% кода вело себя одинаково; добейтесь для вашего движка 100% результата.

Например, кнопка A на игровом пульте Xbox 360 или клавиши курсора на клавиатуре используются очень часто. Гораздо проще написать код, подобный показанному ниже, чтобы позволить осуществлять навигацию по пунктам меню с помощью клавиш перемещения курсора вверх и вниз на клавиатуре. Кроме того, эти вспомогательные свойства упрощают поддержку кнопок крестовины или даже управление меню с помощью левого джойстика игрового пульта. Если бы вы самостоятельно писали все проверки состояний, каждый раз, когда вам необходимо проверить нажатую в данный момент кнопку или клавишу, и была ли она нажата до этого, вы, скорее всего, свихнулись бы через какое-то время.

// Обработка ввода с игрового пульта, также позволяет ввод с клавиатуры
if (Input.GamePadUpJustPressed ||
    Input.KeyboardUpJustPressed)
{
  Sound.Play(Sound.Sounds.Highlight);
  selectedButton =
    (selectedButton + NumberOfButtons - 1) % NumberOfButtons;
} // if (BaseGame.GamePadLeftNowPressed)
else if (Input.GamePadDownJustPressed ||
         Input.KeyboardDownJustPressed)
{
  Sound.Play(Sound.Sounds.Highlight);
  selectedButton = (selectedButton + 1) % NumberOfButtons;
} // else if

К примеру, метод GamePauUpJustPressed делает следующее:

/// <summary>
/// Только что нажата клавиша "вверх" игрового пульта
/// </summary>
/// <returns>Bool</returns>
public static bool GamePadUpJustPressed
{
  get
  {
    return (gamePadState.DPad.Up == ButtonState.Pressed &&
            gamePadStateLastFrame.DPad.Up == ButtonState.Released) ||
           (gamePadState.ThumbSticks.Left.Y > 0.75f &&
            gamePadStateLastFrame.ThumbSticks.Left.Y < 0.75f);
  } // get
} // GamePadUpJustPressed

Код использует текущее состояние игрового пульта, которое устанавливается в начале каждого кадра, и состояние игрового пульта из предыдущего кадра, чтобы проверить, изменилось ли состояние кнопки. Как видите, если писать этот код снова и снова, каждый раз, когда вы хотите проверить состояние кнопки игрового пульта, можно сойти с ума. Для одного или двух раз прекрасно подойдет копирование и вставка, но если вы используете один и тот же код снова и снова, напишите вспомогательный метод или свойство, позволив ему выполнять за вас всю работу. В будущем, когда вы напишете больше кода UI, это значительно упростит проверку всех состояний ввода и работу с множеством различных устройств ввода.

Метод Update класса Input

Метод Update не делает ничего по-настоящему сложного, но это наиболее важная часть класса Input, поскольку все состояния последнего кадра копируются из предыдущих состояний, а затем вы получаете новые состояния для клавиатуры, мыши и игрового пульта. Чтобы помочь с относительными перемещениями мыши, добавлен код для их поддержки. Относительные перемещения мыши — это одна из вещей, не поддерживаемых в XNA, в отличие от классов DirectInput из DirectX, которая позволяет вам получать от мыши относительные данные вместо абсолютной позиции указателя мыши, то есть (–3.5, +7.0) вместо позиции (350, 802).

/// <summary>
/// Обновление, вызываемое из BaseGame.Update().
/// Отлавливает все новые состояния для клавиатуры,
/// мыши и игрового пульта
/// </summary>
internal static void Update()
{
#if XBOX360
  // На XBox360 нет поддержки мыши :(
  mouseDetected = false;
#else
  // Обработка переменных ввода с мыши
  mouseStateLastFrame = mouseState;
  mouseState = Microsoft.Xna.Framework.Input.Mouse.GetState();

  // Обновление mouseXMovement и mouseYMovement
  lastMouseXMovement += mouseState.X - mouseStateLastFrame.X;
  lastMouseYMovement += mouseState.Y - mouseStateLastFrame.Y;
  mouseXMovement = lastMouseXMovement / 2.0f;
  mouseYMovement = lastMouseYMovement / 2.0f;
  lastMouseXMovement -= lastMouseXMovement / 2.0f;
  lastMouseYMovement -= lastMouseYMovement / 2.0f;

  if (MouseLeftButtonPressed == false)
    startDraggingPos = MousePos;
  mouseWheelDelta = mouseState.ScrollWheelValue - mouseWheelValue;
  mouseWheelValue = mouseState.ScrollWheelValue;

  // Проверяем, была ли мышь перемещена в этом кадре, если она
  // еще не обнаружена. Это позволяет игнорировать мышь, даже когда
  // она была захвачена на машине Windows, если используется только
  // игровой пульт или клавиатура
  if (mouseDetected == false)
    mouseDetected = mouseState.X != mouseStateLastFrame.X ||
                    mouseState.Y != mouseStateLastFrame.Y ||
                    mouseState.LeftButton != mouseStateLastFrame.LeftButton;
#endif

  // Обработка ввода с клавиатуры
  keysPressedLastFrame = new List<Keys>(keyboardState.GetPressedKeys());
  keyboardState = Microsoft.Xna.Framework.Input.Keyboard.GetState();

  // И, наконец, захватываем ввод с пульта XBox (используется только 1 игрок)
  gamePadStateLastFrame = gamePadState;
  gamePadState = Microsoft.Xna.Framework.Input.GamePad.GetState(
                                                     PlayerIndex.One);

  // Обработка вибрации
  if (leftRumble > 0 || rightRumble > 0)
  {
    if (leftRumble > 0)
      leftRumble  -= 1.5f * BaseGame.MoveFactorPerSecond;
    if (rightRumble > 0)
      rightRumble -= 1.5f * BaseGame.MoveFactorPerSecond;
    Microsoft.Xna.Framework.Input.GamePad.SetVibration(
                           PlayerIndex.One, leftRumble, rightRumble);
  } // if (leftRumble)
} // Update()

Вы можете обратить внимание на код для вибрации игрового пульта в конце метода Update, который позволяет вам использовать показанный ниже метод для включения на время вибрации игрового пульта, и больше ни о чем не беспокоиться. Метод Update автоматически уменьшает эффект вибрации, если GamePadRumble не вызывается снова в следующем кадре. Вы можете также использовать затихающий эффект вибрации для незначительных событий. Играть в XNA Shooter намного интереснее, когда вибрация включена.

/// <summary>
/// Вибрация игрового пульта
/// </summary>
/// <param name="setLeftRumble">Установка левого мотора</param>
/// <param name="setRightRumble">Установка правого мотора</param>
public static void GamePadRumble(
            float setLeftRumble, float setRightRumble)
{
  leftRumble  = setLeftRumble;
  rightRumble = setRightRumble;
} // GamePadRumble(setLeftRumble, setRightRumble)

Активные зоны

В классе Input также есть несколько методов, помогающих вам определить, находится ли мышь над элементом UI, таким как кнопка меню. Используйте следующий метод, чтобы проверить, проходит ли указатель мыши над элементом UI. Метод также автоматически воспроизводит для вас звук подсветки, если вы только что вошли в заданный прямоугольник.

/// <summary>
/// Мышь в прямоугольнике
/// </summary>
/// <param name="rect">Прямоугольник</param>
/// <returns>Bool</returns>
public static bool MouseInBox(Rectangle rect)
{
#if XBOX360
  return false;
#else
  bool ret = mouseState.X >= rect.X &&
             mouseState.Y >= rect.Y &&
             mouseState.X < rect.Right &&
             mouseState.Y < rect.Bottom;
  bool lastRet = mouseStateLastFrame.X >= rect.X &&
                 mouseStateLastFrame.Y >= rect.Y &&
                 mouseStateLastFrame.X < rect.Right &&
                 mouseStateLastFrame.Y < rect.Bottom;

  // Произошла подсветка?
  if (ret && lastRet == false)
    Sound.Play(Sound.Sounds.Highlight);

  return ret;
#endif
} // MouseInBox(rect)

Например, вы можете использовать код, чтобы позволить выбирать элементы вашего главного меню, как показано на рис. 10.2. Как я говорил раньше, стремитесь к простоте. Меню должно только показывать все важные части вашей игры. Фоновая текстура также должна быть хорошей картинкой из вашей игры — некий графический коллаж или работа, исполненная на заказ вашим профессиональным художником (если он у вас есть).


Рис. 10.2

Рис. 10.2


Чтобы заставить это работать, вы используете код, подобный следующему фрагменту:

// Визуализация фона
game.RenderMenuBackground();

// Показываем все кнопки
int buttonNum =0;
MenuButton [] menuButtons ==new MenuButton []
  {
    MenuButton.Missions,
    MenuButton.Highscore,
    MenuButton.Credits,
    MenuButton.Exit,
    MenuButton.Back,
  };
foreach (MenuButton button in menuButtons)
  // Не визуализируем кнопку возврата
  if (button !=MenuButton.Back)
  {
    if (game.RenderMenuButton(button,buttonLocations [buttonNum]))
    {
      if (button == MenuButton.Missions)
        game.AddGameScreen(new Mission());
      else if (button == MenuButton.Highscore)
        game.AddGameScreen(new Highscores());
      else if (button == MenuButton.Credits)
        game.AddGameScreen(new Credits());
      else if (button == MenuButton.Exit)
        quit = true;
      }//if
      buttonNum++;
      if (buttonNum >=buttonLocations.Length)
        break;
      }//if

   //Горячие клавиши: M=Миссия, H=Рекорды, C=Авторы, Esc=Выход
   if (Input.KeyboardKeyJustPressed(Keys.M))
     game.AddGameScreen(new Mission());
   else if (Input.KeyboardKeyJustPressed(Keys.H))
     game.AddGameScreen(new Highscores());
   else if (Input.KeyboardKeyJustPressed(Keys.C))
     game.AddGameScreen(new Credits());
   else if (Input.KeyboardEscapeJustPressed)
     quit = true;

   //Нажатие кнопок вверх/вниз на пульте XBox меняет выбранный пункт
   if (Input.GamePadDownJustPressed)
   {
     xInputMenuSelection =
       (xInputMenuSelection + 1) % buttonLocations.Length;
     SelectMenuItemForXInput();
   }//if (Input.GamePad)
   else if (Input.GamePadUpJustPressed)
   {
     if (xInputMenuSelection <= 0)
       xInputMenuSelection = buttonLocations.Length;
     xInputMenuSelection--;
     SelectMenuItemForXInput();
   }//if (Input.GamePad)

Если у вас больше элементов управления, как на экране настроек, где устанавливаются и снимаются флажки, обрабатываются полосы прокрутки или редактируемые поля, код становится несколько сложнее, и, прежде чем вы начнете копировать и вставлять один и тот же код снова и снова, добавьте класс UI который будет для вас обрабатывать весь код UI. Если вы используете редактируемое поле один раз, можно написать код прямо в классе Options, как я делал для Rocket Commander XNA. Но если вы используете его несколько раз, код должен быть подвергнут рефакторингу. Обработка текстовых полей в XNA также несколько сложнее, поскольку вам необходимо реализовать собствненные методы обработки ввода с клавиатуры для текста.

Ввод текста в XNA

Вы могли обратить внимание, что в классе Input нет переменной keyboardStateLastFrame, поскольку в отличие от MouseState и GamePadState вы не можете использовать структуру KeyboardState снова в следующем кадре; данные будут неверны. Причина этого в том, что структура KeyboardState использует для всех клавиш внутренний список, который в следующем кадре используется снова и перезаписывается при вызове Keyboard.GetState новыми состояниями всех клавиш. Чтобы устранить эту проблему вы должны хранить свой собственный список клавиш, который клонируется каждый раз, когда вы устанавливаете его по состоянию клавиш в последнем кадре. Использование конструктора обобщенного класса List автоматически клонирует для вас все элементы.

Использование этого списка работает почти так же, как непосредственное использование состояния клавиатуры. В качестве примера взгляните на свойство KeyboardSpaceJustPressed класса Input:

/// <summary>
/// Только что нажат пробел?
/// </summary>
/// <returns>Bool</returns>
public static bool KeyboardSpaceJustPressed
{
  get
  {
    return keyboardState.IsKeyDown(Keys.Space) &&
           keysPressedLastFrame.Contains(Keys.Space) == false;
  } // get
} // KeyboardSpaceJustPressed

Остальная часть класса Input очень похожа на тот код, который вы уже видели, за исключением методов для ввода текста с клавиатуры, которые я упомянул минуту назад. Чтобы получить ввод с клавиатуры для текстового поля таким же образом, как вы делаете это с обычным текстовым полем Windows, был написан следующий тестовый модуль:

/// <summary>
/// Проверка ввода текста с клавиатуры
/// </summary>
public static void TestKeyboardChatInput()
{
  // Лучшая версия с помощью Input.HandleKeyboardInput!
  string chatText = "";
  TestGame.Start("TestKeyboardChatInput",
    null,
    delegate
    {
      TextureFont.WriteText(100, 100,
        "Your chat text: " + chatText +
        // Добавляем мерцающий |
        ((int)(BaseGame.TotalTime / 0.35f) % 2 == 0 ? "|" : ""));
      Input.HandleKeyboardInput(ref chatText);
    },
    null);
} // TestKeyboardChatInput()

Тестовый модуль просто отображает строку chatText и мерцающий символ | позади нее. Затем используется метод HandleKeyboardInput, чтобы позволить пользователю печатать новый текст. Взгляните на метод HandleKeyboardInput:

/// <summary>
/// Вспомогательный метод обработки ввода с клавиатуры,
/// перехватывающий данные клавиатуры для ввода текста.
/// Используется только для ввода имени игрока в игре
/// </summary>
/// <param name="inputText">Введенный текст</param>
public static void HandleKeyboardInput(ref string inputText)
{
  // Нажата ли клавиша Shift (проверяем оба, левый и правый)
  bool isShiftPressed =
    keyboardState.IsKeyDown(Keys.LeftShift) ||
    keyboardState.IsKeyDown(Keys.RightShift);

  // Перебираем все нажатые клавиши
  foreach (Keys pressedKey in keyboardState.GetPressedKeys())
    // Обрабатываем только те, которые не были нажаты в предыдущем кадре
    if (keysPressedLastFrame.Contains(pressedKey) == false)
    {
      // Нет специальных клавиш?
      if (IsSpecialKey(pressedKey) == false &&
          // Допустимо не более 32 символов
          inputText.Length < 32)
      {
        // Добавляем букву к нашему inputText.
        // Также проверяем состояние Shift!
        inputText += KeyToChar(pressedKey, isShiftPressed);
      } // if (IsSpecialKey)
      else if (pressedKey == Keys.Back &&
        inputText.Length > 0)
      {
        // Удаляем 1 символ в конце
        inputText = inputText.Substring(0, inputText.Length - 1);
      } // else if
    } // foreach if (WasKeyPressedLastFrame)
} // HandleKeyboardInput(inputText)

Метод использует два других вспомогательных метода из класса Input. Во-первых, IsSpecialKey используется чтобы посмотреть, может ли клавиша быть добавлена к тексту, или это F1, клавиша управления курсором, Delete, Enter или другая подобная клавиша, которая не будет добавляться непосредственно к тексту. Затем вы обрабатываете специальный случай для клавиши Backspace. Вы можете расширить код, чтобы обрабатывать в текстовых полях клавиши Enter или Delete, если это нужно в вашей игре.

Следующая проблема в том, что вы не можете просто добавить клавишу к вашему вводимому тексту; во-первых, клавиша A будет добавлять к строке вводимого текста символ A, независимо от того, нажал ли пользователь клавишу Shift. Также специальные символы, такие как +, -, { и т.д., будут отображаться в вашем вводимом тексте как Plus, Minus, OemOpenBrackets. Было бы замечательно, если для помощи вам XNA предоставлял действительное значение клавиши, но он этого не делает; вы делаете это самостоятельно с помощью вспомогательного метода KeyToChar из класса Input:

/// <summary>
/// Вспомогательный метод преобразования клавиши в символ.
/// Примечание: если расположение клавиш отличается от стандартной
/// клавиатуры QWERTY, метод не будет правильно работать. Большинство
/// клавиатур возвращают одни и те же значения для A-Z и 0-9,
/// но специальные клавиши могут отличаться. Извините, легкого способа
/// исправить это в XNA нет... Для игр с возможностью общения (окном)
/// вы должны использовать для сбора ввода с клавиатуры соытия Windows,
/// что гораздо лучше!
/// </summary>
/// <param name="key">Клавиша</param>
/// <returns>Символ</returns>
public static char KeyToChar(Keys key, bool shiftPressed)
{
  // Если клавиша не найдена, просто возвращаем пробел
  char ret = ' ';
  int keyNum = (int)key;
  if (keyNum >= (int)Keys.A && keyNum <= (int)Keys.Z)
  {
    if (shiftPressed)
      ret = key.ToString()[0];
    else
      ret = key.ToString().ToLower()[0];
  } // if (keyNum)
  else if (keyNum >= (int)Keys.D0 && keyNum <= (int)Keys.D9 &&
           shiftPressed == false)
  {
    ret = (char)((int)'0' + (keyNum - Keys.D0));
  } // else if
  else if (key == Keys.D1 && shiftPressed)
    ret = '!';
  else if (key == Keys.D2 && shiftPressed)
    ret = '@';

  [и т.д. еще около 20 проверок специальных клавиш]

  // Возвращаем результат
  return ret;
} // KeyToChar(key)

С этим методом HandleKeyboardInput работает теперь так, как вы ожидали, и ваш тестовый модуль может быть запущен и проверен. В классе Input есть гораздо больше тестовых модулей; исследуйте их, чтобы больше узнать о доступных свойствах и методах.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz