现在我们要用到一个新判定“判定-三角”。
注意,判定块一定要是填充,而且不能包括边线。因为边线在getBounds()的时候会稍微超出界限。
在判定-三角的第一帧加上一句:
isSlope = true那么如果 mc.isSlope 就表明该mc是斜面。通过判断 mc._rotation可以判断出mc的正反,通过 mc.getBounds()可以获得上下左右边缘,进而算出某处斜面的上表面坐标。

附件:
您所在的用户组无法下载或查看附件首先统一一下坐标系。这里讨论的所有坐标都是相对_root的,比如我们用 mc.getBounds( _root ) 把mc范围映射到_root下。用_root坐标系演算很方便,因为它很容易与局部坐标系转换,例如用 localToGlobal 和 globalToLocal。
首先,我们要获得地板上表面的坐标。不只是在地板上走动时需要它,在判定落地时也要比较自己位置和地面置。
1 mc.getTop(x) 获得地板x 处上表面的坐标y。
如图,挪动鼠标可以看到效果。
这样在地板上走动的时候,我们可以得到自己的位置。另外,以后落地判定时也要比较地面位置和自己位置。
function getTop(x){
var bounds = this.getBounds(_root); //获得自己相对于_root的范围
if( this.isSlope ) { //如果自己是斜面的话
var percent = (x - bounds.xMin)/(bounds.xMax-bounds.xMin); //主角在斜面上的位置,0~1之间
if(this._rotation == 180) percent = 1- percent; //如果斜面mc反向的话,那么比率反过来
var y = bounds.yMax - percent*(bounds.yMax-bounds.yMin); //得到主角所处位置地板的上表面坐标
if(y < bounds.yMin) y = bounds.yMin; //如果越界,那么就是到了另一个平面
else if( y > bounds.yMax) y = bounds.yMax;
return y;
}
else return bounds.yMin; //否则是平面,直接返回上表面坐标
} 另外封装一个函数,检测x是否在地板范围之外。
2 mc.isOutOfBound(x)
如果x在地板范围之外,返回true。
function isOutOfBound(x){
var bounds = this.getBounds(_root);
return (x < bounds.xMin- 0.5 || x > bounds.xMax+ 0.5); //稍微宽一点,避免误差
} 假如移动超出了脚下 landMC的边界,那么就移到和该 landMC邻接的mc上。
如何判定邻接呢?
我们抽象出两个方法获得地板表面的端点,这样只要两个mc的相对端点相同,他们就是邻接的。
3 mc.getLeftTop () 获得地板mc的上表面左端点 {x, y}。mc.getRightTop() 获得地板mc的上表面右端点 {x, y}//获得图块最左边顶点{x,y}
function getLeftTop(){
var bounds = this.getBounds(_root);
if(this.isSlope && this._rotation == 0) return {x:bounds.xMin, y: bounds.yMax};
else return {x:bounds.xMin, y: bounds.yMin};
}
//获得图块最右边顶点{x,y}
function getRightTop(){
var bounds = this.getBounds(_root);
if(this.isSlope && this._rotation == 180) return {x:bounds.xMax, y: bounds.yMax};
else return {x:bounds.xMax, y: bounds.yMin};
}最后是获得地板的斜率,虽然我这个例子用不到,但是这个参数还是非常有用的——比如可以设定斜率越大走得越慢,斜率过大还会往下滑等等。
3 mc.getSlope () 获得地板mc的斜率 slope。
//返回斜率
function getSlope(){
if(!this.isSlope) return 0; //平面斜率是0
var bounds = this.getBounds(_root);
var slope = (bounds.yMax - bounds.yMin)/ (bounds.xMax - bounds.xMin);
if(this._rotation == 0) slope = -slope; //注意坐标系与常用的y轴方向相反,所以向右下斜时斜率为正
return slope;
} 假如你的判定mc放在 _root.背景判定中,那么可以在主时间轴上添加如下代码:
//给每个判定mc一些方法,坐标系都是基于_root
for(var i in 背景判定) {
if (!(背景判定 instanceof MovieClip)) return;
背景判定.getTop = getTop;
背景判定.isOutOfBound = isOutOfBound;
背景判定.getLeftTop = getLeftTop;
背景判定.getRightTop = getRightTop;
背景判定.getSlope = getSlope;
}现在,我们已经抽象出来了“地板”这个层次。这样就不再讨论斜面、平面的细节,而只需要分析“地板”了。
这里是包含了上述代码的fla文件。主场景有一个MC“主角”,一个MC“背景判定”,背景判定里面包含4块连到一起的地板。请下载它(自制一个也行),我们将在这个fla层面之上继续我们的讨论。
如果想自制,他看起来是这个样子的。里面的代码除了_quality = "low"以外全部列在上面了:

附件:
您所在的用户组无法下载或查看附件
附件:
您所在的用户组无法下载或查看附件
附件:
您所在的用户组无法下载或查看附件
附件:
您所在的用户组无法下载或查看附件把主角移动到如图位置,然后把上面红色mc命名为 "mc1",把其他mc的名字删除(注意,由于手误有几个mc名字重复,这样会引起错误,所以一定要删除其他mc的名字)

附件:
您所在的用户组无法下载或查看附件首先实现在当前地板上移动。在主时间轴上加上如下代码:
主角.landMC = 背景判定.mc1; //一开始就设定主角.landMC 为mc1
主角.speed = 8;
function onEnterFrame(){
主角.xinc = 0;
if(Key.isDown(Key.LEFT)) 主角.xinc = -主角.speed;
if(Key.isDown(Key.RIGHT)) 主角.xinc = 主角.speed;
主角._x += 主角.xinc;
主角._y = 主角.landMC.getTop( 主角._x);
} 现在主角在mc1上移动正常。但是他无法判断超出边界
然后我们定义一个
moveFromLand( oldLand, x) 函数。当离开oldLand,走到 x 位置的时候,该函数搜索一个与oldLand相邻的地板。他看起来是像这样的:
function moveFromLand( oldLand, x ){
if(x < oldLand.getBounds(_root).xMin) var isLeft = true; //判断是左边越界还是右边越界
else isLeft = false;
if (isLeft) var p = oldLand.getLeftTop(); //如果超出左边界,那么获得左顶点坐标
else var p = oldLand.getRightTop(); //否则获得右顶点坐标
//搜索一个相邻的mc
for(var i in 背景判定){
var landNow = 背景判定;
if( landNow == oldLand || ! (landNow instanceof MovieClip)) continue;
if(!landNow.isOutOfBound(x)){
if(isLeft) var newP = landNow.getRightTop(); //获得和原来mc相对的顶点
else var newP = landNow.getLeftTop();
//和原顶点坐标相差3以内则相邻
if(Math.abs(newP.x - p.x) + Math.abs(newP.y - p.y) < 3) return landNow;
}
}
return null; //没有找到
}enterframe中插入黑体字部分:
主角._x += 主角.xinc;
if( 主角.landMC.isOutOfBound( 主角._x ) ){ //如果主角超出了移动范围
主角.landMC = moveFromLand( 主角.landMC, 主角._x );
}
主角._y = 主角.landMC.getTop( 主角._x );运行看看,现在主角会从一个地板移动到另外一个了。注意一旦移出所有地板范围,landMC会变成 null,这样代码就会失效。
更正式一些,如果有多个地板,他会选择一个最低的。比如同时有一个上坡和一个平路,他会优先选择平路。这个判断很重要,不然他会随机选取一个(其实是根据你摆放mc的先后次序)。这段代码比较复杂,具体实现参考下面的源代码。

附件:
您所在的用户组无法下载或查看附件下面我们实现跳跃。跳跃的关键在于:判断落到哪一个地板上。
什么叫“落到一块地板上”?就是上一帧在地板表面之上,而这一帧在地板表面之下。
我们用
getLand( prevX, prevY, x, y) 函数获得当前落地地板。 它需要传入上一帧的坐标 prevX, prevY 和当前坐标 x, y 作为参数。
function getLand( prevX, prevY, x, y){
for(var i in 背景判定){
var landNow = 背景判定;
if(!landNow.isOutOfBound(x)) {
if( landNow.getTop(x) <= y && landNow.getTop(prevX) >= prevY ) return landNow;
}
}
return null;
}恩,函数相当简短,这归功于前面良好的抽象。不过,同样的,如果有多个mc可选,那么应当选取表面最高的那个。该函数的完整实现参考下面给出的原文件。

附件:
您所在的用户组无法下载或查看附件主角的控制代码如下,这样我们的主角就可以跳跃了:
G = 1;
主角.speed = 8;
主角.jumpPower = 15;
主角.landMC = null;
主角.yinc = 0;
function onEnterFrame(){
主角.xinc = 0;
if(Key.isDown(Key.LEFT)) 主角.xinc = -主角.speed;
if(Key.isDown(Key.RIGHT)) 主角.xinc = 主角.speed;
主角._x += 主角.xinc; //执行移动
if(主角.landMC){//如果主角在地上,那么检测越界、获得新的landMC
if(主角.landMC.isOutOfBound(主角._x)) 主角.landMC = moveFromLand(主角.landMC, 主角._x);
}else{//如果不在地上的话,那么判断是否落地
主角.landMC = getLand( 主角._x-主角.xinc, 主角._y, 主角._x, 主角._y + 主角.yinc);
}
//如果经过判断发现主角在地面上的话,那么_y直接放在地面上
if(主角.landMC) {
主角._y = 主角.landMC.getTop(主角._x);
主角.yinc = 0;
}else {//如果现在主角不在地板上的话,执行重力部分,注意这两行的次序
主角._y += 主角.yinc;
主角.yinc += G;
}
//跳跃
if(主角.landMC && Key.isDown(Key.SPACE)){
主角.yinc = -主角.jumpPower; //给主角一个初速度
主角.landMC = null;
}
}事实上斜面的实现本身已经贴完了,而且已经发现那个传说中的bug的出现条件了, debuging。。。。。
如图,我得到了矩形的范围: bounds{xMin, yMin, xMax, yMax} ,已知斜面向上,主角P坐标x,求P 斜面上坐标y 。自己算算看~~
另外,正向的mc _rotation = 0,反向的_rotation = 180。斜面是向上还是向下就要通过这个判断
最后的润色:
1避免舍入误差mc的取值范围并不是连续的。他常常会对参数进行舍入,比如把10.3166666667舍入为10.32。这在某些情况下是致命的。前文所述的bug就是来自于此。在两次传入的参数之间存在微妙的差别,以至于造成误判。
执行移动的顺序应当如下所示。每次都把状态写入_x _y中,用prevX和prevY保存上一次的_x和_y,而不是用 _x和 xinc来推断。
//执行移动
主角.prevX = 主角._x;
主角._x += 主角.xinc;
主角.prevY = 主角._y;
主角._y += 主角.yinc;
if(主角.landMC){//如果主角在地上,那么检测越界、获得新的landMC
if(主角.landMC.isOutOfBound(主角._x)) 主角.landMC = moveFromLand(主角.landMC, 主角._x);
}else{//如果不在地上的话,那么判断是否落地
主角.landMC = getLand( 主角.prevX, 主角.prevY, 主角._x, 主角._y);
}注意重力的yinc增加部分要相应删除。
2 通过按键控制跳跃高度的更好方法
相对于我的上一贴,我发现了一种更好的通过按键时间控制跳跃高度的方法。
上一贴中,当按键松开时,我分情况讨论速度的变化。比较啰嗦而且效果这并不自然。现在不但代码精简了,而且效果更加平滑:
if( spacePressPrev && !Key.isDown(Key.SPACE) ){ //SPACE松开时
if(主角.yinc<0) 主角.yinc /=3;
}3 下+跳跃 跳到下一层,以及岩石
下+跳跃实现很简单。让 y 增加一点点,这样主角就落到了表面之下;同时置landMC = null。至于岩石,另外作两类判定mc,在他们的时间轴上加上 isStone = true。在岩石上下 + 跳跃是无效的。
if(Key.isDown(Key.DOWN)) { //如果按了下
if(!主角.landMC.isStone) {
主角._y += 1; //如果脚下不是硬方块,那么跳到下一层
主角.landMC = null;
}
}else{
主角.yinc = -主角.jumpPower; //如果没有按下,给主角一个初速度
主角.landMC = null;
} 完整的实现在这里:
空格跳跃,按键时间控制跳跃高度。一般地面 下+ 跳跃可以落到下一层;
如果任何人在这个版本中发现bug,请通知我(比如莫名其妙穿过地板掉下去之类)

附件:
您所在的用户组无法下载或查看附件通过_rotation来判断MC的正反的解释一般来说,我们将一个mc左右反向是将 _xscale 取反 但是你可以试试从库中拖出一个mc,用自由变换工具编辑他,然后trace他的属性。
你会发现无论面向左右他的 _xscale 都是正数。反倒是 _rotation 会变化:正向为0 反向为 180。
斜面教程结束---------------------------------------------------------------