Flash/Flex学习笔记(40):正向运动学
所谓"正向运动学"通俗点讲就是把几个连接部件的一端固定起来,另一个端可以自由(向前/向外)运动。比如人的行走,单个下肢可以理解为脚连接小腿,小腿连接大腿,大腿连接腰。行走的过程,相当于二条腿相对固定于腰部,大腿运动驱动小腿,小腿又驱动脚,从而带动整个连接系统的一系列运动。
先来一个基本的关节类Segment:(就是一个圆角矩形+二个小圆圈)
package {
?import flash.display.Sprite;
import flash.geom.Point;
public class Segment extends Sprite {
?private var color:uint;
?private var segmentWidth:Number;
private var segmentHeight:Number;
?public var vx:Number=0;
?public var vy:Number=0;
public function Segment(segmentWidth:Number,segmentHeight:Number,color:uint=0xffffff) {
?this.segmentWidth=segmentWidth;
this.segmentHeight=segmentHeight;
?this.color=color;
?init();
?}
?public function init():void {
?// 绘制关节?
?graphics.lineStyle(0);
graphics.beginFill(color);
?graphics.drawRoundRect(- segmentHeight/2,- segmentHeight/2,segmentWidth+segmentHeight,segmentHeight,segmentHeight,segmentHeight);
graphics.endFill();
?// 绘制两个“枢轴”?
?graphics.drawCircle(0,0,2);??graphics.drawCircle(segmentWidth,0,2);
?}
//获得自由端的坐标??public function getPin():Point {
?var angle:Number=rotation*Math.PI/180;
?var xPos:Number=x+Math.cos(angle)*segmentWidth;
?var yPos:Number=y+Math.sin(angle)*segmentWidth;
return new Point(xPos,yPos);
?}
?}
?}

为了动态控制关节的旋转,再来一个简单的滑块控件类:(下列代码看起来吃力的同学,建议先看Flash/Flex学习笔记(36):自己动手实现一个滑块控件(JimmySilder))
package {
?import flash.display.Sprite;
?import flash.events.MouseEvent;
?import flash.geom.Rectangle;
?import flash.events.Event;
?public class SimpleSlider extends Sprite {
?private var _width:Number=6;
?private var _height:Number=100;
private var _value:Number;
private var _max:Number=100;
?private var _min:Number=0;
private var _handle:Sprite;
private var _back:Sprite;
private var _backWidth:Number=0;
?private var _handleHeight:Number=20;
?private var _backColor:uint=0xcccccc;
?private var _backBorderColor:uint=0x999999;
private var _handleColor:uint=0x000000;
private var _handleBorderColor:uint=0xcccccc;
private var _handleRadius:Number=2;
private var _backRadius:Number=2;
public function SimpleSlider(min:Number = 0, max:Number = 100, value:Number = 100 ) {
_min=min;
_max=max;
?value=Math.min(Math.max(value,min),max);
init();
}
?
private function init():void {
_back = new Sprite () ;
?addChild(_back);
?_handle = new Sprite () ;
?_handle.buttonMode=true;
addChild(_handle);
?_handle.addEventListener( MouseEvent.MOUSE_DOWN , MouseDownHandler );
draw();
updatePosition();
}
private function draw():void {
?drawBack();
?drawHandle();
?}
?private function drawBack():void {
?_back.graphics.clear();
?_back.graphics.beginFill( _backColor );
?_back.graphics.lineStyle( 0, _backBorderColor );
?_back.graphics.drawRoundRect( 0, 0, _backWidth , _height , _backRadius , _backRadius );
?_back.graphics.endFill();
?_back.x=_width/2-_backWidth/2;
?}
private function drawHandle():void {
?_handle.graphics.clear();
?_handle.graphics.beginFill( _handleColor );
?_handle.graphics.lineStyle( 0, _handleBorderColor );
?_handle.graphics.drawRect( 0, 0, _width , _handleHeight );
_handle.graphics.endFill();
?}
?private function updatePosition():void {
?var handleRange:Number=_height-_handleHeight;
?var valueRange:Number=_max-_min;
?_handle.y = handleRange - ( _value - _min ) / valueRange * handleRange ;
?}
?private function updateValue():void {
?var handleRange:Number=_height-_handleHeight;
?var valueRange:Number=_max-_min;
?_value = ( handleRange - _handle.y ) / handleRange * valueRange + _min ;
dispatchEvent( new Event ( Event.CHANGE ));
?}
private function MouseUpHandler( e:MouseEvent ):void {
?stage.removeEventListener( MouseEvent.MOUSE_MOVE , MouseMoveHandler );
?stage.removeEventListener( MouseEvent.MOUSE_UP , MouseUpHandler );
?_handle.stopDrag();
?}
?private function MouseDownHandler( e:MouseEvent ):void {
?stage.addEventListener( MouseEvent.MOUSE_MOVE , MouseMoveHandler );
?stage.addEventListener( MouseEvent.MOUSE_UP , MouseUpHandler );
_handle.startDrag( false , new Rectangle ( 0, 0, 0, _height - _handleHeight ));
?}
?
private function MouseMoveHandler( e:MouseEvent ):void {
?updateValue();
?}
public function invalidate():void {
?draw();
?}
?public function move( x:Number , y:Number ):void {
?this.x=x;
?this.y=y;
?}
?public function setSize( w:Number , h:Number ):void {
?_width=w;
?_height=h;
?draw();
}
?public function set backBorderColor( n:uint ):void {
?_backBorderColor=n;
?draw();
?}
?
public function get backBorderColor():uint {
?return _backBorderColor;
?}
?public function set backColor( n:uint ):void {
_backColor=n;
?draw();
?}
?public function get backColor():uint {
return _backColor;
?}
public function set backRadius( n:Number ):void {
?_backRadius=n;
?}
?public function get backRadius():Number {
?return _backRadius;
?}
?public function set backWidth( n:Number ):void {
?_backWidth=n;
?draw();
?}
?public function get backWidth():Number {
?return _backWidth;
?}
?public function set handleBorderColor( n:uint ):void {
?_handleBorderColor=n;
?draw();
?}
?public function get handleBorderColor():uint {
?return _handleBorderColor;
?}
?public function set handleColor( n:uint ):void {
_handleColor=n;
?draw();
?}
public function get handleColor():uint {
return _handleColor;
?}
?public function set handleRadius( n:Number ):void {
?_handleRadius=n;
?draw();
?}
public function get handleRadius():Number {
?return _handleRadius;
?}
?public function set handleHeight( n:Number ):void {
?_handleHeight=n;
draw();
?updatePosition();
?}
?public function get handleHeight():Number {
?return _handleHeight;
?}
?override public function set height( n:Number ):void {
?_height=n;
?draw();
?}
?override public function get height():Number {
?return _height;
?}
?public function set max( n:Number ):void {
?_max=n;
?updatePosition();
}
?public function get max():Number {
return _max;
?}
?public function set min( n:Number ):void {
_min=n;
?updatePosition();
}
?public function get min():Number {
?return _min;
?}
?public function set value( n:Number ):void {
value=n;
?value=Math.min(_max,Math.max(_value,_min));
?updatePosition();
?}
?public function get value():Number {
?return _value;
?}
?override public function set width( n:Number ):void {
_width=n;
?draw();
?}
?override public function get width():Number {
?return _width;
}
?}
?}
基本测试:
var segment:Segment=new Segment(100,20);
?addChild(segment);
segment.x=50;
?segment.y=120;
?var slider:SimpleSlider=new SimpleSlider(-90,90,0);
?addChild(slider);
?slider.x=200;
?slider.y=70;
slider.addEventListener(Event.CHANGE,onChange);
?function onChange(event:Event):void {
?segment.rotation=slider.value;
?}
双关节运动测试:
package {
?import flash.display.Sprite;
?import flash.events.Event;
?public class TwoSegments extends Sprite {
private var slider0:SimpleSlider;???????
?private var slider1:SimpleSlider;
?private var segment0:Segment;
?private var segment1:Segment;
?public function TwoSegments() {
?init();
?}
?private function init():void {
?segment0=new Segment(100,20);
?addChild(segment0);
?segment0.x=50;
?segment0.y=150;
?segment1=new Segment(100,20);
addChild(segment1);
?//关键:segment1的固定端连接到segment0的自由端
segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?slider0=new SimpleSlider(-90,90,0);
addChild(slider0);
?slider0.x=320;
?slider0.y=90;
?slider0.addEventListener(Event.CHANGE,onChange);
?slider1=new SimpleSlider(-90,90,0);
?addChild(slider1);
slider1.x=340;
?slider1.y=90;
?slider1.addEventListener(Event.CHANGE,onChange);
?}
?private function onChange(event:Event):void {
?segment0.rotation=slider0.value;
?segment1.rotation=slider1.value;
?segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?}
?}
}
如果把segment0与segment1分别看做人的胳膊与手臂,上面这个示例显然有二个地方不自然:
1.没有人的(前)手臂向下做-90度的弯曲(除非脱臼)
2.人的上肢整体向上抬时,手臂会随着胳膊一起绕肩关节向上旋转,而不应该一直固定于某个角度
修正的方法很简单,onChange改成下面这样:
?private function onChange(event:Event):void {
?segment0.rotation=slider0.value;
?segment1.rotation=slider1.value + segment0.rotation;//注意这行
?segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?}
同时限制一下slider1的角度范围,改成下面这样:
slider1=new SimpleSlider(-160,0,0);
单腿原地“踢”模拟
?package {
?import flash.display.Sprite;
import flash.events.Event;
?public class Walking1 extends Sprite {
?private var segment0:Segment;
?private var segment1:Segment;
?private var cycle:Number=0;
?private var offset:Number = -Math.PI/2;//小腿的运动看上去应该滞后于大腿,所以需要加入反向偏移量
?public function Walking1() {
?init();
?trace(Math.PI/180);
?trace(0.05*180/Math.PI);
?}
?private function init():void {
?segment0=new Segment(100,20);
?addChild(segment0);
segment0.x=200;
segment0.y=200;
?segment1=new Segment(100,20);
?addChild(segment1);
segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?addEventListener(Event.ENTER_FRAME,onEnterFrame);
?}
private function onEnterFrame(event:Event):void {
?cycle+=.05;
?var angle0:Number=Math.sin(cycle)*45 + 90;//-45到45整体加上90度以后,就变成45到135,即:大腿垂直方向左右摆动45度
?var angle1:Number = Math.sin(cycle + offset) * 45 + 45;//即:小腿相对大腿末端做0-90度的正向旋转。建议大家尝试修改一下这里的+45值的大小,看看效果有什么不同
?segment0.rotation=angle0;
?segment1.rotation=segment0.rotation+angle1;
?segment1.x=segment0.getPin().x;
segment1.y=segment0.getPin().y;
?}
?}
?}
双腿原地行走:
package {
?import flash.display.Sprite;
?import flash.events.Event;
public class Walking4 extends Sprite {
private var segment0:Segment;
?private var segment1:Segment;
?private var segment2:Segment;
?private var segment3:Segment;
?private var cycle:Number=0;
?private var offset:Number=- Math.PI/2;//小腿的运动看上去应该滞后于大腿,所以需要加入反向偏移量
?public function Walking4() {
?init();?????????
?}
?private function init():void {
?segment0=new Segment(100,35);//第一条大腿
addChild(segment0);
?segment0.x=200;
?segment0.y=50;
?segment1=new Segment(100,20);???????????
?addChild(segment1);
?segment1.x=segment0.getPin().x;//第一条小腿连接到第一条大腿
?segment1.y=segment0.getPin().y;
?segment2=new Segment(100,35);//第二条大腿
?segment2.x = segment0.x;//第二条大腿与第一条大腿坐标相同,视觉效果上看,就象都固定在腰部
?segment2.y = segment0.y;
addChild(segment2);
segment3=new Segment(100,20);???????????
?addChild(segment3);
?segment3.x=segment2.getPin().x;//第二条小腿连接到第二条大腿
?segment3.y=segment2.getPin().y;
?addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
?}
?private function EnterFrameHandler(event:Event):void {
?walk(segment0, segment1, cycle);?
?walk(segment2, segment3, cycle + Math.PI);//注意这里的:+Math.PI,如果不加这个,二条腿的频率/角度完全相同,将重叠在一起,加上180度以后,正好反相过来,一条腿在前,另一条腿在后
cycle += .05;
?}
//把"走"的动作封装起来
?private function walk(segA:Segment, segB:Segment, cyc:Number):void {
?var angleA:Number=Math.sin(cyc)*45+90;
?var angleB:Number=Math.sin(cyc+offset)*45+45;
?segA.rotation=angleA;
?segB.rotation=segA.rotation+angleB;
?segB.x=segA.getPin().x;
?segB.y=segA.getPin().y;
?}
?}
?}
加入滑块控制条后的样子:
?package {
?import flash.display.Sprite;
?import flash.events.Event;
?public class Walking5 extends Sprite {
private var segment0:Segment;
?private var segment1:Segment;
private var segment2:Segment;
?private var segment3:Segment;
?private var speedSlider:SimpleSlider;
private var thighRangeSlider:SimpleSlider;
?private var thighBaseSlider:SimpleSlider;
?private var calfRangeSlider:SimpleSlider;
private var calfOffsetSlider:SimpleSlider;
private var cycle:Number=0;
public function Walking5() {
?init();
?}
?private function init():void {
?segment0=new Segment(100,30);
addChild(segment0);
segment0.x=200;
?segment0.y=100;
?segment1=new Segment(100,20);
addChild(segment1);
?segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?segment2=new Segment(100,30);
?addChild(segment2);
?segment2.x=200;
segment2.y=100;
?segment3=new Segment(100,20);
?addChild(segment3);
?segment3.x=segment2.getPin().x;
segment3.y=segment2.getPin().y;
?//控制速度的滑块
?speedSlider=new SimpleSlider(0,0.5,0.11);
?addChild(speedSlider);
?speedSlider.x=10;
?speedSlider.y=10;
?//控制大腿能分开的最大角度
?thighRangeSlider=new SimpleSlider(0,90,45);
?addChild(thighRangeSlider);
?thighRangeSlider.x=30;
thighRangeSlider.y=10;
?//大腿旋转的偏移量
?thighBaseSlider=new SimpleSlider(0,180,90);
addChild(thighBaseSlider);??????????
?thighBaseSlider.x=50;
?thighBaseSlider.y=10;
?//小腿旋转的偏移量
?calfRangeSlider=new SimpleSlider(0,90,45);
?addChild(calfRangeSlider);??????????
?calfRangeSlider.x=70;
?calfRangeSlider.y=10;
//小腿相对大腿滞后的偏移量
?calfOffsetSlider=new SimpleSlider(-3.14,3.14,-1.57);
?addChild(calfOffsetSlider);?????????
calfOffsetSlider.x=90;
?calfOffsetSlider.y=10;
?addEventListener(Event.ENTER_FRAME, EnterFrameHandler);
?}
private function EnterFrameHandler(e:Event):void {
walk(segment0, segment1, cycle);
?walk(segment2, segment3, cycle + Math.PI);
cycle+=speedSlider.value;
?}
?private function walk(segA:Segment, segB:Segment,cyc:Number):void {
?var angleA:Number = Math.sin(cyc) * thighRangeSlider.value + thighBaseSlider.value;
?var angleB:Number = Math.sin(cyc +calfOffsetSlider.value) * calfRangeSlider.value + calfRangeSlider.value;
?segA.rotation=angleA;
?segB.rotation=segA.rotation+angleB;
?segB.x=segA.getPin().x;
segB.y=segA.getPin().y;
?}
?}
?}
真正的行走: