我正在使用Javascript(HTML5 Canvas)开发游戏 . 我实现了一个简单的算法,它允许一个物体跟随另一个混合了基本物理的物体(一个力矢量在正确的方向上驱动物体,速度叠加动量,但是由一个恒定的拖曳力减慢) . 此刻,我按照鼠标(x,y)坐标将其设置为矩形 . 这是代码:
// rectangle x, y position
var x = 400; // starting x position
var y = 250; // starting y position
var FPS = 60; // frames per second of the screen
// physics variables:
var velX = 0; // initial velocity at 0 (not moving)
var velY = 0; // not moving
var drag = 0.92; // drag force reduces velocity by 8% per frame
var force = 0.35; // overall force applied to move the rectangle
var angle = 0; // angle in which to move
// called every frame (at 60 frames per second):
function update(){
// calculate distance between mouse and rectangle
var dx = mouseX - x;
var dy = mouseY - y;
// calculate angle between mouse and rectangle
var angle = Math.atan(dy/dx);
if(dx < 0)
angle += Math.PI;
else if(dy < 0)
angle += 2*Math.PI;
// calculate the force (on or off, depending on user input)
var curForce;
if(keys[32]) // SPACE bar
curForce = force; // if pressed, use 0.35 as force
else
curForce = 0; // otherwise, force is 0
// increment velocty by the force, and scaled by drag for x and y
velX += curForce * Math.cos(angle);
velX *= drag;
velY += curForce * Math.sin(angle);
velY *= drag;
// update x and y by their velocities
x += velX;
y += velY;
而且每秒60帧的效果很好 . 现在,棘手的部分:我的问题是,如果我将其更改为不同的帧速率(例如,30 FPS),我如何修改力和拖动值以保持运动不变?
也就是说,现在我的矩形(其位置由x和y变量决定)以大约每秒4个像素的最大速度移动,并在大约1秒内加速到其最大速度 . 但是,如果我改变帧速率,它移动得更慢(例如30 FPS加速到每帧仅2个像素) .
那么,我怎样才能创建一个以FPS(每秒帧数)作为输入的方程式,并吐出正确的“拖动”和“强制”值,它们的实时行为方式相同?
我知道这是一个很重要的问题,但也许有游戏设计经验或编程物理知识的人可以提供帮助 . 感谢你付出的努力 .
jsFiddle:http://jsfiddle.net/BadDB
4 回答
我会介绍一个实际的时间测量 . 然后,您应该将公式重新设计为实际经过时间和所需最大速度的函数 . 使用实际经过时间的好处是,即使在(因为负载或你有什么)不在编程的FPS下运行的系统上,方程也能很好地工作 .
另外 - 你应该使用
Math.atan2(dy, dx)
而不是Math.atan(dy/dx)
. (想想dx == 0
时会发生什么 . )理想情况下,您应该将时间范围视为自上一帧开始经过的时间,然后根据实际增量计算比例因子,并将其用于基于时间的计算 .
考虑到理想情况下,您的游戏以60fps运行 . 同样要考虑的是,对于每一个游戏框架,你的游戏很少会以1000毫秒/ 60英尺的速度运行 .
所以你的理想是
1000/60
. 你的实际将是current_timestamp - previous_timestamp
. 您的时间范围是actual/ideal
.现在,您只需使用比例来转换时间敏感值 .
任何时间函数都可以使用"ideal-value-per-frame"(
magnitude = 8; current_magnitude = magnitude * scale; vec.x *= current_magnitude; vec.y *= current_magnitude; vec.z *= current_magnitude;
) .你只需要小心了解何时繁殖,何时不要 . 如果计算是基于时间的,则预乘 . 如果不是,那就不要了 .
如果你的游戏以15fps运行,你的时间尺度将是4倍,对吧?这不应该影响汽车的扭矩 - 发动机正在建造的被压抑的能量 . 它受到的影响是在这段精确的时间内发生了多少加速度(线性或其他) .
如果汽车应该加速0.5m / s ^ 2,或者你要决定的任何东西,那么你只看到加速度的一部分(加到当前速度上),这适用于第二个特定部分你现在在 . 然后在下一次更新中,汽车应该以你设定的速度行驶,乘以你计算的时间尺度,作为当前帧与前一帧之间的差值 .
旋转应以相同的方式计算 .
这允许您将不一致的帧速率与实际操作分开,因为您始终基于百分比而不是硬编号(涉及时间)进行操作 .
这也允许像子弹时间这样容易愚蠢地完成 . 将添加的项目符号时间因子添加到 everything ,瞄准除外 . 或者如果你想做一个忍者反射 - 无论如何,对玩家应用不同的时间尺度,而不是敌人 .
对于冻结时间,您有两种选择:
将所有内容设置为0并忘记之前的值(让所有内容从0恢复,恢复时)
将所有内容设置为0但保留之前所有值的"frame" . 之后忘记0,但是将下一个时间段视为暂停发生后仅经过1帧 .
当你在谈论如何处理
drag
时 - 你自己说:drag
在模拟中是constant
. 它没有谈论,在那段时间内阻力对物体的影响与它在任何其他时间范围内对任何物体的影响是一致的 .力量加倍并保持拖动不变 .
EDIT:
数学:
可以通过两个参数完全描述运动:初始加速度和终端速度 . 如果这两个看起来正确,那么动作将是正确的 .
对于初始加速度,拖动(这种拖动)无关紧要 . 由于
force
是一个加速度,我们所要做的就是在一秒钟内添加它,以获得一秒钟的加速度:force30 * 30 = force60 * 60
force30 = force60 * 60/30 = 2.0 * force60 = 2.0(0.35)= 0.7
当力和阻力 balancer 时,会发生终端速度 .
Vterm * drag = force
drag = force / Vterm
我们想要扩展Vterm,但我们也在缩放
force
,因此缩放项取消;drag
无需更改 .力的积分是速度,当你按照 time 进行积分时,速度的积分就是位置 . 如果你的时间步长等于两帧之间的时间,那么如果你有一个碰撞后探测器,你的子弹物理将会起作用 .
两帧之间的时间与FPS成反比 .
FPS =总帧数/总时间
时间步长=(1.0 /(浮点)FPS秒)* K. K是一个常数,使你的时间步长足够小,所以你的物理足够稳定 .