Roda de armas em Doom 1993

Saudações.



Muitos de nós se relacionam calorosamente com os videogames da velha escola lançados na virada do século. Eles têm uma excelente atmosfera, dinâmica frenética e muitas soluções originais que não se tornam obsoletas após décadas. No entanto, hoje em dia, a visão da interface do jogo mudou um pouco - corredores lineares substituíram níveis confusos, a regeneração substituiu os kits de primeiros socorros e, em vez de uma longa fila de teclas de 0 a 9 para selecionar um arsenal, primeiro a roda do mouse e depois a roda virtual, chegaram. É sobre ele hoje que será discutido.



imagem



Resumo histórico



Anteriormente, durante o surgimento do gênero do atirador, a questão do controle do mouse não foi levantada - apenas o teclado foi usado para controlar o protagonista. Além disso, também não havia um formato de gerenciamento único - o WASD tornou-se o padrão um pouco mais tarde. Você pode ler mais sobre os antigos layouts de teclado para jogos aqui .



Assim, naqueles jogos em que a capacidade de selecionar equipamentos foi implementada (Doom, Wolfenstein, Quake etc.), foi implementada da única maneira intuitiva na época - usando as teclas numéricas do teclado. E por muitos anos esse método foi o único.

Então, no final dos anos 90, tornou-se possível trocar de armas com a roda do mouse.



Não foi possível encontrar informações inequívocas sobre esse tópico, mas no CS 1.6 esse recurso foi ativado no console. No entanto, esses precedentes podem ter existido antes - nesse caso, indique isso nos comentários ou no PM. Mas, na forma usual de roda de armas em nosso tempo, ela só foi usada com o Crysis e seu menu Suit. Embora tentativas de fazer algo semelhante tenham começado no HL2, a “roda” foi para as massas apenas no final dos anos 2000 e agora é predominante. .



No entanto, este é apenas um resumo histórico, de interesse apenas como história. No âmbito deste artigo, não haverá longas discussões sobre os motivos da popularidade desta ou daquela solução. além de provar qual é o melhor seletor. Simplesmente porque o seguinte descreverá o processo de adaptação do bom e velho Doom à escolha de armas com o mouse.



Estabelecendo objetivos



Para implementar o WW, você precisa, de alguma forma, interceptar o movimento do mouse, acompanhar seu movimento enquanto a tecla seletora é mantida pressionada e, quando solta, emular um clique no botão correspondente ao setor selecionado.



Para isso, usei a linguagem Java, em particular, a interceptação de chave é realizada usando a biblioteca jnativehook, e pressionar é devido ao awt.Robot. O processamento dos ganchos recebidos não é difícil, portanto, é feito manualmente.



Implementação



Anteriormente, foram desenvolvidas classes que definem pares de coordenadas para determinar o vetor de deslocamento.



Em particular, a classe Shift permite armazenar um vetor bidimensional, bem como determinar seu comprimento, e a classe NormalisedShift, projetada para armazenar um vetor normalizado, entre outras coisas, permite determinar o ângulo entre o vetor interceptado e o vetor (1,0).



Cabeçalho do 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);
    };
};




Eles não são de interesse particular e apenas as linhas 73-74, que normalizam o vetor, requerem comentário. Entre outras coisas, o vetor é invertido. o quadro de referência muda - o fato é que, do ponto de vista do software e do ponto de vista da matemática familiar, os vetores são tradicionalmente direcionados de maneira diferente. É por isso que os vetores da classe Shift têm a origem no canto superior esquerdo e a classe NormalizedShift tem o canto inferior esquerdo.



Para implementar o trabalho do programa, foi implementada a classe Wheel, que implementa as interfaces NativeMouseMotionListener e NativeKeyListener. O código está sob o spoiler.



Cabeçalho do 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;
        }
    }




Vamos descobrir o que está acontecendo aqui.



A variável KEYCODE armazena o código da chave usada para chamar o seletor. Normalmente, isso é TAB, mas, se necessário, você pode alterá-lo no código ou, idealmente, retirá-lo do arquivo de configuração.



prev armazena a posição do cursor do mouse que estava no momento em que o seletor foi chamado. Surrent mantém a posição atual do cursor em um determinado momento. Assim, quando você solta a tecla seletora, os vetores são subtraídos e o deslocamento do cursor durante a espera da tecla seletora é gravado na variável shift.



Então, na linha 140, o vetor normaliza, isto é, reduzido para formar quando seu comprimento é próximo a um. Depois disso, o vetor normalizado é transmitido ao combinador, que estabelece a correspondência entre o código da tecla a ser pressionado e o ângulo de rotação do vetor. Por motivos de legibilidade, o ângulo é convertido em graus, bem como - é orientado ao longo de um círculo unitário completo (o acos funciona apenas com ângulos de até 180 graus).



A classe ButtonMatcher define a correspondência entre o ângulo e o código da chave selecionado.



Cabeçalho do 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);
    }
}




Além disso, a variável numberOfButtons determina o número de setores e seus botões correspondentes, baseShift define o ângulo de rotação (em particular, fornece simetria sobre o eixo vertical e gira a roda 90 graus para que a arma branca fique por cima), e a matriz de códigos armazena códigos teclas - caso os botões sejam alterados e os códigos não sejam consecutivos. Em uma versão mais elaborada, seria possível extraí-los do arquivo de configuração, mas com o layout padrão das teclas - a versão atual é bastante viável.



Conclusão



No âmbito deste artigo, foi descrita a possibilidade de personalizar a interface dos atiradores clássicos para os padrões modernos. Obviamente, não adicionamos nenhum kit de primeiros socorros ou linearidade aqui - existem muitos mods para isso, mas muitas vezes é nesses detalhes que uma interface amigável e conveniente se encontra. O autor percebe que provavelmente não descreveu a melhor maneira de alcançar o resultado desejado e também espera nos comentários uma foto com um pão e um trólebus, mas, no entanto, foi uma experiência interessante que, talvez, incentive alguns jogadores a descobrir O maravilhoso mundo de Java.



Críticas construtivas são bem-vindas.



Código fonte



All Articles