所谓"正向运动学"通俗点讲就是把几个连接部件的一端固定起来,另一个端可以自由(向前/向外)运动。比如人的行走,单个下肢可以理解为脚连接小腿,小腿连接大腿,大腿连接腰。行走的过程,相当于二条腿相对固定于腰部,大腿运动驱动小腿,小腿又驱动脚,从而带动整个连接系统的一系列运动。
先来一个基本的关节类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; } } }
真正的行走:
package { import flash.display.Sprite; import flash.display.StageScaleMode; import flash.display.StageAlign; import flash.display.Stage; import flash.events.Event; import flash.geom.Point; public class RealWalk extends Sprite { private var segment0:Segment;//大腿1 private var segment1:Segment;//小腿1 private var segment2:Segment;//大腿2 private var segment3:Segment;//小腿2 //各控制滑块 private var speedSlider:SimpleSlider; private var thighRangeSlider:SimpleSlider; private var thighBaseSlider:SimpleSlider; private var calfRangeSlider:SimpleSlider; private var calfOffsetSlider:SimpleSlider; private var gravitySlider:SimpleSlider; private var cycle:Number=0; private var vx:Number=0; private var vy:Number=0; public function RealWalk() { init(); } private function init():void { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; segment0=new Segment(50,15); addChild(segment0); segment0.x=200; segment0.y=100; segment1=new Segment(50,10); addChild(segment1); segment1.x=segment0.getPin().x; segment1.y=segment0.getPin().y; segment2=new Segment(50,15); addChild(segment2); segment2.x=200; segment2.y=100; segment3=new Segment(50,10); addChild(segment3); segment3.x=segment2.getPin().x; segment3.y=segment2.getPin().y; speedSlider=new SimpleSlider(0,0.3,0.12); 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; gravitySlider=new SimpleSlider(0,1,0.2); addChild(gravitySlider); gravitySlider.x=110; gravitySlider.y=10; addEventListener(Event.ENTER_FRAME, EnterFrameHandler); } private function EnterFrameHandler(event:Event):void { doVelocity(); walk(segment0, segment1, cycle); walk(segment2, segment3, cycle + Math.PI); cycle+=speedSlider.value; checkFloor(segment1); checkFloor(segment3); checkWalls(); } //行走姿态的处理 private function walk(segA:Segment, segB:Segment,cyc:Number):void { var foot:Point=segB.getPin(); 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; segB.vx=segB.getPin().x-foot.x; segB.vy=segB.getPin().y-foot.y; } //下肢的速度处理 private function doVelocity():void { vy+=gravitySlider.value; //因为小腿是跟着大腿的,所以只要处理大腿的速度即可 segment0.x+=vx; segment0.y+=vy; segment2.x+=vx; segment2.y+=vy; } private function checkFloor(seg:Segment):void { var yMax:Number=seg.getBounds(this).bottom; //如果最下面的小腿超出了舞台下边界 if (yMax>stage.stageHeight) { var dy:Number=yMax-stage.stageHeight; //将所有的关节(大腿和小腿)全部上移,以防止两条腿超出舞台下边界 segment0.y-=dy; segment1.y-=dy; segment2.y-=dy; segment3.y-=dy; //速度反弹 vx-=seg.vx; vy-=seg.vy; } } //屏幕环绕 private function checkWalls():void { var w:Number=stage.stageWidth+200; if (segment0.x>stage.stageWidth+100) { segment0.x-=w; segment1.x-=w; segment2.x-=w; segment3.x-=w; } else if (segment0.x < 100*-1) { segment0.x+=w; segment1.x+=w; segment2.x+=w; segment3.x+=w; } } } }