サイトトップ

Director Flash 書籍 業務内容 プロフィール

Macromedia Flash非公式テクニカルノート

Graphics.drawPath()メソッドでパスの多いかたちを描く

ID: FN1109001 Product: Flash CS4 and above Platform: All Version: 10 and above/ActionScript 3.0

Graphics.drawPath()メソッドを使うと、Graphicsクラスの基本的な描画メソッド、Graphics.moveTo()Graphics.lineTo()およびGraphics.curveTo()を組合わせて1回の呼出しで線が描けます。パスの多い複雑なかたちを描くのに便利で、とくに同じ形状を数多くのインスタンスに描画する場合には処理が速められます。

本稿ではGraphics.drawPath()メソッドの基本的な使い方をご説明します。パスの多いかたちのお題には、星形を選びました。関数として定めて、頂点の数や大きさは引数で自由に決められるようにします。


01 Graphics.moveTo()とGraphics.lineTo()メソッドで星形を描く
まずは、Graphics.moveTo()Graphics.lineTo()メソッドを用いた関数の定義から始めましょう。そして、次項でGraphics.drawPath()メソッドを使った関数に書替えます。なお、Graphics.moveTo()Graphics.lineTo()メソッドを使った直線によるかたちの描き方については、gihyo.jp連載「ActionScript 3.0で始めるオブジェクト指向スクリプティング」第42回「Vector3Dクラスの3次元空間座標とインスタンスへの描画」の「Spriteインスタンスに直線を描く」をお読みください。

星形を描く関数は、つぎのように定めます。引数には頂点数(山の数)に、山と谷の半径を数値で定め、描画するGraphicsオブジェクトも渡します(図001)。タイムラインに置くインスタンスでなくGraphicsオブジェクトを引数にしたのは、SpriteやMovieClip(Sprite.graphicsプロパティ)とShape(Shape.graphicsプロパティ)のいずれのインスタンスにも描けるようにするためです。なお、中心は基準点とします。

xDrawStar(頂点数:uint, 山の半径:Number, 谷の半径:Number, 描画するGraphicsオブジェクト:Graphics):void
図001■頂点数および山と谷の半径で描く星形を定める

頂点数5

星の描き方は、つぎのような流れです。頂点数は山の数としましたので、まず谷も含めて「頂点数×2」で360度(2πラジアン)を等分します。その角度を順に回しながら、山と谷の座標を交互にとって直線で1周結べばよいのです。直線でかたちを描くには、Graphics.moveTo()メソッドで書き初めの位置を定め、あとはGraphics.lineTo()メソッドで各点を直線で結びます。そのように定めた関数(xDrawStar())が、つぎのスクリプト001です。

スクリプト001■Graphics.moveTo()とGraphics.lineTo()メソッドで星形を描く関数
    // フレームアクション
  1. function xDrawStar(numVertex:uint, longRadius:Number, shortRadius:Number, myGraphics:Graphics):void {
  2.   var nStart:Number = Math.PI;
  3.   var nTheta:Number = nStart / numVertex;
  4.   var nRadians:Number = 0;
  5.   numVertex *= 2
  6.   nStart /= 2;
  7.   myGraphics.moveTo(0, -longRadius);
  8.   for (var i:uint = 1; i < numVertex; i++) {
  9.     nRadians = i * nTheta - nStart;
  10.     myGraphics.lineTo(shortRadius * Math.cos(nRadians), shortRadius * Math.sin(nRadians));
  11.     nRadians = (++i) * nTheta - nStart;
  12.     myGraphics.lineTo(longRadius * Math.cos(nRadians), longRadius * Math.sin(nRadians));
  13.   }
  14. }

スクリプト001は、前述のとおり、関数(xDrawStar())に渡された頂点数(numVertex)を谷も含めて2倍にしています(第5行目)。その頂点数で1周2πラジアン(360度)を割れば、頂点間の角度(nTheta)が導けます。ただ、実際には半周のπラジアンを分子にしたので、2倍にする前の頂点数が分母になっています(第3行目)。

もうひとつ気をつけるのは、星形の描き始めを時計の12時の向き、つまり-π/2ラジアン(-90度)にしたことです(スクリプト001第7行目)。角度0は時計の3時になりますので、頂点の角度から2/πラジアンを引かなければなりません。その値を予め変数(nStart)に納めています(第2および第6行目)。

書き初めを山にしましたので(スクリプト001第7行目)、forループ内では谷と山を順に直線で結びます(第8〜13行目)。頂点ごとの角度を求めるとき、π/2を引いていることも確かめておきましょう(第9および第11行目)。

なお、原点からの距離(半径)をr、正のx軸となす角度をθとするとき、その(x, y)座標はつぎの式で表されます。詳しくは、前出「ActionScript 3.0で始めるオブジェクト指向スクリプティング」第16回「三角関数を使った楕円軌道のアニメーション」をお読みください。

x = r cosθ
y = r sinθ

フレームアクションにつぎのようなステートメントを加えれば、関数(xDrawStar())の呼出しが試せます。[ムービープレビュー]で確かめると、ステージの真ん中に置かれたShapeインスタンスに、5つの頂点をもつ青い星形が描かれます(図002)。

var myShape:Shape = new Shape();
var myGraphics:Graphics = myShape.graphics;
addChild(myShape);
myGraphics.beginFill(0x0000FF);
xDrawStar(5, 90, 40, myGraphics);   // 関数の呼出し
myGraphics.endFill();
myShape.x = stage.stageWidth / 2;
myShape.y = stage.stageHeight / 2;

図002■ステージ中央に描かれた5頂点の星形


02 Graphics.drawPath()メソッドで星形を描く
Graphics.drawPath()メソッドには、引数としてふたつのVectorオブジェクトを渡します。第1引数はintベース型のVectorオブジェクトで、使うメソッドを順に整数で加えます。整数値は、GraphicsPathCommandクラスの定数として下表001のように定められていますので、それを用います。第2引数はNumberベース型のVectorオブジェクトで、メソッドに渡す座標値を納めます。

Graphicsオブジェクト.drawPath(コマンドVector:Vector.<int>, 座標Vector:Vector.<Number>):void
表001■GraphicsクラスのおもなメソッドとGraphicsPathCommandクラス定数
Graphicsクラスのメソッド GraphicsPathCommandクラスの定数 定数値
moveTo() MOVE_TO 1
lineTo() LINE_TO 2
curveTo() CURVE_TO 3

それでは、前掲スクリプト001のGraphics.moveTo()Graphics.lineTo()メソッドを、Graphics.drawPath()メソッドに置換えましょう。関数(xDrawStar())の使い勝手も考えて、引数を少し変えます。初めの3つの数値はスクリプト001と同じです。けれど、Graphicsオブジェクトを渡すのは止め、替わりにふたつのVectorオブジェクトを加えました。これらは、後でGraphics.drawPath()メソッドにふたつの引数として渡すVectorオブジェクトで、中身は空です。

xDrawStar(頂点数:uint, 山の半径:Number, 谷の半径:Number, コマンドVector:Vector.<int>, 座標Vector:Vector.<Number>):void

前掲スクリプト001の関数(xDrawStar())は、引数のGraphicsオブジェクトに線を描きました。しかし、新しい関数はふたつのVectorオブジェクトに必要な値を納めるだけに止め、描画はしません。Graphics.drawPath()メソッドが、引数に渡されたふたつのVectorオブジェクトからかたちを描くからです。Graphics.drawPath()メソッドを使って書替えた関数が、つぎのスクリプト002です。

スクリプト002■Graphics.drawPath()メソッドで星形を描く関数
    // フレームアクション
  1. function xDrawStar(numVertex:uint, longRadius:Number, shortRadius:Number, commands:Vector.<int>, coordinates:Vector.<Number>):void {
  2.   var nStart:Number = Math.PI;
  3.   var nTheta:Number = nStart / numVertex;
  4.   var nRadians:Number = 0;
  5.   numVertex *= 2
  6.   nStart /= 2;
  7.   commands.push(GraphicsPathCommand.MOVE_TO);
  8.   coordinates.push(0, -longRadius);
  9.   for (var i:uint = 1; i < numVertex; i++) {
  10.     commands.push(GraphicsPathCommand.LINE_TO, GraphicsPathCommand.LINE_TO);
  11.     nRadians = i * nTheta - nStart;
  12.     coordinates.push(shortRadius * Math.cos(nRadians), shortRadius * Math.sin(nRadians));
  13.     nRadians = (++i) * nTheta - nStart;
  14.     coordinates.push(longRadius * Math.cos(nRadians), longRadius * Math.sin(nRadians));
  15.   }
  16. }

関数(xDrawStar())が行っている計算は、前掲スクリプト001と変わりません。ただ、Graphicsオブジェクトへの描画は行わず、呼出すメソッドの定数と座標値をふたつのVectorオブジェクト(commandsとcoordinates)それぞれに納めているという違いがあるだけです(スクリプト002第7〜15行目)。

この関数数(xDrawStar())はつぎのようなスクリプトをフレームアクションに加えて呼出すと、前項と同じ5頂点の星形がステージ中央に描けます(前掲図002)。関数の呼出しだけでは何も描かれませんので、Graphics.drawPath()メソッドにふたつのVectorオブジェクトを渡して描画しています。

var myShape:Shape = new Shape();
var myGraphics:Graphics = myShape.graphics;
var commands:Vector. = new Vector.();
var coordinates:Vector. = new Vector.();
addChild(myShape);
myGraphics.beginFill(0x0000FF);
xDrawStar(5, 90, 40, commands, coordinates);
myGraphics.drawPath(commands, coordinates);
myGraphics.endFill();
myShape.x = stage.stageWidth / 2;
myShape.y = stage.stageHeight / 2;


03 Graphics.drawPath()メソッドは同じかたちをたくさん描くとき使うのがよい
[ヘルプ]の「Graphics.drawPath()メソッド」には、Graphics.lineTo()Graphics.curveTo()Graphics.drawPath()メソッドに替えると直ちに描画が速くなるような説明があります。

一般に、描画では、drawPath()を使用する方が、一連の個別のlineTo()メソッドとcurveTo()メソッドを使用するよりもレンダリングが高速です。

しかし、筆者の試したかぎりでは、使うメソッドを替えただけで明らかに処理が速くなるということはなさそうでした。むしろ、Graphics.drawPath()メソッドの使い方を活かすという捉え方がよさそうに思われます。

ひとつは、複雑なパスを描く場合です。Graphics.drawPath()メソッドを用いれば、描画メソッドと座標をまとめて1度に呼出せるのは便利です。それだけでなく、座標値が多ければ予めVectorオブジェクトに納められていることも少なくないでしょう。そのVectorオブジェクトがNumberベース型なら、そのままGraphics.drawPath()メソッドの第2引数に渡せます。

[ヘルプ]の[描画API]は、そのような場合を示しています。Graphics.moveTo()Graphics.lineTo()メソッドを使った初めの例では、座標値がNumberベース型のVectorオフジェクトに納められていて、エレメントの座標値をひとつひとつ取出したうえでGraphics.moveTo()Graphics.lineTo()メソッドに渡しています。

var coords:Vector.<Number> = Vector.<Number>([132, 20, 46, 254, 244, 100, 20, 98, 218, 254]);

container.graphics.moveTo(coords[0], coords[1]);
container.graphics.lineTo(coords[2], coords[3]);
container.graphics.lineTo(coords[4], coords[5]);
container.graphics.lineTo(coords[6], coords[7]);
container.graphics.lineTo(coords[8], coords[9]);

それに対して、Graphics.drawPath()メソッドを使う例では、座標値のVectorオフジェクト(Numberベース型)はそのまま第2引数として渡されます。第1引数のコマンドVector(intベース型)を別につくらなければならないとはいえ、扱いは速められるでしょう[*1]

var commands:Vector.<int> = Vector.<int>([1, 2, 2, 2, 2]);
var coords:Vector.<Number> = Vector.<Number>([132, 20, 46, 254, 244, 100, 20, 98, 218, 254]);

container.graphics.drawPath(commands, coords);

Graphics.drawPath()メソッドの使い方がもっとも活かせるのは、同じかたちをたくさんのインスタンスに描く場合です。かたちが同じなら、引数に渡すふたつのVectorオブジェクトをつくり直さずに済み、そのまま使い回せるからです。たとえば、前掲スクリプト002の関数を使って、タイムラインに数多くの星を描いてみます。

前掲スクリプト002のフレームアクションにつぎのスクリプト003を加えると、タイムラインに置いた500個のShapeインスタンスに12頂点の同じ星を描きます。星の塗り色とインスタンスの位置は、ランダムに決めています(図003)。

スクリプト003■たくさんのShapeインスタンスに同じかたちの星を描く
    // フレームアクションに追加
  1. var commands:Vector.<int> = new Vector.<int>();
  2. var coordinates:Vector.<Number> = new Vector.<Number>();
  3. var count:uint = 500;
  4. var nWidth:int = stage.stageWidth;
  5. var nHeight:int = stage.stageHeight;
  6. xDrawStar(12, 10, 4, commands, coordinates);
  7. for (var i:uint = 0; i < count; i++) {
  8.   var myShape:Shape = new Shape();
  9.   var myGraphics:Graphics = myShape.graphics;
  10.   addChild(myShape);
  11.   myGraphics.beginFill(int(Math.random() * 0xFFFFFF));
  12.   myGraphics.drawPath(commands, coordinates);
  13.   myGraphics.endFill();
  14.   myShape.x = Math.random() * nWidth;
  15.   myShape.y = Math.random() * nHeight;
  16. }

図003■12頂点の星形500個をランダムな色と位置で描く

タイムラインのShapeインスタンスに星形を描く基本的な流れは、前項に掲げたフレームアクションと変わりません。ただ、500個のShapeをつくってタイムラインに置き、ランダムな塗りと位置に星形を描く処理はforループで行っています(第7〜16行目)。Graphics.drawPath()メソッドも、この繰返しの中で呼出します。

けれど、ふたつの引数となるVectorオブジェクトをつくる関数(xDrawStar)は、forループの外で呼出されていることにご注目ください(第6行目)。つまり、描く星形は同じなので、ひとたびつくられたふたつのVectorオブジェクトは、そのまま繰返し使い回されているということです。その分、Graphics.moveTo()Graphics.lineTo()メソッドを用いるより速く描画できます。


04 メソッドGraphics.moveTo()とGraphics.lineTo()による描画の速さをGraphics.drawPath()と比べる
Graphics.moveTo()Graphics.lineTo()メソッドによる描画とGraphics.drawPath()メソッドの速さをサンプルスクリプトで比べてみましょう。wonderflに以下のテスト用コードをアップロードしました。100頂点の星形500個を描く3つのやり方について、処理にかかった時間をミリ秒で示します[*2]

  1. Graphics.drawPath(): forループの中でVectorオブジェクトをつくる
    • test_drawPath()メソッド(wonderfl第69〜84行目)
    • drawStar_w_drawPath()メソッド(wonderfl第115〜130行目)
  2. Graphics.drawPath() 2: forループの外でつくったVectorオブジェクトを使い回す
    • test_drawPath2()メソッド(wonderfl第85〜100行目)
    • drawStar_w_drawPath()メソッド(wonderfl第115〜130行目)
  3. traditional Graphics API: Graphics.moveTo()とGraphics.lineTo()メソッドを使う
    • test_API()メソッド(wonderfl第56〜68行目)
    • drawStar_w_API()メソッド(wonderfl第101〜114行目)

Comparing Graphics.drawPath() method with traditional Graphics API - wonderfl build flash online

まず第3の手法が、本稿01「Graphics.moveTo()とGraphics.lineTo()メソッドで星形を描く」に当たります(スクリプト001)。つぎに第2は、前項03「Graphics.drawPath()メソッドは同じかたちをたくさん描くとき使うのがよい」と同じく、1度つくった引数のVectorオブジェクトふたつをforループの中で使い回しています(スクリプト002と003)。残る第1は、第2のスクリプト(003)でVectorオブジェクトをつぎのようにforループの中で繰返しつくった場合です。

  1. // xDrawStar(12, 10, 4, commands, coordinates);
  2. for (var i:uint = 0; i < count; i++) {
  3.   var myShape:Shape = new Shape();
  4.   var myGraphics:Graphics = myShape.graphics;
  5.   addChild(myShape);
  6.   myGraphics.beginFill(int(Math.random() * 0xFFFFFF));
      xDrawStar(12, 10, 4, commands, coordinates);
  7.   myGraphics.drawPath(commands, coordinates);
  8.   myGraphics.endFill();

結果は環境によっても試行によっても変わってきます。筆者の環境では、第1と第3とは速さに明らかな差が認められませんでした。第2はおおむね他のふたつより速く、Vectorオブジェクトの使い回しは有効と思われます。

[*1] Flash Professional CS5以降なら、「エレメントをもったVectorインスタンスの生成」の04でご紹介した「新しいnewシンタックスを使う」方がVectorインスタンスはより速くつくれます。また、何度も参照するShape.graphicsプロパティは、予め変数にとっておくとよいでしょう。

var container:Shape = new Shape();
var myGraphics:Graphics = container.graphics;
// container.graphics.beginFill(0x442299);
myGraphics.beginFill(0x442299);

// var commands:Vector.<int> = Vector.<int>([1, 2, 2, 2, 2]);
var commands:Vector.<int> = new <int>[1, 2, 2, 2, 2];
// var coords:Vector.<Number> = Vector.<Number>([132, 20, 46, 254, 244, 100, 20, 98, 218, 254]);
var coords:Vector.<Number> = new <Number>[132, 20, 46, 254, 244, 100, 20, 98, 218, 254];

// container.graphics.drawPath(commands, coords);
myGraphics.drawPath(commands, coords);

addChild(container);

[*2] ランダムな色と位置を決めるために用いるMath.random()メソッドで時間が費やされないように、ふたつの500個分の数値はそれぞれ予めVectorオブジェクトに納め、計測時間には含めていません(initialize()メソッドwonderfl第38〜43行目)。また、Shapeインスタンスも予め500個Vectorオブジェクトとタイムラインに加えてあります(createInstances()メソッドwonderfl第44〜55行目)。


作成者: 野中文雄
作成日: 2011年9月15日


Copyright © 2001-2011 Fumio Nonaka.  All rights reserved.