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

 9パッチを利用してダイアログを実装します。
 ただのダイアログでは寂しいので、開くアニメーション、閉じるアニメーションを付け加えます。
 部分的なコードではわかり難いので、以前作ったサンプルプロジェクトに追記して動くプロジェクトとして掲載しましょう。
 題して「AndEngineのピンチズームとスクロールとロングタップを実装したサンプルプロジェクトにナインパッチを利用したアニメーションするオリジナルダイアログを実装する」略して「AndEngineでアニメーションダイアログを実装する」です。
 
 ピンチズームとスクロールは前回のままスプライトの Sprite への視点の拡大縮小移動を行いますが、ロングタップはスプライトの Sprite を回転させることを止め、スプライトの画面にタッチイベントを渡さないように全面にマスクをかけた上で、9パッチを利用した Sprite をアニメーションさせて開いたダイアログにコーラの Sprite を貼り付けます。コーラの Sprite をタッチすることによってダイアログをアニメーションで閉じ、マスクを外すこととします。
device-2014-06-21-115804 → device-2014-06-21-115813
 
 用意する画像は、コーラの画像、4×4ピクセルを黒く塗りつぶしただけのマスク画像、9パッチ画像です。
cola mask ninePatch
 
 9パッチ画像は16×16ピクセルでこのように作ってあります。
 ninePatch-zoom

 つづく。

「パロディことわざ」をApplivさんにレビューしていただきました

 公開以来、なんとも不評な「パロディことわざ」のレビューを Appliv さんに無料で書いていただきました。
 取引条件は「リンクの掲載」です。
 
 舞台裏を書いてしまっては身も蓋もないのですが、根が正直なので書いてしまいました。
 
--------8<-----------8<-----------8<--------
「パロディことわざ」のApplivレビュー
Applivの面白いネタ・お笑いランキングに掲載中です。応援よろしくお願いします!
-------->8----------->8----------->8--------
 

 今後の記事にこのリンクを付け加えるつもりです。

 Appliv の担当の方もこの記事を確認されると思うのですが、気を悪くなさらずに。
 お詫びの印に Appliv さんへのリンクをふんだんに書いております。

AndEngineで9パッチを使う(簡単)

 9パッチは自前で実装しても大したこたぁないやと、録に調べずに自前コードを書いていたのですが、AndEngine では2行で書けることを発見。ショック。

	NineSliceSprite dialogSprite = new NineSliceSprite(pX, pY, pWidth, pHeight, pTextureRegion, pInsetLeft, pInsetTop, pInsetRight, pInsetBottom, pVertexBufferObjectManager);
	this.attachChild(dialogSprite);

 Android 純正と違って、用いる画像の周囲に透過部分を入れなくても、拡張子も.9.png にしなくてもよいらしい。
 早く気付くべきだった。
 
 
 今作っている「撮影した画像をスライドパズル化するアプリ」で、カメラの設定がどうやっても横向きにしかならない。縦にしようとするとプレビュー画面が細長くなってしまう。
 でも、このアプリはどうしても縦向きにしたいのです(いや「どうしても」という程ではないのですが・・)。
 
 そこで、カメラ画面以外はダミーの Entity に貼り付けて270度回転させて表示するといった回りくどいことやっています。
 もう、座標系がぐるぐる回って訳わからん・・。上記の9パッチも縦画面の中、横に開いたりしてびっくりものです。

AndEngineのサンプルプロジェクトにピンチズームとスクロールとロングタップを実装する

 お詫びと訂正。
 AndEngineのサンプルプロジェクト(その4)の GameScene.java の中で

    private Sprite sprite;
     
    @Override
    public void createScene(){
        createBackground();
        Sprite sprite = new Sprite(MainActivity.CAMERA_WIDTH / 2, MainActivity.CAMERA_HEIGHT / 2, resourcesManager.sprite_region, vbom);
        attachChild(sprite);
    }

と、しておりました。メンバ変数の意味がありませんね。次の記事につなげるためにも

    private Sprite sprite;
     
    @Override
    public void createScene(){
        createBackground();
        sprite = new Sprite(MainActivity.CAMERA_WIDTH / 2, MainActivity.CAMERA_HEIGHT / 2, resourcesManager.sprite_region, vbom);
        attachChild(sprite);
    }

このように、直させていただきます。
 
 さて。
 
 予告どおり、というか思い付きでロングタップも含め実装してみました。
 GameScene.java に追記。

package com.chabaori.sample;

import org.andengine.entity.scene.IOnSceneTouchListener;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.sprite.Sprite;
import org.andengine.input.touch.TouchEvent;
import org.andengine.input.touch.detector.ContinuousHoldDetector;
import org.andengine.input.touch.detector.HoldDetector;
import org.andengine.input.touch.detector.HoldDetector.IHoldDetectorListener;
import org.andengine.input.touch.detector.PinchZoomDetector;
import org.andengine.input.touch.detector.PinchZoomDetector.IPinchZoomDetectorListener;
import org.andengine.input.touch.detector.ScrollDetector;
import org.andengine.input.touch.detector.ScrollDetector.IScrollDetectorListener;
import org.andengine.input.touch.detector.SurfaceScrollDetector;

import com.chabaori.sample.SceneManager.SceneType;

public class GameScene extends BaseScene  implements IOnSceneTouchListener, IScrollDetectorListener, IPinchZoomDetectorListener, IHoldDetectorListener{
	private Sprite sprite;
    final static float MIN_ZOOM = 1.0f;
    final static float MAX_ZOOM = 3.0f;
    private SurfaceScrollDetector mScrollDetector;
    private PinchZoomDetector mPinchZoomDetector;
    private float mPinchZoomStartedCameraZoomFactor;
    private boolean isTouchOK = true;
    private boolean isMove;
    private boolean isHold = false;
    private ContinuousHoldDetector continuousHoldDetector;
    private long holdTime = 200;
    private float rotation = 0;
	
	@Override
	public void createScene(){
        setOnSceneTouchListener(this);
        setOnAreaTouchTraversalFrontToBack();
        mScrollDetector = new SurfaceScrollDetector(this);
        mScrollDetector.setTriggerScrollMinimumDistance(15f);
        mPinchZoomDetector = new PinchZoomDetector(this);

        continuousHoldDetector = new ContinuousHoldDetector(this);
        registerUpdateHandler(continuousHoldDetector);
        
        setOnSceneTouchListener(this);
        setTouchAreaBindingOnActionDownEnabled(true);
        isTouchOK = true;

		createBackground();
    	sprite = new Sprite(MainActivity.CAMERA_WIDTH / 2, MainActivity.CAMERA_HEIGHT / 2, resourcesManager.sprite_region, vbom);
    	attachChild(sprite);
	}

	@Override
	public void onBackKeyPressed(){
		System.exit(0);
	}

	@Override
	public SceneType getSceneType(){
		return SceneType.SCENE_GAME;
	}
	
	@Override
	public void disposeScene(){
		background.detachSelf();
		background.dispose();
		sprite.detachSelf();
		sprite.dispose();
		this.detachSelf();
		this.dispose();
	}

    @Override
    public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent){
        if(isTouchOK){
            if(isHold){
                isHold = false;
                continuousHoldDetector.reset();
            }
            continuousHoldDetector.onTouchEvent(pSceneTouchEvent);
            if(!isHold){
                mPinchZoomDetector.onTouchEvent(pSceneTouchEvent);
                
                if(mPinchZoomDetector.isZooming()){
                    mScrollDetector.setEnabled(false);
                    isMove = true;
                }
                else{
                    if (pSceneTouchEvent.isActionMove()){
                        mScrollDetector.setEnabled(true);
                    }
                    else if (pSceneTouchEvent.isActionUp()){
                        if(!isMove){
                            isTouchOK = false;
                            final float zoomValue = 1.0f;
                            camera.setZoomFactor(zoomValue);
                            isTouchOK = true;
                        }
                        isMove = false;
                    }
                    else if (pSceneTouchEvent.isActionOutside()){
                        isMove = false;
                    }
                    mScrollDetector.onTouchEvent(pSceneTouchEvent);
                }
            }
        }
        return true;
    }
 
    @Override
    public void onScrollStarted(final ScrollDetector pScollDetector, final int pPointerID, final float pDistanceX, final float pDistanceY) {
        final float zoomFactor = camera.getZoomFactor();
        camera.offsetCenter(-pDistanceX / zoomFactor, pDistanceY / zoomFactor);
        isMove = true;
    }
 
    @Override
    public void onScroll(final ScrollDetector pScollDetector, final int pPointerID, final float pDistanceX, final float pDistanceY) {
        final float zoomFactor = camera.getZoomFactor();
        camera.offsetCenter(-pDistanceX / zoomFactor, pDistanceY / zoomFactor);
    }
     
    @Override
    public void onScrollFinished(final ScrollDetector pScollDetector, final int pPointerID, final float pDistanceX, final float pDistanceY) {
        final float zoomFactor = camera.getZoomFactor();
        camera.offsetCenter(-pDistanceX / zoomFactor, pDistanceY / zoomFactor);
    }
 
    @Override
    public void onPinchZoomStarted(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent) {
        mPinchZoomStartedCameraZoomFactor = camera.getZoomFactor();
    }
 
    @Override
    public void onPinchZoom(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent, final float pZoomFactor) {
        float zoomValue = mPinchZoomStartedCameraZoomFactor * pZoomFactor;
        if(zoomValue < 1.1) zoomValue = MIN_ZOOM;
        else if(zoomValue > MAX_ZOOM) zoomValue = MAX_ZOOM;
        camera.setZoomFactor(zoomValue);
    }
 
    @Override
    public void onPinchZoomFinished(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent, final float pZoomFactor) {
        float zoomValue = mPinchZoomStartedCameraZoomFactor * pZoomFactor;
        if(zoomValue < 1.1) zoomValue = MIN_ZOOM;
        else if(zoomValue > MAX_ZOOM) zoomValue = MAX_ZOOM;
        camera.setZoomFactor(zoomValue);
    }
    
    @Override
    public void onHoldStarted(HoldDetector pHoldDetector, int pPointerID, float pHoldX, float pHoldY){
        isTouchOK = false;
    }
 
    @Override
    public void onHold(HoldDetector pHoldDetector, long pHoldTimeMilliseconds, int pPointerID, float pHoldX, float pHoldY){
        if(!isHold && pHoldTimeMilliseconds > holdTime){
            isHold = true;
            rotation += 90;
            if(rotation == 360) rotation = 0; 
            sprite.setRotation(rotation);
            isTouchOK = true;
        }
    }
         
    @Override
    public void onHoldFinished(HoldDetector pHoldDetector, long pHoldTimeMilliseconds, int pPointerID, float pHoldX, float pHoldY){
    }
}

 ロングタップ制御外にピンチズームを入れただけなのに複雑になりましたね。
 この他にも、各クラスの BoundCamera を ZoomCamera に直す必要があります。
 MainActivity クラスの onCreateEngineOptions() に camera.setBounds(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT); を加えるのもお忘れなく。
 
 Sprite をピンチで3倍までズームアップし、タップで等倍に戻します。ロングタップで90度右回転。
device-2014-06-10-223552 device-2014-06-10-223727
 タップとロングタップの制御が甘いのはご愛嬌。
 
 
 そうそう、認証もらった。

アンドロイダー公認デベロッパー認証

AndEngineのサンプルプロジェクト(まとめ)

 お約束のプロジェクトアーカイブは、こちら からどうぞ。zip ファイルであります。
 AndEngine のライブラリ参照は切ってあるので、適宜設定してください。
 
 これらのクラスは、イギリス在住の Matim さんのページ(19歳でエライねぇ)を参考にさせて(否、丸写しか・・)もらったものです。
 
 サンプルプログラムは、スプライト表示のみの機能しかないので、あっさりしたコードです。じっくり読んで追いかければ処理の流れはわかるかと思います。
 その上で、上記サイト等を参考にコードを追加していけば、簡単なゲーム位はすぐ出来るはずです。
 
 ぜひ、参考にしてみてください。
 
 
 
 
 
 これで、サンプルの紹介は終わりにしようと思ったのですが、(その1)で『タッチ制御のプログラムを抜書きしても「全体がわからないから使いどころがわからない」というご意見もあるかもしれないと思ったので、サンプルプロジェクトを作ってみました。』と書いておいて、タッチ制御を入れないのもなんだかな・・、上でも『簡単なゲーム位はすぐ出来るはずです。』とも書いているし・・、簡単にタッチ制御のコードを追加してみようかと思った次第。
 
 次回「AndEngineのサンプルプロジェクトにピンチズームとスクロールを実装する」です。タイトル長いな。