Новый nape. Создание тел, материалы, фильтрация столкновений.

Новый nape:
1. Новый nape. Hello world
2. Новый nape. Обработка событий
3. Новый nape. Testbed. И немного теории
4. Новый nape. Создание тел, материалы, фильтрация столкновений(Его вы сейчас читаете)
5. Новый nape. Соединение тел
6. Новый nape. Приложение сил к телам и raycast

Все примеры кода в Testbed: Скачать проект для FlashDevelop.

Немного про тела

Казалось бы уже рассматривали создание тел, тем не менее мне задают вопросы в личку, а значит мало рассматривали.

Тело (Body) в nape — это нечто, обладающее массой, моментом инерции, текущим положением и углом поворота, а также скоростями движения и вращения. Одно тело может состоять из нескольких фигур (Shape). У каждого тела есть своя собственная локальная система координат, которая движется и вращается вместе с телом, таким образом при движении тела в его собственной системе координат все фигуры остаются на месте. Для преобразования между системами координат у класса Body есть методы worldToLocal и localToWorld.

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

Тела в nape бывают трех типов:
  • Динамическое тело летает по всему миру, сталкивается со всем и вообще ведет себя как и положено по законам физики. Мы можем в любой момент произвольно поменять ему положение, угол поворота или скорость, и это приведет к ожидаемому результату: оно просто изменит положение или скорость, а дальше опять будет вести себя нормально.
  • Статическое тело никуда не двигается, а динамические тела стучаться об него. Двигать или менять угол поворота статическому телу нельзя, а скорости иметь ему не положено. Если нужно все же его переместить, то прежде чем менять ему положение, его нужно удалить из мира, а потом добавить снова.
  • Кинематическое тело — это тело, которое ведет себя как статическое, т.е. не подчиняется законам физики, но соударяется с динамическими телами, но мы можем его двигать. Можно задавать его положение напрямую, но если это делать каждый шаг, то столкновения с ним динамических тел будут считаться не совсем корректно, как будто оно стоит на месте, а можно задать телу скорость и оно будет двигаться с заданной скоростью, корректно ударяя динамические тела.

Материалы

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

Материал в nape задается классом Material. Рассматривать его подробно смысла нет, скажу только что можно создать свой материал и задать ему плотность, коэффициенты трения и упругости, а можно воспользоваться готовым материалом. Я везде пользуюсь последним способом.

staticBody.shapes.add(new Circle(50, new Vec2( -20, 0), Material.ice()));//Например лёд


Фильтрация столкновений

Фильтрация столкновений в nape делается на уровне фигур. Если две фигуры разных тел пересеклись, то выполняется проверка должны ли они столкнуться или свободно пройти друг свозь друга. Для этого используется класс InteractionFilter, который можно присвоить каждой фигуре. Два поля отвечающих за фильтрацию столкновений называются collisionGroup и collisionMask. Можно сказать, что одна фигура «согласна» столкнуться с другой, если для хотя бы одного установленного бита в ее collisionGroup установлен соответствующий бит collisionMask другой фигуры. Столкновение произойдет только если «согласны» обе фигуры. Т.е. столкнуться фигуры если (shape1.filter.collisionGroup & shape2.filter.collisionMask) &&(shape2.filter.collisionGroup & shape1.filter.collisionMask). По умолчанию фигуры имеют группу 1, т.е. в группе установлен первый бит, и готовы сталкиваться со всеми т.е. установлены все биты маски.

Nape поддерживает 32 группы столкновений, т.к. в int 32 бита. Чтобы работать с этими битами надо либо уметь считать в двоичной системе в уме, либо пользоваться двоичным калькулятором. Но если нам достаточно восьми групп, то можно воспользоваться шестнадцатеричной записью: 0x00000001 — первая группа, 0x00000010 — вторая и так далее до восьмой 0x10000000. Кроме того полезно знать про оператор побитового отрицания, который превращает нули в единицы и наоборот. Примеры:

// фигуры будут относится ко второй группе, и "согласны" сталкиваться со всеми телами кроме тел второй группы:
private var filter1:InteractionFilter = new InteractionFilter(0x0010, ~0x0010);
// а тут у нас третья группа и столкновение только между собой и с телами первой группы (по умолчанию все 
//тела как раз первой группы), но не второй и четвертой
private var filter2:InteractionFilter = new InteractionFilter(0x0100, 0x0101);


Кроме того абсолютно аналогично задается возможность фигуры быть сенсором, т.е. если в соответствии с collisionGroup столкновение не должно произойти, но нужно получить уведомление о пересечении тел, для этого используются поля sensorGroup и sensorMask. Также фильтруется взаимодействие с водой.

Пример

package Tests
{
	import nape.dynamics.*;
	import nape.geom.*;
	import nape.phys.*;
	import nape.shape.*
	
	public class Test1 extends Test
	{
		private var staticBody:Body;
		private var kinematicBody:Body;
		
		// фигуры будут относится ко второй группе, и "согласны" сталкиваться со всеми телами кроме тел второй группы:
		private var filter1:InteractionFilter = new InteractionFilter(0x0010, ~0x0010);
		// а тут у нас третья группа и столкновение только между собой и с телами первой группы (по умолчанию все 
		//тела как раз первой группы), но не второй и четвертой
		private var filter2:InteractionFilter = new InteractionFilter(0x0100, 0x0101);
		
		public function Test1() 
		{
			super("Test #1. Body types and filters.");
			
			createWalls(Material.ice());
			
			//просто статическое обычное тело с центром в точке 100,100 повернутое на 60 градусов.
			staticBody = new Body(BodyType.STATIC, new Vec2(100, 100));
			staticBody.rotation = Math.PI / 3;
			//положения его фигур задаются в локальной системе координат
			staticBody.shapes.add(new Circle(50, new Vec2( -20, 0), Material.ice()));
			staticBody.shapes.add(new Circle(50, new Vec2( 20, 0), Material.ice()));
			staticBody.space = space;
			
			//кинематическое тело. начало координат будет в точке 0,0
			kinematicBody = new Body(BodyType.KINEMATIC);
			//поэтому система координат тела и мировая будет совпадать
			//т.е. можно задавать положения фигур в мировых координатах
			kinematicBody.shapes.add(new Polygon(Polygon.rect(200, 200, 100, 50)));
			//вызывать align для кинематического тела не обязательно, но можно
			//начало координат тела будет совмещено с центром масс
			kinematicBody.align();
			trace(kinematicBody.position);
			kinematicBody.space = space;
			//зададим телу скорость
			kinematicBody.velocity.x = 100;
			//и скорость вращения
			kinematicBody.angularVel = Math.PI * 2;
			// переместим тело на середину экрана
			kinematicBody.position.x = Main.stageWidth / 2;
			
			//создадим динамические тела второй группы, которые не будут сталкиваться друг с другом
			for (var i:int = 0; i < 10; ++i)
			{
				var body:Body = new Body(BodyType.DYNAMIC);
				body.shapes.add(new Circle(30, new Vec2(Math.random() * (Main.stageWidth - 200) + 100, 100), 
					Material.wood(), filter1));
				body.align();
				body.space = space;
			}

			//создадим динамические тела третьей группы, которые будут сталкиваться друг с другом,
			//но не с телами второй группы
			for (i = 0; i < 10; ++i)
			{
				body= new Body(BodyType.DYNAMIC);
				body.shapes.add(new Polygon(Polygon.rect(Math.random() * (Main.stageWidth - 200) + 50, 200, 100, 20), 
					Material.wood(), filter2));
				body.align();
				body.space = space;
			}
		}
		
		override public function update():void
		{
			// Изменим скорость кинематичского тела, чтобы оно не уезжало слишком далеко
			if (kinematicBody.position.x > Main.stageWidth || kinematicBody.position.x < 0)
				kinematicBody.velocity.x *= -1;
				
			// телепортируем какое-нибудь динамическое тело наверх
			if (Math.random() > 0.8)
			{
				var body:Body = space.dynamics.at(Math.floor(Math.random() * space.dynamics.length));
				body.position.y = 100;
			}
		}
	}

}


Результат: (Найдите с помощью кнопок Next и Prev тест с названием Test 1.)
  • +16

Комментарии (7)

0
Спс, появился стимул поковырять nape. Может даже пушку свою создам)))
0
Фильтрация коллизий без коллбеков — адски неудобно :/
  • n0uk
  • n0uk
0
Обработка событий была описана раньше.
0
Я думаю имеется ввиду колбеки при обработке коллизий, позволяющие отменить столкновение. Насколько я понимаю, это будет ImmListener, но пока ImmListener не реализован, так что увы…
0
body.fluidEnabled = true; и все что с этим связанно — однозначно фича нового нейпа.
В целом его Лука шибко причесал, за что ему и спасибо:).
Автору + :)
  • Bazz
  • Bazz
0
спасибо за статьи. чтобы «считать в двоичной системе в уме», делаю так:
body.shapes.add(new Polygon(Polygon.rect(0, 0, 50, 50), Material.ice(), 
 new InteractionFilter(1<<0, (1<<1) + (1<<2) +(1<<3))));

т.е. шейп первой группы колизит со 2-й, 3-й и 4-й группами
0
Это очень хорошо, пока не надо в дебагере глазами константы считывать. А 0x10, 0x100 и т.д. гораздо проще с этим. Если не хватает можно добавлять комбинации 0x2, 0x4, 0x8, 0xF.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.