Améliorer la classe Mouse

Dans cet article, je vais vous exposer mes impressions sur la gestion de la souris dans le Framework XNA (notamment les différences par rapport au clavier et au gamepad), et qu’est ce qui peut être fait pour améliorer son fonctionnement.

 

Constations

 

Je suis parti d’un constat simple : avec le clavier et le gamepad, nous avons à notre disposition deux structures (KeyboardState et GamePadState) qui permettent d’avoir l’état du clavier et des gamepads à un instant précis. A partir de ces structures, nous pouvons savoir, à l’aide des méthodes IsKeyDown (Keys key)et IsButtonDown(Buttons button) quelles touches pour le clavier et quels boutons pour les gamepads sont enfoncés (ou pas).

Il existe également une structure permettant de décrire l’état de la souris à un instant T, MouseState. Sauf qu’il n’existe aucune méthode du genre “IsMouseButtonDown” qui puisse permettre de savoir si, par exemple, on a scrollé vers le haut, si on cliqué avec le bouton gauche, ou encore si on a déplacé la souris vers la droite ou vers la gauche, etc… Nous avons seulement des valeurs de type int ou de type ButtonState à notre disposition. Je me suis donc servi de ces valeurs pour arriver au comportement souhaité, c’est à dire quelque chose du genre:

_monMouseState.IsMouseButtonDown(MouseButtons.MoveRight)

 

Passons à l’écriture de ma solution.

 

 

Ma solution

 

J’ai eu besoin de 3 ensembles de données : 1 classe EnhancedMouse (équivalent de la classe Mouse du Framework, mais qui ajoute des fonctionnalités supplémentaires), 1 structure EnhancedMouseState (équivalent de la structure MouseState du Framework, mais avec des fonctionnalités en plus), et enfin 1 énumération MouseButtons qui contient des valeurs pour chaque “bouton”, au sens large, de la souris.

 

1. L’énumération MouseButtons

 

Voici tout d’abord l’énumération MouseButtons que j’ai créé :

public enum MouseButtons

{

    Left = 0,

    Right,

    Middle,

    X1,

    X2,

    MoveHorizontal,

    MoveVertical,

    ScrollUp,

    ScrollDown,

    MoveRight,

    MoveLeft,

    MoveUp,

    MoveDown

}

 

J’espère que vous comprenez mieux ce que je veux dire par “bouton au sens large”. Comme vous pouvez le voir, il y a des valeurs correspondantes aux boutons physiques d’une souris (Left, Middle, X1, etc…) et d’autres boutons, comme ScrollUp ou MoveRight qui correspondent plus à un mouvement particulier avec la souris plutôt d’un bouton. Mais pour arriver à notre but, il nous sera plus simple et plus puissant de considérer ces mouvements comme des boutons, pressés ou pas.

 

2. La structure EnhancedMouseState

 

Voici maintenant la structure EnhancedMouseState, un peu plus compliqué:

public struct EnhancedMouseState

{

    private Dictionary<MouseButtons, bool> _mouseButtonState;

    internal Dictionary<MouseButtons, bool> MouseButtonState { get { return _mouseButtonState; } }

 

     private int _xMove;

    public int XMove { get { return _xMove; } set { _xMove = value; } }

 

    private int _yMove;

    public int YMove { get { return _yMove; } set { _yMove = value; } }

 

    private int _x;

    public int X { get { return _x; } set { _x = value; } }

 

    private int _y;

    public int Y { get { return _y; } set { _y = value; } }

 

    private bool _initialized;

 

    internal void Initialize()

    {

        if(!_initialized)

        {

            _mouseButtonState = new Dictionary<MouseButtons, bool>();

                     _mouseButtonState.Add(MouseButtons.Left, false);

            _mouseButtonState.Add(MouseButtons.Right, false);

            _mouseButtonState.Add(MouseButtons.Middle, false);

            _mouseButtonState.Add(MouseButtons.ScrollDown, false);

            _mouseButtonState.Add(MouseButtons.ScrollUp, false);

            _mouseButtonState.Add(MouseButtons.MoveHorizontal, false);

            _mouseButtonState.Add(MouseButtons.MoveVertical, false);

            _mouseButtonState.Add(MouseButtons.X1, false);

            _mouseButtonState.Add(MouseButtons.X2, false);

            _mouseButtonState.Add(MouseButtons.MoveLeft, false);

            _mouseButtonState.Add(MouseButtons.MoveRight, false);

            _mouseButtonState.Add(MouseButtons.MoveUp, false);

            _mouseButtonState.Add(MouseButtons.MoveDown, false);

 

            _initialized = true;

        }

 

    }

 

    public bool IsMouseButtonDown(MouseButtons mouseButton)

    {

        return _mouseButtonState[mouseButton] == true;

    }

 

    public bool IsMouseButtonUp(MouseButtons mouseButton)

    {

        return _mouseButtonState[mouseButton] == false;

    }

}

 

Nous avons ici 1 dictionnaire, qui nous permettra de lier un bouton (de type MouseButtons, notre énumération) à un état booléen (bouton enfoncé ou pas), 4 champs de type int avec leur propriété, _x pour la position horizontale de la souris, _y pour la position verticale de la souris, _xMove qui contient, en pixels, la distance de déplacement horizontale de la souris depuis la dernière frame affichée, et enfin _yMove, idem que _xMove mais verticalement. Nous avons un dernier champ de type bool, _initialized, qui nous permet juste d’effectuer un contrôle et de s’assurer qu’une même instance de cette structure est initialisée une seule fois.

Il y a aussi 3 méthodes dans cette structure. La méthode Initialize() qui va nous permettre d’initialiser le dictionnaire et d’ajouter un item pour chaque bouton de l’énumération. Ensuite, les méthodes IsMouseButtonDown(MouseButtons mouseButton) et IsMouseButtonUp(MouseButtons mouseButton) qui prennent en paramètre un argument de type MouseButtons et qui permettent de savoir, respectivement, si un bouton de l’énumération est appuyé ou s’il  n’est pas appuyé. Pour cela, les méthodes vont juste piocher dans le dictionnaire, qui s’occupe de faire le lien avec l’état du bouton en question.

 

3. La classe EnhancedMouse

 

Enfin, voici le classe EnhancedMouse. C’est ici que va se passer toute la logique pour constituer un EnhancedMouseState valide :

public static class EnhancedMouse

{

    private static MouseState _currentMs;

    private static MouseState _previousMs;

    private static EnhancedMouseState _enhancedMouseState;

 

    static EnhancedMouse()

    {

        _enhancedMouseState = new EnhancedMouseState();

        _enhancedMouseState.Initialize();

    }

 

    public static EnhancedMouseState GetState()

    {

        Update();

        return _enhancedMouseState;

    }

 

    private static void Update()

    {

        _previousMs = _currentMs;

        _currentMs = Microsoft.Xna.Framework.Input.Mouse.GetState();

        _enhancedMouseState.MouseButtonState[MouseButtons.Left] = _currentMs.LeftButton == ButtonState.Pressed;

        _enhancedMouseState.MouseButtonState[MouseButtons.Right] = _currentMs.RightButton == ButtonState.Pressed;

        _enhancedMouseState.MouseButtonState[MouseButtons.Middle] = _currentMs.MiddleButton == ButtonState.Pressed;

        _enhancedMouseState.MouseButtonState[MouseButtons.X1] = _currentMs.XButton1 == ButtonState.Pressed;

        _enhancedMouseState.MouseButtonState[MouseButtons.X2] = _currentMs.XButton2 == ButtonState.Pressed;

 

        _enhancedMouseState.XMove = _currentMs.X – _previousMs.X;

        _enhancedMouseState.MouseButtonState[MouseButtons.MoveHorizontal] = _enhancedMouseState.XMove != 0;

        _enhancedMouseState.MouseButtonState[MouseButtons.MoveRight] = _enhancedMouseState.XMove > 0;

        _enhancedMouseState.MouseButtonState[MouseButtons.MoveLeft] = _enhancedMouseState.XMove < 0;

 

        _enhancedMouseState.YMove = _previousMs.Y – _currentMs.Y;

        _enhancedMouseState.MouseButtonState[MouseButtons.MoveVertical] = _enhancedMouseState.YMove != 0;

        _enhancedMouseState.MouseButtonState[MouseButtons.MoveUp] = _enhancedMouseState.YMove > 0;

        _enhancedMouseState.MouseButtonState[MouseButtons.MoveDown] = _enhancedMouseState.YMove < 0;

 

        _enhancedMouseState.MouseButtonState[MouseButtons.ScrollUp] = _currentMs.ScrollWheelValue – _previousMs.ScrollWheelValue > 0;

        _enhancedMouseState.MouseButtonState[MouseButtons.ScrollDown] = _currentMs.ScrollWheelValue – _previousMs.ScrollWheelValue < 0;

 

        _enhancedMouseState.X = _currentMs.X;

        _enhancedMouseState.Y = _currentMs.Y;

    }

}

 

Abordons le plus gros morceaux de cet article tranquillement. Si on reprend le fonctionnement des classes GamePad, Keyboard ou Mouse, nous pouvons remarquer deux choses : premièrement, ce sont des classes statiques, et deuxièmement, elles fournissent la méthode GetState(), permettant de récupérer le State du périphérique correspondant à un instant donné. Nous allons donc partir de là pour faire notre classe EnhancedMouse; nous la rendons statique et on lui ajoute une méthode GetState retournant un objet de type  EnhancedMouseState (notre structure créée ci-dessus).

 

Ensuite, nous allons décider des champs que la classe doit contenir. Pour éviter d’avoir à ré-instancier à chaque boucle de jeu un EnhancedMouseState, on garde en mémoire un champs de ce type, qui s’appelle ici _enhancedMouseState. En plus de ça, vu que l’on se sert de la structure MouseState existante pour construire notre EnhancedMouseState, nous avons besoin d’en garder deux variable dans la classe : une pour la boucle de jeu courante, et une autre pour la boucle de jeu précédente. C’est grâce à ces 2 variables que l’on saura si la molette ou souris ont bougé.

 

Continuons avec le constructeur statique EnhancedMouse dans lequel on initialise notre EnhancedMouseState, et la méthode la plus importante, Update(). C’est dans cette méthode que nous allons mettre à jour _enhancedMouseState en déterminant les valeurs qui ont changé depuis la dernière boucle de jeu. Décrivons son fonctionnement:

 

– On assigne le _previousMs à la valeur contenu dans _currentMs, et on met à jour _currentMs avec le MouseState courant de la souris. Nous avons maintenant à jour l’état de la souris à la boucle de jeu précédente et courante.

 

– On rentre dans le gros du code ici. On commence simple. Dans _currentMs, nous avons accès à chaque bouton physique de la souris. A partir de là, nous pouvons mettre à jour les états dans _enhancedMouseState correspondants à ces boutons (Left, Right, Middle, X1 et X2) avec un petit test booléen. Si l’état du bouton en question est à l’état pressé, alors à l’aide du dictionnaire MouseButtonState de _enhancedMouseState, on met son état true, false sinon.

 

– Ensuite, un peu plus compliqué puisque _previousMs et _currentMs interviennent tous les deux. Nous voulons savoir si la souris a bougé horizontalement. Pour cela, on va calculer le delta (_enhancedMouseState.XMove) de la valeur X de la souris entre la frame courante et la frame précédente. Si cette valeur est différente de 0, la souris à bougé horizontalement, et on peut donc mettre à true l’état du “bouton” que j’ai appelé MoveHorizontal. En plus de ça, si cette valeur est positive, on peut mettre l’état du “bouton” MoveRight à true, et si elle est négative, on peut définir l’état du “bouton’” MoveLeft à true.

 

– La même logique est ensuite appliquée aux mouvements verticaux de la souris, sauf qu’on remplace X par Y.

 

– Vient ensuite le tour de la molette. Dans la même idée que pour les mouvements de la souris, si le delta entre la valeur de la molette à la frame courante et à la frame précédente est positif, on peut assigner l’état du “bouton” ScrollUp à true, s’il est négatif, on assignera l’état du “bouton” ScrollDown à true.

 

– Enfin, dernière étape, on fait simplement une copie de la position de la souris vers _enhancedMouseState.

 

 

Il reste encore 2 lignes de code à écrire, lignes qui constitueront le corps de la méthode GetState(). Quand on appellera cette méthode quelque part ailleurs dans notre programme, il faudra d’abord mettre à jour _enhancedMouseState en invoquant la méthode Update(), puis le retourner afin de l’utiliser.

 

 

Conclusion

 

En guise de conclusion, un petit exemple de code pour montrer que nous sommes bien arrivé à faire ce que nous voulions :

public override void Update(GameTime gameTime)

{

    _ems = EnhancedMouse.GetState();

   

    if (_ems.IsMouseButtonDown(MouseButtons.MoveRight)) {

        // tourner la caméra à droite

    }

    if (_ems.IsMouseButtonDown(MouseButtons.MoveLeft)) {

        // tourner la caméra à gauche

    }

    if (_ems.IsMouseButtonDown(MouseButtons.ScrollUp)) {

        // prendre arme suivante

    }

    if (_ems.IsMouseButtonDown(MouseButtons.Left)) {

        // exploser la tête de l’ennemi 🙂

    }

    // …..

}

 

Nous voyons clairement que nous avons le même comportement qu’avec les autres classes Mouse, GamePad ou Keyboard du Framework. Imaginons que ce soit le méthode Update de votre jeu, on récupère d’abord l’état de la souris à l’aide de la méthode GetState() de la classe statique EnhancedMouse, puis on a plus qu’à effectuer les tests sur les “boutons” de la souris pour effecteur des actions !

 

Petite ouverture pour la fin, j’ai imaginé cette solution dans le but de créer un GameComponent dédié aux entrées utilisateurs, dans lequel on assigne une action (marcher, courir, tirer, etc…) à une touche ou un bouton. Et depuis, le clavier, et surtout la souris, sont entièrement programmables depuis un fichier xml ! CQFD.

Publicité

Publié le 4 février 2011, dans XNA 4.0. Bookmarquez ce permalien. Poster un commentaire.

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s

%d blogueurs aiment cette page :