吹き出しクラス
package {
import flash.display.Sprite;
[SWF(width=465, height=465, backgroundColor=0xFFFFFF, frameRate=30)]
public class FlashTest extends Sprite {
public function FlashTest() {
var textStr1:String = "あんなの飾りです。偉い人にはそれが分からんのですよ。\n(通常モード)";
var textStr2:String = "あんなの飾りです。偉い人にはそれが分からんのですよ。\n(debugモード)";
var fukidashi1:Fukidashi = new Fukidashi(stage, 70 , 250 , 150 , textStr1);//通常モードでオブジェクト作成
var fukidashi2:Fukidashi = new Fukidashi(stage, 270 , 250 , 150 , textStr2, true);//debugモードでオブジェクト作成
}
}
}
import flash.accessibility.AccessibilityProperties;
import flash.display.*;
import flash.events.*;
import flash.geom.Point;
import flash.text.*;
internal class Fukidashi {
public var bottomSheet:Sprite;//このクラスで描画するのを乗せるSprite
public const REP_LENGTH:Number=2;//コントロールポイントの位置を決めるための代表値
public const PADDING:Number=5;
public const TAIL_POSITION:uint = 5;//この番号のラインの途中に尻尾を作成(変えるとだめ!)
public const TAIL_POSITION_RATE:Number = 0.5;//ベジェ曲線のtがこの値を中心として尻尾が生える
public const TAIL_WIDTH_RATE:Number = 0.04;//TAIL_POSITION_RATE-TAIL_WIDTH_RATE ~ TAIL_POSITION_RATE+TAIL_WIDTH_RATE で尻尾の根元
public var commentTF:TextField;//文字列はこのテキストフィールドで表示
public function Fukidashi(st:Object , tailX0:Number , tailY0:Number, w0:Number , textStr:String , fDebug:Boolean=false) {// コンストラクタ
var ii:uint;
var h0:Number;//文字列を表示する高さ(横幅w0は引数で受け、文字数によりh0を後で決めてます)
//bottomSheetの作成
bottomSheet = new Sprite();
st.addChild(bottomSheet);
bottomSheet.visible = true;//デフォルトは表示
if (fDebug) {
var debugSheet:Sprite = new Sprite();//デバッグ用の点などはここに描きます
}
//デバッグ時のみtailX0 , tailY0の表示
if (fDebug) {
var tailTopSpr:Sprite = new Sprite();
tailTopSpr.graphics.beginFill(0xFF0000);
tailTopSpr.graphics.drawCircle(tailX0, tailY0, 3);
tailTopSpr.graphics.endFill();
st.addChild(tailTopSpr);
}
//テキストフィールド設定
commentTF= new TextField();
commentTF.text = textStr;
commentTF.width = w0 + 4;
commentTF.multiline = true;
commentTF.wordWrap = true;
h0 = commentTF.textHeight + 4;//ここでテキストフィールドの高さを決めてます
commentTF.height = h0;
if (fDebug) {
//テキストフィールドのデバッグ用表示
var displayArea:Sprite = new Sprite();
displayArea.graphics.beginFill(0xEEEEEE);
displayArea.graphics.drawRect(0, 0, w0, h0);
displayArea.graphics.endFill();
debugSheet.addChild(displayArea);
//PADDINGを考慮した外枠の表示(ここにアンカーポイントが乗ります)
var displayFrame:Sprite = new Sprite();
displayFrame.graphics.lineStyle(0, 0xAAAAAA);
displayFrame.graphics.drawRect(-PADDING, -PADDING, w0+2*PADDING, h0+2*PADDING);
debugSheet.addChild(displayFrame);
}
//アンカーポイント
var anchorPoint:Array = new Array();
anchorPoint.push(new Point(w0 *0.11, -PADDING ) ) ;
anchorPoint.push(new Point(w0 *0.675, -PADDING ) ) ;
anchorPoint.push(new Point(w0 + PADDING , h0 *0.21));
anchorPoint.push(new Point(w0 + PADDING , h0 *0.9));
anchorPoint.push(new Point(w0 *0.825, h0 + PADDING ) ) ;
anchorPoint.push(new Point(w0 *0.36, h0 + PADDING ) ) ;
anchorPoint.push(new Point(-PADDING , h0 *0.85));
anchorPoint.push(new Point( -PADDING , h0 * 0.25));
var nAnchorPoint:int = anchorPoint.length;//アンカーポイントの数
if (fDebug) {//アンカーポイントのデバッグ用表示
for (ii = 0; ii < nAnchorPoint; ii++) drawDebugDot(anchorPoint[ii].x , anchorPoint[ii].y , 0x0000CC);
}
//コントロールポイントの位置を決める係数
//※コントロールポイント座標はアンカーポイントからの相対座標で、この係数とREP_LENGTHから決まります
var keisuuArr:Array = new Array();
keisuuArr.push( { x:1.6 , y:-2.4 } );
keisuuArr.push( { x:-2.4 , y:-2 } );
keisuuArr.push( { x:8.2 , y:-2 } );
keisuuArr.push( { x:1.8 , y:-5.6 } );
keisuuArr.push( { x:1.6 , y:2.8 } );
keisuuArr.push( { x:1.4 , y:-2.6 } );
keisuuArr.push( { x:0.8 , y:3.6 } );
keisuuArr.push( { x:3.8 , y:1.2 } );
keisuuArr.push( { x:-3.2 , y:1.8 } );
keisuuArr.push( { x:3.4 , y:2 } );
keisuuArr.push( { x:-5.4 , y:2.4 } );
keisuuArr.push( { x:-1.6 , y:5 } );
keisuuArr.push( { x:-1.4 , y:-2 } );
keisuuArr.push( { x:-1.6 , y:2.4 } );
keisuuArr.push( { x:-1 , y:-2.4 } );
keisuuArr.push( { x: -5.2 , y: -0.8 } );
//コントロールポイント
var controlPoint:Array = new Array();
//コントロールポイント座標計算
var ite1:int, ite2:int, xx:Number, yy:Number;
for (ii = 0; ii < nAnchorPoint; ii++) {
ite2 = ii * 2;
ite1 = (ite2 - 1 == -1) ? 15 : ite2 - 1;
xx = REP_LENGTH * keisuuArr[ite1].x;
yy = REP_LENGTH * keisuuArr[ite1].y;
controlPoint.push(new Point(xx, yy));
xx = REP_LENGTH * keisuuArr[ite2].x;
yy = REP_LENGTH * keisuuArr[ite2].y;
controlPoint.push(new Point(xx, yy));
}
var nControlPoint:int = controlPoint.length;//コントロールポイントの数
//ベジェ曲線を描く
//※輪郭は3次ベジェ曲線を直線をつないで表示、尻尾部分は2次ベジェ曲線(curveToメソッド)で表示してます
var tailTopPt:Point;//尻尾の先端の座標
var p0:Point, p1:Point, p2:Point, p3:Point , t:Number, pt:Point , dt:Number=0.02;
var nextnn:uint;
var bezierCurveSpr:Sprite = new Sprite();//ベジェ曲線はここに乗せます
bottomSheet.addChild(bezierCurveSpr);
bezierCurveSpr.graphics.beginFill(0xFFFFFF);//吹き出し内部の色
bezierCurveSpr.graphics.lineStyle(0, 0x333333);//吹き出しの線の幅と色
for (ii = 0; ii < nAnchorPoint; ii++) {
drawBezierCurve(ii);
}
bezierCurveSpr.graphics.endFill();
function drawBezierCurve(nn:uint):void {//ベジェ曲線を描く
p0 = new Point(anchorPoint[nn].x , anchorPoint[nn].y);//アンカーポイント
nextnn = (nn + 1 == nAnchorPoint) ? 0 : nn + 1;//最後だと次のnnは0になる
p3 = new Point(anchorPoint[nextnn].x , anchorPoint[nextnn].y);//アンカーポイント
ite1 = nn * 2 + 1;
ite2 = (ite1 + 1 == nControlPoint) ? 0 : ite1 + 1;
p1 = new Point(p0.x+controlPoint[ite1].x , p0.y+controlPoint[ite1].y);//コントロールポイント
p2 = new Point(p3.x+controlPoint[ite2].x , p3.y+controlPoint[ite2].y);//コントロールポイント
if (fDebug) {//デバッグ時のコントロールポイントの表示
drawDebugDot(p1.x , p1.y , 0x990000);
drawDebugDot(p2.x , p2.y , 0x990000);
}
if (nn==0) bezierCurveSpr.graphics.moveTo(p0.x, p0.y);//最初だけmoveするけどそれ以降は不要
for (t = 0.0; t <= 1.0; t += dt) {
if ( (nn == TAIL_POSITION) && (TAIL_POSITION_RATE - TAIL_WIDTH_RATE < t) && (t < TAIL_POSITION_RATE + TAIL_WIDTH_RATE) ) {//尻尾の根元の部分か?
if ( (TAIL_POSITION_RATE-dt/2 < t)&&(t < TAIL_POSITION_RATE+dt/2) ) {
//↑t==TAIL_POSITION_RATEで評価すると誤差のせいかtrueにならないことがあったのでこんな風にしてます
//↑がtrueの場合のみ1回だけ↓の関数をCALLして尻尾を描きます
drawTail(getBezierPoint(TAIL_POSITION_RATE-TAIL_WIDTH_RATE),getBezierPoint(t),getBezierPoint(TAIL_POSITION_RATE+TAIL_WIDTH_RATE));
}
}else{//通常こちらで輪郭を描く
pt = getBezierPoint(t);
bezierCurveSpr.graphics.lineTo(pt.x, pt.y);
}
}
bezierCurveSpr.graphics.lineTo(p3.x, p3.y);
}
function drawTail(tailPtA:Point, tailPtC:Point, tailPtB:Point):void {//尻尾部分の描画(ここは1回だけCALLされます)
//ここは2次のベジェ曲線(curveToメソッド)で描く
tailTopPt= new Point(tailPtC.x - 17 , tailPtC.y + 20);//尻尾の先っぽの位置をこのようにして決めてます
//第1の線
drawATailCurve(1, tailPtA, tailTopPt);
//第2の線
drawATailCurve(2, tailTopPt, tailPtB);
function drawATailCurve(curveNumber:uint, p0:Point , p1:Point):void {//1つの2次ベジェ曲線を描きます
//p0,p1はアンカーポイント
var pCent:Point;//2つのアンカーポイントの中点
var pCont:Point;//コントロールポイント
pCent = new Point( (p0.x + p1.x) / 2 , (p0.y + p1.y) / 2);
//コントロールポイントは中点を通り、アンカーポイントを結ぶ直線と直交する直線上にあるように計算
var tpt1:Point , tpt2:Point, rad:Number = 10 * Math.PI / 180;//この角度を小さくするほど直線に近くなる
if (curveNumber==2) rad *= -1;
var xl1:Number = p0.x - pCent.x;
var yl1:Number = p0.y - pCent.y;
tpt1 = new Point(pCent.x + Math.cos(rad) * xl1 - Math.sin(rad) * yl1 , pCent.y + Math.sin(rad) * xl1 + Math.cos(rad) * yl1);
rad *= -1;
xl1 = p1.x - pCent.x;
yl1 = p1.y - pCent.y;
tpt2 = new Point(pCent.x + Math.cos(rad) * xl1 - Math.sin(rad) * yl1 , pCent.y + Math.sin(rad) * xl1 + Math.cos(rad) * yl1);
pCont = new Point( (tpt1.x + tpt2.x) / 2 , (tpt1.y + tpt2.y) / 2);
bezierCurveSpr.graphics.curveTo(pCont.x , pCont.y , p1.x , p1.y);//2次ベジェ曲線
}
}
function getBezierPoint(t:Number):Point {//3次ベジェ曲線を求める式
return new Point(Math.pow(1 - t, 3) * p0.x + 3 * t * Math.pow(1 - t, 2) * p1.x + 3 * t * t * (1 - t) * p2.x + t * t * t * p3.x , Math.pow(1 - t, 3) * p0.y + 3 * t * Math.pow(1 - t, 2) * p1.y + 3 * t * t * (1 - t) * p2.y + t * t * t * p3.y);
}
//位置調整(引数の座標に尻尾の先が来るようにbottomSheetを移動)
bottomSheet.x = tailX0-tailTopPt.x;
bottomSheet.y = tailY0-tailTopPt.y;
//デバッグ時のみ
if (fDebug) bottomSheet.addChild(debugSheet);
//テキストフィールド
bottomSheet.addChild(commentTF);//ベジェ曲線より後じゃないとだめなのでここでaddChildしてます
function drawDebugDot(xx:Number , yy:Number , color:Number):void {//デバッグ用の点の表示
var dot:Sprite = new Sprite();
dot.graphics.beginFill(color);
dot.graphics.drawCircle(xx, yy, 3);
dot.graphics.endFill();
debugSheet.addChild(dot);
}
}////////////////////////////////////////////////////////////////////////////////コンストラクタ
////////////////////////////////////////////////////////////////////////////////
public function set visible(f:Boolean):void {
bottomSheet.visible = f;
}////////////////////////////////////////////////////////////////////////////////
}