《making things move 》第十章

本文深入探讨了使用高级旋转公式进行对象旋转的优化方法,并详细解析了如何实现倾斜表面的精确反弹,包括碰撞检测与响应的优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《making things move 》第十章
有意思,这章开始变得有难度了! [surprised]

旋转

简单的绕坐标旋转

第3章曾经研究过这个问题,当时还总结了一个公式,现在已经应该看见x就想到cos *半径了

以ball到中心距离为半径,以中心为圆心旋转:

Media 点这里 显示/隐藏 媒体

var vr:Number = .05;
var dx:Number = ball._x - Stage.width / 2;
var dy:Number = ball._y - Stage.height / 2;
var angle:Number = Math.atan2(dy, dx);
var radius:Number = Math.sqrt(dx * dx + dy * dy);

function onEnterFrame():Void
{
 ball._x = Stage.width / 2 + Math.cos(angle) * radius;
 ball._y = Stage.height / 2 + Math.sin(angle) * radius;
 angle += vr;
}

高级绕坐标旋转

这章重点,引进一个全新的公式:

x1 = cos(angle) * x − sin(angle) * y;
y1 = cos(angle) * y + sin(angle) * x;


只需要知道3个量,相对于某个点的坐标x,y,相对与这个点的angle 就可以得出相对于这个点,旋转后的新的坐标

推导过程一定相当复杂,作者也指出,如果你不是开发火箭的话不用尝试理解它,尽管把它背下来,并会使用就行了

使用方法:
var vr:Number = .05;
var cosine:Number = Math.cos(vr);
var sine:Number = Math.sin(vr);
function onEnterFrame():Void {
 var x:Number = ball._x - Stage.width / 2;
 var y:Number = ball._y - Stage.height / 2;
//旋转公式
 var x1:Number = cosine * x - sine * y;
 var y1:Number = cosine * y + sine * x;

 ball._x = Stage.width / 2 + x1;
 ball._y = Stage.height / 2 + y1;
}

Media 点这里 显示/隐藏 媒体

跟上边简单旋转效果是一样的!

你可能会问,既然效果都是一样的,而且新公式这么复杂,为什么要用它?

我们看看下边的例子

旋转多个对象:

如果旋转多个mc,如果用简单公式应该这样做 :
for(var i:Number = 0;i<mcCount;i++)
{
 var mc:MovieClip = this["mc" + i];
 var dx:Number = mc._x − centerX;
 var dy:Number = mc._y − centerY;
 var angle:Number = Math.atan2(dy, dx);
 var dist:Number = Math.sqrt(dx * dx + dy * dy);
 angle += vr;
 mc._x = centerX + Math.cos(angle) * dist;
 mc._y = centerY + Math.sin(angle) * dist;
}

然而高级公式看上去会是这个样子
var cosine:Number = Math.cos(vr);
var sine:Number = Math.sin(vr);
for(var i:Number = 0;i<mcCount;i++)
{
 var mc:MovieClip = this["mc" + i];
 var x:Number = mc._x − centerX;
 var y:Number = mc._y − centerY;
 var x1:Number = cosine * x − sine * y;
 var y1:Number = cosine * y + sine * x;
 mc._x = centerX + x1;
 mc._y = centerY + y1;
}

注意到了简单公式版本中,在for循环中有4次用到了Math的方法 ,意思是每个物体每一帧移动都要执行4个Math方法
而在高级公式版本中,只有了2次Math类的方法,并且是在for循环外边,意思是,无论多少物体多少帧移动都只执行2个Math方法
举个例子,比如有30个mc同时旋转,在简单公式版本中每帧都要执行120个Math方法,而在高级公式中无论多少帧,你都只执行2个Math方法!哪个效率高一些?

注意到高级公式中,vr角参数代表移动的速度,通常情况下,vr角不变,所以你只需要计算一次sin和cos,当然如果需要角的大小有变化的话,你需要每帧都计算一下角的sin和cos。

看一个例子

在这个例子中,用mouse来控制旋转的速度,如果mouse在场景中心则不旋转,鼠标向右,顺时针旋转并且越往右速度越快,鼠标向左,逆时针旋转,并且离中心越远旋转速度越快

Media 点这里 显示/隐藏 媒体

function onEnterFrame():Void {
 //每次算一下角速度
 var angle:Number = (_xmouse - 270) * .001;
// sine, cosine
 var cosine:Number = Math.cos(angle);
 var sine:Number = Math.sin(angle);
 for(var i:Number = 0;i<4;i++)
 {
 var ball:MovieClip = this["ball" + i];
 var x:Number = ball._x - Stage.width / 2;
 var y:Number = ball._y - Stage.height / 2;
//旋转公式
 var x1:Number = cosine * x - sine * y;
 var y1:Number = cosine * y + sine * x;
 ball._x = Stage.width / 2 + x1;
 ball._y = Stage.height / 2 + y1;
 }
}

成角度的反弹

上下左右边反弹我们做的时候很简单,通常有3步

1 .Determine when you have passed a boundary.

2. Reset the object so it is resting directly on the boundary.

3. Reverse its velocity on the axis of the collision.

但如果与小球接触的表面有一定角度,我们怎么做?有点复杂,但我们想到了一个好方法

直接把坐标系统旋转成水平,然后按照我们熟悉的水平反弹方法做,然后再把坐标旋转回来!

看图比较好理解:

attachments/200611/10_144527_1.gif


A ball hitting an angled surface

attachments/200611/10_144535_2.gif


旋转场景

attachments/200611/10_144542_3.gif


反弹后
attachments/200611/10_144549_4.gif


场景又被旋转回来

实际操作:

Media 点这里 显示/隐藏 媒体


画一个mc叫line 注册点在中间, 用旋转工具把他旋转个角度,还有我们的老朋友ball也拉到舞台上

用心理解一下这个代码吧
var vx:Number = 0;
var vy:Number = 0;
var gravity:Number = .5;
var bounce:Number = -0.7;

function onEnterFrame():Void
{
 vy += gravity;
 ball._x += vx;
 ball._y += vy;

 // 得到line的角度 与sin , cos 值
 var angle:Number = line._rotation * Math.PI / 180;
 var cosine:Number = Math.cos(angle);
 var sine:Number = Math.sin(angle);

 // 得到ball相对于line的位置

 var x:Number = ball._x - line._x;
 var y:Number = ball._y - line._y;

 // 旋转这个角度后ball相对于line位置
 /*

 注意这里跟公式不太一样,其实这个是逆时针旋转公式,
 因为line已经顺时针旋转一个角度了,想让line水平一定要逆时针旋转

*/
 var x1:Number = cosine * x + sine * y;
 var y1:Number = cosine * y - sine * x;

 // 旋转速度向量
 var vx1:Number = cosine * vx + sine * vy;
 var vy1:Number = cosine * vy - sine * vx;

 // 执行旋转后的反弹
 if(y1 > -ball._height / 2)
 // y1 > 0 -ball._height/2的缩写
 {
 //碰撞,修改位置,执行反弹
 y1 = -ball._height / 2;
 vy1 *= bounce;
 }

 // 所有的东西再旋转回原样

 x = cosine * x1 - sine * y1;
 y = cosine * y1 + sine * x1;
 vx = cosine * vx1 - sine * vy1;
 vy = cosine * vy1 + sine * vx1;

 // reset actual ball position

 ball._x = line._x + x;
 ball._y = line._y + y;
}

优化代码

上边的代码是为了清晰的说明问题,但有些计算是不需要的,我们省略掉‘

完整代码:
var vx:Number = 0;
var vy:Number = 0;
var gravity:Number = .5;
var bounce:Number = -0.7;

onEnterFrame = function()
{
 vy += gravity;
 ball._x += vx;
 ball._y += vy;

 // get angle, sine and cosine
 var angle:Number = line._rotation * Math.PI / 180;
 var cosine:Number = Math.cos(angle);
 var sine:Number = Math.sin(angle);

 // get position of ball, relative to line
 var x:Number = ball._x - line._x;
 var y:Number = ball._y - line._y;

 // rotate line
 var y1:Number = cosine * y - sine * x;
 
//这里虽然是旋转,但有些值暂时不需要,我们不用算出来,挪到if里边,即真的碰状了再真正计算出来

 if(y1 > -ball._height / 2)
 {
 // rotate line
 var x1:Number = cosine * x + sine * y;

 // rotate velocity
 var vx1:Number = cosine * vx + sine * vy;
 var vy1:Number = cosine * vy - sine * vx;

 // perform bounce with rotated values
 y1 = -ball._height / 2;
 vy1 *= bounce;

 // rotate everything back
 x = cosine * x1 - sine * y1;
 y = cosine * y1 + sine * x1;
 vx = cosine * vx1 - sine * vy1;
 vy = cosine * vy1 + sine * vx1;
 // reset actual ball position
 ball._x = line._x + x;
 ball._y = line._y + y;
 }
}

当一切优化好以后,挪动一下小球,发现了一个bug未处理

Media 点这里 显示/隐藏 媒体


处理边缘问题

问题出在小球碰到线的检测,两种方法 :

1. Hit testing

原理: 小球hittest到line的时候再处理反弹
function onEnterFrame ():Void
{
 vy += gravity;
 ball._x += vx;
 ball._y += vy;

 if(ball.hitTest(line))
 {
 // all the rest of the stuff that was in this function
 }
}

2.Bounds checking

原理: 小球在line左右线头之间时再处理反弹
function onEnterFrame ():Void
{
 vy += gravity;
 ball._x += vx;
 ball._y += vy;

 var bounds:Object = line.getBounds(this);
 
 //getBounds(holder)方法会得到该mc相对于holder的四个边界值

 if(ball._x > bounds.xMin && ball._x < bounds.xMax)
 {
 // all the rest of the stuff that was in this function
 }
}

无论用上面哪种办法,小球都会顺利的掉到下边了,效果:

Media 点这里 显示/隐藏 媒体


动态角度

在onEnterFrame里最上边加一句

line._rotation = (Stage.width / 2 - _xmouse) * .1;

Media 点这里 显示/隐藏 媒体


回想一下,我们是怎么做的,

当小球碰到line并且小球在应该的位置以下,就把小球放到应该的位置,并反弹

有一个问题出现了,如果我们的小球只是从下边经过碰到了线呢?

hitTest 或 bounds check 都返回 true, Flash 就会认为是小球在线上反弹, 将会把线下的小球放到线上边

解决这个问题是当旋转完成,ball._y += vy 后比较 vy1 和 y1, 只有当vy1比较大时候才会发生反弹 .看一下图

attachments/200611/12_003558_1.gif


这段解释值得你思考一段时间了,嘴笨表达不清,请看原文

With the ball on the left, the y velocity is greater than the y position in relation to the line. This means that just before it moved, it had to be above the line. With the ball on the right, the velocity is less than the relative y position. In other words, it’s below the line on this frame, and it was below the line on the last frame. So it’s just moving underneath the frame. The only time you want to do a bounce is when the ball goes from above the line to below it.

以前的代码:
...
// rotate line
 var y1:Number = cosine * y - sine * x;

if(y1 > -ball._height / 2)
 {
 // rotate line
 var x1:Number = cosine * x + sine * y;

 // rotate velocity
 var vx1:Number = cosine * vx + sine * vy;
 var vy1:Number = cosine * vy - sine * vx;
...

比较 vy与y1 现在我们需要提前算出vy
. . .
// rotate line
 var y1:Number = cosine * y - sine * x;
var vy1:Number = cosine * vy - sine * vx;

if(y1 > -ball._height / 2 && y1 < vy1)
 {
 // rotate line
 var x1:Number = cosine * x + sine * y;

 // rotate velocity
 var vx1:Number = cosine * vx + sine * vy;
. . .

如果你跟我一样以前混闪8的,也许会想到ox-thedarknes的一个多层地板跳跃,也用了同样的类似处理手法!

y1 < vy1 按当时ox的写法是这样的 y1-vy1<0

解释就是: 既然这帧碰撞了,如果上一帧ball在line之上,那么就真正的碰撞了!

我们把现在的位置y1减掉 这帧里移动的距离vy1,如果小于0 说明上一帧在0之上!

(ps。这个问题让我怀念起了当年闪8游戏区神一样的人物 ox_thedardness 虽然已经淡出flash界了,但是其思想影响了一带人。。。)

终极例子

例子非常好,将所学的东西做了一个大串联!
Media 点这里 显示/隐藏 媒体

var vx:Number = 0;
var vy:Number = 0;
var gravity:Number = .5;
var bounce:Number = -0.7;
var left:Number = 0;
var right:Number = Stage.width;
var top:Number = 0;
var bottom:Number = Stage.height;
var dragging:Boolean = false;
var oldX:Number;
var oldY:Number;

function onEnterFrame ():Void
{
 if(dragging)
 {
 // dragging时候不停的计算速度
 vx = ball._x - oldX;
 vy = ball._y - oldY;
 oldX = ball._x;
 oldY = ball._y;
 }
 else
 {
 // otherwise normal motion code
 vy += gravity;
 ball._x += vx;
 ball._y += vy;
 for(var i:Number = 0;i<5;i++)
 {
 // moved the line checking stuff to its own method
 // and passed in a reference to each line
 checkLine(this["line" + i]);
 }

 // basic boundary bouncing code
 if(ball._x + ball._width / 2 > right)
 {
 ball._x = right - ball._width / 2;
 vx *= bounce;
 }
 else if(ball._x - ball._width / 2 < left)
 {
 ball._x = left + ball._width / 2;
 vx *= bounce;
 }
 if(ball._y + ball._height / 2 > bottom)
 {
 ball._y = bottom - ball._height / 2;
 vy *= bounce;
 }
 else if(ball._y - ball._height / 2< top)
 {
 ball._y = top + ball._height / 2;
 vy *= bounce;
 }
 }
}

function checkLine(line:MovieClip)
{
 // nothing new here!
 var bounds:Object = line.getBounds(this);
 if(ball._x > bounds.xMin && ball._x < bounds.xMax)
 {
 // get angle, sine and cosine
 var angle:Number = line._rotation * Math.PI / 180;
 var cosine:Number = Math.cos(angle);
 var sine:Number = Math.sin(angle);

 // get position of ball, relative to line
 var x:Number = ball._x - line._x;
 var y:Number = ball._y - line._y;

 // rotate line
 var y1:Number = cosine * y - sine * x;
 var vy1:Number = cosine * vy - sine * vx;

 if(y1 > -ball._height / 2 && y1 < vy1)
 {
 // rotate line
 var x1:Number = cosine * x + sine * y;

 // rotate velocity
 var vx1:Number = cosine * vx + sine * vy;

 // perform bounce with rotated values
 y1 = -ball._height / 2;
 vy1 *= bounce;

 // rotate everything back
 x = cosine * x1 - sine * y1;
 y = cosine * y1 + sine * x1;
 vx = cosine * vx1 - sine * vy1;
 vy = cosine * vy1 + sine * vx1;

 // reset actual ball position
 ball._x = line._x + x;
 ball._y = line._y + y;
 }
 }
}

// basic drag and drop functionality
ball.onPress = function()
{
 oldX = this._x;
 oldY = this._y;
 dragging = true;
 this.startDrag();
}
ball.onRelease = ball.onReleaseOutside = function()
{
 dragging = false;
 this.stopDrag();
}

其实代码还可以优化。。。有兴趣的还可以试下

you might want to have the checkLine function return true or false based on whether or not it has hit a line.
var vx:Number = 0;
var vy:Number = 0;
var gravity:Number = .5;
var bounce:Number = -0.7;
var left:Number = 0;
var right:Number = Stage.width;
var top:Number = 0;
var bottom:Number = Stage.height;
var dragging:Boolean = false;
var oldX:Number;
var oldY:Number;

function onEnterFrame():Void
{
  if(dragging)
  {
    vx = ball._x - oldX;
    vy = ball._y - oldY;
    oldX = ball._x;
    oldY = ball._y;
  }
  else
  {
    vy += gravity;
    ball._x += vx;
    ball._y += vy;
    for(var i:Number = 0;i<5;i++)
    {
      if(checkLine(this["line" + i])){
        trace("碰到了,不需要再for其他的了,下一帧再说吧")
        break
        }
      ;
    }
  
    if(ball._x + ball._width / 2 > right)
    {
      ball._x = right - ball._width / 2;
      vx *= bounce;
    }
    else if(ball._x - ball._width / 2 < left)
    {
      ball._x = left + ball._width / 2;
      vx *= bounce;
    }
    if(ball._y + ball._height / 2 > bottom)
    {
      ball._y = bottom - ball._height / 2;
      vy *= bounce;
    }
    else if(ball._y - ball._height / 2< top)
    {
      ball._y = top + ball._height / 2;
      vy *= bounce;
    }
  }
}

function checkLine(line:MovieClip)
{
  var bounds:Object = line.getBounds(this);
  if(ball._x > bounds.xMin && ball._x < bounds.xMax)
  {
    // get angle, sine and cosine
    var angle:Number = line._rotation * Math.PI / 180;
    var cosine:Number = Math.cos(angle);
    var sine:Number = Math.sin(angle);
    
    // get position of ball, relative to line
    var x:Number = ball._x - line._x;
    var y:Number = ball._y - line._y;
    
    // rotate line
    var y1:Number = cosine * y - sine * x;
    var vy1:Number = cosine * vy - sine * vx;
    
    
    if(y1 > -ball._height / 2 && y1 < vy1)
    {
      // rotate line
      var x1:Number = cosine * x + sine * y;
  
      // rotate velocity
      var vx1:Number = cosine * vx + sine * vy;
      
      // perform bounce with rotated values
      y1 = -ball._height / 2;
      vy1 *= bounce;
    
      // rotate everything back
      x = cosine * x1 - sine * y1;
      y = cosine * y1 + sine * x1;
      vx = cosine * vx1 - sine * vy1;
      vy = cosine * vy1 + sine * vx1;
      
      // reset actual ball position
      ball._x = line._x + x;
      ball._y = line._y + y;
      return true
    }
  }
}

ball.onPress = function()
{
  oldX = this._x;
  oldY = this._y;
  dragging = true;
  this.startDrag();
}
ball.onRelease = ball.onReleaseOutside = function()
{
  dragging = false;
  this.stopDrag();
}

In some cases though, particularly in a dense area of lines, you might want to check all of the lines on every frame. So, I’ll leave it to you to decide if such an optimization is appropriate.

本章重点: 就是那两个公式

顺时针旋转:
引用
 x1 = Math.cos(angle) * x − Math.sin(angle) * y;
y1 = Math.cos(angle) * y + Math.sin(angle) * x; 
逆时针旋转
引用
x1 = Math.cos(angle) * x + Math.sin(angle) * y;
y1 = Math.cos(angle) * y − Math.sin(angle) * x; 
 
内容概要:本文详细探讨了基于阻尼连续可调减振器(CDC)的半主动悬架系统的控制策略。首先建立了CDC减振器的动力学模型,验证了其阻尼特性,并通过实验确认了模型的准确性。接着,搭建了1/4车辆悬架模型,分析了不同阻尼系数对悬架性能的影响。随后,引入了PID、自适应模糊PID和模糊-PID并联三种控制策略,通过仿真比较它们的性能提升效果。研究表明,模糊-PID并联控制能最优地提升悬架综合性能,在平顺性和稳定性间取得最佳平衡。此外,还深入分析了CDC减振器的特性,优化了控制策略,并进行了系统级验证。 适用人群:从事汽车工程、机械工程及相关领域的研究人员和技术人员,尤其是对车辆悬架系统和控制策略感兴趣的读者。 使用场景及目标:①适用于研究和开发基于CDC减振器的半主动悬架系统的工程师;②帮助理解不同控制策略(如PID、模糊PID、模糊-PID并联)在悬架系统中的应用及其性能差异;③为优化车辆行驶舒适性和稳定性提供理论依据和技术支持。 其他说明:本文不仅提供了详细的数学模型和仿真代码,还通过实验数据验证了模型的准确性。对于希望深入了解CDC减振器工作原理及其控制策略的读者来说,本文是一份极具价值的参考资料。同时,文中还介绍了多种控制策略的具体实现方法及其优缺点,为后续的研究和实际应用提供了有益的借鉴。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值