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