天天看點

程式設計憤怒的小鳥代碼python_Python 憤怒的小鳥代碼實作(1):實體引擎pymunk使用

Python 憤怒的小鳥代碼實作(1):實體引擎pymunk使用

2019-10-04 16:38:47 marble_xu 閱讀數 9887更多

版權聲明:本文為部落客原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結和本聲明。

python 憤怒的小鳥代碼實作(1):實體引擎pymunk使用

遊戲介紹

最近比較忙,國慶正好有時間寫了python版本的憤怒的小鳥,使用了實體引擎pymunk,圖檔資源是從github上下載下傳的,實作了一個可玩的簡單版本。

功能實作如下:

支援小鳥類型:紅色小鳥,藍色小鳥,黃色小鳥。

支援障礙物的類型:玻璃,木頭,石頭。

支援障礙物的形狀:各種長度的長方形,正方形和圓形。

使用json檔案儲存關卡資訊,設定小豬和障礙物的位置。

遊戲截圖如下:

圖1

程式設計憤怒的小鳥代碼python_Python 憤怒的小鳥代碼實作(1):實體引擎pymunk使用

圖2

程式設計憤怒的小鳥代碼python_Python 憤怒的小鳥代碼實作(1):實體引擎pymunk使用

圖3

程式設計憤怒的小鳥代碼python_Python 憤怒的小鳥代碼實作(1):實體引擎pymunk使用

完整代碼

遊戲實作代碼的github連結 憤怒的小鳥

這邊是csdn的下載下傳連結 憤怒的小鳥

Pymunk介紹

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

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

pip install pymunk

1

介紹下在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的直線。

class pymunk.Segment(body, a, b, radius)

Bases: pymunk.shapes.Shape

A line segment shape between two point. Meant mainly as a static shape.

1

2

3import pymunk as pm

class Physics():

def __init__(self):

self.reset()

def reset(self, level=None):

self.level = level

# init space: set gravity and dt

self.space = pm.Space()

self.space.gravity = (0.0, -700.0)

self.dt = 0.002

self.birds = []

self.pigs = []

self.blocks = []

self.path_timer = 0

self.check_collide = False

self.setup_lines()

self.setup_collision_handler()

def setup_lines(self):

# Static Ground

x, y = to_pymunk(c.SCREEN_WIDTH, c.GROUND_HEIGHT)

static_body = pm.Body(body_type=pm.Body.STATIC)

static_lines = [pm.Segment(static_body, (0.0, y), (x, y), 0.0)]

for line in static_lines:

line.elasticity = 0.95

line.friction = 1

line.collision_type = COLLISION_LINE

self.space.add(static_lines)

self.static_lines = static_lines

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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

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

add_collision_handler(collision_type_a, collision_type_b)

Return the CollisionHandler for collisions between objects of type collision_type_a and collision_type_b.

1

2

我們這裡隻用到了 post_solve 回調函數,在兩個物體碰撞結束後,擷取碰撞沖擊力(collision impulse)。

post_solve

Two shapes are touching and their collision response has been processed.

func(arbiter, space, data)

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.

1

2

3

4

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

COLLISION_BIRD = 1

COLLISION_PIG = 2

COLLISION_BLOCK = 3

COLLISION_LINE = 4

def setup_collision_handler(self):

def post_solve_bird_line(arbiter, space, data):

if self.check_collide:

bird_shape = arbiter.shapes[0]

my_phy.handle_bird_collide(bird_shape, True)

def post_solve_pig_bird(arbiter, space, data):

if self.check_collide:

pig_shape = arbiter.shapes[0]

my_phy.handle_pig_collide(pig_shape, MAX_IMPULSE)

def post_solve_pig_line(arbiter, space, data):

if self.check_collide:

pig_shape = arbiter.shapes[0]

my_phy.handle_pig_collide(pig_shape, arbiter.total_impulse.length, True)

def post_solve_pig_block(arbiter, space, data):

if self.check_collide:

if arbiter.total_impulse.length > MIN_DAMAGE_IMPULSE:

pig_shape = arbiter.shapes[0]

my_phy.handle_pig_collide(pig_shape, arbiter.total_impulse.length)

def post_solve_block_bird(arbiter, space, data):

if self.check_collide:

block_shape, bird_shape = arbiter.shapes

my_phy.handle_bird_collide(bird_shape)

if arbiter.total_impulse.length > 1100:

my_phy.handle_block_collide(block_shape, arbiter.total_impulse.length)

self.space.add_collision_handler(

COLLISION_BIRD, COLLISION_LINE).post_solve = post_solve_bird_line

self.space.add_collision_handler(

COLLISION_PIG, COLLISION_BIRD).post_solve = post_solve_pig_bird

self.space.add_collision_handler(

COLLISION_PIG, COLLISION_LINE).post_solve = post_solve_pig_line

self.space.add_collision_handler(

COLLISION_PIG, COLLISION_BLOCK).post_solve = post_solve_pig_block

self.space.add_collision_handler(

COLLISION_BLOCK, COLLISION_BIRD).post_solve = post_solve_block_bird

def handle_pig_collide(self, pig_shape, impulse, is_ground=False):

for pig in self.pigs:

if pig_shape == pig.phy.shape:

if is_ground:

pig.phy.body.velocity = pig.phy.body.velocity * 0.8

else:

damage = impulse // MIN_DAMAGE_IMPULSE

pig.set_damage(damage)

# must init as a global parameter to use in the post_solve handler

my_phy = Physics()

建立一個pymunk物體

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

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

pymunk.moment_for_circle(mass, inner_radius, outer_radius, offset=(0, 0))

Calculate the moment of inertia for a hollow circle

inner_radius and outer_radius are the inner and outer diameters. (A solid circle has an inner diameter of 0)

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

class pymunk.Body(mass=0, moment=0, body_type=)

1

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

class pymunk.Circle(body, radius, offset=(0, 0))

Bases: pymunk.shapes.Shape

A circle shape defined by a radius

設定形狀的一些屬性

摩擦系數(friction)

Friction coefficient.

Pymunk uses the Coulomb friction model, a value of 0.0 is frictionless.

A value over 1.0 is perfectly fine.

彈力 (elasticity)

Elasticity of the shape.

A value of 0.0 gives no bounce, while a value of 1.0 will give a ‘perfect’ bounce.

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

pymunk.Space.add(*objs)

Add one or many shapes, bodies or joints to the spaceclass PhyPig():

def __init__(self, x, y, radius, space):

mass = 5

inertia = pm.moment_for_circle(mass, 0, radius, (0, 0))

body = pm.Body(mass, inertia)

body.position = x, y

shape = pm.Circle(body, radius, (0, 0))

shape.elasticity = 0.95

shape.friction = 1

shape.collision_type = COLLISION_PIG

space.add(body, shape)

self.body = body

self.shape = shape

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

pymunk 狀态更新

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

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

pymunk.Space.step(dt)

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)def to_pygame(p):

"""Convert position of pymunk to position of pygame"""

return int(p.x), int(-p.y+600)

def update(self, game_info, level, mouse_pressed):

pigs_to_remove = []

#From pymunk doc:Performing multiple calls with a smaller dt

# creates a more stable and accurate simulation

#So make five updates per frame for better stability

for x in range(5):

self.space.step(self.dt)

...

for pig in self.pigs:

pig.update(game_info)

if pig.phy.body.position.y < 0 or pig.life <= 0:

pigs_to_remove.append(pig)

poly = pig.phy.shape

p = to_pygame(poly.body.position)

x, y = p

w, h = pig.image.get_size()

# change to [left, top] position of pygame

x -= w * 0.5

y -= h * 0.5

angle_degree = math.degrees(poly.body.angle)

pig.update_position(x, y, angle_degree)

for pig in pigs_to_remove:

self.space.remove(pig.phy.shape, pig.phy.shape.body)

self.pigs.remove(pig)

level.update_score(c.PIG_SCORE)

...

編譯環境

python3.7 + pygame1.9 + pymunk 5.5.0