Flash/Flex学习笔记(46):正向运动学
所谓"正向运动学"通俗点讲就是把几个连接部件的一端固定起来,另一个端可以自由(向前/向外)运动。比如人的行走,单个下肢可以理解为脚连接小腿,小腿连接大腿,大腿连接腰。行走的过程,相当于二条腿相对固定于腰部,大腿运动驱动小腿,小腿又驱动脚,从而带动整个连接系统的一系列运动。
先来一个基本的关节类Segment:(就是一个圆角矩形+二个小圆圈)
01 package {
02 import flash.display.Sprite;
03 import flash.geom.Point;
04
05 public class Segment extends Sprite {
06
07 private var color:uint;
08 private var segmentWidth:Number;
09 private var segmentHeight:Number;
10 public var vx:Number=0;
11 public var vy:Number=0;
12
13 public function Segment(segmentWidth:Number,segmentHeight:Number,color:uint=0xffffff) {
14 this.segmentWidth=segmentWidth;
15 this.segmentHeight=segmentHeight;
16 this.color=color;
17 init();
18 }
19
20 public function init():void {
21
22 // 绘制关节
23 graphics.lineStyle(0);
24 graphics.beginFill(color);
25 graphics.drawRoundRect(- segmentHeight/2,- segmentHeight/2,segmentWidth+segmentHeight,segmentHeight,segmentHeight,segmentHeight);
26 graphics.endFill();
27
28 // 绘制两个“枢轴”
29 graphics.drawCircle(0,0,2);
30 graphics.drawCircle(segmentWidth,0,2);
31 }
32
33 //获得自由端的坐标
34 public function getPin():Point {
35 var angle:Number=rotation*Math.PI/180;
36 var xPos:Number=x+Math.cos(angle)*segmentWidth;
37 var yPos:Number=y+Math.sin(angle)*segmentWidth;
38 return new Point(xPos,yPos);
39 }
40 }
41 }

为了动态控制关节的旋转,再来一个简单的滑块控件类:(下列代码看起来吃力的同学,建议先看Flash/Flex学习笔记(36):自己动手实现一个滑块控件(JimmySilder))
001 package {
002 import flash.display.Sprite;
003 import flash.events.MouseEvent;
004 import flash.geom.Rectangle;
005 import flash.events.Event;
006
007 public class SimpleSlider extends Sprite {
008
009 private var _width:Number=6;
010 private var _height:Number=100;
011 private var _value:Number;
012 private var _max:Number=100;
013 private var _min:Number=0;
014 private var _handle:Sprite;
015 private var _back:Sprite;
016 private var _backWidth:Number=0;
017 private var _handleHeight:Number=20;
018 private var _backColor:uint=0xcccccc;
019 private var _backBorderColor:uint=0x999999;
020 private var _handleColor:uint=0x000000;
021 private var _handleBorderColor:uint=0xcccccc;
022 private var _handleRadius:Number=2;
023 private var _backRadius:Number=2;
024
025 public function SimpleSlider(min:Number = 0, max:Number = 100, value:Number = 100 ) {
026 _min=min;
027 _max=max;
028 _value=Math.min(Math.max(value,min),max);
029 init();
030 }
031
032 private function init():void {
033 _back = new Sprite () ;
034 addChild(_back);
035 _handle = new Sprite () ;
036 _handle.buttonMode=true;
037 addChild(_handle);
038 _handle.addEventListener( MouseEvent.MOUSE_DOWN , MouseDownHandler );
039 draw();
040 updatePosition();
041 }
042
043 private function draw():void {
044 drawBack();
045 drawHandle();
046 }
047
048 private function drawBack():void {
049 _back.graphics.clear();
050 _back.graphics.beginFill( _backColor );
051 _back.graphics.lineStyle( 0, _backBorderColor );
052 _back.graphics.drawRoundRect( 0, 0, _backWidth , _height , _backRadius , _backRadius );
053 _back.graphics.endFill();
054 _back.x=_width/2-_backWidth/2;
055 }
056
057 private function drawHandle():void {
058 _handle.graphics.clear();
059 _handle.graphics.beginFill( _handleColor );
060 _handle.graphics.lineStyle( 0, _handleBorderColor );
061 _handle.graphics.drawRect( 0, 0, _width , _handleHeight );
062 _handle.graphics.endFill();
063 }
064
065 private function updatePosition():void {
066 var handleRange:Number=_height-_handleHeight;
067 var valueRange:Number=_max-_min;
068 _handle.y = handleRange - ( _value - _min ) / valueRange * handleRange ;
069 }
070
071 private function updateValue():void {
072 var handleRange:Number=_height-_handleHeight;
073 var valueRange:Number=_max-_min;
074 _value = ( handleRange - _handle.y ) / handleRange * valueRange + _min ;
075 dispatchEvent( new Event ( Event.CHANGE ));
076 }
077
078 private function MouseUpHandler( e:MouseEvent ):void {
079 stage.removeEventListener( MouseEvent.MOUSE_MOVE , MouseMoveHandler );
080 stage.removeEventListener( MouseEvent.MOUSE_UP , MouseUpHandler );
081 _handle.stopDrag();
082 }
083
084 private function MouseDownHandler( e:MouseEvent ):void {
085 stage.addEventListener( MouseEvent.MOUSE_MOVE , MouseMoveHandler );
086 stage.addEventListener( MouseEvent.MOUSE_UP , MouseUpHandler );
087 _handle.startDrag( false , new Rectangle ( 0, 0, 0, _height - _handleHeight ));
088 }
089
090 private function MouseMoveHandler( e:MouseEvent ):void {
091 updateValue();
092 }
093
094 public function invalidate():void {
095 draw();
096 }
097
098 public function move( x:Number , y:Number ):void {
099 this.x=x;
100 this.y=y;
101 }
102
103 public function setSize( w:Number , h:Number ):void {
104 _width=w;
105 _height=h;
106 draw();
107 }
108
109 public function set backBorderColor( n:uint ):void {
110 _backBorderColor=n;
111 draw();
112 }
113
114 public function get backBorderColor():uint {
115 return _backBorderColor;
116 }
117
118 public function set backColor( n:uint ):void {
119 _backColor=n;
120 draw();
121 }
122
123 public function get backColor():uint {
124 return _backColor;
125 }
126
127 public function set backRadius( n:Number ):void {
128 _backRadius=n;
129 }
130
131 public function get backRadius():Number {
132 return _backRadius;
133 }
134
135 public function set backWidth( n:Number ):void {
136 _backWidth=n;
137 draw();
138 }
139
140 public function get backWidth():Number {
141 return _backWidth;
142 }
143
144 public function set handleBorderColor( n:uint ):void {
145 _handleBorderColor=n;
146 draw();
147 }
148
149 public function get handleBorderColor():uint {
150 return _handleBorderColor;
151 }
152
153 public function set handleColor( n:uint ):void {
154 _handleColor=n;
155 draw();
156 }
157
158 public function get handleColor():uint {
159 return _handleColor;
160 }
161 public function set handleRadius( n:Number ):void {
162 _handleRadius=n;
163 draw();
164 }
165 public function get handleRadius():Number {
166 return _handleRadius;
167 }
168 public function set handleHeight( n:Number ):void {
169 _handleHeight=n;
170 draw();
171 updatePosition();
172 }
173 public function get handleHeight():Number {
174 return _handleHeight;
175 }
176 override public function set height( n:Number ):void {
177 _height=n;
178 draw();
179 }
180 override public function get height():Number {
181 return _height;
182 }
183 public function set max( n:Number ):void {
184 _max=n;
185 updatePosition();
186 }
187 public function get max():Number {
188 return _max;
189 }
190 public function set min( n:Number ):void {
191 _min=n;
192 updatePosition();
193 }
194 public function get min():Number {
195 return _min;
196 }
197 public function set value( n:Number ):void {
198 _value=n;
199 _value=Math.min(_max,Math.max(_value,_min));
200 updatePosition();
201 }
202 public function get value():Number {
203 return _value;
204 }
205 override public function set width( n:Number ):void {
206 _width=n;
207 draw();
208 }
209 override public function get width():Number {
210 return _width;
211 }
212 }
213 }
基本测试:
01 var segment:Segment=new Segment(100,20);
02 addChild(segment);
03 segment.x=50;
04 segment.y=120;
05
06 var slider:SimpleSlider=new SimpleSlider(-90,90,0);
07 addChild(slider);
08 slider.x=200;
09 slider.y=70;
10
11 slider.addEventListener(Event.CHANGE,onChange);
12
13 function onChange(event:Event):void {
14 segment.rotation=slider.value;
15 }
双关节运动测试:
01 package {
02 import flash.display.Sprite;
03 import flash.events.Event;
04 public class TwoSegments extends Sprite {
05 private var slider0:SimpleSlider;
06 private var slider1:SimpleSlider;
07 private var segment0:Segment;
08 private var segment1:Segment;
09
10 public function TwoSegments() {
11 init();
12 }
13 private function init():void {
14 segment0=new Segment(100,20);
15 addChild(segment0);
16 segment0.x=50;
17 segment0.y=150;
18 segment1=new Segment(100,20);
19 addChild(segment1);
20
21 //关键:segment1的固定端连接到segment0的自由端
22 segment1.x=segment0.getPin().x;
23 segment1.y=segment0.getPin().y;
24
25 slider0=new SimpleSlider(-90,90,0);
26 addChild(slider0);
27 slider0.x=320;
28 slider0.y=90;
29 slider0.addEventListener(Event.CHANGE,onChange);
30 slider1=new SimpleSlider(-90,90,0);
31 addChild(slider1);
32 slider1.x=340;
33 slider1.y=90;
34 slider1.addEventListener(Event.CHANGE,onChange);
35 }
36 private function onChange(event:Event):void {
37 segment0.rotation=slider0.value;
38 segment1.rotation=slider1.value;
39 segment1.x=segment0.getPin().x;
40 segment1.y=segment0.getPin().y;
41 }
42 }
43 }
如果把segment0与segment1分别看做人的胳膊与手臂,上面这个示例显然有二个地方不自然:
1.没有人的(前)手臂向下做-90度的弯曲(除非脱臼)
2.人的上肢整体向上抬时,手臂会随着胳膊一起绕肩关节向上旋转,而不应该一直固定于某个角度
修正的方法很简单,onChange改成下面这样:
1 private function onChange(event:Event):void {
2 segment0.rotation=slider0.value;
3 segment1.rotation=slider1.value + segment0.rotation;//注意这行
4 segment1.x=segment0.getPin().x;
5 segment1.y=segment0.getPin().y;
6 }
同时限制一下slider1的角度范围,改成下面这样:
1 slider1=new SimpleSlider(-160,0,0);
单腿原地“踢”模拟
01 package {
02 import flash.display.Sprite;
03 import flash.events.Event;
04 public class Walking1 extends Sprite {
05 private var segment0:Segment;
06 private var segment1:Segment;
07 private var cycle:Number=0;
08 private var offset:Number = -Math.PI/2;//小腿的运动看上去应该滞后于大腿,所以需要加入反向偏移量
09
10 public function Walking1() {
11 init();
12 trace(Math.PI/180);
13 trace(0.05*180/Math.PI);
14 }
15 private function init():void {
16 segment0=new Segment(100,20);
17 addChild(segment0);
18 segment0.x=200;
19 segment0.y=200;
20 segment1=new Segment(100,20);
21 addChild(segment1);
22 segment1.x=segment0.getPin().x;
23 segment1.y=segment0.getPin().y;
24 addEventListener(Event.ENTER_FRAME,onEnterFrame);
25 }
26 private function onEnterFrame(event:Event):void {
27 cycle+=.05;
28 var angle0:Number=Math.sin(cycle)*45 + 90;//-45到45整体加上90度以后,就变成45到135,即:大腿垂直方向左右摆动45度
29 var angle1:Number = Math.sin(cycle + offset) * 45 + 45;//即:小腿相对大腿末端做0-90度的正向旋转。建议大家尝试修改一下这里的+45值的大小,看看效果有什么不同
30 segment0.rotation=angle0;
31 segment1.rotation=segment0.rotation+angle1;
32 segment1.x=segment0.getPin().x;
33 segment1.y=segment0.getPin().y;
34 }
35 }
36 }
双腿原地行走:
01 package {
02 import flash.display.Sprite;
03 import flash.events.Event;
04 public class Walking4 extends Sprite {
05 private var segment0:Segment;
06 private var segment1:Segment;
07 private var segment2:Segment;
08 private var segment3:Segment;
09
10 private var cycle:Number=0;
11 private var offset:Number=- Math.PI/2;//小腿的运动看上去应该滞后于大腿,所以需要加入反向偏移量
12
13 public function Walking4() {
14 init();
15 }
16 private function init():void {
17
18 segment0=new Segment(100,35);//第一条大腿
19 addChild(segment0);
20 segment0.x=200;
21 segment0.y=50;
22
23 segment1=new Segment(100,20);
24 addChild(segment1);
25 segment1.x=segment0.getPin().x;//第一条小腿连接到第一条大腿
26 segment1.y=segment0.getPin().y;
27
28 segment2=new Segment(100,35);//第二条大腿
29 segment2.x = segment0.x;//第二条大腿与第一条大腿坐标相同,视觉效果上看,就象都固定在腰部
30 segment2.y = segment0.y;
31 addChild(segment2);
32
33
34 segment3=new Segment(100,20);
35 addChild(segment3);
36 segment3.x=segment2.getPin().x;//第二条小腿连接到第二条大腿
37 segment3.y=segment2.getPin().y;
38
39 addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
40 }
41
42 private function EnterFrameHandler(event:Event):void {
43 walk(segment0, segment1, cycle);
44 walk(segment2, segment3, cycle + Math.PI);//注意这里的:+Math.PI,如果不加这个,二条腿的频率/角度完全相同,将重叠在一起,加上180度以后,正好反相过来,一条腿在前,另一条腿在后
45 cycle += .05;
46 }
47
48 //把"走"的动作封装起来
49 private function walk(segA:Segment, segB:Segment, cyc:Number):void {
50 var angleA:Number=Math.sin(cyc)*45+90;
51 var angleB:Number=Math.sin(cyc+offset)*45+45;
52 segA.rotation=angleA;
53 segB.rotation=segA.rotation+angleB;
54 segB.x=segA.getPin().x;
55 segB.y=segA.getPin().y;
56 }
57 }
58 }
加入滑块控制条后的样子:
01 package {
02 import flash.display.Sprite;
03 import flash.events.Event;
04 public class Walking5 extends Sprite {
05 private var segment0:Segment;
06 private var segment1:Segment;
07 private var segment2:Segment;
08 private var segment3:Segment;
09 private var speedSlider:SimpleSlider;
10 private var thighRangeSlider:SimpleSlider;
11 private var thighBaseSlider:SimpleSlider;
12 private var calfRangeSlider:SimpleSlider;
13 private var calfOffsetSlider:SimpleSlider;
14 private var cycle:Number=0;
15
16 public function Walking5() {
17 init();
18 }
19
20 private function init():void {
21 segment0=new Segment(100,30);
22 addChild(segment0);
23 segment0.x=200;
24 segment0.y=100;
25 segment1=new Segment(100,20);
26 addChild(segment1);
27 segment1.x=segment0.getPin().x;
28 segment1.y=segment0.getPin().y;
29 segment2=new Segment(100,30);
30 addChild(segment2);
31 segment2.x=200;
32 segment2.y=100;
33 segment3=new Segment(100,20);
34 addChild(segment3);
35 segment3.x=segment2.getPin().x;
36 segment3.y=segment2.getPin().y;
37
38 //控制速度的滑块
39 speedSlider=new SimpleSlider(0,0.5,0.11);
40 addChild(speedSlider);
41 speedSlider.x=10;
42 speedSlider.y=10;
43
44 //控制大腿能分开的最大角度
45 thighRangeSlider=new SimpleSlider(0,90,45);
46 addChild(thighRangeSlider);
47 thighRangeSlider.x=30;
48 thighRangeSlider.y=10;
49
50 //大腿旋转的偏移量
51 thighBaseSlider=new SimpleSlider(0,180,90);
52 addChild(thighBaseSlider);
53 thighBaseSlider.x=50;
54 thighBaseSlider.y=10;
55
56 //小腿旋转的偏移量
57 calfRangeSlider=new SimpleSlider(0,90,45);
58 addChild(calfRangeSlider);
59 calfRangeSlider.x=70;
60 calfRangeSlider.y=10;
61
62 //小腿相对大腿滞后的偏移量
63 calfOffsetSlider=new SimpleSlider(-3.14,3.14,-1.57);
64 addChild(calfOffsetSlider);
65 calfOffsetSlider.x=90;
66 calfOffsetSlider.y=10;
67
68 addEventListener(Event.ENTER_FRAME, EnterFrameHandler);
69 }
70
71 private function EnterFrameHandler(e:Event):void {
72 walk(segment0, segment1, cycle);
73 walk(segment2, segment3, cycle + Math.PI);
74 cycle+=speedSlider.value;
75 }
76 private function walk(segA:Segment, segB:Segment,cyc:Number):void {
77 var angleA:Number = Math.sin(cyc) * thighRangeSlider.value + thighBaseSlider.value;
78 var angleB:Number = Math.sin(cyc +calfOffsetSlider.value) * calfRangeSlider.value + calfRangeSlider.value;
79 segA.rotation=angleA;
80 segB.rotation=segA.rotation+angleB;
81 segB.x=segA.getPin().x;
82 segB.y=segA.getPin().y;
83 }
84 }
85 }
真正的行走:
浙公网安备 33010602011771号