Nape 2 — Соединения

Их называют по разному: соединения (joints), ограничения (constraints). Смысл один — мы ограничиваем наши тела в движении и соединяем между собой.
Я буду использовать термин соединения (joint), мне так больше нравится (Nape использует термин ограничение (constraints), но только для базового класса и для пакета где они находятся, а все остальные ограничения Лука переназвал с окончанием Joint).
В новом Nape соединения (как и все) претерпели большие изменения. Были изменены имена соединений на более логичны, API, соединения стали намного точнее с большими возможностями.

Constraint — это базовый класс для всех соединений.
Рассмотрим его свойства.

active - говорит о том активировано сейчас соединение или нет. По умолчанию установлено в true. Если соединение установить в false то оно перестает влиять на тела (связывать их), но не удаляется из мира и если опять установить в true, то оно автоматически свяжет тела.

ignore - если установлено в true, то тела, относящиеся к этому соединению, не будут сталкиваться.

stiff - указывает на то будет ли соединение жесткое. Если true, двиг будет старатся максимально точно держать соединение в положенном ему месте. В этом режиме движок не просчитывает позиционные ошибки, и не воздействует на скорость тела. Это значит что в этом случае инициализация таких параметров как maxForce и breakUnderForce не имеет смысла.
Если установлено в false, тогда соединение начинает вести себя как «мягкое». В этом режиме соединение рассматривается в месте с frequency и damping коефициентами, что позволяет соединению быть типа пружины.

«Ошибка позиционирования» (positional error) — это насколько далеко соединение отклоняется от положенной позици. Движок постоянно пытается решить эти позиционные ошибки соединения.

Позиционная ошибка

frequency - устанавливает силу «мягкого» соединения. Если у соединения не установлен damping, тогда оно будет показывать колебательные движения с заданной частотой. Значение должно быть больше 0, иначе приводит к ошибке. Не имеет значения если соединение жесткое.

damping - затухание для мягкого соединения. Значение должно быть больше 0 (нету затухания) до 1 (максимальное затухание), хотя разрешены любые позитивные числа. Не имеет значения если соединение жесткое.

maxForce - максимальная сила, которая разрешается для использования в процессе корректировки скорости. При жестком соединении не имеет смысла.

maxError - для мягкого соединения. Максимум ошибки, которую будет пытаться решить соединение за секунду. Его также можно использовать для жесткого соединения, но только в связке с breakUnderError (то есть breakUnderError=true).

removeOnBreak - при true, при «поломке» соединения оно будет удалятся из мира. Если false, тогда свойство active соединения будет установлено в false, но будет и дальше присутствовать в мире и в любой момент ему можно будет опять присвоить active=true.

breakUnderForce - если true, то соединение будет уничтожено и удалено из мира, если сила необходимая для решения соединения выше чем maxForce. Также как и maxForce не имеет значения для жестких соединений, так как они не используют силу для решения позиционирования соединения.

breakUnderError - если true, то соединение будет уничтожено и удалено из мира, если позиционная ошибка будет выше maxError.

Для экспериментов с соединениями я создал два тела: круг и квадрат.

private function createBodies():void
{
	var circleRadius:int = 20;
	var boxSize:int = 20;
	var material:Material = new Material(0.5);
 
	//
	_body_1 = new Body(BodyType.DYNAMIC, new Vec2(APP_WIDTH/2, 50));
	_body_1.shapes.add(new Circle(circleRadius, null, material));
	_body_1.space = _core.space();
 
	_body_2 = new Body(BodyType.DYNAMIC, new Vec2(APP_WIDTH/2+100, 100));
	_body_2.shapes.add(new Polygon(Polygon.box(boxSize, boxSize), material));
	_body_2.space = _core.space();
 
	var ground:Body = new Body(BodyType.STATIC, new Vec2(APP_WIDTH/2, APP_HEIGHT-50));
	ground.shapes.add(new Polygon(Polygon.box(APP_WIDTH, 50), material));
	ground.space = _core.space();
}

PivotJoint - соединение шарнир. Бублик надет на гвоздь. Он может вращатся но перемещатся никуда не может.
Конструктор ожидает четыре параметра: первые два это тела, которые будут связаны, следующие два это точки привязки на телах (в локальных координатах тела). По этим точками соединение будет связывать эти тела.
Пример. Создаю два тела. Они свободно падают. Устанавливаю таймер на одну секунду. Через секунду создаю для них PivotJoint соединение. Точки соединения для тел устанавливаю в их центры масс. Устанавливаю флаг ignor в true, чтобы тела не выталкивались друг из друга. (красной точкой обозначено место соединения)

private function createPivotJoint(event:TimerEvent):void
{
	var pivotJoint:PivotJoint = new PivotJoint(_body_1, _body_2, _body_1.localCOM, _body_2.localCOM);
	pivotJoint.ignore = true;
	pivotJoint.space = _space;
}

This movie requires Flash Player 9

Установим это соединение на краях фигуры и посмотрим что получится.

private function createPivotJoint(event:TimerEvent):void
{
	var anchorBody_1:Vec2 = new Vec2(_body_1.localCOM.x, _body_1.localCOM.y + 15);
	var anchorBody_2:Vec2 = new Vec2(_body_2.localCOM.x + 5, _body_2.localCOM.y - 5);
	var pivotJoint:PivotJoint = new PivotJoint(_body_1, _body_2, anchorBody_1, anchorBody_2);
	pivotJoint.ignore = true;
	pivotJoint.space = _space;
}

This movie requires Flash Player 9

Продемонстрирую пример с активацией и деактивацией соединения. Через каждые 3 секунды буду то активировать то деактивировать соединение.

private var pivotJoint:PivotJoint;
 
private function createPivotJoint(event:TimerEvent):void
{
	var anchorBody_1:Vec2 = new Vec2(_body_1.localCOM.x, _body_1.localCOM.y + 15);
	var anchorBody_2:Vec2 = new Vec2(_body_2.localCOM.x + 5, _body_2.localCOM.y - 5);
	pivotJoint = new PivotJoint(_body_1, _body_2, anchorBody_1, anchorBody_2);
	pivotJoint.ignore = true;
	pivotJoint.space = _core.space();
 
	TimeUtil.getTimer(deactivateJoint, 3000);
}
 
private function deactivateJoint(event:TimerEvent):void
{
	pivotJoint.active = !pivotJoint.active;
}

This movie requires Flash Player 9

Еще одно демо.
Для круга использую мягкое соединение, для квадрата жесткое.

// Для квадрата
pivotJoint = new PivotJoint(_body_2, _core.space().world, _body_2.localCOM, _body_2.position);
pivotJoint.stiff = true;
pivotJoint.breakUnderError = true;
pivotJoint.maxError = 1;
pivotJoint.space = _space;
 
// Для круга
var pivotStatic:PivotJoint = new PivotJoint(_body_1, _core.space().world, _body_1.localCOM, new Vec2(APP_WIDTH/2, APP_HEIGHT/2));
pivotStatic.stiff = false;
pivotStatic.breakUnderForce = true;
pivotStatic.maxForce = 3000;
pivotStatic.frequency = 0.5;
pivotStatic.damping = 0.0;
pivotStatic.space = _space;

Попробуйте натянуть немного круг и запустить так, чтобы он ударился об квадрат (квадрату специально было выставлено маленькое значение maxError). А потом и круг оторвать :)

This movie requires Flash Player 9

Исходник с экспериментом.
Сегодня были основы соединений на примере PivotJoint. В следующей статье посмотрим все остальные соединения.

Поделиться в соц. сетях

Опубликовать в LiveJournal
Опубликовать в Google Plus
  • Ash

    Благодарю вас за отличные статьи по Nape. C ними гораздо легче удается разбираться с этим движком. Хотелось бы узнать как там реализуется физика мягких тел, видел в примерах от Луки, так что предполагаю, что есть такая возможность.

  • Ash

    http://www.newgrounds.com/dump/item/b3000c07f9d39a44d0e4842c5ad8b057
    вот как круги например в этом примере

  • VirtualMaestro

    Рад что помогают.
    Насколько я помню, мягкие тела реализуются как обычные маленькие шарики связаны каким нибудь мягким соединением (напр. PivotJoint). Надо тестировать, мне мягкие тела не надо были :)

    На счет вашего вопроса по Angry Birds.
    Я такой механики не реализовывал, но там не должно быть ничего сложного. Думаю можно пойти несколькими путями.
    Напр. оттягивать физическое тело. В исходнике к этой статье (InitNape класс), есть реализовано как хватать тело.
    Или напр. просто за спрайт оттягивать.
    То место откуда начали тащить тело это пусть будет ноль. Когда вы оттащили назад (назад и вниз) получилось, скажем, (-20, 20).
    Когда отпустили то в случае со спрайтом перемещаете тело в эту позицию (позицию запуска), если это случай где вы тащите физ тело то уже ничего перемещать не надо оно уже здесь :)
    Потом этому телу придаете импульс (или силу) applyImpulse (applyForce) с противоположными координатами (20, -20), и точкой приложения в центре тела.
    получится что то типа
    applyLocalForce(new Vec2(20, -20), new Vec2(0,0))

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

  • Ash

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

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

    связи сильно нагружают процессор насколько я выявил при тестировании. Надеюсь вы поймете что я написал)))))

  • VirtualMaestro

    Дело в том что это насколько я знаю единственный способ на этом движке (да вообще на любом движке импульсного типа) сделать мягкое тело. Иначе надо использовать физ движок основанный на физике Верлета.
    А графика рисуется особым образом — аппроксимируется по вершинам тел. Где то на http://flashgamedev.ru/ это обсуждали

  • Ash

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

  • VirtualMaestro

    Только через Polygon, задавая вершины.
    Почитайте тут код, там у меня строится треугольник
    http://flashnotes.ru/2012/03/12/nape-2-filtraciya-vzaimodejstvij/

    Задаются вершины через структуру GeomPoly, но там может быть и Array, Vector…

  • Ash

    Спасибо, что бы я без вас делал)))
    Кстати, почему когда соединение добавляешь нужно обязательно два тела?…..а если я хочу добавить соединение просто с пустым местом?…
    вы это делаете непонятным мне способом, к сожалению…через переменную core..

  • VirtualMaestro

    Во всех соединениях должно быть два тела. Одно что крепится, другое куда крепится.
    В случае если нужно прикрепить, нарп. круг на шарнир (PivotJoint) чтобы он висел в воздухе, то можно использовать тело мира.
    У класса Space (мир я его называю :) ) есть свойство world, это и есть тело мира. Можно обойтись и без него но Лука добавил его для удобства, если вам надо что то подвесить в пространстве без использования дополнительных тел.
    Напр., прикрепим шарниром центр круга по центру нашего пространства.
    var pivotStatic:PivotJoint = new PivotJoint(circleBody, _space.world, circleBody.localCOM, new Vec2(APP_WIDTH/2, APP_HEIGHT/2));

  • Ash

    да прибудет вам счастье!)))
    отлично. спасибо!!
    а вы на каком-нибудь форуме сидите или еще где?….где можно спрашивать и не засорять вам комментарии. я конечно злоупотреблять не собираюсь, но по nape не очень много туторов, а движок отличный. Иногда бывает на мелочи какой застрянешь и все…дня два просидишь(это я не о nape, вообще об AS).

  • VirtualMaestro

    Бываю тут http://flashgamedev.ru/ — отличное комьюнити

  • dmitriy

    Отличные уроки, спасибо огромное. Все доходчиво и понятно.
    Вот хотелось бы урока или примеров примеров по созданию чего-то подобного:
    http://deltaluca.me.uk/docnew/swf/DestructableTerrain.html
    http://deltaluca.me.uk/docnew/swf/Cutting.html

  • dmitriy

    Прошу прощения, ошибся с последней ссылкой. Имел ввиду:
    http://deltaluca.me.uk/docnew/swf/DynamicDestruction.html

  • VirtualMaestro

    Пожалуйста. Постараюсь сделать, только пока не знаю время, очень много задач.

  • dmitriy

    А можете хотябы кратко рассказать про это:
    InteractionType.SENSOR — сенсорные пересечения;
    InteractionType.FLUID — флюидные взаимодействия;

    Что такое вообще флюиды и сенсоры?

  • VirtualMaestro

    У каждого шейпа есть InteractionFilter (http://deltaluca.me.uk/docnew/pckg/nape/dynamics/InteractionFilter.html), кроме маски и группы для столкновения, также есть маски и группы для сенсоров и флюидных взаимодействий.
    Что такое флюид? Это просто вода. В новой версии Nape появилась вода и каждый шейп можно сделать как воду (пример тут http://deltaluca.me.uk/docnew/swf/WaterBalls.html)
    fluidGroup и fluidMask имеют значение только если для шейпа флаг fluidEnabled установлен в true.
    Как использовать сенсор? Например, в игре нужно зафиксировать момент когда герой входит в какую либо зону или, например, когда попадает в область чекпоинта, и тут нужно отловить это событие, чтобы сохранить игру, или еще пример он входит в радиус действия бомбы и когда он в этом радиусе бомба взрывается. Разница с InteractionType.COLLISION в том, что сенсор не участвует в столкновениях, он просто фиксирует событие пересечения. Хотя по-сути тоже самое можно сделать с помощью InteractionType.COLLISION, но с InteractionType.SENSOR это делается проще, гибче и очевидней.

    InteractionType.FLUID используется для отлова взаимодействие с водой (напр. фиксирует, что объект пересекся с водой или наоборот вышел из области воды).

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

  • dmitriy

    Спасибо большое.
    Я, если честно, думал что это нечто более необычное. :)

  • VirtualMaestro

    Не так страшен черт как его малюют :)
    Пожалуйста

  • Евгений

    Доброго вечера)

    А дальше статей про остальные соединения подробнее не планируется пока?) очень уж хорошо идут.

    И насущный вопрос: очень хочется сделать веревки. Для этого я так понимаю есть родное соединение PulleyJoint либо использовать маленькие связанные прямоугольники. Второй вариант при тестах дает много косяков, звенья застревают где не надо, даже между собой. А первый вариант — не знаю как запилить графику для соединения.. не подскажете способ?) что-то вроде того, как у этих ребят реализовано http://www.toffeegames.com/successful-experiment.html