Я всегда так пишу программы. От частного к общему. Главная причина в том, что я заранее не знаю, куда иду. Ибо зачастую путь и есть цель. (О как!) Но многие вполне справедливо имеют право закидывать меня помидорами и сыпать не менее красивыми цитатами. (Вроде "Не зная куда идешь - придешь в никуда.") Что ж - у каждого своя голова на плечах. Но тот, кто прочитает мой блог и, возможно, будет копировать мой код - вместе со мной придет к моему рефакторингу.
Теперь ближе к делу. Есть у нас в игре пока два вида объектов. Герой и Астероид. И у них много общего. А потом появятся еще объекты, и под каждый пришлось бы писать код с нуля... если бы не рефакторинг, который мы сейчас и проведем! (Кода будет больше чем в предыдущих постах!)
Вот код некоторого базового объекта, от которого мы позже унаследуем наши астероиды, героя и прочие вкусности. Кроме того, обратите внимание на поле mBody. Оно необходимо для физического взаимодействия между объектами. В этом посте мы о нем говорить не будем. Вы можете либо выпилить его из кода, либо прочитать следующий пост на эту тему, либо написать свой класс BodyRectangle.
package ru.sapfil.AETest2; import org.andengine.engine.camera.Camera; import org.andengine.entity.sprite.Sprite; import org.andengine.opengl.texture.region.ITextureRegion; import org.andengine.opengl.vbo.VertexBufferObjectManager; public class BasicObject extends Sprite implements GlobalConstants{ // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== public float vx, vy; private float deltaX, deltaY; private float health; public BodyRectangle mBody; final Camera mCamera; // =========================================================== // 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; }; // =========================================================== // Getter & Setter // =========================================================== public float GetSpeedX(){return vx;} public float GetSpeedY(){return vy;}; public void ReverseX(){ vx *= -1;}; public void ReverseY(){ vy *= -1;}; public void SetSpeed(float vx, float vy){ this.vx = vx; this.vy = vy; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== // 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; } // returns sprite to the screen if it is partially offscreen public void StayInCamera(){ if (this.getX() < 0) this.setX(0); if (this.getX() > mCamera.getWidth() - this.getWidth()) this.setX(mCamera.getWidth() - this.getWidth()); if (this.getY() < 0) this.setY(0); if (this.getY() > mCamera.getHeight() - this.getHeight()) this.setY(mCamera.getHeight() - this.getHeight()); resetBody(); } 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; }; // =========================================================== // Inner and Anonymous Classes // =========================================================== }Как видим, функция Update() возвращает разные значения.
По традиции возврат значения 0 обозначает, что все ОК. Ну а другие значения мы будем использовать для "нестандартных" состояний объекта.
Если функция вернула 1, значит объект на экране, но его здоровье равно или ниже нуля. Например, наш герой стрелял-стрелял в астероид и наконец его "убил", значит нужно не просто убрать его из программы, но и создать красивый взрыв на его месте. Или вот например дадим мы герою бластер, стреляющий сгустками плазмы. Каждый такой сгусток - такой же объект. При попадании в цель он будет отнимать у цели чуток здоровья, при этом сам он будет полностью терять свое здоровье. Возврат единицы позволит нам создать в месте попадания пули в объект - какой-нибудь красивый эффектик.
2 обозначает, что объект (тот же астероид) улетел за экран и его надо убирать из программы, но при этом совсем не нужно красивых эффектов (к тому же сильно жрущих ФПС). Геройские пули, так же улетающие за экран - не нуждаются в спец-эффектах перед удалением.
Функция StayInCamera() нужна нам в основном для героя, но мало ли что - пусть она будет в базовом классе.
Класс полностью соответствует нашим пожеланиям к астеройдам, по-этому Asteroid.java удален из моего пакета. Hero.java теперь имеет вот такой вид:
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, 40, 100, mCamera); } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== public void getPositionOffset(float[] pOffset) { pOffset[VERTEX_INDEX_X] = this.getX() + 13f; pOffset[VERTEX_INDEX_Y] = this.getY() + 40f; } // =========================================================== // Methods // =========================================================== public void heroUpdate (float dt, float acs_dx, float acs_dy){ this.SetSpeed(-acs_dx * maxSpeed, acs_dy * maxSpeed); this.Update(dt); this.StayInCamera(); } // =========================================================== // Inner and Anonymous Classes // =========================================================== }Теперь нам нужен класс, занимающийся обработкой группы базовых объектов. Пока я написал простой класс. Он будет создавать идентичные объекты. Позже, конечно, нужно будет переписать его так, чтобы можно было создавать разные, но подобные объекты. Например, те же астероиды разных размеров и форм. Но пока есть вот такой класс:
package ru.sapfil.AETest2; import java.util.ArrayList; import org.andengine.engine.camera.Camera; import org.andengine.entity.Entity; import org.andengine.opengl.texture.region.ITextureRegion; import org.andengine.opengl.vbo.VertexBufferObjectManager; public class BasicObjectsCollection extends Entity implements GlobalConstants{ // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== public final ArrayListТут хотелось бы сказать вот о чем. Весь AndEnginre сильно завязан на свой класс Entity. Практически все объекты (Scene, Sprite и пр.) - его наследники. У Entity есть 2 важных метода - AttachChild(Entity) и DetachChild(Entity). Во-первых, добавлять детишек можно сколько угодно. Во вторых, каждое дитё так же может иметь своих детишек. В третьих, все детишки всех поколений отправляются на рэндер. (Конечно, в том случае, если их прародитель добавлен к сцене методом AttachChild() ). В нашем классе есть список ArrayList. Я где-то уже писал про него. Так вот при создании нового объекта нам надо не только добавить его в контейнер ArrayList, но еще и "прогнать" его через метод AttachChild(). С другой стороны, при удалении объекта надо провернуть все в обратном порядке - сначала detachChild(), а потом mList.remove(i).mList = new ArrayList (); private int count; private ITextureRegion mTextureRegion; private VertexBufferObjectManager mVertexBufferObjectManager; private Camera mCamera; // =========================================================== // Constructors // =========================================================== BasicObjectsCollection(ITextureRegion mTextureRegion, VertexBufferObjectManager pVertexBufferObjectManager, Camera camera){ this.mCamera = camera; this.mTextureRegion = mTextureRegion; this.mVertexBufferObjectManager = pVertexBufferObjectManager; count = 0; } // =========================================================== // Getter & Setter // =========================================================== public int GetCount(){ return count;} // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== public void AddNewObject (float x, float y, float vx, float vy, float w, float h, float health){ mList.add(new BasicObject(x, y, mTextureRegion, mVertexBufferObjectManager, vx, vy, w, h, health, mCamera)); this.attachChild(mList.get(count)); count++; }; public byte Update (float dt){ for (int i = 0; i < count; i++) { switch (mList.get(i).Update(dt)){ case 2: case 1: {this.detachChild(mList.get(i)); mList.remove(i); count--; break;} case 0: {}; default: {}; }; } return 0; }; };
Можно еще вот что сделать. Сейчас в моем коде в сцену сваливается все вперемешку. Однако можно создать класс Layer, унаследованный от Entity. И добавить в сцену слои, а уже в слои добавлять детишек. Я это не придумывал, а подсмотрел в примерах к тому же AndEngine (Конкретно в игрушке Snake).
Забыл сказать - в функции Update() пока что нет различных решений для объектов, вернувших 1 или 2 в своем Update(). И те и те просто убираются со сцены.
Пока все. Чуть позже будет пост про то, как заставить объекты взаимодействовать. Так же я еще не разбирался со SpriteBatch и SpriteGroup. Эти две волшебные штуки должны помочь улучшить производительность при обработке одинаковых спрайтов.
Комментариев нет:
Отправить комментарий