August 2008
Flex入門者がアニメーションを基礎から説明してみる #7(最終回)
- 2008-08-10 (Sun)
- ActionScript , Flash/Flex
突然ですがこの「Flex入門者がアニメーションを基礎から説明してみる」の連載は今回で終了とします。 なぜかというと学習している書籍の内容的に1、2回のエントリーで書ききれるものが尽きてしまったからです。 本当はここから以下のようなものに話は及びます。
キネマティクス
人体の腕や足のように、関節を持った部品どうしが関連を持って運動する
3Dの基本
奥行きのある空間を平面上に表します。RPGゲームの3Dダンジョンのようなことができます。
3Dの応用
物体を立体的に表現し、そこに照明を当てて影などをつけます。 まだ読んでないので正確には理解してませんが。
これらのことをブログに書こうと思ったら、一つの話題を理解しやすく書くのに5回程度のエントリーを必要としそうです。 そしてそれは書籍をそのまま転載することとあまり差がなくなってしまいそうな気がするので、僕の望むところではありません。
まとめとして
最終回は新しいことを解説しません。 今まで解説してきたことの中でメインとなっていた、物体を現実と同じように運動させる方法についてのまとめを書きます。
僕がボールを使用して解説した運動は以下のものです。
等速直線運動
一番基本的な運動です。力を加えたらそっちの方に飛んで行く、もしくは転がっていく。
反射
主に壁に跳ね返ったときに使用しました。 反射する障害物に角度があるとき、すなわち外壁ではなく斜めになっている壁などに反射させるには少し高度な応用が必要です。僕は解説していませんが、書籍には1章割いて解説があります。
落下運動
これは等加速度運動の一例でした。宇宙空間のロケットの噴射にも応用できます。
摩擦
ボールが転がり続けたり、跳ね返り続けないための処置です。 現実世界は常に摩擦があるので、これを行わないと自然な動きになりません。
バネ
これは重力の応用です。 重力は常に同じ力が下に加わっていますが、バネはバネの始点に向かって、離れているほど強い加速度が加わります。
一つずつ解説
さて、あなたがここでの例のようにボールなどをリアルに運動させようとしたとき、まず行うべきは「等速直線運動」の処理を実装することです。 具体的に言うと、以下の2つを用意するのです。
等速直線運動の実装
X方向の速度とY方向の速度をフィールドで宣言する
このようにします。 最初は固定で適当な値を入れてもいいでしょう。
private var vx:Number; //X方向の速度 private var vy:Number; //Y方向の速度単位時間ごとに速度を位置に加算する
Flexの場合はENTER_FRAMEイベントにリスナ関数をセットして、フレーム毎に実行される関数内で速度を物体の位置に加算します。 ballというオブジェクトが表示されているのであれば以下のようにします。
private function onEnterFrame(event:Event):void{ ball.x += vx; ball.y += vy; }
これだけでボールは同じ方向に向かって進み続けるでしょう。 これ自体はとっても簡単ですが、このとっても簡単なことはいつも変わらないし、あとの仕事はさらにシンプルなのです。
壁の反射、重力、バネ、摩擦などの実装
色々なサンプルを掲載してきましたが、やっていることにはある共通点があります。 それはいじるのは速度だけということです。 壁の外にはみ出たときなど、特殊な例でない限り、物体のX座標やY座標はいじりません。 常に速度を適切な場所で少し変更するだけで、これらは実現できるのです。 一つずつ解説していきましょう。
壁の反射
物体が移動することにより、壁との衝突を検知した時点で速度を反転させるだけです。 つまり、横の壁に衝突したら
vx *= -1;します。縦の壁に衝突したらvy *= -1;です。 たったこれだけ。重力
単位時間ごとつまりENTER_FRAMEのリスナ関数内で速度に重力の影響を加算します。 上のonEnterFrame関数に以下の一行を加えるような感じです。
private function onEnterFrame(event:Event):void{ ball.x += vx; ball.y += vy; vy += gravity; //gravityは重力の作用を表す数値(0.1などを代入しておく) }摩擦
摩擦とは常に速度を0に近づけるので、速度に一定の値を単位時間ごとにかけるだけです。 上のサンプルだと以下の2行を加えます。
private function onEnterFrame(event:Event):void{ ball.x += vx; ball.y += vy; vy += gravity; //gravityは重力の作用を表す数値(0.1などを代入しておく) vx *= friction; //frictionは摩擦を表す数値(0.95などを代入しておく) vy *= firction; }
バネの解説は前回やったため割愛します。 どうでしょうか?とてもシンプルではないですか? 実際に完成させるにはもう少し色々な処理を考える必要がありますが、基本的にはこれでいけます。
そしてこれらは、必要なものだけを取捨選択して書き加えればよいのです。 例えばビリヤード台を上から見た図なら、等速直線運動と反射、摩擦を実装する。しかし重力はいらないから実装しない、とか。 バネと重力はいるけど、摩擦は実装せずにずっとビヨンビヨン動かしておこう、とか。 自分の作りたい世界に合わせて、必要なものだけを書き加えるという実にシンプルな作り方ができるのです。
さらに、ここで解説していない処理を加えるとしても、多くの場合は既存のコードに修正はほとんどいりません。 その処理に必要な操作を、「速度」に対して行うだけでいいのです。そういえばボールをドラッグで掴んで投げた処理もそうでした。 とっても複雑な処理も、実は一つずつの処理を順番に実行している場合がほとんどなのです。
どうですか?簡単でしょう? 多くの人がアニメーションの簡単さ、楽しさに気付いてくれることを期待しています。 もちろん、もっともっと複雑なことがやりたい人は、簡単なことばかりでは済まないないでしょう。 その場合でも、この本さえあれば、きっと糸口は見つかります。
また宣伝かよ、とか思ったそこのあなた。だから初めからそうだと言ってるじゃないですか。
では皆さん、楽しいアニメーション作成を。
Flex入門者がアニメーションを基礎から説明してみる #6
- 2008-08-05 (Tue)
- ActionScript , Flash/Flex
今回はバネです。ていうかFlexBuilderの試用期間があと10日に迫ってまいりました。あと10日で連載終了などできるわけもなく・・・・・・買うしかないか・・・。それともフリーのFlashDeveloper使うかな?いや、ここは大人しく購入しておくのがいいだろう。
だって買ったら頑張って使わなきゃ元取れないし!
だから頑張れるし!!
摩擦で速度を抹殺せよ!!
夜は恥ずかしいダジャレがためらうことなく口から出てきます。
まあそれはいいとして、前回作成したコードに修正を加えます。 あのままだと、ボールは最終的に転がりつづけたままなかなか止まりません。 壁に反射したときに速度は少し減少しているので、そのうち止まりますが、現実世界には摩擦というものがあります。 摩擦とは、いついかなるときも、速度を0に近づけているものです。
ということでonEnterFrame関数の中で、常に速度に摩擦を適用すればよさそうです。 ここで大事なのは「速度を0に近づける」ということ。 重力のように一定値を引くのではありません。
例えば速度3→1になるのは速度が2減少しています。 では速度が1のときは?同じように2減少させると-1になってしまいます。 これでは反対側に飛んでいくだけなので、摩擦で物体が停止しようとしているときの動作ではありません。
正解は1以下の数値をかけるのです。これで速度は一定して弱まり続けて最終的に0に限りなく近づきます。 フィールド変数として摩擦を表す定数を宣言しましょう。
//摩擦による速度の低下
public var friction:Number = 0.98;
次に、この定数をonEnterFrame関数の最後でX, Y方向の速度にそれぞれ乗じます。
//摩擦の考慮
vx *= friction;
vy *= friction;
これでどうなりますか? 今までよりずっと本物らしくなったでしょう。 摩擦をよりリアルに再現しようとすると、もっと複雑な計算が必要なのですが、手軽さと結果のバランスを考えるとこれくらいが最適な方法だと考えられます。
いよいよバネ!
いや~長かった。 それではバネの動きを実装しましょう。
まず、バネの端っこはもちろんこのボールに繋げるわけですが、もう片方の端も固定しなければなりません。 そこでバネのもう一つの端を画面の真ん中に設定します。 フィールド変数で以下のように宣言しましょう。
//バネの中心点
public var springX:Number = stage.stageWidth / 2;
public var springY:Number = stage.stageHeight / 2;
次に、ボールの真ん中と、バネの中心点が繋がっているように線を描写する関数を作っておきます。
private function drawSpring():void{
//画面の中心に円の描画
graphics.clear();
graphics.beginFill(0);
graphics.drawCircle(springX, springY, 5);
graphics.endFill();
//ボールと画面の中心を線でつなぐ
graphics.lineStyle(2, 0);
graphics.moveTo(ball.x, ball.y);
graphics.lineTo(springX, springY);
}
そしてバネの力をボールの運動に反映させます。 この場合はどのように考えたらいいのでしょうか。
まず、バネは常にボールをある力で引っ張ります。常に引っ張るということはボールの速度に常に影響を与え続けるということです。 ということは重力と同じようにonEnterFrame関数内で、速度に対して何らかの処理をすればいいということですね。 速度に何をしたらいいのでしょうか。ポイントは以下の2つです。
- バネの中心点に向かった力を加える
- バネの中心点から離れれば離れるほど、与える力は強くなる
これらを考慮したコードは以下のようになります。
//バネによる速度の変化
vx += (springX - ball.x) * 0.07;
vy += (springY - ball.y) * 0.07;
(springX, springY)というバネの中心点の座標から、ボールの中心点の座標を引いています。 そしてそれに一定数を掛け合わせ、速度に加えています。
バネの中心点の座標と、ボールの中心点の座標の差が負の値になってもこのコードはうまく動作します。 その理由は皆さん考えてください。 上記のコードを、先ほどの摩擦の計算の前に入れましょう。このバネの力にも摩擦は考慮されるべきだからです。
そして、onEnterFrame関数の一番最後でバネの描写の関数であるdrawSpringを呼び出します。
//バネの描画
drawSpring();
ボールのドラッグ中にもバネは描画して欲しいので、onDragEnterFrame関数の最後にも入れておきましょう。
完成!!
さぁ、これでリアルな動きをするようになりました。 ボールをドラッグしてバネを伸ばし、ボタンを離してみてください。もしくは思いっきり投げつけてみましょう。
最後にここまでで出来たソースコードを掲載します。 次回のネタは今考えているところです。お楽しみに!
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class Animation2 extends Sprite{
//ボール
public var ball:Sprite;
//ボールの半径
public var ballR:Number = 50;
//ボールの横方向の移動量
public var vx:Number = 0;
//ボールの縦方向の移動量
public var vy:Number = 0;
//重力によるy方向の加速度
public var ay:Number = 2;
//バネの中心点
public var springX:Number = stage.stageWidth / 2;
public var springY:Number = stage.stageHeight / 2;
//摩擦による速度の低下
public var friction:Number = 0.98;
//ドラッグ中のマウスのX,Y座標
public var tempX:Number;
public var tempY:Number;
public function Animation2(){
init();
}
public function init():void{
//ボールの作成
ball = new Sprite();
ball.graphics.beginFill(0xff0000); //赤色で塗りつぶし開始
ball.graphics.drawCircle(0, 0, ballR); //座標(0, 0)に半径50の円
ball.graphics.endFill(); //塗りつぶし終了
//ボールの位置指定(画面の真ん中)
ball.x = stage.stageWidth / 2;
ball.y = stage.stageHeight / 2;
//ボールをマウスでドラッグできるようにする
ball.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
ball.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
//ボールを画面に追加
addChild(ball);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onMouseDown(event:MouseEvent):void{
//ボールの運動を停止させる
vx = 0;
vy = 0;
//ドラッグ中はボールの移動処理をしない
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
//ドラッグ中にマウスの座標を保存する処理を登録
addEventListener(Event.ENTER_FRAME, onDragEnterFrame);
ball.startDrag();
}
private function onMouseUp(event:MouseEvent):void {
//ボールの移動処理を再開
addEventListener(Event.ENTER_FRAME, onEnterFrame);
//マウスの座標を保存する処理を除去
removeEventListener(Event.ENTER_FRAME, onDragEnterFrame);
//ボールを放り投げるX速度とY速度を設定
vx = mouseX - tempX;
vy = mouseY - tempY;
ball.stopDrag();
}
private function onDragEnterFrame(event:Event):void{
tempX = mouseX;
tempY = mouseY;
//バネの描画
drawSpring();
}
private function onEnterFrame(event:Event):void{
//速度に従ってボールの移動
ball.x += vx;
ball.y += vy;
//画面のはみ出しチェック
checkWall();
//重力による加速
vy += ay;
//バネによる速度の変化
vx += (springX - ball.x) * 0.07;
vy += (springY - ball.y) * 0.07;
//摩擦の考慮
vx *= friction;
vy *= friction;
//バネの描画
drawSpring();
}
private function checkWall():void {
//横向きのチェック
if(ball.x < ballR){
ball.x = ballR;
vx *= -0.9;
}else if(stage.stageWidth - ballR < ball.x){
ball.x = stage.stageWidth - ballR;
vx *= -0.9;
}
//縦向きのチェック
if(ball.y < ballR){
ball.y = ballR;
vy *= -0.9;
}else if(stage.stageHeight - ballR < ball.y){
ball.y = stage.stageHeight - ballR;
vy *= -0.9;
}
}
private function drawSpring():void{
//画面の中心に円の描画
graphics.clear();
graphics.beginFill(0);
graphics.drawCircle(springX, springY, 5);
graphics.endFill();
//ボールと画面の中心を線でつなぐ
graphics.lineStyle(2, 0);
graphics.moveTo(ball.x, ball.y);
graphics.lineTo(springX, springY);
}
}
}
Flex入門者がアニメーションを基礎から説明してみる #5
- 2008-08-03 (Sun)
- ActionScript , Flash/Flex
さて、今回はバネの運動を紹介しようと思いましたが、やめました。 その事前準備の説明で今回の記事は十分な長さになりそうだからです。
今回紹介するのは今までの復習のようなものです。 基本となるのははボールの落下、反射です。そこに少しだけ機能を加えてボールのドラッグと、ボールを放り投げる処理を入れて今回は終わります。 そこに次回、鮮やかな修正を加えてバネの動きを実現します。
復習 「ボールの落下、反射」
ではまず、新しいActionScriptプロジェクトを作成しましょう。 いつも通り、Flex Builder 3を前提として話していますので、他の環境の方は読み替えてください。 単にActionScriptのクラスファイルを1つ作成するだけです。 プロジェクト名、クラス名は何でもいいです。僕はAnimation2というプロジェクトを作成しました。
そこに以下のように入力しましょう。
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class Animation2 extends Sprite{
//ボール
public var ball:Sprite;
//ボールの半径
public var ballR:Number = 50;
//ボールの横方向の移動量
public var vx:Number = 0;
//ボールの縦方向の移動量
public var vy:Number = 0;
//重力によるy方向の加速度
public var ay:Number = 2;
public function Animation2(){
init();
}
public function init():void{
//ボールの作成
ball = new Sprite();
ball.graphics.beginFill(0xff0000); //赤色で塗りつぶし開始
ball.graphics.drawCircle(0, 0, ballR); //座標(0, 0)に半径50の円
ball.graphics.endFill(); //塗りつぶし終了
//ボールの位置指定(画面の真ん中)
ball.x = stage.stageWidth / 2;
ball.y = stage.stageHeight / 2;
//ボールを画面に追加
addChild(ball);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void{
//速度に従ってボールの移動
ball.x += vx;
ball.y += vy;
//画面のはみ出しチェック
checkWall();
//重力による加速
vy += ay;
}
private function checkWall():void {
//横向きのチェック
if(ball.x < ballR){
ball.x = ballR;
vx *= -0.9;
}else if(stage.stageWidth - ballR < ball.x){
ball.x = stage.stageWidth - ballR;
vx *= -0.9;
}
//縦向きのチェック
if(ball.y < ballR){
ball.y = ballR;
vy *= -0.9;
}else if(stage.stageHeight - ballR < ball.y){
ball.y = stage.stageHeight - ballR;
vy *= -0.9;
}
}
}
}
最初から読み進めてきた方なら、なんなく理解できるでしょう。 今までと少し違うのはボールが枠からはみ出したかどうかのチェックをcheckWallという一つの関数に切り出したくらいです。 あと、重力の加速度を2に設定して、少し前よりも重力を強くしています。
落下はonEnterFrameの最後のvy += ay;がポイントでした。反射はcheckWall関数で枠からはみ出していたときに、速度に-0.9をかけて、反転しつつ少し絶対値を小さくしています。