1
votes

Comment faire rebondir un objet pour toujours à la même hauteur

J'essaie de faire rebondir une balle pour toujours, à la même hauteur toujours, comme si aucune énergie n'était perdue dans le rebond.

Je ne sais pas pourquoi, selon ce que je fais, il rebondit plus haut avec le temps, ou il rebondit plus bas à chaque saut. Je m'attends à ce qu'à chaque rebond, il atteigne toujours la même hauteur, pas plus haut ni plus bas.

Ceci est mon code (le code est en pyglet, mais il est vraiment facile à lire même si vous ne connaissez pas la bibliothèque). Ici, un carré est déposé du milieu de l'écran, marqué par une flèche, et je m'attends à ce que le cube rebondisse dans le sol et revienne exactement à la hauteur d'origine en revenant:

    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

J'ai essayé différentes commandes de mise à jour, comme modifier la vitesse en fonction de l'accélération avant de changer la vitesse après le rebond, ou même ne pas accélérer du tout après le rebond (ce qui semble fonctionner le mieux) mais le rebond n'est toujours pas précis et change constamment de hauteur:

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

J'ai même essayé quelque chose de plus complexe: créer 2 dt, un avant et un après le rebond, et faire 2 mises à jour d'accélération, mais cela n'a pas fonctionné non plus.

Pouvez-vous m'aider? Comment programmer la physique du jeu pour un scénario aussi simple?


0 commentaires

3 Réponses :


1
votes

L'intégration numérique est difficile! Puisque vous pouvez facilement résoudre exactement l'équation balistique unidimensionnelle, faites-le: calculer

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

Il s'agit de la méthode de Verlet de vitesse dans le cas trivial d'une accélération constante. Si y1<0 , vous pouvez résoudre l'équation quadratique pour savoir quand elle a rebondi et redémarrer l'intégration à partir de ce point (avec la vitesse annulée).

Si vous souhaitez incorporer une physique plus compliquée tout en étant numériquement précis, pensez au centrage de votre variable de vitesse. Une meilleure précision peut être obtenue en l'étalant - la définir à des points dans le temps qui sont à mi-chemin entre les points auxquels la position est définie donne la méthode saute-mouton similaire.

Une approche très différente pour les forces conservatrices consiste à définir une énergie totale pour la balle et à la déplacer en fonction de la quantité qu'elle doit être cinétique en fonction de sa hauteur. Même dans ce cas, vous devez inclure la correction ci-dessus avec dt*dt pour éviter des problèmes numériques près de l'altitude maximale.


0 commentaires

0
votes

La raison pour laquelle votre équation ne fonctionne pas est que votre fonction de mise à jour ne dispose pas de la quantité de changement de position provoquée par l'accélération. Cela devrait fonctionner.

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 commentaires

Le carré rebondit pour toujours à une hauteur similaire sur votre code, mais la hauteur qu'il atteint varie légèrement.


@someguy Eh bien, je blâme les nombres à virgule flottante, ou peut-être que quelqu'un pourrait pointer du doigt si j'ai fait une erreur ou non.



0
votes

J'ai réfléchi, et je crois qu'un objet n'atteindra généralement pas la même hauteur même si le programme est parfaitement précis. Parce que le rebond fait que l'objet prend des positions différentes en montant et en descendant, en haut l'objet peut apparaître plus bas, du fait que dans la boucle de jeu, seules certaines images du mouvement réel de l'objet sont affichées. Ces cadres peuvent ne pas correspondre à la position la plus élevée de la balle. Désolé c'est si tard, mais voici mon essai en js. Vous pouvez l'exécuter dans la console de n'importe quelle page Web. Notez que l'énergie totale reste à peu près la même et serait probablement plus précise sans mon codage bâclé.

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)


0 commentaires