AndEngineで和文縦書き、一応完成

 device-2014-02-14-232308
 2回にわたってお送りしました「和文縦書き」も本日をもって一応の完成です。
 <br>タグで改行し、<color=255,255,255,255> </color>という独自のタグで文字色を変更します。色指定の書式は格好悪いが目を瞑りましょう。
 行末、行頭の禁則処理は、和文で出現する、。ー「」()あたりまでフォローしました。が、行末禁則処理の直後に<br>があると更に改行し1行空いた状態となります。仕様です。
 半角英数字は、つなげて横向き表示としました。長さがその行に収まらない場合は、次の行に追い出します。1行の文字数長を超える半角英数字を表示しようとすると・・・たぶんエラーになります。仕様です。

 AndEngine の Scene に、文字列をばらして一文字ずつ縦に Text として表示するということは、文字ひとつひとつが Entity となります。せっかくだから AndEngine での表示ならではの機能も加えました。
 ひとつが、文字の回転です。指定秒数後に指定時間かけて一回転します。
 ふたつめが、文字のフェードイン(アウト)です。指定秒数後に指定時間かけて云々。文字をインデックスで指定、全文を一気に、全文を先の文字から順番に、といったパターンを用意しました。
 device-2014-02-14-223025 device-2014-02-14-223052
 タイトルだけ作ってみました。前は、回転中。後は、フェードアウト中。

 縦書き処理のソースコードはこちらです。

public class VerticalText extends Entity{

	public VerticalText(float pX, float pY, float bottomY, IFont pFont, float fontSize, CharSequence pText, VertexBufferObjectManager vbom){
		this(pX, pY, bottomY, pFont, fontSize, Color.argb(255, 0, 0, 0), pText, vbom);
	}

	public VerticalText(float pX, float pY, float bottomY, IFont pFont, float fontSize, int fontColor, CharSequence pText, VertexBufferObjectManager vbom){
		this(pX, pY, bottomY, pFont, fontSize, fontColor, pText, 1.2f, 1.8f, vbom);
	}

	public VerticalText(float pX, float pY, float bottomY, IFont pFont, float fontSize, int fontColor, CharSequence pText, float fontSpan, float lineSpan, VertexBufferObjectManager vbom){
		super(0, 0, MainActivity.CAMERA_WIDTH, MainActivity.CAMERA_HEIGHT);
		
		float x = pX;
		float y = pY;
        float scale = fontSize / ResourcesManager.fontSize;
		float fontSpacing = pFont.getAscent() * fontSpan * scale;
        float lineSpacing = pFont.getLineHeight() * lineSpan * scale;
        float adjustX, adjustY, plusY;
        String singleByte = "";
        Text text;
        CharSetting setting;
        
        boolean isColorChange = false;
        int color = fontColor;
        String colorString = "";

        String[] string = pText.toString().split("");
        int length = string.length;

        for (int i = 1 ; i < length ; i++) {
        	if(isColorChange){
        		colorString += string[i];
        		if(colorString.length() == 15){
        			String[] colorSplit = colorString.split(",");
        			color = Color.argb(
        					Integer.parseInt(colorSplit[0]),
        					Integer.parseInt(colorSplit[1]),
        					Integer.parseInt(colorSplit[2]),
        					Integer.parseInt(colorSplit[3]));
        			colorString = "";
        			isColorChange = false;
        		}
        	}
        	else if(string[i].matches("[=<>/0-9a-zA-Z]+")){
             	singleByte += string[i];
             	if(singleByte.equalsIgnoreCase("<br>")){
                    x -= lineSpacing;
                    y = pY;
                    singleByte = "";
             	}
             	else if(singleByte.equalsIgnoreCase(">")){
             		singleByte = "";
             	}
             	else if(singleByte.equalsIgnoreCase("<color=")){
             		isColorChange = true;
             		singleByte = "";
             	}
             	else if(singleByte.equalsIgnoreCase("</color>")){
             		color = fontColor;
             		singleByte = "";
             	}
             }
        	 else{
        		 if(!singleByte.isEmpty()){
        			 text = new Text(x, y, pFont, singleByte, vbom);
        			 text.setColor(color);
        			 text.setScale(scale);
        			 text.setRotation(90.0f);
        			 
                     plusY = 0;
                     while(true){
                    	 plusY -= fontSpacing;
                    	 if(text.getWidth() * scale < plusY) break;
                     }
                     
        			 if(y - text.getWidth() - bottomY < 0 ){
                         x -= lineSpacing;
                         y = pY;
        			 }
        			 
        			 adjustY = y - (plusY / 2) - (fontSpacing / 2);
        			 text.setPosition(x, adjustY);

        			 this.attachChild(text);
                     
        			 y = y - plusY;
        			 singleByte = "";
        		 }
        		 
                 if (y + (fontSpacing * 2) - bottomY < 0) {
                 	if(CharSetting.isLineEndWrap(string[i])){
                         x -= lineSpacing;
                         y = pY;
                 	}
                 }
                 
                 text = new Text(x, y, pFont, string[i], vbom);
              	
                 adjustX = x - ((ResourcesManager.fontSize - text.getWidth()) / 2 * scale);
                 text.setPosition(adjustX, y);
                 text.setColor(color);
                 text.setScale(scale);
                 setting = CharSetting.getSetting(string[i]);
                 if (setting != null){
                     text.setRotation(setting.angle);
                     text.setPosition(x - (fontSpacing * setting.x * scale), y + (fontSpacing * setting.y * scale));
                 }
                 this.attachChild(text);

                 if (y + (fontSpacing * 2) - bottomY < 0) {
                 	if(i < length - 1 && !CharSetting.isLineHeadWrap(string[i + 1])){
                         x -= lineSpacing;
                         y = pY;
                 	}
                     else y += fontSpacing;
                 }
                 else y += fontSpacing;
        	 }
        }
	}
	
	public void rotateFont(int index, float startTime, float rotateTime){
        DelayModifier delayModifier = new DelayModifier(startTime);
        RotationModifier rotationModifier = new RotationModifier(rotateTime, 0, 360);
        SequenceEntityModifier sequenceEntityModifier = new SequenceEntityModifier(delayModifier, rotationModifier);
        sequenceEntityModifier.setAutoUnregisterWhenFinished(true);
		this.getChildByIndex(index).registerEntityModifier(sequenceEntityModifier);
	}
	
	public void transparentFont(int index, float startTime, float transparentTime, float fromAlpha, float toAlpha){
        DelayModifier delayModifier = new DelayModifier(startTime);
        AlphaModifier alphaModifier = new AlphaModifier(transparentTime, fromAlpha, toAlpha);
        SequenceEntityModifier sequenceEntityModifier = new SequenceEntityModifier(delayModifier, alphaModifier);
        sequenceEntityModifier.setAutoUnregisterWhenFinished(true);
		this.getChildByIndex(index).registerEntityModifier(sequenceEntityModifier);
	}
	
	public void transparentAllFont(float startTime, float transparentTime, float fromAlpha, float toAlpha){
		for(int i = 0 ; i < this.getChildCount() ; i++){
			transparentFont(i, startTime, transparentTime, fromAlpha, toAlpha);
		}
	}
	
	public void transparentTurnFont(float startTime, float transparentTime, float fromAlpha, float toAlpha){
		for(int i = 0 ; i < this.getChildCount() ; i++){
			transparentFont(i, startTime + (transparentTime * i / 2), transparentTime, fromAlpha, toAlpha);
		}
	}
}

 インポート文は省略。コメントなし。
 画面サイズやフォントの元サイズを Static 変数参照しているので、汎用性なし。注意されたし。
 AndEngine は GLES2-AnchorCenter を使っているので、左下が (0,0) の座標系です。文章が進むと y 座標は小さくなり、改行で x 座標が小さくなります。float fontSpacing = pFont.getAscent() * fontSpan * scale; だから、字数が進めば、現在の y 座標から差し引かなければならないと思いきや、fontSpacing が何故か-数値なので y += fontSpacing; となってます。原因は追究せず、ありのままを受け入れます。
 Modifier 群は、詰め込めばいろいろなエフェクトがかけらるので、遊び甲斐あり。ひと文字ごとにガクガクブルブルさせれば、貞子チックになること請け合いです。

 そして、個別位置調整と禁則文字チェックのデータクラス。

public class CharSetting{
    public final String charcter;
    public final float angle;
    public final float x;
    public final float y;

    public CharSetting(String charcter, float angle, float x, float y){
        super();
        this.charcter = charcter;
        this.angle = angle;
        this.x = x;
        this.y = y;
    }

    private final static CharSetting[] settings = {
            new CharSetting("、", 0.0f, 0.4f, -0.6f), 		new CharSetting("。", 0.0f, 0.4f, -0.6f),
            new CharSetting("「", 90.0f, 0.0f, -0.725f),		new CharSetting("」", 90.0f, 0.0f, 0.0f),
            new CharSetting("(", 90.0f, -0.1f, -0.725f),		new CharSetting(")", 90.0f, 0.0f, 0.0f),
            new CharSetting("ぁ", 0.0f, 0.0f, -0.1f), 		new CharSetting("ぃ", 0.0f, 0.05f, -0.15f),
            new CharSetting("ぅ", 0.0f, -0.075f, -0.1f), 		new CharSetting("ぇ", 0.0f, 0.05f, -0.1f),
            new CharSetting("ぉ", 0.0f, 0.05f, -0.15f), 		new CharSetting("っ", 0.0f, 0.1f, -0.2f),
            new CharSetting("ゃ", 0.0f, 0.15f, -0.15f), 		new CharSetting("ゅ", 0.0f, 0.15f, -0.15f),
            new CharSetting("ょ", 0.0f, 0.05f, -0.1f), 		new CharSetting("ァ", 0.0f, 0.0f, -0.2f),
            new CharSetting("ィ", 0.0f, 0.0f, -0.15f), 		new CharSetting("ゥ", 0.0f, 0.0f, -0.15f),
            new CharSetting("ェ", 0.0f, 0.05f, -0.2f), 		new CharSetting("ォ", 0.0f, 0.0f, -0.15f),
            new CharSetting("ッ", 0.0f, 0.05f, -0.15f), 		new CharSetting("ャ", 0.0f, 0.0f, -0.15f),
            new CharSetting("ュ", 0.0f, 0.1f, -0.2f), 		new CharSetting("ョ", 0.0f, 0.0f, -0.2f),
            new CharSetting("ー", -90.0f, 0.0f, 0.2f)
    };

    public static CharSetting getSetting(String character){
    	CharSetting charSetting = null;
    	int length = settings.length;
        for (int i = 0 ; i < length ; i++){
            if (settings[i].charcter.equals(character)){
                charSetting = settings[i];
                break;
            }
        }
        return charSetting;
    }

    private final static String[] LINE_HEAD_WRAP = {
            "、", "。", "」", ")", "ー"
    };

    public static boolean isLineHeadWrap(String s){
    	boolean isLineHeadWrap = false;
        for (String functuantionMark : LINE_HEAD_WRAP){
            if (functuantionMark.equals(s)){
            	isLineHeadWrap = true;
            	break;
            }
        }
        return isLineHeadWrap;
    }
    
    private final static String[] LINE_END_WRAP = {
        "「", "("
    };

    public static boolean isLineEndWrap(String s){
    	boolean isLineEndWrap = false;
        for (String functuantionMark : LINE_END_WRAP){
            if (functuantionMark.equals(s)){
            	isLineEndWrap = true;
            	break;
            }
        }
        return isLineEndWrap;
    }
}

先の VerticalText クラスを呼び出す実装は、こんな感じです。

		float rotateStartTime = 3.0f;
		float rotateTime = 3.0f;
		float alphaStartTime = 6.0f;
		float alphaTime = 3.0f;
		
		String titleString = "<color=255,255,000,000>パ</color>ロディことわざ";
		VerticalText titleText = new VerticalText(x(5), y(18), y(0), resourcesManager.font, 70, titleString, vbom);
		titleText.setPosition(MainActivity.CAMERA_WIDTH / 2, MainActivity.CAMERA_HEIGHT / 2);
		this.attachChild(titleText);

		String copyrightString = "ちゃばおり社 謹製";
		VerticalText copyrightText = new VerticalText(x(9), y(19), y(0), resourcesManager.font, 30, copyrightString, vbom);
		copyrightText.setPosition(MainActivity.CAMERA_WIDTH / 2, MainActivity.CAMERA_HEIGHT / 2);
		this.attachChild(copyrightText);

		String ownerString = "編者 ことぶき屋";
		VerticalText ownerText = new VerticalText(x(1), y(9), y(0), resourcesManager.font, 30, ownerString, vbom);
		ownerText.setPosition(MainActivity.CAMERA_WIDTH / 2, MainActivity.CAMERA_HEIGHT / 2);
		this.attachChild(ownerText);
		
		titleText.rotateFont(0, rotateStartTime, rotateTime);
		titleText.transparentAllFont(alphaStartTime, alphaTime, 1, 0);
		copyrightText.transparentAllFont(alphaStartTime, alphaTime, 1, 0);
		ownerText.transparentAllFont(alphaStartTime, alphaTime, 1, 0);

 文字列は、リソースID で引っ張ってくるのが正解。
 x() や y() は、座標を返す別メソッドです。気になさらずに。

 ぜひ、ご活用ください。

AndEngineで和文縦書きしてみるが、道半ば

 今作っているアプリは、日本語メインの「パロディことわざ」なので、なんとしてでも縦書きにしたいと思ったのですが、私の Android 2.3 機の WebView は縦書きCSSに対応していないことがわかりました。

 また、AndEngine を使わないで作ろうとしていたのですが、スプラッシュ画面やライフサイクル対応の実装を一から作るのも面倒だな・・・と。

 ならば、AndEngine で実装しようと、今日作ったのがこれです。
 device-2014-02-11-192113
 左右へのブレを調整して、禁則処理を入れてと、まだまだ道程は遠いのですが、完成しないこともないのではないかと思い、途中経過を掲載した次第です。

 こんなソースです。一応完成バージョンをご覧ください。2014/02/15
 
 フォントサイズも指定して、スケールの調整で表示しようという魂胆です。他にも色指定も加えたいし、<br>タグは解釈できるようにしたいものです。
 この VarticalText クラスを Scene を持っているクラスで Text クラスと同様にして貼り付けます。
 
 プログラミングに際しては、tomorrowkey さんのブログ「明日の鍵」から「Androidで縦書きを実現する」を参考に(ソースのコピーも)させていただきました。
 
 
 
 それと、このブログ WordPress テーマのスタイルシートを弄ったので、ちょっとは見やすくなったかな。

パズルゲーム第3弾 Colorful Slider リリース

 スライドパズルは、ロジックも簡単だから一週間で組めました。
 無料アプリ(広告表示)ですから、お気軽に遊んでください。
 device-2014-02-09-014353
 普通のスライドパズルのようにパネルに数字が書いてあったり、絵や写真があったり、はたまた、撮った写真をパズルにしたり、・・・では芸がないと思い、お隣のパネルと同じ色で接するように配置すれば、クリアとしました。
 クリア時の組み合わせパターンは、一般的なスライドパズルより増えているのですが、パネルの位置が特定できないので激ムズとなっています。
 device-2014-02-09-014407
 こんなステージは、絶対解けません。
 このままでは即アンインストールとなってしまうでしょうから、「Give up」ボタンを配置し1秒に1手さかのぼりながら自動解答するようにしてあります。
 自動解答中は、タイマーとしてお使いいただけます。9×9面なら180秒位は計れます。
 QR_slider ic_launcher-web
 Colorful Slider を Google Play からダウンロード

 以上、タイマーアプリ「Colorful Slider」の紹介でした。
 
 
 それにしても、これまでに公開したパズルゲームは難易度高く一般受けしないものばかりです。
 
 次回は、ゲームアプリではない「パロディことわざ」を予定しています。タイトル名からは何が何だか。
 これも一般受けしないと断言できます。
 
 ゲームではないので、AndEngine は使わないですね。

Polygons Mine Sweeper リリース

 御存知マインスイーパーの多角形バージョン「Polygons Mine Sweeper」を公開しました。

 device-2014-02-02-093641 こんな感じです。

 無料アプリ(広告表示)ですので、お気軽に遊んでください。

 画面構成は「Polygons Lights Out」をベースとしており、ロジックは以前作成したJavaアプレット版を参考にしていますので、短期間で完成しましたね。
 流用箇所が多いので、プログラム的 AndEngine の工夫点は増えてはいません。
 爆発アニメーションに爆発サウンドと本筋以外に時間をかけています。

 QR_MineSweeper ic_launcher-web
 Polygons Mine Sweeper を Google Play からダウンロード

 いろいろな形のパネルでのマインスイーパーをお楽しみください。

 次のアプリは、再びオーソドックスな「スライドパズル」を作る予定です。