Here I calculate velocity and angular velocity from scratch and get behavior of object that I want. But this method double calculations for collision.
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 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(9,1)
self.item_list.append(self.item_1)
self.physics_engine = Optional[arcade.PymunkPhysicsEngine]
damping = 0.9
gravity = (0,-20)
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 = 1,
body_type=arcade.PymunkPhysicsEngine.STATIC)
self.physics_engine.add_sprite(self.item_1, friction=0.2,collision_type="item", elasticity = 1)
def hit_handler_pre(dynamic_sprite, static_sprite, _arbiter, _space, _data):
if _arbiter.is_first_contact:
_data["a_v"] = _arbiter.shapes[0].body.velocity
_data["a_w"] = _arbiter.shapes[0].body.angular_velocity
arbApllyImpulses(_arbiter, _space, _data)
return True
def hit_handler_post(dynamic_sprite, static_sprite, _arbiter, _space, _data):
if _arbiter.is_first_contact:
_arbiter.shapes[0].body.velocity = _data["a_v"]
_arbiter.shapes[0].body.angular_velocity = _data["a_w"]
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)
# 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_ = arcade.SpriteSolidColor(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.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_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()