package com.ninjarun;

import java.util.ArrayList;

import javax.microedition.khronos.opengles.GL10;

import org.andengine.engine.camera.Camera;
import org.andengine.engine.camera.SmoothCamera;
import org.andengine.engine.camera.hud.HUD;
import org.andengine.engine.camera.hud.controls.BaseOnScreenControl;
import org.andengine.engine.camera.hud.controls.BaseOnScreenControl.IOnScreenControlListener;
import org.andengine.engine.camera.hud.controls.DigitalOnScreenControl;
import org.andengine.engine.handler.IUpdateHandler;
import org.andengine.engine.options.EngineOptions;
import org.andengine.engine.options.ScreenOrientation;
import org.andengine.engine.options.resolutionpolicy.FillResolutionPolicy;
import org.andengine.entity.IEntity;
import org.andengine.entity.modifier.ScaleModifier;
import org.andengine.entity.primitive.Rectangle;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.shape.IShape;
import org.andengine.entity.sprite.AnimatedSprite;
import org.andengine.entity.sprite.Sprite;
import org.andengine.entity.text.Text;
import org.andengine.entity.util.FPSLogger;
import org.andengine.extension.physics.box2d.PhysicsConnector;
import org.andengine.extension.physics.box2d.PhysicsFactory;
import org.andengine.extension.physics.box2d.PhysicsWorld;
import org.andengine.extension.tmx.TMXLayer;
import org.andengine.extension.tmx.TMXLoader;
import org.andengine.extension.tmx.TMXLoader.ITMXTilePropertiesListener;
import org.andengine.extension.tmx.TMXObject;
import org.andengine.extension.tmx.TMXObjectGroup;
import org.andengine.extension.tmx.TMXProperties;
import org.andengine.extension.tmx.TMXTile;
import org.andengine.extension.tmx.TMXTileProperty;
import org.andengine.extension.tmx.TMXTiledMap;
import org.andengine.extension.tmx.util.exception.TMXLoadException;
import org.andengine.input.touch.TouchEvent;
import org.andengine.opengl.texture.TextureOptions;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlasTextureRegionFactory;
import org.andengine.opengl.texture.region.TextureRegion;
import org.andengine.opengl.texture.region.TiledTextureRegion;
import org.andengine.ui.activity.SimpleBaseGameActivity;
import org.andengine.util.debug.Debug;

import android.hardware.SensorManager;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.Manifold;

public class MainActivity_5_2_9_1 extends SimpleBaseGameActivity {
	private SmoothCamera camera;

	private static final int CAMERA_WIDTH = 800;
	private static final int CAMERA_HEIGHT = 480;

	public Scene mScene;
	private TMXTiledMap mTMXTiledMap;

	private BitmapTextureAtlas mBitmapTextureAtlas;

	public TiledTextureRegion mPlayer_MPTextureRegion;
	private TiledTextureRegion mPlayerTextureRegion;

	// 적 선언
	private TiledTextureRegion mEnemyTextureRegion;
	// 총알 선언
	private TiledTextureRegion mShurikenTextureRegion;

	public PhysicsWorld mPhysicsWorld;

	private static int mapOffset = 100;

	// FixtureDefs 생성을 위한 Category bits
	private static final short CATEGORYBIT_WALL = 0x0001;
	private static final short CATEGORYBIT_PLAYER = 0x0002;
	private static final short CATEGORYBIT_ENEMY = 0x0004;
	private static final short CATEGORYBIT_SHURIKEN = 0x0008;

	// FixtureDefs 생성을 위한 Mask bits
	private static final short MASKBITS_WALL = -1;

	private static final short MASKBITS_PLAYER = CATEGORYBIT_WALL
			+ CATEGORYBIT_ENEMY;

	private static final short MASKBITS_ENEMY = CATEGORYBIT_WALL
			+ CATEGORYBIT_SHURIKEN +  CATEGORYBIT_PLAYER;

	private static final short MASKBITS_SHURIKEN = CATEGORYBIT_WALL
			+ CATEGORYBIT_ENEMY;

	public ArrayList<IEntity> mEntityList;
	private static AnimatedSprite player_sprite;
	private static Body player_body;
	private FixtureDef boxFixtureDef;
	private Rectangle rect;

	// 컨트롤러 선언
	private DigitalOnScreenControl mDigitalOnScreenControl;
	private TextureRegion mOnScreenControlBaseTextureRegion;
	private TextureRegion mOnScreenControlKnobTextureRegion;

	private BitmapTextureAtlas mOnScreenControlTexture;
	private BitmapTextureAtlas mHUDTextureAtlas;

	// 컨트롤러에 따른 플레이어 캐릭터의 방향 지정
	private static float mLinearVelocityX = 8.0f;
	public boolean isPlayerMoving = false;

	// 점프에 대한 셋팅
	private TextureRegion mJumpTextureRegion;
	public HUD mHUD;
	private boolean isLanded = false;
	public static float mImpulseY = 10f;
	private String fix1_name = "", fix2_name = "";

	// --------------------------------------
	// 표창의 추가에 따른 변수 선언
	// --------------------------------------
	// 플레이어의 방향 값 설정
	public static final short PLAYER_TO_RIGHT = 1;
	public static final short PLAYER_TO_LEFT = -1;
	private int shurikenCount = 0;
	private boolean shurikenPresent = false;
	private TextureRegion mShootTextureRegion;
	public int playerDir = 1;

	private boolean desShuriken = false; // 표창의 메모리 해제를 위해
	private boolean doShuriken = false; // 표창을 던지고 있는 중인가 체크

	// --------------------------------------
	// 적 출현에 대한 선언
	// --------------------------------------
	public int enemyCount = 0;
	boolean[] enemyLandedArr;
	public int remainingEnemies;

	@Override
	public EngineOptions onCreateEngineOptions() {
		this.camera = new SmoothCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT, 200,
				200, 1.0f);

		EngineOptions engineOptions = new EngineOptions(true,
				ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(),
				camera);

		return engineOptions;
	}

	@Override
	protected void onCreateResources() {
		BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");

		// 닌자, 적, 표창 이미지 호출
		loadImages();

		// 컨트롤러 이미지 호출
		loadControllers();
	}

	// ---------------------------------------------------------
	// DigitalOnScreenControl 추가
	// ---------------------------------------------------------
	public void callController() {

		// HUD란 모니터 화면, 게임 화면을 의미합니다.
		// HUD에 붙여야 카메라가 이동해도 항상 화면의 일정한 곳에 텍스트 점수, 버튼이 고정됩니다.
		mHUD = new HUD();

		// ------------------------------------------------------
		// 점프 버튼을 생성하고 HUD에 붙입니다.
		// ------------------------------------------------------
		Sprite jump = new Sprite(CAMERA_WIDTH - 120, CAMERA_HEIGHT - 175,
				mJumpTextureRegion, this.getVertexBufferObjectManager()) {

			@Override
			public boolean onAreaTouched(TouchEvent pEvent, float pX, float pY) {

				// 점프 버튼이 클릭되고, 플레이어 캐릭이 땅에 도착했을 때 점프한다.
				if (pEvent.isActionDown() && isLanded) {

					jumpPlayer(player_body);

					isLanded = false;
				}
				if (pEvent.isActionUp()) {

					isLanded = false;

				}
				return false;

			}

		};
		jump.setScale(0.70f);
		mHUD.registerTouchArea(jump);
		mHUD.attachChild(jump);

		// ------------------------------------------------------
		// 표창을 던지는 버튼을 생성하고 HUD에 붙입니다.
		// ------------------------------------------------------
		Sprite shoot = new Sprite(CAMERA_WIDTH - 200, CAMERA_HEIGHT - 110,
				mShootTextureRegion, this.getVertexBufferObjectManager()) {
			@Override
			public boolean onAreaTouched(TouchEvent pEvent, float pX, float pY) {

				if (pEvent.isActionDown()) {

					if (!doShuriken) {
						// 표창을 던지되 플레이어가 바라보고 있는 방향으로 쏘아야 합니다.
						// 그래서 playerDir이 필요합니다.
						showShuriken(player_sprite, playerDir, "shuriken");
					}
					doShuriken = true;
				}

				if (pEvent.isActionUp()) {
					doShuriken = false;
				}
				return false;
			}
		};
		shoot.setScale(0.60f);
		mHUD.registerTouchArea(shoot);
		mHUD.attachChild(shoot);

		// mCamera에 mHUD를 붙여야 캐릭터가 화면을 벗어나 이동을 해도 버튼이 그대로 머물게 됩니다.
		// 그리고 이 라인이 있어야 점프 버튼이 보입니다.
		camera.setHUD(mHUD);

		// ----------------------------------------------------------------------
		// 컨트롤러 셋팅
		// ----------------------------------------------------------------------
		this.mDigitalOnScreenControl = new DigitalOnScreenControl(10,
				CAMERA_HEIGHT
						- this.mOnScreenControlBaseTextureRegion.getHeight()
						- 5, this.camera,
				this.mOnScreenControlBaseTextureRegion,
				this.mOnScreenControlKnobTextureRegion, 0.1f,
				this.getVertexBufferObjectManager(),
				new IOnScreenControlListener() {

					@Override
					public void onControlChange(
							final BaseOnScreenControl pBaseOnScreenControl,
							final float pValueX, final float pValueY) {

						// 컨트롤러 우측 방향 && 캐릭터가 뛰고 있지 않을 때
						if (pValueX > 0 && !player_sprite.isAnimationRunning()) {
							movePlayerRight(player_sprite, player_body);
						}

						// 컨트롤러 좌측 방향 && 캐릭터가 뛰고 있지 않을 때
						else if (pValueX < 0
								&& !player_sprite.isAnimationRunning()) {
							movePlayerLeft(player_sprite, player_body);
						}

						// 컨트롤러가 중앙에 있고, 플레이어가 움직이고 있다면 플레이어를 멈춘다.
						else if (pValueX == 0) {
							if (isPlayerMoving) {
								stopPlayer(player_sprite, player_body);
								isPlayerMoving = false;
							}
						}
					}
				});

		this.mDigitalOnScreenControl.getControlBase().setBlendFunction(
				GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
		this.mDigitalOnScreenControl.getControlBase().setAlpha(0.55f);
		this.mDigitalOnScreenControl.getControlBase().setScaleCenter(0, 128);
		this.mDigitalOnScreenControl.getControlBase().setScale(1.5f);
		this.mDigitalOnScreenControl.getControlKnob().setAlpha(0.40f);
		this.mDigitalOnScreenControl.getControlKnob().setScale(0.7f);
		this.mDigitalOnScreenControl.refreshControlKnobPosition();
		this.mDigitalOnScreenControl.setAllowDiagonal(false);

	}

	/***********************************************************
	 * 
	 * 표창이 나타나게 하고, UserData에 이름을 셋팅합니다.
	 * 
	 ***********************************************************/

	public void showShuriken(AnimatedSprite _playerSprite, int _playerDir,
			String shurikenName) {
		// 메모리 해제에 사용되므로 표창의 갯수 카운팅은 매우 중요합니다.
		shurikenCount++;

		AnimatedSprite mShurikenSprite = new AnimatedSprite(
				_playerSprite.getX() + _playerDir, _playerSprite.getY()
						+ _playerSprite.getHeight() / 4,
				mShurikenTextureRegion, this.getVertexBufferObjectManager());

		FixtureDef mShurikenFixtureDef = PhysicsFactory.createFixtureDef(0, 0f,
				0f, false, CATEGORYBIT_SHURIKEN, MASKBITS_SHURIKEN, (short) 0);

		Body mShurikenBody = PhysicsFactory.createCircleBody(
				this.mPhysicsWorld, mShurikenSprite, BodyType.DynamicBody,
				mShurikenFixtureDef);
		mShurikenBody.setUserData(shurikenName + shurikenCount);

		// AndEngine에는 Body에 총알 같은 움직임을 적용하는 setBullet()이라는 함수가 별도로 존재합니다.
		mShurikenBody.setBullet(true);

		this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(
				mShurikenSprite, mShurikenBody, true, true));
		mShurikenSprite.setUserData(shurikenName + shurikenCount);
		mShurikenBody.setLinearVelocity(_playerDir * 20, 0);

		mScene.attachChild(mShurikenSprite);

		shurikenPresent = true;
		mEntityList.add(mShurikenSprite);
	}

	/*********************************************************
	 * 
	 * mImpulseY 값 만큼 점프합니다.
	 * 
	 *********************************************************/

	public void jumpPlayer(Body _playerBody) {

		_playerBody.applyLinearImpulse(0, -mImpulseY,
				_playerBody.getPosition().x, _playerBody.getPosition().y);

	}

	/*********************************************************
	 * 
	 * 컨트롤러 동작에 따른 플레이어 캐릭터 우측으로 움직이기
	 * 
	 *********************************************************/

	public void movePlayerRight(AnimatedSprite _playerSprite, Body _playerBody) {
		_playerBody.setLinearVelocity(mLinearVelocityX,
				_playerBody.getLinearVelocity().y);
		// 4컷자리 이미지
		_playerSprite.animate(new long[] { 50, 50, 50, 50 }, 0, 3, false);

		if (_playerSprite.getUserData().toString().contains("player_self")) {
			isPlayerMoving = true;
			playerDir = PLAYER_TO_RIGHT;
		}

	}

	/*********************************************************
	 * 
	 * 컨트롤러 동작에 따른 플레이어 캐릭터 좌측으로 움직이기
	 * 
	 *********************************************************/

	public void movePlayerLeft(AnimatedSprite _playerSprite, Body _playerBody) {

		_playerBody.setLinearVelocity(-mLinearVelocityX,
				_playerBody.getLinearVelocity().y);

		_playerSprite.animate(new long[] { 50, 50, 50, 50 }, 0, 3, false);

		if (_playerSprite.getUserData().toString().contains("player_self")) {
			isPlayerMoving = true;
			playerDir = PLAYER_TO_LEFT;
		}
	}

	/*********************************************************
	 * 
	 * 컨트롤러 동작에 따른 플레이어 캐릭터 멈추기
	 * 
	 *********************************************************/

	public void stopPlayer(AnimatedSprite _playerSprite, Body _playerBody) {
		_playerBody.setLinearVelocity(0f, _playerBody.getLinearVelocity().y);
	}

	/*********************************************************
	 * 
	 * 컨트롤러 구성을 위한 이미지를 추가합니다.
	 * 
	 *********************************************************/

	private void loadControllers() {

		// create atlas of 512*1024
		this.mHUDTextureAtlas = new BitmapTextureAtlas(
				this.getTextureManager(), 512, 1024,
				TextureOptions.BILINEAR_PREMULTIPLYALPHA);

		// 점프 버튼 이미지 로드
		this.mJumpTextureRegion = BitmapTextureAtlasTextureRegionFactory
				.createFromAsset(this.mHUDTextureAtlas, this, "jump.png", 0,
						128);

		// 표창 던지기 버튼 이미지 로드
		this.mShootTextureRegion = BitmapTextureAtlasTextureRegionFactory
				.createFromAsset(mHUDTextureAtlas, this, "shoot.png", 128, 0);

		this.mEngine.getTextureManager().loadTexture(this.mHUDTextureAtlas);

		// create atlas of 256*128
		this.mOnScreenControlTexture = new BitmapTextureAtlas(
				this.getTextureManager(), 256, 128,
				TextureOptions.BILINEAR_PREMULTIPLYALPHA);

		// 컨트롤러 이미지를 atlas에 추가
		this.mOnScreenControlBaseTextureRegion = BitmapTextureAtlasTextureRegionFactory
				.createFromAsset(this.mOnScreenControlTexture, this,
						"onscreen_control_base.png", 0, 0);

		this.mOnScreenControlKnobTextureRegion = BitmapTextureAtlasTextureRegionFactory
				.createFromAsset(this.mOnScreenControlTexture, this,
						"onscreen_control_knob.png", 128, 0);

		// 디지털 컨트롤의 이미지 출력
		this.mEngine.getTextureManager().loadTexture(
				this.mOnScreenControlTexture);

	}

	private void loadImages() {

		// create atlas of 512*512
		this.mBitmapTextureAtlas = new BitmapTextureAtlas(
				this.getTextureManager(), 1024, 1025, TextureOptions.BILINEAR);

		// 플레이어 캐릭터
		this.mPlayerTextureRegion = BitmapTextureAtlasTextureRegionFactory
				.createTiledFromAsset(this.mBitmapTextureAtlas, this,
						"ninja.gif", 0, 0, 4, 1);

		// 적 캐릭터
		// 적캐릭터 경우는 그냥 원하는대로 만들어서 넣으면 됩니다.
		this.mEnemyTextureRegion = BitmapTextureAtlasTextureRegionFactory
				.createTiledFromAsset(this.mBitmapTextureAtlas, this,
						"enemy.gif", 0, 128, 4, 1);

		// 표창
		this.mShurikenTextureRegion = BitmapTextureAtlasTextureRegionFactory
				.createTiledFromAsset(this.mBitmapTextureAtlas, this,
						"weapon.gif", 0, 256, 1, 1);

		this.mBitmapTextureAtlas.load();
	}

	/*************************************************************
	 * 
	 * 충돌에 대한 처리를 합니다.
	 * 
	 *************************************************************/
	private void createCollision() {
		this.mPhysicsWorld.setContactListener(new ContactListener() {

			public void beginContact(Contact contact) {

				// 2개의 바디가 부딪혔을 때 userData를 가져옵니다.
				if (contact.getFixtureA().getBody().getUserData() != null
						&& contact.getFixtureA().getBody().getUserData() != null) {

					fix1_name = contact.getFixtureA().getBody().getUserData()
							.toString();
					fix2_name = contact.getFixtureB().getBody().getUserData()
							.toString();

				} else {
					fix1_name = "";
					fix2_name = "";
				}

				// player와 벽속성이 부딪힐때 isLanded를 true로 설정합니다.
				// 이렇게 설정해야 점프 버튼 클릭시 계속해서 높이 떠오르는 것을 막을 수 있습니다.
				if ((fix1_name.contains("player") && fix2_name.contains("wall"))
						|| (fix2_name.contains("player") && fix1_name
								.contains("wall"))) {
					isLanded = true;
				}

				// 총알과 벽의 충돌
				if ((fix1_name.contains("shuriken") && fix2_name
						.equalsIgnoreCase("wall"))
						|| (fix2_name.contains("shuriken") && fix1_name
								.equalsIgnoreCase("wall"))) {

					desShuriken = true;
				}

			}

			public void endContact(Contact contact) {
			}

			public void preSolve(Contact contact, Manifold oldManifold) {

			}

			public void postSolve(Contact contact, ContactImpulse impulse) {

			}

		});

	}

	/*************************************************************
	 * 
	 * 메모리 해제
	 * 
	 *************************************************************/

	public void destroyGameObject(String name) {

		if (mPhysicsWorld.getPhysicsConnectorManager().findBodyByShape(
				findShape(name)) != null) {
			// 스프라이트를 스크린에서 제거하고 스크린과 관련된 바디를 삭제합니다.
			mScene.detachChild(findShape(name));
			mPhysicsWorld.destroyBody(mPhysicsWorld
					.getPhysicsConnectorManager().findBodyByShape(
							findShape(name)));
			mPhysicsWorld.unregisterPhysicsConnector(mPhysicsWorld
					.getPhysicsConnectorManager().findPhysicsConnectorByShape(
							findShape(name)));
		}
	}

	/**************************************************************
	 * 
	 * 표창이 중력의 영향을 받지 않고 직선으로 날아가도록 합니다.
	 * 
	 **************************************************************/

	private void makeShurikenDefyGravity() {
		for (IEntity pEntity : mEntityList) {
			if (pEntity.getUserData() != null) {
				if (pEntity.getUserData().toString().contains("shuriken")) {

					final Body pBulletBody = mPhysicsWorld
							.getPhysicsConnectorManager().findBodyByShape(
									(IShape) pEntity);
					if (pBulletBody != null)
						pBulletBody.applyForce(new Vector2(0,
								-SensorManager.GRAVITY_EARTH), pBulletBody
								.getWorldCenter());

				}
			}
		}
	}

	/**********************************************************************************
	 * 
	 * player and enemy, 두 바디 사이의 거리를 측정합니다.
	 * 
	 **********************************************************************************/

	public float getDistance(Body _player, Body _enemy) {
		float dist_x = (float) Math.pow(
				_player.getPosition().x - _enemy.getPosition().x, 2);

		float dist_y = (float) Math.pow(
				_player.getPosition().y - _enemy.getPosition().y, 2);

		// Debug.d(dist_x+" "+dist_y);
		return ((float) Math.sqrt(dist_x + dist_y));
	}

	/**********************************************************************************
	 * 
	 * 플레이어 캐릭터의 움직이는 방향에 따라서 적 캐릭터가 해당 위치로 이동합니다.
	 * 
	 **********************************************************************************/

	public void doAIActions(Body temp_enemy_body, int _playerDir, int action) {
		temp_enemy_body.applyLinearImpulse(0, -5.0f,
				temp_enemy_body.getPosition().x,
				temp_enemy_body.getPosition().y);
		if (_playerDir == PLAYER_TO_RIGHT)
			temp_enemy_body.setLinearVelocity(-mLinearVelocityX,
					temp_enemy_body.getLinearVelocity().y);

		if (_playerDir == PLAYER_TO_LEFT)
			temp_enemy_body.setLinearVelocity(mLinearVelocityX,
					temp_enemy_body.getLinearVelocity().y);

	}

	/**********************************************************************************
	 * 
	 * 적의 바디에 인공지능을 부여합니다.
	 * 
	 **********************************************************************************/

	public void doAICalculations(Body _playerBody) {
		for (int i = 0; i < enemyCount; i++) {
			IShape temp_enemy_shape = findShape("enemy" + i);
			Body temp_enemy_body = mPhysicsWorld.getPhysicsConnectorManager()
					.findBodyByShape(temp_enemy_shape);

			if (temp_enemy_body != null) {

				if (getDistance(_playerBody, temp_enemy_body) < 10.0f
						&& enemyLandedArr[i]) {

					doAIActions(temp_enemy_body, playerDir, 0);

					enemyLandedArr[i] = false;

				}

				else if (enemyLandedArr[i]) {
					temp_enemy_body.setLinearVelocity(0, 0);
				}

			}

		}
	}

	/***************************************************************
	 * 
	 * UpdateHandler
	 * 
	 ***************************************************************/
	public void createUpdateHandler() {

		this.mScene.registerUpdateHandler(new IUpdateHandler() {

			public void onUpdate(float pSecondsElapsed) {

				// 표창이 중력의 영향을 받지 않고 직선으로 날아가도록 합니다.
				makeShurikenDefyGravity();

				doAICalculations(player_body);

				// destroyGameObject 부분을 잘 선언하여야 미사일을 쏜 후, 미사일이 정상적으로 사라지게 됩니다.
				if (desShuriken) {

					if (fix1_name.contains("shuriken"))
						destroyGameObject(fix1_name);

					if (fix2_name.contains("shuriken"))
						destroyGameObject(fix2_name);

					desShuriken = false;
				}

			}

			@Override
			public void reset() {

			}

		});

	}

	@Override
	protected Scene onCreateScene() {
		this.mEngine.registerUpdateHandler(new FPSLogger());

		mScene = new Scene();

		try {
			final TMXLoader tmxLoader = new TMXLoader(this.getAssets(),
					this.mEngine.getTextureManager(),
					TextureOptions.BILINEAR_PREMULTIPLYALPHA,
					this.getVertexBufferObjectManager(),
					new ITMXTilePropertiesListener() {
						@Override
						public void onTMXTileWithPropertiesCreated(
								final TMXTiledMap pTMXTiledMap,
								final TMXLayer pTMXLayer,
								final TMXTile pTMXTile,
								final TMXProperties<TMXTileProperty> pTMXTileProperties) {
						}
					});

			// 타일맵 로드
			this.mTMXTiledMap = tmxLoader.loadFromAsset("tmx/back.tmx");

		} catch (final TMXLoadException e) {
			Debug.e(e);
		}

		final TMXLayer tmxLayer = this.mTMXTiledMap.getTMXLayers().get(0);
		mScene.attachChild(tmxLayer);

		this.mPhysicsWorld = new PhysicsWorld(new Vector2(0,
				SensorManager.GRAVITY_EARTH), false);
		mScene.registerUpdateHandler(this.mPhysicsWorld);

		// 벽과 적군에 대한 타일맵 로드
		loadObject(mTMXTiledMap);

		// 충돌에 대한 처리를 함
		createCollision();

		createUpdateHandler();

		// 컨트롤러 동작 호출
		callController();
		mScene.setChildScene(this.mDigitalOnScreenControl);

		mEntityList = new ArrayList<IEntity>(mScene.getChildCount());

		for (int i = 0; i < mScene.getChildCount(); i++)
			mEntityList.add(mScene.getChildByIndex(i));

		showPlayer("player", mPlayerTextureRegion);

		player_sprite = (AnimatedSprite) findShape("player");
		player_body = mPhysicsWorld.getPhysicsConnectorManager()
				.findBodyByShape(player_sprite);
		
		// 적캐릭터를 로드하고, 배열에 할당합니다.
		// 적캐릭터에 인공지능을 부여하기 위해 필요합니다.
		enemyLandedArr = new boolean[enemyCount];

		for (int i = 0; i < enemyCount; i++) {
			enemyLandedArr[i] = true;
		}
		remainingEnemies = enemyCount;

		// 이 부분을 설정해야 타일맵에서 좌우 여백이 제대로 셋팅됩니다.
		this.camera.setBoundsEnabled(true);
		this.camera.setBounds(0, 0, tmxLayer.getHeight(), tmxLayer.getWidth());

		// 맵에서 카메라 따라오기
		camera.setChaseEntity(player_sprite);
		return mScene;
	}

	/**********************************************************************************
	 * 
	 * 파라미터로 받은 xLoc, yLoc에 적캐릭터가 등장하도록 합니다. UserData에는 ["enemy"+적갯수]가 저장됩니다.
	 * 
	 **********************************************************************************/

	public void showEnemy(int xLoc, int yLoc) {

		AnimatedSprite mEnemySprite = new AnimatedSprite(xLoc, yLoc,
				mEnemyTextureRegion, this.getVertexBufferObjectManager());
		mEnemySprite.setScale(0.9f);

		FixtureDef mEnemyFixtureDef = PhysicsFactory.createFixtureDef(0, 0f,
				0f, false, CATEGORYBIT_ENEMY, MASKBITS_ENEMY, (short) 0);

		Body mEnemyBody = PhysicsFactory.createBoxBody(this.mPhysicsWorld,
				mEnemySprite, BodyType.DynamicBody, mEnemyFixtureDef);
		mEnemyBody.setUserData("enemy" + enemyCount);

		this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(
				mEnemySprite, mEnemyBody, true, true));
		mEnemySprite.setUserData("enemy" + enemyCount);
		enemyCount++;

		mScene.attachChild(mEnemySprite);
		mEnemySprite.animate(100);

		mEnemySprite.setCullingEnabled(true);
	}

	/*****************************************************
	 * 
	 * 플레이어 캐릭터 등장
	 * 
	 *****************************************************/

	public void showPlayer(String playerName, TiledTextureRegion playerTexture) {

		AnimatedSprite mPlayerSprite = new AnimatedSprite(mapOffset, 0,
				playerTexture, this.getVertexBufferObjectManager());

		final FixtureDef mPlayerFixtureDef = PhysicsFactory.createFixtureDef(0,
				0f, 0f, false, CATEGORYBIT_PLAYER, MASKBITS_PLAYER, (short) 0);

		Body mPlayerBody = PhysicsFactory.createBoxBody(this.mPhysicsWorld,
				mPlayerSprite, BodyType.DynamicBody, mPlayerFixtureDef);
		mPlayerSprite.setUserData(playerName);
		mPlayerBody.setUserData(playerName);
		this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(
				mPlayerSprite, mPlayerBody, true, false));

		mScene.attachChild(mPlayerSprite);
		mEntityList.add(mPlayerSprite);
	}

	/***********************************************************
	 * 
	 * UserData string (name)에 해당하는 모양을 찾는 함수 입니다.
	 * 
	 ***********************************************************/

	public IShape findShape(String shapeName) {
		IShape pShape = null;

		// 존재하는 모든 entities들을 체크하면서
		// UserData 문자열에 해당하는 값이 있는 entity를 리턴합니다.
		for (IEntity pEntity : mEntityList) {
			if (pEntity.getUserData() != null) {
				if (pEntity.getUserData().toString()
						.equalsIgnoreCase(shapeName)) {
					pShape = (IShape) pEntity;
				}
			}
		}
		return pShape;
	}

	/************************************************************
	 * 
	 * tmx map에 사각 영역을 선언해두고, 이 영역에 적이 나타나게 됩니다.
	 * 
	 ************************************************************/
	private void loadEnemiesFromObjects(TMXObjectGroup _group) {

		// tmx group에서 모든 Object들을 탐색함
		for (final TMXObject object : _group.getTMXObjects()) {

			// 적 캐릭터를 등장시킴
			showEnemy(object.getX(), object.getY());
		}
	}

	private void loadObject(TMXTiledMap map) {

		// tmx map에서 모든 object groups을 가져와서 속성 이름을 체크합니다.

		for (final TMXObjectGroup group : map.getTMXObjectGroups()) {

			if (group.getName().equals("wall")) {
				loadWallFromObjects(group, "wall");
			}

			// 타일맵상에 속성을 enemies라고 표기하면 게임 시작시 해당 위치에 자동으로 enemies가 나타납니다.
			if (group.getName().equals("enemies")) {
				loadEnemiesFromObjects(group);
			}

		}

	}

	private void loadWallFromObjects(TMXObjectGroup _group,
			String _userData) {

		for (final TMXObject object : _group.getTMXObjects()) {

			// tmx map에 사각형을 생성합니다.
			rect = new Rectangle(object.getX(), object.getY(),
					object.getWidth(), object.getHeight(),
					this.getVertexBufferObjectManager());

			// 벽에 해당하는 FixtureDef를 생성합니다.
			boxFixtureDef = PhysicsFactory.createFixtureDef(0, 0, 1f, false,
					CATEGORYBIT_WALL, MASKBITS_WALL, (short) 0);

			// 벽을 StaticBody로 선언하고 userData에 셋팅합니다.
			PhysicsFactory.createBoxBody(this.mPhysicsWorld, rect,
					BodyType.StaticBody, boxFixtureDef).setUserData(_userData);

			// 사각형 영역을 보이지 않게 만듭니다.
			rect.setVisible(false);

			mScene.attachChild(rect);
		}
	}

}
