Всем привет. Огромное спасибо Евгению 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 } });п.с. В последнем коде ошибка - она нашлась уже после публикации поста. И я решил ее не исправлять тут :) Кто найдет - тому бублик!
Комментариев нет:
Отправить комментарий