Подтвердить что ты не робот

Добавление управления с помощью ввода-вывода в игру XNA

Я хочу, чтобы в моей игре был обычный ввод текста, но с чистым XNA это очень неприятно.

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

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint MessageBox(IntPtr hWnd, String text, String caption, uint type);

Есть ли что-то похожее на это, что может добавить функциональность InputBox в мою игру, предпочтительнее, не прерывая (приостанавливая) игру?

4b9b3361

Ответ 1

А, ввод текста - у меня очень недавний опыт с этим.

Проблема

Обычно Keyboard.GetKeyboardState() отсасывает при вводе текста, и это по многим причинам, некоторые из которых:

  • Вам нужно закодировать HUGE-переключатель, чтобы определить, какой ключ был нажат.
  • Вам нужно вручную определить, следует ли писать буквы (Shift или CapsLock)
  • Вам нужно расшифровать эти OemPeriod -подобные ключи (как в тесте), чтобы увидеть, где они на самом деле, и сопоставить их с определенными значениями.
  • Невозможно обнаружить/использовать раскладку клавиатуры или язык клавиатуры.
  • Вы должны реализовать собственный механизм повторения синхронизации в случае удержания ключа

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

В-третьих, вам нужно нарисовать TextBox в определенных границах, и вы также можете рисовать каретку (мигающий индикатор вертикальной позиции), текущий выбор (если вы хотите дойти до его реализации), текстура который представляет поле и текстуры для выделенного (с мышью) или выбранного (с фокусом) состояния.

В-четвертых, вам нужно вручную реализовать функции копирования-вставки.


Быстрая заметка

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

Решение

Дело в том, что (по крайней мере, когда мы говорим о Windows, а не X-Box или WP7), в операционной системе уже есть механизмы, необходимые для реализации всего, что вам нужно с клавиатуры:

  • Дает символы, основанные на текущей раскладке клавиатуры и языке.
  • Автоматически обрабатывает повторяющийся ввод (в случае удержания клавиши)
  • Автоматически использует заглавные буквы и предоставляет специальные символы

Решение, которое я использую для ввода клавиатуры, я скопировал этот пост форума Gamedev.net. Это код ниже, и вам просто нужно скопировать его в файл .cs, который вам больше не придется открывать.

Он используется для получения локализованного ввода с вашей клавиатуры, и все, что вам нужно сделать, это инициализировать его в методе переопределения Game.Initialize() (с помощью Game.Window) и подключиться к событиям, чтобы получать вход в любом месте, d нравится.

Вам нужно добавить PresentationCore (PresentationCore.dll) к своим ссылкам, чтобы использовать этот код (необходимый для пространства имен System.Windows.Input). Это работает для .NET 4.0 и для .NET Client Client.

EventInput

using System;
using System.Runtime.InteropServices;   
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System.Text;
using System.Windows.Input;

namespace EventInput
{

    public class KeyboardLayout
    {
        const uint KLF_ACTIVATE = 1; //activate the layout
        const int KL_NAMELENGTH = 9; // length of the keyboard buffer
        const string LANG_EN_US = "00000409";
        const string LANG_HE_IL = "0001101A";

        [DllImport("user32.dll")]
        private static extern long LoadKeyboardLayout(
              string pwszKLID,  // input locale identifier
              uint Flags       // input locale identifier options
              );

        [DllImport("user32.dll")]
        private static extern long GetKeyboardLayoutName(
              System.Text.StringBuilder pwszKLID  //[out] string that receives the name of the locale identifier
              );

        public static string getName()
        {
            System.Text.StringBuilder name = new System.Text.StringBuilder(KL_NAMELENGTH);
            GetKeyboardLayoutName(name);
            return name.ToString();
        }
    }

    public class CharacterEventArgs : EventArgs
    {
        private readonly char character;
        private readonly int lParam;

        public CharacterEventArgs(char character, int lParam)
        {
            this.character = character;
            this.lParam = lParam;
        }

        public char Character
        {
            get { return character; }
        }

        public int Param
        {
            get { return lParam; }
        }

        public int RepeatCount
        {
            get { return lParam & 0xffff; }
        }

        public bool ExtendedKey
        {
            get { return (lParam & (1 << 24)) > 0; }
        }

        public bool AltPressed
        {
            get { return (lParam & (1 << 29)) > 0; }
        }

        public bool PreviousState
        {
            get { return (lParam & (1 << 30)) > 0; }
        }

        public bool TransitionState
        {
            get { return (lParam & (1 << 31)) > 0; }
        }
    }

    public class KeyEventArgs : EventArgs
    {
        private Keys keyCode;

        public KeyEventArgs(Keys keyCode)
        {
            this.keyCode = keyCode;
        }

        public Keys KeyCode
        {
            get { return keyCode; }
        }
    }

    public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
    public delegate void KeyEventHandler(object sender, KeyEventArgs e);

    public static class EventInput
    {
        /// <summary>
        /// Event raised when a character has been entered.
        /// </summary>
        public static event CharEnteredHandler CharEntered;

        /// <summary>
        /// Event raised when a key has been pressed down. May fire multiple times due to keyboard repeat.
        /// </summary>
        public static event KeyEventHandler KeyDown;

        /// <summary>
        /// Event raised when a key has been released.
        /// </summary>
        public static event KeyEventHandler KeyUp;

        delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

        static bool initialized;
        static IntPtr prevWndProc;
        static WndProc hookProcDelegate;
        static IntPtr hIMC;

        //various Win32 constants that we need
        const int GWL_WNDPROC = -4;
        const int WM_KEYDOWN = 0x100;
        const int WM_KEYUP = 0x101;
        const int WM_CHAR = 0x102;
        const int WM_IME_SETCONTEXT = 0x0281;
        const int WM_INPUTLANGCHANGE = 0x51;
        const int WM_GETDLGCODE = 0x87;
        const int WM_IME_COMPOSITION = 0x10f;
        const int DLGC_WANTALLKEYS = 4;

        //Win32 functions that we're using
        [DllImport("Imm32.dll", CharSet = CharSet.Unicode)]
        static extern IntPtr ImmGetContext(IntPtr hWnd);

        [DllImport("Imm32.dll", CharSet = CharSet.Unicode)]
        static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);


        /// <summary>
        /// Initialize the TextInput with the given GameWindow.
        /// </summary>
        /// <param name="window">The XNA window to which text input should be linked.</param>
        public static void Initialize(GameWindow window)
        {
            if (initialized)
                throw new InvalidOperationException("TextInput.Initialize can only be called once!");

            hookProcDelegate = new WndProc(HookProc);
            prevWndProc = (IntPtr)SetWindowLong(window.Handle, GWL_WNDPROC,
                (int)Marshal.GetFunctionPointerForDelegate(hookProcDelegate));

            hIMC = ImmGetContext(window.Handle);
            initialized = true;
        }

        static IntPtr HookProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
        {
            IntPtr returnCode = CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam);

            switch (msg)
            {
                case WM_GETDLGCODE:
                    returnCode = (IntPtr)(returnCode.ToInt32() | DLGC_WANTALLKEYS);
                    break;

                case WM_KEYDOWN:
                    if (KeyDown != null)
                        KeyDown(null, new KeyEventArgs((Keys)wParam));
                    break;

                case WM_KEYUP:
                    if (KeyUp != null)
                        KeyUp(null, new KeyEventArgs((Keys)wParam));
                    break;

                case WM_CHAR:
                    if (CharEntered != null)
                        CharEntered(null, new CharacterEventArgs((char)wParam, lParam.ToInt32()));
                    break;

                case WM_IME_SETCONTEXT:
                    if (wParam.ToInt32() == 1)
                        ImmAssociateContext(hWnd, hIMC);
                    break;

                case WM_INPUTLANGCHANGE:
                    ImmAssociateContext(hWnd, hIMC);
                    returnCode = (IntPtr)1;
                    break;
            }

            return returnCode;
        }
    }
}

Теперь вы уже можете использовать это как есть (подписавшись на событие EventInput.CharEntered) и использовать логику, чтобы определить, куда отправить ваш вход.


KeyboardDispatcher, IKeyboardSubscriber

То, что я сделал, это создать класс KeyboardDispatcher, который обрабатывает диспетчеризацию ввода с клавиатуры с помощью свойства типа IKeyboardSubscriber, которому он отправляет принятый ввод. Идея заключается в том, что вы устанавливаете это свойство для этого элемента управления пользовательского интерфейса, который вы хотите получить.

Определения следующие:

public interface IKeyboardSubscriber
{
    void RecieveTextInput(char inputChar);
    void RecieveTextInput(string text);
    void RecieveCommandInput(char command);
    void RecieveSpecialInput(Keys key);

    bool Selected { get; set; } //or Focused
}

public class KeyboardDispatcher
{
    public KeyboardDispatcher(GameWindow window)
    {
        EventInput.EventInput.Initialize(window);
        EventInput.EventInput.CharEntered += new EventInput.CharEnteredHandler(EventInput_CharEntered);
        EventInput.EventInput.KeyDown += new EventInput.KeyEventHandler(EventInput_KeyDown);
    }

    void EventInput_KeyDown(object sender, EventInput.KeyEventArgs e)
    {
        if (_subscriber == null)
            return;

        _subscriber.RecieveSpecialInput(e.KeyCode);
    }

    void EventInput_CharEntered(object sender, EventInput.CharacterEventArgs e)
    {
        if (_subscriber == null)
            return;
        if (char.IsControl(e.Character))
        {
            //ctrl-v
            if (e.Character == 0x16)
            {
                //XNA runs in Multiple Thread Apartment state, which cannot recieve clipboard
                Thread thread = new Thread(PasteThread);
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
                thread.Join();
                _subscriber.RecieveTextInput(_pasteResult);
            }
            else
            {
                _subscriber.RecieveCommandInput(e.Character);
            }
        }
        else
        {
            _subscriber.RecieveTextInput(e.Character);
        }
    }

    IKeyboardSubscriber _subscriber;
    public IKeyboardSubscriber Subscriber
    {
        get { return _subscriber; }
        set
        {
            if (_subscriber != null)
                _subscriber.Selected = false;
            _subscriber = value;
            if(value!=null)
                value.Selected = true;
        }
    }

    //Thread has to be in Single Thread Apartment state in order to receive clipboard
    string _pasteResult = "";
    [STAThread]
    void PasteThread()
    {
        if (Clipboard.ContainsText())
        {
            _pasteResult = Clipboard.GetText();
        }
        else
        {
            _pasteResult = "";
        }
    }
}

Использование довольно просто, создайте экземпляр KeyboardDispatcher, т.е. в Game.Initialize() и сохраните ссылку на него (чтобы вы могли переключаться между выбранными [сфокусированными] элементами управления) и передавать ему класс, который использует интерфейс IKeyboardSubscriber например, ваш TextBox.


TextBox

Далее ваш фактический контроль. Теперь я изначально запрограммировал довольно сложный ящик, который использовал объекты рендеринга для отображения текста в текстуре, чтобы я мог перемещать его (если текст был больше, чем в поле), но затем, после большой боли, я его оставил и сделал действительно простой версия. Не стесняйтесь его улучшать!

public delegate void TextBoxEvent(TextBox sender);

public class TextBox : IKeyboardSubscriber
{
    Texture2D _textBoxTexture;
    Texture2D _caretTexture;

    SpriteFont _font;

    public int X { get; set; }
    public int Y { get; set; }
    public int Width { get; set; }
    public int Height { get; private set; }

    public bool Highlighted { get; set; }

    public bool PasswordBox { get; set; }

    public event TextBoxEvent Clicked;

    string _text = "";
    public String Text
    {
        get
        {
            return _text;
        }
        set
        {
            _text = value;
            if (_text == null)
                _text = "";

            if (_text != "")
            {
                //if you attempt to display a character that is not in your font
                //you will get an exception, so we filter the characters
                //remove the filtering if you're using a default character in your spritefont
                String filtered = "";
                foreach (char c in value)
                {
                    if (_font.Characters.Contains(c))
                        filtered += c;
                }

                _text = filtered;

                while (_font.MeasureString(_text).X > Width)
                {
                    //to ensure that text cannot be larger than the box
                    _text = _text.Substring(0, _text.Length - 1);
                }
            }
        }
    }

    public TextBox(Texture2D textBoxTexture, Texture2D caretTexture, SpriteFont font)
    {
        _textBoxTexture = textBoxTexture;
        _caretTexture = caretTexture;
        _font = font;           

        _previousMouse = Mouse.GetState();
    }

    MouseState _previousMouse;
    public void Update(GameTime gameTime)
    {
        MouseState mouse = Mouse.GetState();
        Point mousePoint = new Point(mouse.X, mouse.Y);

        Rectangle position = new Rectangle(X, Y, Width, Height);
        if (position.Contains(mousePoint))
        {
            Highlighted = true;
            if (_previousMouse.LeftButton == ButtonState.Released && mouse.LeftButton == ButtonState.Pressed)
            {
                if (Clicked != null)
                    Clicked(this);
            }
        }
        else
        {
            Highlighted = false;
        }
    }

    public void Draw(SpriteBatch spriteBatch, GameTime gameTime)
    {
        bool caretVisible = true;

        if ((gameTime.TotalGameTime.TotalMilliseconds % 1000) < 500)
            caretVisible = false;
        else
            caretVisible = true;

        String toDraw = Text;

        if (PasswordBox)
        {
            toDraw = "";
            for (int i = 0; i < Text.Length; i++)
                toDraw += (char) 0x2022; //bullet character (make sure you include it in the font!!!!)
        } 

        //my texture was split vertically in 2 parts, upper was unhighlighted, lower was highlighted version of the box
        spriteBatch.Draw(_textBoxTexture, new Rectangle(X, Y, Width, Height), new Rectangle(0, Highlighted ? (_textBoxTexture.Height / 2) : 0, _textBoxTexture.Width, _textBoxTexture.Height / 2), Color.White);



        Vector2 size = _font.MeasureString(toDraw);

        if (caretVisible && Selected)
            spriteBatch.Draw(_caretTexture, new Vector2(X + (int)size.X + 2, Y + 2), Color.White); //my caret texture was a simple vertical line, 4 pixels smaller than font size.Y

        //shadow first, then the actual text
        spriteBatch.DrawString(_font, toDraw, new Vector2(X, Y) + Vector2.One, Color.Black);
        spriteBatch.DrawString(_font, toDraw, new Vector2(X, Y), Color.White);
    }


    public void RecieveTextInput(char inputChar)
    {
        Text = Text + inputChar;
    }
    public void RecieveTextInput(string text)
    {
        Text = Text + text;
    }
    public void RecieveCommandInput(char command)
    {
        switch (command)
        {
            case '\b': //backspace
                if (Text.Length > 0)
                    Text = Text.Substring(0, Text.Length - 1);
                break;
            case '\r': //return
                if (OnEnterPressed != null)
                    OnEnterPressed(this);
                break;
            case '\t': //tab
                if (OnTabPressed != null)
                    OnTabPressed(this);
                break;
            default:
                break;
        }
    }
    public void RecieveSpecialInput(Keys key)
    {

    }

    public event TextBoxEvent OnEnterPressed;
    public event TextBoxEvent OnTabPressed;

    public bool Selected
    {
        get;
        set;
    }
}

Когда вы создаете экземпляр TextBox, не забудьте установить значения X, Y и Width (!!!) в экземпляре (Height автоматически задается шрифтом).

Текстурой, которую я использовал для ящика, была TextBoxTexture (у неосвещенного есть градиент, который выглядит красиво на черном фоне:))

Чтобы отобразить окно, вызовите метод .Draw() в экземпляре (в вашем методе Game.Draw()), при запуске spritebatch (SpriteBatch.Begin())!!!). Для каждого отображаемого окна, если вы хотите, чтобы он получил ввод мыши, вы должны вызвать метод .Update().

Если вы хотите, чтобы конкретный экземпляр получал ввод с клавиатуры, используйте ваш экземпляр KeyboardDispatcher для его подписания, например:

_keyboardDispatcher.Subscriber = _usernameTextBox;

Вы можете использовать события Click, Tab и Enter в текстовом поле, чтобы переключаться между подписчиками (что я рекомендую, так как это дает действительно приятное ощущение пользовательскому интерфейсу, когда вы можете его пропустить, и нажмите, чтобы выбрать).


Неразрешенные проблемы

Ofc, я говорил о некоторых функциях, которые я не реализовал, таких как ящик, способный панорамировать текст, если текст был шире, чем поле, возможность перемещать каретку (вставлять текст, а не просто добавлять), для выбора и копирования текста и т.д.

Эти проблемы, которые вы могли бы решить с легким и средним усилием, я в этом уверен, но прежде чем вы это сделаете, спросите себя:

Нужно ли мне это?

Ответ 2

Записав такой код несколько раз, я бы сказал, что не сложно программировать базовое текстовое поле в XNA. Вы определяете прямоугольник, который вы заполняете цветом фона, строкой, которая представляет то, что пользователь набрал, и отображает строку с помощью Spritebatch.DrawString() внутри прямоугольника! Используя SpriteFont.MeasureString(), вы можете выровнять текст так, как хотите, перенести текст на следующую строку, когда он отключен, и т.д.

Затем вы просматриваете каждое обновление Keyboard.GetState() и проверяете, какие клавиши были нажаты. Это может быть самая большая проблема, потому что, если пользователь быстро набирает скорость, вы пропустите несколько нажатий клавиш - игра обновляется столько раз в секунду. Эта проблема широко документирована в Интернете и имеет решения, например здесь.

Другим вариантом будет использование готового компонента XNA GUI, например, то, что вы получаете с Nuclex.

Ответ 3

Ну, самый простой способ был бы следующим (с моей точки зрения;))

using TextboxInputTest.Textbox.TextInput;
private TextboxInput _inputTextBox

то я бы рекомендовал включить мышь (установить ее видимым)

IsMouseVisible = true;

Теперь вам нужно инициализировать сам textBox

this._inputTextBox = new TextboxInput(this, "background_box", "Arial");

это означает игру, которая есть эта (сомневаюсь, что вам нужно будет это изменить)

background_box - это имя изображения, которое вы хотите использовать для отображения (afaik, для этого нет опции по умолчанию)

Arial - это шрифт, который вы хотите использовать (не забудьте добавить его к содержимому игры

Установите положение окна

this._inputTextBox.Position = new Vector2(100,100);

И в качестве последнего шага вам нужно добавить окно в массив компонентов

Components.Add(this._inputTextBox);

Есть много функций, которые вы могли бы изменить, для этого я бы рекомендовал использовать IntelliSense

edit: моя ошибка, извините, я использую их так часто, я совсем забыл об этом;] говоря заранее, что я вижу, рев, не моя работа

http://www.4shared.com/file/RVqzHWk0/TextboxInput.html

Надеюсь, что это помогло.

Привет,

Releis