В Box2D есть специальный интерфейсный класс ContactListener
, реализовав который, можно обрабатывать коллизии. В этой статье покажу как его использовать на практических примерах.
ContactListener
— интерфейс, который можно реализовать в своём классе для дальнейшего использования в игровом мире. Необходимо реализовать 4 метода: beginContact
, endContact
, preSolve
, postSolve
. Наш класс будет иметь примерно такой вид:
public class MyContactListener implements ContactListener{ @Override public void endContact(Contact contact) { } @Override public void beginContact(Contact contact) { } @Override public void preSolve (Contact contact, Manifold oldManifold){ } @Override public void postSolve (Contact contact, ContactImpulse impulse){ } }
Чтобы использовать, необходимо назначить его игровому миру.
World world = new World(new Vector2(0, -20), true); world.setContactListener(new MyContactListener());
beginContact
Срабатывает, когда два объекта начинают накладываться. Прокает только в рамках шага.
endContact
Срабатывает, когда два объекта прекращают соприкасаться. Может быть вызван, когда тело разрушено, таким образом, это событие может иметь место вне временного шага.
preSolve
Срабатывает после обнаружения столкновения, но перед его обработкой. Это позволяет нам как-то изменить контакт до его обработки. Например, можно сделать контакт неактивным. Допустим, вы хотите сделать так, чтобы персонаж проходил сквозь движущуюся платформу. Тогда preSolve
будет выглядеть так:
@Override public void preSolve (Contact contact, Manifold oldManifold){ WorldManifold manifold = contact.getWorldManifold(); for(int j = 0; j < manifold.getNumberOfContactPoints(); j++){ if(contact.getFixtureA().getUserData() != null && contact.getFixtureA().getUserData().equals("p")) contact.setEnabled(false); if(contact.getFixtureB().getUserData() != null && contact.getFixtureB().getUserData().equals("p")) contact.setEnabled(false); } }
Стоит помнить, что контакт будет снова активным при каждом определении коллизии. Поэтому в нашем случае необходимо contact.setEnabled(false)
вызывать постоянно.
contact.getFixtureA().getUserData().equals("p")
используется для идентификации объекта. Напомню, что при создании платформы используется метод platform.getFixtureList().get(0).setUserData("p");
.
postSolve
Метод позволяет осуществить логику игры, которая изменяет физику после контакта. Например, деформировать или уничтожить объект после контакта. Однако, Box2D не позволяет вам изменять физику в методе, потому что вы могли бы разрушить объекты, которые Box2D в настоящее время обрабатывает, приводя к ошибке.
Есть тут одна тонкость — нельзя просто удалить объект, так как он может обрабатываться где-то в данный момент, и в итоге вы получите ошибку:
java.lang.NullPointerException
at com.badlogic.gdx.physics.box2d.World.contactFilter
И так, в методе будем удалять блоки, с которыми столкнулись.
@Override public void postSolve (Contact contact, ContactImpulse impulse){ Body body = null; if(contact.getFixtureA() != null && contact.getFixtureA().getUserData() != null &&contact.getFixtureA().getUserData().equals("b")) body = contact.getFixtureA().getBody(); if(contact.getFixtureB() != null && contact.getFixtureB().getUserData() != null && contact.getFixtureB().getUserData().equals("b")) body = contact.getFixtureB().getBody(); if(body != null){ body.setActive(false); world.destroyBody(body); } }
Теперь, при столкновении блоки, для которых задано getFixtureList().get(0).setUserData("b")
будут уничтожены. Я писал сверху, что при обычном удалении будет ошибка. Но, если перед удалением сделать объект неактивным body.setActive(false)
, то ошибки не будет.
Исходники
Можете скачать исходники Libgdxtutorial-lesson6.1.rar. Они довольно сыроваты, правда. Но понять принципы работы помогут.