Rueda de armas en Doom 1993

Saludos.



Muchos de nosotros tenemos afici贸n por los videojuegos de la vieja escuela que salieron a principios de siglo. Tienen una excelente atm贸sfera, una din谩mica fren茅tica y muchas soluciones originales que no han quedado obsoletas despu茅s de d茅cadas. Sin embargo, hoy en d铆a, la visi贸n de la interfaz del juego ha cambiado un poco: los corredores lineales han reemplazado los niveles confusos, la regeneraci贸n ha reemplazado los botiquines de primeros auxilios y, en lugar de una larga fila de teclas 0-9 para seleccionar un arsenal, primero vino la rueda del mouse y luego la rueda virtual. Se trata de 茅l hoy que se discutir谩.



imagen



Resumen historico



Anteriormente, durante la aparici贸n del g茅nero de tiradores como tal, no se plante贸 la cuesti贸n del control del mouse, solo se us贸 el teclado para controlar al protagonista. Adem谩s, tampoco hab铆a un formato de gesti贸n 煤nico: WASD se convirti贸 en el est谩ndar un poco m谩s tarde. Puede leer m谩s sobre los dise帽os de teclado de juegos antiguos aqu铆 .



En consecuencia, en aquellos juegos donde se implement贸 la capacidad de elegir el equipo (Doom, Wolfenstein, Quake, etc.), se implement贸 la 煤nica forma intuitiva en ese momento: usar las teclas num茅ricas en el teclado. Y durante muchos a帽os este m茅todo fue el 煤nico.

Luego, a finales de los 90, se hizo posible cambiar las armas con la rueda del mouse.



No fue posible encontrar informaci贸n inequ铆voca sobre este tema, pero en CS 1.6 esta caracter铆stica se habilit贸 a trav茅s de la consola. Sin embargo, es posible que haya habido precedentes anteriores; en este caso, ind铆quelo en los comentarios o en el PM. Pero en la forma habitual de la Rueda de armas en nuestro tiempo, comenz贸 a usarse solo con Crysis y su men煤 Traje. Aunque los intentos de hacer algo similar comenzaron en HL2, la "rueda" lleg贸 a las masas solo a fines de los a帽os 00, y ahora es la corriente principal. ...



Sin embargo, este es solo un resumen hist贸rico, de inter茅s solo como historia. En el marco de este art铆culo, no habr谩 largas discusiones sobre las razones de la popularidad de esta o aquella soluci贸n. as铆 como saber qu茅 selector es mejor. Solo porque el proceso de adaptar el viejo Doom a la elecci贸n de herramientas con el mouse se describir谩 a continuaci贸n.



Establecer metas



Para implementar WW, debe interceptar de alguna manera los movimientos del mouse, rastrear su movimiento mientras se presiona la tecla selectora y, cuando se suelta, emular el clic en el bot贸n correspondiente al sector seleccionado.



Para esto, utilic茅 el lenguaje Java, en particular, la intercepci贸n de teclas se realiza utilizando la biblioteca jnativehook, y presionar se debe a awt.Robot. El procesamiento de los ganchos recibidos no es dif铆cil, por lo tanto, se realiza manualmente.



Implementaci贸n



Anteriormente, se desarrollaron clases que definen pares de coordenadas para determinar el vector de desplazamiento.



En particular, la clase Shift le permite almacenar un vector bidimensional, as铆 como determinar su longitud, y la clase NormalizedShift, dise帽ada para almacenar un vector normalizado, entre otras cosas, le permite determinar el 谩ngulo entre el vector interceptado y el vector (1,0).



Spoiler header
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);
    };
};




No son de particular inter茅s, y solo las l铆neas 73-74, que normalizan el vector, requieren comentario. Entre otras cosas, el vector se voltea. neg est谩 cambiando el sistema de referencia; el hecho es que desde el punto de vista del software y desde el punto de vista de las matem谩ticas familiares, los vectores se dirigen tradicionalmente de diferentes maneras. Es por eso que los vectores de la clase Shift tienen el origen en la parte superior izquierda, y la clase NormalizedShift tiene la parte inferior izquierda.



Para implementar el trabajo del programa, se implement贸 la clase Wheel, que implementa las interfaces NativeMouseMotionListener y NativeKeyListener. El c贸digo est谩 debajo del spoiler.



Spoiler header
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;
        }
    }




Veamos qu茅 est谩 pasando aqu铆.



La variable KEYCODE almacena el c贸digo de la clave utilizada para invocar el selector. Por lo general, esto es TAB, pero si es necesario, se puede cambiar en el c贸digo o, idealmente, se puede extraer del archivo de configuraci贸n.



prev almacena la posici贸n del cursor del mouse cuando se llam贸 al selector. La posici贸n actual del cursor se mantiene en actual. En consecuencia, cuando se suelta la tecla de selecci贸n, los vectores se restan y el desplazamiento del cursor se escribe en la variable de desplazamiento durante el tiempo que se mantiene presionada la tecla de selecci贸n.



Luego, en la l铆nea 140, el vector se normaliza, es decir reducido a la forma cuando su longitud es cercana a uno. Despu茅s de eso, el vector normalizado se transfiere al marcador, lo que establece una correspondencia entre el c贸digo de la tecla que se va a presionar y el 谩ngulo de rotaci贸n del vector. Por razones de legibilidad, el 谩ngulo se convierte en grados, as铆 como tambi茅n: se orienta a lo largo de un c铆rculo unitario completo (acos solo funciona con 谩ngulos de hasta 180 grados).



La clase ButtonMatcher define la correspondencia entre el 谩ngulo y el c贸digo clave seleccionado.



Spoiler header
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);
    }
}




Adem谩s, la variable numberOfButtons determina el n煤mero de sectores y sus botones correspondientes, baseShift establece el 谩ngulo de rotaci贸n (en particular, proporciona simetr铆a sobre el eje vertical y gira la rueda 90 grados para que el arma cuerpo a cuerpo est茅 en la parte superior), y la matriz de c贸digos almacena c贸digos teclas: en caso de que se cambien los botones y los c贸digos no ir谩n en una fila. En una versi贸n m谩s refinada, ser铆a posible extraerlos del archivo de configuraci贸n, pero con el dise帽o est谩ndar de las claves, la versi贸n actual es bastante viable.



Conclusi贸n



En el marco de este art铆culo, se describi贸 la posibilidad de personalizar la interfaz de los tiradores cl谩sicos para los est谩ndares modernos. Por supuesto, no agregamos ning煤n botiqu铆n de primeros auxilios o linealidad: hay muchas modificaciones para esto, pero a menudo es con tales detalles que se encuentra una interfaz amigable y conveniente. El autor se da cuenta de que probablemente no describi贸 la forma m谩s 贸ptima de lograr el resultado deseado, y tambi茅n espera una imagen con una barra de pan y un troleb煤s en los comentarios, pero, sin embargo, fue una experiencia interesante que podr铆a alentar a algunos jugadores a descubrir El maravilloso mundo de Java.



Se alienta la cr铆tica constructiva.



C贸digos fuente



All Articles