ロングタップ後のアップ判定をどうする?

 AndEngine でアニメーションダイアログを実装した際に「ダイアログが開いた後に指を離した際、離した位置にコーラが描画されていると即座にダイアログが閉じてしまう」問題が発生したのですが、このようにして解決しました。

Sprite colaSprite = new Sprite(dialogSprite.getWidth() / 2, dialogSprite.getHeight() / 2, resourcesManager.cola_region, vbom){
	boolean isNewTouch = false;
	@Override
	public boolean onAreaTouched(final TouchEvent pAreaTouchEvent, final float x, final float y){
		if(isTouchOK){
			if(pAreaTouchEvent.getAction() == TouchEvent.ACTION_DOWN){
				isNewTouch = true;
			}
			else if(pAreaTouchEvent.getAction() == TouchEvent.ACTION_UP){
				if(isNewTouch){
					detachSelf();
					closeDialog(sec);
				}
				isNewTouch = false;
			}
		}
		return true;
	}
};

 Sprite に boolean 値 isNewTouch(カップヌードルではない)を加えて、ACTION_DOWN と ACTION_UP がセットで動作しないと detachSelf(); や closeDialog(sec); を実行しないようにしました。
 ロングタップでキャンセルされた ACTION_DOWN の後の ACTION_UP は実行されないのです。
 
 シンプルに解決できるロジックが閃いたこの瞬間が、なんとも気持ちイイ!

 プログラミングの醍醐味です。

公認アンドロイドアプリとなりましたが

 アンインストール率75%を誇る「Polygons Lights Out」がアンドロイダーさんの公認アンドロイドアプリとなりました。

 なるほど、手を抜いたとしか思えないスカスカのデザインも「スタイリッシュなデザイン」と言えば良いのですね!
 アンドロイダーさん、勇気をありがとう!
 
 しかし、作者本人が言うのも何だけれど「パズルが好きな人は必ずハマるはず。」って・・・う~む、ハマらんと思うぞ。

AndEngineでアニメーションダイアログを実装する(その3)

 前回からの続きです。
 
 SplashScene の変更は、BaseScene のメソッドをオーバーライドするだけ。エラーになるからね。

	@Override
	protected void drawDialog(final float sec){}

 
 GameScene は、

	@Override
	protected void createScene(){
		// 前回分に hud の処理を追記
		hud = new HUD();
		hud.setTouchAreaBindingOnActionDownEnabled(true);
		camera.setHUD(hud);
	}

	@Override
	public void disposeScene(){
		background.detachSelf();
		background.dispose();
		sprite.detachSelf();
		sprite.dispose();
		// hud の処理を追記
		hud.clearTouchAreas();
		hud.detachSelf();
		hud.dispose();
		this.detachSelf();
		this.dispose();
	}

	@Override
	public void onHold(HoldDetector pHoldDetector, long pHoldTimeMilliseconds, int pPointerID, float pHoldX, float pHoldY){
		if(!isHold && pHoldTimeMilliseconds > holdTime){
			isHold = true;
			// BaseScene のメソッド呼び出しに変更
			openDialog(MainActivity.CAMERA_WIDTH / 2, MainActivity.CAMERA_HEIGHT / 2, MainActivity.CAMERA_WIDTH - 300, MainActivity.CAMERA_HEIGHT - 400);
		}
	}

	// ダイアログの中身をここで書く
    @Override
	protected void drawDialog(final float sec){
    	Sprite colaSprite = new Sprite(dialogSprite.getWidth() / 2, dialogSprite.getHeight() / 2, resourcesManager.cola_region, vbom){
			@Override
			public boolean onAreaTouched(final TouchEvent pAreaTouchEvent, final float x, final float y){
				if(isTouchOK){
					if(pAreaTouchEvent.getAction() == TouchEvent.ACTION_UP){
						detachSelf();
						closeDialog(sec);
					}
				}
				return true;
			}
		};
    	dialogSprite.attachChild(colaSprite);
    	hud.registerTouchArea(colaSprite);
        isTouchOK = true;
	}

 コーラの Sprite の onAreaTouched() をオーバーライドして、自身をタッチされた時に自身を消した上でダイアログを閉じるようにしています。
 
 こんな感じで、サンプルプロジェクトを改造してみました。
 解凍してコードを見るもよし、Eclpse にインポートして動かすもよし(AndEngine の参照が必要です)。
 
 
と、満足していたら、問題発生。
 ロングタップしてダイアログが開いた後に指を離した際、離した位置にコーラが描画されていると即座にダイアログが閉じてしまう。
 ダイアログが開いた後に数秒の待ちを入れるか・・それでも指を離さなければダメだし・・、開いた後の最初の ACTION_UP で isTouchOK を true にしようか・・って、それじゃタッチイベント拾えないし、ACTION_DOWN で動作させようか・・。
 
 さあ、皆さんならどうします?
 
 つらつら考えて、一応の対応策はできたのですが、次回までの練習問題として?のままとしておきます。

AndEngineでアニメーションダイアログを実装する(その2)

 以前作ったサンプルライブラリの内、MainActivity クラスと SceneManager クラスに変更はありません。
 
 新たな素材を読み込むため、ResourcesManager クラスに追記します。
 それぞれの BitmapTextureAtlas と ITextureRegion を用意して

	private BitmapTextureAtlas colaTextureAtlas;
	public ITextureRegion cola_region;
	private BitmapTextureAtlas maskTextureAtlas;
	public ITextureRegion mask_region;
	private BitmapTextureAtlas ninePatchTextureAtlas;
	public ITextureRegion ninePatch_region;

 画像を読み込みます。

	public void loadResources(){
		BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
        spriteTextureAtlas = new BitmapTextureAtlas(activity.getTextureManager(), 256, 256, TextureOptions.BILINEAR);
        sprite_region = BitmapTextureAtlasTextureRegionFactory.createFromAsset(spriteTextureAtlas, activity, "sprite.png", 0, 0);
        spriteTextureAtlas.load();
        colaTextureAtlas = new BitmapTextureAtlas(activity.getTextureManager(), 256, 256, TextureOptions.BILINEAR);
        cola_region = BitmapTextureAtlasTextureRegionFactory.createFromAsset(colaTextureAtlas, activity, "cola.png", 0, 0);
        colaTextureAtlas.load();
        maskTextureAtlas = new BitmapTextureAtlas(activity.getTextureManager(), 4, 4, TextureOptions.BILINEAR);
        mask_region = BitmapTextureAtlasTextureRegionFactory.createFromAsset(maskTextureAtlas, activity, "mask.png", 0, 0);
        maskTextureAtlas.load();
        ninePatchTextureAtlas = new BitmapTextureAtlas(activity.getTextureManager(), 16, 16, TextureOptions.BILINEAR);
        ninePatch_region = BitmapTextureAtlasTextureRegionFactory.createFromAsset(ninePatchTextureAtlas, activity, "ninePatch.png", 0, 0);
        ninePatchTextureAtlas.load();
	}

 
 シーン周りは大改造となります。
 先ずは、BaseScene クラス。
 ZoomCamera につられず、常に等倍・位置固定の Scene のサブクラス HUD クラスにダイアログを表示します。

	protected HUD hud;
	protected NineSliceSprite dialogSprite;
	private float scaleTime = 0.5f;

 今後 BaseScene のサブクラスが増えたときでもコードを加えることなくダイアログが表示できるよう、ここにメソッドを作ってしまいましょう。

	abstract void drawDialog(final float sec);

	protected void openDialog(final float x, final float y, final float width, final float height){
		attachMask();
		final float sec = scaleTime * (height / MainActivity.CAMERA_HEIGHT);
		dialogSprite = new NineSliceSprite(x, y, width, height, resourcesManager.ninePatch_region, 8, 8, 9, 9, vbom);
		ScaleModifier scaleModifier = new ScaleModifier(sec, 1.0f, 1.0f, 0.0f, 1.0f, new IEntityModifierListener(){
			public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem){}
			public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem){
				drawDialog(sec);
			}
		});
        scaleModifier.setAutoUnregisterWhenFinished(true);
		dialogSprite.registerEntityModifier(scaleModifier);
		hud.attachChild(dialogSprite);
	}

	protected void closeDialog(final float sec){
		ScaleModifier scaleModifier = new ScaleModifier(sec, 1.0f, 1.0f, 1.0f, 0.0f, new IEntityModifierListener(){
			public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem){
				detachMask();
			}
			public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem){}
		});
        scaleModifier.setAutoUnregisterWhenFinished(true);
		dialogSprite.registerEntityModifier(scaleModifier);
	}

	protected void attachMask(){
		final Sprite mask = new Sprite(MainActivity.CAMERA_WIDTH / 2, MainActivity.CAMERA_HEIGHT / 2, MainActivity.CAMERA_WIDTH, MainActivity.CAMERA_HEIGHT, resourcesManager.mask_region, vbom){
			@Override
			public boolean onAreaTouched(final TouchEvent pAreaTouchEvent, final float x, final float y){ 
				return true;
			}
		};
    	mask.setAlpha(0.5f);
    	hud.attachChild(mask);
		hud.registerTouchArea(mask);
		hud.setOnAreaTouchTraversalFrontToBack();
	}

	protected void detachMask(){
		hud.detachChildren();
		hud.clearTouchAreas();
	}

 openDialog() の最初に attachMask() を呼び出し、前面の全面に黒50%のマスクをかけ、onAreaTouched() の戻り値を true とすることで下のシーンにイベント通知しないようにしています。
 new NineSliceSprite(x, y, width, height, resourcesManager.ninePatch_region, 8, 8, 9, 9, vbom) でダイアログを作り、new ScaleModifier(sec, 1.0f, 1.0f, 0.0f, 1.0f, new IEntityModifierListener(){}) でY軸のスケールを0~1倍へと増やしていきます。終了した時点でダイアログの中身を描画する drawDialog() を呼び出すのですが、具体的には各シーンごとに実装します。
 closeDialog() は、その反対のことをしているだけですね。
 
 つづく。