《毁灭战士1993》中的武器轮

问候。



我们中的许多人都喜欢世纪之交的老式视频游戏。他们拥有出色的氛围,疯狂的动力和许多原始的解决方案,这些解决方案在几十年后都没有过时。但是,如今,游戏界面的愿景已发生了一些变化-线性走廊已取代了令人困惑的关卡,再生已取代了急救包,而不是用长行的0-9键来选择兵工厂,首先是鼠标滚轮,然后是虚拟滚轮。今天我们要谈的就是他。



图片



历史总结



以前,在这样的射击游戏类型出现期间,并未提出鼠标控制的问题-仅使用键盘来控制主角。此外,也没有单一的管理格式-WASD稍后成为标准。您可以在此处阅读有关旧游戏键盘布局的更多信息



因此,在那些实现了设备选择能力的游戏(《毁灭战士》,《德军总部》,《雷神之锤》等)中,当时是唯一直观的方法,即使用键盘上的数字键来实现。多年来,这种方法是唯一的一种。

然后,在90年代后期,使用鼠标滚轮更换武器成为可能。



我们在该主题上找不到明确的信息,但是在CS 1.6中,已通过控制台启用了此功能。但是,以前可能有这样的先例-在这种情况下,请在评论或PM中指出。但是以我们时代熟悉的形式,“武器轮”仅在《孤岛危机》及其“西服”菜单中使用。尽管尝试类似的尝试始于HL2,但“轮”直到00年代后期才进入大众市场,如今已成为主流。



但是,这仅是历史总结,仅作为历史才有意义。在本文的框架中,将不对特定解决方案流行的原因进行冗长的讨论。并了解哪种选择器更好。仅仅因为以下内容将描述使用鼠标将旧版《毁灭战士》改编为武器的过程。



设定目标



为了实现WW,您需要以某种方式拦截鼠标的移动,在按住选择器键的同时跟踪其移动,并在释放鼠标时模拟与所选扇区对应的按钮的单击。



为此,我使用了Java语言,特别是,使用jnativehook库执行了密钥拦截,并使用awt.Robot进行了按。处理接收到的钩子并不困难,因此,它是手动完成的。



实作



以前,开发了定义坐标对以确定位移矢量的类。



特别是,Shift类允许您存储二维向量以及确定其长度,而NormalizedShift类旨在存储归一化向量,除其他事项外,它还允许您确定截取的向量与向量之间的角度(1,0)。



扰流板方向
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);
    };
};




它们没有特别的意义,只有将向量标准化的第73-74行需要注释。其中,向量被翻转。参考框架发生了变化-事实是,从软件的角度和从熟悉的数学的角度来看,向量在传统上的指向是不同的。这就是为什么Shift类的向量的原点位于左上方,而NormalizedShift类的原点位于左下方。



为了实现该程序,实施了Wheel类,该类实现了NativeMouseMotionListener和NativeKeyListener接口。该代码在扰流器下。



扰流板方向
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;
        }
    }




让我们弄清楚这里发生了什么。



KEYCODE变量存储用于调用选择器的键的代码。通常这是TAB,但如有必要,可以在代码中更改它,或者-理想情况下-从配置文件中提取它。



prev存储调用选择器时鼠标光标的位置。 Surrent在给定时间保持当前光标位置。因此,当释放选择器键时,在选择器键被按下的时间内将向量相减,并将光标的移位写入移位变量。



然后,在第140行,将向量归一化,即 当其长度接近统一时简化为形式。之后,将归一化的向量传输到匹配器,该匹配器在要按下的键的代码和向量的旋转角度之间建立对应关系。出于可读性原因,角度将转换为度,以及-沿完整的单位圆定向(acos仅适用于最大180度的角度)。



ButtonMatcher类定义角度和所选键控代码之间的对应关系。



扰流板方向
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);
    }
}




此外,变量numberOfButtons决定扇区及其相应按钮的数量,baseShift设置旋转角度(特别是,它提供了围绕垂直轴的对称性,并将轮子旋转90度,以便近战武器位于顶部),并且代码数组存储代码按键-如果更改了按键并且代码不会连续显示。在更详细的版本中,可以将它们从配置文件中拉出,但是使用标准的键布局-当前版本是相当可行的。



结论



在本文的框架内,描述了为现代标准定制经典射手界面的可能性。当然,我们在这里不添加任何急救箱或线性工具-为此,有很多mod,但是在如此详细的情况下,往往会提供友好而方便的界面。作者意识到他可能没有描述获得期望结果的最佳方法,并且还在评论中等待带有面包和无轨电车的图片,但是尽管如此,这还是一个有趣的经历,也许会鼓励一些游戏玩家发现Java的奇妙世界。



欢迎建设性的批评。



源代码



All Articles