Monogame - 创建 CheckBox 对象
Monogame - Create CheckBox object
我一直在为我的游戏开发 GUI。到目前为止,我已经使用代码创建了看起来像 "forms" 的对象(不要与 winforms 混淆,我使用的是 monogame)。我还创建了按钮。但是现在我很难创建一个复选框。所以这是我的 CheckBoxGUI
class:
// take a look into the recent edits for this post if you need to see my old code.
我的TextOutliner
class曾经用borders/outlines绘制文字:
// take a look into the recent edits for this post if you need to see my old code.
最后,这是我在 TitleScreen
中使用 CheckBoxGUI
class 的方法:
// take a look into the recent edits for this post if you need to see my old code.
这是我的问题(至少我认为是这样,在处理复选框逻辑的方法中):
// take a look into the recent edits for this post if you need to see my old code.
最后,Draw()
:
// take a look into the recent edits for this post if you need to see my old code.
所以方法 CheckBoxInputTS
允许我通过鼠标点击 check/uncheck CheckBoxGUI 项目,但实际上我无法将任何功能附加到此。例如,我可以在同一个方法中执行以下操作:
// take a look into the recent edits for this post if you need to see my old code.
也许我遗漏了一些简单的东西,或者我不明白如何进一步改进我已经必须使其支持 checkBoxes
的不同功能的代码......但是,当我对此进行了测试,如果我打开 window 并选中 window 关闭的框并且不会再次打开。 WindowGUI
class 的 IsOpen
成员通过 class' Draw()
方法中的 if 语句控制 window 是否可见.
如果我尝试对复选框使用其他示例,也会发生同样的事情...一旦 checkBox.IsChecked
具有用户给定的值,我就无法更改它。
如果有人能发现我做错了什么并帮助我清理这段代码,将不胜感激。提前致谢!
编辑:根据建议的答案,这是我目前所掌握的内容:
public class CheckBoxGUI : GUIElement
{
Texture2D checkBoxTexEmpty, checkBoxTexSelected;
public Rectangle CheckBoxTxtRect { get; set; }
public Rectangle CheckBoxMiddleRect { get; set; }
public bool IsChecked { get; set; }
public event Action<CheckBoxGUI> CheckedChanged;
public CheckBoxGUI(Rectangle rectangle, string text, bool isDisabled, ContentManager content)
: base(rectangle, text, isDisabled, content)
{
CheckBoxTxtRect = new Rectangle((Bounds.X + 19), (Bounds.Y - 3), ((int)FontName.MeasureString(Text).X), ((int)FontName.MeasureString(Text).Y));
CheckBoxMiddleRect = new Rectangle((Bounds.X + 16), Bounds.Y, 4, 16);
if (checkBoxTexEmpty == null) checkBoxTexEmpty = content.Load<Texture2D>(Game1.CheckBoxPath + @"0") as Texture2D;
if (checkBoxTexSelected == null) checkBoxTexSelected = content.Load<Texture2D>(Game1.CheckBoxPath + @"1") as Texture2D;
}
public void OnCheckedChanged()
{
var h = CheckedChanged;
if (h != null)
h(this);
}
public override void UnloadContent()
{
base.UnloadContent();
if (checkBoxTexEmpty != null) checkBoxTexEmpty = null;
if (checkBoxTexSelected != null) checkBoxTexSelected = null;
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
if (!Game1.IsSoundDisabled)
{
if ((IsHovered) && (!IsDisabled))
{
if (InputManager.IsLeftClicked())
{
ClickSFX.Play();
IsHovered = false;
}
}
}
if (IsClicked) OnCheckedChanged();
}
public void Draw(SpriteBatch spriteBatch)
{
if ((FontName != null) && ((Text != string.Empty) && (Text != null)))
{
if (IsChecked) spriteBatch.Draw(checkBoxTexSelected, Bounds, Color.White);
else if (IsDisabled) spriteBatch.Draw(checkBoxTexEmpty, Bounds, Color.Gray);
else spriteBatch.Draw(checkBoxTexEmpty, Bounds, Color.Gray);
TextOutliner.DrawBorderedText(spriteBatch, FontName, Text, CheckBoxTxtRect.X, CheckBoxTxtRect.Y, ForeColor);
}
}
}
然后 GUIElement
class:
public abstract class GUIElement
{
protected SpriteFont FontName { get; set; }
protected string Text { get; set; }
protected SoundEffect ClickSFX { get; set; }
public Rectangle Bounds { get; set; }
public Color ForeColor { get; set; }
public Color BackColor { get; set; }
public bool IsDisabled { get; set; }
public bool IsHovered { get; set; }
public bool IsClicked { get; set; }
public GUIElement(Rectangle newBounds, string newText, bool isDisabled, ContentManager content)
{
Bounds = newBounds;
Text = newText;
IsDisabled = isDisabled;
FontName = Game1.GameFontSmall;
ForeColor = Color.White;
BackColor = Color.White;
ClickSFX = content.Load<SoundEffect>(Game1.BGSoundPath + @"1") as SoundEffect;
}
public virtual void UnloadContent()
{
if (Bounds != Rectangle.Empty) Bounds = Rectangle.Empty;
if (FontName != null) FontName = null;
if (Text != string.Empty) Text = string.Empty;
if (ClickSFX != null) ClickSFX = null;
}
public virtual void Update(GameTime gameTime)
{
if (!IsDisabled)
{
if (Bounds.Contains(InputManager.MouseRect))
{
if (InputManager.IsLeftClicked()) IsClicked = true;
ForeColor = Color.Yellow;
IsHovered = true;
}
else if (!Bounds.Contains(InputManager.MouseRect))
{
IsHovered = false;
ForeColor = Color.White;
}
}
else ForeColor = Color.Gray;
}
}
这是我的用法:
// Fields
readonly List<GUIElement> elements = new List<GUIElement>();
CheckBoxGUI chk;
bool check = true;
string text;
// LoadContent()
chk = new CheckBoxGUI(new Rectangle(800, 200, 16, 16), "Hide FPS", false, content);
chk.CheckedChanged += cb => check = cb.IsChecked;
elements.Add(chk);
// UnloadContent()
foreach (GUIElement element in elements) element.UnloadContent();
// Update()
for (int i = 0; i < elements.Count; i++) elements[i].Update(gameTime);
foreach (CheckBoxGUI chk in elements) chk.Update(gameTime);
// Draw()
if (chk.IsChecked) check = true;
else check = false;
if (check) text = "True";
else text = "False";
spriteBatch.DrawString(Game1.GameFontLarge, text, new Vector2(800, 400), Color.White);
if (optionsWindow.IsOpen) chk.Draw(spriteBatch);
听起来您正在努力解决的主要问题是弄清楚如何实现事件。所以我将通过一个非常简单的例子来复习。
假设我们想要实现一个简单的 Click
事件。首先在您的 class 上创建一个活动成员,如下所示:
public event EventHandler Click;
然后在您的 Update
方法中,您需要以与播放音效大致相同的方式引发该事件。
public void Update(GameTime gameTime)
{
if (!Game1.IsSoundDisabled)
{
if ((IsHovered) && (!IsDisabled))
if (InputManager.Instance.IsLeftClicked())
{
clickSFX.Play();
IsHovered = false;
if(Click != null)
Click(this, EventArgs.Empty);
}
}
}
这就是它的全部内容。您现在可以在代码的其他部分注册该事件。例如:
checkBox.Click += CheckBox_Click;
并像在 WinForms 中一样实现事件。
private void CheckBox_Click(object sender, EventArgs args)
{
// do something when the check box is clicked.
}
您无需了解太多有关活动的信息即可开始。我唯一要说的是,如果线程安全是一个问题,您应该养成这样引发事件的习惯:
var eventHandler = Click;
if(eventHandler != null)
eventHandler(this, EventArgs.Empty);
但那是另一个故事了。
关于您的代码还有很多其他方面需要改进,但我认为这超出了这个问题的范围。如果您想获得更多关于您的代码的反馈,我建议您将其发布到 code review。
作为@craftworkgames 建议的替代方法,您还可以通过复选框构造函数传递一个委托,然后直接调用它。这和事件之间的区别很小;一个事件基本上是一个委托列表(即指向函数的托管指针),"firing an event" 只是连续调用您附加的所有处理程序(使用 +=
)。使用事件将提供一种更统一的方式(至少对 .NET 开发人员来说更舒服),但我也会在我的回答中提到一些其他方面。
这是总体思路:
public class CheckBoxGUI : GUIElement
{
public bool IsChecked { get; set; }
// instead of using the event, you can use a single delegate
readonly Action<CheckBoxGUI> OnCheckedChanged;
public CheckBoxGUI(Action<CheckBoxGUI> onCheckedChanged, Rectangle rectangle)
: base(rectangle) // we need to pass the rectangle to the base class
{
OnCheckedChanged = onCheckedChanged;
}
public override bool Update(GameTime gameTime, InputState inputState)
{
if (WasClicked(inputState)) // imaginary method
{
// if we are here, it means we need to handle the inputState
IsChecked = !IsChecked;
// this method will be invoked
OnCheckedChanged(this);
return true;
}
else
{
return false;
}
}
... drawing methods, load/unload content
}
当您实例化复选框时,您可以简单地传递一个匿名方法,该方法将在状态更改时被调用:
var checkbox = new CheckBoxGUI(
// when state is changed, `win.IsOpen` will be set to a new value
cb => win.IsOpen = cb.IsChecked,
new Rectangle(0, 0, 100, 100)
);
(顺便说一句,这种方法和事件之间的区别可以忽略不计:)
// if you had a 'CheckedChanged' event, instantiation would look something like this
var checkbox = new CheckBoxGUI(...);
checkbox.CheckedChanged += cb => win.IsOpen = cb.IsChecked;
创建具有共享功能的基本图形用户界面 class
我还会将大部分功能提取到基础 class 中。这与 Control
class 在 WinForms 中作为基础 class 存在的原因相同,除非您使用基础 class,否则您将一遍又一遍地重复此操作。
如果不出意外,所有元素都有一个边界矩形和检查鼠标点击的相同方法:
public class GUIElement
{
public Rectangle Bounds { get; set; }
public GUIElement(Rectangle rect)
{
Bounds = rect;
}
public virtual bool Update(GameTime game, InputState inputState)
{
// if another element already handled this click,
// no need to bother
if (inputState.Handled)
return false;
// you should actually check if mouse was both clicked and released
// within these bounds, but this is just a demo:
if (!inputState.Pressed)
return false;
// within bounds?
if (!this.Bounds.Contains(inputState.Position))
return false;
// mark as handled: we don't want this event to
// propagate further
inputState.Handled = true;
return true;
}
public virtual void Draw(GameTime gameTime, SpriteBatch sb) { }
}
当您决定要在鼠标松开事件上触发鼠标点击时(就像通常那样),您的代码中只有一个地方应该发生这种变化。
顺便说一句。此 class 还应实现所有 UI 元素之间共享的其他属性。您不需要通过构造函数指定它们的值,但必须指定defaults。这类似于 WinForms 所做的:
public class GUIElement
{
public Rectangle Bounds { get; set; }
public SpriteFont Font { get; set; }
public Color ForeColor { get; set; }
public Color BackColor { get; set; }
// make sure you specify all defaults inside the constructor
// (except for Bounds, of course)
}
为了抽象 mouse/touch/gamepad 输入,我在上面使用了一个相当简单的 InputState
(您以后可能想扩展它):
public class InputState
{
// true if this event has already been handled
public bool Handled;
// true if mouse is being held down
public bool Pressed;
// mouse position
public Point Position;
}
从基础继承class
有了这个,你的 CheckBoxGUI
现在继承了 GUIElement
的好东西:
// this is the class from above
public class CheckBoxGUI : GUIElement
{
public bool IsChecked { get; set; }
readonly Action<CheckBoxGUI> OnCheckedChanged;
public CheckBoxGUI(Action<CheckBoxGUI> onCheckedChanged, Rectangle rectangle)
: base(rectangle) // we need to pass the rectangle to the base class
{
OnCheckedChanged = onCheckedChanged;
}
// Note that this method returns bool, unlike 'void Update'.
// Also, intersections should be handled here, not outside.
public override bool Update(GameTime gameTime, InputState inputState)
{
var handled = base.Update(gameTime, inputState);
if (!handled)
return false;
// if we are here, it means we need to handle the inputState
IsChecked = !IsChecked;
OnCheckedChanged(this);
return true;
}
... drawing methods, load/unload content
}
你的game/sceneclass
在您的主要游戏(或场景)更新方法开始时,您只需创建 InputState
实例并为所有元素调用 HandleInput
:
public void Update(GameTime gameTime)
{
var mouse = Mouse.GetState();
var inputState = new InputState()
{
Pressed = mouse.LeftButton == ButtonState.Pressed,
Position = mouse.Position
};
foreach (var element in this.Elements)
{
element.Update(gameTime, inputState);
}
}
基于事件的替代方案
使用基于事件的方法,您可以将复选框更改为:
// this is the class from above
public class CheckBoxGUI : GUIElement
{
public bool IsChecked { get; set; }
public event Action<CheckBoxGUI> CheckedChanged;
protected virtual void OnCheckedChanged()
{
var h = CheckedChanged;
if (h != null)
h(this);
}
public CheckBoxGUI(Rectangle rectangle)
: base(rectangle) // we need to pass the rectangle to the base class
{ }
// the rest of the class remains the same
}
并实例化它:
var cb = new CheckBoxGUI(new Rectangle(0, 0, 100, 100));
cb.CheckedChanged += cb => win.IsOpen = cb.IsChecked;
更新
这是您的 Update
调用堆栈的样子:
Game.Update
- 调用
TitleScreen.Update
TitleScreen.Update
- 为 gui 元素(不仅仅是复选框)调用
element.Update
GuiElement.Update
- 检查该元素是否被点击(由派生的 classes 重复使用)
CheckBoxGUI.Update
- 调用 GuiElement.Update (base.Update) 检查是否刚刚点击
- 如果单击,更改其状态、视觉属性并触发事件
如果操作正确,您的屏幕 class 中应该有一个 gui 元素 列表,而不是实际实例。屏幕不应该知道或关心正在绘制的元素的确切类型:
readonly List<GuiElement> elements = new LIst<GuiElement>();
var chk = new CheckBoxGUI(new Rectangle(800, 200, 16, 16), "Window", false, content);
chk.CheckedChanged += cb => win.IsOpen = cb.IsChecked;
// from now on, your checkbox is just a "gui element" which knows how to
// update itself and draw itself
elements.Add(chk);
// your screen update method
foreach (var e in elements)
e.Update(gameTime);
// your screen draw method
foreach (var e in elements)
e.Draw(gameTime);
我一直在为我的游戏开发 GUI。到目前为止,我已经使用代码创建了看起来像 "forms" 的对象(不要与 winforms 混淆,我使用的是 monogame)。我还创建了按钮。但是现在我很难创建一个复选框。所以这是我的 CheckBoxGUI
class:
// take a look into the recent edits for this post if you need to see my old code.
我的TextOutliner
class曾经用borders/outlines绘制文字:
// take a look into the recent edits for this post if you need to see my old code.
最后,这是我在 TitleScreen
中使用 CheckBoxGUI
class 的方法:
// take a look into the recent edits for this post if you need to see my old code.
这是我的问题(至少我认为是这样,在处理复选框逻辑的方法中):
// take a look into the recent edits for this post if you need to see my old code.
最后,Draw()
:
// take a look into the recent edits for this post if you need to see my old code.
所以方法 CheckBoxInputTS
允许我通过鼠标点击 check/uncheck CheckBoxGUI 项目,但实际上我无法将任何功能附加到此。例如,我可以在同一个方法中执行以下操作:
// take a look into the recent edits for this post if you need to see my old code.
也许我遗漏了一些简单的东西,或者我不明白如何进一步改进我已经必须使其支持 checkBoxes
的不同功能的代码......但是,当我对此进行了测试,如果我打开 window 并选中 window 关闭的框并且不会再次打开。 WindowGUI
class 的 IsOpen
成员通过 class' Draw()
方法中的 if 语句控制 window 是否可见.
如果我尝试对复选框使用其他示例,也会发生同样的事情...一旦 checkBox.IsChecked
具有用户给定的值,我就无法更改它。
如果有人能发现我做错了什么并帮助我清理这段代码,将不胜感激。提前致谢!
编辑:根据建议的答案,这是我目前所掌握的内容:
public class CheckBoxGUI : GUIElement
{
Texture2D checkBoxTexEmpty, checkBoxTexSelected;
public Rectangle CheckBoxTxtRect { get; set; }
public Rectangle CheckBoxMiddleRect { get; set; }
public bool IsChecked { get; set; }
public event Action<CheckBoxGUI> CheckedChanged;
public CheckBoxGUI(Rectangle rectangle, string text, bool isDisabled, ContentManager content)
: base(rectangle, text, isDisabled, content)
{
CheckBoxTxtRect = new Rectangle((Bounds.X + 19), (Bounds.Y - 3), ((int)FontName.MeasureString(Text).X), ((int)FontName.MeasureString(Text).Y));
CheckBoxMiddleRect = new Rectangle((Bounds.X + 16), Bounds.Y, 4, 16);
if (checkBoxTexEmpty == null) checkBoxTexEmpty = content.Load<Texture2D>(Game1.CheckBoxPath + @"0") as Texture2D;
if (checkBoxTexSelected == null) checkBoxTexSelected = content.Load<Texture2D>(Game1.CheckBoxPath + @"1") as Texture2D;
}
public void OnCheckedChanged()
{
var h = CheckedChanged;
if (h != null)
h(this);
}
public override void UnloadContent()
{
base.UnloadContent();
if (checkBoxTexEmpty != null) checkBoxTexEmpty = null;
if (checkBoxTexSelected != null) checkBoxTexSelected = null;
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
if (!Game1.IsSoundDisabled)
{
if ((IsHovered) && (!IsDisabled))
{
if (InputManager.IsLeftClicked())
{
ClickSFX.Play();
IsHovered = false;
}
}
}
if (IsClicked) OnCheckedChanged();
}
public void Draw(SpriteBatch spriteBatch)
{
if ((FontName != null) && ((Text != string.Empty) && (Text != null)))
{
if (IsChecked) spriteBatch.Draw(checkBoxTexSelected, Bounds, Color.White);
else if (IsDisabled) spriteBatch.Draw(checkBoxTexEmpty, Bounds, Color.Gray);
else spriteBatch.Draw(checkBoxTexEmpty, Bounds, Color.Gray);
TextOutliner.DrawBorderedText(spriteBatch, FontName, Text, CheckBoxTxtRect.X, CheckBoxTxtRect.Y, ForeColor);
}
}
}
然后 GUIElement
class:
public abstract class GUIElement
{
protected SpriteFont FontName { get; set; }
protected string Text { get; set; }
protected SoundEffect ClickSFX { get; set; }
public Rectangle Bounds { get; set; }
public Color ForeColor { get; set; }
public Color BackColor { get; set; }
public bool IsDisabled { get; set; }
public bool IsHovered { get; set; }
public bool IsClicked { get; set; }
public GUIElement(Rectangle newBounds, string newText, bool isDisabled, ContentManager content)
{
Bounds = newBounds;
Text = newText;
IsDisabled = isDisabled;
FontName = Game1.GameFontSmall;
ForeColor = Color.White;
BackColor = Color.White;
ClickSFX = content.Load<SoundEffect>(Game1.BGSoundPath + @"1") as SoundEffect;
}
public virtual void UnloadContent()
{
if (Bounds != Rectangle.Empty) Bounds = Rectangle.Empty;
if (FontName != null) FontName = null;
if (Text != string.Empty) Text = string.Empty;
if (ClickSFX != null) ClickSFX = null;
}
public virtual void Update(GameTime gameTime)
{
if (!IsDisabled)
{
if (Bounds.Contains(InputManager.MouseRect))
{
if (InputManager.IsLeftClicked()) IsClicked = true;
ForeColor = Color.Yellow;
IsHovered = true;
}
else if (!Bounds.Contains(InputManager.MouseRect))
{
IsHovered = false;
ForeColor = Color.White;
}
}
else ForeColor = Color.Gray;
}
}
这是我的用法:
// Fields
readonly List<GUIElement> elements = new List<GUIElement>();
CheckBoxGUI chk;
bool check = true;
string text;
// LoadContent()
chk = new CheckBoxGUI(new Rectangle(800, 200, 16, 16), "Hide FPS", false, content);
chk.CheckedChanged += cb => check = cb.IsChecked;
elements.Add(chk);
// UnloadContent()
foreach (GUIElement element in elements) element.UnloadContent();
// Update()
for (int i = 0; i < elements.Count; i++) elements[i].Update(gameTime);
foreach (CheckBoxGUI chk in elements) chk.Update(gameTime);
// Draw()
if (chk.IsChecked) check = true;
else check = false;
if (check) text = "True";
else text = "False";
spriteBatch.DrawString(Game1.GameFontLarge, text, new Vector2(800, 400), Color.White);
if (optionsWindow.IsOpen) chk.Draw(spriteBatch);
听起来您正在努力解决的主要问题是弄清楚如何实现事件。所以我将通过一个非常简单的例子来复习。
假设我们想要实现一个简单的 Click
事件。首先在您的 class 上创建一个活动成员,如下所示:
public event EventHandler Click;
然后在您的 Update
方法中,您需要以与播放音效大致相同的方式引发该事件。
public void Update(GameTime gameTime)
{
if (!Game1.IsSoundDisabled)
{
if ((IsHovered) && (!IsDisabled))
if (InputManager.Instance.IsLeftClicked())
{
clickSFX.Play();
IsHovered = false;
if(Click != null)
Click(this, EventArgs.Empty);
}
}
}
这就是它的全部内容。您现在可以在代码的其他部分注册该事件。例如:
checkBox.Click += CheckBox_Click;
并像在 WinForms 中一样实现事件。
private void CheckBox_Click(object sender, EventArgs args)
{
// do something when the check box is clicked.
}
您无需了解太多有关活动的信息即可开始。我唯一要说的是,如果线程安全是一个问题,您应该养成这样引发事件的习惯:
var eventHandler = Click;
if(eventHandler != null)
eventHandler(this, EventArgs.Empty);
但那是另一个故事了。
关于您的代码还有很多其他方面需要改进,但我认为这超出了这个问题的范围。如果您想获得更多关于您的代码的反馈,我建议您将其发布到 code review。
作为@craftworkgames 建议的替代方法,您还可以通过复选框构造函数传递一个委托,然后直接调用它。这和事件之间的区别很小;一个事件基本上是一个委托列表(即指向函数的托管指针),"firing an event" 只是连续调用您附加的所有处理程序(使用 +=
)。使用事件将提供一种更统一的方式(至少对 .NET 开发人员来说更舒服),但我也会在我的回答中提到一些其他方面。
这是总体思路:
public class CheckBoxGUI : GUIElement
{
public bool IsChecked { get; set; }
// instead of using the event, you can use a single delegate
readonly Action<CheckBoxGUI> OnCheckedChanged;
public CheckBoxGUI(Action<CheckBoxGUI> onCheckedChanged, Rectangle rectangle)
: base(rectangle) // we need to pass the rectangle to the base class
{
OnCheckedChanged = onCheckedChanged;
}
public override bool Update(GameTime gameTime, InputState inputState)
{
if (WasClicked(inputState)) // imaginary method
{
// if we are here, it means we need to handle the inputState
IsChecked = !IsChecked;
// this method will be invoked
OnCheckedChanged(this);
return true;
}
else
{
return false;
}
}
... drawing methods, load/unload content
}
当您实例化复选框时,您可以简单地传递一个匿名方法,该方法将在状态更改时被调用:
var checkbox = new CheckBoxGUI(
// when state is changed, `win.IsOpen` will be set to a new value
cb => win.IsOpen = cb.IsChecked,
new Rectangle(0, 0, 100, 100)
);
(顺便说一句,这种方法和事件之间的区别可以忽略不计:)
// if you had a 'CheckedChanged' event, instantiation would look something like this
var checkbox = new CheckBoxGUI(...);
checkbox.CheckedChanged += cb => win.IsOpen = cb.IsChecked;
创建具有共享功能的基本图形用户界面 class
我还会将大部分功能提取到基础 class 中。这与 Control
class 在 WinForms 中作为基础 class 存在的原因相同,除非您使用基础 class,否则您将一遍又一遍地重复此操作。
如果不出意外,所有元素都有一个边界矩形和检查鼠标点击的相同方法:
public class GUIElement
{
public Rectangle Bounds { get; set; }
public GUIElement(Rectangle rect)
{
Bounds = rect;
}
public virtual bool Update(GameTime game, InputState inputState)
{
// if another element already handled this click,
// no need to bother
if (inputState.Handled)
return false;
// you should actually check if mouse was both clicked and released
// within these bounds, but this is just a demo:
if (!inputState.Pressed)
return false;
// within bounds?
if (!this.Bounds.Contains(inputState.Position))
return false;
// mark as handled: we don't want this event to
// propagate further
inputState.Handled = true;
return true;
}
public virtual void Draw(GameTime gameTime, SpriteBatch sb) { }
}
当您决定要在鼠标松开事件上触发鼠标点击时(就像通常那样),您的代码中只有一个地方应该发生这种变化。
顺便说一句。此 class 还应实现所有 UI 元素之间共享的其他属性。您不需要通过构造函数指定它们的值,但必须指定defaults。这类似于 WinForms 所做的:
public class GUIElement
{
public Rectangle Bounds { get; set; }
public SpriteFont Font { get; set; }
public Color ForeColor { get; set; }
public Color BackColor { get; set; }
// make sure you specify all defaults inside the constructor
// (except for Bounds, of course)
}
为了抽象 mouse/touch/gamepad 输入,我在上面使用了一个相当简单的 InputState
(您以后可能想扩展它):
public class InputState
{
// true if this event has already been handled
public bool Handled;
// true if mouse is being held down
public bool Pressed;
// mouse position
public Point Position;
}
从基础继承class
有了这个,你的 CheckBoxGUI
现在继承了 GUIElement
的好东西:
// this is the class from above
public class CheckBoxGUI : GUIElement
{
public bool IsChecked { get; set; }
readonly Action<CheckBoxGUI> OnCheckedChanged;
public CheckBoxGUI(Action<CheckBoxGUI> onCheckedChanged, Rectangle rectangle)
: base(rectangle) // we need to pass the rectangle to the base class
{
OnCheckedChanged = onCheckedChanged;
}
// Note that this method returns bool, unlike 'void Update'.
// Also, intersections should be handled here, not outside.
public override bool Update(GameTime gameTime, InputState inputState)
{
var handled = base.Update(gameTime, inputState);
if (!handled)
return false;
// if we are here, it means we need to handle the inputState
IsChecked = !IsChecked;
OnCheckedChanged(this);
return true;
}
... drawing methods, load/unload content
}
你的game/sceneclass
在您的主要游戏(或场景)更新方法开始时,您只需创建 InputState
实例并为所有元素调用 HandleInput
:
public void Update(GameTime gameTime)
{
var mouse = Mouse.GetState();
var inputState = new InputState()
{
Pressed = mouse.LeftButton == ButtonState.Pressed,
Position = mouse.Position
};
foreach (var element in this.Elements)
{
element.Update(gameTime, inputState);
}
}
基于事件的替代方案
使用基于事件的方法,您可以将复选框更改为:
// this is the class from above
public class CheckBoxGUI : GUIElement
{
public bool IsChecked { get; set; }
public event Action<CheckBoxGUI> CheckedChanged;
protected virtual void OnCheckedChanged()
{
var h = CheckedChanged;
if (h != null)
h(this);
}
public CheckBoxGUI(Rectangle rectangle)
: base(rectangle) // we need to pass the rectangle to the base class
{ }
// the rest of the class remains the same
}
并实例化它:
var cb = new CheckBoxGUI(new Rectangle(0, 0, 100, 100));
cb.CheckedChanged += cb => win.IsOpen = cb.IsChecked;
更新
这是您的 Update
调用堆栈的样子:
Game.Update
- 调用
TitleScreen.Update
- 调用
TitleScreen.Update
- 为 gui 元素(不仅仅是复选框)调用
element.Update
- 为 gui 元素(不仅仅是复选框)调用
GuiElement.Update
- 检查该元素是否被点击(由派生的 classes 重复使用)
CheckBoxGUI.Update
- 调用 GuiElement.Update (base.Update) 检查是否刚刚点击
- 如果单击,更改其状态、视觉属性并触发事件
如果操作正确,您的屏幕 class 中应该有一个 gui 元素 列表,而不是实际实例。屏幕不应该知道或关心正在绘制的元素的确切类型:
readonly List<GuiElement> elements = new LIst<GuiElement>();
var chk = new CheckBoxGUI(new Rectangle(800, 200, 16, 16), "Window", false, content);
chk.CheckedChanged += cb => win.IsOpen = cb.IsChecked;
// from now on, your checkbox is just a "gui element" which knows how to
// update itself and draw itself
elements.Add(chk);
// your screen update method
foreach (var e in elements)
e.Update(gameTime);
// your screen draw method
foreach (var e in elements)
e.Draw(gameTime);