弾力のあるシート
適当に球をドラッグして遊んでください。
バネ運動
ザクッと言って、バネ運動の力は、「基点との距離に比例」します。ここではいろいろ端折ってますので、正確なところは物理の教科書を読んでください。ke'ik jo':k skri'ptさんのエントリ(ActionScript3/Flash9)もとても参考になります。
下の球をドラッグして離すと、基点にボヨンと戻るようになっています。
球にリンクしたActionScriptクラスは、以下のとおりです。
戻る方向は、三角関数を使って計算しています。
class Spring extends MovieClip{
var center_x = 100;//基点
var center_y = 100;//基点
var SPRING_CONST = 1.7;
var onDrag = false;
function onLoad(){
this._x = center_x;
this._y = center_y;
}
function onPress(){ //ドラッグ開始
onDrag = true;
this.startDrag();
}
function onRelease(){ //ドラッグ終了
onDrag = false;
this.stopDrag();
}
function onEnterFrame(){
if( onDrag ) return; //ドラッグ中は無効
var rx = center_x - this._x;
var ry = center_y - this._y;
var radian = Math.atan2( ry, rx ) ;
//力は、基点との距離に比例
var N = Math.sqrt(Math.pow(rx,2)+Math.pow(ry,2));
//戻る方向を、X軸、Y軸に按分する。
var sx = N * Math.cos(radian);
var sy = N * Math.sin(radian);
this._x += SPRING_CONST*(sx);
this._y += SPRING_CONST*(sy);
}
}
バネの固定点を複数にする
ここまでは、基点は1つでしたが、タマが4本のバネにつながっていることを考えてみます。次のFlashは、4つのバネの固定点(黒タマ)のバランスがとれるところに、ボヨンするはずです。固定点を移動させて確かめてみてください。
とはいっても、むずかしく考える必要はなく、それぞれの固定点との関係を別々に計算して、最後に足し算すればOKです。修正したSpringクラスは、次のようになっています。
class Spring extends MovieClip{
var relatives:Array; //固定点のオブジェクト
var relative_size = 0;
var Sxs:Array;
var Sys:Array;
function Spring(){
relatives = new Array();
Sxs = new Array();
Sys = new Array();
}
//バネの固定点を追加する。
function addRelative(relative){
this.relatives.push(relative);
relative_size++;
}
:
function onEnterFrame(){
:
for( var i = 0; i < relative_size; i++ ){ //別々に計算して、
var rx = this.relatives[i]._x - this._x;
var ry = this.relatives[i]._y - this._y;
var radian = Math.atan2( ry , rx ) ;
var N = Math.sqrt( Math.pow(rx,2)+Math.pow(ry,2) );
this.Sxs[i] = N * Math.cos(radian);
this.Sys[i] = N * Math.sin(radian);
}
//足し算する。
this._x += SPRING_CONST*this.sumArray(this.Sxs)/this.relative_size;
this._y += SPRING_CONST*this.sumArray(this.Sys)/this.relative_size;
}
}
格子状につなぐ
そして、タマがいくら増えても、関係は変わりません。ひとつのタマにかかる力は、上と同じ4方向だけ。

ですので、Springクラスは変更せずに、タマを生成して、格子状に編みこむコードを書けば、冒頭のようになります。ここでもオブジェクト指向は、威力を発揮しまくりですね。もっと格子を細かくしていけば、トランポリンみたいな感じになると思います。
くどいようですが、正確な物理は端折ってるので、あしからず。
付録-格子を編むコード
var oset = 10;//全体配置のオフセット
var x_int = 25;//横方向の間隔
var y_int = 25;//縦方向の間隔
var x_max = 10;//横に並べる個数
var y_max = 10;//縦に並べる個数
var ballCount = x_max*y_max; //タマの個数
//タマと固定点のオブジェクトの格納領域
var balls:Array = new Array();
var fixTs:Array = new Array();
var fixLs:Array = new Array();
var fixRs:Array = new Array();
var fixBs:Array = new Array();
//シート周辺の固定点を配置する
for( var i =0; i < x_max; i++ ){
fixTs.push(_root.attachMovie( 'fix', 'fixT'+i,
_root.getNextHighestDepth(),
{ _x: (i+1) * x_int+oset, _y: oset } ) );
fixBs.push(_root.attachMovie( 'fix', 'fixB'+i,
_root.getNextHighestDepth(),
{ _x: (i+1) * x_int+oset, _y: (y_int)*(y_max+1)+oset } ) );
fixLs.push(_root.attachMovie( 'fix', 'fixL'+i,
_root.getNextHighestDepth(),
{ _x: oset, _y: (i+1)*y_int+oset } ) );
fixRs.push(_root.attachMovie( 'fix', 'fixR'+i,
_root.getNextHighestDepth(),
{ _x: (x_int)*(x_max+1)+oset, _y: (i+1)*y_int+oset } ) );
}
//タマをたくさん生成する。
//力が均衡するように自動配置されるので、座標指定はいりません。
for( var i = 0; i < ballCount; i++ ){
balls.push( _root.attachMovie( 'ball' , 'ball' + i , _root.getNextHighestDepth(), { } ) );
}
//タマに、それぞれの固定点(4つずつ)を設定する。
var row = 0;
var col = 0;
for( var i = 0; i < ballCount; i++ ){
//上端なら
if( row == 0 ){
balls[i].addRelative( fixTs[ col ] );
}else{
balls[i].addRelative( balls[ i - x_max ] );
}
//下端なら
if( row == y_max - 1 ){
balls[i].addRelative( fixBs[ col ] );
}else{
balls[i].addRelative( balls[ i + x_max ] );
}
//左端なら
if( col == 0 ){
balls[i].addRelative( fixLs[row] );
}else{
balls[i].addRelative( balls[i-1] );
}
//右端なら
if( col == x_max-1 ){
balls[i].addRelative( fixRs[row] );
row++;
col = 0;
}else{
balls[i].addRelative( balls[i+1] );
col++;
}
}