Всем привет. Огромное спасибо Евгению Shiler за то, что вернул меня в работу. В первой части данного поста выложу то, что уже сделано, а затем займусь оптимизацией.
За видео прошу прощения - снимал, чем мог. Чуть позже, возможно, переделаю.
А сделано вот что - добавлена некоторая контрольная точка впереди кораблика. И если эта точка пересекается с астероидом - у кораблика включается "плавильный лучик". Выглядит это, как на картинке. Лучики сделаны обычной кадровой анимацией в 3D-MAXе.
1. Спрайт луча разделен на 2 части - левую и правую. Для удобства "растягивания". Теперь если луч включен - он всегда направлен в центр цели.
2. К спрайту луча добавлена партикл-система, с ней все выглядит более симпатично. Тут возникла одна важная особенность. Оказалось более удобным подключать партикл-систему напрямую в сцену, а не делать систему "дочкой" какого-то промежуточного "родителя". Если она становится дочкой во втором или третьем поколении, то трудно отслеживать ее координаты. Ведь у каждого родителя своя система координат и координаты любого "ребенка" - локальные, относительно спрайта родителя. И вот тут начинаются костыли, "добавки" и прочие некрасивости. По-этому эмиттером мы назначаем какой-нибудь объект, а вот attachChicd() делаем не к эмиттеру, а к сцене. ВНИМАНИЕ! В этом месте я бы хотел услышать фидбэк от тех, кто согласен\не согласен.
3. Вообще вся система луча вынесена в отдельный Java-файл HeroGun. Причем он сам по себе получился довольно громоздкий (150 строк). В нем реализована система "слежения" за целью. То есть пушка сама проверяет - есть у нее цель или нет. При этом выдает об этом сообщения наружу. А снаружи, в основном игровом цикле - эти сообщения получают. И если у пушки уже есть цель, то в этом прогоне не тратится время на новый поиск цели. кроме того наконец-то сделана и опробована система нанесения урона пушкой цели.
4. Создание пушки вынесено в отдельный метод в файле героя. Дело в том, что пока приходится передавать все эти текстурные регионы "в глубину", чтобы там, "на глубине" создавались спрайты. Мне это не нравится и я все еще лелею надежду что-нибудь с этим сделать. Но до тех пор, чтобы хоть как-то разгрузить громоздкий конструктор - я решил разделить его на два - собственно конструктор героя и метод создания пушки, вызывающий конструктор пушки. Думаю все. Теперь код. Вот код "пушки"
А сделано вот что - добавлена некоторая контрольная точка впереди кораблика. И если эта точка пересекается с астероидом - у кораблика включается "плавильный лучик". Выглядит это, как на картинке. Лучики сделаны обычной кадровой анимацией в 3D-MAXе.
// Для начала прикрутим анимированный спрайт луча в нашему герою.
// И добавим парочку управляющих функций.
Hero(final ITextureRegion mHeroFaceTextureRegion, ITiledTextureRegion mHeroRayTextureRegion,
VertexBufferObjectManager pVertexBufferObjectManager, Camera mCamera){
super(mCamera.getWidth()/2, mCamera.getHeight()-2*mHeroFaceTextureRegion.getHeight(),
mHeroFaceTextureRegion, pVertexBufferObjectManager,
0, 0, HERO_BODY_WIDTH, HERO_BODY_HEIGHT, HERO_BODY_HEALTH, mCamera);
this.mHeroRaySprite = new AnimatedSprite(HERO_RAY_OFFSET_X, HERO_RAY_OFFSET_Y,
mHeroRayTextureRegion, pVertexBufferObjectManager);
this.attachChild(this.mHeroRaySprite);
this.mHeroRaySprite.animate(100);
}
// луч сразу включаем и анимируем, но его видимостью мы будем управлять.
// Эти функции возвращают координаты точки, на которую направлен луч.
// Константы определим выше, чтобы было удобнее тестить проект.
public float GetRayX(){
return (this.getX() + HERO_RAY_OFFSET_X); }
public float GetRayY(){
return (this.getY() + HERO_RAY_OFFSET_Y); }
// "Включение/выключение луча" На самом деле луч есть всегда, меняется только его видимость.
public void SetRayOn(){
this.mHeroRaySprite.setVisible(true); }
public void SetRayOff(){
this.mHeroRaySprite.setVisible(false); }
// ---------------------------------------------------------------------------
// Вот такая функция нам понадобится в нашем Intersector
public int CollContainsPoint(BasicObjectsCollection op, float pX, float pY){
for (int i = 0; i < op.GetCount(); i++)
if (op.mList.get(i).mBody.contains(pX, pY))
return i;
return -1;
};
// ---------------------------------------------------------------------------
// И вот так мы будем использовать это в MainActivity:
// ...some code...
private ITiledTextureRegion mHeroRayTextureRegion;
// ...some code...
protected void onCreateResources()
{
this.mHeroRayTextureRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(
this.mHeroTexture, this, "Hero_ray2.png", 0, 64, 5, 3);
// ...some code...
}
// А этот кусочек кода добавим в нашу еже-кадровую процедуру, сразу за функцией столкновения.
// ...some code...
int i = mIntersector.CollContainsPoint(mAsteroids, hero.GetRayX(), hero.GetRayY());
if (i > -1) {
hero.SetRayOn();
mAsteroids.ModifySpeedByID(i, 0.8f, pSecondsElapsed);
hero.ModifySpeed(0.8f, pSecondsElapsed);
}
else hero.SetRayOff();
// ...some code...
// ---------------------------------------------------------------------------
// Вот эту функцию добавим в BasicObject
public void ModifySpeed(float procent, float dt){
this.vx *= (procent - dt)/ procent;
this.vy *= (procent - dt)/ procent;
}
// ---------------------------------------------------------------------------
// Если что-то забыл - в коментах укажите.
Вообще-то нет ничего трудного. К объекту героя аттачим дочерний объект с анимированым спрайтом лучика. Спрайт видимый или невидимый в зависимости от того - если у лучка цель. Наличие цели определяется Intersector-ом. Там кстати есть еще функция ModifySpeed - она понадобилась просто для удобства тестинга. Хотя, возможно она останется в итоговом варианте. ((( прошло несколько дней ))) Я думаю, что не стоит засорять пост множеством кода. Дело в том, что чего-то принципиально нового в нем мало. Сделаем так. Я сначала опишу все на словах, а ниже приведу коды для тех, кому вдруг это будет интересно. 1. Спрайт луча разделен на 2 части - левую и правую. Для удобства "растягивания". Теперь если луч включен - он всегда направлен в центр цели.
2. К спрайту луча добавлена партикл-система, с ней все выглядит более симпатично. Тут возникла одна важная особенность. Оказалось более удобным подключать партикл-систему напрямую в сцену, а не делать систему "дочкой" какого-то промежуточного "родителя". Если она становится дочкой во втором или третьем поколении, то трудно отслеживать ее координаты. Ведь у каждого родителя своя система координат и координаты любого "ребенка" - локальные, относительно спрайта родителя. И вот тут начинаются костыли, "добавки" и прочие некрасивости. По-этому эмиттером мы назначаем какой-нибудь объект, а вот attachChicd() делаем не к эмиттеру, а к сцене. ВНИМАНИЕ! В этом месте я бы хотел услышать фидбэк от тех, кто согласен\не согласен.
3. Вообще вся система луча вынесена в отдельный Java-файл HeroGun. Причем он сам по себе получился довольно громоздкий (150 строк). В нем реализована система "слежения" за целью. То есть пушка сама проверяет - есть у нее цель или нет. При этом выдает об этом сообщения наружу. А снаружи, в основном игровом цикле - эти сообщения получают. И если у пушки уже есть цель, то в этом прогоне не тратится время на новый поиск цели. кроме того наконец-то сделана и опробована система нанесения урона пушкой цели.
4. Создание пушки вынесено в отдельный метод в файле героя. Дело в том, что пока приходится передавать все эти текстурные регионы "в глубину", чтобы там, "на глубине" создавались спрайты. Мне это не нравится и я все еще лелею надежду что-нибудь с этим сделать. Но до тех пор, чтобы хоть как-то разгрузить громоздкий конструктор - я решил разделить его на два - собственно конструктор героя и метод создания пушки, вызывающий конструктор пушки. Думаю все. Теперь код. Вот код "пушки"
package ru.sapfil.AETest2;
class HeroGun extends Entity /*implements IParticleEmitter*/{
//===========================================================
// Constants
// ===========================================================
// ray sprites offset relative to parent sprite
private final static float HERO_LEFT_RAY_OFFSET_X = 10;
private final static float HERO_RIGHT_RAY_OFFSET_X = 32;
private final static float HERO_SPRITE_RAY_OFFSET_Y = -30;
// ray "fusion point" offset
private final static float HERO_RAY_OFFSET_Y = 6;
// ray "active area" size - if target intersect it - ray attacks target
private final static float HERO_RAY_AREA_HALF_SIZE_Y = 2.5f;
private final static float HERO_RAY_AREA_HALF_SIZE_X = 5.0f;
// ray power per second
private final static int RAY_POWER = 50;
// ===========================================================
// Fields
// ===========================================================
// ray "active area"
private BodyRectangle RayArea;
// sprites for left and right parts of the ray
private AnimatedSprite mHeroLeftRaySprite;
private AnimatedSprite mHeroRightRaySprite;
// "fusion" particle system
public SpriteParticleSystem gunPS;
// emitter for particle system
private PointParticleEmitter mPointParticleEmitter;
// ray has "lock-on-target" system
public BasicObject mTarget;
// ===========================================================
// Constructors
// ===========================================================
HeroGun(float pX, float pY, ITiledTextureRegion mHeroLeftRayTextureRegion,
ITiledTextureRegion mHeroRightRayTextureRegion,
ITextureRegion mParticlesTextureRegion,
VertexBufferObjectManager pVertexBufferObjectManager, Camera mCamera) {
// Creating RayArea
RayArea = new BodyRectangle (pX-HERO_RAY_AREA_HALF_SIZE_X,
pY - HERO_RAY_AREA_HALF_SIZE_Y + HERO_SPRITE_RAY_OFFSET_Y,
HERO_RAY_AREA_HALF_SIZE_X * 2, HERO_RAY_AREA_HALF_SIZE_Y * 2);
// Creating sprites
this.mHeroLeftRaySprite = new AnimatedSprite(HERO_LEFT_RAY_OFFSET_X, HERO_SPRITE_RAY_OFFSET_Y,
mHeroLeftRayTextureRegion, pVertexBufferObjectManager);
this.mHeroRightRaySprite = new AnimatedSprite(HERO_RIGHT_RAY_OFFSET_X, HERO_SPRITE_RAY_OFFSET_Y,
mHeroRightRayTextureRegion, pVertexBufferObjectManager);
this.attachChild(this.mHeroLeftRaySprite);
this.attachChild(this.mHeroRightRaySprite);
this.mHeroLeftRaySprite.animate(100);
this.mHeroRightRaySprite.animate(100);
this.mHeroLeftRaySprite.setScaleCenter(0, this.mHeroLeftRaySprite.getHeight());
this.mHeroRightRaySprite.setScaleCenter(this.mHeroRightRaySprite.getWidth(),
this.mHeroRightRaySprite.getHeight());
// Creating particle system
mPointParticleEmitter = new PointParticleEmitter(pX, pY);
gunPS = new SpriteParticleSystem(mPointParticleEmitter, 20, 40, 50, mParticlesTextureRegion , pVertexBufferObjectManager);
gunPS.addParticleInitializer(new VelocityParticleInitializer(-100.0f, 100.0f, -100.0f, 100.0f));
gunPS.addParticleInitializer(new ExpireParticleInitializer(0.3f, 0.8f));
gunPS.addParticleInitializer(new BlendFunctionParticleInitializer(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE));
gunPS.addParticleModifier(new ColorParticleModifier(0.0f, 0.3f, 1.0f, 0.0f, 1.0f, 0.3f, 1.0f, 1.0f));
gunPS.addParticleModifier(new OffCameraExpireParticleModifier(mCamera));
gunPS.addParticleModifier(new ScaleParticleModifier(0.0f, 0.3f, 1.0f, 0.1f, 1.0f, 0.1f));
gunPS.addParticleModifier(new AlphaParticleModifier(0.3f, 0.8f, 1.0f, 0.0f));
// particle system will be attached to the scene
// to have simple access to global coords
// if we attach it here - we will be compelled to use
// big constructions like this: GetParent().GetParent().GetX()
this.SetRayOff();
Log.d("Sapfil_Log", "HeroGun created");
}
// ===========================================================
// Getter & Setter
// ===========================================================
public void SetTarget(IEntity pTarget){
mTarget = (BasicObject) pTarget;
if (pTarget != null)
Log.d("Sapfil_Log", "HeroGun: Target set");
}
public BodyRectangle GetRayArea(){
return RayArea;
};
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
// main updating function
public int Update(float dt){
this.UpdateRayArea(this.getParent().getX() + this.getParent().getScaleCenterX(), this.getParent().getY());
// check - if target is out of "ray_active_area"
if (mTarget != null && !mTarget.mBody.Intersect(RayArea)){
mTarget = null;
Log.d("Sapfil_Log", "HeroGun: Target lost");
}
// check - if target is dead - we must release it
if (mTarget != null && mTarget.GetHealth() <= 0){
mTarget = null;
Log.d("Sapfil_Log", "HeroGun: Target killed");
}
// if target is alive and is "locked_on"
if (mTarget != null)
{
mPointParticleEmitter.setCenter(mTarget.getX(), mTarget.getY());
this.SetRayOn();
this.SetRayScale(mTarget.getX() + mTarget.getScaleCenterX() - this.getParent().getX(),
mTarget.getY() + mTarget.getScaleCenterY() - this.getParent().getY());
mTarget.ReduceHealth((int)(RAY_POWER * dt));
return 1;
}
else{
this.SetRayOff();
return 0;
}
}
private void SetRayOn(){
this.mHeroLeftRaySprite.setVisible(true);
this.mHeroRightRaySprite.setVisible(true);
this.gunPS.setParticlesSpawnEnabled(true);}
private void SetRayOff(){
this.mHeroLeftRaySprite.setVisible(false);
this.mHeroRightRaySprite.setVisible(false);
this.gunPS.setParticlesSpawnEnabled(false);}
// Ray scaling - visual stretching of ray sprites
private void SetRayScale(float target_X, float target_Y){
this.mHeroLeftRaySprite.setScale((target_X-HERO_LEFT_RAY_OFFSET_X)/mHeroLeftRaySprite.getWidth(),
-(target_Y/(-HERO_SPRITE_RAY_OFFSET_Y - HERO_RAY_OFFSET_Y)));
this.mHeroRightRaySprite.setScale(
(HERO_RIGHT_RAY_OFFSET_X + mHeroRightRaySprite.getWidth() - target_X)/mHeroLeftRaySprite.getWidth(),
-(target_Y/(-HERO_SPRITE_RAY_OFFSET_Y - HERO_RAY_OFFSET_Y)));
};
private void UpdateRayArea(float pX, float pY){
RayArea.setX(pX - HERO_RAY_AREA_HALF_SIZE_X);
RayArea.setY(pY + HERO_SPRITE_RAY_OFFSET_Y + HERO_RAY_OFFSET_Y - HERO_RAY_AREA_HALF_SIZE_Y);
};
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
};
Вот код героя: package ru.sapfil.AETest2;
class Hero extends BasicObject implements IParticleEmitter{
// ===========================================================
// Constants
// ===========================================================
private final static float HERO_BODY_WIDTH = 52;
private final static float HERO_BODY_HEIGHT = 52;
private final static float HERO_BODY_HEALTH = 100;
private final static float HERO_ENGINE_PARTICLES_OFFSET_X = 15.5f;
private final static float HERO_ENGINE_PARTICLES_OFFSET_Y = 46f;
// ===========================================================
// Fields
// ===========================================================
private float maxSpeed = 100;
public HeroGun mHeroGun;
// ===========================================================
// Constructors
// ===========================================================
Hero(final ITextureRegion mHeroFaceTextureRegion,
VertexBufferObjectManager pVertexBufferObjectManager, Camera mCamera){
super(mCamera.getWidth()/2, mCamera.getHeight()-2*mHeroFaceTextureRegion.getHeight(),
mHeroFaceTextureRegion, pVertexBufferObjectManager,
0, 0, HERO_BODY_WIDTH, HERO_BODY_HEIGHT, HERO_BODY_HEALTH, mCamera);
Log.d("Sapfil_Log", "Hero created");
}
// ===========================================================
// Getter & Setter
// ===========================================================
public void CreateGun(ITiledTextureRegion mHeroLeftRayTextureRegion,
ITiledTextureRegion mHeroRightRayTextureRegion,
ITextureRegion mParticlesTextureRegion,
VertexBufferObjectManager pVertexBufferObjectManager, Camera mCamera)
{mHeroGun = new HeroGun(this.getX() + this.getWidth()/2, this.getY(),
mHeroLeftRayTextureRegion, mHeroRightRayTextureRegion,
mParticlesTextureRegion,
pVertexBufferObjectManager, mCamera);
this.attachChild(mHeroGun);
Log.d("Sapfil_Log", "HeroGun attached to hero");
}
public BodyRectangle GetRayArea(){
return mHeroGun.GetRayArea();
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
public void getPositionOffset(float[] pOffset) {
pOffset[VERTEX_INDEX_X] = this.getX() + HERO_ENGINE_PARTICLES_OFFSET_X;
pOffset[VERTEX_INDEX_Y] = this.getY() + HERO_ENGINE_PARTICLES_OFFSET_Y + 5;
}
// ===========================================================
// Methods
// ===========================================================
public int Update (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();
return this.mHeroGun.Update(dt);
}
}
Вот код основного цикла игры: scene.registerUpdateHandler(new IUpdateHandler() {
public void onUpdate(float pSecondsElapsed) {
// updating asteroids
switch (mAsteroids.Update(pSecondsElapsed)) {
case 0: {};
} // creating new asteriod
if (mAsteroids.GetCount() < 10){
mAsteroids.AddNewObject(mRandom.nextInt((int)sapCamera.getWidth()), -15, mRandom.nextInt(20)-10, mRandom.nextInt(30), 26, 26, 100);
};
int i = 0; //target number
switch (hero.Update(pSecondsElapsed, accellerometerSpeedY, accellerometerSpeedX))
{
case 0:{ //if heroGun has no target
i = mIntersector.CollWithAreaIntersect(mAsteroids, hero.GetRayArea());
if (i > -1) { //if intersection is found
hero.mHeroGun.SetTarget(mAsteroids.getChildByIndex(i));
}
else { hero.mHeroGun.SetTarget(null); }
break;}
case 1: { //if heroGun has target
mAsteroids.ModifySpeedByID(i, 0.8f, pSecondsElapsed);
hero.ModifySpeed(0.8f, pSecondsElapsed);
break;}
}
// hero with asteroids collision
mIntersector.CollWithSingleIntersect(mAsteroids, 0, hero, 0, true);
}
public void reset() {
// TODO Auto-generated method stub
}
});
п.с. В последнем коде ошибка - она нашлась уже после публикации поста. И я решил ее не исправлять тут :) Кто найдет - тому бублик! 

Комментариев нет:
Отправить комментарий