Статьи

Создайте манипулятор, используя передовую кинематику

Прямая кинематика (FK) — алгоритм позиционирования. Это не так сложно, как кажется, если вы связываете это с последовательностью действий с точки зрения оператора экскаватора. Начиная с ближайшего к нему узла, оператор настраивает ориентацию каждого узла в надежде, что совок достигнет предполагаемого местоположения. Часто делается несколько попыток до достижения успеха. FK — алгоритм для выполнения такой задачи. В этом уроке мы попытаемся реализовать FK и наложить его на экскаватор с двухмерного вида сбоку. Поняв этот алгоритм, можно легко адаптировать его к трехмерной среде. Наслаждайтесь!


В этом руководстве предполагается следующая последовательность:

  • Объяснение математического фона Ф.К.
  • Жесткое кодирование будет выполняться в файле в соответствии с последовательным потоком.
  • Далее, объектно-ориентированный класс разработан для реализации алгоритма FK.
  • Демонстрация класса FK, используемого в проекте.
  • Графика применяется к демонстрации.

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

Иерархические отношения упрощены ниже:

корень> узел1> узел2> узел3> узел4> узел5 …

Приведенная выше флэш-анимация показывает отношения между родителями (выше по иерархии) и дочерними (ниже по иерархии), где каждая стрелка указывает на дочерний узел.


Каждый из этих узлов связан с соседними ограничениями. Я буду называть эти ограничения «Стрелками». Стрелка — это векторы. Они имеют длину (более известную как величина) и угол, который описывает ее ориентацию. В этом руководстве эти стрелки указывают от родительского узла к дочернему узлу. Например, узел 1 присоединяется к корню с помощью стрелки1. Стрелка1 указывает от корня на узел1.

Стрелки также следуют иерархическому порядку, потому что они пытаются объединить иерархическую цепочку узлов. Это иерархическое отношение упрощено, как показано ниже:

стрелка1> стрелка2> стрелка3> стрелка4> стрелка5 …

Так где же стрелка0? Ну, корень является самым высоким в иерархии. На нее не указывает ни одна стрелка, поэтому фактически нет стрелки0.



Регулировка вращения стрелок — самая важная вещь в алгоритме FK. Здесь мы будем вращать стрелку на угол дельта. Если мы повернем стрелку1, положение узла 1 будет обновлено. Но это не все.

В алгоритме FK кинематика перемещается от самой высокой к самой низкой иерархии. Предположим, что наша цепь имеет 6 узлов, соединенных 5 стрелками. Если дельта-вращение применяется к стрелке1, то стрелки2 к стрелке5 также необходимо повернуть на угол дельты. Я выделил стрелки, на которые повлияло изменение.

стрелка1 > стрелка2 > стрелка3 > стрелка4 > стрелка5

Если дельта вращение применяется к стрелке 3, то затрагиваются только стрелка 4 и стрелка 5.

arrow1> arrow2 > arrow3 > arrow4 > arrow5

Для каждой затронутой стрелки также будет затронута позиция связанного с ней узла. Таким образом, node4 и node5 обновят свои позиции.

В приведенном выше SWF, нажмите стрелки, чтобы увидеть, как кинематика пульсирует вниз по узлам (более темные стрелки).


Запустите FlashDevelop и начните новый проект. Добавьте «MileStone1.as» в ваш проект.






Теперь для реализации нашей первой цепочки FK. Во-первых, нам нужно добавить два спрайта на сцену в качестве переменных класса. Корневой узел — это синий круг, а первый узел — фиолетовый круг.

01
02
03
04
05
06
07
08
09
10
11
rootNode = new Sprite();
rootNode.graphics.beginFill(0x4499FF);
rootNode.graphics.drawCircle(0, 0, 20);
rootNode.graphics.endFill();
addChild(rootNode);
  
node1 = new Sprite();
node1.graphics.beginFill(0x772255);
node1.graphics.drawCircle(0, 0, 20);
node1.graphics.endFill();
addChild(node1);

node1 присоединяется к rootNode через vec1 , который является экземпляром класса Vector2D (пользовательский класс). Это переменная класса. Мы начинаем его и устанавливаем его величину до 60.

1
2
vec1 = new Vector2D(0, 0);
vec1.setMagnitude(60);

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

1
2
3
4
5
//setting the nodes
rootNode.x = 150;
rootNode.y = 150;
node1.x = rootNode.x + vec1.x;
node1.y = rootNode.y + vec1.y;

Почти готово. Добавляем слушателя к событию клавиатуры. При нажатии произвольной клавиши, vec1 поворачивается и дополнительно меняет ориентацию node1 отношению к rootNode .

1
2
3
4
5
6
7
8
9
    stage.addEventListener(KeyboardEvent.KEY_DOWN, move);
}
  
private function move(e:KeyboardEvent):void
{
    vec1 = vec1.rotate(Math2.radianOf(10));
    node1.x = rootNode.x + vec1.x;
    node1.y = rootNode.y + vec1.y;
}

Ниже завершена MileStone1. Просто, правда?

Нажмите на SWF, затем нажмите любую клавишу (несколько раз), чтобы увидеть эффект.


Теперь, когда у нас есть базовая настройка, давайте продвинемся немного дальше. Мы добавим node2 (также фиолетовый) в существующую цепочку. node2 соединен с node1 через vec2 , который имеет величину 60

1
2
3
4
5
6
7
8
9
//a second child
node2 = new Sprite();
node2.graphics.beginFill(0x772255);
node2.graphics.drawCircle(0, 0, 20);
node2.graphics.endFill();
addChild(node2);
  
vec2 = new Vector2D(0, 0);
vec2.setMagnitude(60);

Мы инициализируем положение node2 относительно node1 используя vec2 в качестве ограничения, используя следующий код. Не забудьте также включить тот же фрагмент кода в прослушиватель клавиатуры (строка 64 MileStone2).

1
2
node2.x = node1.x + vec2.x;
node2.y = node1.y + vec2.y;

Вот MileStone2 завершено:

Снова используйте клавиатуру, чтобы увидеть эффект.


Хм … Что-то не так с этой реализацией FK. Видите, кинематика не проходит по цепочке. Напомним Шаг 4: Дельта Вращение; кинематика должна двигаться вниз от текущего уровня иерархии до конца. Я выделил строки кода, которые делают это. Чтобы исправить логическую ошибку, просто вставьте строку 65 в MileStone2. Я поставил MileStone3 на случай, если у вас возникнут трудности.

01
02
03
04
05
06
07
08
09
10
private function move(e:KeyboardEvent):void
{
    vec1 = vec1.rotate(Math2.radianOf(10));
    node1.x = rootNode.x + vec1.x;
    node1.y = rootNode.y + vec1.y;
      
    vec2 = vec2.rotate(Math2.radianOf(10));
    node2.x = node1.x + vec2.x;
    node2.y = node1.y + vec2.y;
}

Далее, давайте добавим гибкость нашему контролю. В настоящее время мы контролируем позиционирование node1 и node2. Позволяет управлять только позиционированием node2 .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
private function move(e:KeyboardEvent):void
{
    if (e.keyCode == Keyboard.PAGE_UP) {
        vec1 = vec1.rotate(Math2.radianOf(10));
        node1.x = rootNode.x + vec1.x;
        node1.y = rootNode.y + vec1.y;
          
        vec2 = vec2.rotate(Math2.radianOf(10));
        node2.x = node1.x + vec2.x;
        node2.y = node1.y + vec2.y;
    }
      
    else if (e.keyCode == Keyboard.PAGE_DOWN) {
        vec2 = vec2.rotate(Math2.radianOf(10));
        node2.x = node1.x + vec2.x;
        node2.y = node1.y + vec2.y;
    }
}

Используйте клавиши Page Down и Page Up для перемещения различных узлов.

Да, вращение все еще в одном направлении — против часовой стрелки. Цель выполнения шагов 5 — 13 — получить полное понимание алгоритма FK. Теперь, когда он построен, давайте перенесем это на другой уровень: проектируем класс FKChain, который позволяет легко реализовать FK.


FKChain был разработан, чтобы легко реализовать FK. Я разработал класс со следующими переменными и функциями. Я проведу вас через них.

переменные Цель Тип данных
nodes Удерживать узлы цепочки ФК Vector массив Sprites
arrows Держать стрелки, соединяющие узлы Vector массив Vector2D
lowerLimits Удерживать нижнюю границу допустимого угла Vector массив Number
upperLimits Удерживать верхнюю границу допустимого угла Vector массив Number
selectedIndex Хранить данные текущего выбранного узла Integer

Ассоциация узлов со стрелками наряду с другими ограничениями изображена на следующей диаграмме.


Обратите внимание на связь nodes со arrows , lowerLimits и upperLimits . Угол, отмеченный красным, ограничен согласно формуле на изображении выше.

Также обратите внимание, что я упомянул на шаге 3, что arrow[0] нет arrow[0] . Однако, если мы хотим реализовать границы, нам понадобится arrow[0] чтобы быть горизонтальным вектором, где угол arrow[1] измеряется и оценивается ограничениями.


Теперь, когда вы понимаете болты и гайки класса FKChain, мы переходим к определению функций FKChain.

функции Тип ввода / данных Цель
FKChain() корневой узел / Sprite Инициирует переменные класса. Устанавливает корневой узел в цепочку FK вдоль
с соответствующими ограничениями. Устанавливает selectedNode в корневой узел.
appendNode() следующий дочерний узел / Sprite Добавить новый узел в цепочку вместе с соответствующими ограничениями. наборы
текущий выбран. Устанавливает selectedNode для нового узла.
removeNode() узел для удаления / Sprite Удалить входной узел из цепочки вместе с соответствующими ограничениями.
selectedNode() узел для манипулирования / Sprite Установить текущий выбранный узел для выполнения манипуляций, таких как настройка
его стрелка (величина и угол) и ограничения на угол стрелки.
updateLimits() нижние, верхние границы / Number Устанавливает новые ограничения (верхняя и нижняя граница) на стрелки
угол выбранного узла.
alterMagnitude() величина / Number Устанавливает новую величину на стрелке выбранного узла.
alterAngle() угол для регулировки / Number Вводит дельта-угол на стрелку выбранного узла.
setPosition() (никто) Падение кинематики от иерархии выбранного узла до конца
цепь.

Я также представил несколько геттеров для облегчения разработки. Они используются при прокрутке узлов.

Свойство Цель
currentNode Возвращает текущий выбранный узел.
child Возвращает дочерний элемент выбранного узла.
parent Возвращает родителя выбранного узла.

Теперь у нас есть обзор FKChain, давайте посмотрим на его применение и внутреннюю работу. В вашем проекте создайте класс TestChain . Мы будем вызывать метод рисования для рисования всех необходимых Sprites на нашей сцене. Всего имеется пять спрайтов и скобка для обозначения текущего выбранного узла.

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
public function draw():void
{
    b0 = new Sprite();
    b0.graphics.beginFill(0);
    b0.graphics.drawCircle(0, 0, 15);
    b0.graphics.endFill();
    addChild(b0);
      
    b1 = new Sprite();
    b1.graphics.beginFill(0);
    b1.graphics.drawRect(-80, -10, 80, 20);
    b1.graphics.endFill();
    addChild(b1);
      
    b2 = new Sprite();
    b2.graphics.beginFill(0);
    b2.graphics.drawRect(-40, -10, 40, 20);
    b2.graphics.endFill();
    addChild(b2);
      
    b3 = new Sprite();
    b3.graphics.beginFill(0);
    b3.graphics.drawRect(-40, -10, 40, 20);
    b3.graphics.endFill();
    addChild(b3);
      
    b4 = new Sprite();
    b4.graphics.beginFill(0);
    b4.graphics.drawRect(-40, -10, 40, 20);
    b4.graphics.endFill();
    addChild(b4);
      
    target = new Sprite();
    target.graphics.lineStyle(3, 0xFF0000);
    target.graphics.moveTo( -15, -10);
    target.graphics.lineTo( -15, -15);
    target.graphics.lineTo( -10, -15);
      
    target.graphics.moveTo( 15, -10);
    target.graphics.lineTo( 15, -15);
    target.graphics.lineTo( 10, -15);
      
    target.graphics.moveTo( 15, 10);
    target.graphics.lineTo( 15, 15);
    target.graphics.lineTo( 10, 15);
      
    target.graphics.moveTo( -15, 10);
    target.graphics.lineTo( -15, 15);
    target.graphics.lineTo( -10, 15);
    addChild(target);
}

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
public function initChain():void
{
    c = new FKChain(b0);
      
    c.appendNode(b1);
    c.alterMagnitude(120);
    c.updateLimits(Math2.radianOf( -90), Math2.radianOf(45));
      
    c.appendNode(b2);
    c.updateLimits(Math2.radianOf( -45), Math2.radianOf(90));
      
    c.appendNode(b3);
    c.updateLimits(Math2.radianOf(0), Math2.radianOf(90));
      
    c.appendNode(b4);
      
    //update all node’s position
    b0.x = 250;
    b0.y = 300;
    c.selectNode(b1);
    c.setPosition();
      
    //Place target onto root node initially
    target.x = c.currentNode.x;
    target.y = c.currentNode.y;
}

Во-первых, мы должны создать экземпляр класса FKChain, вызвав его конструктор. Укажите корневой узел в конструкторе. Дальнейшее изучение функции конструктора показывает две основные операции функции. Во-первых, это создание nodes , arrows , upperLimits и lowerLimits . Данные поступают в эти переменные после этого.

Обратите внимание, что корневой узел имеет связанную стрелку. Я явно не показывал, что arrows всегда на один член меньше nodes . Тем не менее, примите это сейчас, поскольку я объясню его использование в следующих разделах Другие выделенные строки являются просто заполнителями, чтобы обеспечить удобную адресацию по ограничениям. Наконец, установка текущего выбранного узла как root для дальнейших манипуляций.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public function FKChain(node0:Sprite)
{
    //instantiate variables
    nodes = new Vector.<Sprite>;
    arrows = new Vector.<Vector2D>;
    upperLimits = new Vector.<Number>;
    lowerLimits = new Vector.<Number>;
      
    //introduce root node
    nodes.push(node0);
    arrows.push(new Vector2D(1, 0));
    lowerLimits.push(0);
    upperLimits.push(0);
    selectNode(node0);
}

После инициации мы можем начать добавлять узлы к экземпляру FKChain. Создание стрелки абстрагируется от пользователей. Я выделил начальную величину и ограничение на угол изгиба (в пределах -90 <угол <90). Эти ограничения легко настраиваются.

01
02
03
04
05
06
07
08
09
10
11
12
public function appendNode(node:Sprite):void
{
    nodes.push(node);
      
    var newArrow:Vector2D = new Vector2D(0, 0);
    newArrow.setMagnitude(60);
      
    arrows.push(newArrow);
    lowerLimits.push(Math2.radianOf(-90));
    upperLimits.push(Math2.radianOf(90));
    selectNode(node);
}

1
2
3
4
5
6
7
8
9
//update all node’s position
b0.x = 250;
b0.y = 300;
c.selectNode(b0);
c.setPosition();
      
//Place target onto root node initially
target.x = c.currentNode.x;
target.y = c.currentNode.y;

Теперь позиция корневого узла находится в верхнем левом углу сцены. Мы переместим местоположение b1 близко к центру. Затем нам нужно переместить другие узлы в цепочке и установить скобку таргетинга на текущий выбранный узел. Давайте рассмотрим дальнейшее setPosition :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public function setPosition():void
{
    var index:int = Math.max(1, selectedIndex);
      
    for (var i:int = index; i < nodes.length; i++)
    {
        //set position
        nodes[i].x = arrows[i].x + nodes[i — 1].x;
        nodes[i].y = arrows[i].y + nodes[i — 1].y;
          
        //set orientation
        nodes[i].rotation = Math2.degreeOf(arrows[i].getAngle());
    }
}

Эта функция пытается переместить и переориентировать все узлы, начиная с текущего выбранного узла. Я установил ограничение на индекс (выделено), потому что логически, когда мы выполняем относительное перемещение, корневой узел не затрагивается никаким родителем. Самый высокий узел в иерархии для выполнения относительного перемещения — это b1 . Не имеет значения, пишете ли вы c.selectNode(b0) или c.selectNode(b1) перед c.setPosition() хотя это повлияет на начальную позицию скобки для трейджинга.


1
2
3
4
5
6
7
public function TestChain()
{
    this.draw();
    this.initChain();
      
    stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
}

Теперь пришло время оживить цепочку. Мы назначим слушателя на событие клавиатуры.

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
private function keyDown(e:KeyboardEvent):void
{
    //scroll through node selection
    if (e.keyCode == Keyboard.SPACE)
    {
        if (c.currentNode == b4)
        {
            c.selectNode(b1);
        }
        else
        {
            c.selectNode(c.child);
        }
    }
      
    else if (e.keyCode == Keyboard.UP)
    {
        c.alterAngle(Math2.radianOf( -1 * step));
        c.setPosition();
    }
    else if (e.keyCode == Keyboard.DOWN)
    {
        c.alterAngle(Math2.radianOf(step));
        c.setPosition();
    }
      
    target.x = c.currentNode.x;
    target.y = c.currentNode.y;
}

При нажатии клавиши пробела целевая скобка будет прокручивать все узлы — кроме корневого узла, b0 . Нажатие клавиши со стрелкой вверх даст отрицательный угол дельты, -1 * step ; в то время как клавиша со стрелкой вниз даст положительный угол дельты, step .

Вот образец TestChain.

Используйте клавишу пробела и клавиши со стрелками вверх и вниз, чтобы управлять им. Обратите внимание, как ограничения влияют на то, как далеко каждый узел может «прогнуться».


Мы использовали alterAngle() в слушателе. Теперь давайте рассмотрим внутреннюю работу этого.

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
public function alterAngle(ang:Number):void
{
    var index:int = Math.max(1, selectedIndex);
      
    //calc deviation between previous arrow and current arrow
    var deviation:Number = arrows[index — 1].angleBetween(arrows[index]);
    var future:Number = deviation + ang;
      
    //ensure ang within defined limits
    if (future > upperLimits[index])
    {
        ang = upperLimits[index] — deviation;
          
    }
    else if (future < lowerLimits[index])
    {
        ang = lowerLimits[index] — deviation;
    }
      
    //update relevant arrows
    for (var i:int = selectedIndex; i < arrows.length; i++)
    {
        arrows[i] = arrows[i].rotate(ang);
    }
}

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


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

1
2
3
4
5
6
7
8
9
public function TestChain2()
{
    this.draw();
    this.initChain();
      
    stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
    stage.addEventListener(KeyboardEvent.KEY_UP, keyUp);
    stage.addEventListener(Event.ENTER_FRAME, animate);
}

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

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
private function keyDown(e:KeyboardEvent):void
{
    //scroll through node selection
    if (e.keyCode == Keyboard.SPACE)
    {
        if (c.currentNode == b4)
        {
            c.selectNode(b0);
        }
        else
        {
            c.selectNode(c.child);
        }
    }
      
    else if (e.keyCode == Keyboard.UP)
    {
        upFlag = true;
    }
    else if (e.keyCode == Keyboard.DOWN)
    {
        downFlag = true;
    }
    else if (e.keyCode == Keyboard.LEFT)
    {
        leftFlag = true;
    }
    else if (e.keyCode == Keyboard.RIGHT)
    {
        rightFlag = true;
    }
}
  
private function keyUp(e:KeyboardEvent):void
{
    if (e.keyCode == Keyboard.UP)
    {
        upFlag = false;
    }
    else if (e.keyCode == Keyboard.DOWN)
    {
        downFlag = false;
    }
    else if (e.keyCode == Keyboard.LEFT)
    {
        leftFlag = false;
    }
    else if (e.keyCode == Keyboard.RIGHT)
    {
        rightFlag = false;
    }
}

Ниже приведены переменные, используемые для анимации кинематики:

1
2
3
4
5
private var angVelo:Number = 0;
private var maxVelo:Number = 5;
private var angAcc:Number = 0.2;
private var angDec:Number = 0.8;
private var step:Number = 5;

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

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
private function animate(e:Event):void
{
    //moving the whole chain
    if (c.currentNode == b0) {
        if (upFlag) {
            b0.y -= step
        }
        else if (downFlag) {
            b0.y += step
        }
        if (leftFlag) {
            b0.x -= step
        }
        else if (rightFlag) {
            b0.x += step
        }
    }
  
    //adjusting angle
    else {
        if (upFlag) {
            angVelo -= angAcc;
              
            //setting cap on angular velocity
            angVelo = Math.max(-1*maxVelo, angVelo);
        }
        else if (downFlag) {
            angVelo += angAcc;
              
            //setting cap on angular velocity
            angVelo = Math.min(maxVelo, angVelo);
        }
        else {
            //decelerate when keys are released
            angVelo *= angDec
        }
          
        //set value to selected node&#39;s arrow.
        c.alterAngle(Math2.radianOf(angVelo));
    }
      
    //refresh position
    c.setPosition();
    target.x = c.currentNode.x;
    target.y = c.currentNode.y;
}

Обратите внимание, что есть немного другая обработка корневого узла. По мере выбора мы будем двигать всю цепочку вверх, вниз, влево или вправо. В то время как другие дети отбираются, мы angVelo угловую скорость, angVelo , вверх или вниз в пределах минимальной и максимальной скорости. Если клавиши отпущены (следовательно, все флаги установлены в false ), текущая скорость замедляется. Чтобы уточнить этот фрагмент выше, прочитайте мой пост по линейной кинематике, чтобы получить представление о приведенном выше коде.

Пример готовой работы показан ниже:

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


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

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





Я назвал эти MovieClip выше Root , Arm1 , Arm2 и Scoop соответственно

Другая деталь заключается в том, что вам нужно нажать Ctrl + L, чтобы открыть панель библиотеки и экспортировать эти ресурсы для ActionScript. Щелкните правой кнопкой мыши, выберите «Связывание» и введите данные, как показано на рисунках ниже.




После того, как все графические ресурсы сделаны, опубликуйте их в формате SWC для правильного импорта в FlashDevelop. Я использую Flash CS3. Нажмите Ctrl + Shift + F12, чтобы открыть настройки публикации. Выберите вкладку Flash и включите Export SWC. Затем нажмите Опубликовать.



Скопируйте новый файл (.swc) в папку lib вашего проекта.


Теперь, когда у вас это есть внутри вашего проекта, вам все равно нужно включить его в библиотеку. Щелкните правой кнопкой мыши свой файл .swc и выберите «Добавить в библиотеку».



Разверните список, как показано на рисунке ниже, чтобы проверить имена переменных. Я использовал следующие имена Arm1 , Arm2 , Root и Scoop . Это имена классов, связанные с этой графикой. Мы будем использовать их позже.



Наконец мы добрались до последней части этого урока. Мы заменим существующий метод draw() на новый, drawAssets() .

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
private function drawAssets():void
{
    rootNode = new Root();
    arm1 = new Arm1();
    arm2 = new Arm2();
    scoop = new Scoop();
      
    addChild(arm1);
    addChild(rootNode);
    addChild(scoop);
    addChild(arm2);
      
    target = new Sprite();
    target.graphics.lineStyle(3, 0xFF0000);
    target.graphics.moveTo( -15, -10);
    target.graphics.lineTo( -15, -15);
    target.graphics.lineTo( -10, -15);
      
    target.graphics.moveTo( 15, -10);
    target.graphics.lineTo( 15, -15);
    target.graphics.lineTo( 10, -15);
      
    target.graphics.moveTo( 15, 10);
    target.graphics.lineTo( 15, 15);
    target.graphics.lineTo( 10, 15);
      
    target.graphics.moveTo( -15, 10);
    target.graphics.lineTo( -15, 15);
    target.graphics.lineTo( -10, 15);
    addChild(target);
}

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

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
private function initChain():void
{
    c = new FKChain(rootNode);
    c.appendNode(arm1);
    c.alterMagnitude(260);
    c.updateLimits(Math2.radianOf( -45), Math2.radianOf(45));
      
    c.appendNode(arm2);
    c.alterMagnitude(100);
    c.updateLimits(Math2.radianOf( 0), Math2.radianOf(90));
      
    c.appendNode(scoop);
    c.alterMagnitude(60);
    c.updateLimits(Math2.radianOf( 45), Math2.radianOf(135));
      
    //update all node’s position
    rootNode.x = 250;
    rootNode.y = 300;
    c.selectNode(rootNode);
    c.setPosition();
      
    //Place target onto root node initially
    target.x = c.currentNode.x;
    target.y = c.currentNode.y;
}

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

1
2
3
4
5
6
7
8
if (c.currentNode == rootNode) {
    if (leftFlag) {
        rootNode.x -= step
    }
    else if (rightFlag) {
        rootNode.x += step
    }
}

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


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