已经写过几次这样的代码,我想说在XNA中编写基本文本框并不难。您定义一个用背景色填充的矩形,一个表示用户键入内容的字符串,然后在矩形内部使用Spritebatch.DrawString()显示该字符串!使用SpriteFont.MeasureString(),您可以根据需要对齐文本,如果超出限制,则将文本包装到下一行,等等。
我希望我的游戏具有正常的文本输入,但是使用纯XNA似乎非常不愉快。
以前我发现这段代码使我可以在整个游戏中使用MessageBox
,安全地暂停其执行并显示一条消息:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint MessageBox(IntPtr hWnd, String text, String caption, uint type);
是否有类似的东西可以在我的游戏中添加InputBox
功能,最好不中断(暂停)游戏?
啊,文本输入-我对此有新的经验。
通常,Keyboard.GetKeyboardState()
在获取文本输入方面很烂,原因很多,其中一些是:
OemPeriod
的键(如在测试中一样,以查看它们的实际位置,并将它们映射到特定值。问题的第二部分是检测哪个文本框(或一般的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客户端配置文件。
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
类型的属性来处理键盘输入,并将接收到的输入发送到该属性。想法是将这个属性设置为要接收输入的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()
中实例化并保留对其的引用(以便您可以在选定的[焦点]控件之间切换),并向其传递一个使用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
由字体自动设置)。] >我用于包装盒的纹理是(在黑色背景上突出显示有渐变, )
要显示该框,请在实例上调用.Draw()
方法(在您的Game.Draw()
方法中),并且已经启动了spritebatch(已调用[C0]!)。对于要显示的每个框,如果希望它接收鼠标输入,则应调用SpriteBatch.Begin()
方法。
[当您希望特定实例接收键盘输入时,请使用您的.Update()
实例进行预订,例如:
KeyboardDispatcher
您可以使用文本框上的
_keyboardDispatcher.Subscriber = _usernameTextBox;
,Click
和Tab
事件来切换订阅者(我建议您这样做,因为当您可以通过Tab键单击它并单击以选择时,它会给UI带来非常好的感觉) 。未解决的问题
Ofc,我曾经讨论过一些我尚未实现的功能,例如,如果文本宽于文本框,则该文本框可以平移文本;可以移动插入号(插入文本,而不仅仅是追加文本) ,以选择和复制文本等。
我相信您可以轻而易举地解决这些问题,但是在您这样做之前,请先问问自己:
已经写过几次这样的代码,我想说在XNA中编写基本文本框并不难。您定义一个用背景色填充的矩形,一个表示用户键入内容的字符串,然后在矩形内部使用Spritebatch.DrawString()显示该字符串!使用SpriteFont.MeasureString(),您可以根据需要对齐文本,如果超出限制,则将文本包装到下一行,等等。
然后您查看Keyboard.GetState()的每个更新,并检查已按下的键。这可能是最大的问题,因为如果用户快速键入内容,您将错过一些按键操作-游戏每秒仅更新多次。该问题已在Internet上广泛记录,并具有解决方案,例如Enter
。
另一种选择是使用预制的XNA GUI组件,例如从here框架中获得的组件。
嗯,最简单的方法如下(从我的角度来看;])
Nuclex
然后我建议启用鼠标(将其设置为可见)
using TextboxInputTest.Textbox.TextInput;
private TextboxInput _inputTextBox
现在需要初始化文本框本身
IsMouseVisible = true;
这代表游戏,就是这个了(您需要更改它)
background_box是您要用于显示的图片的名称(afaik,此功能没有默认选项)
Arial是您想要使用的字体(别忘了您必须将其添加到游戏内容中
设置框的位置
this._inputTextBox = new TextboxInput(this, "background_box", "Arial");
最后,您必须将框添加到组件数组中
this._inputTextBox.Position = new Vector2(100,100);
您可能要编辑许多功能,为此,我建议使用IntelliSense
编辑:我的错,对不起,我经常这样使用它们,我完全忘记了它;]事先说,你们看到的不是我的工作
Components.Add(this._inputTextBox);
希望它有所帮助。
问候,
Releis
已经写过几次这样的代码,我想说在XNA中编写基本文本框并不难。您定义一个用背景色填充的矩形,一个表示用户键入内容的字符串,然后在矩形内部使用Spritebatch.DrawString()显示该字符串!使用SpriteFont.MeasureString(),您可以根据需要对齐文本,如果超出限制,则将文本包装到下一行,等等。
嗯,最简单的方法如下(从我的角度来看;])