gpt4 book ai didi

c# - 向 XNA 游戏添加类似输入框的控件

转载 作者:可可西里 更新时间:2023-11-01 07:50:51 25 4
gpt4 key购买 nike

我希望我的游戏有正常的文本输入,但使用纯 XNA 似乎很不愉快。

早些时候我发现这段代码让我可以使用 MessageBox在我的游戏周围,安全地暂停其执行并显示一条消息:

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

是否有类似的东西可以添加 InputBox我的游戏的功能,最好不要中断(暂停)游戏?

最佳答案

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

问题

通常,Keyboard.GetKeyboardState()获取文本输入很糟糕,这有很多原因,其中一些是:

  • 你必须编写一个巨大的开关来检测按下了什么键
  • 您必须手动检测是否大写字母(Shift 或 CapsLock)
  • 你必须破译那些OemPeriod -like 键(如在测试中)以查看它们的实际位置,并将它们映射到特定值。
  • 无法检测/使用键盘布局或键盘语言
  • 如果键被按住,您必须实现自己的定时重复机制

  • 问题的第二部分是检测您的哪个文本框(或一般的 UI 控件)当前正在接收此输入,因为您不希望所有框在您键入时接收文本。

    第三,您需要在指定的边界内绘制 TextBox,您还可能想要绘制插入符号(闪烁的垂直位置指示器)、当前选择(如果您想到目前为止实现它)、表示框,以及突出显示(使用鼠标)或选中(具有焦点)状态的纹理。

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

    速记

    您可能不需要所有这些功能,因为我不需要它们。您只需要简单的输入和检测键,例如 Enter 或 Tab,以及鼠标点击。也许也贴。

    解决方案

    问题是(至少当我们谈论 Windows 时,而不是 X-Box 或 WP7 时),操作系统已经拥有从键盘实现您需要的一切所需的机制:
  • 根据当前键盘布局和语言提供字符
  • 自动处理重复输入(在按键被按住的情况下)
  • 自动大写并提供特殊字符

  • 我用来获取键盘输入的解决方案,我已经复制了 this Gamedev.net forum post .它是下面的代码,您只需要将其复制粘贴到一个 .cs 文件中,您将永远不必再次打开该文件。

    它用于从您的键盘接收本地化输入,您需要做的就是在您的 Game.Initialize() 中对其进行初始化。覆盖方法(通过使用 Game.Window),并连接到事件以在您想要的任何地方接收输入。

    您需要添加 PresentationCore (PresentationCore.dll) 到您的引用以使用此代码(需要 System.Windows.Input 命名空间)。这适用于 .NET 4.0 和 .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 事件),并使用逻辑来检测将输入发送到何处。

    KeyboardDispatcher, IKeyboardSubscriber

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

    定义如下:
    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()并保留对它的引用(以便您可以在选定的 [focused] 控件之间切换),并将使用 IKeyboardSubscriber 的类传递给它界面,例如您的 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 , TabEnter文本框上的事件以切换订阅者(我推荐这样做,因为当您可以通过选项卡浏览它并单击以选择时,它会给 UI 带来非常好的感觉)。

    尚未解决的问题

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

    这些问题你可以通过轻到中等的努力解决,我敢肯定,但在你做之前,问问自己:

    我真的需要吗?

    关于c# - 向 XNA 游戏添加类似输入框的控件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10216757/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com