Перед нами две последовательные задачи. Первая: наделить все активные объекты неким "физическим телом". Пока объекты имели только графическую интерпретацию, но этого не достаточно. Неважно, что там у нас нарисовано - программа все воспринимает как прямоугольники графики (спрайты). И край спрайта далеко не всегда приходится на видимый край объекта. По-этому нам нужно дополнительное "физическое тело", лучше подходящее для будущей обработки столкновений.
Мы будем использовать прямоугольное тело, меньшее по размерам, чем наш спрайт. Для простоты центр его будет совпадать с центром спрайта. Будем использовать встроенные методы (геттеры-сеттеры) от центра вращения (Rotation Center). Мне кажется это более логичным, возможно в будущем придется вращать спрайты вокруг этой точки. Вторая задача: обработка столкновений. Взаимодействие двух "физических тел". О ней позже. Для решения первой задачи внесем в наши классы следующие изменения: Класс BasicObject:
// ...some code... private float health; public BodyRectangle mBody; // =========================================================== // Constructors // =========================================================== BasicObject(float x, float y, final ITextureRegion mFaceTextureRegion, VertexBufferObjectManager pVertexBufferObjectManager, float vx, float vy, float bodyWidth, float bodyHeight, float health, final Camera pCamera) { super (x-mFaceTextureRegion.getWidth()/2, y-mFaceTextureRegion.getHeight()/2, mFaceTextureRegion, pVertexBufferObjectManager); this.vx = vx; this.vy = vy; this.health = health; this.mBody = new BodyRectangle(x-bodyWidth/2,y-bodyHeight/2,bodyWidth,bodyHeight); this.deltaX = (this.getWidth()-bodyWidth)/2; this.deltaY = (this.getHeight()-bodyHeight)/2; this.mCamera = pCamera; }; //... some code... public float GetAbsSpeed(){ return (float) Math.sqrt(vx*vx + vy*vy); } // returns 1 - object dead, 2 - object offscreen, 0 - object alive and onscreen public byte Update(float dt){ this.setX(this.getX() + vx * dt); this.setY(this.getY() + vy * dt); resetBody(); this.resetRotationCenter(); this.mBody.resetRotationCenter(); if(!this.mCamera.isRectangularShapeVisible(this)) return 1; if (this.health <= 0) return 2; return 0; } private void resetBody(){ this.mBody.setX(this.getX() + deltaX); this.mBody.setY(this.getY() + deltaY); } public void ReduceHealth(int healthToReduce1) { health -= healthToReduce1; }; public void Kill(){ health = 0; };Строка 2 - "здоровье объекта". Если оно ниже или равно нулю - объект мертв. Строка 3 - собственно тело объекта. Код класса BodyRectangle приведу полностью чуть ниже. Пока нам надо знать, что это прямоугольник, совпадающий центром со спрайтом. Строка 10 - в конструкторе появились параметры для нашего тела - ширина, высота, здоровье. Строки 11 и 16 - хитрые преобразования и деления на два предназначены вот для чего. Основная координата спрайта, по которой он располагается на экране - его верхний левый угол. Однако, я хочу , чтобы объект создавался с центром именно в той точке, которую я указал. Ну а дальше пусть его система обрабатывает, как ей удобней. Мне же удобней ориентироваться по центрам объектов, а не по их углу. Строки 17 и 18 - "дельта" - это разница между координатой верхнего левого угла спрайта и верхнего левого угла тела. Соответственно по Х и по У. Используются в функции "сброса координаты тела", то есть выравнивания центра тела и центра спрайта. Сама функция - в строчках 40-43. Строка 22 - нужна будет для обработки столкновений. Возвращает модуль вектора скорости. Строчки 31 и 32 возможно не нужны. Код писался три дня назад, не все помню. Надо бы выяснить и поправить. Строка 33 - проверка "вылетания" объекта за экран. Строка 35 - проверка "жив ли объект" Строки 45 и 49 - убивалки объекта. Хотя... если в строку 45 подать отрицательное значение, то здоровье объекта увеличится. Теперь обещанный код класса BodyRectangle:
package ru.sapfil.AETest2; import org.andengine.entity.shape.RectangularShape; import org.andengine.opengl.shader.PositionColorTextureCoordinatesShaderProgram; import org.andengine.opengl.vbo.IVertexBufferObject; class BodyRectangle extends RectangularShape{ public BodyRectangle(float pX, float pY, float pWidth, float pHeight) { super(pX, pY, pWidth, pHeight, PositionColorTextureCoordinatesShaderProgram.getInstance()); // TODO Auto-generated constructor stub } public boolean Intersect(BodyRectangle secondBody){ return this.collidesWith(secondBody); } public IVertexBufferObject getVertexBufferObject() { // TODO Auto-generated method stub return null; } @Override protected void onUpdateVertices() { // TODO Auto-generated method stub } };Пришлось унаследовать дочерний класс от RectangularShape, потому что родительский класс - интерфейс. А экземпляр интерфейса создать нельзя. Зачем нужны две последние функции, унаследованные от родителя - я не знаю. А жаль... Теперь самый интересный класс. Назвал я его Intersector, хотя можно было назвать Collider, но уж тут кому как нравится. К тому же моя обработка упрощенная и иногда идет в разрез с простейшими законами физики. Об этом потом, а пока вот код класса:
package ru.sapfil.AETest2; import android.annotation.SuppressLint; import android.util.Log; public class Intersector{ public void CollWithSingleIntersect (BasicObjectsCollection op1, int healthToReduce1, BasicObject op2, int healthToReduce2){ this.CollWithSingleIntersect(op1, healthToReduce1, op2, healthToReduce2, false); } public void CollWithSingleIntersect (BasicObjectsCollection op1, int healthToReduce1, BasicObject op2, int healthToReduce2, boolean repulsion){ for (int i = 0; i < op1.GetCount(); i++) if(op1.mList.get(i).mBody.Intersect(op2.mBody)) { HealthReducer(op1.mList.get(i), healthToReduce1); HealthReducer(op2, healthToReduce2); if (repulsion) Repulse(op1.mList.get(i), op2); }; } private void HealthReducer(BasicObject patient, int healthToReduce){ switch (healthToReduce) { case -1: {patient.Kill(); break;} case 0: break; default: patient.ReduceHealth(healthToReduce); } } @SuppressLint("FloatMath") private void Repulse(BasicObject op1, BasicObject op2) { float v = (op1.GetAbsSpeed() + op2.GetAbsSpeed())/2; float deltaX = ((op2.getX()+op2.getRotationCenterX()) - (op1.getX()+op1.getRotationCenterX())); float deltaY = ((op2.getY()+op2.getRotationCenterY()) - (op1.getY()+op1.getRotationCenterY())); double angle = Math.atan2(deltaX,deltaY); op1.SetSpeed( -v * (float)Math.sin(angle), -v * (float)Math.cos(angle)); op2.SetSpeed( v * (float)Math.sin(angle), v * (float)Math.cos(angle)); } }Вот иллюстрация:

package ru.sapfil.AETest2; import org.andengine.engine.camera.Camera; import org.andengine.entity.particle.emitter.IParticleEmitter; import org.andengine.opengl.texture.region.ITextureRegion; import org.andengine.opengl.vbo.VertexBufferObjectManager; class Hero extends BasicObject implements IParticleEmitter{ // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== private float maxSpeed = 100; // =========================================================== // Constructors // =========================================================== Hero(final ITextureRegion mHeroFaceTextureRegion, VertexBufferObjectManager pVertexBufferObjectManager, Camera mCamera){ super(mCamera.getWidth()/2, mCamera.getHeight()-2*mHeroFaceTextureRegion.getHeight(), mHeroFaceTextureRegion, pVertexBufferObjectManager, 0, 0, 52, 52, 100, mCamera); } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== public void getPositionOffset(float[] pOffset) { pOffset[VERTEX_INDEX_X] = this.getX() + 15.5f; pOffset[VERTEX_INDEX_Y] = this.getY() + 46f; } // =========================================================== // Methods // =========================================================== public void heroUpdate (float dt, float acs_dx, float acs_dy){ this.SetSpeed(this.GetSpeedX() -acs_dx * maxSpeed *dt , this.GetSpeedY() + acs_dy * maxSpeed * dt); this.Update(dt); this.StayInCamera(); } // =========================================================== // Inner and Anonymous Classes // =========================================================== }Думаю на этом все. Что будет дальше я пока не знаю. Надо бы изучать класс Level или как-либо еще попытаться создать уровень с заранее заданными параметрами, а не случайно создаваемыми астероидами.
Здравствуйте!
ОтветитьУдалитьОчень хороший блог, побольше бы таких, спасибо.
Как скоро можно ждать новые уроки?
И не могли бы вы оставить ICQ/Skype?
Привет.
УдалитьЯ решил заморозить это проект, раз уж никто не заходил сюда.
Но если хотя бы одному человеку это надо - я готов продолжить работу. :)
Скайп Sapfil2.
Этот комментарий был удален автором.
ОтветитьУдалитьЗдравствуйте! Не могли бы вы выложить полный проект именно этого примера? При вызове что-то делаю неправильно и проект постоянно крашится..
ОтветитьУдалить