├── README.md ├── main.py ├── resources ├── demo │ └── demo1.png └── graphics │ ├── angry_birds.png │ ├── background.png │ ├── block.png │ ├── full-sprite.png │ ├── selected-buttons.png │ └── sling.png └── source ├── __init__.py ├── component ├── __init__.py ├── bird.py ├── block.py ├── button.py ├── physics.py └── pig.py ├── constants.py ├── data └── map │ ├── level_1.json │ ├── level_2.json │ ├── level_3.json │ ├── level_4.json │ ├── level_5.json │ └── level_6.json ├── main.py ├── state ├── __init__.py └── level.py └── tool.py /README.md: -------------------------------------------------------------------------------- 1 | # PythonAngryBirds 2 | a simple implementation of angrybirds using pygame and pymunk 3 | * support three birds:red bird, blue bird, yellow bird, black bird and white bird 4 | * suport three blocks: glass, wood and stone 5 | * use json file to store level data (e.g. position of block and pig) 6 | * TODO: support different pigs 7 | 8 | # Requirement 9 | * Python 3.7 10 | * Python-Pygame 1.9 11 | * Pymunk 5.5.0 12 | 13 | # How To Start Game 14 | $ python main.py 15 | 16 | # How to Play 17 | * use mouse to drag the bird, modify the direction, then release the mouse to shoot the bird 18 | 19 | # Demo 20 | ![demo1](https://raw.githubusercontent.com/marblexu/PythonAngryBirds/master/resources/demo/demo1.png) 21 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | from source.main import main 3 | 4 | if __name__=='__main__': 5 | main() 6 | pg.quit() -------------------------------------------------------------------------------- /resources/demo/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/resources/demo/demo1.png -------------------------------------------------------------------------------- /resources/graphics/angry_birds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/resources/graphics/angry_birds.png -------------------------------------------------------------------------------- /resources/graphics/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/resources/graphics/background.png -------------------------------------------------------------------------------- /resources/graphics/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/resources/graphics/block.png -------------------------------------------------------------------------------- /resources/graphics/full-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/resources/graphics/full-sprite.png -------------------------------------------------------------------------------- /resources/graphics/selected-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/resources/graphics/selected-buttons.png -------------------------------------------------------------------------------- /resources/graphics/sling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/resources/graphics/sling.png -------------------------------------------------------------------------------- /source/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/source/__init__.py -------------------------------------------------------------------------------- /source/component/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/source/component/__init__.py -------------------------------------------------------------------------------- /source/component/bird.py: -------------------------------------------------------------------------------- 1 | __author__ = 'marble_xu' 2 | 3 | import random 4 | import pygame as pg 5 | from .. import tool 6 | from .. import constants as c 7 | 8 | def create_bird(type, x, y): 9 | bird = None 10 | if type == c.RED_BIRD: 11 | bird = RedBird(x, y) 12 | elif type == c.BLUE_BIRD: 13 | bird = BlueBird(x, y) 14 | elif type == c.YELLOW_BIRD: 15 | bird = YellowBird(x, y) 16 | elif type == c.BLACK_BIRD: 17 | bird = BlackBird(x, y) 18 | elif type == c.WHITE_BIRD: 19 | bird = WhiteBird(x, y) 20 | elif type == c.BIG_RED_BIRD: 21 | bird = BigRedBird(x, y) 22 | return bird 23 | 24 | class Bird(): 25 | def __init__(self, x, y, name): 26 | self.frames = [] 27 | self.frame_index = 0 28 | self.animate_timer = 0 29 | self.animate_interval = 100 30 | 31 | self.name = name 32 | self.load_images() 33 | self.frame_num = len(self.frames) 34 | self.image = self.frames[self.frame_index] 35 | self.rect = self.image.get_rect() 36 | self.rect.x = x 37 | self.rect.bottom = y 38 | self.angle_degree = 0 39 | self.state = c.IDLE 40 | self.old_pos = (self.rect.x, self.rect.y) 41 | self.pos_timer = 0 42 | self.path_timer = 0 43 | self.collide = False # collided with ground or shape if it is True 44 | self.mass = 5.0 45 | 46 | def load_frames(self, sheet, frame_rect_list, scale, color=c.WHITE): 47 | frames = [] 48 | for frame_rect in frame_rect_list: 49 | frames.append(tool.get_image(sheet, *frame_rect, color, scale)) 50 | return frames 51 | 52 | def load_images(self): 53 | pass 54 | 55 | def update(self, game_info, level, mouse_pressed): 56 | self.current_time = game_info[c.CURRENT_TIME] 57 | self.handle_state(level, mouse_pressed) 58 | self.animation() 59 | 60 | def handle_state(self, level, mouse_pressed): 61 | if self.state == c.IDLE: 62 | pass 63 | elif self.state == c.ATTACK: 64 | self.attacking(level, mouse_pressed) 65 | self.check_attack_finish() 66 | elif self.state == c.INIT_EXPLODE: 67 | self.init_explode() 68 | elif self.state == c.EXPLODE: 69 | self.exploding(level) 70 | 71 | def attacking(self, level, mouse_pressed): 72 | pass 73 | 74 | def init_explode(self): 75 | pass 76 | 77 | def exploding(self, level): 78 | pass 79 | 80 | def check_attack_finish(self): 81 | if self.pos_timer == 0: 82 | self.pos_timer = self.current_time 83 | self.old_pos = (self.rect.x, self.rect.y) 84 | elif (self.current_time - self.pos_timer) > 500: 85 | distance = tool.distance(self.old_pos[0], self.old_pos[1], self.rect.x, self.rect.y) 86 | if distance < 10: 87 | if self.name == c.BLACK_BIRD: 88 | self.state = c.INIT_EXPLODE 89 | else: 90 | self.state = c.DEAD 91 | self.pos_timer = self.current_time 92 | self.old_pos = (self.rect.x, self.rect.y) 93 | 94 | def animation(self): 95 | if self.state == c.INIT_EXPLODE: 96 | interval = 400 97 | elif self.state == c.EXPLODE: 98 | interval = 100 99 | elif self.frame_index == 0: 100 | interval = 2000 + random.randint(0, 2000) 101 | else: 102 | interval = self.animate_interval 103 | 104 | if (self.current_time - self.animate_timer) > interval: 105 | self.frame_index += 1 106 | if self.frame_index >= self.frame_num: 107 | if self.state == c.INIT_EXPLODE: 108 | self.state = c.EXPLODE 109 | self.frame_index = self.frame_num-1 110 | elif self.state == c.EXPLODE: 111 | self.state = c.DEAD 112 | self.frame_index = self.frame_num-1 113 | else: 114 | self.frame_index = 0 115 | self.animate_timer = self.current_time 116 | 117 | image = self.frames[self.frame_index] 118 | self.image = pg.transform.rotate(image, self.angle_degree) 119 | 120 | def change_image(self, frames): 121 | self.frames = frames 122 | self.frame_num = len(self.frames) 123 | self.frame_index = 0 124 | self.image = self.frames[self.frame_index] 125 | self.animate_timer = self.current_time 126 | 127 | def set_attack(self): 128 | self.state = c.ATTACK 129 | 130 | def set_physics(self, phy): 131 | self.phy = phy 132 | 133 | def set_collide(self): 134 | self.collide = True 135 | 136 | def set_explode(self): 137 | self.state = c.EXPLODE 138 | 139 | def set_dead(self): 140 | self.state = c.DEAD 141 | 142 | def get_radius(self): 143 | return self.rect.w//2 144 | 145 | def update_position(self, x, y, angle_degree=0): 146 | self.rect.x = x 147 | self.rect.y = y 148 | self.angle_degree = angle_degree 149 | 150 | def draw(self, surface): 151 | surface.blit(self.image, self.rect) 152 | 153 | class RedBird(Bird): 154 | def __init__(self, x, y): 155 | Bird.__init__(self, x, y, c.RED_BIRD) 156 | 157 | def load_images(self): 158 | sheet = tool.GFX[c.BIRD_SHEET] 159 | frame_rect_list = [(184, 32, 66, 66), (258, 32, 66, 66), (332, 32, 66, 66), 160 | (404, 32, 66, 66), (472, 32, 66, 66)] 161 | self.frames = self.load_frames(sheet, frame_rect_list, c.BIRD_MULTIPLIER) 162 | 163 | class BlueBird(Bird): 164 | def __init__(self, x, y): 165 | Bird.__init__(self, x, y, c.BLUE_BIRD) 166 | self.clicked = False 167 | 168 | def load_images(self): 169 | sheet = tool.GFX[c.BIRD_SHEET] 170 | frame_rect_list = [(218, 138, 42, 42), (277, 138, 42, 42), (340, 138, 42, 42), 171 | (404, 138, 42, 42), (462, 138, 42, 42)] 172 | self.frames = self.load_frames(sheet, frame_rect_list, 0.7) 173 | 174 | def attacking(self, level, mouse_pressed): 175 | if not self.clicked and mouse_pressed and not self.collide: 176 | self.clicked = True 177 | # create two blue birds when first mouse click 178 | bird_list = [1, -1] 179 | for sign in bird_list: 180 | x, y = self.phy.get_pygame_pos() 181 | bird = BlueBird(x, y) 182 | bird.clicked = True 183 | bird.state = c.ATTACK 184 | level.physics.add_bird_by_copy(bird, self.phy.body.copy()) 185 | 186 | old = self.phy.body.velocity 187 | vec_y = old[1] * 0.5 * sign 188 | bird.phy.body.velocity = (old[0], vec_y) 189 | print('bluebird:[', x, ',', y, ']', 'old:', old, ' new:', bird.phy.body.velocity) 190 | 191 | class YellowBird(Bird): 192 | def __init__(self, x, y): 193 | Bird.__init__(self, x, y, c.YELLOW_BIRD) 194 | self.clicked = False 195 | 196 | def load_images(self): 197 | sheet = tool.GFX[c.BIRD_SHEET] 198 | frame_rect_list = [(190, 208, 74, 74), (266, 208, 74, 74), (341, 208, 74, 74), 199 | (419, 208, 74, 74), (495, 208, 74, 74)] 200 | self.frames = self.load_frames(sheet, frame_rect_list, c.BIRD_MULTIPLIER) 201 | 202 | def attacking(self, level, mouse_pressed): 203 | if not self.clicked and mouse_pressed and not self.collide: 204 | self.clicked = True 205 | # speed velocity of bird when first mouse click 206 | self.phy.body.velocity = self.phy.body.velocity * 3 207 | print('yellow bird:', self.phy.body.velocity) 208 | 209 | class BlackBird(Bird): 210 | def __init__(self, x, y): 211 | Bird.__init__(self, x, y, c.BLACK_BIRD) 212 | self.clicked = False 213 | self.init_explode_show = False 214 | self.exploded = False 215 | 216 | def load_images(self): 217 | sheet = tool.GFX[c.BIRD_SHEET] 218 | frame_rect_list = [(114, 330, 75, 75), (189, 330, 75, 75), (263, 330, 75, 75), 219 | (331, 330, 75, 75), (406, 330, 75, 75)] 220 | self.frames = self.load_frames(sheet, frame_rect_list, c.BIRD_MULTIPLIER) 221 | 222 | init_explode_rect_list = [(114, 330, 75, 75), (481, 330, 75, 75), 223 | (553, 330, 75, 75), (621, 330, 75, 75)] 224 | self.init_explode_frames = self.load_frames(sheet, init_explode_rect_list, c.BIRD_MULTIPLIER) 225 | 226 | sheet = tool.GFX[c.PIG_SHEET] 227 | explode_rect_list = [(408, 199, 112, 112), (275, 200, 130, 130), (133, 201, 139, 139)] 228 | self.explode_frames = self.load_frames(sheet, explode_rect_list, c.BIRD_MULTIPLIER, c.BLACK) 229 | 230 | def attacking(self, level, mouse_pressed): 231 | if not self.clicked and mouse_pressed and not self.collide: 232 | self.clicked = True 233 | self.state = c.EXPLODE 234 | self.phy.body.velocity = self.phy.body.velocity * 0.01 235 | if self.collide: 236 | #if bird collided with other things, it can't explode by clicking 237 | if not self.clicked: 238 | self.state = c.INIT_EXPLODE 239 | self.clicked = True 240 | 241 | def init_explode(self): 242 | if not self.init_explode_show: 243 | self.change_image(self.init_explode_frames) 244 | self.init_explode_show= True 245 | 246 | def exploding(self, level): 247 | if not self.exploded: 248 | self.change_image(self.explode_frames) 249 | level.physics.create_explosion(self.phy.body.position, self.get_radius(), 60, 5) 250 | self.exploded = True 251 | 252 | class WhiteBird(Bird): 253 | def __init__(self, x, y): 254 | Bird.__init__(self, x, y, c.WHITE_BIRD) 255 | self.clicked = False 256 | 257 | def load_images(self): 258 | sheet = tool.GFX[c.BIRD_SHEET] 259 | frame_rect_list = [(141, 443, 85, 85), (228, 441, 85, 85), (313, 438, 85, 85), 260 | (400, 439, 85, 85), (564, 439, 85, 85)] 261 | self.frames = self.load_frames(sheet, frame_rect_list, c.BIRD_MULTIPLIER) 262 | 263 | def attacking(self, level, mouse_pressed): 264 | if not self.clicked and mouse_pressed and not self.collide: 265 | self.clicked = True 266 | vel_x, vel_y = self.phy.body.velocity 267 | self.phy.body.velocity = (vel_x * 2, vel_y + 1000) 268 | egg = Egg(self.rect.centerx, self.rect.bottom + 30) 269 | level.physics.add_egg(egg) 270 | 271 | class Egg(Bird): 272 | def __init__(self, x, y): 273 | Bird.__init__(self, x, y, c.EGG) 274 | self.exploded = False 275 | 276 | def load_images(self): 277 | sheet = tool.GFX[c.PIG_SHEET] 278 | frame_rect_list = [(61, 1035, 44, 59)] 279 | self.frames = self.load_frames(sheet, frame_rect_list, c.BIRD_MULTIPLIER, c.BLACK) 280 | 281 | sheet = tool.GFX[c.PIG_SHEET] 282 | explode_rect_list = [(408, 199, 112, 112), (275, 200, 130, 130), (133, 201, 139, 139)] 283 | self.explode_frames = self.load_frames(sheet, explode_rect_list, c.BIRD_MULTIPLIER, c.BLACK) 284 | 285 | def exploding(self, level): 286 | if not self.exploded: 287 | self.change_image(self.explode_frames) 288 | level.physics.create_explosion(self.phy.body.position, self.get_radius(), 50, 5) 289 | self.exploded = True 290 | 291 | class BigRedBird(Bird): 292 | def __init__(self, x, y): 293 | Bird.__init__(self, x, y, c.BIG_RED_BIRD) 294 | self.mass = 8.0 295 | self.jump = True 296 | 297 | def load_images(self): 298 | sheet = tool.GFX[c.BIRD_SHEET] 299 | frame_rect_list = [(120, 638, 120, 120), (247, 638, 120, 120), (376, 639, 120, 120), 300 | (501, 638, 120, 120)] 301 | self.frames = self.load_frames(sheet, frame_rect_list, c.BIRD_MULTIPLIER) 302 | -------------------------------------------------------------------------------- /source/component/block.py: -------------------------------------------------------------------------------- 1 | __author__ = 'marble_xu' 2 | 3 | import pygame as pg 4 | from .. import tool 5 | from .. import constants as c 6 | 7 | def create_block(x, y, material, shape, type, direction=0): 8 | block = None 9 | if material == c.GLASS: 10 | if shape == c.BEAM: 11 | block = BeamGlass(x, y, type, direction) 12 | elif shape == c.CIRCLE: 13 | block = CircleGlass(x, y, type) 14 | elif material == c.WOOD: 15 | if shape == c.BEAM: 16 | block = BeamWood(x, y, type, direction) 17 | elif shape == c.CIRCLE: 18 | block = CircleWood(x, y, type) 19 | elif material == c.STONE: 20 | if shape == c.BEAM: 21 | block = BeamStone(x, y, type, direction) 22 | elif shape == c.CIRCLE: 23 | block = CircleStone(x, y, type) 24 | return block 25 | 26 | def get_block_mass(name, type): 27 | mass = base = 1.0 28 | if name == c.BEAM: 29 | if type == c.BEAM_TYPE_1: # the shortest length 30 | mass = base 31 | elif type == c.BEAM_TYPE_2: 32 | mass = base * 2 33 | elif type == c.BEAM_TYPE_3: 34 | mass = base * 4 35 | elif type == c.BEAM_TYPE_4: # the longest length 36 | mass = base * 5 37 | elif type == c.BEAM_TYPE_5: # the thick one 38 | mass = base * 4 39 | elif type == c.BEAM_TYPE_6: # the square 40 | mass = base * 2 41 | elif name == c.CIRCLE: 42 | if type == c.CIRCLE_TYPE_1: # the small circle 43 | mass = base * 1.6 44 | elif type == c.BEAM_TYPE_2: # the big circle 45 | mass = base * 6.4 46 | 47 | return mass 48 | 49 | class Block(): 50 | def __init__(self, x, y, name, life): 51 | self.name = name 52 | self.life = life 53 | 54 | self.load_images() 55 | self.setup_images() 56 | self.image = self.images[self.image_index] 57 | self.orig_image = self.image 58 | self.rect = self.image.get_rect() 59 | self.rect.x = x 60 | self.rect.bottom = y 61 | 62 | def load_images(self): 63 | pass 64 | 65 | def get_rect_list(self): 66 | pass 67 | 68 | def setup_images(self): 69 | '''image_threshold is used for change the image of the shape 70 | for 4 images of a shape, the values of image_threshold is [life, life//4 * 3, life//2, life//4]''' 71 | self.image_index = 0 72 | self.image_num = len(self.images) 73 | self.image_threshold = [] 74 | for i in reversed(range(self.image_num)): 75 | i += 1 76 | temp_life = self.life//self.image_num * i 77 | self.image_threshold.append(temp_life) 78 | 79 | def set_physics(self, phy): 80 | self.phy = phy 81 | 82 | def update_position(self, x, y, image): 83 | self.rect.x = x 84 | self.rect.y = y 85 | self.image = image 86 | 87 | def set_damage(self, damage): 88 | self.life -= damage 89 | if self.life < self.image_threshold[self.image_index]: 90 | self.change_image() 91 | 92 | def change_image(self): 93 | if (self.image_index + 1) < self.image_num: 94 | self.image_index += 1 95 | self.image = self.orig_image = self.images[self.image_index] 96 | 97 | def draw(self, surface): 98 | surface.blit(self.image, self.rect) 99 | 100 | class Beam(Block): 101 | def __init__(self, x, y, life, direction): 102 | self.direction = direction 103 | Block.__init__(self, x, y, c.BEAM, life) 104 | 105 | def load_images(self): 106 | rect_list = self.get_rect_list() 107 | self.images = [] 108 | for rect in rect_list: 109 | image = tool.get_image(tool.GFX[c.BLOCK_SHEET], *rect, c.BLACK, 1) 110 | if self.direction == c.VERTICAL: 111 | image = pg.transform.rotate(image, 90) 112 | self.images.append(image) 113 | 114 | class BeamGlass(Beam): 115 | def __init__(self, x, y, type, direction): 116 | self.type = type 117 | Beam.__init__(self, x, y, 4, direction) 118 | self.mass = get_block_mass(self.name, self.type) * c.GLASS_MASS_TIMES 119 | 120 | def get_rect_list(self): 121 | if self.type == c.BEAM_TYPE_1: # the shortest length 122 | rect_list = [( 2, 718, 38, 18), ( 86, 718, 38, 18), 123 | (622, 661, 38, 18), (622, 638, 38, 18)] 124 | elif self.type == c.BEAM_TYPE_2: 125 | rect_list = [(708, 332, 80, 18), (415, 705, 80, 18), 126 | (500, 705, 80, 18), (622, 551, 80, 18)] 127 | elif self.type == c.BEAM_TYPE_3: 128 | rect_list = [(622, 375, 160, 18), (622, 397, 160, 18), 129 | (622, 419, 160, 18), (622, 441, 160, 18)] 130 | elif self.type == c.BEAM_TYPE_4: # the longest length 131 | rect_list = [(501, 332, 200, 18), (415, 375, 200, 18), 132 | (415, 397, 200, 18), (415, 419, 200, 18),] 133 | elif self.type == c.BEAM_TYPE_5: # the thick one 134 | rect_list = [(330, 246, 80, 39), (415, 246, 80, 39), 135 | (500, 246, 80, 39), (585, 246, 80, 39)] 136 | elif self.type == c.BEAM_TYPE_6: # the square 137 | rect_list = [(252, 591, 39, 39), (252, 634, 39, 39), 138 | (252, 677, 39, 39), (330, 418, 39, 39)] 139 | return rect_list 140 | 141 | class BeamWood(Beam): 142 | def __init__(self, x, y, type, direction): 143 | self.type = type 144 | Beam.__init__(self, x, y, 12, direction) 145 | self.mass = get_block_mass(self.name, self.type) * c.WOOD_MASS_TIMES 146 | 147 | def get_rect_list(self): 148 | if self.type == c.BEAM_TYPE_1: # the shortest length 149 | rect_list = [(622, 706, 38, 18), (707, 640, 38, 18), 150 | (750, 640, 38, 18), (707, 662, 38, 18)] 151 | elif self.type == c.BEAM_TYPE_2: 152 | rect_list = [(707, 552, 80, 18), (622, 574, 80, 18), 153 | (707, 574, 80, 18), (622, 596, 80, 18)] 154 | elif self.type == c.BEAM_TYPE_3: 155 | rect_list = [(622, 464, 160, 18), (622, 486, 160, 18), 156 | (622, 508, 160, 18), (622, 530, 160, 18)] 157 | elif self.type == c.BEAM_TYPE_4: # the longest length 158 | rect_list = [(415, 464, 200, 18), (415, 464, 200, 18), 159 | (415, 486, 200, 18), (415, 508, 200, 18)] 160 | elif self.type == c.BEAM_TYPE_5: # the thick one 161 | rect_list = [(670, 247, 80, 38), (330, 290, 80, 38), 162 | (415, 290, 80, 38), (500, 290, 80, 38)] 163 | elif self.type == c.BEAM_TYPE_6: # the square 164 | rect_list = [(330, 462, 38, 38), (330, 505, 38, 38), 165 | (330, 548, 38, 38), (330, 591, 38, 38)] 166 | return rect_list 167 | 168 | class BeamStone(Beam): 169 | def __init__(self, x, y, type, direction): 170 | self.type = type 171 | Beam.__init__(self, x, y, 48, direction) 172 | self.mass = get_block_mass(self.name, self.type) * c.STONE_MASS_TIMES 173 | 174 | def get_rect_list(self): 175 | if self.type == c.BEAM_TYPE_1: # the shortest length 176 | rect_list = [(750, 662, 38, 18), (707, 684, 38, 18), 177 | (750, 684, 38, 18), (707, 706, 38, 18)] 178 | elif self.type == c.BEAM_TYPE_2: 179 | rect_list = [(707, 596, 80, 18), (622, 618, 80, 18), 180 | (707, 618, 80, 18), (622, 640, 80, 18)] 181 | elif self.type == c.BEAM_TYPE_3: 182 | rect_list = [(415, 618, 160, 18), (415, 640, 160, 18), 183 | (415, 662, 160, 18), (415, 684, 160, 18)] 184 | elif self.type == c.BEAM_TYPE_4: # the longest length 185 | rect_list = [(415, 530, 200, 18), (415, 553, 200, 18), 186 | (415, 574, 200, 18), (415, 596, 200, 18)] 187 | elif self.type == c.BEAM_TYPE_5: # the thick one 188 | rect_list = [(585, 290, 80, 38), (670, 290, 80, 38), 189 | (330, 333, 80, 38), (330, 376, 80, 38)] 190 | elif self.type == c.BEAM_TYPE_6: # the square 191 | rect_list = [(330, 634, 38, 38), (330, 677, 38, 38), 192 | (415, 333, 38, 38), (458, 333, 38, 38)] 193 | return rect_list 194 | 195 | class Circle(Block): 196 | def __init__(self, x, y, life): 197 | Block.__init__(self, x, y, c.CIRCLE, life) 198 | self.mass = get_block_mass(self.name, self.type) 199 | 200 | def load_images(self): 201 | rect_list = self.get_rect_list() 202 | self.images = [] 203 | for rect in rect_list: 204 | image = tool.get_image(tool.GFX[c.BLOCK_SHEET], *rect, c.BLACK, 1) 205 | self.images.append(image) 206 | 207 | class CircleGlass(Circle): 208 | def __init__(self, x, y, type): 209 | self.type = type 210 | Circle.__init__(self, x, y, 4) 211 | self.mass = get_block_mass(self.name, self.type) * c.GLASS_MASS_TIMES 212 | 213 | def get_rect_list(self): 214 | if self.type == c.CIRCLE_TYPE_1: # the small circle 215 | rect_list = [(744, 85, 42, 42), (250, 546, 42, 42), 216 | ( 0, 673, 42, 42), ( 84, 673, 42, 42)] 217 | elif self.type == c.BEAM_TYPE_2: # the big circle 218 | rect_list = [(633, 169, 73, 73), (708, 169, 73, 73), 219 | (558, 169, 73, 73), (482, 169, 73, 73)] 220 | return rect_list 221 | 222 | class CircleWood(Circle): 223 | def __init__(self, x, y, type): 224 | self.type = type 225 | Circle.__init__(self, x, y, 12) 226 | self.mass = get_block_mass(self.name, self.type) * c.GLASS_MASS_TIMES 227 | 228 | def get_rect_list(self): 229 | if self.type == c.CIRCLE_TYPE_1: # the small circle 230 | rect_list = [(372, 418, 39, 39), (372, 461, 39, 39), 231 | (372, 504, 39, 39), (372, 547, 39, 39)] 232 | elif self.type == c.BEAM_TYPE_2: # the big circle 233 | rect_list = [(169, 662, 73, 73), (251, 170, 73, 73), 234 | (328, 170, 73, 73), (405, 170, 73, 73)] 235 | return rect_list 236 | 237 | class CircleStone(Circle): 238 | def __init__(self, x, y, type): 239 | self.type = type 240 | Circle.__init__(self, x, y, 48) 241 | self.mass = get_block_mass(self.name, self.type) * c.STONE_MASS_TIMES 242 | 243 | def get_rect_list(self): 244 | if self.type == c.CIRCLE_TYPE_1: # the small circle 245 | rect_list = [(746, 130, 37, 37), (372, 590, 37, 37), 246 | (372, 633, 37, 37), (372, 676, 37, 37)] 247 | elif self.type == c.BEAM_TYPE_2: # the big circle 248 | rect_list = [(252, 246, 73, 73), (252, 321, 73, 73), 249 | (252, 396, 73, 73), (252, 471, 73, 73)] 250 | return rect_list -------------------------------------------------------------------------------- /source/component/button.py: -------------------------------------------------------------------------------- 1 | __author__ = 'marble_xu' 2 | 3 | import pygame as pg 4 | from .. import tool 5 | from .. import constants as c 6 | 7 | class Button(): 8 | def __init__(self, x, y, name): 9 | self.name = name 10 | self.image = self.load_image() 11 | self.rect = self.image.get_rect() 12 | self.rect.x = x 13 | self.rect.y = y 14 | 15 | def load_image(self): 16 | if self.name == c.NEXT_BUTTON: 17 | rect = (142, 365, 130, 100) 18 | scale = 0.54 19 | elif self.name == c.REPLAY_BUTTON: 20 | rect = (24, 4, 100, 100) 21 | scale = 0.6 22 | return tool.get_image(tool.GFX[c.BUTTON_IMG], *rect, c.BLACK, scale) 23 | 24 | def check_mouse_click(self, mouse_pos): 25 | x, y = mouse_pos 26 | if(x >= self.rect.x and x <= self.rect.right and 27 | y >= self.rect.y and y <= self.rect.bottom): 28 | return True 29 | return False 30 | 31 | def draw(self, surface): 32 | surface.blit(self.image, self.rect) -------------------------------------------------------------------------------- /source/component/physics.py: -------------------------------------------------------------------------------- 1 | __author__ = 'marble_xu' 2 | 3 | import math 4 | import pygame as pg 5 | import pymunk as pm 6 | from pymunk import Vec2d 7 | from .. import tool 8 | from .. import constants as c 9 | 10 | COLLISION_BIRD = 1 11 | COLLISION_PIG = 2 12 | COLLISION_BLOCK = 3 13 | COLLISION_LINE = 4 14 | COLLISION_EXPLODE = 5 15 | COLLISION_EGG = 6 16 | 17 | BIRD_IMPULSE_TIMES = 3 18 | MIN_DAMAGE_IMPULSE = 300 19 | 20 | def to_pygame(p): 21 | """Convert position of pymunk to position of pygame""" 22 | return int(p.x), int(-p.y+600) 23 | 24 | def to_pymunk(x, y): 25 | """Convert position of pygame to position of pymunk""" 26 | return (x, -(y-600)) 27 | 28 | class Physics(): 29 | def __init__(self): 30 | self.reset() 31 | 32 | def reset(self, level=None): 33 | self.level = level 34 | # init space: set gravity and dt 35 | self.space = pm.Space() 36 | self.space.gravity = (0.0, -700.0) 37 | self.dt = 0.002 38 | self.birds = [] 39 | self.pigs = [] 40 | self.blocks = [] 41 | self.explodes = [] 42 | self.eggs = [] 43 | self.path_timer = 0 44 | self.check_collide = False 45 | self.explode_timer = 0 46 | self.setup_lines() 47 | self.setup_collision_handler() 48 | 49 | def setup_lines(self): 50 | # Static Ground 51 | x, y = to_pymunk(c.SCREEN_WIDTH, c.GROUND_HEIGHT) 52 | static_body = pm.Body(body_type=pm.Body.STATIC) 53 | static_lines = [pm.Segment(static_body, (0.0, y), (x, y), 0.0)] 54 | 55 | for line in static_lines: 56 | line.elasticity = 0.95 57 | line.friction = 1 58 | line.collision_type = COLLISION_LINE 59 | self.space.add(static_lines) 60 | self.static_lines = static_lines 61 | 62 | def setup_collision_handler(self): 63 | def post_solve_bird_line(arbiter, space, data): 64 | if self.check_collide: 65 | bird_shape = arbiter.shapes[0] 66 | my_phy.handle_bird_collide(bird_shape, True) 67 | def post_solve_pig_bird(arbiter, space, data): 68 | if self.check_collide: 69 | pig_shape = arbiter.shapes[0] 70 | my_phy.handle_pig_collide(pig_shape, arbiter.total_impulse.length * BIRD_IMPULSE_TIMES) 71 | def post_solve_pig_line(arbiter, space, data): 72 | if self.check_collide: 73 | pig_shape = arbiter.shapes[0] 74 | my_phy.handle_pig_collide(pig_shape, arbiter.total_impulse.length, True) 75 | def post_solve_pig_block(arbiter, space, data): 76 | if self.check_collide: 77 | if arbiter.total_impulse.length >= MIN_DAMAGE_IMPULSE: 78 | pig_shape = arbiter.shapes[0] 79 | my_phy.handle_pig_collide(pig_shape, arbiter.total_impulse.length) 80 | def post_solve_block_bird(arbiter, space, data): 81 | if self.check_collide: 82 | block_shape, bird_shape = arbiter.shapes 83 | my_phy.handle_bird_collide(bird_shape) 84 | if arbiter.total_impulse.length >= MIN_DAMAGE_IMPULSE: 85 | my_phy.handle_block_collide(block_shape, arbiter.total_impulse.length) 86 | 87 | def post_solve_block_explode(arbiter, space, data): 88 | if self.check_collide: 89 | block_shape = arbiter.shapes[0] 90 | if arbiter.total_impulse.length > MIN_DAMAGE_IMPULSE: 91 | my_phy.handle_block_collide(block_shape, arbiter.total_impulse.length) 92 | 93 | def post_solve_pig_explode(arbiter, space, data): 94 | if self.check_collide: 95 | pig_shape = arbiter.shapes[0] 96 | if arbiter.total_impulse.length > MIN_DAMAGE_IMPULSE: 97 | my_phy.handle_pig_collide(pig_shape, arbiter.total_impulse.length) 98 | 99 | def post_solve_egg(arbiter, space, data): 100 | if self.check_collide: 101 | egg_shape = arbiter.shapes[0] 102 | my_phy.handle_egg_collide(egg_shape) 103 | 104 | self.space.add_collision_handler( 105 | COLLISION_BIRD, COLLISION_LINE).post_solve = post_solve_bird_line 106 | 107 | self.space.add_collision_handler( 108 | COLLISION_PIG, COLLISION_BIRD).post_solve = post_solve_pig_bird 109 | 110 | self.space.add_collision_handler( 111 | COLLISION_PIG, COLLISION_LINE).post_solve = post_solve_pig_line 112 | 113 | self.space.add_collision_handler( 114 | COLLISION_PIG, COLLISION_BLOCK).post_solve = post_solve_pig_block 115 | 116 | self.space.add_collision_handler( 117 | COLLISION_BLOCK, COLLISION_BIRD).post_solve = post_solve_block_bird 118 | 119 | self.space.add_collision_handler( 120 | COLLISION_BLOCK, COLLISION_EXPLODE).post_solve = post_solve_block_explode 121 | 122 | self.space.add_collision_handler( 123 | COLLISION_PIG, COLLISION_EXPLODE).post_solve = post_solve_pig_explode 124 | 125 | self.space.add_collision_handler( 126 | COLLISION_EGG, COLLISION_LINE).post_solve = post_solve_egg 127 | self.space.add_collision_handler( 128 | COLLISION_EGG, COLLISION_BLOCK).post_solve = post_solve_egg 129 | self.space.add_collision_handler( 130 | COLLISION_EGG, COLLISION_PIG).post_solve = post_solve_egg 131 | 132 | def enable_check_collide(self): 133 | self.check_collide = True 134 | 135 | def add_bird(self, bird, distance, angle, x, y): 136 | x, y = to_pymunk(x, y) 137 | radius = bird.get_radius() 138 | phybird = PhyBird(distance, angle, x, y, self.space, bird.get_radius(), bird.mass) 139 | bird.set_physics(phybird) 140 | self.birds.append(bird) 141 | 142 | def add_egg(self, egg): 143 | x, y = to_pymunk(egg.rect.centerx, egg.rect.centery) 144 | phy = PhyEgg((x, y), egg.rect.w, egg.rect.h, self.space, 10) 145 | egg.set_physics(phy) 146 | self.eggs.append(egg) 147 | 148 | def add_bird_by_copy(self, bird, body): 149 | phybird = PhyBird2(body, self.space) 150 | bird.set_physics(phybird) 151 | self.birds.append(bird) 152 | 153 | def add_pig(self, pig): 154 | '''must use the center position of pygame to transfer to the position of pymunk''' 155 | x, y = to_pymunk(pig.rect.centerx, pig.rect.centery) 156 | radius = pig.rect.w//2 157 | phypig = PhyPig(x, y, radius, self.space) 158 | pig.set_physics(phypig) 159 | self.pigs.append(pig) 160 | 161 | def add_block(self, block): 162 | '''must use the center position of pygame to transfer to the position of pymunk''' 163 | phy = None 164 | x, y = to_pymunk(block.rect.centerx, block.rect.centery) 165 | if block.name == c.BEAM: 166 | length, height = block.rect.w, block.rect.h 167 | phy = PhyPolygon((x, y), length, height, self.space, block.mass) 168 | elif block.name == c.CIRCLE: 169 | radius = block.rect.w//2 170 | phy = PhyCircle((x, y), radius, self.space, block.mass) 171 | if phy: 172 | block.set_physics(phy) 173 | self.blocks.append(block) 174 | else: 175 | print('not support block type:', block.name) 176 | 177 | def add_explode(self, pos, angle, length, mass): 178 | phyexplode = PhyExplode(pos, angle, length, self.space, mass) 179 | self.explodes.append(phyexplode) 180 | 181 | def create_explosion(self, pos, radius, length, mass): 182 | ''' parameter pos is the pymunk position''' 183 | explode_num = 12 184 | sub_pi = math.pi * 2 / explode_num 185 | for i in range(explode_num): 186 | angle = sub_pi * i 187 | x = pos[0] + radius * math.sin(angle) 188 | y = pos[1] + radius * math.cos(angle) 189 | # angle value must calculated by math.pi * 2 190 | self.add_explode((x,y), angle, length, mass) 191 | 192 | def check_explosion(self): 193 | explodes_to_remove = [] 194 | if len(self.explodes) == 0: 195 | return 196 | 197 | if self.explode_timer == 0: 198 | self.explode_timer = self.current_time 199 | elif (self.current_time - self.explode_timer) > 1000: 200 | for explode in self.explodes: 201 | self.space.remove(explode.shape, explode.shape.body) 202 | self.explodes.remove(explode) 203 | self.explode_timer = 0 204 | 205 | for explode in self.explodes: 206 | if explode.is_out_of_length(): 207 | explodes_to_remove.append(explode) 208 | 209 | for explode in explodes_to_remove: 210 | self.space.remove(explode.shape, explode.shape.body) 211 | self.explodes.remove(explode) 212 | 213 | def update(self, game_info, level, mouse_pressed): 214 | birds_to_remove = [] 215 | pigs_to_remove = [] 216 | blocks_to_remove = [] 217 | eggs_to_remove = [] 218 | self.current_time = game_info[c.CURRENT_TIME] 219 | 220 | #From pymunk doc:Performing multiple calls with a smaller dt 221 | # creates a more stable and accurate simulation 222 | #So make five updates per frame for better stability 223 | for x in range(5): 224 | self.space.step(self.dt) 225 | 226 | for bird in self.birds: 227 | bird.update(game_info, level, mouse_pressed) 228 | if (bird.phy.shape.body.position.y < 0 or bird.state == c.DEAD 229 | or bird.phy.shape.body.position.x > c.SCREEN_WIDTH * 2): 230 | birds_to_remove.append(bird) 231 | else: 232 | poly = bird.phy.shape 233 | # the postion transferred from pymunk is the center position of pygame 234 | p = to_pygame(poly.body.position) 235 | x, y = p 236 | w, h = bird.image.get_size() 237 | # change to [left, top] position of pygame 238 | x -= w * 0.5 239 | y -= h * 0.5 240 | angle_degree = math.degrees(poly.body.angle) 241 | bird.update_position(x, y, angle_degree) 242 | self.update_bird_path(bird, p, level) 243 | 244 | for bird in birds_to_remove: 245 | self.space.remove(bird.phy.shape, bird.phy.shape.body) 246 | self.birds.remove(bird) 247 | bird.set_dead() 248 | 249 | for pig in self.pigs: 250 | pig.update(game_info) 251 | if pig.phy.body.position.y < 0 or pig.life <= 0: 252 | pigs_to_remove.append(pig) 253 | poly = pig.phy.shape 254 | p = to_pygame(poly.body.position) 255 | x, y = p 256 | w, h = pig.image.get_size() 257 | x -= w * 0.5 258 | y -= h * 0.5 259 | angle_degree = math.degrees(poly.body.angle) 260 | pig.update_position(x, y, angle_degree) 261 | 262 | for pig in pigs_to_remove: 263 | self.space.remove(pig.phy.shape, pig.phy.shape.body) 264 | self.pigs.remove(pig) 265 | level.update_score(c.PIG_SCORE) 266 | 267 | for block in self.blocks: 268 | if block.life <= 0: 269 | blocks_to_remove.append(block) 270 | poly = block.phy.shape 271 | p = poly.body.position 272 | p = Vec2d(to_pygame(p)) 273 | angle_degree = math.degrees(poly.body.angle) + 180 274 | rotated_image = pg.transform.rotate(block.orig_image, angle_degree) 275 | offset = Vec2d(rotated_image.get_size()) / 2. 276 | p = p - offset 277 | block.update_position(p.x, p.y, rotated_image) 278 | 279 | for block in blocks_to_remove: 280 | self.space.remove(block.phy.shape, block.phy.shape.body) 281 | self.blocks.remove(block) 282 | level.update_score(c.SHAPE_SCORE) 283 | 284 | for egg in self.eggs: 285 | egg.update(game_info, level, mouse_pressed) 286 | if egg.state == c.DEAD: 287 | eggs_to_remove.append(egg) 288 | poly = egg.phy.shape 289 | p = to_pygame(poly.body.position) 290 | x, y = p 291 | w, h = egg.image.get_size() 292 | # change to [left, top] position of pygame 293 | x -= w * 0.5 294 | y -= h * 0.5 295 | angle_degree = math.degrees(poly.body.angle) 296 | egg.update_position(x, y, angle_degree) 297 | 298 | for egg in eggs_to_remove: 299 | self.space.remove(egg.phy.shape, egg.phy.shape.body) 300 | self.eggs.remove(egg) 301 | 302 | self.check_explosion() 303 | 304 | def update_bird_path(self, bird, pos, level): 305 | if bird.path_timer == 0: 306 | bird.path_timer = self.current_time 307 | elif (self.current_time - bird.path_timer) > 50: 308 | bird.path_timer = self.current_time 309 | if not bird.collide: 310 | level.bird_path.append(pos) 311 | 312 | def handle_bird_collide(self, bird_shape, is_ground=False): 313 | for bird in self.birds: 314 | if bird_shape == bird.phy.shape: 315 | if is_ground: # change the velocity of bird to 50% of the original value 316 | if not (bird.name == c.BIG_RED_BIRD and bird.jump): 317 | bird.phy.body.velocity = bird.phy.body.velocity * 0.5 318 | elif bird.name == c.BIG_RED_BIRD: 319 | bird.jump = False 320 | bird.set_collide() 321 | 322 | def handle_pig_collide(self, pig_shape, impulse, is_ground=False): 323 | for pig in self.pigs: 324 | if pig_shape == pig.phy.shape: 325 | if is_ground: 326 | pig.phy.body.velocity = pig.phy.body.velocity * 0.8 327 | else: 328 | damage = impulse // MIN_DAMAGE_IMPULSE 329 | pig.set_damage(damage) 330 | print('pig life:', pig.life, ' damage:', damage, ' impulse:', impulse) 331 | 332 | def handle_block_collide(self, block_shape, impulse): 333 | for block in self.blocks: 334 | if block_shape == block.phy.shape: 335 | damage = impulse // MIN_DAMAGE_IMPULSE 336 | block.set_damage(damage) 337 | print('block damage:', damage, ' impulse:', impulse, ' life:', block.life) 338 | 339 | def handle_egg_collide(self, egg_shape): 340 | for egg in self.eggs: 341 | if egg_shape == egg.phy.shape: 342 | egg.set_explode() 343 | egg.phy.body.velocity = egg.phy.body.velocity * 0.01 344 | break 345 | 346 | def draw(self, surface): 347 | # Draw static lines 348 | if c.DEBUG: 349 | for line in self.static_lines: 350 | body = line.body 351 | pv1 = body.position + line.a.rotated(body.angle) 352 | pv2 = body.position + line.b.rotated(body.angle) 353 | p1 = to_pygame(pv1) 354 | p2 = to_pygame(pv2) 355 | pg.draw.lines(surface, c.RED, False, [p1, p2]) 356 | 357 | for bird in self.birds: 358 | bird.draw(surface) 359 | 360 | for pig in self.pigs: 361 | pig.draw(surface) 362 | 363 | for block in self.blocks: 364 | block.draw(surface) 365 | 366 | for egg in self.eggs: 367 | egg.draw(surface) 368 | 369 | if c.DEBUG: 370 | for explode in self.explodes: 371 | pos = to_pygame(explode.body.position) 372 | pg.draw.circle(surface, c.RED, pos, 5) 373 | 374 | class PhyBird(): 375 | def __init__(self, distance, angle, x, y, space, radius, mass): 376 | self.life = 10 377 | inertia = pm.moment_for_circle(mass, 0, radius, (0, 0)) 378 | body = pm.Body(mass, inertia) 379 | body.position = x, y 380 | power = distance * 53 381 | impulse = power * Vec2d(1, 0) 382 | angle = -angle 383 | body.apply_impulse_at_local_point(impulse.rotated(angle)) 384 | 385 | shape = pm.Circle(body, radius, (0, 0)) 386 | shape.elasticity = 0.95 387 | shape.friction = 1 388 | shape.collision_type = COLLISION_BIRD 389 | space.add(body, shape) 390 | self.body = body 391 | self.shape = shape 392 | 393 | def get_pygame_pos(self): 394 | return to_pygame(self.body.position) 395 | 396 | class PhyBird2(): 397 | def __init__(self, body, space): 398 | self.life = 10 399 | radius = 12 400 | shape = pm.Circle(body, radius, (0, 0)) 401 | shape.elasticity = 0.95 402 | shape.friction = 1 403 | shape.collision_type = COLLISION_BIRD 404 | space.add(body, shape) 405 | self.body = body 406 | self.shape = shape 407 | 408 | 409 | class PhyPig(): 410 | def __init__(self, x, y, radius, space): 411 | mass = 5 412 | inertia = pm.moment_for_circle(mass, 0, radius, (0, 0)) 413 | body = pm.Body(mass, inertia) 414 | body.position = x, y 415 | shape = pm.Circle(body, radius, (0, 0)) 416 | shape.elasticity = 0.95 417 | shape.friction = 1 418 | shape.collision_type = COLLISION_PIG 419 | space.add(body, shape) 420 | self.body = body 421 | self.shape = shape 422 | 423 | class PhyPolygon(): 424 | def __init__(self, pos, length, height, space, mass=5.0): 425 | moment = 1000 426 | body = pm.Body(mass, moment) 427 | body.position = Vec2d(pos) 428 | shape = pm.Poly.create_box(body, (length, height)) 429 | shape.friction = 1 430 | shape.collision_type = COLLISION_BLOCK 431 | space.add(body, shape) 432 | self.body = body 433 | self.shape = shape 434 | 435 | class PhyCircle(): 436 | def __init__(self, pos, radius, space, mass=5.0): 437 | moment = 1000 438 | body = pm.Body(mass, moment) 439 | body.position = Vec2d(pos) 440 | shape = pm.Circle(body, radius, (0, 0)) 441 | shape.friction = 1 442 | shape.collision_type = COLLISION_BLOCK 443 | space.add(body, shape) 444 | self.body = body 445 | self.shape = shape 446 | 447 | class PhyExplode(): 448 | def __init__(self, pos, angle, length, space, mass=5.0): 449 | ''' parater angle is clockwise value ''' 450 | radius = 3 451 | moment = 1000 452 | body = pm.Body(mass, moment) 453 | body.position = Vec2d(pos) 454 | 455 | power = mass * 2000 456 | impulse = power * Vec2d(0, 1) 457 | # the angle of rotated function is counter-clockwise, need to reverse it 458 | angle = -angle 459 | body.apply_impulse_at_local_point(impulse.rotated(angle)) 460 | 461 | shape = pm.Circle(body, radius, (0, 0)) 462 | shape.friction = 1 463 | shape.collision_type = COLLISION_EXPLODE 464 | space.add(body, shape) 465 | self.body = body 466 | self.shape = shape 467 | self.orig_pos = pos 468 | self.length = length 469 | 470 | def is_out_of_length(self): 471 | pos = self.body.position 472 | distance = tool.distance(*pos, *self.orig_pos) 473 | if distance >= self.length: 474 | return True 475 | return False 476 | 477 | class PhyEgg(): 478 | def __init__(self, pos, length, height, space, mass=5.0): 479 | moment = 1000 480 | body = pm.Body(mass, moment) 481 | body.position = Vec2d(pos) 482 | 483 | power = 5000 484 | impulse = power * Vec2d(0, -1) 485 | body.apply_impulse_at_local_point(impulse) 486 | 487 | shape = pm.Poly.create_box(body, (length, height)) 488 | shape.friction = 1 489 | shape.collision_type = COLLISION_EGG 490 | space.add(body, shape) 491 | self.body = body 492 | self.shape = shape 493 | 494 | # must init as a global parameter to use in the post_solve handler 495 | my_phy = Physics() 496 | -------------------------------------------------------------------------------- /source/component/pig.py: -------------------------------------------------------------------------------- 1 | __author__ = 'marble_xu' 2 | 3 | import random 4 | import pygame as pg 5 | from .. import tool 6 | from .. import constants as c 7 | 8 | def create_pig(type, x, y): 9 | pig = None 10 | if type == c.NORMAL_PIG: 11 | pig = NormalPig(x, y) 12 | elif type == c.BIG_PIG: 13 | pig = BigPig(x, y) 14 | return pig 15 | 16 | class Pig(): 17 | def __init__(self, x, y, name, life): 18 | self.name = name 19 | self.life = life 20 | self.animate_timer = 0 21 | self.animate_interval = 100 22 | 23 | self.load_images() 24 | self.setup_images() 25 | self.frame_index = 0 26 | self.frame_num = len(self.frames) 27 | self.image = self.frames[self.frame_index] 28 | self.rect = self.image.get_rect() 29 | self.rect.x = x 30 | self.rect.bottom = y 31 | self.angle_degree = 0 32 | self.state = c.IDLE 33 | 34 | def load_frames(self, sheet, frame_rect_list, scale): 35 | frames = [] 36 | for frame_rect in frame_rect_list: 37 | frames.append(tool.get_image(sheet, *frame_rect, 38 | c.BLACK, scale)) 39 | return frames 40 | 41 | def load_images(self): 42 | pass 43 | 44 | def setup_images(self): 45 | ''' a image is mapping to a frame list 46 | normalpig has 3 images: normal image, hurt1 image and hurt2 image ''' 47 | self.image_index = 0 48 | self.image_num = len(self.images_list) 49 | self.frames = self.images_list[self.image_index] 50 | 51 | self.image_threshold = [] 52 | for i in reversed(range(self.image_num)): 53 | i += 1 54 | temp_life = self.life//self.image_num * i 55 | self.image_threshold.append(temp_life) 56 | 57 | def update(self, game_info): 58 | self.current_time = game_info[c.CURRENT_TIME] 59 | self.animation() 60 | 61 | def animation(self): 62 | if self.frame_index == 0: 63 | interval = 2000 + random.randint(0, 2000) 64 | else: 65 | interval = self.animate_interval 66 | 67 | if (self.current_time - self.animate_timer) > interval: 68 | self.frame_index += 1 69 | if self.frame_index >= self.frame_num: 70 | self.frame_index = 0 71 | self.animate_timer = self.current_time 72 | 73 | image = self.frames[self.frame_index] 74 | self.image = pg.transform.rotate(image, self.angle_degree) 75 | 76 | def set_physics(self, phy): 77 | self.phy = phy 78 | 79 | def set_dead(self): 80 | self.state = c.DEAD 81 | 82 | def update_position(self, x, y, angle_degree): 83 | self.rect.x = x 84 | self.rect.y = y 85 | self.angle_degree = angle_degree 86 | 87 | def set_damage(self, damage): 88 | self.life -= damage 89 | if self.life < self.image_threshold[self.image_index]: 90 | self.change_image() 91 | 92 | def change_image(self): 93 | if (self.image_index + 1) < self.image_num: 94 | self.image_index += 1 95 | self.frames = self.images_list[self.image_index] 96 | self.frame_index = 0 97 | self.frame_num = len(self.frames) 98 | self.image = self.frames[self.frame_index] 99 | 100 | def draw(self, surface): 101 | surface.blit(self.image, self.rect) 102 | 103 | class NormalPig(Pig): 104 | def __init__(self, x, y): 105 | Pig.__init__(self, x, y, c.NORMAL_PIG, 12) 106 | 107 | def load_images(self): 108 | self.images_list = [] 109 | sheet = tool.GFX[c.PIG_SHEET] 110 | normal_rect_list = [(438, 712, 80, 80), (438, 794, 80, 80), (438, 874, 80, 80)] 111 | hurt1_rect_list = [(522, 794, 80, 80), (522, 872, 80, 80), (522, 948, 80, 80)] 112 | hurt2_rect_list = [(438, 956, 80, 80), (522, 792, 80, 80), (522, 1026, 80, 80)] 113 | 114 | rect_lists = [normal_rect_list, hurt1_rect_list, hurt2_rect_list] 115 | for rect_list in rect_lists: 116 | self.images_list.append(self.load_frames(sheet, rect_list, c.NORMAL_PIG_MULTIPLIER)) 117 | 118 | class BigPig(Pig): 119 | def __init__(self, x, y): 120 | Pig.__init__(self, x, y, c.BIG_PIG, 16) 121 | 122 | def load_images(self): 123 | self.images_list = [] 124 | sheet = tool.GFX[c.PIG_SHEET] 125 | normal_rect_list = [(438, 712, 80, 80), (438, 794, 80, 80), (438, 874, 80, 80)] 126 | hurt1_rect_list = [(522, 794, 80, 80), (522, 872, 80, 80), (522, 948, 80, 80)] 127 | hurt2_rect_list = [(438, 956, 80, 80), (522, 792, 80, 80), (522, 1026, 80, 80)] 128 | 129 | rect_lists = [normal_rect_list, hurt1_rect_list, hurt2_rect_list] 130 | for rect_list in rect_lists: 131 | self.images_list.append(self.load_frames(sheet, rect_list, c.BIG_PIG_MULTIPLIER)) -------------------------------------------------------------------------------- /source/constants.py: -------------------------------------------------------------------------------- 1 | __author__ = 'marble_xu' 2 | 3 | DEBUG = False 4 | 5 | START_LEVEL_NUM = 1 6 | 7 | SCREEN_HEIGHT = 650 8 | SCREEN_WIDTH = 1200 9 | SCREEN_SIZE = (SCREEN_WIDTH,SCREEN_HEIGHT) 10 | 11 | GROUND_HEIGHT = 550 12 | 13 | ORIGINAL_CAPTION = "Angry Birds" 14 | 15 | ## COLORS ## 16 | # R G B 17 | GRAY = (100, 100, 100) 18 | NAVYBLUE = ( 60, 60, 100) 19 | WHITE = (255, 255, 255) 20 | RED = (255, 0, 0) 21 | GREEN = ( 0, 255, 0) 22 | FOREST_GREEN = ( 31, 162, 35) 23 | GRASS_GREEN = (130, 200, 100) 24 | BLUE = ( 0, 0, 255) 25 | SKY_BLUE = ( 39, 145, 251) 26 | YELLOW = (255, 255, 0) 27 | ORANGE = (255, 128, 0) 28 | PURPLE = (255, 0, 255) 29 | CYAN = ( 0, 255, 255) 30 | BLACK = ( 0, 0, 0) 31 | NEAR_BLACK = ( 19, 15, 48) 32 | COMBLUE = (233, 232, 255) 33 | GOLD = (255, 215, 0) 34 | 35 | BGCOLOR = WHITE 36 | 37 | 38 | BACKGROUND_MULTIPLER = 1 39 | BIRD_MULTIPLIER = 0.5 40 | NORMAL_PIG_MULTIPLIER = 0.4 41 | BIG_PIG_MULTIPLIER = 0.8 42 | 43 | #STATES FOR ENTIRE GAME 44 | MAIN_MENU = 'main menu' 45 | LOAD_SCREEN = 'load screen' 46 | TIME_OUT = 'time out' 47 | GAME_OVER = 'game over' 48 | LEVEL = 'level' 49 | 50 | #GAME INFO DICTIONARY KEYS 51 | CURRENT_TIME = 'current time' 52 | LEVEL_NUM = 'level num' 53 | SCORE = 'score' 54 | 55 | #STATE 56 | IDLE = 'idle' 57 | ATTACK = 'attack' 58 | OVER = 'over' 59 | DEAD = 'dead' 60 | INIT_EXPLODE = 'init_explode' 61 | EXPLODE = 'explode' 62 | 63 | #LEVEL NAME 64 | MATERIAL = 'material' 65 | SHAPE = 'shape' 66 | TYPE = 'type' 67 | DIRECTION = 'direction' 68 | BIRDS = 'birds' 69 | PIGS = 'pigs' 70 | BLOCKS = 'blocks' 71 | 72 | #BIRD 73 | BIRD_SHEET = 'angry_birds' 74 | RED_BIRD = 'red_bird' 75 | BLUE_BIRD = 'blue_bird' 76 | YELLOW_BIRD = 'yellow_bird' 77 | BLACK_BIRD = 'black_bird' 78 | WHITE_BIRD = 'white_bird' 79 | EGG = 'egg' 80 | BIG_RED_BIRD = 'big_red_bird' 81 | 82 | #PIG 83 | PIG_SHEET = 'full-sprite' 84 | NORMAL_PIG = 'normal_pig' 85 | BIG_PIG = 'big_pig' 86 | 87 | ''' BLOCK INFO ''' 88 | BLOCK_SHEET = 'block' 89 | #BLOCK MATERIAL 90 | GLASS = 'glass' 91 | WOOD = 'wood' 92 | STONE = 'stone' 93 | #SHAPE TYPE 94 | BEAM = 'beam' 95 | CIRCLE = 'circle' 96 | #BEAM SUBTYPE 97 | BEAM_TYPE_1 = 1 98 | BEAM_TYPE_2 = 2 99 | BEAM_TYPE_3 = 3 100 | BEAM_TYPE_4 = 4 101 | BEAM_TYPE_5 = 5 102 | BEAM_TYPE_6 = 6 103 | #CIRCLE SUBTYPE 104 | CIRCLE_TYPE_1 = 1 105 | CIRCLE_TYPE_2 = 2 106 | #DIRECTION 107 | HORIZONTAL = 0 108 | VERTICAL = 1 109 | #MASS TIMES 110 | GLASS_MASS_TIMES = 1 111 | WOOD_MASS_TIMES = 2 112 | STONE_MASS_TIMES = 4 113 | 114 | 115 | #BUTTON 116 | BUTTON_HEIGHT = 10 117 | BUTTON_IMG = 'selected-buttons' 118 | NEXT_BUTTON = 'next_button' 119 | REPLAY_BUTTON = 'replay_button' 120 | 121 | #SCORE 122 | BIRD_SCORE = 10000 123 | PIG_SCORE = 5000 124 | SHAPE_SCORE = 1000 125 | -------------------------------------------------------------------------------- /source/data/map/level_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "birds":[ 3 | {"type":"red_bird"}, 4 | {"type":"red_bird"}, 5 | {"type":"red_bird"}, 6 | {"type":"red_bird"} 7 | ], 8 | "pigs":[ 9 | {"type":"normal_pig", "x":970, "y":550}, 10 | {"type":"normal_pig", "x":970, "y":450} 11 | ], 12 | "blocks":[ 13 | {"x": 890, "y":550, "material":"glass", "shape":"beam", "type":4, "direction":1}, 14 | {"x": 950, "y":550, "material":"wood", "shape":"beam", "type":2, "direction":1}, 15 | {"x":1010, "y":550, "material":"wood", "shape":"beam", "type":2, "direction":1}, 16 | {"x":1070, "y":550, "material":"glass", "shape":"beam", "type":4, "direction":1}, 17 | {"x": 950, "y":470, "material":"wood", "shape":"beam", "type":2, "direction":0}, 18 | {"x": 950, "y":452, "material":"wood", "shape":"beam", "type":2, "direction":1}, 19 | {"x":1010, "y":452, "material":"wood", "shape":"beam", "type":2, "direction":1}, 20 | {"x": 950, "y":372, "material":"wood", "shape":"beam", "type":2, "direction":0}, 21 | {"x": 890, "y":350, "material":"glass", "shape":"beam", "type":4, "direction":0} 22 | ] 23 | } -------------------------------------------------------------------------------- /source/data/map/level_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "birds":[ 3 | {"type":"blue_bird"}, 4 | {"type":"blue_bird"}, 5 | {"type":"red_bird"}, 6 | {"type":"red_bird"} 7 | ], 8 | "pigs":[ 9 | {"type":"normal_pig", "x":1000, "y":550} 10 | ], 11 | "blocks":[ 12 | {"x": 800, "y":550, "material":"wood", "shape":"beam", "type":2, "direction":1}, 13 | {"x": 850, "y":550, "material":"stone","shape":"beam", "type":2, "direction":1}, 14 | {"x": 800, "y":470, "material":"wood", "shape":"beam", "type":2, "direction":1}, 15 | {"x": 850, "y":470, "material":"stone","shape":"beam", "type":2, "direction":1}, 16 | {"x": 930, "y":550, "material":"glass","shape":"beam", "type":2, "direction":1}, 17 | {"x":1070, "y":550, "material":"glass","shape":"beam", "type":2, "direction":1}, 18 | {"x": 930, "y":470, "material":"glass","shape":"beam", "type":3, "direction":0} 19 | ] 20 | } -------------------------------------------------------------------------------- /source/data/map/level_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "birds":[ 3 | {"type":"red_bird"}, 4 | {"type":"red_bird"}, 5 | {"type":"red_bird"}, 6 | {"type":"red_bird"} 7 | ], 8 | "pigs":[ 9 | {"type": "big_pig", "x":950, "y":550}, 10 | {"type": "big_pig", "x":950, "y":484}, 11 | {"type":"normal_pig", "x":970, "y":372}, 12 | {"type":"normal_pig", "x":975, "y":344}, 13 | {"type":"normal_pig", "x":965, "y":316} 14 | ], 15 | "blocks":[ 16 | {"x": 820, "y":550, "material":"stone", "shape":"beam", "type":3, "direction":1}, 17 | {"x": 850, "y":550, "material":"stone", "shape":"beam", "type":3, "direction":1}, 18 | {"x": 880, "y":550, "material":"stone", "shape":"beam", "type":5, "direction":1}, 19 | {"x": 880, "y":470, "material":"stone", "shape":"beam", "type":5, "direction":1}, 20 | {"x": 930, "y":550, "material":"wood", "shape":"beam", "type":3, "direction":1}, 21 | {"x":1010, "y":550, "material":"wood", "shape":"beam", "type":3, "direction":1}, 22 | {"x":1040, "y":550, "material":"stone", "shape":"beam", "type":5, "direction":1}, 23 | {"x":1040, "y":470, "material":"stone", "shape":"beam", "type":5, "direction":1}, 24 | {"x":1090, "y":550, "material":"stone", "shape":"beam", "type":3, "direction":1}, 25 | {"x":1120, "y":550, "material":"stone", "shape":"beam", "type":3, "direction":1}, 26 | {"x": 820, "y":390, "material":"wood", "shape":"beam", "type":3, "direction":0}, 27 | {"x": 980, "y":390, "material":"wood", "shape":"beam", "type":3, "direction":0}, 28 | 29 | {"x": 900, "y":372, "material":"stone", "shape":"beam", "type":3, "direction":1}, 30 | {"x": 920, "y":372, "material":"wood", "shape":"beam", "type":3, "direction":1}, 31 | {"x": 940, "y":372, "material":"wood", "shape":"beam", "type":3, "direction":1}, 32 | {"x":1000, "y":372, "material":"wood", "shape":"beam", "type":3, "direction":1}, 33 | {"x":1020, "y":372, "material":"wood", "shape":"beam", "type":3, "direction":1}, 34 | {"x":1040, "y":372, "material":"stone", "shape":"beam", "type":3, "direction":1}, 35 | {"x": 900, "y":212, "material":"wood", "shape":"beam", "type":2, "direction":0}, 36 | {"x": 980, "y":212, "material":"wood", "shape":"beam", "type":2, "direction":0}, 37 | 38 | {"x": 943, "y":194, "material":"stone", "shape":"circle", "type":2}, 39 | {"x": 905, "y":194, "material":"glass", "shape":"beam", "type":2, "direction":1}, 40 | {"x": 925, "y":194, "material":"glass", "shape":"beam", "type":2, "direction":1}, 41 | {"x":1015, "y":194, "material":"glass", "shape":"beam", "type":2, "direction":1}, 42 | {"x":1035, "y":194, "material":"glass", "shape":"beam", "type":2, "direction":1}, 43 | {"x": 900, "y":114, "material":"glass", "shape":"beam", "type":3, "direction":0}, 44 | 45 | {"x": 910, "y": 98, "material":"glass", "shape":"beam", "type":1, "direction":1}, 46 | {"x": 950, "y": 98, "material":"glass", "shape":"beam", "type":1, "direction":1}, 47 | {"x": 990, "y": 98, "material":"glass", "shape":"beam", "type":1, "direction":1}, 48 | {"x":1030, "y": 98, "material":"glass", "shape":"beam", "type":1, "direction":1} 49 | ] 50 | } -------------------------------------------------------------------------------- /source/data/map/level_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "birds":[ 3 | {"type":"blue_bird"}, 4 | {"type":"blue_bird"}, 5 | {"type":"yellow_bird"}, 6 | {"type":"red_bird"} 7 | ], 8 | "pigs":[ 9 | {"type":"normal_pig", "x": 885, "y":434}, 10 | {"type":"normal_pig", "x":1000, "y":434}, 11 | {"type":"normal_pig", "x": 948, "y":318} 12 | ], 13 | "blocks":[ 14 | {"x": 800, "y":550, "material":"wood", "shape":"beam", "type":2, "direction":1}, 15 | {"x": 860, "y":550, "material":"wood", "shape":"beam", "type":2, "direction":1}, 16 | {"x": 920, "y":550, "material":"wood", "shape":"beam", "type":2, "direction":1}, 17 | {"x": 980, "y":550, "material":"wood", "shape":"beam", "type":2, "direction":1}, 18 | {"x":1040, "y":550, "material":"wood", "shape":"beam", "type":2, "direction":1}, 19 | {"x":1100, "y":550, "material":"wood", "shape":"beam", "type":2, "direction":1}, 20 | 21 | {"x": 800, "y":470, "material":"wood", "shape":"beam", "type":2, "direction":0}, 22 | {"x": 920, "y":470, "material":"wood", "shape":"beam", "type":2, "direction":0}, 23 | {"x":1040, "y":470, "material":"wood", "shape":"beam", "type":2, "direction":0}, 24 | 25 | {"x": 860, "y":452, "material":"wood", "shape":"beam", "type":2, "direction":0}, 26 | {"x": 980, "y":452, "material":"wood", "shape":"beam", "type":2, "direction":0}, 27 | 28 | {"x": 860, "y":434, "material":"wood", "shape":"beam", "type":2, "direction":1}, 29 | {"x": 920, "y":434, "material":"wood", "shape":"beam", "type":2, "direction":1}, 30 | {"x": 980, "y":434, "material":"wood", "shape":"beam", "type":2, "direction":1}, 31 | {"x":1040, "y":434, "material":"wood", "shape":"beam", "type":2, "direction":1}, 32 | 33 | {"x": 860, "y":354, "material":"wood", "shape":"beam", "type":2, "direction":0}, 34 | {"x": 980, "y":354, "material":"wood", "shape":"beam", "type":2, "direction":0}, 35 | 36 | {"x": 920, "y":336, "material":"wood", "shape":"beam", "type":2, "direction":0}, 37 | {"x": 920, "y":318, "material":"wood", "shape":"beam", "type":2, "direction":1}, 38 | {"x": 980, "y":318, "material":"wood", "shape":"beam", "type":2, "direction":1}, 39 | {"x": 920, "y":238, "material":"wood", "shape":"beam", "type":2, "direction":0} 40 | ] 41 | } -------------------------------------------------------------------------------- /source/data/map/level_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "birds":[ 3 | {"type":"red_bird"}, 4 | {"type":"red_bird"}, 5 | {"type":"red_bird"}, 6 | {"type":"red_bird"} 7 | ], 8 | "pigs":[ 9 | {"type":"normal_pig", "x": 770, "y":550}, 10 | {"type":"normal_pig", "x": 965, "y":493} 11 | ], 12 | "blocks":[ 13 | {"x":700, "y":550, "material":"glass", "shape":"beam", "type":5, "direction":1}, 14 | {"x":820, "y":550, "material":"glass", "shape":"beam", "type":5, "direction":1}, 15 | {"x":700, "y":470, "material":"glass", "shape":"beam", "type":3, "direction":0}, 16 | {"x":700, "y":452, "material":"wood", "shape":"beam", "type":6, "direction":0}, 17 | {"x":760, "y":452, "material":"wood", "shape":"beam", "type":6, "direction":0}, 18 | {"x":820, "y":452, "material":"wood", "shape":"beam", "type":6, "direction":0}, 19 | 20 | {"x": 900, "y":550, "material":"stone", "shape":"beam", "type":5, "direction":0}, 21 | {"x": 980, "y":550, "material":"stone", "shape":"beam", "type":5, "direction":0}, 22 | {"x": 900, "y":511, "material":"stone", "shape":"beam", "type":3, "direction":0}, 23 | {"x": 900, "y":493, "material":"glass", "shape":"beam", "type":5, "direction":1}, 24 | {"x":1020, "y":493, "material":"glass", "shape":"beam", "type":5, "direction":1}, 25 | {"x": 900, "y":413, "material":"glass", "shape":"beam", "type":3, "direction":0}, 26 | {"x": 900, "y":395, "material":"wood", "shape":"beam", "type":6, "direction":0}, 27 | {"x": 960, "y":395, "material":"wood", "shape":"beam", "type":6, "direction":0}, 28 | {"x":1020, "y":395, "material":"wood", "shape":"beam", "type":6, "direction":0} 29 | ] 30 | } -------------------------------------------------------------------------------- /source/data/map/level_6.json: -------------------------------------------------------------------------------- 1 | { 2 | "birds":[ 3 | {"type":"yellow_bird"}, 4 | {"type":"black_bird"}, 5 | {"type":"black_bird"}, 6 | {"type":"black_bird"} 7 | ], 8 | "pigs":[ 9 | {"type":"normal_pig", "x": 910, "y":550}, 10 | {"type":"normal_pig", "x":1010, "y":550}, 11 | {"type":"big_pig", "x": 865, "y":414}, 12 | {"type":"big_pig", "x":1010, "y":414} 13 | ], 14 | "blocks":[ 15 | {"x": 810, "y":550, "material":"wood", "shape":"beam", "type":5, "direction":1}, 16 | {"x": 850, "y":550, "material":"wood", "shape":"beam", "type":5, "direction":1}, 17 | {"x": 960, "y":550, "material":"wood", "shape":"beam", "type":5, "direction":1}, 18 | {"x":1070, "y":550, "material":"wood", "shape":"beam", "type":5, "direction":1}, 19 | {"x":1110, "y":550, "material":"wood", "shape":"beam", "type":5, "direction":1}, 20 | 21 | {"x": 810, "y":470, "material":"wood", "shape":"beam", "type":5, "direction":0}, 22 | {"x": 900, "y":470, "material":"wood", "shape":"beam", "type":3, "direction":0}, 23 | {"x": 900, "y":452, "material":"wood", "shape":"beam", "type":3, "direction":0}, 24 | {"x":1070, "y":470, "material":"wood", "shape":"beam", "type":5, "direction":0}, 25 | {"x": 820, "y":432, "material":"wood", "shape":"beam", "type":3, "direction":0}, 26 | {"x": 980, "y":432, "material":"wood", "shape":"beam", "type":3, "direction":0}, 27 | 28 | {"x": 820, "y":414, "material":"wood", "shape":"beam", "type":6, "direction":0}, 29 | {"x": 820, "y":376, "material":"wood", "shape":"beam", "type":6, "direction":0}, 30 | {"x": 960, "y":414, "material":"wood", "shape":"beam", "type":2, "direction":1}, 31 | {"x": 980, "y":414, "material":"wood", "shape":"beam", "type":2, "direction":1}, 32 | {"x":1100, "y":414, "material":"wood", "shape":"beam", "type":6, "direction":0}, 33 | {"x":1100, "y":376, "material":"wood", "shape":"beam", "type":6, "direction":0}, 34 | 35 | {"x": 820, "y":334, "material":"wood", "shape":"beam", "type":3, "direction":0}, 36 | {"x": 820, "y":316, "material":"wood", "shape":"beam", "type":3, "direction":0}, 37 | {"x": 980, "y":334, "material":"wood", "shape":"beam", "type":3, "direction":0}, 38 | {"x": 980, "y":316, "material":"wood", "shape":"beam", "type":3, "direction":0}, 39 | 40 | {"x": 820, "y":298, "material":"wood", "shape":"beam", "type":6, "direction":0}, 41 | {"x": 890, "y":298, "material":"wood", "shape":"beam", "type":6, "direction":0}, 42 | {"x": 960, "y":298, "material":"wood", "shape":"beam", "type":6, "direction":0}, 43 | {"x":1030, "y":298, "material":"wood", "shape":"beam", "type":6, "direction":0}, 44 | {"x":1100, "y":298, "material":"wood", "shape":"beam", "type":6, "direction":0} 45 | ] 46 | } -------------------------------------------------------------------------------- /source/main.py: -------------------------------------------------------------------------------- 1 | __author__ = 'marble_xu' 2 | 3 | import pygame as pg 4 | from . import tool 5 | from . import constants as c 6 | from .state import level 7 | 8 | def main(): 9 | game = tool.Control() 10 | state_dict = {c.LEVEL: level.Level()} 11 | game.setup_states(state_dict, c.LEVEL) 12 | game.main() 13 | -------------------------------------------------------------------------------- /source/state/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonAngryBirds/bf302c0397adfa731e38037d6f92a9b976b5449c/source/state/__init__.py -------------------------------------------------------------------------------- /source/state/level.py: -------------------------------------------------------------------------------- 1 | __author__ = 'marble_xu' 2 | 3 | import os 4 | import json 5 | import math 6 | import pygame as pg 7 | from .. import tool 8 | from .. import constants as c 9 | from ..component import button, physics, bird, pig, block 10 | 11 | bold_font = pg.font.SysFont("arial", 30, bold=True) 12 | 13 | def vector(p0, p1): 14 | """Return the vector of the points 15 | p0 = (xo,yo), p1 = (x1,y1)""" 16 | a = p1[0] - p0[0] 17 | b = p1[1] - p0[1] 18 | return (a, b) 19 | 20 | def unit_vector(v): 21 | """Return the unit vector of the points 22 | v = (a,b)""" 23 | h = ((v[0]**2)+(v[1]**2))**0.5 24 | if h == 0: 25 | h = 0.000000000000001 26 | ua = v[0] / h 27 | ub = v[1] / h 28 | return (ua, ub) 29 | 30 | class Level(tool.State): 31 | def __init__(self): 32 | tool.State.__init__(self) 33 | self.player = None 34 | 35 | def startup(self, current_time, persist): 36 | self.game_info = persist 37 | self.persist = self.game_info 38 | self.game_info[c.CURRENT_TIME] = current_time 39 | self.reset() 40 | 41 | def reset(self): 42 | self.score = self.game_info[c.SCORE] 43 | self.state = c.IDLE 44 | self.physics = physics.my_phy 45 | self.physics.reset(self) 46 | self.load_map() 47 | self.setup_background() 48 | self.setup_buttons() 49 | self.setup_sling() 50 | self.setup_birds() 51 | self.setup_pigs() 52 | self.setup_blocks() 53 | self.over_timer = 0 54 | 55 | def load_map(self): 56 | map_file = 'level_' + str(self.game_info[c.LEVEL_NUM]) + '.json' 57 | file_path = os.path.join('source', 'data', 'map', map_file) 58 | f = open(file_path) 59 | self.map_data = json.load(f) 60 | f.close() 61 | 62 | def setup_background(self): 63 | self.background = tool.GFX['background'] 64 | self.bg_rect = self.background.get_rect() 65 | self.background = pg.transform.scale(self.background, 66 | (int(self.bg_rect.width*c.BACKGROUND_MULTIPLER), 67 | int(self.bg_rect.height*c.BACKGROUND_MULTIPLER))) 68 | self.bg_rect = self.background.get_rect() 69 | self.bg_rect.y = -40 70 | 71 | def setup_buttons(self): 72 | self.buttons = [] 73 | self.buttons.append(button.Button(5, c.BUTTON_HEIGHT, c.NEXT_BUTTON)) 74 | self.buttons.append(button.Button(70, c.BUTTON_HEIGHT, c.REPLAY_BUTTON)) 75 | 76 | def setup_sling(self): 77 | rect_list = [(50, 0, 70, 200), (0, 0, 60, 200)] 78 | 79 | image = tool.get_image(tool.GFX['sling'], *rect_list[0], c.BLACK, 1) 80 | self.sling1_image = image 81 | self.sling1_rect = image.get_rect() 82 | self.sling1_rect.x = 138 83 | self.sling1_rect.y = 420 84 | 85 | image = tool.get_image(tool.GFX['sling'], *rect_list[1], c.BLACK, 1) 86 | self.sling2_image = image 87 | self.sling2_rect = image.get_rect() 88 | self.sling2_rect.x = 120 89 | self.sling2_rect.y = 420 90 | 91 | self.sling_click = False 92 | self.mouse_distance = 0 93 | self.sling_angle = 0 94 | 95 | def setup_birds(self): 96 | self.birds = [] 97 | y = c.GROUND_HEIGHT 98 | 99 | for i, data in enumerate(self.map_data[c.BIRDS]): 100 | x = 120 - (i*35) 101 | tmp = bird.create_bird(data[c.TYPE], x, y) 102 | if tmp: 103 | self.birds.append(tmp) 104 | self.bird_path = [] 105 | self.bird_old_path = [] 106 | self.active_bird = None 107 | self.select_bird() 108 | 109 | def setup_pigs(self): 110 | for data in self.map_data[c.PIGS]: 111 | tmp = pig.create_pig(data[c.TYPE], data['x'], data['y']) 112 | if tmp: 113 | self.physics.add_pig(tmp) 114 | 115 | def setup_blocks(self): 116 | for data in self.map_data[c.BLOCKS]: 117 | if c.DIRECTION in data: 118 | direction = data[c.DIRECTION] 119 | else: 120 | direction = 0 121 | tmp = block.create_block(data['x'], data['y'], data[c.MATERIAL], 122 | data[c.SHAPE], data[c.TYPE], direction) 123 | if tmp: 124 | self.physics.add_block(tmp) 125 | 126 | def update(self, surface, current_time, mouse_pos, mouse_pressed): 127 | self.game_info[c.CURRENT_TIME] = self.current_time = current_time 128 | self.handle_states(mouse_pos, mouse_pressed) 129 | self.check_game_state() 130 | self.draw(surface) 131 | 132 | def handle_states(self, mouse_pos, mouse_pressed): 133 | if self.state == c.IDLE: 134 | self.handle_sling(mouse_pos, mouse_pressed) 135 | elif self.state == c.ATTACK: 136 | if self.active_bird.state == c.DEAD: 137 | self.active_bird = None 138 | self.select_bird() 139 | self.swith_bird_path() 140 | self.state = c.IDLE 141 | elif self.state == c.OVER: 142 | if self.over_timer == 0: 143 | self.over_timer = self.current_time 144 | 145 | for bird in self.birds: 146 | bird.update(self.game_info, self, mouse_pressed) 147 | self.physics.update(self.game_info, self, mouse_pressed) 148 | 149 | self.check_button_click(mouse_pos, mouse_pressed) 150 | 151 | def select_bird(self): 152 | if len(self.birds) > 0: 153 | self.active_bird = self.birds[0] 154 | self.active_bird.update_position(130, 426) 155 | 156 | def handle_sling(self, mouse_pos, mouse_pressed): 157 | if not mouse_pressed: 158 | if self.sling_click: 159 | self.sling_click = False 160 | xo = 154 161 | yo = 444 162 | self.physics.add_bird(self.active_bird, self.mouse_distance, 163 | self.sling_angle, xo, yo) 164 | self.active_bird.set_attack() 165 | self.birds.remove(self.active_bird) 166 | self.physics.enable_check_collide() 167 | self.state = c.ATTACK 168 | elif not self.sling_click: 169 | if mouse_pos: 170 | mouse_x, mouse_y = mouse_pos 171 | if (mouse_x > 100 and mouse_x < 250 and 172 | mouse_y > 370 and mouse_y < 550): 173 | self.sling_click = True 174 | 175 | def draw_sling_and_active_bird(self, surface): 176 | sling_x, sling_y = 135, 450 177 | sling2_x, sling2_y = 160, 450 178 | rope_length = 90 179 | bigger_rope = 102 180 | 181 | if self.sling_click: 182 | mouse_x, mouse_y = pg.mouse.get_pos() 183 | v = vector((sling_x, sling_y), (mouse_x, mouse_y)) 184 | uv_x, uv_y = unit_vector(v) 185 | mouse_distance = tool.distance(sling_x, sling_y, mouse_x, mouse_y) 186 | pu = (uv_x * rope_length + sling_x, uv_y * rope_length + sling_y) 187 | 188 | if mouse_distance > rope_length: 189 | mouse_distance = rope_length 190 | pux, puy = pu 191 | pux -= 20 192 | puy -= 20 193 | pul = pux, puy 194 | pu2 = (uv_x * bigger_rope + sling_x, uv_y * bigger_rope + sling_y) 195 | pg.draw.line(surface, (0, 0, 0), (sling2_x, sling2_y), pu2, 5) 196 | self.active_bird.update_position(pux, puy) 197 | self.active_bird.draw(surface) 198 | pg.draw.line(surface, (0, 0, 0), (sling_x, sling_y), pu2, 5) 199 | 200 | else: 201 | mouse_distance += 10 202 | pu3 = (uv_x * mouse_distance + sling_x, uv_y * mouse_distance + sling_y) 203 | pg.draw.line(surface, (0, 0, 0), (sling2_x, sling2_y), pu3, 5) 204 | self.active_bird.update_position(mouse_x - 20, mouse_y - 20) 205 | self.active_bird.draw(surface) 206 | pg.draw.line(surface, (0, 0, 0), (sling_x, sling_y), pu3, 5) 207 | 208 | # Angle of impulse 209 | dy = mouse_y - sling_y 210 | dx = mouse_x - sling_x 211 | if dx == 0: 212 | dx = 0.00000000000001 213 | self.sling_angle = math.atan((float(dy))/dx) 214 | 215 | if mouse_x < sling_x + 5: 216 | self.mouse_distance = mouse_distance 217 | else: 218 | self.mouse_distance = -mouse_distance 219 | else: 220 | pg.draw.line(surface, (0, 0, 0), (sling_x, sling_y-8), (sling2_x, sling2_y-7), 5) 221 | if self.active_bird.state == c.IDLE: 222 | self.active_bird.draw(surface) 223 | 224 | def check_button_click(self, mouse_pos, mouse_pressed): 225 | if mouse_pressed and mouse_pos: 226 | for button in self.buttons: 227 | if button.check_mouse_click(mouse_pos): 228 | if button.name == c.NEXT_BUTTON: 229 | self.game_info[c.LEVEL_NUM] += 1 230 | self.reset() 231 | elif button.name == c.REPLAY_BUTTON: 232 | self.reset() 233 | 234 | def update_score(self, score): 235 | self.score += score 236 | 237 | def check_victory(self): 238 | if len(self.physics.pigs) > 0: 239 | return False 240 | return True 241 | 242 | def check_lose(self): 243 | if len(self.birds) == 0 and len(self.physics.birds) == 0: 244 | return True 245 | return False 246 | 247 | def check_game_state(self): 248 | if self.state == c.OVER: 249 | if (self.current_time - self.over_timer) > 2000: 250 | self.done = True 251 | elif self.check_victory(): 252 | self.game_info[c.LEVEL_NUM] += 1 253 | self.update_score(len(self.birds) * 10000) 254 | self.game_info[c.SCORE] = self.score 255 | self.next = c.LEVEL 256 | self.state = c.OVER 257 | elif self.check_lose(): 258 | self.next = c.LEVEL 259 | self.state = c.OVER 260 | 261 | def swith_bird_path(self): 262 | self.bird_old_path = self.bird_path.copy() 263 | self.bird_path = [] 264 | 265 | def draw_bird_path(self, surface, path): 266 | for i, pos in enumerate(path): 267 | if i % 3 == 0: 268 | size = 4 269 | elif i % 3 == 1: 270 | size = 5 271 | else: 272 | size = 6 273 | pg.draw.circle(surface, c.WHITE, pos, size, 0) 274 | 275 | def draw(self, surface): 276 | surface.fill(c.GRASS_GREEN) 277 | surface.blit(self.background, self.bg_rect) 278 | for button in self.buttons: 279 | button.draw(surface) 280 | 281 | score_font = bold_font.render("SCORE:", 1, c.WHITE) 282 | number_font = bold_font.render(str(self.score), 1, c.WHITE) 283 | surface.blit(score_font, (1020, c.BUTTON_HEIGHT)) 284 | surface.blit(number_font, (1120, c.BUTTON_HEIGHT)) 285 | 286 | self.draw_bird_path(surface, self.bird_old_path) 287 | self.draw_bird_path(surface, self.bird_path) 288 | 289 | surface.blit(self.sling1_image, self.sling1_rect) 290 | self.draw_sling_and_active_bird(surface) 291 | for bird in self.birds: 292 | bird.draw(surface) 293 | 294 | surface.blit(self.sling2_image, self.sling2_rect) 295 | 296 | self.physics.draw(surface) -------------------------------------------------------------------------------- /source/tool.py: -------------------------------------------------------------------------------- 1 | __author__ = 'marble_xu' 2 | 3 | import os 4 | import json 5 | from abc import abstractmethod 6 | import pygame as pg 7 | from . import constants as c 8 | 9 | class State(): 10 | def __init__(self): 11 | self.start_time = 0.0 12 | self.current_time = 0.0 13 | self.done = False 14 | self.next = None 15 | self.persist = {} 16 | 17 | @abstractmethod 18 | def startup(self, current_time, persist): 19 | '''abstract method''' 20 | 21 | def cleanup(self): 22 | self.done = False 23 | return self.persist 24 | 25 | @abstractmethod 26 | def update(sefl, surface, keys, current_time): 27 | '''abstract method''' 28 | 29 | class Control(): 30 | def __init__(self): 31 | self.screen = pg.display.get_surface() 32 | self.done = False 33 | self.clock = pg.time.Clock() 34 | self.fps = 60 35 | self.keys = pg.key.get_pressed() 36 | self.mouse_pos = None 37 | self.mouse_pressed = False 38 | self.current_time = 0.0 39 | self.state_dict = {} 40 | self.state_name = None 41 | self.state = None 42 | self.game_info = {c.CURRENT_TIME:0.0, 43 | c.LEVEL_NUM:c.START_LEVEL_NUM, 44 | c.SCORE:0} 45 | 46 | def setup_states(self, state_dict, start_state): 47 | self.state_dict = state_dict 48 | self.state_name = start_state 49 | self.state = self.state_dict[self.state_name] 50 | self.state.startup(self.current_time, self.game_info) 51 | 52 | def update(self): 53 | self.current_time = pg.time.get_ticks() 54 | if self.state.done: 55 | self.flip_state() 56 | self.state.update(self.screen, self.current_time, self.mouse_pos, self.mouse_pressed) 57 | self.mouse_pos = None 58 | 59 | def flip_state(self): 60 | previous, self.state_name = self.state_name, self.state.next 61 | persist = self.state.cleanup() 62 | self.state = self.state_dict[self.state_name] 63 | self.state.startup(self.current_time, persist) 64 | 65 | def event_loop(self): 66 | for event in pg.event.get(): 67 | if event.type == pg.QUIT: 68 | self.done = True 69 | elif event.type == pg.KEYDOWN: 70 | self.keys = pg.key.get_pressed() 71 | elif event.type == pg.KEYUP: 72 | self.keys = pg.key.get_pressed() 73 | elif event.type == pg.MOUSEBUTTONDOWN and event.button == 1: 74 | self.mouse_pos = pg.mouse.get_pos() 75 | self.mouse_pressed = True 76 | elif event.type == pg.MOUSEBUTTONUP and event.button == 1: 77 | self.mouse_pressed = False 78 | 79 | def main(self): 80 | while not self.done: 81 | self.event_loop() 82 | self.update() 83 | pg.display.update() 84 | self.clock.tick(self.fps) 85 | if c.DEBUG: 86 | pg.display.set_caption("pos: " + str(pg.mouse.get_pos())) 87 | print('game over') 88 | 89 | def distance(xo, yo, x, y): 90 | """distance between points""" 91 | dx = x - xo 92 | dy = y - yo 93 | d = ((dx ** 2) + (dy ** 2)) ** 0.5 94 | return d 95 | 96 | def get_image(sheet, x, y, width, height, colorkey, scale): 97 | image = pg.Surface([width, height]) 98 | rect = image.get_rect() 99 | 100 | image.blit(sheet, (0, 0), (x, y, width, height)) 101 | image.set_colorkey(colorkey) 102 | image = pg.transform.scale(image, 103 | (int(rect.width*scale), 104 | int(rect.height*scale))) 105 | return image 106 | 107 | def load_all_gfx(directory, colorkey=(255,0,255), accept=('.png', '.jpg', '.bmp', '.gif')): 108 | graphics = {} 109 | for pic in os.listdir(directory): 110 | name, ext = os.path.splitext(pic) 111 | if ext.lower() in accept: 112 | img = pg.image.load(os.path.join(directory, pic)) 113 | if img.get_alpha(): 114 | img = img.convert_alpha() 115 | else: 116 | img = img.convert() 117 | img.set_colorkey(colorkey) 118 | graphics[name] = img 119 | return graphics 120 | 121 | pg.init() 122 | pg.display.set_caption(c.ORIGINAL_CAPTION) 123 | SCREEN = pg.display.set_mode(c.SCREEN_SIZE) 124 | 125 | GFX = load_all_gfx(os.path.join("resources","graphics")) --------------------------------------------------------------------------------