ActionScript 3.0で始めるオブジェクト指向スクリプティング

第53回 座標にもっとも近い線分上の点を内積で求める

この記事を読むのに必要な時間:およそ 1 分

インスタンスを折れ線に沿ってドラッグする

いよいよ仕上げだ。お題に述べたとおり,ステージに階段状の折れ線を描き,インスタンスをマウスでその線に沿ってのみドラッグできるようにする。まず,フレームアクションにつぎのスクリプトを加えると,折れ線が描かれる図5⁠。

// フレームアクションに追加
var vertices:Vector.<Point> = new Vector.<Point>();
vertices.push(new Point(40, 40));
vertices.push(new Point(80, 60));
vertices.push(new Point(100, 90));
vertices.push(new Point(160, 110));
vertices.push(new Point(200, 140));
var mySprite:Sprite = new Sprite();
var myGraphics:Graphics = mySprite.graphics;
addChildAt(mySprite, 0);
myGraphics.lineStyle(1, 0);
myGraphics.moveTo(vertices[0].x, vertices[0].y);
for (var i:uint = 1; i < vertices.length; i++) {
  myGraphics.lineTo(vertices[i].x, vertices[i].y);
}

図5 階段状に折れ線を描く

図5 階段状に折れ線を描く

折れ線の座標はPointオブジェクトにして,Vectorインスタンス(Pointベース型)に加えた。これらのPointオブジェクトは後で,マウス座標からもっとも近い線分上の点を求めるときにも使う。描画用のSpriteインスタンス(myGraphics)をつくり,Graphicsクラスの描画メソッドGraphics.moveTo()Graphics.lineTo()など)で折れ線を描いた。

仕上げは,インスタンスを折れ線に沿ってドラッグするスクリプトだ。ドラッグするMovieClipインスタンスmy_mcは予めタイムラインに置いておこう。前項で定めた,任意の座標から線分上のもっとも近い点を返す関数(xGetClosestPoint())が使われる。つぎのスクリプトをフレームアクションに加えればよい。

// フレームアクションに追加
var nStart:Number = new Point(stage.stageWidth, stage.stageHeight).length;
var my_mc:MovieClip;
my_mc.x = vertices[0].x;
my_mc.y = vertices[0].y;
my_mc.addEventListener(MouseEvent.MOUSE_DOWN, xStartMove);
function xStartMove(eventObject:MouseEvent):void {
  my_mc.addEventListener(Event.ENTER_FRAME, xMove);
  stage.addEventListener(MouseEvent.MOUSE_UP, xStopMove);
}
function xStopMove(eventObject:MouseEvent):void {
  my_mc.removeEventListener(Event.ENTER_FRAME, xMove);
  stage.removeEventListener(MouseEvent.MOUSE_UP, xStopMove);
}
function xMove(eventObject:Event):void {
  var nClosest:Number = nStart;
  var mousePoint:Point = new Point(mouseX, mouseY);
  var nLength:uint = vertices.length - 1;
  for (var i:uint = 0; i < nLength; i++) {
    var currentPoint:Point = 
      xGetClosestPoint(mousePoint, vertices[i], vertices[i + 1]);
    var nDistance:Number = mousePoint.subtract(currentPoint).length;
    if (nDistance <= nClosest) {
      var closestPoint:Point = currentPoint;
      nClosest = nDistance;
    }
  }
  my_mc.x = closestPoint.x;
  my_mc.y = closestPoint.y;
}

イベントInteractiveObject.mouseDown(定数MouseEvent.MOUSE_DOWNでドラッグを始め(リスナー関数xStartMove()⁠⁠,InteractiveObject.mouseUp(定数MouseEvent.MOUSE_UPにより終える(リスナー関数xStopMove()⁠⁠。ドラッグのアニメーションは,DisplayObject.enterFrame(定数Event.ENTER_FRAMEに加えられたリスナー関数(xMove())が行う。

関数xMove()は,ドラッグのためのマウスポインタ座標をPointインスタンス(mousePoint)に入れる。そして,forループで線分の両端座標を順に取出し,マウス座標にもっとも近い線分上の座標(currentPoint)を調べていく。その座標とマウス座標と結ぶ2次元ベクトルをPoint.subtract()メソッドでつくれば,Point.lengthプロパティの値が2点間の距離(nDistance)になる。メソッドPoint.subtract()は,2次元ベクトルの引き算で,ふたつのベクトルの終点を結ぶ新たなPointオブジェクトを返す※1⁠。

  • Pointオブジェクト.subtract(差引くPointオブジェクト)

調べた距離がこれまでの線分より短ければ,そのPointインスタンス(closestPoint)と距離(nClosest)をともに変数に保持する。forループですべての線分を調べ終えたとき保持されているPointオブジェクトが,もっとも近い線分上の点になるので,その位置にインスタンスの座標を設定する。

なお,初めて距離を調べたときには以前の値がないので,予め変数(nStart)に初期値としてステージの対角線の長さを設定した。また,インスタンスは初めに折れ線の始まりの点に置いた。フレームアクション全体をまとめたのが,つぎのスクリプト2だ。

スクリプト2 インスタンスを折れ線に沿ってドラッグする

// フレームアクション: メインタイムライン
function xGetClosestPoint(myPoint:Point, begin:Point, end:Point):Point {
  var myVector3D:Vector3D = new Vector3D(myPoint.x - begin.x, myPoint.y - begin.y);
  var baseVector3D:Vector3D = new Vector3D(end.x - begin.x, end.y - begin.y);
  var nDotProduct:Number = myVector3D.dotProduct(baseVector3D);
  if (nDotProduct > 0) {
    var nBaseLength:Number = baseVector3D.length;
    var nProjection:Number = nDotProduct / nBaseLength;
    if (nProjection < nBaseLength) {
      baseVector3D.scaleBy(nProjection / nBaseLength);
      return new Point(begin.x + baseVector3D.x, begin.y + baseVector3D.y);
    } else {
      return end;
    }
  } else {
    return begin;
  }
}
var vertices:Vector.<Point> = new Vector.<Point>();
vertices.push(new Point(40, 40));
vertices.push(new Point(80, 60));
vertices.push(new Point(100, 90));
vertices.push(new Point(160, 110));
vertices.push(new Point(200, 140));
var mySprite:Sprite = new Sprite();
var myGraphics:Graphics = mySprite.graphics;
addChildAt(mySprite, 0);
myGraphics.lineStyle(1, 0);
myGraphics.moveTo(vertices[0].x, vertices[0].y);
for (var i:uint = 1; i < vertices.length; i++) {
  myGraphics.lineTo(vertices[i].x, vertices[i].y);
}
var nStart:Number = new Point(stage.stageWidth, stage.stageHeight).length;
var my_mc:MovieClip;
my_mc.x = vertices[0].x;
my_mc.y = vertices[0].y;
my_mc.addEventListener(MouseEvent.MOUSE_DOWN, xStartMove);
function xStartMove(eventObject:MouseEvent):void {
  my_mc.addEventListener(Event.ENTER_FRAME, xMove);
  stage.addEventListener(MouseEvent.MOUSE_UP, xStopMove);
}
function xStopMove(eventObject:MouseEvent):void {
  my_mc.removeEventListener(Event.ENTER_FRAME, xMove);
  stage.removeEventListener(MouseEvent.MOUSE_UP, xStopMove);
}
function xMove(eventObject:Event):void {
  var nClosest:Number = nStart;
  var mousePoint:Point = new Point(mouseX, mouseY);
  var nLength:uint = vertices.length - 1;
  for (var i:uint = 0; i < nLength; i++) {
    var currentPoint:Point = 
      xGetClosestPoint(mousePoint, vertices[i], vertices[i + 1]);
    var nDistance:Number = mousePoint.subtract(currentPoint).length;
    if (nDistance <= nClosest) {
      var closestPoint:Point = currentPoint;
      nClosest = nDistance;
    }
  }
  my_mc.x = closestPoint.x;
  my_mc.y = closestPoint.y;
}

これで,インスタンスをドラッグすると,折れ線に沿って移動する図6⁠。さて,次回は外積をお題としたサンプルのムービーをつくろう。

図6 折れ線に沿ってインスタンスがドラッグできる

図6 折れ線に沿ってインスタンスがドラッグできる

※1)
引数のPointオブジェクトの座標が始点で,参照するPointオブジェクトの座標は終点になる。もっとも,前掲スクリプト2では長さPoint.lengthプロパティ)をとっているので,どちらが引数でも変わらない。

今回解説した次のサンプルファイルがダウンロードできます。

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書