Monday, March 3, 2014

[ AndEngine Tutorial : Essential 33 ] 배경 화면의 처리

이 강좌는 [AndEngine for Android Game Development Cookbook by Jayme Schroeder, Brian Broyles (2013)]을 토대로 작성되었습니다. 소스코드는 책의 예제를 그대로 사용하였으며, 해설 또한 책에 근거하여 작성하였지만, 간략함과 이해를 위해 많은 부분이 제거 되거나 작성자의 의견이 첨가 되었습니다.


Mac OS X Maverick
Java 1.6+
Eclipse Kelper
Genymotion
AndEngine GLES 2 (Anchor Center branch)




씬의 배경은 어떻게 처리할 수 있나요?

이번엔 백그라운드를 설정하는 방법에 대해 알아보려 합니다. 단일색깔, 혹은 Entity, 혹은 단일 스프라이트를 이용해서 백그라운드를 채우거나, 아니면, 작은  스프라이트를 반복해서 화면을 가득 채우는 방법을 이용할 수가 있습니다. 백그라운드는 Camera 의 이동이나 줌(zoom)에도  영향을 받지 않습니다.

우선 Background라는 이름을 갖는 타입을 이용할 수 있는데요. 이게 가장 기본적인 방법입니다. 단일 색깔로 scene을 채우게 되죠. 이번 레시피는 Background 타입으로 시작해서 그 하위 타입들을 이용하는 방법들 까지 다양하게 살펴보도록 하겠습니다.

우선 UsingBackground라는 액티비티를 하나 추가해보겠습니다. 그리고, 아래의 코드 처럼 (우리가 앞에서 해왔던 것처럼) 몇 개의 인스턴스 변수를 추가해 주고, EngineOptions를 세팅해주고, onCreateScene()메소드에서 백그라운드를 설정하도록 작성해 주세요.



단일 색깔로 배경을 처리하기 : Background 객체의 활용

package how2quit.tutorial.chapter3;

import java.io.IOException;

import org.andengine.engine.camera.Camera;
import org.andengine.engine.options.EngineOptions;
import org.andengine.engine.options.ScreenOrientation;
import org.andengine.engine.options.resolutionpolicy.FillResolutionPolicy;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.scene.background.Background;
import org.andengine.ui.activity.BaseGameActivity;

public class UsingBackgroundActivity extends BaseGameActivity {

   public static int WIDTH = 800;
   public static int HEIGHT = 480;

   private Scene mScene;
   private Camera mCamera;
   
   @Override
   public EngineOptions onCreateEngineOptions() {
      mCamera = new Camera(0, 0, WIDTH, HEIGHT);
      EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(), mCamera);

      return engineOptions;
   }

   @Override
   public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws IOException {
      // TODO Auto-generated method stub
      pOnCreateResourcesCallback.onCreateResourcesFinished();
   }

   @Override
   public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback)
         throws IOException {
      mScene = new Scene();
      
      final float red = 0;
      final float green = 1;
      final float blue = 1;
      final float alpha = 1;
      
      Background background = new Background(red, green, blue, alpha);
      mScene.setBackground(background);
      mScene.setBackgroundEnabled(true);
      
      pOnCreateSceneCallback.onCreateSceneFinished(mScene);
   }

   @Override
   public void onPopulateScene(Scene pScene,
         OnPopulateSceneCallback pOnPopulateSceneCallback)
         throws IOException {
      // TODO Auto-generated method stub
      pOnPopulateSceneCallback.onPopulateSceneFinished();
   }

}


[위 코드의 실행 화면입니다]


이 방법 이외에도 Scened의 배경을 지정하는 방법은 더 있지만, 여기서 고려해야할 점이 있습니다. 아직까지는 카메라가 이동하질 않습니다. 하지만, 카메라를 이동 시켜야 할때가 분명 오겠죠? 그럴때를 대비해서 '배경을 처리하는 방법으로 무엇을 채택할 것인가'를 따지기 이전에 '카메라가 이동할 경우,  배경도 함께 움직여야 하는가 아닌가'를 먼저 결정해야 합니다.

Background는 카메라가 움직여도 그 모습 그대로 카메라를 따라 다닙니다

왜냐하면, Scened의 배경(background)로 지정이 되면, 그 배경은 카메라의 위치가 이동하더라도 '절대' 움직이지 않기 때문입니다. 줌인/줌아웃이 되는 상황에서도 마찬가지입니다. 그냥 그대로 카메라에 고정되어 있습니다.

Scened의 백그라운드로 지정되는 것과 Scene의 자식노드로 부착(attached)되는 것은 분명히 다릅니다. 전자는 카메라가 이동해도 움직이지 않지만, 후자는 카메라의 이동에 영향을 받아 화면 내에서 움직이게 되거든요.

그럼, AndEngine을 이용해서 배경을 처리하는 방법들을 차례 차례 살펴보도록 하겠습니다. 우선은 카메라의 움직임을 상관하지 않고 처리할 수 있는 방법부터 시작하겠습니다.


EntityBackground 타입의 활용

이 방법은   여러 Entity(혹은 그 하위) 객체들을 함께 묶어서 씬의 배경으로 사용하고 싶을 때 유용합니다. 다음 코드에서는 Entity 객체로 레이어를 만들고, 거기에 색깔을 가진 두 개의 Rectangle 객체를 붙인다음, 그것을 EntityBackground 객체를 통해 씬의 배경으로 처리하는 방법입니다.

public class UsingBackgroundActivity extends BaseGameActivity {

   public static int WIDTH = 1280;
   public static int HEIGHT = 720;

   private Scene mScene;
   private Camera mCamera;
   
   @Override
   public EngineOptions onCreateEngineOptions() {
      mCamera = new Camera(0, 0, WIDTH, HEIGHT);
      EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(), mCamera);

      return engineOptions;
   }

   @Override
   public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws IOException {
      //TODO
      pOnCreateResourcesCallback.onCreateResourcesFinished();
   }

   @Override
   public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws IOException {
      mScene = new Scene();
      
      Rectangle left = new Rectangle(100, 100, 100, 100, mEngine.getVertexBufferObjectManager());
      Rectangle right = new Rectangle(WIDTH-100, HEIGHT-100, 100, 100, mEngine.getVertexBufferObjectManager());

      Entity backgroundEntity = new Entity();
      backgroundEntity.attachChild(left);
      backgroundEntity.attachChild(right);

      EntityBackground entityBackground = new EntityBackground(0, 0, 1, backgroundEntity);
       
      mScene.setBackground(entityBackground);
      mScene.setBackgroundEnabled(true);

      pOnCreateSceneCallback.onCreateSceneFinished(mScene);
   }

   @Override
   public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws IOException {
      //TODO
      pOnPopulateSceneCallback.onPopulateSceneFinished();
   }

}





SpriteBackground의 활용

SpriteBackground는 단일 sprite를 배경으로 사용할 수 있게 해줍니다. 여기서 주의할 점은 카메라의 크기에 맞춰 sprite의 크기가 변화하진 않는 다는 점입니다. 처음 sprite를 생성할 때의 크기 그대로 사용됩니다. 카메라 화면의 크기에 맞게 하려면 그만한 크기로 sprite를 생성해야 하겠죠.

public class UsingBackgroundActivity extends BaseGameActivity {

   public static int WIDTH = 1280;
   public static int HEIGHT = 720;

   private Scene mScene;
   private Camera mCamera;
   private ITextureRegion mBackgroundTextureRegion;
   
   @Override
   public EngineOptions onCreateEngineOptions() {
      mCamera = new Camera(0, 0, WIDTH, HEIGHT);
      EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(), mCamera);

      return engineOptions;
   }

   @Override
   public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws IOException {
      BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
      BuildableBitmapTextureAtlas mBitmapTextureAtlas = new BuildableBitmapTextureAtlas(mEngine.getTextureManager(), 1024, 1024);
      mBackgroundTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBitmapTextureAtlas, UsingBackgroundActivity.this, "bg_800x480.png");
      
      try{
         mBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder(0, 1, 1));
         mBitmapTextureAtlas.load();
      }catch(TextureAtlasBuilderException e){
         Debug.e(e);
      }
      pOnCreateResourcesCallback.onCreateResourcesFinished();
   }

   @Override
   public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws IOException {
      mScene = new Scene();
      
      Sprite sprite = new Sprite(WIDTH*0.5F, HEIGHT*0.5F, mBackgroundTextureRegion, mEngine.getVertexBufferObjectManager());
      SpriteBackground spriteBackground = new SpriteBackground(1,1,1, sprite);
            
      mScene.setBackground(spriteBackground);
      mScene.setBackgroundEnabled(true);
      
      pOnCreateSceneCallback.onCreateSceneFinished(mScene);
   }

   @Override
   public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws IOException {
      //TODO: 
      pOnPopulateSceneCallback.onPopulateSceneFinished();
   }

}

[ 전체 크기보다 작은 텍스쳐를 이용해서 처리하다보니, 이렇게 남는 공간이 생깁니다]



RepeatingBackground의 활용

RepeatingBackground는 크기가 작은 텍스쳐로 전체 화면을 '반복해서' 채우는 방식입니다. 축구장의 잔디밭, 아니면 사막 등을 표현하는데 유용하겠네요. 그래서, 우측의 128 x 128 pixel 크기의 이미지를 이용해서 축구장을 표현해보려 합니다.

여기서 주의깊게 봐야 할 것은 이전 처럼 텍스쳐 객체를 생성할 때와는 다르게 AssetBitmapTexture 클래스를 이용해서 반복 처리할 이미지를 로드한다는 것입니다. 그리고 나서 ITextureRegion 타입으로 추출해 줍니다.(텍스쳐 옵션은 아래 코드처럼 TextureOptions.REPEATING_BILINEAR 로 하거나 TextureOptions.REPEATING_NEAREST로 하시면 됩니다)

그리고, 텍스쳐를 제대로 반복처리할 수 있도록 이미지의 크기는 2의 거듭 제곱으로 가지고 있어야 합니다. OpenGL이 그러한 크기의 텍스쳐를 요구하거든요. 그럼, 코드를 보겠습니다

public class UsingBackgroundActivity extends BaseGameActivity {

   public static int WIDTH = 1280;
   public static int HEIGHT = 720;

   private Scene mScene;
   private Camera mCamera;
   ITextureRegion mRepeatingTextureRegion ;
   
   @Override
   public EngineOptions onCreateEngineOptions() {
      mCamera = new Camera(0, 0, WIDTH, HEIGHT);
      EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(), mCamera);

      return engineOptions;
   }

   @Override
   public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws IOException {
      AssetBitmapTexture mBitmapTexture = null;      
      try{
         mBitmapTexture = new AssetBitmapTexture(mEngine.getTextureManager(), this.getAssets(), "gfx/grass.png", BitmapTextureFormat.RGB_565, TextureOptions.REPEATING_BILINEAR);
      }catch(IOException ex){
         ex.printStackTrace();
      }
      mBitmapTexture.load();
      mRepeatingTextureRegion = TextureRegionFactory.extractFromTexture(mBitmapTexture);
      
      pOnCreateResourcesCallback.onCreateResourcesFinished();
   }

   @Override
   public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws IOException {
      mScene = new Scene();
            
      final float cameraWidth  = WIDTH;
      final float cameraHeight = HEIGHT;
      final float repeatingScale = 1f;

      RepeatingSpriteBackground repeatingBackground = new RepeatingSpriteBackground(cameraWidth, cameraHeight, mRepeatingTextureRegion, repeatingScale, mEngine.getVertexBufferObjectManager());
           
      mScene.setBackground(spriteBackground);
      mScene.setBackgroundEnabled(true);
      
      pOnCreateSceneCallback.onCreateSceneFinished(mScene);
   }

   @Override
   public void onPopulateScene(Scene pScene,
      //TODO
      pOnPopulateSceneCallback.onPopulateSceneFinished();
   }

}





ParallaxBackground의 활용 : 원근법 표현
(카메라의 움직임에 따른 ParallaxBackground 활용)

이전 까지의 정적인 배경 표현에서 벗어나 시각적으로 좀 더 효과적인 (원근법)을 표현할 수 있는 방법을 알아보려 합니다. 2D라는 한계가 있다보니 화면에 표시되는 텍스쳐들의 시차(視差, parallax : 보는 위치에 따라 물체의 방향이나 크기가 차이나게 보이는 것)를 인위적으로 만들어내는 방식으로 표현하려고 해요.

차를 타고 달릴 때를 가정해 보면, 눈에 가까운 가로수들은 아주 빠르게 창밖을 스쳐가지만 멀리있는 산들은 아주 조금씩 움직이잖아요? 그러한 효과를 만들어내기 좋은 게 ParallaxBackground거든요.

우선 코드를 보며 더 설명해 볼까요? 먼저 인스턴스 변수들이 선언된 걸 보죠.

   public static int CAMERA_WIDTH = 1280;
   public static int CAMERA_HEIGHT = 800;
   
   private static final float CAMERA_MIN_CENTER_X = CAMERA_WIDTH * 0.5f - 25;
   private static final float CAMERA_MAX_CENTER_X = CAMERA_WIDTH * 0.5f + 25;
   
   private static final float SCROLL_FACTOR = 5;
   
   private Scene mScene;
   private Camera mCamera;
   
   private ITextureRegion mHillTextureRegion;

이전과 다른 부분이 CAMERA_MIN_CENTER_X, CAMERA_MAX_CENTER_X 입니다. 이 둘은 카메라의 움직임을 좌우로 제한하기 위한 값입니다. 좌우로 범위를 가지고 카메라가 우로 움직이다 MAX 갑에 도달하면 다시 반대로 움직이게 하려는 것이죠. SCROLL_FACTOR는 카메라 움직임의 빠르기를 정해주기 위해 설정한 값입니다.
그리고, 마지막 mHillTextureRegion은 원근을 표시할 별도의 텍스쳐를 위한 변수입니다.(아래 그림을 위한 것입니다)



다음은 onCreateEngineOptions() 메소드인데요. Camera 객체의 생성이 이전과 많이 다릅니다.

   @Override
   public EngineOptions onCreateEngineOptions() {
      mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT){
         // 카메라 위치 값 중 x 좌표를 증가시키거나 감소시킬지를 결정하는 값입니다 
         boolean incrementX = true;

         @Override
         public void onUpdate(float pSecondsElapsed) {
            // 카메라의 이동 속도(SCROLL_FACTOR)와 pSeconeElapsed값을 곱해서 현재의 위치에 더해서 다음 카메라 위치를 잡아 줍니다 
            // 그러다 한계치에 도달하면 방향을 바꾸죠. 반대 방향도 마찬가지구요  
            final float currentCenterX = this.getCenterX();
            float offsetCenterX = 0;
            if(incrementX){
               offsetCenterX = currentCenterX + pSecondsElapsed * SCROLL_FACTOR;
               if(offsetCenterX >= CAMERA_MAX_CENTER_X){
                  incrementX = false;
               }
            }else{
               offsetCenterX = currentCenterX - pSecondsElapsed * SCROLL_FACTOR;
               if(offsetCenterX <= CAMERA_MIN_CENTER_X   ){
                  incrementX = true;
               }
            }
            
            this.setCenter(offsetCenterX, this.getCenterY());
            super.onUpdate(pSecondsElapsed);
         }
      };
      
      EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(), mCamera);
      return engineOptions;
   }



onCreateResources() 메소드에서의 처리는 이전과 별반 다른게 없으니, 맨 아래 전체 코드를 그냥 읽어보시면 될테구요. 중요한 게 onCreateScene()메소드죠.

   @Override
   public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws IOException {

      mScene = new Scene();
      
      final float textureHeight = mHillTextureRegion.getHeight();

      // 세 개의 sprite를 준비하는데, 각각의 위치가 조금씩 다릅니다. X좌표는 동일한데 Y좌표만 가장 먼 것(hillFurthest)이 
      // 가장 가까운 것에 비해 더 높게 위치하겠네요.
      Sprite hillFurthest = new Sprite(CAMERA_WIDTH * 0.5f, textureHeight * 0.5f + 80, mHillTextureRegion, mEngine.getVertexBufferObjectManager());
      Sprite hillMid = new Sprite(CAMERA_WIDTH * 0.5f, textureHeight * 0.5f + 40, mHillTextureRegion, mEngine.getVertexBufferObjectManager());
      Sprite hillClosest = new Sprite(CAMERA_WIDTH * 0.5f, textureHeight * 0.5f + 0, mHillTextureRegion, mEngine.getVertexBufferObjectManager());

      // ParallaxBackground의 기본 색은 푸른 하늘을 표현하기 위해 (0.3f, 0.3f, 0.9f)로 생성되네요.
      // 그리고, update() 내에서는 시간이 업데이트 될 때마다 parallax value를 조정해서 
      // 각각의 ParallaxEntity들이 이전과는 다른 위치에 나타나도록 합니다. 여기에서는 카메라의 위치가
      // 새로운 위치를 정하기 위한 산출값 중의 하나로 사용이 됩니다. 물론, 카메라가 움직이지 않는다고 하더라도
      // (그런 경우는 화면 중앙의 Entity가 가만 있는데도 배경이 흐르는 상황이겠네요)
      // parallaxValueOffset 값을 임으로 증가시키거나 감소시켜 비슷한 효과를 낼 수도 있습니다. 아무튼
      // 여기서는 카메라의 위치에 따른 변화를 만들어내고 있습니다.
      ParallaxBackground background = new ParallaxBackground(0.3f, 0.3f, 0.9f){
         float cameraPreviousX = 0;
         float parallaxValueOffset = 0;
         @Override
         public void onUpdate(float pSecondsElapsed) {
            final float cameraCurrentX = mCamera.getCenterX();
            if(cameraPreviousX != cameraCurrentX){
                  parallaxValueOffset -= cameraCurrentX - cameraPreviousX;   
                  this.setParallaxValue(parallaxValueOffset);
                  cameraPreviousX = cameraCurrentX;
            }
            super.onUpdate(pSecondsElapsed);
         }
         
      };
      // ParallaxEntity 객체 생성자의 첫 번째 인자는 parallax factor 값이며 값이 높을 수록 더 빨리 움직입니다.
      // 이 값에 의해 맨 뒤의 이미지는 천천히, 맨 앞의 이미지는 그 보다 빠르게 움직이는 것입니다.
      background.attachParallaxEntity(new ParallaxEntity(5, hillFurthest));
      background.attachParallaxEntity(new ParallaxEntity(10, hillMid));
      background.attachParallaxEntity(new ParallaxEntity(15, hillClosest));      
      
      mScene.setBackground(background);
      mScene.setBackgroundEnabled(true);
      
      pOnCreateSceneCallback.onCreateSceneFinished(mScene);
   }



그럼 전체 코드를 볼까요?

package how2quit.tutorial.chapter3;

import java.io.IOException;

import org.andengine.engine.camera.Camera;
import org.andengine.engine.options.EngineOptions;
import org.andengine.engine.options.ScreenOrientation;
import org.andengine.engine.options.resolutionpolicy.FillResolutionPolicy;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.scene.background.ParallaxBackground;
import org.andengine.entity.scene.background.ParallaxBackground.ParallaxEntity;
import org.andengine.entity.sprite.Sprite;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlasTextureRegionFactory;
import org.andengine.opengl.texture.atlas.bitmap.BuildableBitmapTextureAtlas;
import org.andengine.opengl.texture.atlas.bitmap.source.IBitmapTextureAtlasSource;
import org.andengine.opengl.texture.atlas.buildable.builder.BlackPawnTextureAtlasBuilder;
import org.andengine.opengl.texture.atlas.buildable.builder.ITextureAtlasBuilder.TextureAtlasBuilderException;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.ui.activity.BaseGameActivity;

public class UsingParallaxBackground extends BaseGameActivity {
   
   public static int CAMERA_WIDTH = 1280;
   public static int CAMERA_HEIGHT = 800;
   
   private static final float CAMERA_MIN_CENTER_X = CAMERA_WIDTH * 0.5f - 25;
   private static final float CAMERA_MAX_CENTER_X = CAMERA_WIDTH * 0.5f + 25;
   
   private static final float SCROLL_FACTOR = 5;
   
   private Scene mScene;
   private Camera mCamera;
   
   private ITextureRegion mHillTextureRegion;

   @Override
   public EngineOptions onCreateEngineOptions() {
      mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT){
          
         boolean incrementX = true;

         @Override
         public void onUpdate(float pSecondsElapsed) {

            final float currentCenterX = this.getCenterX();
            float offsetCenterX = 0;
            if(incrementX){
               offsetCenterX = currentCenterX + pSecondsElapsed * SCROLL_FACTOR;
               if(offsetCenterX >= CAMERA_MAX_CENTER_X){
                  incrementX = false;
               }
            }else{
               offsetCenterX = currentCenterX - pSecondsElapsed * SCROLL_FACTOR;
               if(offsetCenterX <= CAMERA_MIN_CENTER_X   ){
                  incrementX = true;
               }
            }
            
            this.setCenter(offsetCenterX, this.getCenterY());
            super.onUpdate(pSecondsElapsed);
         }
      };
      
      EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(), mCamera);
      return engineOptions;
   }

   @Override
   public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws IOException {
      BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
      BuildableBitmapTextureAtlas bitmapTextureAtlas = new BuildableBitmapTextureAtlas(mEngine.getTextureManager(), 1280, 250);
      mHillTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(bitmapTextureAtlas, getAssets(), "hill.png");
      
      try{
         bitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource BitmapTextureAtlas>(0, 0, 0));
      }catch(TextureAtlasBuilderException e){
         e.printStackTrace();
      }
      bitmapTextureAtlas.load();
      
      pOnCreateResourcesCallback.onCreateResourcesFinished();
   }

   @Override
   public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws IOException {
      mScene = new Scene();
      
      final float textureHeight = mHillTextureRegion.getHeight();
      
      Sprite hillFurthest = new Sprite(CAMERA_WIDTH * 0.5f, textureHeight * 0.5f + 80, mHillTextureRegion, mEngine.getVertexBufferObjectManager());
      Sprite hillMid = new Sprite(CAMERA_WIDTH * 0.5f, textureHeight * 0.5f + 40, mHillTextureRegion, mEngine.getVertexBufferObjectManager());
      Sprite hillClosest = new Sprite(CAMERA_WIDTH * 0.5f, textureHeight * 0.5f + 0, mHillTextureRegion, mEngine.getVertexBufferObjectManager());
      
      ParallaxBackground background = new ParallaxBackground(0.3f, 0.3f, 0.9f){
         float cameraPreviousX = 0;
         float parallaxValueOffset = 0;
         @Override
         public void onUpdate(float pSecondsElapsed) {
            final float cameraCurrentX = mCamera.getCenterX();
            if(cameraPreviousX != cameraCurrentX){
                  parallaxValueOffset -= cameraCurrentX - cameraPreviousX;   
                  this.setParallaxValue(parallaxValueOffset);
                  cameraPreviousX = cameraCurrentX;
            }
            super.onUpdate(pSecondsElapsed);
         }
         
      };
       
      background.attachParallaxEntity(new ParallaxEntity(5, hillFurthest));
      background.attachParallaxEntity(new ParallaxEntity(10, hillMid));
      background.attachParallaxEntity(new ParallaxEntity(15, hillClosest));      
      
      mScene.setBackground(background);
      mScene.setBackgroundEnabled(true);
      
      pOnCreateSceneCallback.onCreateSceneFinished(mScene);
   }

   @Override
   public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws IOException {
      pOnPopulateSceneCallback.onPopulateSceneFinished();
   }

}







AutoParallaxBackground의 활용

ParallaxBackground와 비슷한 동작을 하는 또 다른 클래스가 있어요. AutoParallaxBackground 클래스가 그것인데요. 둘 사이의 다른 점이라면 카메라의 움직임과는 상관없이 ParallaxEntity가 일정한 속도(개발자가 속도를 원하는 데로 정합니다)로 화면을 가로질러 가도록 할 수가 있다는 것입니다.

하늘 위로 구름이 지나도록 하거나, 아니면 레이싱 게임에서는 '일정한 속도'로 지나쳐 가는 주변 풍경 등을 묘사할 때 유용하겠네요. 중요한 것은 '일정한 속도'라는 것입니다. 만약, 어떤 오브젝트가 있고, 그 오브젝트의 움직임에 맞춰 상대적으로 속도를 정하는 경우라면 적절하지 않을 거예요.

카메라의 움직임과는 상관 없다고 했으니, 이전 코드에서 카메라 객체를 생성하며 재정의 했던 onUpdate()메소드 부분은 과감히 지워버립시다.

   @Override
   public EngineOptions onCreateEngineOptions() {
      mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
      
      EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(), mCamera);
      return engineOptions;
   }



그리고, ParallaxBackground를 생성하던 부분도 아래처럼 수정해주세요.

   @Override
   public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws IOException {
      mScene = new Scene();
      
      final float textureHeight = mHillTextureRegion.getHeight();
      
      Sprite hillFurthest = new Sprite(CAMERA_WIDTH * 0.5f, textureHeight * 0.5f + 80, mHillTextureRegion, mEngine.getVertexBufferObjectManager());
      Sprite hillMid = new Sprite(CAMERA_WIDTH * 0.5f, textureHeight * 0.5f + 40, mHillTextureRegion, mEngine.getVertexBufferObjectManager());
      Sprite hillClosest = new Sprite(CAMERA_WIDTH * 0.5f, textureHeight * 0.5f + 0, mHillTextureRegion, mEngine.getVertexBufferObjectManager());
      
      final float autoParallaxSpeed = -50; //일부러 속도를 빠르게 해봤습니다. 음수 양수로 바꿔가며 해보세요.
      AutoParallaxBackground background = new AutoParallaxBackground(0.3f, 0.3f, 0.9f, autoParallaxSpeed);
      
      background.attachParallaxEntity(new ParallaxEntity(5, hillFurthest));
      background.attachParallaxEntity(new ParallaxEntity(10, hillMid));
      background.attachParallaxEntity(new ParallaxEntity(15, hillClosest));      
      
      mScene.setBackground(background);
      mScene.setBackgroundEnabled(true);
      
      pOnCreateSceneCallback.onCreateSceneFinished(mScene);
   }

실행화면은 이전과 거의 비슷하니(카메라를 따라다니는 Entity가 없다보니) 생략할께요.







No comments:

Post a Comment