Auf das Thema antworten  [ 4 Beiträge ] 
Klasse MouseEvents 
Autor Nachricht
Administrator
Benutzeravatar

Registriert: Sa 15. Dez 2012, 19:15
Beiträge: 137
Wohnort: Karlsruhe
Mit Zitat antworten
Hallo Leute!

Wer mit XNA ein Spiel programmieren will wird früher oder später auf das folgende Problem stoßen: XNA kennt nur die Buttonstates Pressed und Released. Verwendet man nun diese um zu prüfen, ob der Benutzer auf das Menü geklickt hat, dann wird diese Prüfung 60 Mal in der Sekunde durchgeführt, das heißt das Menü 30 Mal pro Sekunde geöffnet und geschlossen. Das verursacht beim Programmierer eine ganz eigene Version von epileptischen Anfall.

Anfangs habe ich das mit einer Liste von bool und DateTime.Now.Ticks gelöst. Sodass es nur maximal 5 Mal pro Sekunde zu diesem Unfug kam - aber eine endgültige Lösung war das nicht. Dafür brauchte ich eine eigene Klasse: Die MouseEvents!

Außerdem konnte ich mit dieser Klasse auch die unabdingbaren Events MouseEnter, MouseOver, MouseLeave und MouseOut implementieren. MouseClick - wie bereits angedeutet - auch.


Die statische Klasse MouseEvents
Zunächst eine Übersicht über die Klasse und ihre Funktionen:
Code:
 
  public enum JustStates { Up, JustDown, Down, JustUp }
 
  public static class MouseEvents
  {
    public static MouseState MouseState { get; private set; }
    public static Vector2 MousePosition { get { return new Vector2(MouseState.X, MouseState.Y); } }
    public delegate void OnHandler(Entry mouseEvents);
    public enum Status { Out, Enter, Over, Leave };
    public static List<Entry> Entries = new List<Entry>();
    public static void Update(MouseState mouseState) { ... }
 
    public class Entry : IComparable { ... }
 
    public static class JustState { ... }
  }
 

Zunächst brauchen wir ein Enum das vier Stände speichert anstatt nur zwei für das MouseClick-Event. Um Verwirrungen vorzubeugen habe ich mich einfach für Up (nicht gedrückt), JustDown (gerade gedrückt), Down (gedrückt), JustUp (gerade losgelassen) entschieden. Unter "gerade" verstehe ich den aktuellen Tick. Denn damit die Klasse richtig funktioniert, muss die Methode Update in der Methode Update unseres Spieles aufgerufen werden.

Danach kommt die statische Klasse MouseEvents. Für alle Events (MouseEnter, MouseOver, MouseLeave, MouseOut) braucht es einen Eintrag (Entry) in der Liste Entries. Je nachdem welchen Status diese einzelnen Entries haben, wird eine Methode mit der Signatur OnHandler aufgerufen, sollte eine Methode gesetzt und der Entry aktiv sein. Das erkläre ich gleich etwas genauer. Kümmern wir uns zunächst um das einfachere: Die JustStates für das Event MouseClick.


Event MouseClick
XNA kennt fünf Tasten für die Maus und für jede Taste gibt es in der statischen Klasse JustState in der statischen Klasse MouseEvents ein Property in dem deren aktueller JustState gespeichert ist.
Code:
 
  public static class JustState
  {
    public static JustStates LeftButton { get; set; }
    public static JustStates RightButton { get; set; }
    public static JustStates MiddleButton { get; set; }
    public static JustStates XButton1 { get; set; }
    public static JustStates XButton2 { get; set; }
  }
 

So kann auf folgende einfache Weise auf die Zustande zugegriffen werden:
Code:
 
 if(MouseEvents.JustState.LeftButton == JustStates.JustDown) { OpenMenu(); }
 


Die Informationen in dieser statischen Klasse werden über die Methode MouseEvents.Update() bei jedem Tick erneuert. Sie muss in der Update-Methode in unserem Spiel aufgerufen werden. Hier ein Ausschnitt aus meiner Update-Methode:
Code:
 
  protected override void Update(GameTime gameTime)
  {
    MouseEvents.Update();
  }
 

Wie man sieht, sieht man nicht viel. Deshalb schauen wir uns nun die Methode Update mal genauer an.


Methode Update
Code:
 
public static void Update()
{
  MouseState = Mouse.GetState();
 
  if (MouseState.LeftButton == ButtonState.Pressed)
  {
    if (JustState.LeftButton == JustStates.Up) JustState.LeftButton = JustStates.JustDown;
    else JustState.LeftButton = JustStates.Down;
  }
  else
  {
    if (JustState.LeftButton == JustStates.Down) JustState.LeftButton = JustStates.JustUp;
    else JustState.LeftButton = JustStates.Up;
  }
 
  if (MouseState.RightButton == ButtonState.Pressed)
  {
    if (JustState.RightButton == JustStates.Up) JustState.RightButton = JustStates.JustDown;
    else JustState.RightButton = JustStates.Down;
  }
  else
  {
    if (JustState.RightButton == JustStates.Down) JustState.RightButton = JustStates.JustUp;
    else JustState.RightButton = JustStates.Up;
  }
 
  if (MouseState.MiddleButton == ButtonState.Pressed)
  {
    if (JustState.MiddleButton == JustStates.Up) JustState.MiddleButton = JustStates.JustDown;
    else JustState.MiddleButton = JustStates.Down;
  }
  else
  {
    if (JustState.MiddleButton == JustStates.Down) JustState.MiddleButton = JustStates.JustUp;
    else JustState.MiddleButton = JustStates.Up;
  }
 
  if (MouseState.XButton1 == ButtonState.Pressed)
  {
    if (JustState.XButton1 == JustStates.Up) JustState.XButton1 = JustStates.JustDown;
    else JustState.XButton1 = JustStates.Down;
  }
  else
  {
    if (JustState.XButton1 == JustStates.Down) JustState.XButton1 = JustStates.JustUp;
    else JustState.XButton1 = JustStates.Up;
  }
 
  if (MouseState.XButton2 == ButtonState.Pressed)
  {
    if (JustState.XButton2 == JustStates.Up) JustState.XButton2 = JustStates.JustDown;
    else JustState.XButton2 = JustStates.Down;
  }
  else
  {
    if (JustState.XButton2 == JustStates.Down) JustState.XButton2 = JustStates.JustUp;
    else JustState.XButton2 = JustStates.Up;
  }
 
  foreach (Entry e in Entries.ToArray())
    e.Check();
  }
 

Zunächst wird der aktuelle MouseState gespeichert. Dann die JustState den jeweiligen Tasten zugeordnet und zum Schluss kümmern wir uns um die mögliche Auslösung der Events MouseEnter, MouseOver, MouseLeave, MouseOut und MouseClick aller Einträge.

Exemplarisch für die linke Maustaste:
Wenn die linke Maustaste gedrückt wurde muss festgestellt werden ob sie vorher bereits gedrückt war. War sie das nicht (Up), dann wurde sie gerade gedrückt und ihr Zustand ist JustDown. Ansonsten einfach nur Down.
Wurde sie nicht gedrückt, dann muss geprüft werden, ob sie vorher gedrückt war. Wenn ja (Down), dann wurde sie gerade losgelassen und bekommt den Zustand JustUp. Ansonsten hat sie weiterhin den Zustand (Up). So wird auch aus dem Zustand JustUp jetzt ein Up. Dasselbe gilt für JustDown und Down.


Fr 22. Mär 2013, 11:34
Diesen Beitrag melden
Profil Website besuchen
Administrator
Benutzeravatar

Registriert: Sa 15. Dez 2012, 19:15
Beiträge: 137
Wohnort: Karlsruhe
Mit Zitat antworten
 
Die Klasse Entry
Hier werden alle Informationen für die Events MouseEnter, MouseOver, MouseLeave, MouseOut und MouseClick gespeichert und die jeweilige Methode aufgerufen, sollten die Variablen für die Events nicht null sein.

Zunächst wieder eine Übersicht:
Code:
 
public class Entry : IComparable
  {
    public float VonX { get; set; }
    public float VonY { get; set; }
    public float BisX { get; set; }
    public float BisY { get; set; }
 
    public Vector2 Von
    {
      get { return new Vector2(VonX, VonY); }
      set { VonX = value.X; VonY = value.Y; }
    }
    public Vector2 Bis
    {
      get { return new Vector2(BisX, BisY); }
      set { BisX = value.X; BisY = value.Y; }
    }
    public Vector4 Bounds
    {
      get { return new Vector4(VonX, VonY, BisX, BisY); }
      set { VonX = value.X; VonY = value.Y; BisX = value.Z; BisY = value.W; }
    }
 
    public OnHandler OnEnter { get; set; }
    public OnHandler OnOver { get; set; }
    public OnHandler OnLeave { get; set; }
    public OnHandler OnOut { get; set; }
    public OnHandler OnClick { get; set; }
 
    public Status Status { get; private set; }
    public object UserState { get; set; }
    public bool Active { get; set; }
 
    public Entry(
      float vonX, float vonY, float bisX, float bisY,
      OnHandler onEnter, OnHandler onOver, OnHandler onLeave, OnHandler onOut, OnHandler onClick,
      object userState, bool active = true)
    {
      VonX = vonX; VonY = vonY; BisX = bisX; BisY = bisY;
      OnEnter = onEnter; OnOver = onOver; OnLeave = onLeave; OnOut = onOut; OnClick = onClick;
      Status = MouseEvents.Status.Out; UserState = userState; Active = active;
    }
 
    public Entry(Vector4 bereich, OnHandler onEnter, OnHandler onLeave, OnHandler onClick, object userState, bool active)
    {
      Bounds = bereich; OnEnter = onEnter; OnOver = null; OnLeave = onLeave; OnOut = null; OnClick = onClick;
      Status = MouseEvents.Status.Out; UserState = userState; Active = active;
    }
 
    public void Check() { ... }
   
    int IComparable.CompareTo(object obj) { ... }
  }
 

Zunächst zu den Properties VonX, VonY, BisX, BisY: Alle diese Events werden nur ausgelöst, wenn die Maus sich in einem bestimmten Rechteck befindet. Dieses definiere ich durch zwei Punkte. Den Punkt oben, links und den Punkt unten, rechts vom Rechteck. Dieses kann man auch so über das Property Bounds zurück bekommen. Bzw. die zwei angesprochenen Punkte über die Properties Von bzw. Bis.

Der Status ist im Beitrag oben definiert. Er kann Out, Enter, Over oder Leave sein und wird nur für die Events MouseEnter, MouseOver, MouseLeave und MouseOut benötigt. Der Status speichert, ob die Maus sich gerade in dem Rechteck befindet oder nicht. Bzw. es gerade betreten oder verlassen hat. Dies wird in der Methode Check() überprüft.

Das object UserState ist für eine Übergabe eines Objektes als Referenz gedacht und übergibt konkret bei mir eine Instanz der Klasse MenuButton. Es ist ganz nett. Ähnlich wie beim System.EventHandler der Parameter sender. Nur dass bei meinem Delegaten das ganze Entry übertragen wird und man darüber auf die Referenz dann zugreifen kann.

Aktiv hat quasi die selbe Bedeutung von Visible bei Controls. Wenn der Boolean false ist, werden auch keine Events ausgelöst.

Die zwei Konstruktoren weisen lediglich die übertragenen Werte den Properties zu. Der zweite Konstruktor ist speziell für mein Menü eingefügt worden. Die Methode IComparable.CompareTo(object obj) implementiert die Schnittstelle IComparable wodurch die Entries in einer Liste sortiert werden können. Das habe ich für eine Idee, die ich momentan nicht weiter verfolge, implementiert gehabt.

Ihr kennt Events aus der ganz normalen Umgebung von C#. Man abonniert sie mit dem Operator += . Hier muss man den Properties OnEnter, OnOver, OnLeave, OnOut und OnClick eine Methode mit der Signatur von MouseEvents.OnHandler(MouseEvents.Entry entry) zuweisen oder sie auf null setzen, sollen sie deaktiviert werden. Sollen alle Events vorübergehend deaktiviert werden, dann sollte man Active auf false stellen.


Die Methode Check
Code:
 
public void Check()
  {
    if (!Active) { return; }
 
    if (MouseState.X < Bounds.X || MouseState.X > Bounds.Z || MouseState.Y < Bounds.Y || MouseState.Y > Bounds.W)
    {
      if (Status == Status.Enter || Status == Status.Over)
          {
            Status = Status.Leave;
            if (OnLeave != null) { OnLeave(this); }
            return;
          }
      if (Status == Status.Out || Status == Status.Leave)
          {
            Status = Status.Out;
            if (OnOut != null) { OnOut(this); }
            return;
          }
    }
 
    if (OnClick != null &amp;&amp;
      (MouseState.LeftButton == ButtonState.Pressed
      || MouseState.RightButton == ButtonState.Pressed
      || MouseState.MiddleButton == ButtonState.Pressed
      || MouseState.XButton1 == ButtonState.Pressed
      || MouseState.XButton2 == ButtonState.Pressed)) { OnClick(this); }
    if (Status == Status.Out || Status == Status.Leave)
        {
          Status = Status.Enter;
          if (OnEnter != null) { OnEnter(this); }
          return;
        }
    if (Status == Status.Enter || Status == Status.Over)
        {
          Status = Status.Over;
          if (OnOver != null) { OnOver(this); }
          return;
        }
  }
 

Wird das Object gerade nicht angezeigt, dann braucht es auch keine Überprüfung.

Sollte die Maus sich zu weit links, rechts, oben oder unten vom Bereich befinden, dann können die Events MouseLeave oder MouseOut eintreten. OnClick und die anderen beiden OnEnter oder OnOver nicht. Deshalb die Escape-Anweisung return. OnLeave wird ausgelöst, wenn der letzte Status Enter, oder Over war. OnOut, wenn der letzte Status Out oder Leave war.

Befindet sich die Maus im Bereich, dann wird geprüft, ob eine Maustaste gedrückt wurde und ggf. das Event ausgelöst, sollte das nicht null sein. Das Event OnEnter wird ausgelöst, wenn der vorherige Status Out oder Leave war. OnOver, wenn der vorherige Status Enter oder Over war.

Für OnClick sollte man in der referenzierten Methode noch prüfen, welche Taste denn eigentlich gedrückt wurde. Meist soll sich nämlich nur bei einem Linksklick etwas tun und bei einem Rechtsklick etwas ganz anderes.


Anwendungsbeispiel
Für mein Menü weise ich bei der Initialisierung eines MenuButtons die Events folgender Maßen zu:
Code:
 
  Events = new MouseEvents.Entry(
  Bounds,
  new MouseEvents.OnHandler(OnMouseEnter),
  new MouseEvents.OnHandler(OnMouseLeave),
  new MouseEvents.OnHandler(OnMouseClick),
  this,
  visible);
 

Bounds sind die Begrenzungen des Buttons (eigenes Property); OnMouseEnter, OnMouseLeave und OnMouseClick die jeweiligen Methoden die beim Auslösen der Events aufgerufen werden sollen. Danach kommt der UserState (sender beim System.EventHandler) und ein Boolean, ob der Button sichtbar ist. Es wird der zweite Konstruktor hier verwendet.


Ich hoffe, ich konnte euch die Klassen etwas näher bringen ;)

Schöne Grüße,
Magony

_________________
Bei Fragen, Lob, Kritik, Vorschläge, hilfreiche Hinweise oder Alternativvorschläge: Beitrag, neues Thema oder PN.
Für Dinge die diskutiert werden sollten, bitte neues Thema im jeweiligen Forum.
Wenn du nicht weißt wohin: Forum Unsortiert.


Fr 22. Mär 2013, 12:21
Diesen Beitrag melden
Profil Website besuchen
Administrator
Benutzeravatar

Registriert: Sa 15. Dez 2012, 19:15
Beiträge: 137
Wohnort: Karlsruhe
Mit Zitat antworten
Hier kann die komplette Klasse heruntergeladen werden:

http://download.magony.org/?file=csharp/XNA/MouseEvents/MouseEvents.zip

_________________
Bei Fragen, Lob, Kritik, Vorschläge, hilfreiche Hinweise oder Alternativvorschläge: Beitrag, neues Thema oder PN.
Für Dinge die diskutiert werden sollten, bitte neues Thema im jeweiligen Forum.
Wenn du nicht weißt wohin: Forum Unsortiert.


Mi 27. Mär 2013, 14:39
Diesen Beitrag melden
Profil Website besuchen
Administrator
Benutzeravatar

Registriert: Sa 15. Dez 2012, 19:15
Beiträge: 137
Wohnort: Karlsruhe
Mit Zitat antworten
Hallo Leute!

In diesem Thread biete ich euch die Bibliothek magonyORG.Input als OpenSource mit einer kleinem Testumgebung und einem Beispiel an. Sie beinhaltet sowohl die MouseEvents als auch die KeyEvents und den Button. Außerdem bietet sie weitere Verbesserungen und wird auch in Zukunft weiterentwickelt.

Schöne Grüße,
Magony

_________________
Bei Fragen, Lob, Kritik, Vorschläge, hilfreiche Hinweise oder Alternativvorschläge: Beitrag, neues Thema oder PN.
Für Dinge die diskutiert werden sollten, bitte neues Thema im jeweiligen Forum.
Wenn du nicht weißt wohin: Forum Unsortiert.


Sa 28. Sep 2013, 14:37
Diesen Beitrag melden
Profil Website besuchen
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Auf das Thema antworten   [ 4 Beiträge ] 

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast


Du darfst neue Themen in diesem Forum erstellen.
Du darfst Antworten zu Themen in diesem Forum erstellen.
Du darfst deine Beiträge in diesem Forum nicht ändern.
Du darfst deine Beiträge in diesem Forum nicht löschen.
Du darfst keine Dateianhänge in diesem Forum erstellen.

Suche nach:
Gehe zu:  
cron
Powered by phpBB® Forum Software © phpBB Group
Designed by ST Software
Deutsche Übersetzung durch phpBB.de

Impressum