输入框添加类似控制XNA游戏 [英] Adding inputbox-like control to XNA game

查看:125
本文介绍了输入框添加类似控制XNA游戏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想我的比赛有正常的文本输入,但它似乎非常不愉快采用纯XNA做。

I want my game to have normal text input, but it seems very unpleasant to do using pure XNA.

早些时候,我发现了这片code的,它可以让我用的MessageBox 各地我的比赛,安全地暂停其执行并显示一条消息:

Earlier I found this piece of code which lets me use MessageBox all around my game, safely pausing its execution and showing a message:

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

有没有类似这样的东西可能我的比赛,$ P $添加输入框功能pferrably不中断(暂停)游戏?

Is there something similar to this which could add InputBox functionality to my game, preferrably without interrupting (pausing) the game?

推荐答案

嗯,文本输入 - 我有非常最近这方面的经验。

Ah, the text input - I have very recent experience with this.

通常情况下, Keyboard.GetKeyboardState()吸在获得文本输入,这是有很多原因,其中一些是:

Usually, Keyboard.GetKeyboardState() sucks at getting text input, and that is for many reasons, some of them being:


  • 您必须code一个巨大的开关来检测一下键已经pressed

  • 您必须手动检测是否大写字母(Shift或CapsLock键)

  • 您已经破译那些 OemPeriod 键式的(如测试),看看它们实际上是,并将它们映射到特定的值。

  • 有没有方法来检测/使用的键盘布局和键盘语言

  • 您必须实现自己的机制为关键的情况下,定时重复被按下

  • You have to code a HUGE switch to detect what key had been pressed
  • You have to manually detect whether to capitalize letters (Shift or CapsLock)
  • You have to decipher those OemPeriod-like keys (as in test) to see where they actually are, and map them to specific values.
  • There is no way to detect/use keyboard layout or keyboard language
  • You have to implement own mechanism for timing repetition in case of key being held down

问题的第二部分是检测它的文本框(或一般UI控件)的目前正在接受该输入,因为你不希望所有的框,你输入接收文本。

Second part of the problem is detecting which of your TextBoxes (or UI controls in general) is currently receiving this input, since you don't want all of your boxes to receive text as you type.

第三,你需要绘制文本框在指定的范围,你也可以想画的符号(闪烁的垂直位置指示器),当前的选择(如果你想这么远来实现它),纹理那些重新presents箱子,和纹理突出显示(使用鼠标)或选定(具有焦点)的状态。

Third, you need to draw the TextBox in specified bounds, and you could also want to draw the caret (the blinking vertical position indicator), the current selection (if you want to go so far to implement it), the texture that represents the box, and the textures for highlighted (with mouse) or selected (has focus) state.

第四,你必须手动实现复制粘贴功能。

Fourth, you have to manually implement copy-paste features.

您可能并不需要所有这些功能,因为我并不需要他们。你只是想简单的输入,并检测键,如Enter或Tab键,以及鼠标点击。也许还贴上。

You probably don't need all these features, as I didn't need them. You'd just want simple input, and detection for keys such as enter or tab, as well as mouse click. Maybe also paste.

问题是(至少当我们谈论的Windows,而不是X-box游戏机或WP7)操作系统已经拥有必要的机制来实现你从你的键盘需要的一切:

The thing is (at least when we talk about Windows, not X-Box or WP7), the operating system already has the mechanisms necessary to implement everything you need from your keyboard:


  • 基于当前的键盘布局和语言文字给人

  • 自动处理重复输入(在关键的情况下被关押下)

  • 自动大写,并提供特殊字符

解决方法我用获取键盘输入,我已经复制了<一个href=\"http://www.gamedev.net/topic/457783-xna-getting-text-from-keyboard/page__p__4040190#entry4040190\">this Gamedev.net论坛帖子。它是低于code,而你只是需要将其复制粘贴到你永远不会有再次打开cs文件。

Solution I use for getting keyboard input, I've copied off this Gamedev.net forum post. It is the code below, and you just need to copy-paste it into a .cs file which you'll never have to open again.

它用于从键盘接收输入的本地化,以及所有你需要做的就是在你的 Game.Initialize()重写方法将其初始化(通过使用游戏。窗口),并挂接到事件接收输入任何你想。

It is used for receiving localized input from your keyboard, and all you need to do is initialize it in your Game.Initialize() override method (by using Game.Window), and hook up to the events to receive input anywhere you'd like.

您需要添加 presentationCore (presentationCore.dll)添加到您的参考,才能使用此code(用于 System.Windows.Input 命名空间)。这适用于.NET 4.0和.NET 4.0客户端配置文件。

You need to add PresentationCore (PresentationCore.dll) to your references in order to use this code (needed for System.Windows.Input namespace). This works for .NET 4.0 and for .NET 4.0 Client Profile.

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 事件),并用逻辑来检测发送您的意见。

Now you could already use this as it is (by subscribing to EventInput.CharEntered event), and use logic to detect where to send your input.

我所做的就是创建一个类 KeyboardDispatcher ,它通过其类型的属性的方式处理键盘输入的调度 IKeyboardSubscriber 来它发送接收到的输入。我们的想法是,你将此属性设置为要接收输入的UI控件。

What I did was create a class KeyboardDispatcher, which handles the dispatching of keyboard input by way of having a property of type IKeyboardSubscriber to which it sends received input. The idea is that you set this property to that UI control that you want to receive input.

定义如下:

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 接口的类,比如你的文本框

Usage is fairly simple, instantiate KeyboardDispatcher, i.e. in Game.Initialize() and keep a reference to it (so you can switch between selected [focused] controls), and pass it a class that uses the IKeyboardSubscriber interface, such as your TextBox.

接下来是你的实际控制人。现在,我原本程序性的用来渲染目标呈现文本到纹理,所以我可以四处移动它(如果文本比箱大)一个相当复杂的盒子,但随后很多的痛苦后,我报废,并取得了很简单的版本。随意提高它!

Next up is your actual control. Now I've originally programed a fairly complicated box that used render targets to render the text to a texture so I could move it around (if text was larger than the box), but then after a lot of pain i scrapped it and made a really simple version. Feel free to improve it!

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;
    }
}

当你实例化一个文本框,不要忘记设置 X ÿ 宽度(!!!)上的实例值(身高是自动按字体设置)。

When you instantiate a TextBox, don't forget to set X, Y, and Width (!!!) values on the instance (Height is auto-set by font).

我用盒子的质地(不再高亮度显示有一个梯度,这看起来不错,黑色背景:))

The texture I used for the box was (unhighlighted has a gradient, which looks nice on a black background :) )

要显示框中调用实例上的 .Draw()方法(在你的 Game.Draw()法),与spritebatch已经开始( SpriteBatch.Begin()叫!)。对于每个盒子你显示,如果你想让它收到鼠标输入你应该叫 .Update()方法。

To display the box call the .Draw() method on the instance (in your Game.Draw() method), with spritebatch already started (SpriteBatch.Begin() called!!!). For each box you're displaying, if you want it to recieve mouse input you should call .Update() method.

当你想要一个具体的实例来收到键盘输入,使用 KeyboardDispatcher 实例订阅它,如:

When you want a specific instance to recieve keyboard input, use your KeyboardDispatcher instance to subscribe it, such as:

_keyboardDispatcher.Subscriber = _usernameTextBox;

您可以使用点击设置页输入在文本框事件切换用户(我建议,因为它给的时候可以通过标签它一个非常好的感觉的用户界面,然后单击以选中)。

You can use the Click, Tab and Enter events on the textbox to switch subscribers (which I recommend as it gives a really nice feel to the UI when you can tab through it, and click to select).

OFC,我已经谈了一些功能我还没有实现,如框能够平移文本如果文本比框更宽,要四处移动(插入文本的能力,而不仅仅是追加),以选择和复制文本等。

Ofc, I had talked about some features I had not implemented, such as the box being able to pan the text if the text was wider than the box, the ability to move the caret around (insert the text, not just append), to select and copy text, etc.

您可以用一个轻到中等努力解决这些问题,我敢肯定,但在此之前,问问自己:

These problems you could solve with a light to medium effort, I'm sure of it, but before you do, ask yourself:

这篇关于输入框添加类似控制XNA游戏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆