所谓"正向运动学"通俗点讲就是把几个连接部件的一端固定起来,另一个端可以自由(向前/向外)运动。比如人的行走,单个下肢可以理解为脚连接小腿,小腿连接大腿,大腿连接腰。行走的过程,相当于二条腿相对固定于腰部,大腿运动驱动小腿,小腿又驱动脚,从而带动整个连接系统的一系列运动。
先来一个基本的关节类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
}
真正的行走: