Я всегда так пишу программы. От частного к общему. Главная причина в том, что я заранее не знаю, куда иду. Ибо зачастую путь и есть цель. (О как!) Но многие вполне справедливо имеют право закидывать меня помидорами и сыпать не менее красивыми цитатами. (Вроде "Не зная куда идешь - придешь в никуда.") Что ж - у каждого своя голова на плечах. Но тот, кто прочитает мой блог и, возможно, будет копировать мой код - вместе со мной придет к моему рефакторингу.
Теперь ближе к делу. Есть у нас в игре пока два вида объектов. Герой и Астероид. И у них много общего. А потом появятся еще объекты, и под каждый пришлось бы писать код с нуля... если бы не рефакторинг, который мы сейчас и проведем! (Кода будет больше чем в предыдущих постах!)
Вот код некоторого базового объекта, от которого мы позже унаследуем наши астероиды, героя и прочие вкусности. Кроме того, обратите внимание на поле 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 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;
};
};
Тут хотелось бы сказать вот о чем. Весь AndEnginre сильно завязан на свой класс Entity. Практически все объекты (Scene, Sprite и пр.) - его наследники. У Entity есть 2 важных метода - AttachChild(Entity) и DetachChild(Entity). Во-первых, добавлять детишек можно сколько угодно. Во вторых, каждое дитё так же может иметь своих детишек. В третьих, все детишки всех поколений отправляются на рэндер. (Конечно, в том случае, если их прародитель добавлен к сцене методом AttachChild() ). В нашем классе есть список ArrayList. Я где-то уже писал про него. Так вот при создании нового объекта нам надо не только добавить его в контейнер ArrayList, но еще и "прогнать" его через метод AttachChild(). С другой стороны, при удалении объекта надо провернуть все в обратном порядке - сначала detachChild(), а потом mList.remove(i).Можно еще вот что сделать. Сейчас в моем коде в сцену сваливается все вперемешку. Однако можно создать класс Layer, унаследованный от Entity. И добавить в сцену слои, а уже в слои добавлять детишек. Я это не придумывал, а подсмотрел в примерах к тому же AndEngine (Конкретно в игрушке Snake).
Забыл сказать - в функции Update() пока что нет различных решений для объектов, вернувших 1 или 2 в своем Update(). И те и те просто убираются со сцены.
Пока все. Чуть позже будет пост про то, как заставить объекты взаимодействовать. Так же я еще не разбирался со SpriteBatch и SpriteGroup. Эти две волшебные штуки должны помочь улучшить производительность при обработке одинаковых спрайтов.
Комментариев нет:
Отправить комментарий