
Hello, Keyboard!
1

Предлагаю вниманию разработчиков небольшую памятку по управлению движением персонажей. Надеюсь, этот пост поможет новичкам (а возможно и не только им) освоить аккуратное и точное считывание пожеланий игрока с помощью клавиатуры.
Движение с постоянной скоростью.
На этом примере мы сможем меньше думать о красоте и вместо этого раз и навсегда отработаем прием качественной обработки клавиатуры.Сначала, как всегда, весь рулон:
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import flash.geom.Point;
stage.frameRate = 30;
// Это наш ГэГэ. Он может перемещаться как в
// горизонтальном, так и вертикальном направлениях.
var _gg:Sprite;
// Модуль скорости движения.
var _velValue:Number = 3.0;
// Направление, задаваемое игроком.
var _direction:Point = new Point();
// А тут у нас вспомогательные переменные, для работы с клавой.
var _keyL:Boolean = false;
var _keyR:Boolean = false;
var _keyU:Boolean = false;
var _keyD:Boolean = false;
// Рисуем демо сцену
createDemoScene();
// Слушаем необходимые события.
stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
/** Рисуем простенькую демо сцену. */
function createDemoScene():void
{
_gg = new Sprite();
_gg.graphics.beginFill(0xff3300);
_gg.graphics.drawCircle(0, 0, 10);
_gg.graphics.endFill();
_gg.x = stage.stageWidth / 2;
_gg.y = stage.stageHeight / 2;
addChild(_gg);
}
/** Весь движняк, как обычно. */
function onEnterFrame(event:Event):void
{
// Вычисляем скорость на основании вектора направления.
var velocity:Point = _direction.clone();
velocity.normalize(_velValue);
// И, собственно, движемся в заданном направлении.
_gg.x += velocity.x;
_gg.y += velocity.y;
}
/** Когда наступили на кнопочку на клавиатуре.
Обрабатываются сразу стрелочки и WASD.
Здесь все просто: куда только что нажали,
туда мы и летим. Таким образом здесь у нас
организован приоритет последней нажатой
кнопки */
function onKeyDown(event:KeyboardEvent):void
{
switch (event.keyCode)
{
case Keyboard.LEFT:
case Keyboard.A:
_keyL = true;
_direction.x = -1;
break;
case Keyboard.RIGHT:
case Keyboard.D:
_keyR = true;
_direction.x = 1;
break;
case Keyboard.UP:
case Keyboard.W:
_keyU = true;
_direction.y = -1;
break;
case Keyboard.DOWN:
case Keyboard.S:
_keyD = true;
_direction.y = 1;
break;
}
}
/** Когда кнопочку отпустили, соответственно.
Здесь же при отпускании одной из парных
кнопок (пары вправо-влево и вверх-вниз)
мы проверяем, не нажата ли в это время
вторая кнопка из пары. */
function onKeyUp(event:KeyboardEvent):void
{
switch (event.keyCode)
{
case Keyboard.LEFT:
case Keyboard.A:
_keyL = false;
_direction.x = _keyR ? 1 : 0;
break;
case Keyboard.RIGHT:
case Keyboard.D:
_keyR = false;
_direction.x = _keyL ? -1 : 0;
break;
case Keyboard.UP:
case Keyboard.W:
_keyU = false;
_direction.y = _keyD ? 1 : 0;
break;
case Keyboard.DOWN:
case Keyboard.S:
_keyD = false;
_direction.y = _keyU ? -1 : 0;
break;
}
}
В результате у нас получается такое управление:
- +28
- Zebestov
Комментарии (33)
Вроде все просто, а постоянно сделать забываешь.
Теперь буду отсюда копипастить.
Скажем, если речь идет о космическом корабле с двигателями, направленными соответствующим образом, то все правильно — по диагонали корабль будет двигаться быстрее, потому что вектора тяги ложатся на катеты, давая в результате гипотенузу :)
Ты не прав :) Нету такой техники или животных, которые бы бегом наискось разгонялись в корень из двух раз быстрее за счет более полного использования ног :) Мощность двигателя (копыт) — довольно характерная величина и она ограничивает предельное ускорение именно по модулю (независимо от направления), а не отдельно каждую составляющую.
Если делать как предлагаешь ты — игроки очень быстро начинают ощущать, что по диагоналям гонять быстрее, в итоге получается как в думе: где тоже все бегали наискось, зажимая вперед и стрейф одновременно :)
Также: модель управления с ускорением не всегда хороша для экшинов с кнопочным управлением. Потому, что это управление третьего порядка. Поясню: игрок, в конечном итоге, хочет управлять координатами ГГ, помещая его в то или иное место пространства. Наиболее резкое и отзывчивое управление — управление «первого порядка» — когда нажатие кнопки напрямую задает кординаты (пример — волк, ловящий яйца). Управление «второго поядка» — нажатие кнопки задает скорость, а координаты меняются уже опосредовано через постепенную добавку к ним значения скорости. Управление «третьего порядка» — это управление ускорением. Оно наиболее физически-правдоподобно, но являет собой самую долгую связь от пожелания игрока до фактического изменения координат ГГ.
Исключительно важно минимизировать время задержки — от нажатия кнопки до желаемого изменения координат ГГ. Если просто задрать пределы для ускорений и скоростей, то управление может получиться отзывчивым НО не точным: игроку будет сложно задавать и отрабатывать малые перемещения. Поэтому в ход идут всякие хаки: в частности, используется смешанная модель управления: когда в начальный момент времени, когда игрок только нажал кнопку, скажем, «в бок» — ему в этом направлении пинком дается некая минимальная скорость, обеспечивающая почти мгновенное смещение ГГ в этом направлении на минимальную актуальную величину, скажем на пол корпуса. А далее, если кнопка все еще нажата, к уже имеющейся скорости плюсуется ускорение.
Способ торможения ГГ тоже важен: после отпускания кнопки, управляемая игроком штука должна как можно быстрее занять определенную поззицию, без существенных заносов и вылетов относительно координат, в которых игрок отпустил кнопку.
По диагонали работают сразу два.
У аппаратов с множеством двигателей (космический корабль) мелкие боковые моторы используются только для ориентирования (поворота), а для движения — используются маршевые двигатели. У судов с повышенной маневренностью (некоторые буксиры, некоторые спасательные катера) — расположенный внизу водомет может вращаться на 360 градусов, обеспечивая тягу в нужную сторону.
Но самое главное — когда по диагонали ускорение и\или скорость выше — это косяк, это быстро приучает игрока гонять только по диагоналям, что не правильно :)
В думе — косой бег и ускорение об стенку (чудеса колижн-детектора, который выпердоливая игрока из стены подкидывал немножко вдоль вектора движения), в квейке — косой бег убрали, зато случайно добавили рокетждамп. :)
А кто помнит физику? Как там по проекциям сил? )
Если на объект воздействуют две перпендикулярных силы, то какой будет результирующая по величине? Корень из суммы квадратов сил? )
Без учета трения и гравитации.
Для частного случая, если силы под 90 градусов, модуль будет определяться по теореме Пифагора: корень из суммы квадратов.
Во-первых, уменьшение в полтора раза (~1.4) при диагональном движении — это вопрос одного if, который никак не влияет на суть поста. Если это настолько критичный момент — я с радостью добавлю эту «фичу» в код прямо сейчас.
Во-вторых, нельзя говорить об исключительной важности соблюдения принципов одного типа управления при реализации другого. Ты верно подметил про «не всегда». Так вот не всегда «важно минимизировать время задержки — от нажатия кнопки до желаемого изменения координат ГГ» — мы таки не всегда в играх не координатами управляем :)
Кстати по-нормальному там не if делается, а вычисляется вектор «хотения» игрока (куда он хочет двинуть перса с учетом всех нажатых кнопок) и это вектор нормализуется до единичной длины — перед тем, как сношать этим вектором скорость или там ускорение :)
— Ну да, есть еще стрельба :)
Управление же движением преследует, в конечном итоге, именно управление координатами: поместить героя в определенное место пространства, чтобы он что-то там собрал, либо что-бы не собрал вражеские выстрелы в прежнем месте. А вот например на скорость перемещения персонажа как на самобытный параметр игроку глубоко наплевать, как и на ускорение. Это если не рассматривать всякие там рэйсинги )
Ну да, есть еше вормикс, танкионлайн, прочие игры, где ты не управляешь координатой объекта ;)
На мой вкус неплохо бы добавить обработку потери фокуса, раз уж обещаешь «освоить аккуратное и точное считывание пожеланий игрока с помощью клавиатуры».
Обработка потери приложением фокуса — это отдельная задача, имеющая лишь косвенное отношение к теме поста. Обещание продемонстрировать точный анализ состояния клавиш управления я сдержал на 146% :)
Еще мелкий баг в примерах. В коде в статье всё ок, но скомпилированные примеры чуть сложнее, они меняют картинку в зависимости от того, куда кораблик летит. И бак такой, если зажать кнопку «влево», потом нажать «вправо», то кораблик полетит вправо, но картинка останется от движения влево. Если сделать наоборот, т.е. вначале нажать «вправо», а потом «влево» всё ок. С вверх/вниз тоже самое.
В этих примерах было бы минимально достаточно только обрабатывать отпускание всех нажатых клавиш при потере фокуса.
А управление определятся скорее геймдизайном.
ИМХО тут нет предмета для спора.
Я вообще считаю, что лучше одной мышкой без клавы обходиться. И что?
Именно поэтому, к слову, я не стесняюсь таких недопустимых в принципе вещей, как задание значений скорости (ускорения) прямо в обработчике клавиатуры — не годится (на мой вкус) в реальном проекте так мешать управление с логикой. Разумеется, должен быть некий единичный вектор, который однозначно определяет сочетание кнопок. А уже в логической части следует производить с этим вектором все необходимые манипуляции. Однако я посчитал это неуместным усложнением, которое может отвлечь от самой сути — чуткой и однозначной реакции на нажатие и отпускание клавиш управления. Возможно я ошибся и стоило (стоит) сделать все «как надо», насколько это допускает код в одном кадре (опять же, для наглядности и удобной копипасты «для поковирать»).
Вроде даже яснее стало — напрасно боялся :)