Передача энергии от одного объекта к другому при столкновении (у пимунка / бурундука)

Это вопрос о пимпуке, но я знаю, что есть намного больше пользователей Chimpunk; Так что, если ваш ответ включает код C / Chipmunk, ничего страшного. Хотя я не знаю, как писать код на C, обычно я могу понять, что происходит, если я читаю чужой.

Настройка

Я моделирую игру со скользящими объектами сверху вниз (например, керлинг или шаффлборд). Я сделал минимальный пример соответствующей части кода (в конце вопроса), но его суть:

  • Создаются два идентичных физических объекта (я называю их, следуя терминологии завивки, «камнями»)
  • их местоположения имеют одинаковое значение x, но разные значения y (одно находится на прямой линии над другим).
  • используя apply_impulse (на данный момент, хотя были опробованы другие методы), нижний камень `` запускается '' прямо в другой камень

Чего я надеюсь достичь

Когда камни сталкиваются, нижний камень должен внезапно остановиться (или, может быть, немного отскочить назад - я пока не вспоминаю эту деталь), в то время как вся или большая часть его энергии передается верхнему камню, что может начать движение вверх по экрану.

Что я получаю взамен

Когда камни сталкиваются, нижний камень не останавливается, а начинает толкать верхний камень вверх. Это как если бы нижние имели большую массу, чем верхние, но они созданы с той же функцией, поэтому они должны быть идентичными.

Я загрузил .gif на imgur, который иллюстрирует это, если это помогает: https://imgur.com/a/FF6Xq

Это более низкая частота кадров, чем при фактическом запуске скрипта, но она все же показывает, что происходит.

Что я пробовал

Читая документацию pygame, чтобы попытаться определить все свойства тела и формы, которые могут иметь значение, я попытался настроить все следующее в различных комбинациях:

  • масса тела
  • shape.friction
  • форма. эластичность
  • 'трение о грунт' (max_force ограничения поворота, которое имитирует трение о грунт в сценарии сверху вниз)
  • 'мощность', с которой запускается нижний камень (один из аргументов, переданных в apply_impulse)
  • использование apply_force вместо apply_impulse

Настройка любого / всех из них внесла заметные и ожидаемые изменения в поведение камней, но ни одна из них не изменила фундаментальную проблему, когда один камень толкает другой при столкновении.

Я читал об использовании pymunk.CollisionHandler (), но еще не пробовал его использовать. Из документации я понимаю, что это в основном предназначено для добавления дополнительных эффектов к столкновениям, а не для изменения базовой физики того, что происходит при столкновении в первую очередь. Но, возможно, я неправильно понял и открыт для любых предложений.

Я просмотрел несколько демонстраций пимунков. В частности, демонстрация с именем newtons_cradle.py демонстрирует желаемое мной поведение. Это симуляция одного из этих гаджетов с пятью шарами, подвешенными в ряд; когда пользователь тянет один мяч за конец назад, он попадает в остальную часть ряда, и энергия передается мячу на противоположной стороне. newtons_crade.py имеет только два основных отличия от моего кода:

  • это "вид сбоку" (значит, сила тяжести больше 0)
  • вместо использования apply_impulse или apply_force, только сила тяжести используется для продвижения мяча к другим (сдерживается ограничением).

К сожалению, в моей нисходящей настройке использование гравитации не подходит. Таким образом, проблема может заключаться в использовании мной apply_impulse / apply_force, но я не вижу способа изменить способ их использования (я уже пробовал различные комбинации мощности и массы, а также настраивал параметры ограничений).

Даже указание мне в правильном направлении - то есть некоторые советы о том, что еще я мог бы прочитать, что еще я мог бы попытаться изменить, - был бы очень признателен. Я не могу быть первым, кто попробует это на пимунке / бурундуке, но мне не удалось найти пример. По крайней мере, на стороне пимунков; Если бы я мог изучить хороший пример на C / Chipmunk, он тоже был бы полезен.

Спасибо всем за ваше время.

Минимальный пример кода

Необязательно изучать код, чтобы понять вопрос, но я разместил его здесь на всякий случай, если он будет полезен. Несмотря на то, что он урезан, чтобы показать только основную часть кода, это полный сценарий, и его можно запустить. Это в Python 3.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import pygame
from pygame.locals import *
import pymunk
import pymunk.pygame_util

def add_and_tether_stone(space,sx,sy):
    """Creates a stone and its corresponding shape, and tethers it with constraints that simulate ground friction and govern spin."""
    #body
    mass = stone_mass
    radius = stone_radius
    moment = pymunk.moment_for_circle(mass, 0, radius)
    body = pymunk.Body(mass, moment)
    body.position = sx,sy
    #shape
    shape = pymunk.Circle(body, radius)
    shape.friction = stone_friction
    shape.elisticity = stone_elisticity
    space.add(body, shape)
    #constraints
    fpiv = pymunk.constraint.PivotJoint(space.static_body,body,(0.0,0.0),(0.0,0.0))
    fpiv.max_force = ground_friction
    fpiv.max_bias = 0.0
    fmot = pymunk.constraint.SimpleMotor(space.static_body,body,0)
    fmot.max_force = 5000000 #arbitry 'very high' value clamps down on the high rotation imparted by apply_impulse or apply_force
    space.add(fpiv)
    space.add(fmot)
    return body,shape,fpiv,fmot

def launch_stone(body,power):
    """Launches a stone in the manner of a player taking a shot."""
    body.apply_impulse_at_world_point((0,power),(0,0)) #force(x,y),offset(x,y)

def main():
    global ground_friction,stone_mass,stone_radius,stone_friction,stone_elisticity
    running = True
    #PyGame setup
    pygame.init()
    screen = pygame.display.set_mode((500,500))
    clock = pygame.time.Clock()
    sheet = pygame.Surface((500,500))
    sheetcolor = (0,0,0)
    sheet.fill(sheetcolor)
    sheet = sheet.convert()
    sheetblit = (0,0)
    screen.blit(sheet,sheetblit)

    #PyMunk setup
    space = pymunk.Space() #space.damping defaults to 1.0, and space.gravity defaults to (0.0, 0.0).
    draw_options = pymunk.pygame_util.DrawOptions(sheet) #used only for the pygame_util debug draw mode

    #Constants to Tweak
    stone_mass = 1.4
    stone_radius = 20
    power = 340 #in a full implementation, this would vary with player input
    ground_friction = 4.5
    stone_friction = 2.0
    stone_elisticity = 1.0

    #Setup for the minimal example: add two stones and launch one at the other.
    stone_a = add_and_tether_stone(space,40,260)
    stone_b = add_and_tether_stone(space,40,21)
    launch_stone(stone_b[0],power)

    while running:
        for event in pygame.event.get(): #listen for controls (all the controls except 'esc' have been removed for the minimal example)
            if event.type == KEYDOWN and event.key == K_ESCAPE:
                running = False
        #Draw, update physics, and advance
        sheet.fill(sheetcolor)
        space.debug_draw(draw_options) #from pymunk.pygame-util (handy!)
        screen.blit(sheet,sheetblit)
        space.step(1/50.0)
        pygame.display.flip()
        clock.tick(50)

if __name__ == '__main__':
    sys.exit(main())

Еще раз спасибо всем за ваше время.


person J.C.T.Seymour    schedule 21.08.2017    source источник


Ответы (1)


Возможно, я что-то пропустил в вашем коде (у меня здесь только Python командной строки, поэтому я не могу запустить ваш скрипт), но я не могу воссоздать вашу проблему.

Вот короткий код, который я пробовал, и он работает так, как вы хотите:

import pymunk

s = pymunk.Space()

b1 = pymunk.Body(1,10)
b1.position = 0,0

b2 = pymunk.Body(1,10)
b2.position = 0,10

c1 = pymunk.Circle(b1, 1)
c1.elasticity = 1.0
c2 = pymunk.Circle(b2, 1)
c2.elasticity = 1.0

j1 = pymunk.constraint.PivotJoint(s.static_body, b1, (0,0),(0,0))
j1.max_force = 4.5
j1.max_bias = 0

j2 = pymunk.constraint.PivotJoint(s.static_body, b2, (0,0),(0,0))
j2.max_force = 4.5
j2.max_bias = 0

j3 = pymunk.constraint.SimpleMotor(s.static_body,b1,0)
j3.max_force = 5000000
j4 = pymunk.constraint.SimpleMotor(s.static_body,b2,0)
j4.max_force = 5000000

s.add(b1,b2,c1,c2,j1,j2,j3,j4)

b1.apply_impulse_at_world_point((0,30),(0,0))

for x in range(25):
    s.step(0.02)
    print(b1.position, b2.position)

Это распечатывает это на моем экране (так что b1 остановился, и все движение передается на b2):

Vec2d(0.0, 0.6) Vec2d(0.0, 10.0)
Vec2d(0.0, 1.1982) Vec2d(0.0, 10.0)
Vec2d(0.0, 1.7946) Vec2d(0.0, 10.0)
Vec2d(0.0, 2.3891999999999998) Vec2d(0.0, 10.0)
Vec2d(0.0, 2.9819999999999998) Vec2d(0.0, 10.0)
Vec2d(0.0, 3.573) Vec2d(0.0, 10.0)
Vec2d(0.0, 4.1622) Vec2d(0.0, 10.0)
Vec2d(0.0, 4.7496) Vec2d(0.0, 10.0)
Vec2d(0.0, 5.3352) Vec2d(0.0, 10.0)
Vec2d(0.0, 5.9190000000000005) Vec2d(0.0, 10.0)
Vec2d(0.0, 6.501) Vec2d(0.0, 10.0)
Vec2d(0.0, 7.081200000000001) Vec2d(0.0, 10.0)
Vec2d(0.0, 7.659600000000001) Vec2d(0.0, 10.0)
Vec2d(0.0, 8.2362) Vec2d(0.0, 10.0)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 10.584682725252637)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 11.159477451815137)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 11.732472178377638)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 12.303666904940137)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 12.873061631502637)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 13.440656358065137)
person viblo    schedule 22.08.2017
comment
Спасибо! Сравнив ваш код и мой, я смог найти проблему. Установка shape.elasticity на 1.0 (по умолчанию 0) имела решающее значение - и, к моему неудовольствию, опечатка в слове «эластичность» (как «эластичность») означает, что, хотя я думал, что устанавливаю эластичность, на самом деле он так и не был изменен. Ух, какой способ найти опечатку, разместив свой код на S.O! Что ж, спасибо за вашу помощь, и мне очень жаль, что этот вопрос оказался скучным для сообщества SO. - person J.C.T.Seymour; 23.08.2017