Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/284.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 单游戏-创建复选框对象_C#_Checkbox_Monogame - Fatal编程技术网

C# 单游戏-创建复选框对象

C# 单游戏-创建复选框对象,c#,checkbox,monogame,C#,Checkbox,Monogame,我一直在为我的游戏开发GUI。到目前为止,我已经使用代码创建了看起来像“表单”的对象(不要与winforms混淆,我使用的是monogame)。我还创建了按钮。但是现在我很难创建一个复选框。下面是我的CheckBoxGUI类: // take a look into the recent edits for this post if you need to see my old code. public abstract class GUIElement { protected Spr

我一直在为我的游戏开发GUI。到目前为止,我已经使用代码创建了看起来像“表单”的对象(不要与winforms混淆,我使用的是monogame)。我还创建了按钮。但是现在我很难创建一个复选框。下面是我的
CheckBoxGUI
类:

// take a look into the recent edits for this post if you need to see my old code.
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;
    }
}
My
TextOutliner
用于绘制带边框/轮廓的文本的类:

// take a look into the recent edits for this post if you need to see my old code.
最后,下面是我如何在我的
标题屏幕中使用
CheckBoxGUI
类:

// take a look into the recent edits for this post if you need to see my old code.
这是我的问题(至少我认为是这样,在处理复选框逻辑的方法中):

最后,
Draw()

因此,
checkboxinputs
方法允许我通过鼠标单击来选中/取消选中CheckBoxGUI项,但我实际上无法为其附加任何功能。例如,我可以在同一方法中执行以下操作:

// take a look into the recent edits for this post if you need to see my old code.
也许我遗漏了一些简单的东西,或者我不知道如何对代码进行进一步的改进,我已经不得不让它支持
复选框的不同功能。。。但是,当我测试这个时,如果我打开窗口并选中该框,窗口将关闭,并且不会再次打开。
WindowGUI
类的
IsOpen
成员通过类'
Draw()
方法中的if语句控制窗口是否可见

如果我尝试使用其他复选框示例,也会发生同样的情况。。。一旦
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);
        }
    }
}
这是我的用法:

// 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);
//字段
只读列表元素=新列表();
CheckBoxGUI-chk;
布尔检查=真;
字符串文本;
//LoadContent()
chk=新的CheckBoxGUI(新矩形(800、200、16、16),“隐藏FPS”,false,content);
chk.CheckedChanged+=cb=>check=cb.IsChecked;
元素。添加(chk);
//卸载内容()
foreach(元素中的GUIElement元素)元素;
//更新()
对于(inti=0;i
听起来,您正在努力解决的主要问题是如何实现事件。所以我将用一个非常简单的例子来说明这一点

假设我们想要实现一个简单的
点击事件。首先在类上创建一个事件成员,如下所示:

public event EventHandler Click;
var eventHandler = Click;

if(eventHandler != null)
    eventHandler(this, EventArgs.Empty);
然后在
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.
}
你不需要知道很多其他的事情就可以开始了。我唯一想说的是,如果线程安全是一个问题,那么您应该养成这样的习惯:

public event EventHandler Click;
var eventHandler = Click;

if(eventHandler != null)
    eventHandler(this, EventArgs.Empty);
但这是另一个时代的故事


您的代码还有很多其他方面需要改进,但我认为这超出了这个问题的范围。如果您想获得更多关于代码的反馈,我建议将其发布。

作为@craftworkgames建议的替代方案,您还可以通过复选框构造函数传递一个委托,并直接调用它。这与事件之间的差异是微不足道的;事件基本上是一个委托列表(即指向函数的托管指针),“触发事件”只需依次调用所有附加的处理程序(使用
+=
)。使用事件将提供一种更统一的方式(至少对.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
}
(顺便说一句,这种方法和事件之间的差异可以忽略不计:)

创建具有共享功能的基本gui类

我还将把大部分功能提取到基类中。这与WinForms中存在的
控件
类作为基类的原因相同,除非使用基类,否则您将反复重复此操作

如果没有其他内容,则所有元素都有一个边框,并且检查鼠标单击的方式相同:

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) { }
}
当您决定要在鼠标启动事件中触发鼠标单击时(就像通常那样),您的代码中只有一个地方应该发生此更改

顺便说一句,这个类还应该实现所有UI元素之间共享的其他属性。您不需要通过构造函数指定它们的值,但必须指定默认值。这与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)
}
为了抽象鼠标/触摸屏/游戏板输入,我在上面使用了一个相当简单的
InputState
(您可能希望稍后对其进行扩展):

从基类继承

有了它,您的
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
}
基于事件的备选方案

使用基于事件的方法,可以将复选框更改为:

// 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
}
更新

这就是
更新
调用堆栈的外观:

  • 游戏。更新

    • 调用标题屏幕。更新
  • 标题屏幕。更新

    • 调用
      元素。为gui元素更新
      (不仅仅是复选框)
  • GuiElement.Update

    • 检查此元素是否为cl
      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;
      }
      
      // 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
      }
      
      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;
      
      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);