Русский
Русский
English
Статистика
Реклама

Weapon wheel в Doom 1993

Приветствую.
Многие из нас с теплотой относятся к олдскульным видеоиграм, вышедшим на стыке веков. У них превосходная атмосфера, бешеная динамика и множество оригинальных решений, которые не устарели спустя десятилетия. Однако в наши дни видение интерфейса игр несколько изменилось на смену запутанным уровням пришли линейные коридоры, на смену аптечкам регенерация, а вместо длинного ряда клавиш 0-9 для выбора арсенала пришли сначала колесико мыши, а затем виртуальное колесо. Именно о нем сегодня и пойдет речь.
image


Историческая сводка
Раньше, во время появления жанра шутеров как таковых, вопрос об управлении мышкой не стоял для управления протагонистом использовалась только клавиатура. Причем единого формата управления тоже не было WASD стал стандартом чуть позднее. Более подробно о старых игровых раскладках клавиатуры можно почитать вот тут
Соответственно, в тех играх, где была реализована возможность выбора снаряжения (Doom, Wolfenstein, Quake etc) был реализован единственным интуитивным на тот момент способом с помощью цифровых клавиш на клавиатуре. И на многие годы этот способ был единственным.
Потом, в конце 90х годов, появилась возможность смены вооружения колесиком мышки. Однозначной информации на эту тему найти не удалось, однако в CS 1.6 такая возможность включалась через консоль. Впрочем, возможно такие прецеденты были и ранее в таком случае, просьба указать на это в комментариях или в ЛС. А вот в привычном в наше время виде Weapon Wheel вошло в использование лишь с Crysis'ом и его Suit menu, Хотя попытки сделать нечто похожее были начиная с HL2, в массы колесо пошло лишь в конце 00х годов, а сейчас является мейнстримом.

Впрочем, это лишь историческая сводка, представляющая интерес только в качестве истории. В рамках данной статьи не будет пространных рассуждений о причинах популярности того или иного решения. а так же вкусовщины о том, какой селектор лучше. Просто потому, что ниже будет описан процесс адаптации старого-доброго Doom к выбору орудия с помощью мышки.

Постановка задач
Для того, что бы реализовать WW, нужно каким-либо образом перехватывать движения мышки, отслеживать ее перемещение, пока зажата клавиша селектора, и, по отпусканию, эмулировать нажатие на кнопку, соответствующую выбранному сектору.
Для этого мной был использован язык Java, в частности, перехват клавиш осуществляется за счет библиотеки jnativehook, а нажатие за счет awt.Robot. Обработка полученных хуков не представляет сложностей, поэтому производится вручную.

Реализация
Предварительно были разработаны классы, задающие пары координат, для определния вектора смещения.
В частности, класс Shift позволяет хранить двумерный вектор, а также определять его длину, а класс NormalisedShift, разработанный для хранения нормализованного вектора, помимо прочего, позволяет определить угол между перехваченным вектором и вектором (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 хранит положение курсора мыши, которое было на момент вызова селектора. В сurrent поддерживается актуальное положение курсора в настоящий момент времени. Соответственно, при отпускании клавиши селектора происходит вычитание векторов и в переменную shift записывается смещение курсора за время удержания клавиши селектора.
Затем, в строке 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 градусов так, что бы орудие ближнего боя было сверху), а массив codes хранит в себе коды клавиш на случай, если кнопки будут изменены, и коды не будут идти подряд. В более доработанной версии можно было бы подтягивать их из конфигурационного файла, но при стандартном расположении клавиш текущая версия вполне жизнеспособна.

Заключение
В рамках данной статьи была описана возможность кастомизации интерфейса классических шутеров для современных стандартов. Конечно, ни аптечек, ни линейности мы тут не добавляем для этого есть множество модов, но зачастую именно в подобных деталях и кроется дружелюбный и удобный интерфейс. Автор осознает, что, вероятно, описал не самый оптимальный способ достижения требуемого результата, а так же ждет в комментариях картинку с буханкой и троллейбусом, но тем не менее это был интересный опыт, который, возможно, сподвигнет какого-нибудь геймера открыть для себя удивительный мир Java.
Конструктивная критика приветствуется.
Исходники
Источник: habr.com
К списку статей
Опубликовано: 29.06.2020 18:10:05
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Java

Игры и игровые приставки

Doom

Modding

Игры

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru