Python 憤怒的小鳥代碼實現:物理引擎pymunk使用


游戲介紹

最近比較忙,周末正好有時間寫了python版本的憤怒的小鳥,使用了物理引擎pymunk,圖片資源是從github上下載的,實現了一個可玩的簡單版本。

功能實現如下:

  • 支持小鳥類型:紅色小鳥,藍色小鳥,黃色小鳥。
  • 支持障礙物的類型:玻璃,木頭,石頭。
  • 支持障礙物的形狀:各種長度的長方形,正方形和圓形。
  • 使用json文件保存關卡信息,設置小豬和障礙物的位置。

游戲截圖如下:

圖2

圖3

完整代碼

游戲實現代碼的github鏈接 憤怒的小鳥
這邊是csdn的下載鏈接 憤怒的小鳥

 

Pymunk介紹

pymunk是一個2D的物理引擎, 它實際是封裝了 c語言寫的2D物理引擎Chipmunk,可以實現碰撞,旋轉等物理運動。

安裝pymunk,可以直接使用pip工具,安裝最新的pymunk 5.5.0:

 

1     pip install pymunk

 

介紹下在pymunk中會使用到的四個基本的類:

  • 剛體 (pymunk.Body):一個剛體具有物體的物理屬性(質量、坐標、旋轉角度、速度等),它自己是沒有形狀的。
  • 碰撞形狀 (pymunk.Circle, pymunk.Segment and pymunk.Poly):通過將形狀附加到實體,你可以定義一個實體的形狀。你可以將多個形狀附加到單個實體上來定義一個復雜的形狀,如果不需要形狀,則可以不附加任何形狀。
  • 約束/關節 (pymunk.constraint.PinJoint, pymunk.constraint.SimpleMotor):你可以在兩個實體之間附加關節以約束它們的行為。比如在兩個實體間保持一個固定的距離。
  • 空間 (pymunk.Space): 空間是pymunk中基本的模擬單元。你可以添加實體,形狀和關節到空間,然后整體更新空間。pymunk會控制空間中所有的實體,形狀和關節如何相互作用。

代碼實現

將物理引擎相關的代碼單獨放在了一個文件 (source\component\physics.py)中,減少代碼的耦合。
定義了一個Physics類,向外提供所有物理引擎相關的函數。
這篇文章只介紹physics.py 中pymunk相關的代碼。

 

pymunk相關初始化

reset 函數初始化了 空間類(pm.Space), 設置了兩個參數

  • gravity : 重力
  • dt (Time step length) : 表示pymunk中每次更新的時間段值,比如dt值是0.002,表示時間段是0.002秒。

setup_lines函數設置了一條直線,作為地面。
Segment類創建了一條從點a 到 點b的直線。


1 class pymunk.Segment(body, a, b, radius)
2 Bases: pymunk.shapes.Shape
3 A line segment shape between two point. Meant mainly as a static shape.

交流群:632408235 

 1 import pymunk as pm
 2 
 3 class Physics():
 4     def __init__(self):
 5         self.reset()
 6 
 7     def reset(self, level=None):
 8         self.level = level
 9         # init space: set gravity and dt
10         self.space = pm.Space()
11         self.space.gravity = (0.0, -700.0)
12         self.dt = 0.002
13         self.birds = []
14         self.pigs = []
15         self.blocks = []
16         self.path_timer = 0
17         self.check_collide = False
18         self.setup_lines()
19         self.setup_collision_handler()
20 
21     def setup_lines(self):
22         # Static Ground
23         x, y = to_pymunk(c.SCREEN_WIDTH, c.GROUND_HEIGHT)
24         static_body = pm.Body(body_type=pm.Body.STATIC)
25         static_lines = [pm.Segment(static_body, (0.0, y), (x, y), 0.0)]
26 
27         for line in static_lines:
28             line.elasticity = 0.95
29             line.friction = 1
30             line.collision_type = COLLISION_LINE
31         self.space.add(static_lines)
32         self.static_lines = static_lines

 

setup_collision_handler 函數用來設置在兩種類型的物體在碰撞發生時,可以由用戶使用的回調函數。 

add_collision_handler 函數添加兩種類型物體a和b碰撞時會調用的handler。比如小豬和小鳥這兩種類型物體的注冊函數就是:add_collision_handler(COLLISION_PIG, COLLISION_BIRD)

1 add_collision_handler(collision_type_a, collision_type_b)
2 Return the CollisionHandler for collisions between objects of type collision_type_a and collision_type_b.

 

我們這里只用到了 post_solve 回調函數,在兩個物體碰撞結束后,獲取碰撞沖擊力(collision impulse)。

 

1 post_solve
2 Two shapes are touching and their collision response has been processed.
3 func(arbiter, space, data)
4 You can retrieve the collision impulse or kinetic energy at this time if you want to use it to calculate sound volumes or damage amounts. See Arbiter for more info.

 

比如handle_pig_collide函數在小豬和障礙物碰撞后,會根據沖擊力的大小來相應減去小豬的生命。

 

 1 COLLISION_BIRD = 1
 2 COLLISION_PIG = 2
 3 COLLISION_BLOCK = 3
 4 COLLISION_LINE = 4
 5 
 6     def setup_collision_handler(self):
 7         def post_solve_bird_line(arbiter, space, data):
 8             if self.check_collide:
 9                 bird_shape = arbiter.shapes[0]
10                 my_phy.handle_bird_collide(bird_shape, True)
11         def post_solve_pig_bird(arbiter, space, data):
12             if self.check_collide:
13                 pig_shape = arbiter.shapes[0]
14                 my_phy.handle_pig_collide(pig_shape, MAX_IMPULSE)
15         def post_solve_pig_line(arbiter, space, data):
16             if self.check_collide:
17                 pig_shape = arbiter.shapes[0]
18                 my_phy.handle_pig_collide(pig_shape, arbiter.total_impulse.length, True)
19         def post_solve_pig_block(arbiter, space, data):
20             if self.check_collide:
21                 if arbiter.total_impulse.length > MIN_DAMAGE_IMPULSE:
22                     pig_shape = arbiter.shapes[0]
23                     my_phy.handle_pig_collide(pig_shape, arbiter.total_impulse.length)
24         def post_solve_block_bird(arbiter, space, data):
25             if self.check_collide:
26                 block_shape, bird_shape = arbiter.shapes
27                 my_phy.handle_bird_collide(bird_shape)
28                 if arbiter.total_impulse.length > 1100:
29                     my_phy.handle_block_collide(block_shape, arbiter.total_impulse.length)
30 
31         self.space.add_collision_handler(
32             COLLISION_BIRD, COLLISION_LINE).post_solve = post_solve_bird_line
33 
34         self.space.add_collision_handler(
35             COLLISION_PIG, COLLISION_BIRD).post_solve = post_solve_pig_bird
36 
37         self.space.add_collision_handler(
38             COLLISION_PIG, COLLISION_LINE).post_solve = post_solve_pig_line
39 
40         self.space.add_collision_handler(
41             COLLISION_PIG, COLLISION_BLOCK).post_solve = post_solve_pig_block
42 
43         self.space.add_collision_handler(
44             COLLISION_BLOCK, COLLISION_BIRD).post_solve = post_solve_block_bird
45 
46     def handle_pig_collide(self, pig_shape, impulse, is_ground=False):
47         for pig in self.pigs:
48             if pig_shape == pig.phy.shape:
49                 if is_ground:
50                     pig.phy.body.velocity = pig.phy.body.velocity * 0.8
51                 else:
52                     damage = impulse // MIN_DAMAGE_IMPULSE
53                     pig.set_damage(damage)
54 
55 # must init as a global parameter to use in the post_solve handler
56 my_phy = Physics()

創建一個pymunk物體

創建物體一般有下面五個步驟

  1.  moment_for_circle

    函數根根據質量和轉動慣量來創建一個剛體(pymunk.Body)。

 

1 pymunk.moment_for_circle(mass, inner_radius, outer_radius, offset=(0, 0))
2 Calculate the moment of inertia for a hollow circle
3 inner_radius and outer_radius are the inner and outer diameters. (A solid circle has an inner diameter of 0)

 

 據質量(mass), 圓的半徑 來計算出剛體的轉動慣量(Moment Of Inertia),慣量就像剛體的旋轉質量。

 

1 class pymunk.Body(mass=0, moment=0, body_type=<class 'CP_BODY_TYPE_DYNAMIC'>)

 

根據剛體,和形狀類型創建一個碰撞形狀,比如圓形就是 pymunk.Circle。 

 

1 class pymunk.Circle(body, radius, offset=(0, 0))Bases: pymunk.shapes.ShapeA circle shape defined by a radius

 

  1. 設置形狀的一些屬性

摩擦系數(friction)

 

1 Friction coefficient.
2 Pymunk uses the Coulomb friction model, a value of 0.0 is frictionless.
3 A value over 1.0 is perfectly fine.

 

彈力 (elasticity)

 

1 Elasticity of the shape.
2 A value of 0.0 gives no bounce, while a value of 1.0 will give a ‘perfect’ bounce. 

 

最后將這個剛體和碰撞形狀都添加到空間中。

 

1  pymunk.Space.add(*objs)
2  Add one or many shapes, bodies or joints to the space

 

 1 class PhyPig():
 2     def __init__(self, x, y, radius, space):
 3         mass = 5
 4         inertia = pm.moment_for_circle(mass, 0, radius, (0, 0))
 5         body = pm.Body(mass, inertia)
 6         body.position = x, y
 7         shape = pm.Circle(body, radius, (0, 0))
 8         shape.elasticity = 0.95
 9         shape.friction = 1
10         shape.collision_type = COLLISION_PIG
11         space.add(body, shape)
12         self.body = body
13         self.shape = shape

 

PhyPig 類的初始化函數創建了一個小豬物體,參數有物體的位置(x,y), 可以將小豬作為一個圓形物體,所以參數有圓的半徑(radius), 參數space就是我們上面創建的空間類。

pymunk 狀態更新

update函數是更新函數,代碼只顯示了小豬相關的代碼。

step 函數的參數dt值就是上面設置的時間段值,表示這次調用 該空間經過了多少時間,pymunk 根據這個時間值更新空間中的所有物體的狀態(比如速度,位置等)。按照pymunk 文檔的說明,將dt值設小一點,每次調用多次會使得模擬更穩定和精確,所以這里每次調用5次step函數。


1 pymunk.Space.step(dt)
2 Update the space for the given time step.

 

遍歷所有的小豬:

  • 檢查小豬的狀態,如果生命小於零或者y軸位置超出了范圍,刪除這個小豬。
  • 更新小豬的位置

pygame 和 pymunk 中對於位置的值是不同的, y軸的坐標需要進行轉換,具體看 to_pygame 函數,600是高度。pymunk 中 body.position的值是物體的中間位置,對應pygame 中 rect 的centerx 和 centery,所以需要轉成[left, top]位置。

  • pygame中,以左上角的位置為(0,0)
  • pymunk中,以左下角的位置為(0,0)
 1 def to_pygame(p):
 2     """Convert position of pymunk to position of pygame"""
 3     return int(p.x), int(-p.y+600)
 4 
 5     def update(self, game_info, level, mouse_pressed):
 6         pigs_to_remove = []
 7 
 8         #From pymunk doc:Performing multiple calls with a smaller dt
 9         #                creates a more stable and accurate simulation
10         #So make five updates per frame for better stability
11         for x in range(5):
12             self.space.step(self.dt)
13         ...
14         for pig in self.pigs:
15             pig.update(game_info)
16             if pig.phy.body.position.y < 0 or pig.life <= 0:
17                 pigs_to_remove.append(pig)
18             poly = pig.phy.shape
19             p = to_pygame(poly.body.position)
20             x, y = p
21             w, h = pig.image.get_size()
22             # change to [left, top] position of pygame
23             x -= w * 0.5
24             y -= h * 0.5
25             angle_degree = math.degrees(poly.body.angle)
26             pig.update_position(x, y, angle_degree)
27 
28         for pig in pigs_to_remove:
29             self.space.remove(pig.phy.shape, pig.phy.shape.body)
30             self.pigs.remove(pig)
31             level.update_score(c.PIG_SCORE)
32         ...

 

完善后的代碼已打包成python教程,交流群:632408235 可到當中獲取

編譯環境

python3.7 + pygame1.9 + pymunk 5.5.0


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM