ラグドール物理
「詳解 ActionScript 3.0アニメーション」の「6.2ベレ法」では「ラグドール物理」という用語が出てきます。
http://en.wikipedia.org/wiki/Ragdoll_physics
にあるとおり、ボロ布でできた縫いぐるみのように、力の入っていないグダグダのキャラクタの運動をシミュレーションする方法です。どんな時に使うかというと、キャラクタが気絶したり死んだときが代表的です。
6章のVerletPoint.asとVerletStick.asを利用して、ラグドール物理の例を作ってみました。
メインドキュメントは RagDoll.as です。また、点と点をバネでつなぐ VerletSpring も定義します。図で灰色で描画されているのがバネです。
RagDoll.swf
(↑クリックすると再生)
マウスをクリックすると、キャラクタが跳ね上がります。上下の矢印キーで、バネが緊くなったり、緩んだりします。
最初、バネは最強になっていてVerletStickと変わりません。[↓]キーを押すと、どんどんグニャグニャになって潰れてしまいます。
一度グニャグニャになったキャラクタを、マウスとキーの操作で再び立たせることができたら、エライです。
(ソースは続きに掲載。)
RagDoll.as
package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; import flash.events.KeyboardEvent; import flash.ui.Keyboard; import flash.geom.Rectangle; public class RagDoll extends Sprite { private var _points:Array; private var _sticks:Array; private var _springs:Array; private var _stageRect:Rectangle; private var _curK:Number; public function RagDoll() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; _stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); _points = new Array(); _sticks = new Array(); _springs = new Array(); _curK = 1; var neck:VerletPoint = makePoint(125, 150); var lhead:VerletPoint = makePoint(100, 100); var rhead:VerletPoint = makePoint(150, 100); var lhand:VerletPoint = makePoint(20, 150); var rhand:VerletPoint = makePoint(230, 150); var lhip:VerletPoint = makePoint(80, 250); var rhip:VerletPoint = makePoint(170, 250); var lfoot:VerletPoint = makePoint(70, 350); var rfoot:VerletPoint = makePoint(180, 350); makeStick(lhead, rhead); makeStick(lhead, neck); makeStick(rhead, neck); makeStick(lhand, neck); makeStick(rhand, neck); makeStick(neck, lhip); makeStick(neck, rhip); makeStick(lhip, rhip); makeStick(lhip, lfoot); makeStick(rhip, rfoot); makeSpring(lfoot, rhip); makeSpring(rfoot, lhip); makeSpring(lhead, lhip); makeSpring(rhead, rhip); makeSpring(lhead, lhand); makeSpring(rhead, rhand); makeSpring(lhand, rhand); makeSpring(lfoot, rfoot); makeSpring(lhand, lhip); makeSpring(rhand, rhip); addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); } private function onEnterFrame(event:Event):void { updatePoints(); for (var i:int = 0; i < 1; i++) { constrainPoints(); updateSticks(); updateSprings(); } graphics.clear(); renderSprings(); renderPoints(); renderSticks(); } private function makePoint(xpos:Number, ypos:Number):VerletPoint { var point:VerletPoint = new VerletPoint(xpos, ypos); _points.push(point); return point; } private function makeStick(pointA:VerletPoint, pointB:VerletPoint, length:Number = -1):VerletStick { var stick:VerletStick = new VerletStick(pointA, pointB, length); _sticks.push(stick); return stick; } private function makeSpring(pointA:VerletPoint, pointB:VerletPoint, length:Number = -1):VerletSpring { var spring:VerletSpring = new VerletSpring(pointA, pointB, _curK, length); _springs.push(spring); return spring; } private function updatePoints():void { for (var i:int = 0; i < _points.length; i++) { var point:VerletPoint = _points[i] as VerletPoint; point.y += .5; point.update(); } } private function constrainPoints():void { for (var i:int = 0; i < _points.length; i++) { var point:VerletPoint = _points[i] as VerletPoint; point.constrain(_stageRect); } } private function renderPoints():void { for (var i:int = 0; i < _points.length; i++) { var point:VerletPoint = _points[i] as VerletPoint; point.render(graphics); } } private function updateSticks():void { for (var i:int = 0; i < _sticks.length; i++) { var stick:VerletStick = _sticks[i] as VerletStick; stick.update(); } } private function renderSticks():void { for (var i:int = 0; i < _sticks.length; i++) { var stick:VerletStick = _sticks[i] as VerletStick; stick.render(graphics); } } private function updateSprings():void { for (var i:int = 0; i < _springs.length; i++) { var spring:VerletSpring = _springs[i] as VerletSpring; spring.update(); } } private function renderSprings():void { for (var i:int = 0; i < _springs.length; i++) { var spring:VerletSpring = _springs[i] as VerletSpring; spring.render(graphics); } } public function onMouseDown(event:MouseEvent):void { _points[0].y -= 100; } private function onKeyUp(event:KeyboardEvent):void { if (event.keyCode == Keyboard.UP && _curK <= 0.5) { _curK *= 2; } else if (event.keyCode == Keyboard.DOWN && _curK > 1.0 / 256) { _curK /= 2; } for (var i:int = 0; i < _springs.length; i++) { var spring:VerletSpring = _springs[i] as VerletSpring; spring.k = _curK; } } } }
package { import flash.display.Graphics; public class VerletSpring { private var _pointA:VerletPoint; private var _pointB:VerletPoint; private var _k:Number; private var _length:Number; public function VerletSpring(pointA:VerletPoint, pointB:VerletPoint, k:Number, length:Number = -1) { _pointA = pointA; _pointB = pointB; _k = k; if (length == -1) { var dx:Number = _pointA.x - _pointB.x; var dy:Number = _pointA.y - _pointB.y; _length = Math.sqrt(dx * dx + dy * dy); } else { _length = length; } } public function update():void { var dx:Number = _pointB.x - _pointA.x; var dy:Number = _pointB.y - _pointA.y; var dist:Number = Math.sqrt(dx * dx + dy * dy); var diff:Number = _length - dist; var offsetX:Number = (diff * dx / dist) / 2; var offsetY:Number = (diff * dy / dist) / 2; var k:Number = 20; _pointA.x -= offsetX * _k; _pointA.y -= offsetY * _k; _pointB.x += offsetX * _k; _pointB.y += offsetY * _k; } public function render(g:Graphics):void { g.lineStyle(0, 0xcccccc); g.moveTo(_pointA.x, _pointA.y); g.lineTo(_pointB.x, _pointB.y); } public function set k(v:Number):void { _k = v; } } }