вторник, 4 июня 2013 г.

ADT r22 и noClasDefFoundException

WTF! Обновился до последнего ADT и получил неработающий проект... Твою девизию! А так хотелось поработать, а не ковыряться во всяких ишуес, солюшенс и прочей херни, чтобы поставить одну единственную галочку. Б#;%:аааа!

Ситуация: Вы имеете работающий проект с использованием сторонних библиотек. После обновления до ADT r22 у вас в проекте возможно появится папка Abdroid Private Libraries, в этой папке будет валяться какая-нибудь библиотека (или несколько), в моем случае ей оказалась  android-support-v4.jar. Так вот, чтобы у вас не выскакивала вышеозначенная ошибка сделайте так: свойства проекта (properties)->Java Build Path-> Order&Export->ставим галочку на Android Private Libraries. И добавлю классическую фразу буржуйских программеров: It's work for me!

Мне одному кажется, что этот слоган висит на дверях лаборатории Google?

Надеюсь, что сэкономил читателю пару часов =)

понедельник, 6 мая 2013 г.

Немного о Render-to-Texture + ThermalVision Effect

Доброго, всем!
   
Если по простому, то Render-to-texture это технология OpenGL позволяющая рендерить содержимое экрана в специальную текстуру (не на экран)  а уже потом на экран. Возможно кто-то со мной не согласится, но выглядит это именно так. Что же это нам дает? Ответ на этот вопрос отчасти уже виден в предыдущих сообщениях: RippleEffect, MetaBlob. В примере RippleEffect шейдер применяется ко всем спрайтам находящимся на сцене, в примере MetaBlob рисуется геометрический шейдер также на всю сцену. Итак, по порядку.
Если вы внимательно разобрали приведенные выше примеры, то заметили, что для отображения картинки используется "специальный" спрайт. Именно, этот спрайт отвечает за отрисовку содержимого текущего фрейма. Нафиг он нам нужен? Очень нужен. =) Представьте себе, что вы захотели наложить шейдер не на конкретный спрайт а на какую-то область  экрана (или на весь экран), например для того, чтобы все что находится в этой области экрана видоизменялось согласно шейдеру. На картинке это может выглядеть так:




Также, вам может понадобится наложить несколько шейдеров и здесь тоже на помощь нам придет технология Render-to-texture.
Andengine дает нам возможность использовать данную технологию при помощи специального класса RenderTexture. Как и с обычной текстурой нам надо ее сначала создать, а потом передать ей графику. Создание RenderTexture почти ничем не отличается от создания обычной текстуры, разница заключается в "заполнении" текстуры графикой.
Создание текстуры:

mRenderTexture = new RenderTexture(this.getTextureManager(), renderTextureWidth, renderTextureHeight, PixelFormat.RGBA_8888);
mRenderTexture.init(pGLState);

Привязываем к спрайту:

mRenderTextureSprite = new UncoloredSprite(0f, 0f, TextureRegionFactory.extractFromTexture(mRenderTexture), getVertexBufferObjectManager());

Заполнение текстуры осуществляется в onDraw того контейнера (Entity), содержимое которого мы хотим передать в эту саму текстуру, размеры текстуры задаются в соответствии с размерами этого контейнера.
Заполнение текстуры выполняется в onDraw контейнера:

mRenderTexture.begin(pGLState, false, true, Color.TRANSPARENT);
   super.onDrawFrame(pGLState);
}
mRenderTexture.end(pGLState);


Теперь внесем немного ясности. Обратите внимание, что для отрисовки текстуры используется UncoloredSprite, а не просто Sprite. UncoloredSprite отличается тем, что у него отключены цветовые свойства, что приводит к повышению производительности при отрисовке. На самом деле вы можете использовать и простой спрайт. Заполнение текстуры можно вызвать в методе onDraw любого контейнера-Entity (Sprite, Rectangle, Mesh, SpriteGroup), а если мы хотим юзать весь экран, то лучше это сделать в onDrawFrame самого движка. Понятно, что для того, чтобы нам залезть в отрисовку, нам необходимо ее перекрыть. Вот как это может выглядеть для всего экрана (взято из указанных выше примеров):


//перекрыли создание движка

    @Override
    public Engine onCreateEngine(final EngineOptions pEngineOptions) {
        return new LimitedFPSEngine(pEngineOptions, 120) {
          
//перекрыли метод onDrawFrame
    @Override
    public void onDrawFrame(GLState pGLState)
          throws InterruptedException {
//если текстура еще не создана, то создаем ее
        if (!mRenderTextureInitialized) {
           initRenderTexture(pGLState);
           mRenderTextureInitialized = true;
        }

//заполняем текстуру содержимым фрейма
        mRenderTexture.begin(pGLState, false, true, Color.TRANSPARENT);
       { 
          super.onDrawFrame(pGLState);
       }
       mRenderTexture.end(pGLState);
     
//рисуем спрайт с нашей заполненной текстурой     
       pGLState.pushProjectionGLMatrix();
       pGLState.orthoProjectionGLMatrixf(0, mCamera.getSurfaceWidth(), 0, mCamera.getSurfaceHeight(), -1, 1);
       {
          mRenderTextureSprite.onDraw(pGLState, mCamera);
       }
       pGLState.popProjectionGLMatrix();
    }
   
    // процедура инициализации текстуры       
    private void initRenderTexture(GLState pGLState) {
                  //здесь размеры текстуры задали на всю поверхность
        mRenderTexture = new RenderTexture(this.getTextureManager(),mCamera.getSurfaceWidth(), mCamera.getSurfaceHeight(), PixelFormat.RGBA_8888);
        mRenderTexture.init(pGLState);
                  //размер спрайта также будет на весь экран
        mRenderTextureSprite = new UncoloredSprite(mCamera.getSurfaceWidth()/2, mCamera.getSurfaceHeight()/2, TextureRegionFactory.extractFromTexture(mRenderTexture), getVertexBufferObjectManager());
        //установили шейдер на спрайт  
        mRenderTextureSprite.setShaderProgram(ShockwaveShaderProgram.getInstance());    
    }
   };
 }



Для отдельно взятого спрайта нам надо перекрыть его метод onDraw.

Не трудно догадаться, что если нам понадобиться больше одного шейдера, то нам необходимы две RenderTexure и два спрайта через которые мы будем их отображать. Пример такой реализации под AndEngine GLES2 (не AnchorCenter! но адаптировать легко! вапще не сложно! ура!) лежит здесь.

На последок пара советов:
1. Не злоупотребляйте количеством шейдеров на один спрайт, мощности мобильных ГПУ пока что слабоваты. Постарайтесь подобрать оптимальный вариант эффекта с одни шейдером.
2. ФПС приложения напрямую зависит от размера экрана. Например, на HTC One S наш FrostedGlass идет без проблем, а на планшете Galaxy Tab (не смотря на всю его мощность) этот шейдер заметно притормаживает.
3. Не перекрывайте метод onDrawFrame движка в коде основной активити. Создайте для этого отдельный класс-наследник и обеспечьте его методами установки/снятия шейдеров по мере необходимости. Это поможет избежать трудностей с чтением кода и позволит сделать алгоритм вашей игры более гибким.
4. Если шейдер отлично идет на вашем  устройстве, это не означает что он пойдет на другом. Тестируйте на максимально возможном количестве девайсов.
5. Хотя с шейдером можно сделать то, что нельзя классической анимацией, не думайте, что разгрузив основной процессор отображением графики, вы получите качественный прирост производительности. Это не так. В случае мобильных устройств прироста fps я не наблюдал + батарея умирала не попрощавшись. Тем не менее использовать шейдера нужно, только не злоупотреблять ими, и дать возможность отключения.

Вот вроде бы и все, что я хотел рассказать. На десерт состряпал небольшой пример с комментариями. В нем, постарался максимально упростить понимание работы с RenderTexture и шейдерами в целом, привел пример наследника класса Engine, а также показал код реализованного шейдера ThermalVision содранного из лаборатории небезызвестного Geeks3D.com.
проект на гитхабе: https://github.com/unregistered33/ShaderTest.git


понедельник, 18 марта 2013 г.

AndEngine. Frosted Glass шейдер (2-е текстуры).

Предупреждение!: Я перешел на Anchor Center  и уходить не собираюсь ;) Но вы не переживайте, этот код подойдет и для старой версии GLES2, только координаты спрайтов поменяйте.
Всем привет! Наконец-то мне удалось найти отправную точку для создания шейдера использующего 2-е (!) текстуры. Вот эта точка: http://www.andengine.org/forums/tutorials/water-shader-t10103.html (автор Matthias) :) К сожалению, данный пример, хоть и показывает что используются две текстуры, все же выглядит хардкорно. Более того, показанный там пример реализации меня лично отпугнул, так как он больше похож на некий хак, а не на нормальный кодинг. Тем не менее автору поста респект, он открыл для меня строчку texture.bind(pGLState) - недостающее звено в изучении шейдеров. ))) Я советую вам пройти по данной ссылке и изучить пример автора поста, для того чтобы вы лучше поняли смысл моих претензий, а суть в следующем:

  1. @Override
  2. protected void preDraw(final GLState pGLState,
  3.                          final Camera pCamera) {
  4.     setShaderProgram(waterShader);
  5.     super.preDraw(pGLState, pCamera);
  6.                                             
        GLES20.glUniform1f(WaterShaderProgram.sUniformTime, secondsElapsed);
  7.     GLES20.glUniform1f(WaterShaderProgram.sUniformFrequency, Frequency);
  8.     GLES20.glUniform1f(WaterShaderProgram.sUniformAmplitude, Amplitude);                         };
Может мне кто-нибудь объяснить, за каким таким "надом" нам необходимо устанавливать шейдер и инициализировать униформы каждый раз(!!!) когда происходит preDraw(...)! Теперь далее:
  1.  pGLState.pushProjectionGLMatrix();
  2.   {
  3.      GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
  4.      displacementTexture.bind(pGLState, GLES20.GL_TEXTURE1);
  5.      GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
  6.      RenderTextureBackgroundSprite.onDraw(pGLState, pCamera);
  7.   }
Эта хрень также вызывается постоянно(!) на onDraw(...) контейнера содержащего спрайт на котором отрисовывается шейдер... (И народ еще удивляется "чо у нас фпс упал с 60-и до 30-и?").  По поводу RenderTexture* у меня есть соображения о которых я напишу позже. Если вы поняли хотя-бы половину кода из draw() - напишите мне.
       Перед тем, как написать это сообщение, я запостил на форум свое решение данного вопроса  (ник Unregistered) и попросил дать замечания по поводу моего кода, но наши забугорные коллеги, то ли не посмотрели, то ли не знают что сказать, то ли болт забили =), поэтому обращаюсь к Вам, братья программеры! 
       Если код приведенный в конце сообщения вызовет у вас приступ тошноты или неадекватное восприятие действительности - пишите и мы вместе все исправим! Кроме того, на базе этого класса мы вместе можем сделать библиотеку шейдеров которую легко использовать в наших будущих приложениях. Есть предложение разместиться на GitHub.
      Класс, который я хочу представить, позволяет довольно быстро создавать(портировать) шейдеры, а применять их - дело двух-трех строк. Примерно за 3 часа мне удалось перенести 6 шейдеров из библиотеки Geeks3D.com и большая часть времени ушла на приведение текста к строке Java =). В общем, вот что у нас на текущий момент в активе: Ripple (Pulse), FrostedGlass(2 текстуры),  Dream Vision, Thermal Vision, GaussianBlur (горизонтальный/вертикальный), Water(из примера на форуме AndEngine 2-текстуры). 
   
     Перед тем как начнем - пара ремарок:
1. Если вы захотите прикрутить шейдер к спрайту который имеет альфу, то при запуске, вместо альфы вы увидите безпонтовую черноту, это не ошибка подхода, а просто так написан шейдер. Если кто-то соображает в GLSL буду рад получить код "исправленного" шейдера, который бы учитывал это дело..
2. Класс реализован в виде синглтона потому, что у меня реально мало времени чтобы рисовать супер-реализацию, я хотел много и быстро так, что уж извините. :) Если кто из вас захочет поучаствовать в создании библиотеки я буду только рад. Для новичков скажу, что минус в данном случае такой: например вы прикрутили шейдер к одному спрайту и ко второму. Теперь, если вы поменяете параметр шейдера, то изменение отразится на обоих спрайтах., что, собственно , мало кому нужно. Выхода два: Переделывание кода под обычный объект, или моя статья с другой (чуток измененной) реализацией. Думаю, вы выберете первое =)

     Итак, обещанный FrostedGlass эффект (не пугайтесь, что он такой длинный, основное место занимает код самого шейдера, ну он очень клсассный).
     Параметры от которых стоит отталкиваться: (mNoiseTexture, 2.0f, 5.0f, 5.0f, 0.215f,  CAMERA_WIDTH, CAMERA_HEIGHT);: 

public class FrostedGlassEffectShader extends Object{
 
 private static final FrostedGlassEffectShader INSTANCE = new FrostedGlassEffectShader();
 
 public  float vx_offset; 
 public  float pixelX; 
 public  float pixelY;
 public  float frequency;
 public  float width;
 public  float height;
 
 public  ITexture maskTexture;


 public FrostedGlassEffectShader() {
 
 }

 public static FrostedGlassEffectShader getInstance(){
  return INSTANCE;
 }
 
 public static void setup(ITexture pMaskTexture, float pvx_offset, float pPixelX, float pPixelY, float pFrequency, float pWidth, float pHeight){
  
  getInstance().vx_offset = pvx_offset;
  getInstance().pixelX = pPixelX;
  getInstance().pixelY = pPixelY;
  getInstance().frequency = pFrequency;
  getInstance().width = pWidth;
  getInstance().height = pHeight;
   getInstance().maskTexture = pMaskTexture;
  
 }
 
 public static FrostedGlassEffectShaderProgram getShaderProgram(){  
  return FrostedGlassEffectShaderProgram.getInstance();
 }

 public static class FrostedGlassEffectShaderProgram extends ShaderProgram {
 private static FrostedGlassEffectShaderProgram instance;

 public static final String FRAGMENTSHADER ="precision lowp float;\n"
  + "varying mediump vec2 " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ";\n"
  + "uniform sampler2D " + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ";\n" // 0
  + "uniform sampler2D " + ShaderProgramConstants.UNIFORM_TEXTURE_1 + ";\n" // 1
  + "uniform float vx_offset;\n"
  + "uniform float PixelX;\n"
  + "uniform float PixelY;\n"
  + "uniform float Freq;\n"
  + "uniform float rt_w;\n" 
  + "uniform float rt_h;\n" 

  + "vec4 spline(float x, vec4 c1, vec4 c2, vec4 c3, vec4 c4, vec4 c5, vec4 c6, vec4 c7, vec4 c8, vec4 c9)\n"
  + "{\n"
  + " float w1, w2, w3, w4, w5, w6, w7, w8, w9;\n"
  + " w1 = 0.0;\n"
  + " w2 = 0.0;\n"
  + "w3 = 0.0;\n"
  + " w4 = 0.0;\n"
  + " w5 = 0.0;\n"
  + "w6 = 0.0;\n"
  + " w7 = 0.0;\n"
  + "w8 = 0.0;\n"
  + "w9 = 0.0;\n"
  + "float tmp = x * 8.0;\n"
  + "if (tmp<=1.0) {\n"
  + "w1 = 1.0 - tmp;\n"
  + "w2 = tmp;\n"
  + "}\n"
  + "else if (tmp<=2.0) {\n"
  + "tmp = tmp - 1.0;\n"
  + "w2 = 1.0 - tmp;\n"
  + "w3 = tmp;\n"
  + "}\n"
  + "else if (tmp<=3.0) {\n"
  + "tmp = tmp - 2.0;\n"
  + "w3 = 1.0-tmp;\n"
  + "w4 = tmp;\n"
  + "}\n"
  + "else if (tmp<=4.0) {\n"
  + "tmp = tmp - 3.0;\n"
  + "w4 = 1.0-tmp;\n"
  + "w5 = tmp;\n"
  + "}\n"
  + "else if (tmp<=5.0) {\n"
  + "tmp = tmp - 4.0;\n"
  + "w5 = 1.0-tmp;\n"
  + "w6 = tmp;\n"
  + "}\n"
  + "else if (tmp<=6.0) {\n"
  + "tmp = tmp - 5.0;\n"
  + "w6 = 1.0-tmp;\n"
  + "w7 = tmp;\n"
  + "}\n"
  + "else if (tmp<=7.0) {\n"
  + "tmp = tmp - 6.0;\n"
  + "w7 = 1.0 - tmp;\n"
  + " w8 = tmp;\n"
  + "}\n"
  + "else\n" 
  + "{\n"
  + "tmp = clamp(tmp - 7.0, 0.0, 1.0);\n"
  + "w8 = 1.0-tmp;\n"
  + "w9 = tmp;\n"
  + "}\n"
  + "return w1*c1 + w2*c2 + w3*c3 + w4*c4 + w5*c5 + w6*c6 + w7*c7 + w8*c8 + w9*c9;\n"
  + "}\n"

  + "vec3 NOISE2D(vec2 p)\n"
  + "{ return texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_1 + ",p).xyz; }\n"

  + "void main() \n"
  + "{ \n"
  + "vec2 uv = " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".xy;\n"
  + "vec3 tc = vec3(1.0, 0.0, 0.0);\n"
     
  + "if (uv.x < (vx_offset-0.005))\n"
  + "{\n"
  + "float DeltaX = PixelX / rt_w;\n"
  + "float DeltaY = PixelY / rt_h;\n"
  + "vec2 ox = vec2(DeltaX,0.0);\n"
  + "vec2 oy = vec2(0.0,DeltaY);\n"
  + "vec2 PP = uv - oy;\n"
  + "vec4 C00 = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ",PP - ox);\n"
  + "vec4 C01 = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ",PP);\n"
  + "vec4 C02 = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ",PP + ox);\n"
  + "PP = uv;\n"
  + "vec4 C10 = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ",PP - ox);\n"
  + "vec4 C11 = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ",PP);\n"
  + "vec4 C12 = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ",PP + ox);\n"
  + "PP = uv + oy;\n"
  + "vec4 C20 = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ",PP - ox);\n"
  + "vec4 C21 = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ",PP);\n"
  + "vec4 C22 = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ",PP + ox);\n"
     
  + "float n = NOISE2D(Freq*uv).x;\n"
  + "n = mod(n, 0.111111)/0.111111;\n"
  + "vec4 result = spline(n,C00,C01,C02,C10,C11,C12,C20,C21,C22);\n"
  + "tc = result.rgb;  \n"
  + "}\n"
  + "else if (uv.x>=(vx_offset+0.005))\n"
  + "{\n"
  + " tc = texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", uv).rgb;\n"
  + "}\n"
   
  + "gl_FragColor = vec4(tc, 1.0);\n"
  + "}\n";

 public static int sUniformModelViewPositionMatrixLocation = ShaderProgramConstants.LOCATION_INVALID;
 public static int sUniformTexture0Location = ShaderProgramConstants.LOCATION_INVALID;
 public static int sUniformTexture1Location = ShaderProgramConstants.LOCATION_INVALID;
 public static int sUniformVx_offsetLocation = ShaderProgramConstants.LOCATION_INVALID;
 public static int sUniformFrequencyLocation = ShaderProgramConstants.LOCATION_INVALID;
 public static int sUniformPixelXLocation = ShaderProgramConstants.LOCATION_INVALID;
 public static int sUniformPixelYLocation = ShaderProgramConstants.LOCATION_INVALID;
 public static int sUniformRT_WidthLocation = ShaderProgramConstants.LOCATION_INVALID;
 public static int sUniformRT_HeightLocation = ShaderProgramConstants.LOCATION_INVALID;
 
 public static FrostedGlassEffectShaderProgram getInstance() {
  if (instance == null)
   instance = new FrostedGlassEffectShaderProgram();
  return instance;
 }

 private FrostedGlassEffectShaderProgram() {
  super(PositionTextureCoordinatesShaderProgram.VERTEXSHADER,
    FRAGMENTSHADER);
 }

 @Override
 protected void link(final GLState pGLState)
   throws ShaderProgramLinkException {
  GLES20.glBindAttribLocation(this.mProgramID,
    ShaderProgramConstants.ATTRIBUTE_POSITION_LOCATION,
    ShaderProgramConstants.ATTRIBUTE_POSITION);
  GLES20.glBindAttribLocation(this.mProgramID,
    ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES_LOCATION,
    ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES);

  super.link(pGLState);

  FrostedGlassEffectShaderProgram.sUniformModelViewPositionMatrixLocation = this
    .getUniformLocation(ShaderProgramConstants.UNIFORM_MODELVIEWPROJECTIONMATRIX);
  FrostedGlassEffectShaderProgram.sUniformTexture0Location = this
    .getUniformLocation(ShaderProgramConstants.UNIFORM_TEXTURE_0);
  FrostedGlassEffectShaderProgram.sUniformTexture1Location = this
  .getUniformLocation(ShaderProgramConstants.UNIFORM_TEXTURE_1);
  FrostedGlassEffectShaderProgram.sUniformVx_offsetLocation = this
  .getUniformLocation("vx_offset");
  FrostedGlassEffectShaderProgram.sUniformFrequencyLocation = this
  .getUniformLocation("Freq");
  FrostedGlassEffectShaderProgram.sUniformPixelXLocation = this
  .getUniformLocation("PixelX");
  FrostedGlassEffectShaderProgram.sUniformPixelYLocation = this
  .getUniformLocation("PixelY");
  FrostedGlassEffectShaderProgram.sUniformRT_WidthLocation = this
  .getUniformLocation("rt_w");
  FrostedGlassEffectShaderProgram.sUniformRT_HeightLocation = this
  .getUniformLocation("rt_h");
 }

 @Override
 public void bind(final GLState pGLState,
   final VertexBufferObjectAttributes pVertexBufferObjectAttributes) {
  GLES20.glDisableVertexAttribArray(ShaderProgramConstants.ATTRIBUTE_COLOR_LOCATION);
  
  
  
  super.bind(pGLState, pVertexBufferObjectAttributes);
  
  FrostedGlassEffectShader.getInstance().maskTexture.bind(pGLState, GLES20.GL_TEXTURE1);
  GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
  
  GLES20.glUniformMatrix4fv(
    FrostedGlassEffectShaderProgram.sUniformModelViewPositionMatrixLocation, 1,
    false, pGLState.getModelViewProjectionGLMatrix(), 0);
  GLES20.glUniform1i(FrostedGlassEffectShaderProgram.sUniformTexture0Location, 0);
  GLES20.glUniform1i(FrostedGlassEffectShaderProgram.sUniformTexture1Location, 1);
  GLES20.glUniform1f(sUniformVx_offsetLocation, FrostedGlassEffectShader.getInstance().vx_offset);
  GLES20.glUniform1f(sUniformFrequencyLocation, FrostedGlassEffectShader.getInstance().frequency);
  GLES20.glUniform1f(sUniformPixelXLocation, FrostedGlassEffectShader.getInstance().pixelX);
  GLES20.glUniform1f(sUniformPixelYLocation, FrostedGlassEffectShader.getInstance().pixelY);
  GLES20.glUniform1f(sUniformRT_WidthLocation, FrostedGlassEffectShader.getInstance().width);
  GLES20.glUniform1f(sUniformRT_HeightLocation, FrostedGlassEffectShader.getInstance().height);
 }

 @Override
 public void unbind(final GLState pGLState) throws ShaderProgramException {
  GLES20.glEnableVertexAttribArray(ShaderProgramConstants.ATTRIBUTE_COLOR_LOCATION);
  super.unbind(pGLState);
 }
 
 }
}


Напоминаю - этот шейдер использует 2-е текстуры. Первая текстура - автоматически берется со спрайта к которому мы применяем шейдер, а вторую (текстура с шумом) вы должны подгрузить отдельно стандартными средствами AndEngine.

Как использовать этот класс:

Sprite sprite = new Sprite(CAMERA_WIDTH/2, CAMERA_HEIGHT/2, 800,480, mSprite1TextureRegion, getVertexBufferObjectManager()); 
FrostedGlassEffectShader.setup(mNoiseTexture, 0, 5.0f, 5.0f, 0.215f, 800f, 480f);
this.getShaderProgramManager().loadShaderProgram(FrostedGlassEffectShader.getShaderProgram());
sprite.setShaderProgram(FrostedGlassEffectShader.getShaderProgram());

mScene.attachChild(sprite);

mNoiseTexture - это та самая текстура с "шумом". Я использовал вот эту (Geeks3D.com):

Загрузить ее можно например вот так:

mNoiseTexture = new BitmapTexture(
     this.getTextureManager(), new IInputStreamOpener() {

      @Override
      public InputStream open() throws IOException {
       return getAssets().open("gfx/noise.jpg");
      }
     }, TextureOptions.BILINEAR_PREMULTIPLYALPHA);

mNoiseTexture.load();

Параметры шейдера:
В объяснениях, пожалуй, нуждается только pvx_offset - параметр отвечающий за положение границы эффекта, меняется от 0 - 1. Что это за граница такая, вы можете легко выяснить сами задавая разные значения этому параметру.
Остальные параметры влияют на внешний вид. Обратите внимание, что все параметры являются uniform, т.е. мы можем задавать их значения в RT и сразу видеть производимый эффект! Это можно сделать например так:

mScene.registerUpdateHandler(new TimerHandler(0.01f, true, new  ITimerCallback() {
   private boolean dir = false;
   @Override
   public void onTimePassed(TimerHandler pTimerHandler) {
    
    if (FrostedGlassEffectShader.getInstance().vx_offset >= 1 ){
     
     dir = true;
    }
    if (FrostedGlassEffectShader.getInstance().vx_offset <= 0 ){
     
     dir = false;
    }
    if (dir){
     FrostedGlassEffectShader.getInstance().vx_offset -=0.01f;
    }else{
     FrostedGlassEffectShader.getInstance().vx_offset +=0.01f;
    }
    
   }
  }));

    Рассказывать по классу почти нечего, а если все-таки возникнут вопросы или пожелания - пишите сюда.
    Если вы дочитали сообщение до этой строчки, значит вы заинтересованы в дальнейшем развитии библиотеки)) И, специально для вас, далее пойдут пояснения к данному классу.
  • Класс является наследником Object, сделал я это для того, чтобы переменные которые участвуют в настройке и "жизни" шейдера, не болтались где попало, а являлись частью одного целого.
  • Одним из основных методов класса является метод setup(... ... ...); Этот метод был  введен для первоначальной инициализации шейдера. Через этот метод мы передаем доп.текстуры, начальные значения и т.д. Параметры метода будут меняться в зависимости от того какой шейдер вы захотите реализовать. Например: в шейдере FrostedGlass у нас это текстура "шума" и обычные значения потому, что этот шейдер является "стационарным". Т.е. весь его эффект заключается в отображении картинки "через замерзшее стекло" (конечно мы можем прикрутить к нему риалтайм изменения, но его суть все же не в этом), а если, например у нас шейдер изначально подразумевает некое движение (Ripple или Water)? Тогда, через наш setup(), мы можем передать объект Engine, чтобы внутри класса реализовать изменение переменной времени и, как следствие, дать волнам движение не выходя за рамки самого класса, а извне, спокойно управлять, например, интенсивностью, амплитудой, частотой и скоростью. Если коротко, нам нужен этот метод чтобы облегчить себе работу с шейдером в будущем. Кстати, для некоторых шейдеров (для тех которые не имеют uniform), метод setup() можно вообще не указывать или лучше оставить пустышкой чтобы не нарушать общности.
  • Реализация шейдерной программы ничем не отличается от описанных мною в предыдущих статьях. Разница только в том, что если мы используем более одной текстуры, то нам надо ее прибиндить в методе bind() (см. код) и потом обязательно указать активную текстуру строкой 
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
     (активной в нашем случае всегда должна быть TEXTURE_0, т.е. текстура того спрайта к которому мы применяем шейдер). Если не задать активную текстуру, то наблюдается такой эффект - все спрайты размещенные на сцене будут иметь текстуру вашего "отшейдеренного" :) спрайта.
         Вот, наверное, и все ключевые моменты данной реализации. 
      Если вы соберетесь самостоятельно портировать какой-нибудь шейдер запомните пару ключевых моментов которые помогут вам сэкономить время на отладку:
  • если в исходнике найденого вами шейдера униформам сразу задаются значения:
    uniform float param1 = 1.0;
    uniform float param2 = 0.25;
    то вам необходимо убрать эти значения (= 1.0 и =0.25) из кода шейдера и задать их программно (вынести в переменные вашего основного класса), либо задать их внутри шейдера как константы: const ..., но это не наш метод :)).
  • если в шейдере задан массив, например:
    float offset[3] = float[]( 0.0, 1.3846153846, 3.2307692308 );
    то исправьте на float offset[3] = float[3]( 0.0, 1.3846153846, 3.2307692308 ); т.е. явно укажите длину массива в правой части.
Если этого не сделать, то компилятор шейдера будет выдавать ошибку без каких-либо объяснений, учтите это. Вообще, таких несоответствий много и если я с ними буду сталкиваться, то обязательно вас предупрежу.
      И напоследок поразмышляем о  RenderTexture. На мой взгляд, этот класс нужен нам для проецирования изображения на экране на специальную текстуру с которой можно потом что-нибудь сотворить... Ну например, шейдер с эффектом движения горячего воздуха (Heat-эффект). Этот эффект нельзя применить к конкретному спрайту потому, что он теряет всякий смысл (как мы знаем, любое изображение должно искажаться если на него смотреть сквозь жар от костра). Т.е. у нас имеется "жар от костра", а за ним движутся другие шашлыки объекты (спрайты), которые искажаются за жаром, и не искажаются если они не за жаром. Вот тут, я думаю, нам и понадобитя RenderTexture, но проверим мы это в следующем сообщении ибо на сегодня уже достаточно. 
         
        Всем удачи, присылайте свои реализации шейдеров!

* По поводу этого класса я напишу следующую статью. А сейчас скажу только, что этот класс-сказка! :)
https://github.com/unregistered33/ShaderTest.git

воскресенье, 17 февраля 2013 г.

Менеджер ресурсов

Привет! В этом сообщении я освещу некоторые вопросы связанные с загрузкой и освобождением ресурсов игры и на итог мы напишем шаблон класса который будет организовывать работу с этими ресурсами.

Ресурсами игры считается вся используемая графика, музыка, звуки и шрифты.
Многие из вас, только начиная работать с AndEngine, действовали по шаблону взятому из примеров: создавали активити, прописывали в ней при необходимости поля текстур, регионов и спрайтов; заполняли поля в onCreateResources(); собирали сцену в onCreateScene() и радовались жизни =) Проблемы начинались тогда, когда объект игры представлял собой не просто спрайт, а комплексный объект использующий свои дополнительные свойства и методы, содержащий более одного спрайта и т.д. С подходом описанным выше, приходиться либо объявлять поля активити как protected/public или передавать текстуры через конструктор объекта. Как результат:
- грязный код,
- активити заполнено полями которые этой активти не используются,
- множество параметров в конструкторах объектов,
- сложности в отслеживании неиспользуемых ресурсов.

Нам было бы гораздо удобнее иметь некий менеджер, который бы давал нам необходимые текстуры и вызывался тогда когда он нам нужен без его глобального указателя, чтобы этот указатель тоже не таскать за собой через конструкторы объектов, при этом, чтобы не нарушать lifecycle игры, ресурсы все-таки должны грузиться в onCreateResources().

Вы уже наверняка догадались, что этим условиям идеально подходит синглтон.
Сразу скажу, что решение вопроса доступа к ресурсам через синглтон целиком и полностью зависит от того, как организованы ваши игровые объекты. Т.е. игровым объектом может быть простое расширение спрайта (Object extended Sprite), а может быть и объект содержащий несколько спрайтов. Здесь я приведу код простого ResourceManager-а просто для того. чтобы обозначить сам подход к реализации.


package com.expedition107.evileye;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import org.andengine.audio.music.Music;
import org.andengine.audio.music.MusicFactory;
import org.andengine.audio.sound.Sound;
import org.andengine.audio.sound.SoundFactory;
import org.andengine.engine.Engine;
import org.andengine.entity.scene.Scene;
import org.andengine.opengl.font.Font;
import org.andengine.opengl.font.FontFactory;
import org.andengine.opengl.texture.TextureOptions;
import org.andengine.opengl.texture.bitmap.BitmapTexture;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.texture.region.TextureRegionFactory;
import org.andengine.util.adt.color.Color;
import org.andengine.util.adt.io.in.IInputStreamOpener;

import android.content.Context;
import android.graphics.Typeface;

public class ResourceManager {

 private static ResourceManager Instance;
 
 //Поля для доступа к "движковым" переменным
 private Context currentActivity;
 private Engine engine;

 //текстуры
 public BitmapTexture mBackgroundBitmapTexture;
 public BitmapTexture mCloudsBitmapTexture;
 public BitmapTexture mHeroBitmapTexture;
 //регионы
 public ITextureRegion bgrTextureRegion;
    public ITextureRegion heroTextureRegion;
    public ITextureRegion cloudTextureRegion;
    public ITextureRegion arrowTextureRegion;
    
 //музыка и звуки
    public Music bgrMusic;
 public ArrayList<Sound> sounds = new ArrayList<Sound>(); 
 
 //шрифт
 public Font font;


 public synchronized static ResourceManager getInstance(){
  if(Instance == null){
   Instance = new ResourceManager();
  }
  return Instance;
 }
 
 //для того чтобы наш менеджер заработал мы должны его проинициализировать
 public synchronized void init(Context pContext, Engine pEngine){
  currentActivity = pContext;
  engine = pEngine;
 }
 
 public synchronized void loadGameSceneTextures(){
  
  try {
   mBackgroundBitmapTexture = new BitmapTexture(engine.getTextureManager(), new IInputStreamOpener() {
    @Override
    public InputStream open() throws IOException {
     return currentActivity.getAssets().open("gfx/background.png");
    }
   }, TextureOptions.BILINEAR);
   mBackgroundBitmapTexture.load();
   bgrTextureRegion = TextureRegionFactory.extractFromTexture(mBackgroundBitmapTexture);
   
   mCloudsBitmapTexture = new BitmapTexture(engine.getTextureManager(), new IInputStreamOpener() {
    @Override
    public InputStream open() throws IOException {
     return currentActivity.getAssets().open("gfx/clouds.png");
    }
   }, TextureOptions.BILINEAR);
   mCloudsBitmapTexture.load();
   cloudTextureRegion = TextureRegionFactory.extractFromTexture(mCloudsBitmapTexture);
   
   mHeroBitmapTexture = new BitmapTexture(engine.getTextureManager(), new IInputStreamOpener() {
    @Override
    public InputStream open() throws IOException {
     return currentActivity.getAssets().open("gfx/hero.png");
    }
   }, TextureOptions.BILINEAR);
   mHeroBitmapTexture.load();
   heroTextureRegion = TextureRegionFactory.extractFromTexture(mHeroBitmapTexture, 0, 0, 100, 100);
   arrowTextureRegion = TextureRegionFactory.extractFromTexture(mHeroBitmapTexture, 110, 0, 50, 5);
   
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  
  
  
  
 }
 
 public synchronized void unloadGameSceneTextures(){
  mBackgroundBitmapTexture.unload();
  mCloudsBitmapTexture.unload();
  mHeroBitmapTexture.unload();
  System.gc();
 }
 
 public synchronized void loadSounds(){
  SoundFactory.setAssetBasePath("sounds/");
   try {
    sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hit1sound.mp3"));    
    sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hit2sound.mp3"));
    sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hit3sound.mp3"));
    sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hit4sound.mp3"));
    sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hero_die_sound.mp3"));
    sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "shootsound.mp3"));
   } catch (final IOException e) {
             
   }
 } 
 
 public synchronized void unloadSounds(){
  while (getInstance().sounds.size() > 0){
   if (!getInstance().sounds.get(0).isReleased()){
    getInstance().sounds.get(0).release();
   }
   getInstance().sounds.remove(0);
  }
 }
 
 public synchronized void loadMusic(){
  MusicFactory.setAssetBasePath("music/");
   try {
    bgrMusic = MusicFactory.createMusicFromAsset(getInstance().engine.getMusicManager(), getInstance().currentActivity, "mfx/bgrmusic1.mp3");
   } catch (final IOException e) {
             
   }
 } 
 public synchronized void unloadMusic(){
  if (!bgrMusic.isReleased())
   bgrMusic.release();
 }
 
 public synchronized void loadFont(){
  FontFactory.setAssetBasePath("fonts/");
  font = FontFactory.create(getInstance().engine.getFontManager(), getInstance().engine.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.NORMAL),  32f, true);
  font.load();
 }
 
 public synchronized void unloadFont(){
  font.unload();
 }
}


Как им пользоваться:
В onCreateResources() нашей активити c начала инициализируем ResourceManager, а затем грузим всю нашу графику, звуки, шрифты и т.д.:

@Override
 public void onCreateResources() {
  ResourceManager.getInstance().init(this, mEngine);
  ResourceManager.getInstance().loadGameSceneTextures();
  ResourceManager.getInstance().loadSounds();
  ResourceManager.getInstance().loadMusic();
  ResourceManager.getInstance().loadFonts();
 }

Где-то в коде..
...
ResourceManager rm = ResourceManager.getInstance();
mArrowSprite = new Sprite(pX, pY, rm.arrowTextureRegion,  rm.engine.getVertexBufferObjectManager());
...

Вы можете спросить - и чо же такого в нем менеджерского, чтобы выделять этот код в отдельный класс? Ответ на этот вопрос кроется в организации структуры игры. Если ваша игра состоит только из одной сцены (а такого не бывает ;-)), то менеджер не нужен. Но в самом простом случае мы имеем как минимум две сцены: 1. Главное меню 2. Сцена с самой игрой. Нам не нужны ресурсы игры когда мы находимся в главном меню и наоборот - в игре нам не нужны ресурсы главного меню. Так вот, чтобы загружать/выгружать нужные/ненужные ресурсы и поможет наш менеджер.
PS. Продумывайте какие ресурсы будут задействованы во время главного геймплея. Все ресурсы необходимо грузить до того как игра началась. Если в процессе игры приложение начнет что-то загружать - это немедленно отразится на производительности.


четверг, 14 февраля 2013 г.

AndEngine GLES2-AnchorCenter vs AndEngine GLES2. Стоит ли овчинка выделки?

Всем привет!

В этом сообщении хочу немного прояснить ситуацию с новой версией AndEngine GLES2-AnchorCenter (сокращенно будем писать AC).

NOTE: Здесь я не буду подробно расписывать разницу между GLES2 и AC потому, что для этого нужно досконально знать оба движка. Данное сообщение скорее призвано  сэкономить ваше время на установку рабочей сборки AC, дать небольшое представление о целесообразности миграции с GLES2 на AC, а также немного осветить особенности нового двига.

Многие из вас, кто уже работал с версиями AndEngine GLES1 и GLES2, встречали на официальном форуме движка упоминание об AndEngine AnchorCenter (AC), а так же о том, что, это, на текущий момент, самая передовая версия AndEngine. Многие старожилы форума уже давно на нем работают, и активно участвуют в развитии данной версии. Тем не менее, от создателя движка, нет никаких официальных сообщений (релизов) этой версии в которых бы говорилось что-то вроде "ребята, велком в новую версию. юзаем, сообщаем о багах... + список отличий от GLES2". Но ведь люди ее используют, причем весьма успешно решают задачи, о которых в GLES2 даже не заикались! Даже книжку написали по нему. Ситуация усугубляется тем, что многие баги движка, почему-то в первую очередь исправляются именно в ветке AC, а не в официальном GLES2. Примером тому может служить баг в Engine.java в функциях

protected void onUpdateScene(final float pSecondsElapsed)
protected void onUpdateUpdateHandlers(final float pSecondsElapsed)

проявляющийся в лагах графики (jitter effect) при движении камеры отслеживающей какой-нибудь объект.

А так же баг в расширении AndEngineLiveWallpaper, проявляющийся в том, что обои после перезагрузки девайса или при переходе с активити на хоумскрин замораживались.И вообще, глядя на историю изменений веток GLES2 и AC, на момент написания этого сообщения, видно, что правки по GLES2 последний раз были два месяца назад, а по AC два дня назад.

На днях, в очередной раз просматривая форум на предмет интересных статей или вопросов, я опять столкнулся с Anchor Center и моя чаша терпения переполнилась...

Итак, настало время разобраться, что это за AnchorCenter и стоит ли на него переходить.
Начал я с того, что для экспериментов создал отдельный workspace. Дальше по порядку:
1. С помощью Git импортируем версию движка из ветки GLES2-AnchorCenter (здесь и далее только из нее)
https://github.com/nicolasgramlich/AndEngine.git (Почему именно c помощью Git а не просто скачать архивом, поясню: раз уж движок активно развивается и дорабатывается, то каждый раз (через месяц, неделю, день) искать отличия и выкачивать зип обновленной версии, а потом повторять процедуру импорта и линковки будет сильно накладно по времени и нервам. Git в этом случае просто спасение утопающих. Кто не умеет им пользоваться, тому самое время научиться).
2. Импортируем расширение для физики AndEngineBox2DExtension.
https://github.com/nicolasgramlich/AndEnginePhysicsBox2DExtension.git
А теперь... АХТУНГ! НЕ КАЧАЙТЕ ЭТУ ВЕРСИЮ! По крайней мере пока. На момент написания сообщения (15 февраля 2013 года) это глючный порт*. Я потратил кучу времени, чтобы удостовериться, что он НЕ юзабельный (но не обижаюсь, потому, что мне никто и не обещал, что он рабочий=)), Так что не тратьте на него свое время. Рабочая и наиболее полная (RopeJoint, GearJoint, WheelJoint, EdgeShape и т.д.) версия порта Box2D лежит тут:
https://github.com/RealFictionFactory/AndEnginePhysicsBox2DExtension.git
3. Также под AC есть расширение AndEngineTMXTiledMapExtension, но я его не проверял. Лежит здесь:
https://github.com/nicolasgramlich/AndEngineTMXTiledMapExtension.git
4. Расширение для живых обоев:
https://github.com/nicolasgramlich/AndEngineLiveWallpaperExtension.git
Должно быть рабочее. В принципе, там особо ничего не поменялось кроме исправления бага про который я написал выше.
5. Настоятельно рекомендую расширение AndEngineDebugDrawExtension:
https://github.com/nazgee/AndEngineDebugDrawExtension.git Честь и слава людям, которые делают подобные вещи! Расширение позволяет без всякой графики (да и с графикой тоже) дебажить физический мир который вы построили в своем приложении.
6. Примеры AndEngineExamples:
https://github.com/nicolasgramlich/AndEngineExamples.git
Когда я их качал пару месяцев назад, то запустить не хватило терпения. Все было в полном хаосе. Сейчас посмотрел в ветку и там, напротив src, написано следующее:
Updated all examples to the latest and greatest of AndEngine@GLES2-AnchorCenter!

Последнее изменение было 9 месяцев назад... Имеет смысл их качать или нет предлагаю удостовериться самим. Сам сделаю это попозже.

Ок. Это вроде и все, что нам нужно для создания игры на новом движке. А как же расширение для  мультитача, текстурпакера? А они уже в самом движке, что не может не радовать =).
По расширениям AndEngineMODPlayerExtension, AndEngineMultiplayerExtension, AndEngineSVGTextureRegionExtension сказать пока ничего не могу, судя по репозиторию, эти расширения присутствуют только для GLES2, но с одной стороны, в них нет ничего такого что не работало бы и в AC, а с другой стороны - за все время пользования движком, я подключал их только ради ознакомления, в реальных задачах они не участвовали (хотя это, возможно, мой минус). И все бы хорошо, но это, как ни странно - мелочи. Приятные, удобные но... мелочи.

Основное отличие AC от предыдущей версии состоит в том, что поменялась ось координат. Теперь точка (0,0) находится не в левом верхнем углу, а в левом нижнем. Такое изменение создатель движка мотивирует тем, что привел координаты сцены к родной системе OpenGL, в результате, освободились ресурсы процессора которые тратились на перевод из одних координат в другие. Ну чтож, это нововведение можно считать целесообразным, а нам не так уж и долго перестроиться. Кроме инверсии оси ординат,  координаты спрайта относительно сцены теперь также не левый верхний угол, а... центр этого спрайта! Т.е. создали спрайт:

Sprite sprite = new Sprite(100,200, mSpriteTextureRegion, VBO);

и поместили на сцену

mScene.attachChild(sprite);

Так вот центр спрайта будет аккурат в координате (100,200) . И sprite.getX(), sprite.getY() вернет нам те же 100 и 200. (Ага! Вот почему он AnchorCenter!) Немного мозголомно правда? Здесь привыкать будет чуть сложнее, но мы привыкнем, я точно знаю =) Хорошо, ну а это-то извращение для чего понадобилось? Думаю, опять же для экономии ресурсов. От центра спрайта, проще высчитывать вершины и делать с ними различные преобразования. Кстати центр этот можно задавать. Для этого в Entity предусмотрены функции setAnchorCenter.... Функции getWidthScaled и getHeightScaled теперь deprecated. Тем кому приходилось сталкиваться со скайлом и дальнейшей привязкой координат это оценят.
Итак, смена системы координат - основное отличие. Имхо, за этим изменением кроются гораздо более серьезные возможности движка о которых пока умалчивается.
Вот что пишет Nicolas Gramlich в своем блоге (цитата):

"...The coordinate system in the GLES2-AnchorCenter branch has its origin in the lower left. This was changed for multiple reasons:
  • It is the native OpenGL coordinate system. (I can save a few +- calculations here and there.)
  • It is the same coordinate system as cocos2d-iphone and cocos2d-x. (This eases porting efforts in both directions by a whole bunch!)
  • It allowed me to easily/efficiently write the AndEngineCocosBuilderExtension, which allows reading a format exported by CocosBuilder.
  • It just feels more natural for any side-scrolling game.
Another thing that changed in this branch is that the anchorpoints (rotatincenter, scalecenter, etc…) are now relative, from 0.0 to 1.0, instead of being absolute values. So in general, AndEngine got a little more cocos2d-like on this branch, which is definitely not a bad thing...."  

Про AndEngineCocosBuilderExtension заметили? Я тоже заметил. =) Судя по всему отличный инструмент, надо будет его попробовать.**

Для эксперимента взял одну из наших недоработанных игр включающую в себя физику и попробовал перенести ее на новый двиг. С тем набором расширений который перечислен выше (а этих наборов пришлось перебрать несколько )) эксперимент завершился удачно, небольшие заминки возникали именно из-за новой системы координат. Кстати, замечено, что с новой системой, прикручивать графику к физтелам стало удобнее. Чтобы понять почему удобнее,  нужно пройти путь от GLES1 до AnchorCenter поэтому объяснить это я не берусь, да оно и не к чему ).

Резюмируя данное сообщение немного по-философствуем на тему переходить или нет на AnchorCenter =):

"ЗА"
1. Исправления багов, рефакторинг и новые плюшки делаются уже для AC, в то время как столкнувшись с багом в GLES2 вам обеспечены скитания по исходникам или форуму в поисках нужной заплатки. Плюшек и рефакторинга в GLES2 ожидать вообще не приходится.
2. Наиболее необходимые расширения движка за исключением Box2D уже интегрированы в сам движок.
3. Отличия GLES2 и AC значительны по внутреннему содержанию, но с точки зрения программиста переходить не так уж и проблемно (вспомним GLES1 vs GLES2 и выдохнем =)).

"ПРОТИВ"
1. Если сам движок вполне юзабельный, то расширение Box2D в официальной ветке вызывает грусть. Не смотря на то, что есть добрые люди как Andrzej J. Debicki (RealFictionFactory) и Michal Stawinski (nazgee), все-таки хочется, чтобы рабочая версия лежала и на официальной ветке.
2. Программистская  агорафобия  =)

В целом, советую дописать то, что не дописано, на той версии на которой вы работаете сейчас, ну а следующий шедевр начинать уже в AnchorCenter! 

Спасибо за внимание.

* - глючность порта у меня проявилась в использовании PrismaticJoint. Тело, которое должно было двигаться по оси X почему-то "тонуло" под действием силы тяжести. Что я только с ним не вытворял - ничего не помогало, хотя по определению PrismaticJoint никак не могло дать такую степень свободы. В итоге оказалось, что такое поведение тела случается если зафиксировать его вращение body.setFixedRotation(true); Ну кто бы мог подумать, что эта опция напрочь убивает джоинт, и какие еще подобные сюрпризы нам уготованы?
** - попробовать это расширение не получилось =(, потому как не смог найти этот билдер под Windows. Мало того, говорят, что его и не будет. А с другой стороны - если у программиста есть Мак, то он наверное пишет под iOS ага? Короче тут облом (.