You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Python模拟的物体实现弹跳而非仅自由下落?

质点弹跳的碰撞分辨率实现指导

初始下落质点实现代码

我尝试制作软体物体,先从实现下落质点入手,编写了以下Python代码:

import numpy as np
import tkinter as tk
import math
root=tk.Tk()
root.geometry("700x350")
c=tk.Canvas(root, width=700, height=350, background="white")
c.pack()
class point():
    def __init__(self,pos,vel):
        self.position=np.array(pos)
        self.velocity=np.array(vel)
    def show(self):
        x=self.position[0]
        y=self.position[1]
        c.delete("all")
        c.create_oval(x-4,y-4,x+4,y+4,fill='black')
class runner():
    def __init__(self,points,force):
        self.ps=points
        self.grav=force
    def update(self,dt):
        for p in self.ps:
            p.velocity=p.velocity+np.array(self.grav)*dt
            p.position=p.position+p.velocity*dt
            p.show()
ps=[point([350,350//2],[-2,-2])]
x=runner(ps,[0,0.1])
while True:
    x.update(1)
    root.update()

问题说明

我已经掌握Python中的碰撞检测,但对**collision resolution(碰撞分辨率)**的实现方式存疑,想请教如何让该质点触地后实现弹跳,明确其碰撞后的运动方向与受力计算方法。

尝试的碰撞实现代码

我尝试编写了一段代码,但仍需指导完善:

import numpy as np
import tkinter as tk
import math
import warnings
warnings.filterwarnings("error")
root=tk.Tk()
root.geometry("700x350")
c=tk.Canvas(root, width=700, height=350, background="white")
c.pack()
class Collision:
    def __init__(self, normal=[0.0, 0.0], depth=float('-inf')):
        self.normal = np.array(normal)
        self.depth = np.clip(depth,-1000,100)

def find_collision(position):
    return Collision([0.0, 1.0], -(300-position[1]))

class point():
    def __init__(self,pos,vel):
        self.position=np.array(pos)
        self.velocity=np.array(vel)
    def show(self):
        x=self.position[0]
        y=self.position[1]
        c.delete("all")
        c.create_oval(x-4,y-4,x+4,y+4,fill='black')
class runner():
    def __init__(self,points,force):
        self.ps=points
        self.grav=force
        self.friction=10
        self.elasticity=1
    def update(self,dt):
        for p in self.ps:
            p.velocity=p.velocity+np.array(self.grav)*dt
            c = find_collision(p.position)
            
            if c.depth>=0:
                try:
                    p.position += c.normal * c.depth
                    p.position=np.clip(p.position,-345,345)
                    vn = c.normal * np.dot(c.normal, p.velocity)
                    vt = p.velocity - vn
                    
                    vn = -self.elasticity * vn
                    
                    vt *= math.exp(-self.friction * dt)
                    
                    p.velocity = vn + vt
                    
                except RuntimeWarning:
                    print(c.normal,c.depth)
            p.position=p.position+p.velocity*dt
            p.show()
ps=[point([350,350//2],[0,0])]
x=runner(ps,[0,0.1])
while True:
    x.update(1)
    root.update()

代码优化指导

1. 碰撞检测逻辑修正

当前find_collision函数的深度计算未考虑质点半径,导致质点视觉上会穿入地面。Canvas高度为350,y轴向下,质点半径4像素,所以地面碰撞阈值应为350 - 4 = 346,修正后的函数:

def find_collision(position):
    # 地面位于y=346,避免质点视觉穿入地面
    ground_y = 346
    depth = ground_y - position[1]
    if depth < 0:
        # 未碰撞,返回无效碰撞
        return Collision([0.0, 1.0], float('-inf'))
    return Collision([0.0, 1.0], depth)

2. 碰撞分辨率顺序修正

原代码的更新顺序错误,导致碰撞修正后的位置被速度更新覆盖。正确顺序应为:检测碰撞→修正位置和速度→应用重力→更新位置。修正后的update方法:

def update(self,dt):
    for p in self.ps:
        c = find_collision(p.position)
        
        if c.depth >= 0:
            # 1. 将质点移出碰撞区域,避免持续碰撞
            p.position += c.normal * c.depth
            
            # 2. 分解速度为法向(垂直地面)和切向(平行地面)分量
            vn = np.dot(c.normal, p.velocity) * c.normal
            vt = p.velocity - vn
            
            # 3. 法向弹跳:弹性系数反转法向速度,1为完全弹性,0为完全非弹性
            vn = -self.elasticity * vn
            
            # 4. 切向摩擦:指数衰减切向速度,模拟地面摩擦
            vt *= math.exp(-self.friction * dt)
            
            # 5. 合并速度分量
            p.velocity = vn + vt
        
        # 6. 应用重力更新速度
        p.velocity += np.array(self.grav) * dt
        
        # 7. 更新位置,并限制x轴范围避免移出画布
        p.position += p.velocity * dt
        p.position[0] = np.clip(p.position[0], 4, 696)
        
        p.show()

3. 参数调整建议

  • elasticity(弹性系数):建议设为0.8,更符合现实中的弹跳效果(完全弹性1会导致质点永远弹跳)。
  • friction(摩擦系数):建议设为3,避免切向速度衰减过快,导致质点很快停止横向运动。

4. 移除不必要的警告处理

逻辑修正后不会出现数值异常,可删除warnings.filterwarnings("error")及try-except块。


内容的提问来源于stack exchange,提问作者Ishwari Katariya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 11:35:10