2回にわたってお送りしました「和文縦書き」も本日をもって一応の完成です。
<br>タグで改行し、<color=255,255,255,255> </color>という独自のタグで文字色を変更します。色指定の書式は格好悪いが目を瞑りましょう。
行末、行頭の禁則処理は、和文で出現する、。ー「」()あたりまでフォローしました。が、行末禁則処理の直後に<br>があると更に改行し1行空いた状態となります。仕様です。
半角英数字は、つなげて横向き表示としました。長さがその行に収まらない場合は、次の行に追い出します。1行の文字数長を超える半角英数字を表示しようとすると・・・たぶんエラーになります。仕様です。
AndEngine の Scene に、文字列をばらして一文字ずつ縦に Text として表示するということは、文字ひとつひとつが Entity となります。せっかくだから AndEngine での表示ならではの機能も加えました。
ひとつが、文字の回転です。指定秒数後に指定時間かけて一回転します。
ふたつめが、文字のフェードイン(アウト)です。指定秒数後に指定時間かけて云々。文字をインデックスで指定、全文を一気に、全文を先の文字から順番に、といったパターンを用意しました。
タイトルだけ作ってみました。前は、回転中。後は、フェードアウト中。
縦書き処理のソースコードはこちらです。
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() は、座標を返す別メソッドです。気になさらずに。
ぜひ、ご活用ください。