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

如何在Pymunk中结合Shapely实现可破坏地形/地形变形功能?

问题核心原因

你的代码存在4个关键错误,共同导致了闪烁、边缘碰撞无效的问题:

1. 坐标转换逻辑错误

你通过local_to_world将地形顶点转为世界坐标,用Shapely计算差集后拿到的也是世界坐标,但创建pm.Poly时第二个参数要求传入绑定刚体的局部坐标,你直接传入世界坐标会导致新生成的形状位置完全错位,只有在地形刚体初始位置为(0,0)时巧合部分生效。

2. 重复触发碰撞回调

你使用begin碰撞回调,且返回值为True,只要挖掘机传感器和地形处于接触状态,每帧都会触发回调、反复删除重建地形形状,直接导致闪烁。

3. 形状简化阈值过高

autogeometry.simplify_curves的第二个参数设为30,会把小于30像素的形状变化直接抹除,碰撞地形边缘时产生的小范围差集会被简化掉,看起来就像没有效果,只有碰撞顶点产生大的形状变化时才能生效。

4. 删除逻辑位置错误

你在遍历MultiPolygon的循环内部执行space.remove(arbiter.shapes[0]),第一次循环就删除了原地形形状,后续循环访问arbiter.shapes[0]时已经是无效对象,多块拆分的地形会生成失败。


修复方案

按以下步骤修改代码即可解决问题:

第一步:修改碰撞回调函数

def handle(arbiter, space, data):
    # 先判断原形状是否已经被处理过,避免重复操作
    if not hasattr(arbiter.shapes[0], "to_remove"):
        # 标记原形状待删除
        arbiter.shapes[0].to_remove = True
        radius = arbiter.shapes[0].radius
        # 计算两个多边形的世界坐标
        platform_verts = [arbiter.shapes[0].body.local_to_world(vertex) for vertex in arbiter.shapes[0].get_vertices()]
        platform = Polygon(platform_verts)
        digger_verts = [arbiter.shapes[1].body.local_to_world(vertex) for vertex in arbiter.shapes[1].get_vertices()]
        digger = Polygon(digger_verts)
        diff = platform.difference(digger)
        # 处理差集结果
        polies = []
        if type(diff) is MultiPolygon:
            polies = list(diff.geoms)
        else:
            polies = [diff]
        # 先删除原形状,再添加新形状
        space.remove(arbiter.shapes[0])
        for polygon in polies:
            if polygon.area < 10: # 过滤过小的碎块
                continue
            # 差集的世界坐标转成地形刚体的局部坐标
            local_verts = [arbiter.shapes[0].body.world_to_local(vect) for vect in polygon.exterior.coords]
            # 调低简化阈值,保留小变化
            vects = autogeometry.simplify_curves(local_verts, 1)
            if len(vects) <3: # 过滤无效多边形
                continue
            new_shape = pm.Poly(arbiter.shapes[0].body, vects, radius=radius)
            new_shape.friction = arbiter.shapes[0].friction
            new_shape.elasticity = arbiter.shapes[0].elasticity
            new_shape.collision_type = arbiter.shapes[0].collision_type
            if new_shape.area > 100:
                space.add(new_shape)
    # 返回False终止本次碰撞事件后续流程,避免重复触发
    return False

第二步:修复地形初始化的冗余代码

你原来的地形初始化代码存在冗余,修改为:

# 删掉原来的make_rect创建dirt的冗余代码,直接初始化地形
dirt_body = pm.Body(body_type=pm.Body.KINEMATIC)
# 地形顶点用局部坐标,刚体位置设为(0,0)和原来逻辑一致
dirt = pm.shapes.Poly(dirt_body, [(300, 300), (600, 300), (600, 500), (300,500)], radius=1)
dirt.collision_type = 1

第三步:可选优化

如果还有偶发闪烁,可以把地形修改逻辑从碰撞回调移到主循环的space.step(dt)之后执行,用队列暂存待处理的地形形状,避免物理更新过程中修改空间对象导致的异常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 20:36:00