суббота, 7 июля 2012 г.

Делаем облака. ParallaxSprite.

Все что описано в этом сообщении, касается движка AndEngine версии GLES2, под версию GLES1 не проверялось.

Мне встречалась попытка реализации такого класса на stackoverflow.com, но приведенный там код у меня не заработал и сейчас, к сожалению,  я не могу дать на него прямую ссылку.

Итак, к делу.
В AndEngine есть великолепный класс - AutoParallaxBackground который позволяет сделать непрерывно движущуюся картинку бэкграунда. Конечно, картинка должна быть согласована, т.е. начало картинки должно совпадать с концом (чисто визуально), иначе, все получится, но будет виден разрыв. Благодаря такому поведению спрайта, создается впечатление глубины сцены, а если его сделать многослойным - получается очень красиво и профессионально.Ну про AutoParalaxBackground мы можем подробно узнать из AndEngine examples.
А, что делать, если нам нужно такое же "поведение" спрайта, но не на бэкграунде, а поверх сцены? Например, сделать непрерывно движущиеся облака поверх солнца или там луны, и при этом к объекту, который перекрывается облаками нам нужен доступ как к обычному спрайту (это максимально развязывает руки дизайнеру и разработчику), К сожалению, AndEngine не предоставляет такого класса... Хотя почему к сожалению? Есть повод пораскинуть мозгами! (а у меня есть тема для блога) Короче, начнем раскидывать:-)

public class ParallaxSprite extends Sprite {

    private float mParallaxSpeed;
    private float mOffsetX = 0;

    public ParallaxSprite(float pX, float pY, float mParallaxSpeed, ITextureRegion pTextureRegion, VertexBufferObjectManager pVertexBufferObjectManager) {
        super(pX, pY, pTextureRegion, pVertexBufferObjectManager);

        this.mParallaxSpeed = mParallaxSpeed;
        this.mOffsetX = pX - (pX * mParallaxSpeed);
    }

    @Override
    public void onManagedUpdate(float pSecondsElapsed){
     super.onManagedUpdate(pSecondsElapsed);
     this.mOffsetX += this.mParallaxSpeed * pSecondsElapsed; 
    }
    
    @Override
    protected void onManagedDraw(GLState pGLState, Camera pCamera) {
     pGLState.pushModelViewGLMatrix();
        {
         final float shapeWidthScaled = this.getWidthScaled();
         final float cameraWidth = pCamera.getWidth();
            float baseOffsetX = (this.mOffsetX * this.mParallaxSpeed)% shapeWidthScaled;

            while(baseOffsetX > 0) {
             baseOffsetX -= shapeWidthScaled;
   }
            
            pGLState.translateModelViewGLMatrixf(baseOffsetX, 0, 0);
            
            float currentMaxX = baseOffsetX;
   
   do {
    
    this.preDraw(pGLState, pCamera);
             this.draw(pGLState, pCamera);
             this.postDraw(pGLState, pCamera);
    pGLState.translateModelViewGLMatrixf(shapeWidthScaled, 0, 0);
    currentMaxX += shapeWidthScaled;
   } while(currentMaxX < cameraWidth);
        }
        pGLState.popModelViewGLMatrix();
    }
    
    public void setSpeed(float pSpeed){
     this.mParallaxSpeed = pSpeed;
    }
    
    public float getSpeed(float pSpeed){
     return this.mParallaxSpeed;
    }
}
Использование:
ParallaxSprite spriteClouds1 = new ParallaxSprite(0, 0, 5.0f, mRegionClouds,
    this.getVertexBufferObjectManager())

0,0 - это координаты левого верхнего угла спрайта относительно сцены.
mRegionClouds - это регион текстуры нашего спрайта.
this.getVertexBufferManager() - это так надо ;-)
Вот и все.
Что мы натворили: перекрыли метод отрисовки стандартного спрайта немного измененным кодом AutoParallaxBackground. Если интересуют какие именно отличия - велком в исходный код AutoParallaxBackground.
Еще тройка интересных, на мой взгляд, замечаний:
1.Здесь реализовано горизонтальное движение, а если хотим вертикальное? Посмотрите на этот незамысловатый код внимательней и увидите, что его ничего не стоит переделать и двигать по оси Y, причем делать это в зависимости от ситуации. Зачем двигать спрайт вертикально? Чтобы сделать джампер круче Doodle например ;-)
2.Наши облака представляют собой два спрайта имеющих одну и ту же текстуру. Они занимают всю сцену и имеют разные скорости, таким образом, создается впечатление, что облака постоянно меняются, хотя на самом деле две одинаковых картинки движутся относительно друг друга с разной скоростью.
3. Вы можете изменить направление движения поставив третий параметр < 0.

Кроме того, эта штука реализована у нас в обоях. Добро пожаловать и прокомментировать!

9 комментариев:

  1. Да, много чудовищных терминов конечно, ну а так нормально....Вперёд к покорению интернета.

    ОтветитьУдалить
  2. Привет. У тебя глюк с нумерацией строк. Он проявляется в Хроме с недавнего времени. Проверь. Вчера я нашел костыль на эту тему. Если надо - обращайся...

    ОтветитьУдалить
  3. Sapfil, привет! А как с тобой связаться?

    ОтветитьУдалить
    Ответы
    1. Если по поводу глюков с подсветкой, то я написал небольшой пост на эту тему.
      http://sapfil-proger.blogspot.com/2012/09/syntaxhighlighter.html
      Надеюсь, что это сэкономит время таким как я :).

      Если просто связаться по какому-либо другому поводу - можно в скайпе "Sapfil2"
      Но учиться у меня нечему. Я вот пытаюсь сейчас разобраться с второй версией AndEngine. И совершенно не понимаю, что такое "VertexBufferObjectManager". :(

      Удалить
    2. ага. именно глюки с подсветкой, а по поводу буфермэнеджера - видимо обслуживать вершины объекта может не только менеджер активити, но и какой-то другой. по сути он представляет собой класс с двумя arraylist в одном из которых загруженные объекты, а в другом которые будут выгружены. Вообще,имхо, его вызов остался там, потому, что его забыли или поленились убрать. ;)

      Удалить
  4. Ага. Додумался прочесть твой пост.
    "this.getVertexBufferManager() - это так надо ;-)"
    Если я правильно понял, движок сам создает этот шайтан-буфер, и к нему просто нужно обращаться через "this"?

    Не все понял в твоем ответе про буферменеджер - слаб я еще в яве. :( Но хочу сказать одно - в первой версии движка AndEngine этой фигни не было. И она намеренно добавлена во вторую. Значит оно именно кому-то очень надо. Но это надо изучать OpenGL, видимо...

    ОтветитьУдалить
  5. Мы пишем игры еще с первой версии, и в курсе этих изменений. Тем не менее, со 2-й версией работать комфортней =) This - это ссылка объекта на самого себя. Если нужно создать спрайт в другом классе, то нужно обратиться к твоей главной активити. Я это делаю создавая специальную ссылку Instance которую можно использовать в любом месте проекта, и вызывать любые (не приват) методы активити и поля.

    ОтветитьУдалить
    Ответы
    1. Дада. Про this я знаю из С++. Там опыта побольше у меня :)
      Ок. Думаю твой совет про Instance мне потребуется в ближайшее время.

      П.с. А чего не правишь разметку кода? :)

      Удалить
    2. пока некогда) как раз рисуем новую игрушку. как будет повод для статьи так и исправлю уже везде)

      Удалить