我正在努力使球永远弹跳,始终保持在相同的高度,就像弹跳中没有能量丢失一样。
我不知道为什么,这取决于我做的事,随着时间的流逝,它会跳得更高,或者每次跳时它都会跳得更低。我的期望是,每次弹跳都应始终达到相同的高度,而不是更高或更低。
这是我的代码(代码在pyglet中,但是即使您不知道该库,它也很容易阅读)。在这里,从屏幕中间放下一个正方形,并用箭头标记。我希望立方体在地板上反弹并在返回时精确回到原始高度:
import pyglet
class Polygon(object):
def __init__(self, vertices, color, velocity=0, acceleration=-600):
self.vertices = vertices
self.y_idx = 1
self.num_vertices = int(len(self.vertices) // 2)
self.colors = color * self.num_vertices
self.velocity = velocity
self.acceleration = acceleration
self.bottom_edge = 0
def draw(self):
self.vertex_list = pyglet.graphics.vertex_list(self.num_vertices,
("v2f", self.vertices),
("c3B", self.colors))
self.vertex_list.draw(pyglet.gl.GL_POLYGON)
def move_by_offset(self, offset):
for i in range(1, len(self.vertices), 2):
self.vertices[i] += offset # only modify y values
def bounce(self, dt):
if self.vertices[self.y_idx] < self.bottom_edge:
self.velocity = abs(self.velocity)
return True
return False
def update(self, dt):
# move
self.move_by_offset(self.velocity * dt)
# check if bounce
self.bounce(dt)
# accelerate
self.velocity += self.acceleration * dt
class GameWindow(pyglet.window.Window):
def __init__(self, objects=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.objects = objects
def update(self, dt):
for obj in self.objects:
obj.update(dt)
def on_draw(self):
self.clear()
for obj in self.objects:
obj.draw()
class Game(object):
def __init__(self, w=400, h=400, title="My game", resizable=False):
self.w = w
self.h = h
objects = [
# square
Polygon(vertices=[w/2-20, h/2, w/2-20, h/2+40, w/2+20, h/2+40, w/2+20, h/2],
color=[0, 128, 32], # green
velocity=0,
acceleration=-6000),
# arrow, marks exactly how high the square should bounce
Polygon(vertices=[w/2, h/2, w/2+40, h/2+20, w/2+30, h/2, w/2+40, h/2-20],
color=[255, 255, 0], # yellow
velocity=0,
acceleration=0)
]
self.window = GameWindow(objects, self.w, self.h, title, resizable)
def update(self, dt):
self.window.update(dt)
if __name__ == "__main__":
game = Game(resizable=False)
pyglet.clock.schedule_interval(game.update, 1/120)
pyglet.app.run()
我尝试了不同的更新顺序,例如在弹跳后改变速度之前根据加速度修改速度,或者在弹跳后甚至根本不加速(这似乎是最好的),但是弹跳仍然不准确,并且会不断改变高度:< / p>
def update2(self, dt):
# move
self.move_by_offset(self.velocity * dt)
# accelerate
self.velocity += self.acceleration * dt
# check if bounce
self.bounce(dt)
def update3(self, dt):
# move
self.move_by_offset(self.velocity * dt)
# check if bounce
bounced = self.bounce(dt)
if not bounced:
# accelerate (only if no bounce happened)
self.velocity += self.acceleration * dt
我什至尝试了更复杂的方法:创建2个dt,一个在反弹之前一个,一个在反弹之后,然后进行2个加速更新,但这也不起作用。
你们能帮我吗?在如此简单的情况下,如何编程游戏物理学?
答案 0 :(得分:1)
数字积分很难!由于您可以轻松轻松地精确求解一维弹道方程,因此请执行以下操作:计算
y1=y0+v0*dt+g*dt*dt/2
v1=v0+g*dt
在恒定加速度的平凡情况下,这是velocity Verlet method。如果为y1<0
,则可以求解二次方程式以找出何时弹起并从该点重新开始积分(速度取为负)。
如果要在数值上仍然准确的同时合并更复杂的物理,请考虑速度变量的居中。错开可以得到更好的准确性-在定义位置的点之间的中间时间点定义它会得到类似的leapfrog method。
保守力的一种非常不同的方法是定义球的总能量,并根据其高度根据其必须具有的动力学来移动球。即使这样,您也必须在dt*dt
中包含以上校正,以避免在最大高度附近出现数值问题。
答案 1 :(得分:0)
之所以方程式不起作用,是因为您的更新函数缺少由加速度引起的位置变化量。这应该起作用。
import pyglet
import math
class Polygon(object):
def __init__(self, vertices, color, velocity=0, acceleration=-600):
self.vertices = vertices
self.y_idx = 1
self.num_vertices = int(len(self.vertices) // 2)
self.colors = color * self.num_vertices
self.velocity = velocity
self.acceleration = acceleration
self.bottom_edge = 0
def draw(self):
self.vertex_list = pyglet.graphics.vertex_list(self.num_vertices,
("v2f", self.vertices),
("c3B", self.colors))
self.vertex_list.draw(pyglet.gl.GL_POLYGON)
def move_by_offset(self, offset):
for i in range(1, len(self.vertices), 2):
self.vertices[i] += offset # only modify y values
def bounce(self, dt):
if self.vertices[self.y_idx] < self.bottom_edge:
self.velocity = abs(self.velocity)
dropped_height = (self.velocity**2) / (-self.acceleration * 2)
drop_time = math.sqrt(2 * dropped_height / -self.acceleration)
print("dropped height:", dropped_height)
print("drop time:", drop_time)
return True
return False
def update(self, dt):
# move
move_by_velocity = self.velocity * dt
move_by_acceleration = 1/2 * -self.acceleration * dt * dt
self.move_by_offset(move_by_velocity + move_by_acceleration)
# check if bounce
self.bounce(dt)
# accelerate
self.velocity += self.acceleration * dt
class GameWindow(pyglet.window.Window):
def __init__(self, objects=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.objects = objects
def update(self, dt):
for obj in self.objects:
obj.update(dt)
def on_draw(self):
self.clear()
for obj in self.objects:
obj.draw()
class Game(object):
def __init__(self, w=400, h=400, title="My game", resizable=False):
self.w = w
self.h = h
objects = [
# square
Polygon(vertices=[w/2-20, h/2, w/2-20, h/2+40, w/2+20, h/2+40, w/2+20, h/2],
color=[0, 128, 32], # green
velocity=0,
acceleration=-6000),
# arrow, marks exactly how high the square should bounce
Polygon(vertices=[w/2, h/2, w/2+40, h/2+20, w/2+30, h/2, w/2+40, h/2-20],
color=[255, 255, 0], # yellow
velocity=0,
acceleration=0)
]
self.window = GameWindow(objects, self.w, self.h, title, resizable)
def update(self, dt):
self.window.update(dt)
if __name__ == "__main__":
game = Game(resizable=False)
pyglet.clock.schedule_interval(game.update, 1/120)
pyglet.app.run()
答案 2 :(得分:0)
我一直在思考,我相信即使程序是完全准确的,物体通常也不会达到相同的高度。由于弹跳会导致对象在上升和下降过程中采取不同的位置,因此在游戏循环中仅显示该对象真实运动的某些帧,因此该对象在顶部可能会显得较低。这些框架可能与球的最高位置不相关。 抱歉,这太晚了,但是这是我在js中的尝试。您可以在任何网页的控制台中运行它。请注意,总能量几乎保持不变,并且如果没有我的草率编码,可能会更准确。
document.body.innerHTML = '<canvas id="myCanvas" width="375" height="555"></canvas> <p id ="text"></p>'
var x = 200;
var y = 105.3;
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.canvas.width = window.innerWidth
ctx.canvas.height = window.innerHeight-120
var g = .2
var ballRadius = 3;
var xsp = 0;
var ysp = 0;
var iysp = 0;
var p = 0;
var ip = 0;
var iy = 0;
var high = 200;
var a = 0
var b = 0
var fix = 0
var iter = 0
var fpms = 10
var gi = 0
var gii = 0
var brek = 100000000
var dt = 1
var smallt = 0
var mass = 1
var total = 0
function totale() {
total = parseFloat(((mass*g*(500-y))+(.5*mass*(ysp*ysp))).toFixed(8))
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(0,0,200,105.3-ballRadius);
ctx.fillStyle = "#0085DD";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(0,500,200,100);
ctx.fillStyle = "#0085DD";
ctx.fill();
ctx.closePath();
}
function draw() {
if (iter==brek) {
clearInterval(mainLoop)
return;
}
iysp = ysp
iy = y
ysp = parseFloat((ysp + g*dt).toFixed(8))
y = parseFloat((y + ((ysp+iysp)/2)*dt).toFixed(8))
totale()
if (y > 500) {
ysp = iysp
y = iy
ysp = Math.sqrt(iysp*iysp+(2*g*(500-y)))
b=ysp
smallt = 1-((ysp-iysp)/g)
ysp = ysp*-1+((g*dt)*smallt)
y = parseFloat((500 + ((ysp+b*-1)/2)*dt*smallt).toFixed(8))
}
if (y < iy) {
high = y
}
iter ++
document.getElementById("text").innerHTML = '__y speed= '+ysp+'<br>'+'__highest y value= '+high+'<br>'+'__y pos'+(y)+'<br>'+'__smallt= '+(smallt)+'<br>'+'__iter= '+iter+'__total e= '+total
ctx.clearRect(0,0,canvas.width,canvas.height)
drawBall();
}
mainLoop = setInterval(draw,fpms)