Пусть прямоугольный объект имеет угловую скорость 0, угол 0, центр тяжести (0,0) и движется прямо. После чего прямоугольный объект сталкивается с плоской стеной.
Я заметил, что прямоугольные объекты после столкновения со стенами (статическими объектами) в Аркаде начинают вращаться и сдвигаться. В зависимости от соотношения высоты и ширины скорость поворота различна. Но он присутствует всегда.
До столкновения;
После столкновения
На картинках выше правый объект работает без настройки, поэтому он ведет себя странно (|angular_velocity| > 0). Объект слева от ведущего не вращается после столкновения со стеной (|angular_velocity| = 0), поскольку он имеет пользовательские значения скорости и angular_velocity из hit_handler.
Объект слева, item_1, ведет себя правильно, но требует большого количества дополнительных действий. расчеты. Объект справа, item_2, ведет себя как любой другой прямоугольник в Python Arcade или Pymunk.
Поскольку я использую Python Arcade и Pymunk, я вижу следующие возможные решения проблемы:
- Установите момент объекта на INF. Но тогда объект никогда не вращается, поэтому это странное решение.
- Получить все вычисления (связанные с столкновением) от Chipmunk в hit_handler через _arbiter._arbiter: pymunk._chipmunk.ffi. CData. Каким-то образом обработайте данные из CData на Python и придумайте, как исправить данные, вызывающие описанную выше ошибку.
- Выполните расчеты скорости и angular_velocity с нуля. в Python. Кажется, этот метод работает и представлен в коде ниже. Но для игры, которую я делаю, это слишком медленно.
Вот мой код (чтобы начать движение, введите любую клавишу):
from typing import Optional
import arcade
from pyglet.math import Vec2
import numpy as np
# Set how many rows and columns we will have
ROW_COUNT = 10
COLUMN_COUNT = 10
# This sets the WIDTH and HEIGHT of each grid location
WIDTH = 32
HEIGHT = 32
# This sets the margin between each cell
# and on the edges of the screen.
MARGIN = 1
# Do the math to figure out our screen dimensions
SCREEN_WIDTH = ((WIDTH + MARGIN) * COLUMN_COUNT + MARGIN)*2
SCREEN_HEIGHT = ((HEIGHT + MARGIN) * ROW_COUNT + MARGIN)*2
SCREEN_TITLE = "Game example"
DYNAMIC_TYPE = "item"
STATIC_TYPE = "wall"
def cpv(x,y):
return (x,y)
def cpvrotate(t1,t2):
x = t1[0]*t2[0] - t1[1]*t2[1]
y = t1[0]*t2[1] + t1[1]*t2[0]
return cpv(x,y)
def cpvadd(t1,t2):
x = t1[0]+t2[0]
y = t1[1]+t2[1]
return cpv(x,y)
def cpvsub(t1,t2):
x = t1[0]-t2[0]
y = t1[1]-t2[1]
return cpv(x,y)
def cpvdot(t1,t2):
x = t1[0]*t2[0]
y = t1[1]*t2[1]
return x+y
def cpvcross(t1,t2):
x = t1[0]*t2[1]
y = t1[1]*t2[0]
return x-y
def cpvmult(t1,number):
return cpv(t1[0]*number,t1[1]*number)
def cpvperp(t1):
return cpv(-t1[1],t1[0])
def cpvneg(t1):
return cpv(-t1[0],-t1[1])
def relative_velocity(a, b, r1, r2):
v1_sum = cpvadd(a.velocity, cpvmult(cpvperp(r1), a.angular_velocity))
v2_sum = cpvadd(b.velocity, cpvmult(cpvperp(r2), b.angular_velocity))
return cpvsub(v2_sum, v1_sum)
def normal_relative_velocity(a, b, r1, r2, n):
return cpvdot(relative_velocity(a, b, r1, r2), n)
def get_con_bounce(a, b, r1, r2, n, arb):
return normal_relative_velocity(a, b, r1, r2, n)*arb.restitution
def get_vr(a, b, r1, r2, n, surface_vr):
vr = cpvadd(relative_velocity(a, b, r1, r2), surface_vr)
return vr
def apply_impulse(body, j, r):
i_inv = 1.0/body.moment
m_inv = 1.0/body.mass
velocity = cpvmult(j, m_inv)
angular_velocity = i_inv*cpvcross(r, j)
return velocity, angular_velocity
def k_scalar_body(body, r, n):
rcn = cpvcross(r, n)
i_inv = 1.0/body.moment
m_inv = 1.0/body.mass
return m_inv + i_inv*rcn*rcn
def k_scalar(a, b, r1, r2, n):
return k_scalar_body(a, r1, n) + k_scalar_body(b, r2, n)
def cpfclamp(f,min_,max_):
return min(max(f, min_), max_)
def arbApplyImpulse(r1, r2, n, arb, _space, _data, jnAcc=0.0, jtAcc=0.0):
a = arb.shapes[0].body
b = arb.shapes[1].body
nMass = 1.0/k_scalar(a, b, r1, r2, n)
tMass = 1.0/k_scalar(a, b, r1, r2, cpvperp(n))
surface_vr = arb.surface_velocity
bounce = get_con_bounce(a, b, r1, r2, n, arb)
vr = get_vr(a, b, r1, r2, n, surface_vr)
vrn = cpvdot(vr, n)
vrt = cpvdot(vr, cpvperp(n))
jn = -(bounce + vrn)*nMass
jnOld = jnAcc
jnAcc = max(jnOld+jn, 0)
jtMax = arb.friction*jnAcc
jt = -vrt*tMass
jtOld = jtAcc
jtAcc = cpfclamp(jtOld + jt, -jtMax, jtMax)
_j = cpvrotate(n, cpv(jnAcc - jnOld, jtAcc - jtOld))
v,w = apply_impulse(a, cpvneg(_j), r1)
return v, w, bounce
def ArbSetContactPointSet(arb, set_, number, swapped):
p1 = set_[number].point_a
p2 = set_[number].point_b
if swapped:
r1 = cpvsub(p2, arb.shapes[0].body.position)
r2 = cpvsub(p1, arb.shapes[1].body.position)
else:
r1 = cpvsub(p1, arb.shapes[0].body.position)
r2 = cpvsub(p2, arb.shapes[1].body.position)
return r1,r2
def arbApllyImpulses(_arbiter, _space, _data):
swapped = False
v_total = (0,0)
w_total = 0
bounce_total = []
for i in range(len(_arbiter.contact_point_set.points)):
r1,r2 = ArbSetContactPointSet(_arbiter, _arbiter.contact_point_set.points,
i, swapped)
#print("r1 r2 ", r1,r2)
v, w, bounce = arbApplyImpulse(r1, r2, _arbiter.normal, _arbiter,_space, _data)
bounce_total.append(bounce)
v_total = cpvadd(v_total,v)
w_total+=w
_data["a_w"] += w_total
#CUSTOM VELOCITY
_data["a_v"] = list(abs(np.average(bounce_total))*(v_total / np.linalg.norm(v_total)))
class Custom_sprite(arcade.SpriteSolidColor):
def __init__(self, *argv,**kargv):
super(Custom_sprite, self).__init__(*argv,**kargv)
def pymunk_moved(self, physics_engine, dx, dy, d_angle):
""" Handle when the sprite is moved by the physics engine. """
physics_object = physics_engine.sprites[self]
pass
class MyGame(arcade.Window):
def __init__(self, width, height, title):
super().__init__(width, height, title)
# Set the background color of the window
self.background_color = arcade.color.BLACK
self.wall_list = arcade.SpriteList()
self.item_list = arcade.SpriteList()
self.create_map()
self.item_1 = self.create_item(1,1)
self.item_2 = self.create_item(1,3)
self.item_list.append(self.item_1)
self.item_list.append(self.item_2)
self.physics_engine = Optional[arcade.PymunkPhysicsEngine]
damping = 0.9
gravity = (0,0)
self.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,
gravity=gravity)
self.physics_engine.space.collision_slop = 2
self.physics_engine.add_sprite_list(self.wall_list,
friction=0.2,
collision_type="wall",
elasticity = 0.8,
body_type=arcade.PymunkPhysicsEngine.STATIC)
self.physics_engine.add_sprite(self.item_1, friction=0.2,collision_type="item", elasticity = 0.4)
self.physics_engine.add_sprite(self.item_2, friction=0.2, elasticity = 0.4)
def hit_handler_begin(dynamic_sprite, static_sprite, _arbiter, _space, _data):
print()
print("START COLLISION")
print(_arbiter.contact_point_set)
print("ARB angular velocity",_arbiter.shapes[0].body.angular_velocity)
print("ARB angle",_arbiter.shapes[0].body.angle)
print("ARB velocity", _arbiter.shapes[0].body.velocity)
return True
def hit_handler_pre(dynamic_sprite, static_sprite, _arbiter, _space, _data):
if _arbiter.is_first_contact:
print()
print("PRE HIT")
print(_arbiter.contact_point_set)
_data["a_v"] = _arbiter.shapes[0].body.velocity
_data["a_w"] = _arbiter.shapes[0].body.angular_velocity
arbApllyImpulses(_arbiter, _space, _data)
print(_data)
print("ARB angular velocity",_arbiter.shapes[0].body.angular_velocity)
print("ARB angle",_arbiter.shapes[0].body.angle)
print("ARB velocity", _arbiter.shapes[0].body.velocity)
return True
def hit_handler_post(dynamic_sprite, static_sprite, _arbiter, _space, _data):
if _arbiter.is_first_contact:
print()
print("HIT First")
print(_arbiter.contact_point_set)
print("ARB angular velocity",_arbiter.shapes[0].body.angular_velocity)
print("ARB angle",_arbiter.shapes[0].body.angle)
print("ARB velocity", _arbiter.shapes[0].body.velocity)
_arbiter.shapes[0].body.velocity = _data["a_v"]
_arbiter.shapes[0].body.angular_velocity = _data["a_w"]
print("NEW ARB angular velocity",_arbiter.shapes[0].body.angular_velocity)
print("NEW ARB velocity",_arbiter.shapes[0].body.velocity)
def hit_handler_separate(dynamic_sprite, static_sprite, _arbiter, _space, _data):
print()
print("SEP")
print("ARB angular velocity",_arbiter.shapes[0].body.angular_velocity)
print("ARB angle",_arbiter.shapes[0].body.angle)
print("ARB velocity", _arbiter.shapes[0].body.velocity)
self.physics_engine.add_collision_handler(DYNAMIC_TYPE, STATIC_TYPE, begin_handler = hit_handler_begin)
self.physics_engine.add_collision_handler(DYNAMIC_TYPE, STATIC_TYPE, pre_handler = hit_handler_pre)
self.physics_engine.add_collision_handler(DYNAMIC_TYPE, STATIC_TYPE, post_handler = hit_handler_post)
self.physics_engine.add_collision_handler(DYNAMIC_TYPE, STATIC_TYPE, separate_handler = hit_handler_separate)
# Create the cameras. One for the GUI, one for the sprites.
# We scroll the 'sprite world' but not the GUI.
self.camera_sprites = arcade.Camera(SCREEN_WIDTH, SCREEN_HEIGHT)
self.camera_gui = arcade.Camera(SCREEN_WIDTH, SCREEN_HEIGHT)
def scroll_to_item(self, x, y):
position = Vec2(x - self.width / 2, y - self.height / 2)
self.camera_sprites.move_to(position, 0.5)
def create_wall_item(self, row, column, width, height):
x = column * (WIDTH + MARGIN) + (WIDTH / 2 + MARGIN)
y = row * (HEIGHT + MARGIN) + (HEIGHT / 2 + MARGIN)
sprite = arcade.SpriteSolidColor(width, height, arcade.color.GRAY)
sprite.center_x = x
sprite.center_y = y
self.wall_list.append(sprite)
def create_map(self):
# Create a list of solid-color sprites to represent each grid location
for row in range(-1,ROW_COUNT+1):
for column in range(-1,COLUMN_COUNT+1):
if row==-1 or column==-1 or row==ROW_COUNT or column==COLUMN_COUNT:
self.create_wall_item(row,column, WIDTH, HEIGHT)
def get_sprite_coords_by_cell(self, row, column):
x = column * (WIDTH + MARGIN) + (WIDTH / 2 + MARGIN)
y = row * (HEIGHT + MARGIN) + (HEIGHT / 2 + MARGIN)
return x, y
def create_item(self, row = 1, column = 1):
x, y = self.get_sprite_coords_by_cell(row, column)
item_ = Custom_sprite(WIDTH//4, HEIGHT, arcade.color.BLUE)
item_.center_x = x
item_.center_y = y
item_.angle = 0
return item_
def on_draw(self):
"""
Render the screen.
"""
# We should always start by clearing the window pixels
self.clear()
# Select the camera we'll use to draw all our sprites
self.camera_sprites.use()
self.item_list.draw()
self.item_1.draw_hit_box(color = arcade.color.GREEN)
self.item_2.draw_hit_box(color = arcade.color.GREEN)
self.wall_list.draw()
# Select the (unscrolled) camera for our GUI
self.camera_gui.use()
def apply_force(self, physics_engine, sprite, force):
""" Apply force to a Sprite. """
physics_object = physics_engine.sprites[sprite]
physics_object.body.apply_force_at_local_point(force, (0, 0))
def apply_force_world_point(self, physics_engine, sprite, force):
""" Apply force to a Sprite. """
physics_object = physics_engine.sprites[sprite]
physics_object.body.apply_force_at_world_point(force, (sprite.center_x, sprite.center_y))
def on_key_press(self, key, modifiers):
"""Called whenever a key is pressed. """
force = ( 0, 200)
self.physics_engine.apply_impulse(self.item_1, force)
self.physics_engine.apply_impulse(self.item_2, force)
def on_update(self, delta_time):
""" Movement and game logic """
self.physics_engine.step()
x = self.item_1.center_x
y = self.item_1.center_y
self.scroll_to_item(x,y)
def main():
MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
arcade.run()
if __name__ == "__main__":
main()
Подробнее здесь: https://stackoverflow.com/questions/792 ... hon-arcade