В процессе нашего изучения Box2d мы не затронули очень важную тему - обработку событий. Разумеется, Box2d обрабатывает события столкновения объектов вполне самостоятельно и без нашей помощи, но иногда нам тоже хочется принять в этом участие :) Внесем изменения в наш проект Kepler таким образом, чтобы столкнувшиеся объекты "сливались" в один.
Один из столкнувшихся объектов будем удалять, а у второго будем изменять параметры. Нам необходимо зарегистрировать объект ContactListener, чтобы получать сообщения о коллизиях и внести в проект изменения, связанные с обработкой этих коллизий. Главное, о чем необходимо помнить, это то, что объекты Box2d НЕ ДОЛЖНЫ изменяться, в процессе расчета очередной итерации. Получив уведомление о коллизии, мы должны сохранить параметры, для того, чтобы выполнить необходимые изменения после расчета итерации Box2d. Измененный модуль Task будет выглядеть следующим образом:
package com.WhiteRabbit.Kepler;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import org.jbox2d.callbacks.ContactImpulse;
import org.jbox2d.callbacks.ContactListener;
import org.jbox2d.collision.Manifold;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.contacts.Contact;
public class Task extends TimerTask implements ContactListener {
private static long SECONDS_INTERVAL = 1000L;
private static long FRAME_RATE = 15L;
private KeplerActivity ctx;
private Timer timer = new Timer();
private World world;
private Body ground;
public Task(KeplerActivity ctx) {
this.ctx = ctx;
configure();
}
@Override
public void beginContact(Contact contact) {}
@Override
public void endContact(Contact contact) {}
@Override
public void preSolve(Contact contact, Manifold folds) {}
@Override
public void postSolve(Contact contact, ContactImpulse impulse) {
Item a = (Item)contact.getFixtureA().getBody().getUserData();
Item b = (Item)contact.getFixtureB().getBody().getUserData();
if (a.isMarked() || b.isMarked()) return;
a.mark(); b.delete();
Vec2 aSpeed = a.getBody().getLinearVelocity();
Vec2 bSpeed = b.getBody().getLinearVelocity();
a.getBody().setLinearVelocity(new Vec2(aSpeed.x + bSpeed.x,
aSpeed.y + bSpeed.y));
a.setRadius((float)Math.sqrt((a.getRadius()*a.getRadius()) +
(b.getRadius()*b.getRadius())));
}
private void configure() {
MainView v = ctx.getView();
float X = v.SZ_X / 2;
float Y = v.SZ_Y / 2;
// Создать мир
Vec2 gravity = new Vec2(0.0f, 0.0f);
world = new World(gravity, true);
world.setContactListener(this);
BodyDef bd = new BodyDef();
ground = world.createBody(bd);
// Создать границы
bd.position.set(0, Y);
Body edge = world.createBody(bd);
PolygonShape sd = new PolygonShape();
sd.setAsBox(1, Y);
edge.createFixture(sd, 1);
bd.position.set(2 * X, Y);
edge = world.createBody(bd);
edge.createFixture(sd, 1);
bd.position.set(X, 0);
edge = world.createBody(bd);
sd.setAsBox(X, 1);
edge.createFixture(sd, 1);
bd.position.set(X, 2 * Y);
edge = world.createBody(bd);
edge.createFixture(sd, 1);
// Создать объекты
CircleShape shape = new CircleShape();
bd.type = BodyType.DYNAMIC;
Random r = new Random();
for (int i = 0; i < 15; i++) {
bd.position.set(r.nextInt((int)X * 2 - 10) + 5,
r.nextInt((int)Y * 2 - 10) + 5);
bd.linearVelocity.set(r.nextInt(20) - 5, r.nextInt(20) - 10);
shape.m_radius = r.nextInt(4) + 1;
Body b = world.createBody(bd);
b.createFixture(shape, r.nextInt(3) + 1);
ctx.getView().addBody(b, shape.m_radius);
}
// Запустить рассчет
timer.scheduleAtFixedRate(this, 0, SECONDS_INTERVAL / FRAME_RATE);
}
public void terminate() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
@Override
public void run() {
// Выполнить расчет итерации
float timeStep = 2.0f / FRAME_RATE;
world.step(timeStep, 10, 10);
// Удалить столкнувшиеся объекты
ctx.getView().removeDeleted(world);
// Рассчитать действующие силы
ctx.getView().calcForces();
// Обновить view
ctx.getView().update();
}
}
- Мы наследуем интерфейс ContactListener и регистрируем его вызовом setContactListener
- Для обработки коллизий будем использовать метод postSolve (от других он выгодно отличается наличием параметра impulse, по которому можно оценивать "силу" соударения, чего мы правда не будем делать)
- В configure вместо 3 генерируем 15 объектов, используя генератор случайных чисел для формирования начальных параметров
- Добавляем границы по краям экрана, чтобы объекты не улетали в открытое пространство
- В методе run добавляем фазу удаления объектов помеченных для удаления
В MainView немного изменяем метод addBody (сохраняя в Body ссылку на объект Item) и добавляем метод removeDeleted, осуществляющий удаление объектов:
package com.WhiteRabbit.Kepler;
import java.util.ArrayList;
import java.util.List;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.Fixture;
import org.jbox2d.dynamics.World;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainView extends SurfaceView implements SurfaceHolder.Callback {
import java.util.ArrayList;
import java.util.List;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.Fixture;
import org.jbox2d.dynamics.World;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainView extends SurfaceView implements SurfaceHolder.Callback {
...
public void addBody(Body b, float r) {
Item it = new Item(b, r);
b.setUserData(it);
items.add(it);
}
public void removeDeleted(World w) {
List<Item> newItems = new ArrayList<Item>();
for (Item it: items) {
if (it.isDeleted()) {
w.destroyBody(it.getBody());
} else {
if (it.isMarked()) {
Body b = it.getBody();
CircleShape shape = new CircleShape();
shape.m_radius = it.getRadius();
Fixture f = b.getFixtureList();
if (f !=null) {
b.destroyFixture(f);
}
b.createFixture(shape, 1);
it.clear();
}
newItems.add(it);
}
}
items = newItems;
}
...
}
Item it = new Item(b, r);
b.setUserData(it);
items.add(it);
}
public void removeDeleted(World w) {
List<Item> newItems = new ArrayList<Item>();
for (Item it: items) {
if (it.isDeleted()) {
w.destroyBody(it.getBody());
} else {
if (it.isMarked()) {
Body b = it.getBody();
CircleShape shape = new CircleShape();
shape.m_radius = it.getRadius();
Fixture f = b.getFixtureList();
if (f !=null) {
b.destroyFixture(f);
}
b.createFixture(shape, 1);
it.clear();
}
newItems.add(it);
}
}
items = newItems;
}
...
}
Item приобретает новые члены, но не становится от этого менее тривиальным:
package com.WhiteRabbit.Kepler;
import org.jbox2d.dynamics.Body;
public class Item {
private Body body;
private float radius;
private boolean isDeleted = false;
private boolean isMarked = false;
public Item(Body body, float radius) {
this.body = body;
this.radius = radius;
}
public boolean isDeleted() {
return isDeleted;
}
public boolean isMarked() {
return isDeleted || isMarked;
}
public void delete() {
isDeleted = true;
}
public void mark() {
isMarked = true;
}
public void clear() {
isMarked = false;
}
public Body getBody() {
return body;
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
}
import org.jbox2d.dynamics.Body;
public class Item {
private Body body;
private float radius;
private boolean isDeleted = false;
private boolean isMarked = false;
public Item(Body body, float radius) {
this.body = body;
this.radius = radius;
}
public boolean isDeleted() {
return isDeleted;
}
public boolean isMarked() {
return isDeleted || isMarked;
}
public void delete() {
isDeleted = true;
}
public void mark() {
isMarked = true;
}
public void clear() {
isMarked = false;
}
public Body getBody() {
return body;
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
}
Вот и все. Запускаем программу на выполнение и наблюдаем картину "должен остаться только один":
Как обычно, полный исходный код проекта можно взять здесь.
Комментариев нет:
Отправить комментарий