Статьи

Как добавить контроль жестов мышью в ваши Flash-проекты: Multi-Stroke Gestures

В первой части этой серии я представил класс для обработки распознавания одиночного удара мыши: MGesture . Этот урок делает шаг вперед, обнаруживая последовательность штрихов.

Для этого мы будем использовать класс GManager в тандеме с MGesture . После ознакомления с функциональностью GManager будет разработано приложение, демонстрирующее его использование.


Давайте посмотрим на конечный результат, к которому мы будем стремиться. Чтобы использовать презентацию Flash ниже, используйте мышь для выполнения жеста, как показано стрелкой в ​​верхнем левом углу. Жест, нажав левую кнопку мыши, перемещая мышь, удерживая кнопку, а затем отпустив ее.


Номера на разных направлениях

Первый шаг, пересмотр целых чисел, используемых для представления различных направлений из части 1 . Надеемся, что вы можете сформировать мысленную картину на диаграмме выше, так как мы будем ссылаться на нее в течение всего этого урока.


Когда пользователь делает жест любым указательным устройством (мышью, пером планшета и т. Д.), Векторы, успешно обнаруженные с течением времени, фактически образуют последовательность целых чисел. Обратитесь к диаграмме ниже.

Примеры кодирования последовательности жестов

Обратите внимание, что уникальные целые числа в последовательности равны 0, 4, 1. Однако я скажу, что уникальные целые числа равны 0, 1. Посмотрим правде в глаза: трудно сделать точный штрих на моем планшете Bamboo, и тем более с помощью мыши. Ошибки неизбежны. Наш алгоритм должен тогда основываться на большом повторении целых чисел в непрерывной строке. Их здесь 0, 1 — 4, вероятно, из-за неточного удара. Следовательно, этот жест может быть однозначно идентифицирован как последовательность 0, а затем 1.

Я включил еще несколько жестов с сопровождающими их уникальными последовательностями ниже.

Примеры кодирования последовательности жестов

Цель этого класса проста:

  1. Чтобы зарегистрировать уникальные последовательности штрихов для сравнения.
  2. Для захвата последовательности, обнаруженной текущего жеста, сделанного пользователем.
  3. Сравнить последовательность в (2) со всеми последовательностями штрихов в (1).
  4. Чтобы вернуть результат поиска.

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


GManager имеет следующие переменные:

переменная Тип данных Цель
gestSeq Vector.<Vector.<int>> 2D массив для записи различных последовательностей штрихов.
gestName Vector.<String> 1D массив для записи имен последовательностей штрихов.
strokes Vector.<int> 1D массив для записи ограничения на gestCurrent по сравнению с последовательностью штрихов.
gestCurrent Vector.<int> 1D массив для записи текущей последовательности штрихов.
_order Boolean Определяет, должны ли наборы жестов в gestSeq обнаруживаться по порядку.
orderCurrent int Текущая последовательность, чтобы определить, _order ли _order ; начинается с 0.

Методы в GManager следующие:

метод вход Выход Описание
GManager недействительным недействительным Инициирование класса, gestSeq и gestName, strokes инициализированы
register Vector.<int>, String, int недействительным Зарегистрируйте последовательность хода, имя и ограничение хода (необязательно).
remove int недействительным Удалить выбранную последовательность штрихов
removeAll недействительным недействительным Удалить все зарегистрированные последовательности штрихов
start недействительным недействительным Подготовьте переменную для записи текущей последовательности gestCurrent , gestCurrent
populate int недействительным gestCurrent с обнаруженными штрихами (единственное число)
tracer недействительным Vector.<int> gestCurrent и выводит gestCurrent для целей отладки.
dropStrokes Vector.<int>, int Vector.<int> Устраните ненужные удары в gestCurrent . Держите сеть и диагонали (0), оставляйте только сеть (1), оставляйте только диагонали (2). (Часть 3 дополнительно объяснит его использование.)
dropDuplicates Vector.<int>, int Vector.<int> Определите действительное уникальное целое число в текущей последовательности и отбросьте
дубликаты. Введите минимальные дубликаты (кроме себя), которые будут считаться допустимым штрихом
checkMatch int int Проверяет, gestSeq ли элемент gestSeq с gestCurrent
end недействительным Array Возвращает результат поиска. Результатом является Array с индексом соответствия
последовательность в gestSeq и ее имя в gestName

Свойства GManager следующие:

Свойство Типы доступа Цель
useOrder Геттер / Сеттер Получает и устанавливает, gestSeq ли gestSeq быть обнаружен в порядке
length Только добытчик Получает длину gestSeq

Как уже упоминалось, первым шагом является регистрация уникальных последовательностей штрихов, с которыми можно сравнить введенный жест. Конструктор инициирует необходимые переменные (подсвеченные) и методы, которые следуют за регистрами и удаляет из предопределенных последовательностей штрихов.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Constructor to initiate gestSeq, stokes, gestName
*/
public function GManager(){
    gestSeq = new Vector.<Vector.<int>>;
    gestName = new Vector.<String>;
    strokes = new Vector.<int>;
}
 
/**
* Method to register a set/ sequence of gesture
* @param sequence Numerical representation of gesture sequence
* @param identifier Name representation of gesture
* @param allowed Stokes allowed: Both (0), main four (1), diagonals (2)
*/
public function register(sequence:Vector.<int>, name:String, onlyStrokes:int = 0):void {
    gestSeq.push(sequence);
    gestName.push(name);
    strokes.push(onlyStrokes);
}
 
/**
* Method to remove a specific set of gesture
* @param selectSet Selected sequence to remove
*/
public function remove(selectSet:int):void {
    gestSeq.splice(selectSet, 1);
    gestName.splice(selectSet, 1);
    strokes.splice(selectSet, 1);
}
 
/**
* Method to remove all predefined gestures.
*/
public function removeAll():void {
    gestSeq = new Vector.<Vector.<int>>;
    gestName = new Vector.<String>;
    strokes = new Vector.<int>;
}

Ниже приведены свойства, специально предназначенные для последовательностей удержания штриха, gestSeq :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
/**
* Property to get total sets of predefined gestures
*/
public function get length ():int {
    return gestSeq.length;
}
 
/**
* Property to get current setting on sequential gesture detection
*/
public function get useOrder():Boolean {
    return _order;
}
 
/**
* Property to determine whether sets of predefined gestures should be detected sequentially
*/
public function set useOrder(value:Boolean):void {
    _order = value;
}

После регистрации последовательностей штрихов для обнаружения в них мы можем подготовить gestCurrent и записать в него последовательные действительные единичные штрихи. Методы ниже обеспечат это. Tracer — это метод для отслеживания последовательности текущего жеста.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Method to start recording current set of gesture made
*/
public function start():void {
    gestCurrent = new Vector.<int>;
}
 
/**
* Method to populate gesture into current set of gesture
* @param gestureCode Gesture detected from MGesture
*/
public function populate (gestureCode:int):void {
    //Accept only valid singular gesture (0~8) into current sequence
    if(gestureCode > -1) gestCurrent.push(gestureCode);
}
 
/**
* Method used for debugging.
*/
public function tracer():Vector.<int> {
    return gestCurrent;
    trace(gestCurrent);
}

Примеры кодирования последовательности жестов

К настоящему времени мы уже можем написать программу для проверки последовательности текущего жеста, как указано в шаге 2. Вы можете скачать исходный код и посмотреть на класс CheckOut2 . Включите панель «Вывод» в FlashDevelop, чтобы просмотреть последовательность целых чисел, зафиксированных вашим текущим жестом.

Я также включил демонстрацию этого ниже и поместил TextField для отображения последовательности вместо панели «Вывод» в FlashDevelop.


Я включил исходный код checkOut2 как checkOut2 ниже. Нетрудно понять логику, если вы поняли, как назначаются обработчики событий. ( Прочтите это руководство, чтобы узнать больше об обработчиках событий .) Я также выделил важные моменты, в которых GManager методы GManager .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class CheckOut2 extends Sprite
{
private var ges:MGesture;
private var gesMan:GManager;
private var t:TextField;
 
public function CheckOut2()
{
    //initiating MGesture to allow singular gesture detection
    ges = new MGesture(stage);
 
    //initiating GManager to allow sequential gesture detection
    gesMan = new GManager();
 
    t = new TextField()
    tx = 50;
    ty = 50;
    t.autoSize = TextFieldAutoSize.LEFT;
    addChild(t);
    stage.addEventListener(MouseEvent.MOUSE_DOWN, start);
    stage.addEventListener(MouseEvent.MOUSE_UP, end);
}
 
private function start(e:MouseEvent):void
{
    //Start detecting singular and sequential gesture
    ges.start();
    gesMan.start();
    stage.addEventListener(MouseEvent.MOUSE_MOVE, check);
 
    //Start drawing gesture
    graphics.clear();
    graphics.lineStyle(3);
    graphics.moveTo(mouseX, mouseY);
}
 
private function check(e:MouseEvent):void
{
    gesMan.populate(ges.evalDirections());
    graphics.lineTo(mouseX, mouseY) //Drawing gesture
}
 
private function end(e:MouseEvent):void
{
    stage.removeEventListener(MouseEvent.MOUSE_MOVE, check);
    t.text = gesMan.tracer().toString()
}
}

Очистка штрихов в текущем жесте важна, так как есть:

  1. Удары, затрудняющие распознавание жестов.
  2. Удары, которые дублируются
  3. Штрихи, которые являются недействительными

Я разделил работу по очистке на две фазы, а именно dropStrokes и dropDuplicates . dropStrokes устраняет штрихи, которые затрудняют обнаружение жестов. (Эта функция будет подробно объяснена в части 3, когда мы обнаружим буквы алфавита.) dropDuplicates удаляет дубликаты и недействительные штрихи.

dropStrokes будет хранить только обводки в gestCurrent соответствии с третьим параметром, onlyStrokes , в register . Это определяется, когда мы регистрируем последовательности жестов изначально. Для дальнейшего понимания, опять же остерегайтесь части 3. Но сейчас просто держите ее в затылке. На самом деле, этот урок пока не использует эту функцию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Method to rid irrelevant strokes based upon condition
* @param strokesCondition
* @return Vector of
*/
private function dropStrokes(inputArray:Vector.<int>, strokesCondition:int):Vector.<int> {
    var xDiagonals:Vector.<int> = inputArray.slice(0,inputArray.length);
 
    //Keep mains only
    if (strokesCondition == 1) {
        for (var i:int = 0; i < xDiagonals.length; i++) {
            if (xDiagonals[i] > 3) {
                xDiagonals.splice(i, 1);
                i—
            }
        }
    }
 
    else if (strokesCondition == 2) {
        for (var j:int = 0; j < xDiagonals.length; j++) {
            if (xDiagonals[j] <4) {
                xDiagonals.splice(j, 1);
                j—
            }
        }
    }
 
    return xDiagonals
}


Проведите пальцем слева направо, чтобы перейти к следующему кадру.

Второй этап очистки — это удаление дублирующих и недействительных штрихов, как указано в шаге 2. Реализация описана ниже.

Я разместил презентацию Flash выше, чтобы облегчить ваше понимание алгоритма. Сделайте правый жест, используя мышь, чтобы перейти к следующему кадру, сделайте левый, чтобы вернуться к предыдущему кадру. Жест вверх переводит вас в последний кадр, жест вниз — в первый кадр.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Method to rid duplicates and possible mistaken gestures
* @param minDuplicates Repetition to be considered high repetition
* @return Vector array w/o duplicates and possible mistaken gestures
*/
private function dropDuplicates (inputArray:Vector.<int>, minDuplicates:int = 1):Vector.<int> {
 
    //Append end of line indicator to integer array
    var xDuplicates:Vector.<int> = inputArray.slice(0,inputArray.length);
    xDuplicates.push( -1);
 
    //Initiate variables
    var count:int = 1;
    var keepIndex:int = 0;
 
    //Ripping duplicates and invalid moves
    for (var i:int = 0; i < xDuplicates.length — 1; i++) {
        if (xDuplicates[i] == xDuplicates[i + 1]) count ++
        else{
            if (count > minDuplicates) {
                xDuplicates.splice(keepIndex, count — 1)
                keepIndex++
                i = keepIndex
                count = 1
            }
            else xDuplicates.splice(keepIndex, minDuplicates)
            i—
        }
    }
 
    xDuplicates.splice(xDuplicates.length — 1, 1);
    return xDuplicates;
}

После обрезки gesCurrent наша следующая задача — найти правильное соответствие в наборе предопределенных последовательностей жестов. Мы можем проверить соответствие между текущим жестом и членом предопределенных жестов, используя checkMatch . Введите индекс gestSeq вы хотите проверить соответствие с gestCurrent . Возвращаемое значение -1 указывает на отсутствие совпадения; в противном случае индекс gestSeq совпадает.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Method to check if gestCurrent matches with gestSeq
* @param index Member of gestSeq
* @return Match (1) or fail (-1)
*/
private function checkMatch(index:int):int {
    //Pessimistic search, assumes not found initially
    var matched:int = -1;
 
    //clean input first
    var cleaned:Vector.<int> = dropStrokes(gestCurrent, strokes[index]);
    cleaned = dropDuplicates(cleaned);trace(cleaned,» xduplicates»)
 
    //Check only those of same length
    if (cleaned.length == gestSeq[index].length) {
        var counter:int = cleaned.length;
 
        //Scroll through each integer of selected predefined gesture
        for (var member:int = 0; member < cleaned.length; member++)
        {
            if (cleaned[member] == gestSeq[index][member]) counter—;
            else break;
        }
 
        //if all integers matched, current index of gestSeq matches gestCurrent
        if (counter == 0 ) matched = index;
    }
 
    return matched;
}

Наконец, мы можем вывести результат. Результат будет зависеть от того, _order или нет. Мы проверяем совпадение между gestCurrent и текущим gestSeq только если _order включен, в противном случае нам нужно будет найти совпадение с любым членом в gestSeq .

Независимо от результата поиска, нам нужно вывести результат. Результат поиска будет -1 если не найдено ни одного совпадения; если есть совпадение, будет возвращен массив индекса штриховки в сочетании с его именем. Результат равен -2 если gestSeq пуст.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Method to evaluate gesture according to preferred approach (in sequential order or not)
* @return No predefined gestures (-2), No match of gesture (-1), array with index of match and its description
*/
public function end():Array {
    //Pessimistic search, -2 indicates that there are no predefined gesture
    var result:Array = [ -2, «No predefined gestures»];
 
    //If there are predefined gestures
    if (gestSeq.length > 0) {
 
        //Find the match in order
        if (_order) {
            if (checkMatch(orderCurrent) > -1) {
                result = [orderCurrent, gestName[orderCurrent]];
                orderCurrent++;
            }
        }
 
        //Find a match in no specific order
        else if (!_order) {
                //Scroll through all gestures
            for (var i:int = 0; i < gestSeq.length; i++)
            {
                if (checkMatch(i) > -1) {
                    result = [i, gestName[i]];
                }
            }
        }
 
        //Handle when cant find
        else {
            result = [ -1, «Gesture is invalid.»]
        }
    }
    return result
}

С шагом 14 мы завершили GManager . Нам нужно создать графические активы дальше. Я создал графические ресурсы во Flash Pro и экспортирую их в формате .swc. Нажмите Ctrl + Shift + F12, чтобы открыть окно «Параметры публикации» во Flash, и установите флажок «Экспорт SWC» на вкладке «Flash». Я сделал свои ресурсы доступными для скачивания, но не стесняйтесь разрабатывать свои собственные.

Создание графики во Flash Ide
Панель связи открыта
Экспорт для Actionscript
Опубликовать SWC

Опубликовать SWC

Поместите ваш SWC в папку lib FlashDevelop, как показано выше. Создайте класс с именем «Main2» во FlashDevelop и создайте экземпляр мувиклипа, который содержит графику. Я выделил соответствующий ActionScript ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
private var ges:MGesture;
private var gesMan:GManager;
private var a:Arrows;
private var seq:Boolean = false;
 
public function Main2()
{
    //Importing graphics in form of movieclip
    a = new Arrows();
    a.stop();
    ax = 50;
    ay = 50;
    addChild(a);

Мы зарегистрируем наши последовательности жестов в порядке расположения графики в опубликованном мувиклипе. Мы делаем это, чтобы упростить прокрутку кадров после достижения результата. Обратите внимание, что я выделил строку 33 реализации; Вы можете раскомментировать это в моем исходном файле, чтобы увидеть, что сопоставление сделано по порядку.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public function Main2()
{
    //Importing graphics in form of movieclip
    a = new Arrows();
    a.stop();
    ax = 50;
    ay = 50;
    addChild(a);
     
    //initiating MGesture to allow singular gesture detection
    ges = new MGesture(stage);
     
    //initiating GManager to allow sequential gesture detection
    gesMan = new GManager();
    //this.seq = true;
     
    //Detecting gestures in sequence
    if (seq) {
        a.gotoAndStop(2);
        gesMan.useSeq = true;
    }
     
    //Register gesture sequences in line with frames
    //Register gesture sequences in line with frames
    gesMan.register(new < int > [2, 1], «2 Stroke Down, CCW»);
    gesMan.register(new < int > [1, 0], «2 Stroke Right, CCW»);
    gesMan.register(new < int > [0, 3], «2 Stroke Up, CCW»);
    gesMan.register(new < int > [3, 2], «2 Stroke Left, CCW»);
    gesMan.register(new < int > [3, 2, 1], «3 Stroke Down, CCW»);
    gesMan.register(new < int > [2, 1, 0], «3 Stroke Right, CCW»);
    gesMan.register(new < int > [1, 0, 3], «3 Stroke Up, CCW»);
    gesMan.register(new < int > [0, 3, 2], «3 Stroke Left, CCW»);
     
    gesMan.register(new < int > [3, 0], «2 Stroke Right, CW»);
    gesMan.register(new < int > [0, 1], «2 Stroke Down, CW»);
    gesMan.register(new < int > [1, 2], «2 Stroke Left, CW»);
    gesMan.register(new < int > [2, 3], «2 Stroke Up, CW»);
    gesMan.register(new < int > [2, 3, 0], «3 Stroke Right, CW»);
    gesMan.register(new < int > [3, 0, 1], «3 Stroke Down, CW»);
    gesMan.register(new < int > [0, 1, 2], «3 Stroke Left, CW»);
    gesMan.register(new < int > [1, 2, 3], «3 Stroke Up, CW»);
     
    gesMan.register(new < int > [0, 3, 0, 3, 0],»Step Up»);
    gesMan.register(new < int > [0, 1, 0, 1, 0], «Step Down»);
    gesMan.register(new < int > [4], «South East»);
    gesMan.register(new < int > [5], «South West»);
    gesMan.register(new < int > [6], «North West»);
    gesMan.register(new < int > [7], «North East»);
    gesMan.register(new < int > [4, 5, 4, 5], «Zigzag»);
     
    stage.addEventListener(MouseEvent.MOUSE_DOWN, start);
    stage.addEventListener(MouseEvent.MOUSE_UP, end);
}

Запись последовательности жестов осуществляется по тому же CheckOut2 что и CheckOut2 . Я предполагаю, что читатели прошли Шаг 10. Итак, наконец, мы выведем результат. После успешного совпадения (> -1) мы перейдем к соответствующему кадру. Опять же, это зависит от того, включили ли вы использование последовательности путем включения seq или нет.

01
02
03
04
05
06
07
08
09
10
11
private function end(e:MouseEvent):void
{
    stage.removeEventListener(MouseEvent.MOUSE_MOVE, check);
     
    //evaluate gesture and output result
    var output:Array = gesMan.end()
    if (output[0] > -1) {
        if (seq)a.nextFrame();
        else a.gotoAndStop(output[0] + 2);
    }
}

Нажмите Ctrl + Enter, чтобы опубликовать свой проект. Жест с помощью мыши и что было обнаружено. Я включил оба проекта (один запрашивает у вас соответствие данному жесту; другой отображает нарисованный вами жест, если он соответствует тому, который он знает). Веселиться.


Неточности в пользовательских жестах распространены, и любое приложение, которое включает жесты, должно учитывать это. В следующей части этого руководства я MGesture использовать MGesture и GManager для разработки приложения распознавания алфавита. Я укажу несколько деталей для соответствующей настройки, чтобы улучшить распознавание жестов.

Теперь вам должно быть легко разрабатывать приложения, которые обнаруживают последовательности жестов любой комбинации. Надеюсь, этот урок помог вам в некоторых отношениях. Дайте мне знать о ваших комментариях, запросах и найденных ошибках. Спасибо.