【问题标题】:How to make object bounce forever at same height如何使物体在同一高度永远反弹
【发布时间】:2020-11-25 00:26:01
【问题描述】:

我试图让球永远弹跳,总是在相同的高度,就像在弹跳中没有能量损失一样。

我不知道为什么,这取决于我做什么,随着时间的推移它会弹得更高,或者每次跳跃都会弹得更低。我的期望是,在每次反弹时,它应该始终达到相同的高度,而不是更高或更低。

这是我的代码(代码在 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()

我尝试了不同的更新顺序,比如在弹跳后改变速度之前根据加速度修改速度,甚至弹跳后根本不加速(这似乎效果最好)但弹跳仍然不准确并且不断改变高度:

    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 次加速更新,但这也不起作用。

你们能帮帮我吗?为这样一个简单的场景编写游戏物理的方法是什么?

【问题讨论】:

    标签: python pygame game-physics pyglet


    【解决方案1】:

    数值积分很难!由于您可以轻松准确地求解一维弹道方程,因此请这样做:计算

    y1=y0+v0*dt+g*dt*dt/2
    v1=v0+g*dt
    

    这是在恒定加速度的平凡情况下的velocity Verlet method。如果y1&lt;0,您可以求解二次方程以找出何时它反弹并从该点重新开始积分(速度为负)。

    如果您想在保持数值准确的同时加入更复杂的物理,请考虑速度变量的居中。通过错开它可以获得更好的准确性 - 在定义位置的点之间的时间点定义它会得到类似的leapfrog method

    保守力的一种非常不同的方法是定义球的总能量,并根据它的高度根据它的运动量来移动它。即使这样,您也必须在dt*dt 中包含上述更正,以避免在最大高度附近出现数值问题。

    【讨论】:

      【解决方案2】:

      您的方程式不起作用的原因是您的更新函数缺少由加速度引起的位置变化量。这应该可以。

      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()
      

      【讨论】:

      • 方块在您的代码上以相似的高度永远反弹,但它们达到的高度略有不同。
      • @someguy 好吧,我责怪浮点数,或者如果我犯了任何错误,也许有人可以指出。
      【解决方案3】:

      我一直在想,我相信即使程序完全准确,一个物体通常也不会达到相同的高度。因为反弹导致对象在向上和向下的过程中占据不同的位置,在顶部,对象可能会显得更低,因为在游戏循环中仅显示对象真实运动的某些帧。这些帧可能与球的最高位置无关。 抱歉,这么晚了,但这是我在 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)

      【讨论】:

        猜你喜欢
        • 2021-08-29
        • 1970-01-01
        • 1970-01-01
        • 2013-07-19
        • 2016-08-02
        • 1970-01-01
        • 1970-01-01
        • 2020-11-15
        • 1970-01-01
        相关资源
        最近更新 更多