Roue d'arme dans Doom 1993

Salutations.



Beaucoup d'entre nous se rapportent chaleureusement aux jeux vidéo de la vieille école sortis au tournant du siècle. Ils ont une excellente atmosphère, une dynamique effrénée et de nombreuses solutions originales qui ne sont pas devenues obsolètes après des décennies. Cependant, aujourd'hui, la vision de l'interface de jeu a un peu changé - les couloirs linéaires ont remplacé les niveaux confus, la régénération a remplacé les kits de premiers secours, et au lieu d'une longue rangée de touches 0-9, la molette de la souris puis la molette virtuelle sont venues sélectionner l'arsenal. C'est de lui aujourd'hui qu'on discutera.



image



Résumé historique



Auparavant, lors de l'émergence du genre de tir en tant que tel, la question du contrôle de la souris n'était pas posée - seul le clavier était utilisé pour contrôler le protagoniste. De plus, il n'y avait pas non plus de format de gestion unique - WASD est devenu la norme un peu plus tard. Vous pouvez en savoir plus sur les anciennes dispositions de clavier de jeu ici .



En conséquence, dans les jeux où la possibilité de sélectionner des équipements a été implémentée (Doom, Wolfenstein, Quake, etc.), elle a été implémentée de la seule manière intuitive à l'époque - en utilisant les touches numériques du clavier. Et pendant de nombreuses années, cette méthode a été la seule.

Puis, à la fin des années 90, il est devenu possible de changer d'armes avec la molette de la souris.



Nous n'avons pas pu trouver d'informations sans ambiguïté sur ce sujet, mais dans CS 1.6, cette fonctionnalité était activée via la console. Cependant, de tels précédents peuvent avoir existé auparavant - dans ce cas, veuillez l'indiquer dans les commentaires ou dans le MP. Mais sous la forme familière à notre époque, la roue d'armes n'a été utilisée qu'avec Crysis et son menu Suit. Bien qu'il y ait eu des tentatives de faire quelque chose de similaire depuis HL2, la «roue» n'est allée aux masses qu'à la fin des années 00, et maintenant elle est grand public .



Cependant, ce n'est qu'un résumé historique, d'intérêt seulement en tant qu'histoire. Dans le cadre de cet article, il n'y aura pas de longues discussions sur les raisons de la popularité d'une solution particulière. ainsi que le goût sur lequel le sélecteur est le meilleur. Tout simplement parce que le processus d'adaptation du bon vieux Doom au choix des outils avec la souris sera décrit ci-dessous.



Fixer des objectifs



Pour implémenter WW, vous devez d'une manière ou d'une autre intercepter le mouvement de la souris, suivre son mouvement pendant que la touche de sélection est maintenue enfoncée et, une fois relâchée, émuler un clic sur le bouton correspondant au secteur sélectionné.



Pour cela, j'ai utilisé le langage Java, en particulier, l'interception des touches se fait à l'aide de la bibliothèque jnativehook, et le pressage se fait à l'aide de awt.Robot. Le traitement des crochets reçus n'est pas difficile, par conséquent, il est effectué manuellement.



la mise en oeuvre



Auparavant, des classes ont été développées qui définissent des paires de coordonnées pour déterminer le vecteur de déplacement.



En particulier, la classe Shift vous permet de stocker un vecteur bidimensionnel, ainsi que de déterminer sa longueur, et la classe NormalizedShift, conçue pour stocker un vecteur normalisé, entre autres, vous permet de déterminer l'angle entre le vecteur intercepté et le vecteur (1,0).



En-tête de spoiler
class Shift{
    int xShift;
    int yShift;

    public int getxShift() {
        return xShift;
    }

    public int getyShift() {
        return yShift;
    }

    public void setxShift(int xShift) {
        this.xShift = xShift;
    }

    public void setyShift(int yShift) {
        this.yShift = yShift;
    }
    double getLenght(){
        return Math.sqrt(xShift*xShift+yShift*yShift);
    }

}
class NormalisedShift{
  double normalizedXShift;
  double normalizedYShift;
  double angle;
  NormalisedShift (Shift shift){
      if (shift.getLenght()>0)
      {
          normalizedXShift = -shift.getxShift()/shift.getLenght();
        normalizedYShift = -shift.getyShift()/shift.getLenght();
      }
      else
      {
          normalizedXShift = 0;
          normalizedYShift = 0;
      }
  }
  void calcAngle(){
      angle = Math.acos(normalizedXShift);
  }

  double getAngle(){
      calcAngle();
      return (normalizedYShift<0?angle*360/2/Math.PI:360-angle*360/2/Math.PI);
    };
};




Ils ne présentent pas d'intérêt particulier et seules les lignes 73-74, normalisant le vecteur, nécessitent un commentaire. Entre autres choses, le vecteur est inversé. neg a un système de référence différent - le fait est que du point de vue du logiciel et du point de vue des mathématiques familières, les vecteurs sont traditionnellement dirigés de différentes manières. C'est pourquoi les vecteurs de la classe Shift ont l'origine en haut à gauche et la classe NormalizedShift en bas à gauche.



Pour implémenter le travail du programme, la classe Wheel a été implémentée, qui implémente les interfaces NativeMouseMotionListener et NativeKeyListener. Le code est sous le spoiler.



En-tête de spoiler
public class Wheel  implements NativeMouseMotionListener, NativeKeyListener {

    final int KEYCODE = 15;
    Shift prev = new Shift();
    Shift current = new Shift();
    ButtomMatcher mathcer = new ButtomMatcher();


    boolean wasPressed = false;

    @Override
    public void nativeMouseMoved(NativeMouseEvent nativeMouseEvent) {
        current.setxShift(nativeMouseEvent.getX());
        current.setyShift(nativeMouseEvent.getY());

    }
    @Override
    public void nativeMouseDragged(NativeMouseEvent nativeMouseEvent) {

    }
    @Override
    public void nativeKeyTyped(NativeKeyEvent nativeKeyEvent) {

    }

    @Override
    public void nativeKeyPressed(NativeKeyEvent nativeKeyEvent) {
        if (nativeKeyEvent.getKeyCode()==KEYCODE){
            if (!wasPressed)
            {
                prev.setxShift(current.getxShift());
                prev.setyShift(current.getyShift());
            }
            wasPressed = true;

        }
    }

    @Override
    public void nativeKeyReleased(NativeKeyEvent nativeKeyEvent) {
        if (nativeKeyEvent.getKeyCode() == KEYCODE){
            Shift shift = new Shift();
            shift.setxShift(prev.getxShift() - current.getxShift());
            shift.setyShift(prev.getyShift() - current.getyShift());
            NormalisedShift normalisedShift = new NormalisedShift(shift);
            mathcer.pressKey(mathcer.getCodeByAngle(normalisedShift.getAngle()));
            wasPressed = false;
        }
    }




Voyons ce qui se passe ici.



La variable KEYCODE stocke le code de la clé utilisée pour appeler le sélecteur. Il s'agit généralement de TAB, mais si nécessaire, il peut être modifié dans le code ou - idéalement - extrait du fichier de configuration.



prev stocke la position du curseur de la souris au moment où le sélecteur a été appelé. La position actuelle du curseur est maintenue dans le courant. Par conséquent, lorsque la touche de sélection est relâchée, les vecteurs sont soustraits et le décalage du curseur est écrit dans la variable de décalage pendant que la touche de sélection est maintenue enfoncée.



Ensuite, à la ligne 140, le vecteur normalise, c'est-à-dire réduit à se former lorsque sa longueur est proche de l'unité. Après cela, le vecteur normalisé est transmis au matcher, qui établit la correspondance entre le code de touche à appuyer et l'angle de rotation du vecteur. Pour des raisons de lisibilité, l'angle se traduit en degrés et est également orienté dans un cercle unitaire complet (acos ne fonctionne qu'avec des angles jusqu'à 180 degrés).



La classe ButtonMatcher définit la correspondance entre l'angle et le code clé sélectionné.



En-tête de spoiler
class ButtomMatcher{

    Robot robot;
    final int numberOfButtons = 6;
    int buttonSection = 360/numberOfButtons;
    int baseShift = 90-buttonSection/2;
    ArrayList<Integer> codes = new ArrayList<>();
    void matchButtons(){
        for (int i =49; i<55; i++)
            codes.add(i);

    }
    int getCodeByAngle(double angle){
        angle= (angle+360-baseShift)%360;
        int section = (int) angle/buttonSection;
        System.out.println(codes.get(section));
        return codes.get(section);
    }
    ButtomMatcher() {
        matchButtons();
        try
        {
            robot = new Robot();
        }
        catch (AWTException e) {
            e.printStackTrace();
        }
    }
    void pressKey(int keyPress)
    {

        robot.keyPress(keyPress);
        robot.keyRelease(keyPress);
    }
}




De plus, la variable numberOfButtons détermine le nombre de secteurs et leurs boutons correspondants, baseShift définit l'angle de rotation (en particulier, fournit une symétrie autour de l'axe vertical et fait tourner la roue de 90 degrés pour que l'arme de mêlée soit en haut), et le tableau de codes stocke les codes touches - au cas où les boutons seraient modifiés et que les codes n'iraient pas dans une rangée. Dans une version plus raffinée, il serait possible de les extraire du fichier de configuration, mais avec la disposition standard des touches, la version actuelle est tout à fait viable.



Conclusion



Dans le cadre de cet article, la possibilité de personnaliser l'interface des tireurs classiques pour les normes modernes a été décrite. Bien sûr, nous n'ajoutons ni trousses de premiers soins ni linéarité ici - il existe de nombreux mods pour cela, mais c'est souvent dans ces détails que réside l'interface conviviale et pratique. L'auteur se rend compte qu'il n'a probablement pas décrit le moyen le plus optimal pour obtenir le résultat souhaité, et attend également une photo avec un pain et un trolleybus dans les commentaires, mais c'était néanmoins une expérience intéressante qui, peut-être, encouragera un joueur à découvrir monde étonnant de Java.



La critique constructive est encouragée.



Code source



All Articles