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?
3 Réponses :
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.
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()
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.
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)