├── .gitignore ├── color_change.py ├── cursors ├── cursor.py ├── cursor_images.png └── example.py ├── drag_text.py ├── eight_dir_move.py ├── eight_dir_movement_adjusted.py ├── four_direction_movement ├── eight_dir_move_four_dir_anim.py ├── four_dir_anim.py ├── four_dir_mask.py ├── four_dir_naive.py ├── four_dir_obstacles.py ├── four_dir_obstacles_test.py ├── shader.png └── skelly.png ├── platforming ├── base_face.png ├── fall_mask.py ├── fall_rect.py ├── fall_rotate.py ├── just_face.png ├── moving_platforms.py ├── moving_platforms_ease.py ├── shader.png └── smallface.png ├── punch_a_hole ├── frac.jpg └── punch.py ├── readme.txt ├── resizable ├── resizable_aspect_ratio.py └── resizable_screen.py ├── rotation_animation ├── asteroid_simple.png └── rotate_animate.py ├── tank_turret ├── tank.py ├── turret.png ├── turret_gamepad.py └── turret_mouse.py └── topdown_scrolling ├── pond.png ├── scrolling.py ├── scrolling_mouse.py └── smallface.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | __pycache__ 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /color_change.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | This example serves as a basics introduction. 5 | Change the screen color with a mouse click. 6 | 7 | This program is intentionally over-commented to serve as an introduction for 8 | those with no knowledge of pygame. More advanced examples will not adhere to 9 | this convention. 10 | 11 | -Written by Sean J. McKiernan 'Mekire' 12 | """ 13 | 14 | import os # Used for os.environ. 15 | import sys # Used for sys.exit. 16 | import random # Used for random.randint. 17 | 18 | import pygame as pg # I abbreviate pygame as pg for brevity. 19 | 20 | 21 | CAPTION = "Click to Change My Color" 22 | SCREEN_SIZE = (500, 500) 23 | 24 | 25 | class App(object): 26 | """ 27 | This is the main class for our application. 28 | It manages our event and game loops. 29 | """ 30 | def __init__(self): 31 | """ 32 | Get a reference to the screen (created in main); define necessary 33 | attributes; and set our starting color to black. 34 | """ 35 | self.screen = pg.display.get_surface() # Get reference to the display. 36 | self.clock = pg.time.Clock() # Create a clock to restrict framerate. 37 | self.fps = 60 # Define your max framerate. 38 | self.done = False # A flag to tell our game when to quit. 39 | self.keys = pg.key.get_pressed() # All the keys currently held. 40 | self.color = pg.Color("black") # The screen will start as black. 41 | 42 | def event_loop(self): 43 | """ 44 | Our event loop; called once every frame. Only things relevant to 45 | processing specific events should be here. It should not 46 | contain any drawing/rendering code. 47 | """ 48 | for event in pg.event.get(): # Check each event in the event queue. 49 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 50 | # If the user presses escape or closes the window we're done. 51 | self.done = True 52 | elif event.type == pg.MOUSEBUTTONDOWN: 53 | # If the user clicks the screen, change the color. 54 | self.color = [random.randint(0, 255) for _ in range(3)] 55 | elif event.type in (pg.KEYUP, pg.KEYDOWN): 56 | # Update keys when a key is pressed or released. 57 | self.keys = pg.key.get_pressed() 58 | 59 | def main_loop(self): 60 | """ 61 | Our game loop. It calls the event loop; updates the display; 62 | restricts the framerate; and loops. 63 | """ 64 | while not self.done: 65 | self.event_loop() # Run the event loop every frame. 66 | self.screen.fill(self.color) # Fill the screen with the new color. 67 | pg.display.update() # Make updates to screen every frame. 68 | self.clock.tick(self.fps) # Restrict framerate of program. 69 | 70 | 71 | def main(): 72 | """ 73 | Prepare our environment, create a display, and start the program. 74 | """ 75 | os.environ['SDL_VIDEO_CENTERED'] = '1' # Center the window (optional). 76 | pg.init() # Initialize Pygame. 77 | pg.display.set_caption(CAPTION) # Set the caption for the window. 78 | pg.display.set_mode(SCREEN_SIZE) # Prepare the screen. 79 | App().main_loop() 80 | pg.quit() 81 | sys.exit() 82 | 83 | 84 | if __name__ == "__main__": 85 | main() 86 | -------------------------------------------------------------------------------- /cursors/cursor.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script includes a function that can compile cursor data from an image. 3 | 4 | There are both advantages and disadvantages to using a real cursor over an 5 | image that follows the mouse. See the documentation at the top of the example 6 | script for more details. 7 | 8 | -Sean McKiernan 9 | """ 10 | 11 | import pygame as pg 12 | 13 | 14 | def cursor_from_image(image,size,hotspot,location=(0,0),flip=False): 15 | """ 16 | This function's return value is of the form accepted by 17 | pg.mouse.set_cursor() (passed using the *args syntax). The argument image 18 | is an already loaded image surface containing your desired cursor; size is 19 | a single integer corresponding to the width of the cursor (must be a 20 | multiple of 8); hotspot is a 2-tuple representing the exact point in your 21 | cursor that will represent the mouse position; location is a 2-tuple for 22 | where your cursor is located on the passed in image. Setting flip to True 23 | will create the cursor with colors opposite to the source image. 24 | 25 | Color in image to color in cursor defaults: 26 | Black ( 0, 0, 0) ---> Black 27 | White (255, 255, 255) ---> White 28 | Cyan ( 0, 255, 255) ---> Xor (only available on certain systems) 29 | Any Other Color ---------> Transparent 30 | """ 31 | if size%8: 32 | raise ValueError("Size must be a multiple of 8.") 33 | compile_args = (".", "X", "o") if flip else ("X", ".", "o") 34 | colors = {( 0, 0, 0,255) : ".", 35 | (255,255,255,255) : "X", 36 | ( 0,255,255,255) : "o"} 37 | cursor_string = [] 38 | for j in range(size): 39 | this_row = [] 40 | for i in range(size): 41 | where = (i+location[0], j+location[1]) 42 | pixel = tuple(image.get_at(where)) 43 | this_row.append(colors.get(pixel, " ")) 44 | cursor_string.append("".join(this_row)) 45 | xors,ands = pg.cursors.compile(cursor_string, *compile_args) 46 | size = size, size 47 | return size, hotspot, xors, ands 48 | -------------------------------------------------------------------------------- /cursors/cursor_images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/cursors/cursor_images.png -------------------------------------------------------------------------------- /cursors/example.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script demonstrates the usage of the cursor_from_image function. 3 | There are benefits to using a genuine hardware cursor over an image that 4 | follows the mouse. Real cursors respond much faster as they are not drawn 5 | directly by your program logic. If your framerate drops, the cursor will still 6 | operate unaffected. 7 | 8 | There are however two disadvantages; we cannot use color, and we 9 | cannot use a cursor larger than 32x32 pixels (doing so results in 10 | mouse.set_cursor acting as a blit which defeats the point). If you are 11 | wishing to implement these features then an image that follows the mouse is 12 | indeed what you want. 13 | 14 | Controls: 15 | With the grab cursor selected, clicking will switch the cursor between the 16 | grabbing and non-grabbing versions. The keys 0-9 and q,w,e,r will change 17 | between all the available cursors. 18 | 19 | -Sean McKiernan 20 | """ 21 | 22 | import os 23 | import sys 24 | import pygame as pg 25 | 26 | from cursor import cursor_from_image 27 | 28 | 29 | SCREEN_SIZE = (500, 500) 30 | BACKGROUND_COLOR = (50, 50, 60) 31 | CAPTION = "Custom Mouse Cursors" 32 | 33 | #This dictionary helps us change the cursor with keyboard input. 34 | CURSOR_TYPES = {"1" : "default", 35 | "2" : "cross", 36 | "3" : "dropper", 37 | "4" : "knight", 38 | "5" : "open", 39 | "6" : "diamond", 40 | "7" : "broken_x", 41 | "8" : "tri_left", 42 | "9" : "tri_right", 43 | "0" : "arrow", 44 | "q" : "thick_arrow", 45 | "w" : "sizer_xy", 46 | "e" : "sizer_x", 47 | "r" : "sizer_y"} 48 | 49 | 50 | class Control(object): 51 | """Not a hair out of place.""" 52 | def __init__(self): 53 | """ 54 | Prepare the essentials; create our dictionary of cursors; and set 55 | the initial cursor as desired. 56 | """ 57 | pg.init() 58 | os.environ["SDL_VIDEO_CENTERED"] = "TRUE" 59 | pg.display.set_caption(CAPTION) 60 | self.screen = pg.display.set_mode(SCREEN_SIZE) 61 | self.screen_rect = self.screen.get_rect() 62 | self.done = False 63 | self.keys = pg.key.get_pressed() 64 | self.clock = pg.time.Clock() 65 | self.fps = 60.0 66 | self.cursor_dict = self.make_cursor_dict() 67 | self.cursor = self.change_cursor("open") 68 | 69 | def make_cursor_dict(self): 70 | """ 71 | Create a dictionary that holds cursor data for all of our cursors, 72 | including the default cursor. 73 | """ 74 | cursor_image = pg.image.load("cursor_images.png").convert() 75 | thick = pg.cursors.compile(pg.cursors.thickarrow_strings) 76 | sizer_x = pg.cursors.compile(pg.cursors.sizer_x_strings) 77 | sizer_y = pg.cursors.compile(pg.cursors.sizer_y_strings) 78 | sizer_xy = pg.cursors.compile(pg.cursors.sizer_xy_strings) 79 | cursors = { 80 | "cross" : cursor_from_image(cursor_image, 16, (8,8)), 81 | "dropper" : cursor_from_image(cursor_image, 16, (0,15), (16,0)), 82 | "open" : cursor_from_image(cursor_image, 24, (12,12), (32,0)), 83 | "closed" : cursor_from_image(cursor_image, 24, (12,12), (32,24)), 84 | "knight" : cursor_from_image(cursor_image, 32, (16,16), (0,16)), 85 | "diamond" : pg.cursors.diamond, 86 | "broken_x" : pg.cursors.broken_x, 87 | "tri_left" : pg.cursors.tri_left, 88 | "tri_right": pg.cursors.tri_right, 89 | "arrow" : pg.cursors.arrow, 90 | "thick_arrow" : ((24,24), (0,0), thick[0], thick[1]), 91 | "sizer_xy" : ((24,16), (6,6), sizer_xy[0], sizer_xy[1]), 92 | "sizer_x" : ((24,16), (9,5), sizer_x[0], sizer_x[1]), 93 | "sizer_y" : ((16,24), (5,9), sizer_y[0], sizer_y[1]), 94 | "default" : pg.mouse.get_cursor()} 95 | return cursors 96 | 97 | def change_cursor(self, name): 98 | """ 99 | Set the cursor to the value corresponding to name in the 100 | cursor_dict. Return the cursor_name argument as a convenience. 101 | """ 102 | pg.mouse.set_cursor(*self.cursor_dict[name]) 103 | template = "{} - Current cursor: {}" 104 | pg.display.set_caption(template.format(CAPTION, name.capitalize())) 105 | return name 106 | 107 | def event_loop(self): 108 | """ 109 | Change between open and closed hands if using the grab cursor. 110 | Change to other cursors with the keys 0-9 and q,w,e,r. 111 | """ 112 | for event in pg.event.get(): 113 | self.keys = pg.key.get_pressed() 114 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 115 | self.done = True 116 | elif event.type == pg.MOUSEBUTTONDOWN and event.button == 1: 117 | if self.cursor == "open": 118 | self.cursor = self.change_cursor("closed") 119 | elif event.type == pg.MOUSEBUTTONUP and event.button == 1: 120 | if self.cursor == "closed": 121 | self.cursor = self.change_cursor("open") 122 | elif event.type == pg.KEYDOWN: 123 | key = event.unicode 124 | if key in CURSOR_TYPES: 125 | self.cursor = self.change_cursor(CURSOR_TYPES[key]) 126 | 127 | def main_loop(self): 128 | """The bare minimum for a functioning main loop.""" 129 | while not self.done: 130 | self.event_loop() 131 | self.screen.fill(BACKGROUND_COLOR) 132 | pg.display.update() 133 | self.clock.tick(self.fps) 134 | 135 | 136 | def main(): 137 | """Off we go.""" 138 | Control().main_loop() 139 | pg.quit() 140 | sys.exit() 141 | 142 | 143 | if __name__ == "__main__": 144 | main() 145 | -------------------------------------------------------------------------------- /drag_text.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | A very simple example showing how to drag an item with the mouse. 5 | Dragging in this example uses relative mouse movement. 6 | 7 | -Written by Sean J. McKiernan 'Mekire' 8 | """ 9 | 10 | import os 11 | import sys 12 | 13 | import pygame as pg 14 | 15 | 16 | CAPTION = "Drag the Red Square" 17 | SCREEN_SIZE = (1000, 600) 18 | 19 | 20 | class Character(object): 21 | """ 22 | A class to represent our lovable red sqaure. 23 | """ 24 | SIZE = (150, 150) 25 | 26 | def __init__(self, pos): 27 | """ 28 | The argument pos corresponds to the center of our rectangle. 29 | """ 30 | self.rect = pg.Rect((0,0), Character.SIZE) 31 | self.rect.center = pos 32 | self.text, self.text_rect = self.setup_font() 33 | self.click = False 34 | 35 | def setup_font(self): 36 | """ 37 | If your text doesn't change it is best to render once, rather than 38 | re-render every time you want the text. Rendering text every frame is 39 | a common source of bottlenecks in beginner programs. 40 | """ 41 | font = pg.font.SysFont('timesnewroman', 30) 42 | message = "I'm a red square" 43 | label = font.render(message, True, pg.Color("white")) 44 | label_rect = label.get_rect() 45 | return label, label_rect 46 | 47 | def check_click(self, pos): 48 | """ 49 | This function is called from the event loop to check if a click 50 | overlaps with the player rect. 51 | pygame.mouse.get_rel must be called on an initial hit so that 52 | subsequent calls give the correct relative offset. 53 | """ 54 | if self.rect.collidepoint(pos): 55 | self.click = True 56 | pg.mouse.get_rel() 57 | 58 | def update(self, screen_rect): 59 | """ 60 | If the square is currently clicked, update its position based on the 61 | relative mouse movement. Clamp the rect to the screen. 62 | """ 63 | if self.click: 64 | self.rect.move_ip(pg.mouse.get_rel()) 65 | self.rect.clamp_ip(screen_rect) 66 | self.text_rect.center = (self.rect.centerx, self.rect.centery+90) 67 | 68 | def draw(self, surface): 69 | """ 70 | Blit image and text to the target surface. 71 | """ 72 | surface.fill(pg.Color("red"), self.rect) 73 | surface.blit(self.text, self.text_rect) 74 | 75 | 76 | class App(object): 77 | """ 78 | A class to manage our event, game loop, and overall program flow. 79 | """ 80 | def __init__(self): 81 | """ 82 | Get a reference to the screen (created in main); define necessary 83 | attributes; and create our player (draggable rect). 84 | """ 85 | self.screen = pg.display.get_surface() 86 | self.screen_rect = self.screen.get_rect() 87 | self.clock = pg.time.Clock() 88 | self.fps = 60 89 | self.done = False 90 | self.keys = pg.key.get_pressed() 91 | self.player = Character(self.screen_rect.center) 92 | 93 | def event_loop(self): 94 | """ 95 | This is the event loop for the whole program. 96 | Regardless of the complexity of a program, there should never be a need 97 | to have more than one event loop. 98 | """ 99 | for event in pg.event.get(): 100 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 101 | self.done = True 102 | elif event.type == pg.MOUSEBUTTONDOWN and event.button == 1: 103 | self.player.check_click(event.pos) 104 | elif event.type == pg.MOUSEBUTTONUP and event.button == 1: 105 | self.player.click = False 106 | elif event.type in (pg.KEYUP, pg.KEYDOWN): 107 | self.keys = pg.key.get_pressed() 108 | 109 | def render(self): 110 | """ 111 | All drawing should be found here. 112 | This is the only place that pygame.display.update() should be found. 113 | """ 114 | self.screen.fill(pg.Color("black")) 115 | self.player.draw(self.screen) 116 | pg.display.update() 117 | 118 | def main_loop(self): 119 | """ 120 | This is the game loop for the entire program. 121 | Like the event_loop, there should not be more than one game_loop. 122 | """ 123 | while not self.done: 124 | self.event_loop() 125 | self.player.update(self.screen_rect) 126 | self.render() 127 | self.clock.tick(self.fps) 128 | 129 | 130 | def main(): 131 | """ 132 | Prepare our environment, create a display, and start the program. 133 | """ 134 | os.environ['SDL_VIDEO_CENTERED'] = '1' 135 | pg.init() 136 | pg.display.set_caption(CAPTION) 137 | pg.display.set_mode(SCREEN_SIZE) 138 | App().main_loop() 139 | pg.quit() 140 | sys.exit() 141 | 142 | 143 | if __name__ == "__main__": 144 | main() 145 | -------------------------------------------------------------------------------- /eight_dir_move.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | This script implements a basic sprite that can move in all 8 directions. 5 | 6 | -Written by Sean J. McKiernan 'Mekire' 7 | """ 8 | 9 | import os 10 | import sys 11 | 12 | import pygame as pg 13 | 14 | 15 | CAPTION = "Move me with the Arrow Keys" 16 | SCREEN_SIZE = (500, 500) 17 | 18 | TRANSPARENT = (0, 0, 0, 0) 19 | 20 | # This global constant serves as a very useful convenience for me. 21 | DIRECT_DICT = {pg.K_LEFT : (-1, 0), 22 | pg.K_RIGHT : ( 1, 0), 23 | pg.K_UP : ( 0,-1), 24 | pg.K_DOWN : ( 0, 1)} 25 | 26 | 27 | class Player(object): 28 | """ 29 | This class will represent our user controlled character. 30 | """ 31 | SIZE = (100, 100) 32 | 33 | def __init__(self, pos, speed): 34 | """ 35 | The pos argument is a tuple for the center of the player (x,y); 36 | speed is given in pixels/frame. 37 | """ 38 | self.rect = pg.Rect((0,0), Player.SIZE) 39 | self.rect.center = pos 40 | self.speed = speed 41 | self.image = self.make_image() 42 | 43 | def make_image(self): 44 | """ 45 | Creates our hero (a red circle with a black outline). 46 | """ 47 | image = pg.Surface(self.rect.size).convert_alpha() 48 | image.fill(TRANSPARENT) 49 | image_rect = image.get_rect() 50 | pg.draw.ellipse(image, pg.Color("black"), image_rect) 51 | pg.draw.ellipse(image, pg.Color("red"), image_rect.inflate(-12, -12)) 52 | return image 53 | 54 | def update(self, keys, screen_rect): 55 | """ 56 | Updates our player appropriately every frame. 57 | """ 58 | for key in DIRECT_DICT: 59 | if keys[key]: 60 | self.rect.x += DIRECT_DICT[key][0]*self.speed 61 | self.rect.y += DIRECT_DICT[key][1]*self.speed 62 | self.rect.clamp_ip(screen_rect) # Keep player on screen. 63 | 64 | def draw(self, surface): 65 | """ 66 | Blit image to the target surface. 67 | """ 68 | surface.blit(self.image, self.rect) 69 | 70 | 71 | class App(object): 72 | """ 73 | A class to manage our event, game loop, and overall program flow. 74 | """ 75 | def __init__(self): 76 | """ 77 | Get a reference to the display surface; set up required attributes; 78 | and create a Player instance. 79 | """ 80 | self.screen = pg.display.get_surface() 81 | self.screen_rect = self.screen.get_rect() 82 | self.clock = pg.time.Clock() 83 | self.fps = 60 84 | self.done = False 85 | self.keys = pg.key.get_pressed() 86 | self.player = Player(self.screen_rect.center, 5) 87 | 88 | def event_loop(self): 89 | """ 90 | One event loop. Never cut your game off from the event loop. 91 | Your OS may decide your program has hung if the event queue is not 92 | accessed for a prolonged period of time. 93 | """ 94 | for event in pg.event.get(): 95 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 96 | self.done = True 97 | elif event.type in (pg.KEYUP, pg.KEYDOWN): 98 | self.keys = pg.key.get_pressed() 99 | 100 | def render(self): 101 | """ 102 | Perform all necessary drawing and update the screen. 103 | """ 104 | self.screen.fill(pg.Color("white")) 105 | self.player.draw(self.screen) 106 | pg.display.update() 107 | 108 | def main_loop(self): 109 | """ 110 | One game loop. Simple and clean. 111 | """ 112 | while not self.done: 113 | self.event_loop() 114 | self.player.update(self.keys, self.screen_rect) 115 | self.render() 116 | self.clock.tick(self.fps) 117 | 118 | 119 | def main(): 120 | """ 121 | Prepare our environment, create a display, and start the program. 122 | """ 123 | os.environ['SDL_VIDEO_CENTERED'] = '1' 124 | pg.init() 125 | pg.display.set_caption(CAPTION) 126 | pg.display.set_mode(SCREEN_SIZE) 127 | App().main_loop() 128 | pg.quit() 129 | sys.exit() 130 | 131 | 132 | if __name__ == "__main__": 133 | main() 134 | -------------------------------------------------------------------------------- /eight_dir_movement_adjusted.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | Script is identical to eight_dir_move with an adjustment so that the player 5 | moves at the correct speed when travelling at an angle. The script is also 6 | changed to use a variable time step to update the player. 7 | This means that speed is now in pixels per second, rather than pixels per 8 | frame. The advantage of this is that the game will always update at the same 9 | speed, regardless of any frame loss. 10 | 11 | -Written by Sean J. McKiernan 'Mekire' 12 | """ 13 | 14 | import os 15 | import sys 16 | import math 17 | 18 | import pygame as pg 19 | 20 | 21 | CAPTION = "Move me with the Arrow Keys" 22 | SCREEN_SIZE = (500, 500) 23 | 24 | TRANSPARENT = (0, 0, 0, 0) 25 | 26 | DIRECT_DICT = {pg.K_LEFT : (-1, 0), 27 | pg.K_RIGHT : ( 1, 0), 28 | pg.K_UP : ( 0,-1), 29 | pg.K_DOWN : ( 0, 1)} 30 | 31 | # X and Y Component magnitude when moving at 45 degree angles 32 | ANGLE_UNIT_SPEED = math.sqrt(2)/2 33 | 34 | 35 | class Player(object): 36 | """ 37 | This class will represent our user controlled character. 38 | """ 39 | SIZE = (100, 100) 40 | 41 | def __init__(self, pos, speed): 42 | """ 43 | The pos argument is a tuple for the center of the player (x,y); 44 | speed is given in pixels/second. 45 | """ 46 | self.rect = pg.Rect((0,0), Player.SIZE) 47 | self.rect.center = pos 48 | self.true_pos = list(self.rect.center) 49 | self.speed = speed 50 | self.image = self.make_image() 51 | 52 | def make_image(self): 53 | """ 54 | Creates our hero (a red circle/ellipse with a black outline). 55 | """ 56 | image = pg.Surface(self.rect.size).convert_alpha() 57 | image.fill(TRANSPARENT) 58 | image_rect = image.get_rect() 59 | pg.draw.ellipse(image, pg.Color("black"), image_rect) 60 | pg.draw.ellipse(image, pg.Color("red"), image_rect.inflate(-12, -12)) 61 | return image 62 | 63 | def update(self, screen_rect, keys, dt): 64 | """ 65 | Updates our player appropriately every frame. 66 | """ 67 | vector = [0, 0] 68 | for key in DIRECT_DICT: 69 | if keys[key]: 70 | vector[0] += DIRECT_DICT[key][0] 71 | vector[1] += DIRECT_DICT[key][1] 72 | factor = (ANGLE_UNIT_SPEED if all(vector) else 1) 73 | frame_speed = self.speed*factor*dt 74 | self.true_pos[0] += vector[0]*frame_speed 75 | self.true_pos[1] += vector[1]*frame_speed 76 | self.rect.center = self.true_pos 77 | if not screen_rect.contains(self.rect): 78 | self.rect.clamp_ip(screen_rect) 79 | self.true_pos = list(self.rect.center) 80 | 81 | def draw(self, surface): 82 | """ 83 | Draws the player to the target surface. 84 | """ 85 | surface.blit(self.image, self.rect) 86 | 87 | 88 | class App(object): 89 | """ 90 | A class to manage our event, game loop, and overall program flow. 91 | """ 92 | def __init__(self): 93 | """ 94 | Get a reference to the display surface; set up required attributes; 95 | and create a Player instance. 96 | """ 97 | self.screen = pg.display.get_surface() 98 | self.screen_rect = self.screen.get_rect() 99 | self.clock = pg.time.Clock() 100 | self.fps = 60 101 | self.done = False 102 | self.keys = pg.key.get_pressed() 103 | self.player = Player(self.screen_rect.center, 190) 104 | 105 | def event_loop(self): 106 | """ 107 | One event loop. Never cut your game off from the event loop. 108 | """ 109 | for event in pg.event.get(): 110 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 111 | self.done = True 112 | elif event.type in (pg.KEYUP, pg.KEYDOWN): 113 | self.keys = pg.key.get_pressed() 114 | 115 | def render(self): 116 | """ 117 | Perform all necessary drawing and update the screen. 118 | """ 119 | self.screen.fill(pg.Color("white")) 120 | self.player.draw(self.screen) 121 | pg.display.update() 122 | 123 | def main_loop(self): 124 | """ 125 | One game loop. Simple and clean. 126 | """ 127 | self.clock.tick(self.fps)/1000.0 128 | dt = 0.0 129 | while not self.done: 130 | self.event_loop() 131 | self.player.update(self.screen_rect, self.keys, dt) 132 | self.render() 133 | dt = self.clock.tick(self.fps)/1000.0 134 | 135 | 136 | def main(): 137 | """ 138 | Prepare our environment, create a display, and start the program. 139 | """ 140 | os.environ['SDL_VIDEO_CENTERED'] = '1' 141 | pg.init() 142 | pg.display.set_caption(CAPTION) 143 | pg.display.set_mode(SCREEN_SIZE) 144 | App().main_loop() 145 | pg.quit() 146 | sys.exit() 147 | 148 | 149 | if __name__ == "__main__": 150 | main() 151 | -------------------------------------------------------------------------------- /four_direction_movement/eight_dir_move_four_dir_anim.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is an example showing movement in 8-directions; but using image frames 3 | only in the four orthogonal directions. I am still using the direction stack 4 | even though it is a bit complex so that the player's frame matches the 5 | key held; not just the last key pressed. This example also uses a timestep for 6 | updating at a constant framerate, and has accurate 45 degree movement speed. 7 | 8 | -Written by Sean J. McKiernan 'Mekire' 9 | """ 10 | 11 | import os 12 | import sys 13 | import math 14 | import random 15 | import pygame as pg 16 | 17 | 18 | CAPTION = "8-Direction Movement w/ 4-Direction Animation" 19 | SCREEN_SIZE = (500, 500) 20 | BACKGROUND_COLOR = (40, 40, 40) 21 | TRANSPARENT = (0, 0, 0, 0) 22 | COLOR_KEY = (255, 0, 255) 23 | 24 | DIRECT_DICT = {pg.K_LEFT : (-1, 0), 25 | pg.K_RIGHT : ( 1, 0), 26 | pg.K_UP : ( 0,-1), 27 | pg.K_DOWN : ( 0, 1)} 28 | 29 | #X and Y Component magnitude when moving at 45 degree angles 30 | ANGLE_UNIT_SPEED = math.sqrt(2)/2 31 | 32 | 33 | class Player(pg.sprite.Sprite): 34 | """ 35 | This time we inherit from pygame.sprite.Sprite. We are going to take 36 | advantage of the sprite.Group collission functions (though as usual, doing 37 | all this without using pygame.sprite is not much more complicated). 38 | """ 39 | def __init__(self, rect, speed, direction=pg.K_RIGHT): 40 | """ 41 | Arguments are a rect representing the Player's location and 42 | dimension, the speed(in pixels/frame) of the Player, and the Player's 43 | starting direction (given as a key-constant). 44 | """ 45 | pg.sprite.Sprite.__init__(self) 46 | self.rect = pg.Rect(rect) 47 | self.remainder = [0, 0] #Adjust rect in integers; save remainders. 48 | self.mask = self.make_mask() 49 | self.speed = speed #Pixels per second; not pixels per frame. 50 | self.direction = direction 51 | self.old_direction = None #The Players previous direction every frame. 52 | self.direction_stack = [] #Held keys in the order they were pressed. 53 | self.redraw = False #Force redraw if needed. 54 | self.image = None 55 | self.frame = 0 56 | self.frames = self.get_frames() 57 | self.animate_timer = 0.0 58 | self.animate_fps = 7.0 59 | self.walkframes = [] 60 | self.walkframe_dict = self.make_frame_dict() 61 | self.adjust_images() 62 | 63 | def make_mask(self): 64 | """ 65 | Create a collision mask slightly smaller than our sprite so that 66 | the sprite's head can overlap obstacles; adding depth. 67 | """ 68 | mask_surface = pg.Surface(self.rect.size).convert_alpha() 69 | mask_surface.fill(TRANSPARENT) 70 | mask_surface.fill(pg.Color("white"), (10,20,30,30)) 71 | mask = pg.mask.from_surface(mask_surface) 72 | return mask 73 | 74 | def get_frames(self): 75 | """Get a list of all frames.""" 76 | sheet = SKEL_IMAGE 77 | indices = [[0,0], [1,0], [2,0], [3,0]] 78 | return get_images(sheet, indices, self.rect.size) 79 | 80 | def make_frame_dict(self): 81 | """ 82 | Create a dictionary of direction keys to frames. We can use 83 | transform functions to reduce the size of the sprite sheet needed. 84 | """ 85 | frames = {pg.K_LEFT : [self.frames[0], self.frames[1]], 86 | pg.K_RIGHT: [pg.transform.flip(self.frames[0], True, False), 87 | pg.transform.flip(self.frames[1], True, False)], 88 | pg.K_DOWN : [self.frames[3], 89 | pg.transform.flip(self.frames[3], True, False)], 90 | pg.K_UP : [self.frames[2], 91 | pg.transform.flip(self.frames[2], True, False)]} 92 | return frames 93 | 94 | def adjust_images(self): 95 | """Update the sprite's walkframes as the sprite's direction changes.""" 96 | if self.direction != self.old_direction: 97 | self.walkframes = self.walkframe_dict[self.direction] 98 | self.old_direction = self.direction 99 | self.redraw = True 100 | self.make_image() 101 | 102 | def make_image(self): 103 | """Update the sprite's animation as needed.""" 104 | now = pg.time.get_ticks() 105 | if self.redraw or now-self.animate_timer > 1000/self.animate_fps: 106 | self.frame = (self.frame+1)%len(self.walkframes) 107 | self.image = self.walkframes[self.frame] 108 | self.animate_timer = now 109 | if not self.image: 110 | self.image = self.walkframes[self.frame] 111 | self.redraw = False 112 | 113 | def add_direction(self, key): 114 | """Add a pressed direction key on the direction stack.""" 115 | if key in DIRECT_DICT: 116 | if key in self.direction_stack: 117 | self.direction_stack.remove(key) 118 | self.direction_stack.append(key) 119 | self.direction = self.direction_stack[-1] 120 | 121 | def pop_direction(self, key): 122 | """Pop a released key from the direction stack.""" 123 | if key in DIRECT_DICT: 124 | if key in self.direction_stack: 125 | self.direction_stack.remove(key) 126 | if self.direction_stack: 127 | self.direction = self.direction_stack[-1] 128 | 129 | def update(self, obstacles, dt): 130 | """Adjust the image and move as needed.""" 131 | vector = [0, 0] 132 | for key in self.direction_stack: 133 | vector[0] += DIRECT_DICT[key][0] 134 | vector[1] += DIRECT_DICT[key][1] 135 | factor = (ANGLE_UNIT_SPEED if all(vector) else 1) 136 | frame_speed = self.speed*factor*dt 137 | self.remainder[0] += vector[0]*frame_speed 138 | self.remainder[1] += vector[1]*frame_speed 139 | vector[0], self.remainder[0] = divfmod(self.remainder[0], 1) 140 | vector[1], self.remainder[1] = divfmod(self.remainder[1], 1) 141 | if vector != [0, 0]: 142 | self.adjust_images() 143 | self.movement(obstacles, vector[0], 0) 144 | self.movement(obstacles, vector[1], 1) 145 | 146 | def movement(self, obstacles, offset, i): 147 | """Move player and then check for collisions; adjust as necessary.""" 148 | self.rect[i] += offset 149 | collisions = pg.sprite.spritecollide(self, obstacles, False) 150 | callback = pg.sprite.collide_mask 151 | while pg.sprite.spritecollideany(self, collisions, callback): 152 | self.rect[i] += (1 if offset<0 else -1) 153 | self.remainder[i] = 0 154 | 155 | def draw(self, surface): 156 | """Draw method seperated out from update.""" 157 | surface.blit(self.image, self.rect) 158 | 159 | 160 | class Block(pg.sprite.Sprite): 161 | """Something to run head-first into.""" 162 | def __init__(self, location): 163 | """The location argument is where I will be located.""" 164 | pg.sprite.Sprite.__init__(self) 165 | self.image = self.make_image() 166 | self.rect = self.image.get_rect(topleft=location) 167 | self.mask = pg.mask.from_surface(self.image) 168 | 169 | def make_image(self): 170 | """Let's not forget aesthetics.""" 171 | image = pg.Surface((50,50)).convert_alpha() 172 | image.fill([random.randint(0, 255) for _ in range(3)]) 173 | image.blit(SHADE_MASK, (0,0)) 174 | return image 175 | 176 | 177 | class Control(object): 178 | """Being controlling is our job.""" 179 | def __init__(self): 180 | """Initialize standard attributes standardly.""" 181 | self.screen = pg.display.get_surface() 182 | self.screen_rect = self.screen.get_rect() 183 | self.clock = pg.time.Clock() 184 | self.fps = 60.0 185 | self.done = False 186 | self.keys = pg.key.get_pressed() 187 | self.player = Player((0,0,50,50), 190) 188 | self.player.rect.center = self.screen_rect.center 189 | self.obstacles = self.make_obstacles() 190 | 191 | def make_obstacles(self): 192 | """Prepare some obstacles for our player to collide with.""" 193 | obstacles = [Block((400,400)), Block((300,270)), Block((150,170))] 194 | for i in range(9): 195 | obstacles.append(Block((i*50,0))) 196 | obstacles.append(Block((450,50*i))) 197 | obstacles.append(Block((50+i*50,450))) 198 | obstacles.append(Block((0,50+50*i))) 199 | return pg.sprite.Group(obstacles) 200 | 201 | def event_loop(self): 202 | """Add/pop directions from player's direction stack as necessary.""" 203 | for event in pg.event.get(): 204 | self.keys = pg.key.get_pressed() 205 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 206 | self.done = True 207 | elif event.type == pg.KEYDOWN: 208 | self.player.add_direction(event.key) 209 | elif event.type == pg.KEYUP: 210 | self.player.pop_direction(event.key) 211 | 212 | def draw(self): 213 | """Draw all elements to the display surface.""" 214 | self.screen.fill(BACKGROUND_COLOR) 215 | self.obstacles.draw(self.screen) 216 | self.player.draw(self.screen) 217 | 218 | def display_fps(self): 219 | """Show the program's FPS in the window handle.""" 220 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 221 | pg.display.set_caption(caption) 222 | 223 | def main_loop(self): 224 | """Our main game loop; I bet you'd never have guessed.""" 225 | delta = self.clock.tick(self.fps)/1000.0 226 | while not self.done: 227 | self.event_loop() 228 | self.player.update(self.obstacles, delta) 229 | self.draw() 230 | pg.display.update() 231 | delta = self.clock.tick(self.fps)/1000.0 232 | self.display_fps() 233 | 234 | 235 | def get_images(sheet, frame_indices, size): 236 | """Get desired images from a sprite sheet.""" 237 | frames = [] 238 | for cell in frame_indices: 239 | frame_rect = ((size[0]*cell[0],size[1]*cell[1]), size) 240 | frames.append(sheet.subsurface(frame_rect)) 241 | return frames 242 | 243 | 244 | def divfmod(x, y): 245 | """Identical to the builtin divmod but using math.fmod to retain signs.""" 246 | fmod = math.fmod(x, y) 247 | div = (x-fmod)//y 248 | return div, fmod 249 | 250 | 251 | def main(): 252 | """Initialize, load our images, and run the program.""" 253 | global SKEL_IMAGE, SHADE_MASK 254 | os.environ['SDL_VIDEO_CENTERED'] = '1' 255 | pg.init() 256 | pg.display.set_caption(CAPTION) 257 | pg.display.set_mode(SCREEN_SIZE) 258 | SKEL_IMAGE = pg.image.load("skelly.png").convert() 259 | SKEL_IMAGE.set_colorkey(COLOR_KEY) 260 | SHADE_MASK = pg.image.load("shader.png").convert_alpha() 261 | Control().main_loop() 262 | pg.quit() 263 | sys.exit() 264 | 265 | 266 | if __name__ == "__main__": 267 | main() 268 | -------------------------------------------------------------------------------- /four_direction_movement/four_dir_anim.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | This script implements a basic sprite that can only move orthogonally. 5 | Orthogonal-only movement is slightly trickier than 8-direction movement 6 | because you can't just create a simple movement vector. 7 | Extra work must be done to make key overlaps execute cleanly. 8 | 9 | -Written by Sean J. McKiernan 'Mekire' 10 | """ 11 | 12 | import os 13 | import sys 14 | import itertools 15 | 16 | import pygame as pg 17 | 18 | 19 | CAPTION = "4-Direction Movement with Animation" 20 | SCREEN_SIZE = (500, 500) 21 | 22 | BACKGROUND_COLOR = pg.Color("slategray") 23 | COLOR_KEY = pg.Color("magenta") 24 | 25 | DIRECT_DICT = {pg.K_LEFT : (-1, 0), 26 | pg.K_RIGHT : ( 1, 0), 27 | pg.K_UP : ( 0,-1), 28 | pg.K_DOWN : ( 0, 1)} 29 | 30 | 31 | class Player(object): 32 | """ 33 | This class will represent our user controlled character. 34 | """ 35 | SIZE = (50, 50) 36 | 37 | def __init__(self, pos, speed, facing=pg.K_RIGHT): 38 | """ 39 | The pos argument is a tuple for the center of the player (x,y); 40 | speed is in pixels/frame; and facing is the Player's starting 41 | direction (given as a key-constant). 42 | """ 43 | self.speed = speed 44 | self.direction = facing 45 | self.old_direction = None # Player's previous direction every frame. 46 | self.direction_stack = [] # Held keys in the order they were pressed. 47 | self.redraw = True # Forces redraw if needed. 48 | self.animate_timer = 0.0 49 | self.animate_fps = 7 50 | self.image = None 51 | self.walkframes = None 52 | self.walkframe_dict = self.make_frame_dict() 53 | self.adjust_images() 54 | self.rect = self.image.get_rect(center=pos) 55 | 56 | def make_frame_dict(self): 57 | """ 58 | Create a dictionary of direction keys to frame cycles. We can use 59 | transform functions to reduce the size of the sprite sheet needed. 60 | """ 61 | frames = split_sheet(SKEL_IMAGE, Player.SIZE, 4, 1)[0] 62 | flips = [pg.transform.flip(frame, True, False) for frame in frames] 63 | walk_cycles = {pg.K_LEFT : itertools.cycle(frames[0:2]), 64 | pg.K_RIGHT: itertools.cycle(flips[0:2]), 65 | pg.K_DOWN : itertools.cycle([frames[3], flips[3]]), 66 | pg.K_UP : itertools.cycle([frames[2], flips[2]])} 67 | return walk_cycles 68 | 69 | def adjust_images(self, now=0): 70 | """ 71 | Update the sprite's walkframes as the sprite's direction changes. 72 | """ 73 | if self.direction != self.old_direction: 74 | self.walkframes = self.walkframe_dict[self.direction] 75 | self.old_direction = self.direction 76 | self.redraw = True 77 | self.make_image(now) 78 | 79 | def make_image(self, now): 80 | """ 81 | Update the sprite's animation as needed. 82 | """ 83 | elapsed = now-self.animate_timer > 1000.0/self.animate_fps 84 | if self.redraw or (self.direction_stack and elapsed): 85 | self.image = next(self.walkframes) 86 | self.animate_timer = now 87 | self.redraw = False 88 | 89 | def add_direction(self, key): 90 | """ 91 | Add a pressed direction key on the direction stack. 92 | """ 93 | if key in DIRECT_DICT: 94 | if key in self.direction_stack: 95 | self.direction_stack.remove(key) 96 | self.direction_stack.append(key) 97 | self.direction = self.direction_stack[-1] 98 | 99 | def pop_direction(self, key): 100 | """ 101 | Pop a released key from the direction stack. 102 | """ 103 | if key in DIRECT_DICT: 104 | if key in self.direction_stack: 105 | self.direction_stack.remove(key) 106 | if self.direction_stack: 107 | self.direction = self.direction_stack[-1] 108 | 109 | def get_event(self, event): 110 | """ 111 | Handle events pertaining to player control. 112 | """ 113 | if event.type == pg.KEYDOWN: 114 | self.add_direction(event.key) 115 | elif event.type == pg.KEYUP: 116 | self.pop_direction(event.key) 117 | 118 | def update(self, now, screen_rect): 119 | """ 120 | Updates our player appropriately every frame. 121 | """ 122 | self.adjust_images(now) 123 | if self.direction_stack: 124 | direction_vector = DIRECT_DICT[self.direction] 125 | self.rect.x += self.speed*direction_vector[0] 126 | self.rect.y += self.speed*direction_vector[1] 127 | self.rect.clamp_ip(screen_rect) 128 | 129 | def draw(self, surface): 130 | """ 131 | Draws the player to the target surface. 132 | """ 133 | surface.blit(self.image, self.rect) 134 | 135 | 136 | class App(object): 137 | """ 138 | A class to manage our event, game loop, and overall program flow. 139 | """ 140 | def __init__(self): 141 | """ 142 | Get a reference to the display surface; set up required attributes; 143 | and create a Player instance. 144 | """ 145 | self.screen = pg.display.get_surface() 146 | self.screen_rect = self.screen.get_rect() 147 | self.clock = pg.time.Clock() 148 | self.fps = 60 149 | self.done = False 150 | self.keys = pg.key.get_pressed() 151 | self.player = Player(self.screen_rect.center, 3) 152 | 153 | def event_loop(self): 154 | """ 155 | Pass events on to the player. 156 | """ 157 | for event in pg.event.get(): 158 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 159 | self.done = True 160 | elif event.type in (pg.KEYUP, pg.KEYDOWN): 161 | self.keys = pg.key.get_pressed() 162 | self.player.get_event(event) 163 | 164 | def display_fps(self): 165 | """ 166 | Show the program's FPS in the window handle. 167 | """ 168 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 169 | pg.display.set_caption(caption) 170 | 171 | def update(self): 172 | """ 173 | Update the player. 174 | The current time is passed for purposes of animation. 175 | """ 176 | now = pg.time.get_ticks() 177 | self.player.update(now, self.screen_rect) 178 | 179 | def render(self): 180 | """ 181 | Perform all necessary drawing and update the screen. 182 | """ 183 | self.screen.fill(BACKGROUND_COLOR) 184 | self.player.draw(self.screen) 185 | pg.display.update() 186 | 187 | def main_loop(self): 188 | """ 189 | Our main game loop; I bet you'd never have guessed. 190 | """ 191 | while not self.done: 192 | self.event_loop() 193 | self.update() 194 | self.render() 195 | self.clock.tick(self.fps) 196 | self.display_fps() 197 | 198 | 199 | def split_sheet(sheet, size, columns, rows): 200 | """ 201 | Divide a loaded sprite sheet into subsurfaces. 202 | 203 | The argument size is the width and height of each frame (w,h) 204 | columns and rows are the integer number of cells horizontally and 205 | vertically. 206 | """ 207 | subsurfaces = [] 208 | for y in range(rows): 209 | row = [] 210 | for x in range(columns): 211 | rect = pg.Rect((x*size[0], y*size[1]), size) 212 | row.append(sheet.subsurface(rect)) 213 | subsurfaces.append(row) 214 | return subsurfaces 215 | 216 | 217 | def main(): 218 | """ 219 | Prepare our environment, create a display, and start the program. 220 | """ 221 | global SKEL_IMAGE 222 | os.environ['SDL_VIDEO_CENTERED'] = '1' 223 | pg.init() 224 | pg.display.set_caption(CAPTION) 225 | pg.display.set_mode(SCREEN_SIZE) 226 | SKEL_IMAGE = pg.image.load("skelly.png").convert() 227 | SKEL_IMAGE.set_colorkey(COLOR_KEY) 228 | App().main_loop() 229 | pg.quit() 230 | sys.exit() 231 | 232 | 233 | if __name__ == "__main__": 234 | main() 235 | -------------------------------------------------------------------------------- /four_direction_movement/four_dir_mask.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shows the direction of collision. This method uses masks (pixel perfect) 3 | collision methods, and is a little (possibly over) complicated. It finds the 4 | direction of a collision by calculating a finite difference between colliding 5 | mask elements in both directions. This technique can be extended to find the 6 | actually angle of collision (normal vector) between two simple colliding 7 | shapes. 8 | 9 | -Written by Sean J. McKiernan 'Mekire' 10 | """ 11 | 12 | import os 13 | import sys 14 | import random 15 | import pygame as pg 16 | 17 | 18 | CAPTION = "Direction of Collision: Masks" 19 | SCREEN_SIZE = (500, 500) 20 | BACKGROUND_COLOR = (40, 40, 40) 21 | COLOR_KEY = (255, 0, 255) 22 | TEXT_COLOR = (200, 200, 230) 23 | 24 | DIRECT_DICT = {pg.K_LEFT : (-1, 0), 25 | pg.K_RIGHT : ( 1, 0), 26 | pg.K_UP : ( 0,-1), 27 | pg.K_DOWN : ( 0, 1)} 28 | 29 | OPPOSITE_DICT = {pg.K_LEFT : "right", 30 | pg.K_RIGHT : "left", 31 | pg.K_UP : "bottom", 32 | pg.K_DOWN : "top"} 33 | 34 | 35 | class Player(pg.sprite.Sprite): 36 | """ 37 | This time we inherit from pygame.sprite.Sprite. We are going to take 38 | advantage of the sprite.Group collission functions (though as usual, doing 39 | all this without using pygame.sprite is not much more complicated). 40 | """ 41 | def __init__(self, rect, speed, direction=pg.K_RIGHT): 42 | """ 43 | Arguments are a rect representing the Player's location and 44 | dimension, the speed(in pixels/frame) of the Player, and the Player's 45 | starting direction (given as a key-constant). 46 | """ 47 | pg.sprite.Sprite.__init__(self) 48 | self.rect = pg.Rect(rect) 49 | self.mask = self.make_mask() 50 | self.speed = speed 51 | self.direction = direction 52 | self.collision_direction = None 53 | self.first_collision_per_frame = None 54 | self.old_direction = None #The Players previous direction every frame. 55 | self.direction_stack = [] #Held keys in the order they were pressed. 56 | self.redraw = False #Force redraw if needed. 57 | self.image = None 58 | self.frame = 0 59 | self.frames = self.get_frames() 60 | self.animate_timer = 0.0 61 | self.animate_fps = 7.0 62 | self.walkframes = [] 63 | self.walkframe_dict = self.make_frame_dict() 64 | self.adjust_images() 65 | 66 | def make_mask(self): 67 | """ 68 | Create a collision mask slightly smaller than our sprite so that 69 | the sprite's head can overlap obstacles; adding depth. 70 | """ 71 | mask_surface = pg.Surface(self.rect.size).convert_alpha() 72 | mask_surface.fill((0,0,0,0)) 73 | mask_surface.fill(pg.Color("white"), (10,20,30,30)) 74 | mask = pg.mask.from_surface(mask_surface) 75 | return mask 76 | 77 | def get_frames(self): 78 | """Get a list of all frames.""" 79 | sheet = SKEL_IMAGE 80 | indices = [[0,0],[1,0],[2,0],[3,0]] 81 | return get_images(sheet,indices,self.rect.size) 82 | 83 | def make_frame_dict(self): 84 | """ 85 | Create a dictionary of direction keys to frames. We can use 86 | transform functions to reduce the size of the sprite sheet needed. 87 | """ 88 | frames = {pg.K_LEFT : [self.frames[0], self.frames[1]], 89 | pg.K_RIGHT: [pg.transform.flip(self.frames[0], True, False), 90 | pg.transform.flip(self.frames[1], True, False)], 91 | pg.K_DOWN : [self.frames[3], 92 | pg.transform.flip(self.frames[3], True, False)], 93 | pg.K_UP : [self.frames[2], 94 | pg.transform.flip(self.frames[2], True, False)]} 95 | return frames 96 | 97 | def adjust_images(self): 98 | """Update the sprite's walkframes as the sprite's direction changes.""" 99 | if self.direction != self.old_direction: 100 | self.walkframes = self.walkframe_dict[self.direction] 101 | self.old_direction = self.direction 102 | self.redraw = True 103 | self.make_image() 104 | 105 | def make_image(self): 106 | """Update the sprite's animation as needed.""" 107 | now = pg.time.get_ticks() 108 | if self.redraw or now-self.animate_timer > 1000/self.animate_fps: 109 | if self.direction_stack: 110 | self.frame = (self.frame+1)%len(self.walkframes) 111 | self.image = self.walkframes[self.frame] 112 | self.animate_timer = now 113 | if not self.image: 114 | self.image = self.walkframes[self.frame] 115 | self.redraw = False 116 | 117 | def add_direction(self, key): 118 | """Add a pressed direction key on the direction stack.""" 119 | if key in DIRECT_DICT: 120 | if key in self.direction_stack: 121 | self.direction_stack.remove(key) 122 | self.direction_stack.append(key) 123 | self.direction = self.direction_stack[-1] 124 | 125 | def pop_direction(self, key): 126 | """Pop a released key from the direction stack.""" 127 | if key in DIRECT_DICT: 128 | if key in self.direction_stack: 129 | self.direction_stack.remove(key) 130 | if self.direction_stack: 131 | self.direction = self.direction_stack[-1] 132 | 133 | def update(self, obstacles): 134 | """Adjust the image and move as needed.""" 135 | self.adjust_images() 136 | self.collision_direction = None 137 | if self.direction_stack: 138 | self.movement(obstacles, 0) 139 | self.movement(obstacles, 1) 140 | 141 | def movement(self, obstacles, i): 142 | """Move player and then check for collisions; adjust as necessary.""" 143 | change = self.speed*DIRECT_DICT[self.direction][i] 144 | self.rect[i] += change 145 | collisions = pg.sprite.spritecollide(self, obstacles, False) 146 | callback = pg.sprite.collide_mask 147 | collide = pg.sprite.spritecollideany(self, collisions, callback) 148 | if collide and not self.collision_direction: 149 | self.collision_direction = self.get_collision_direction(collide) 150 | while collide: 151 | self.rect[i] += (1 if change<0 else -1) 152 | collide = pg.sprite.spritecollideany(self, collisions, callback) 153 | 154 | def get_collision_direction(self, other_sprite): 155 | """Find what side of an object the player is running into.""" 156 | dx = self.get_finite_difference(other_sprite, 0, self.speed) 157 | dy = self.get_finite_difference(other_sprite, 1, self.speed) 158 | abs_x, abs_y = abs(dx), abs(dy) 159 | if abs_x > abs_y: 160 | return ("right" if dx>0 else "left") 161 | elif abs_x < abs_y: 162 | return ("bottom" if dy>0 else "top") 163 | else: 164 | return OPPOSITE_DICT[self.direction] 165 | 166 | def get_finite_difference(self, other_sprite, index, delta=1): 167 | """ 168 | Find the finite difference in area of mask collision with the 169 | rects position incremented and decremented in axis index. 170 | """ 171 | base_offset = [other_sprite.rect.x-self.rect.x, 172 | other_sprite.rect.y-self.rect.y] 173 | offset_high = base_offset[:] 174 | offset_low = base_offset[:] 175 | offset_high[index] += delta 176 | offset_low[index] -= delta 177 | first_term = self.mask.overlap_area(other_sprite.mask, offset_high) 178 | second_term = self.mask.overlap_area(other_sprite.mask, offset_low) 179 | return first_term - second_term 180 | 181 | def draw(self, surface): 182 | """Draw method seperated out from update.""" 183 | surface.blit(self.image, self.rect) 184 | 185 | 186 | class Block(pg.sprite.Sprite): 187 | """Something to run head-first into.""" 188 | def __init__(self, location): 189 | """The location argument is where I will be located.""" 190 | pg.sprite.Sprite.__init__(self) 191 | self.image = self.make_image() 192 | self.rect = self.image.get_rect(topleft=location) 193 | self.mask = pg.mask.from_surface(self.image) 194 | 195 | def make_image(self): 196 | """Let's not forget aesthetics.""" 197 | image = pg.Surface((50,50)).convert_alpha() 198 | image.fill([random.randint(0, 255) for _ in range(3)]) 199 | image.blit(SHADE_MASK, (0,0)) 200 | return image 201 | 202 | 203 | class Control(object): 204 | """Being controlling is our job.""" 205 | text_cache = {} 206 | 207 | def __init__(self): 208 | """Initialize standard attributes standardly.""" 209 | self.screen = pg.display.get_surface() 210 | self.screen_rect = self.screen.get_rect() 211 | self.clock = pg.time.Clock() 212 | self.fps = 60.0 213 | self.done = False 214 | self.keys = pg.key.get_pressed() 215 | self.player = Player((0,0,50,50), 3) 216 | self.player.rect.center = self.screen_rect.center 217 | self.obstacles = self.make_obstacles() 218 | 219 | def make_obstacles(self): 220 | """Prepare some obstacles for our player to collide with.""" 221 | obstacles = [Block((400,400)), Block((300,270)), Block((150,170))] 222 | for i in range(9): 223 | obstacles.append(Block((i*50,0))) 224 | obstacles.append(Block((450,50*i))) 225 | obstacles.append(Block((50+i*50,450))) 226 | obstacles.append(Block((0,50+50*i))) 227 | return pg.sprite.Group(obstacles) 228 | 229 | def render_text(self, text, font, color, cache=True): 230 | """ 231 | Returns a rendered surface of the text; if available the surface is 232 | retrieved from the text_cache to avoid rerendering. 233 | """ 234 | if text in Control.text_cache: 235 | return Control.text_cache[text] 236 | else: 237 | image = font.render(text, True, color) 238 | if cache: 239 | Control.text_cache[text] = image 240 | return image 241 | 242 | def event_loop(self): 243 | """Add/pop directions from player's direction stack as necessary.""" 244 | for event in pg.event.get(): 245 | self.keys = pg.key.get_pressed() 246 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 247 | self.done = True 248 | elif event.type == pg.KEYDOWN: 249 | self.player.add_direction(event.key) 250 | elif event.type == pg.KEYUP: 251 | self.player.pop_direction(event.key) 252 | 253 | def draw(self): 254 | """Draw all elements to the display surface.""" 255 | self.screen.fill(BACKGROUND_COLOR) 256 | self.obstacles.draw(self.screen) 257 | self.player.draw(self.screen) 258 | self.draw_collision_direction() 259 | 260 | def draw_collision_direction(self): 261 | """Blit a message to the screen if player is colliding.""" 262 | if self.player.collision_direction: 263 | direction = self.player.collision_direction 264 | text = "Collided with {} edge.".format(direction) 265 | image = self.render_text(text, FONT, TEXT_COLOR) 266 | rect = image.get_rect(center=(self.screen_rect.centerx, 375)) 267 | self.screen.blit(image, rect) 268 | 269 | def display_fps(self): 270 | """Show the program's FPS in the window handle.""" 271 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 272 | pg.display.set_caption(caption) 273 | 274 | def main_loop(self): 275 | """Our main game loop; I bet you'd never have guessed.""" 276 | while not self.done: 277 | self.event_loop() 278 | self.player.update(self.obstacles) 279 | self.draw() 280 | pg.display.update() 281 | self.clock.tick(self.fps) 282 | self.display_fps() 283 | 284 | 285 | def get_images(sheet,frame_indices,size): 286 | """Get desired images from a sprite sheet.""" 287 | frames = [] 288 | for cell in frame_indices: 289 | frame_rect = ((size[0]*cell[0],size[1]*cell[1]), size) 290 | frames.append(sheet.subsurface(frame_rect)) 291 | return frames 292 | 293 | 294 | def main(): 295 | """Initialize, load our images, create font object, and run the program.""" 296 | global SKEL_IMAGE, SHADE_MASK, FONT 297 | os.environ['SDL_VIDEO_CENTERED'] = '1' 298 | pg.init() 299 | pg.display.set_caption(CAPTION) 300 | pg.display.set_mode(SCREEN_SIZE) 301 | SKEL_IMAGE = pg.image.load("skelly.png").convert() 302 | SKEL_IMAGE.set_colorkey(COLOR_KEY) 303 | SHADE_MASK = pg.image.load("shader.png").convert_alpha() 304 | FONT = pg.font.SysFont("arial", 30) 305 | Control().main_loop() 306 | pg.quit() 307 | sys.exit() 308 | 309 | 310 | if __name__ == "__main__": 311 | main() 312 | -------------------------------------------------------------------------------- /four_direction_movement/four_dir_naive.py: -------------------------------------------------------------------------------- 1 | """ 2 | Showing the direction of collision. As the name suggests, this is a very naive 3 | implementation. It will only tell you an edge collision occurs if the midpoint 4 | of that edge is in the player's rect. This may be sufficient if trying to see 5 | if a player succesfully landed on an enemy, but it is very limited. 6 | 7 | -Written by Sean J. McKiernan 'Mekire' 8 | """ 9 | 10 | import os 11 | import sys 12 | import random 13 | import pygame as pg 14 | 15 | 16 | CAPTION = "Direction of Collision: Naive" 17 | SCREEN_SIZE = (500, 500) 18 | BACKGROUND_COLOR = (40, 40, 40) 19 | COLOR_KEY = (255, 0, 255) 20 | TEXT_COLOR = (200, 200, 230) 21 | 22 | DIRECT_DICT = {pg.K_LEFT : (-1, 0), 23 | pg.K_RIGHT : ( 1, 0), 24 | pg.K_UP : ( 0,-1), 25 | pg.K_DOWN : ( 0, 1)} 26 | 27 | 28 | class Player(pg.sprite.Sprite): 29 | """ 30 | This time we inherit from pygame.sprite.Sprite. We are going to take 31 | advantage of the sprite.Group collission functions (though as usual, doing 32 | all this without using pygame.sprite is not much more complicated). 33 | """ 34 | TEXT_CACHE = {} 35 | 36 | def __init__(self, rect, speed, direction=pg.K_RIGHT): 37 | """ 38 | Arguments are a rect representing the Player's location and 39 | dimension, the speed(in pixels/frame) of the Player, and the Player's 40 | starting direction (given as a key-constant). 41 | """ 42 | pg.sprite.Sprite.__init__(self) 43 | self.rect = pg.Rect(rect) 44 | self.speed = speed 45 | self.direction = direction 46 | self.collision_direction = None 47 | self.old_direction = None #The Players previous direction every frame. 48 | self.direction_stack = [] #Held keys in the order they were pressed. 49 | self.redraw = False #Force redraw if needed. 50 | self.image = None 51 | self.frame = 0 52 | self.frames = self.get_frames() 53 | self.animate_timer = 0.0 54 | self.animate_fps = 7.0 55 | self.walkframes = [] 56 | self.walkframe_dict = self.make_frame_dict() 57 | self.adjust_images() 58 | 59 | def get_frames(self): 60 | """Get a list of all frames.""" 61 | sheet = SKEL_IMAGE 62 | indices = [[0,0], [1,0], [2,0], [3,0]] 63 | return get_images(sheet, indices, self.rect.size) 64 | 65 | def make_frame_dict(self): 66 | """ 67 | Create a dictionary of direction keys to frames. We can use 68 | transform functions to reduce the size of the sprite sheet needed. 69 | """ 70 | frames = {pg.K_LEFT : [self.frames[0], self.frames[1]], 71 | pg.K_RIGHT: [pg.transform.flip(self.frames[0], True, False), 72 | pg.transform.flip(self.frames[1], True, False)], 73 | pg.K_DOWN : [self.frames[3], 74 | pg.transform.flip(self.frames[3], True, False)], 75 | pg.K_UP : [self.frames[2], 76 | pg.transform.flip(self.frames[2], True, False)]} 77 | return frames 78 | 79 | def adjust_images(self): 80 | """Update the sprites walkframes as the sprite's direction changes.""" 81 | if self.direction != self.old_direction: 82 | self.walkframes = self.walkframe_dict[self.direction] 83 | self.old_direction = self.direction 84 | self.redraw = True 85 | self.make_image() 86 | 87 | def make_image(self): 88 | """Update the sprite's animation as needed.""" 89 | now = pg.time.get_ticks() 90 | if self.redraw or now-self.animate_timer > 1000/self.animate_fps: 91 | if self.direction_stack: 92 | self.frame = (self.frame+1)%len(self.walkframes) 93 | self.image = self.walkframes[self.frame] 94 | self.animate_timer = now 95 | if not self.image: 96 | self.image = self.walkframes[self.frame] 97 | self.redraw = False 98 | 99 | def add_direction(self,key): 100 | """Add a pressed direction key on the direction stack.""" 101 | if key in DIRECT_DICT: 102 | if key in self.direction_stack: 103 | self.direction_stack.remove(key) 104 | self.direction_stack.append(key) 105 | self.direction = self.direction_stack[-1] 106 | 107 | def pop_direction(self,key): 108 | """Pop a released key from the direction stack.""" 109 | if key in DIRECT_DICT: 110 | if key in self.direction_stack: 111 | self.direction_stack.remove(key) 112 | if self.direction_stack: 113 | self.direction = self.direction_stack[-1] 114 | 115 | def update(self,obstacles): 116 | """Adjust the image and move as needed.""" 117 | self.adjust_images() 118 | self.collision_direction = None 119 | if self.direction_stack: 120 | self.movement(obstacles, 0) 121 | self.movement(obstacles, 1) 122 | 123 | def movement(self, obstacles, i): 124 | """Move player and then check for collisions; adjust as necessary.""" 125 | direction_vector = DIRECT_DICT[self.direction] 126 | self.rect[i] += self.speed*direction_vector[i] 127 | collisions = pg.sprite.spritecollide(self, obstacles, False) 128 | self.get_collision_direction(collisions) 129 | while collisions: 130 | collision = collisions.pop() 131 | self.adjust_on_collision(self.rect, collision, i) 132 | 133 | def adjust_on_collision(self, rect_to_adjust, collide, i): 134 | """Adjust player's position if colliding with a solid block.""" 135 | if rect_to_adjust[i] < collide.rect[i]: 136 | rect_to_adjust[i] = collide.rect[i]-rect_to_adjust.size[i] 137 | else: 138 | rect_to_adjust[i] = collide.rect[i]+collide.rect.size[i] 139 | 140 | def get_collision_direction(self, collisions): 141 | """A naive implementation to find direction of collision.""" 142 | for sprite in collisions: 143 | check_dict = {"left" : sprite.rect.midleft, 144 | "right" : sprite.rect.midright, 145 | "top" : sprite.rect.midtop, 146 | "bottom" : sprite.rect.midbottom} 147 | for direction,test in check_dict.items(): 148 | if self.rect.collidepoint(test): 149 | self.collision_direction = direction 150 | 151 | def draw(self, surface): 152 | """Draw method seperated out from update.""" 153 | surface.blit(self.image, self.rect) 154 | 155 | 156 | class Block(pg.sprite.Sprite): 157 | """Something to run head-first into.""" 158 | def __init__(self,location): 159 | """The location argument is where I will be located.""" 160 | pg.sprite.Sprite.__init__(self) 161 | self.image = self.make_image() 162 | self.rect = self.image.get_rect(topleft=location) 163 | 164 | def make_image(self): 165 | """Let's not forget aesthetics.""" 166 | image = pg.Surface((50,50)).convert_alpha() 167 | image.fill([random.randint(0, 255) for _ in range(3)]) 168 | image.blit(SHADE_MASK, (0,0)) 169 | return image 170 | 171 | 172 | class Control(object): 173 | """Being controlling is our job.""" 174 | text_cache = {} 175 | 176 | def __init__(self): 177 | """Initialize standard attributes standardly.""" 178 | self.screen = pg.display.get_surface() 179 | self.screen_rect = self.screen.get_rect() 180 | self.clock = pg.time.Clock() 181 | self.fps = 60.0 182 | self.done = False 183 | self.keys = pg.key.get_pressed() 184 | self.player = Player((0,0,50,50), 3) 185 | self.player.rect.center = self.screen_rect.center 186 | self.obstacles = self.make_obstacles() 187 | 188 | def make_obstacles(self): 189 | """Prepare some obstacles for our player to collide with.""" 190 | obstacles = [Block((400,400)), Block((300,270)), Block((150,170))] 191 | for i in range(9): 192 | obstacles.append(Block((i*50,0))) 193 | obstacles.append(Block((450,50*i))) 194 | obstacles.append(Block((50+i*50,450))) 195 | obstacles.append(Block((0,50+50*i))) 196 | return pg.sprite.Group(obstacles) 197 | 198 | def render_text(self, text, font, color, cache=True): 199 | """ 200 | Returns a rendered surface of the text; if available the surface is 201 | retrieved from the text_cache to avoid rerendering. 202 | """ 203 | if text in Control.text_cache: 204 | return Control.text_cache[text] 205 | else: 206 | image = font.render(text, True, color) 207 | if cache: 208 | Control.text_cache[text] = image 209 | return image 210 | 211 | def event_loop(self): 212 | """Add/pop directions from player's direction stack as necessary.""" 213 | for event in pg.event.get(): 214 | self.keys = pg.key.get_pressed() 215 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 216 | self.done = True 217 | elif event.type == pg.KEYDOWN: 218 | self.player.add_direction(event.key) 219 | elif event.type == pg.KEYUP: 220 | self.player.pop_direction(event.key) 221 | 222 | def draw(self): 223 | """Draw all elements to the display surface.""" 224 | self.screen.fill(BACKGROUND_COLOR) 225 | self.obstacles.draw(self.screen) 226 | self.player.draw(self.screen) 227 | self.draw_collision_direction() 228 | 229 | def draw_collision_direction(self): 230 | """Blit a message to the screen if player is colliding.""" 231 | if self.player.collision_direction: 232 | direction = self.player.collision_direction 233 | text = "Collided with {} edge.".format(direction) 234 | image = self.render_text(text, FONT, TEXT_COLOR) 235 | rect = image.get_rect(center=(self.screen_rect.centerx,375)) 236 | self.screen.blit(image, rect) 237 | 238 | def display_fps(self): 239 | """Show the program's FPS in the window handle.""" 240 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 241 | pg.display.set_caption(caption) 242 | 243 | def main_loop(self): 244 | """Our main game loop; I bet you'd never have guessed.""" 245 | while not self.done: 246 | self.event_loop() 247 | self.player.update(self.obstacles) 248 | self.draw() 249 | pg.display.update() 250 | self.clock.tick(self.fps) 251 | self.display_fps() 252 | 253 | 254 | def get_images(sheet, frame_indices, size): 255 | """Get desired images from a sprite sheet.""" 256 | frames = [] 257 | for cell in frame_indices: 258 | frame_rect = ((size[0]*cell[0],size[1]*cell[1]), size) 259 | frames.append(sheet.subsurface(frame_rect)) 260 | return frames 261 | 262 | 263 | def main(): 264 | """Initialize, load our images, create font object, and run the program.""" 265 | global SKEL_IMAGE, SHADE_MASK, FONT 266 | os.environ['SDL_VIDEO_CENTERED'] = '1' 267 | pg.init() 268 | pg.display.set_caption(CAPTION) 269 | pg.display.set_mode(SCREEN_SIZE) 270 | SKEL_IMAGE = pg.image.load("skelly.png").convert() 271 | SKEL_IMAGE.set_colorkey(COLOR_KEY) 272 | SHADE_MASK = pg.image.load("shader.png").convert_alpha() 273 | FONT = pg.font.SysFont("arial", 30) 274 | run_it = Control() 275 | run_it.main_loop() 276 | pg.quit() 277 | sys.exit() 278 | 279 | 280 | if __name__ == "__main__": 281 | main() 282 | -------------------------------------------------------------------------------- /four_direction_movement/four_dir_obstacles.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | This script is identical to the four_dir_anim.py example except that some 5 | simple obstacles have been added to demonstrate basic collission detection. 6 | 7 | -Written by Sean J. McKiernan 'Mekire' 8 | """ 9 | 10 | import os 11 | import sys 12 | import random 13 | import itertools 14 | 15 | import pygame as pg 16 | 17 | 18 | CAPTION = "4-Direction Movement with Obstacles" 19 | SCREEN_SIZE = (500, 500) 20 | 21 | BACKGROUND_COLOR = pg.Color("darkslategray") 22 | COLOR_KEY = pg.Color("magenta") 23 | 24 | DIRECT_DICT = {pg.K_LEFT : (-1, 0), 25 | pg.K_RIGHT : ( 1, 0), 26 | pg.K_UP : ( 0,-1), 27 | pg.K_DOWN : ( 0, 1)} 28 | 29 | 30 | class Player(pg.sprite.Sprite): 31 | """ 32 | This time we inherit from pygame.sprite.Sprite. We are going to take 33 | advantage of the sprite.Group collission functions (though as usual, 34 | doing all this without using pygame.sprite is not much more complicated). 35 | """ 36 | SIZE = (50, 50) 37 | 38 | def __init__(self, pos, speed, facing=pg.K_RIGHT, *groups): 39 | """ 40 | The pos argument is a tuple for the center of the player (x,y); 41 | speed is in pixels/frame; and facing is the Player's starting 42 | direction (given as a key-constant). 43 | """ 44 | super(Player, self).__init__(*groups) 45 | self.speed = speed 46 | self.direction = facing 47 | self.old_direction = None # Player's previous direction every frame. 48 | self.direction_stack = [] # Held keys in the order they were pressed. 49 | self.redraw = True # Forces redraw if needed. 50 | self.animate_timer = 0.0 51 | self.animate_fps = 7 52 | self.image = None 53 | self.walkframes = None 54 | self.walkframe_dict = self.make_frame_dict() 55 | self.adjust_images() 56 | self.rect = self.image.get_rect(center=pos) 57 | 58 | def make_frame_dict(self): 59 | """ 60 | Create a dictionary of direction keys to frame cycles. We can use 61 | transform functions to reduce the size of the sprite sheet needed. 62 | """ 63 | frames = split_sheet(SKEL_IMAGE, Player.SIZE, 4, 1)[0] 64 | flips = [pg.transform.flip(frame, True, False) for frame in frames] 65 | walk_cycles = {pg.K_LEFT : itertools.cycle(frames[0:2]), 66 | pg.K_RIGHT: itertools.cycle(flips[0:2]), 67 | pg.K_DOWN : itertools.cycle([frames[3], flips[3]]), 68 | pg.K_UP : itertools.cycle([frames[2], flips[2]])} 69 | return walk_cycles 70 | 71 | def adjust_images(self, now=0): 72 | """ 73 | Update the sprite's walkframes as the sprite's direction changes. 74 | """ 75 | if self.direction != self.old_direction: 76 | self.walkframes = self.walkframe_dict[self.direction] 77 | self.old_direction = self.direction 78 | self.redraw = True 79 | self.make_image(now) 80 | 81 | def make_image(self, now): 82 | """ 83 | Update the sprite's animation as needed. 84 | """ 85 | elapsed = now-self.animate_timer > 1000.0/self.animate_fps 86 | if self.redraw or (self.direction_stack and elapsed): 87 | self.image = next(self.walkframes) 88 | self.animate_timer = now 89 | self.redraw = False 90 | 91 | def add_direction(self, key): 92 | """ 93 | Add a pressed direction key on the direction stack. 94 | """ 95 | if key in DIRECT_DICT: 96 | if key in self.direction_stack: 97 | self.direction_stack.remove(key) 98 | self.direction_stack.append(key) 99 | self.direction = self.direction_stack[-1] 100 | 101 | def pop_direction(self, key): 102 | """ 103 | Pop a released key from the direction stack. 104 | """ 105 | if key in DIRECT_DICT: 106 | if key in self.direction_stack: 107 | self.direction_stack.remove(key) 108 | if self.direction_stack: 109 | self.direction = self.direction_stack[-1] 110 | 111 | def get_event(self, event): 112 | """ 113 | Handle events pertaining to player control. 114 | """ 115 | if event.type == pg.KEYDOWN: 116 | self.add_direction(event.key) 117 | elif event.type == pg.KEYUP: 118 | self.pop_direction(event.key) 119 | 120 | def update(self, now, obstacles): 121 | """ 122 | We have added some logic here for collission detection against the 123 | sprite.Group, obstacles. 124 | """ 125 | self.adjust_images(now) 126 | if self.direction_stack: 127 | self.movement(obstacles, 0) 128 | self.movement(obstacles, 1) 129 | 130 | def movement(self, obstacles, i): 131 | """ 132 | Move player and then check for collisions; adjust as necessary. 133 | """ 134 | direction_vector = DIRECT_DICT[self.direction] 135 | self.rect[i] += self.speed*direction_vector[i] 136 | collision = pg.sprite.spritecollideany(self, obstacles) 137 | while collision: 138 | self.adjust_on_collision(collision, i) 139 | collision = pg.sprite.spritecollideany(self, obstacles) 140 | 141 | def adjust_on_collision(self, collide, i): 142 | """ 143 | Adjust player's position if colliding with a solid block. 144 | """ 145 | if self.rect[i] < collide.rect[i]: 146 | self.rect[i] = collide.rect[i]-self.rect.size[i] 147 | else: 148 | self.rect[i] = collide.rect[i]+collide.rect.size[i] 149 | 150 | def draw(self, surface): 151 | """ 152 | Draw sprite to surface (not used if using group draw functions). 153 | """ 154 | surface.blit(self.image, self.rect) 155 | 156 | 157 | class Block(pg.sprite.Sprite): 158 | """ 159 | Something to run head-first into. 160 | """ 161 | def __init__(self, pos, *groups): 162 | """ 163 | The pos argument is where the topleft will be (x, y). 164 | """ 165 | super(Block, self).__init__(*groups) 166 | self.image = self.make_image() 167 | self.rect = self.image.get_rect(topleft=pos) 168 | 169 | def make_image(self): 170 | """ 171 | Let's not forget aesthetics. 172 | """ 173 | fill_color = [random.randint(0, 255) for _ in range(3)] 174 | image = pg.Surface((50,50)).convert_alpha() 175 | image.fill(fill_color) 176 | image.blit(SHADE_MASK, (0,0)) 177 | return image 178 | 179 | 180 | class App(object): 181 | """ 182 | A class to manage our event, game loop, and overall program flow. 183 | """ 184 | def __init__(self): 185 | """ 186 | Get a reference to the display surface; set up required attributes; 187 | and create a Player instance. 188 | """ 189 | self.screen = pg.display.get_surface() 190 | self.screen_rect = self.screen.get_rect() 191 | self.clock = pg.time.Clock() 192 | self.fps = 60 193 | self.done = False 194 | self.keys = pg.key.get_pressed() 195 | self.player = Player(self.screen_rect.center, 3) 196 | self.blocks = self.make_blocks() 197 | self.all_sprites = pg.sprite.Group(self.player, self.blocks) 198 | 199 | def make_blocks(self): 200 | """ 201 | Prepare some obstacles for our player to collide with. 202 | """ 203 | blocks = pg.sprite.Group() 204 | for pos in [(400,400), (300,270), (150,170)]: 205 | Block(pos, blocks) 206 | for i in range(9): 207 | Block((i*50,0), blocks) 208 | Block((450,50*i), blocks) 209 | Block((50+i*50,450), blocks) 210 | Block((0,50+50*i), blocks) 211 | return blocks 212 | 213 | def event_loop(self): 214 | """ 215 | Pass events on to the player. 216 | """ 217 | for event in pg.event.get(): 218 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 219 | self.done = True 220 | elif event.type in (pg.KEYUP, pg.KEYDOWN): 221 | self.keys = pg.key.get_pressed() 222 | self.player.get_event(event) 223 | 224 | def display_fps(self): 225 | """ 226 | Show the program's FPS in the window handle. 227 | """ 228 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 229 | pg.display.set_caption(caption) 230 | 231 | def update(self): 232 | """ 233 | Update the player. 234 | The current time is passed for purposes of animation. 235 | """ 236 | now = pg.time.get_ticks() 237 | self.player.update(now, self.blocks) 238 | 239 | def render(self): 240 | """ 241 | Perform all necessary drawing and update the screen. 242 | """ 243 | self.screen.fill(BACKGROUND_COLOR) 244 | self.all_sprites.draw(self.screen) 245 | pg.display.update() 246 | 247 | def main_loop(self): 248 | """ 249 | Our main game loop; I bet you'd never have guessed. 250 | """ 251 | while not self.done: 252 | self.event_loop() 253 | self.update() 254 | self.render() 255 | self.clock.tick(self.fps) 256 | self.display_fps() 257 | 258 | 259 | def split_sheet(sheet, size, columns, rows): 260 | """ 261 | Divide a loaded sprite sheet into subsurfaces. 262 | 263 | The argument size is the width and height of each frame (w,h) 264 | columns and rows are the integer number of cells horizontally and 265 | vertically. 266 | """ 267 | subsurfaces = [] 268 | for y in range(rows): 269 | row = [] 270 | for x in range(columns): 271 | rect = pg.Rect((x*size[0], y*size[1]), size) 272 | row.append(sheet.subsurface(rect)) 273 | subsurfaces.append(row) 274 | return subsurfaces 275 | 276 | 277 | def main(): 278 | """ 279 | Prepare our environment, create a display, and start the program. 280 | """ 281 | global SKEL_IMAGE, SHADE_MASK 282 | os.environ['SDL_VIDEO_CENTERED'] = '1' 283 | pg.init() 284 | pg.display.set_caption(CAPTION) 285 | pg.display.set_mode(SCREEN_SIZE) 286 | SKEL_IMAGE = pg.image.load("skelly.png").convert() 287 | SKEL_IMAGE.set_colorkey(COLOR_KEY) 288 | SHADE_MASK = pg.image.load("shader.png").convert_alpha() 289 | App().main_loop() 290 | pg.quit() 291 | sys.exit() 292 | 293 | 294 | if __name__ == "__main__": 295 | main() 296 | -------------------------------------------------------------------------------- /four_direction_movement/four_dir_obstacles_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script demonstrate writing a custom collided callback function for 3 | pygame.sprite.spritecollide. This can be useful and isn't particularly 4 | intuitive from the documentation, but is by no means required reading. 5 | 6 | -Written by Sean J. McKiernan 'Mekire' 7 | """ 8 | 9 | import os 10 | import sys 11 | import random 12 | import pygame as pg 13 | 14 | 15 | CAPTION = "Collided Callback Test" 16 | SCREEN_SIZE = (500, 500) 17 | BACKGROUND_COLOR = (40, 40, 40) 18 | COLOR_KEY = (255, 0, 255) 19 | 20 | DIRECT_DICT = {pg.K_LEFT : (-1, 0), 21 | pg.K_RIGHT : ( 1, 0), 22 | pg.K_UP : ( 0,-1), 23 | pg.K_DOWN : ( 0, 1)} 24 | 25 | 26 | def collide_other(other): 27 | """ 28 | The other argument is a pygame.Rect that you would like to use for 29 | sprite collision. Return value is a collided callable for use with the 30 | pygame.sprite.spritecollide function. 31 | """ 32 | def collide(one, two): 33 | return other.colliderect(two.rect) 34 | return collide 35 | 36 | 37 | class Player(pg.sprite.Sprite): 38 | def __init__(self, rect, speed, direction=pg.K_RIGHT): 39 | """ 40 | Arguments are a rect representing the Player's location and 41 | dimension, the speed(in pixels/frame) of the Player, and the Player's 42 | starting direction (given as a key-constant). 43 | """ 44 | pg.sprite.Sprite.__init__(self) 45 | self.rect = pg.Rect(rect) 46 | hit_size = int(0.6*self.rect.width), int(0.4*self.rect.height) 47 | self.hitrect = pg.Rect((0,0), hit_size) 48 | self.hitrect.midbottom = self.rect.midbottom 49 | self.speed = speed 50 | self.direction = direction 51 | self.old_direction = None #The Players previous direction every frame. 52 | self.direction_stack = [] #Held keys in the order they were pressed. 53 | self.redraw = False #Force redraw if needed. 54 | self.image = None 55 | self.frame = 0 56 | self.frames = self.get_frames() 57 | self.animate_timer = 0.0 58 | self.animate_fps = 7.0 59 | self.walkframes = [] 60 | self.walkframe_dict = self.make_frame_dict() 61 | self.adjust_images() 62 | 63 | def set_rects(self, value, attribute="topleft"): 64 | """ 65 | Set the position of both self.rect and self.hitrect together. 66 | The attribute of self.rect will be set to value; then the midbottom 67 | points will be set equal. 68 | """ 69 | setattr(self.rect, attribute, value) 70 | self.hitrect.midbottom = self.rect.midbottom 71 | 72 | def get_frames(self): 73 | """Get a list of all frames.""" 74 | sheet = SKEL_IMAGE 75 | indices = [[0,0], [1,0], [2,0], [3,0]] 76 | return get_images(sheet, indices, self.rect.size) 77 | 78 | def make_frame_dict(self): 79 | """ 80 | Create a dictionary of direction keys to frames. We can use 81 | transform functions to reduce the size of the sprite sheet we need. 82 | """ 83 | frames = {pg.K_LEFT : [self.frames[0], self.frames[1]], 84 | pg.K_RIGHT: [pg.transform.flip(self.frames[0], True, False), 85 | pg.transform.flip(self.frames[1], True, False)], 86 | pg.K_DOWN : [self.frames[3], 87 | pg.transform.flip(self.frames[3], True, False)], 88 | pg.K_UP : [self.frames[2], 89 | pg.transform.flip(self.frames[2], True, False)]} 90 | return frames 91 | 92 | def adjust_images(self): 93 | """Update the sprite's walkframes as the sprite's direction changes.""" 94 | if self.direction != self.old_direction: 95 | self.walkframes = self.walkframe_dict[self.direction] 96 | self.old_direction = self.direction 97 | self.redraw = True 98 | self.make_image() 99 | 100 | def make_image(self): 101 | """Update the sprite's animation as needed.""" 102 | now = pg.time.get_ticks() 103 | if self.redraw or now-self.animate_timer > 1000/self.animate_fps: 104 | if self.direction_stack: 105 | self.frame = (self.frame+1)%len(self.walkframes) 106 | self.image = self.walkframes[self.frame] 107 | self.animate_timer = now 108 | if not self.image: 109 | self.image = self.walkframes[self.frame] 110 | self.redraw = False 111 | 112 | def add_direction(self, key): 113 | """Add a pressed direction key on the direction stack.""" 114 | if key in DIRECT_DICT: 115 | if key in self.direction_stack: 116 | self.direction_stack.remove(key) 117 | self.direction_stack.append(key) 118 | self.direction = self.direction_stack[-1] 119 | 120 | def pop_direction(self, key): 121 | """Pop a released key from the direction stack.""" 122 | if key in DIRECT_DICT: 123 | if key in self.direction_stack: 124 | self.direction_stack.remove(key) 125 | if self.direction_stack: 126 | self.direction = self.direction_stack[-1] 127 | 128 | def update(self, obstacles): 129 | """Adjust the image and move as needed.""" 130 | self.adjust_images() 131 | if self.direction_stack: 132 | self.movement(obstacles, 0) 133 | self.movement(obstacles, 1) 134 | 135 | def movement(self, obstacles, i): 136 | """Move player and then check for collisions; adjust as necessary.""" 137 | direction_vector = DIRECT_DICT[self.direction] 138 | self.hitrect[i] += self.speed*direction_vector[i] 139 | callback = collide_other(self.hitrect) #Collidable callback created. 140 | collisions = pg.sprite.spritecollide(self, obstacles, False, callback) 141 | while collisions: 142 | collision = collisions.pop() 143 | self.adjust_on_collision(self.hitrect, collision, i) 144 | self.rect.midbottom = self.hitrect.midbottom 145 | 146 | def adjust_on_collision(self, rect_to_adjust, collide, i): 147 | """Adjust player's position if colliding with a solid block.""" 148 | if rect_to_adjust[i] < collide.rect[i]: 149 | rect_to_adjust[i] = collide.rect[i]-rect_to_adjust.size[i] 150 | else: 151 | rect_to_adjust[i] = collide.rect[i]+collide.rect.size[i] 152 | 153 | def draw(self, surface): 154 | """Draw method seperated out from update.""" 155 | surface.blit(self.image, self.rect) 156 | 157 | 158 | class Block(pg.sprite.Sprite): 159 | """Something to run head-first into.""" 160 | def __init__(self, location): 161 | """The location argument is where I will be located.""" 162 | pg.sprite.Sprite.__init__(self) 163 | self.image = self.make_image() 164 | self.rect = self.image.get_rect(topleft=location) 165 | 166 | def make_image(self): 167 | """Let's not forget aesthetics.""" 168 | image = pg.Surface((50,50)).convert_alpha() 169 | image.fill([random.randint(0, 255) for _ in range(3)]) 170 | image.blit(SHADE_MASK, (0,0)) 171 | return image 172 | 173 | 174 | class Control(object): 175 | """Being controlling is our job.""" 176 | def __init__(self): 177 | """Initialize standard attributes standardly.""" 178 | self.screen = pg.display.get_surface() 179 | self.screen_rect = self.screen.get_rect() 180 | self.clock = pg.time.Clock() 181 | self.fps = 60.0 182 | self.done = False 183 | self.keys = pg.key.get_pressed() 184 | self.player = Player((0,0,50,50), 3) 185 | self.player.set_rects(self.screen_rect.center, "center") 186 | self.obstacles = self.make_obstacles() 187 | 188 | def make_obstacles(self): 189 | """Prepare some obstacles for our player to collide with.""" 190 | obstacles = [Block((400,400)), Block((300,270)), Block((150,170))] 191 | for i in range(9): 192 | obstacles.append(Block((i*50,0))) 193 | obstacles.append(Block((450,50*i))) 194 | obstacles.append(Block((50+i*50,450))) 195 | obstacles.append(Block((0,50+50*i))) 196 | return pg.sprite.Group(obstacles) 197 | 198 | def event_loop(self): 199 | """Add/pop directions from player's direction stack as necessary.""" 200 | for event in pg.event.get(): 201 | self.keys = pg.key.get_pressed() 202 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 203 | self.done = True 204 | elif event.type == pg.KEYDOWN: 205 | self.player.add_direction(event.key) 206 | elif event.type == pg.KEYUP: 207 | self.player.pop_direction(event.key) 208 | 209 | def draw(self): 210 | """Draw all elements to the display surface.""" 211 | self.screen.fill(BACKGROUND_COLOR) 212 | self.obstacles.draw(self.screen) 213 | self.player.draw(self.screen) 214 | 215 | def display_fps(self): 216 | """Show the program's FPS in the window handle.""" 217 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 218 | pg.display.set_caption(caption) 219 | 220 | def main_loop(self): 221 | """Our main game loop; I bet you'd never have guessed.""" 222 | while not self.done: 223 | self.event_loop() 224 | self.player.update(self.obstacles) 225 | self.draw() 226 | pg.display.update() 227 | self.clock.tick(self.fps) 228 | self.display_fps() 229 | 230 | 231 | def get_images(sheet, frame_indices, size): 232 | """Get desired images from a sprite sheet.""" 233 | frames = [] 234 | for cell in frame_indices: 235 | frame_rect = ((size[0]*cell[0],size[1]*cell[1]), size) 236 | frames.append(sheet.subsurface(frame_rect)) 237 | return frames 238 | 239 | 240 | def main(): 241 | """Initialize, load our images, and run the program.""" 242 | global SKEL_IMAGE,SHADE_MASK 243 | os.environ['SDL_VIDEO_CENTERED'] = '1' 244 | pg.init() 245 | pg.display.set_caption(CAPTION) 246 | pg.display.set_mode(SCREEN_SIZE) 247 | SKEL_IMAGE = pg.image.load("skelly.png").convert() 248 | SKEL_IMAGE.set_colorkey(COLOR_KEY) 249 | SHADE_MASK = pg.image.load("shader.png").convert_alpha() 250 | run_it = Control() 251 | run_it.main_loop() 252 | pg.quit() 253 | sys.exit() 254 | 255 | 256 | if __name__ == "__main__": 257 | main() 258 | -------------------------------------------------------------------------------- /four_direction_movement/shader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/four_direction_movement/shader.png -------------------------------------------------------------------------------- /four_direction_movement/skelly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/four_direction_movement/skelly.png -------------------------------------------------------------------------------- /platforming/base_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/platforming/base_face.png -------------------------------------------------------------------------------- /platforming/fall_mask.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrates some simple platforming using mask collision. Shows the benefit 3 | of using pixel perfect collision detection. When using mask collision 4 | techniques, it is generally best to perfom initial rectangle collision tests 5 | before investing in the more expensive mask detection methods. 6 | 7 | Note that this code does not allow the sprite to traverse slopes. 8 | That will be addressed in a future example. 9 | 10 | -Written by Sean J. McKiernan 'Mekire' 11 | """ 12 | 13 | import os 14 | import sys 15 | import random 16 | import pygame as pg 17 | 18 | 19 | CAPTION = "Basic Platforming: Pixel Perfect Collision" 20 | SCREEN_SIZE = (700, 500) 21 | BACKGROUND_COLOR = (50, 50, 50) 22 | 23 | 24 | class _Physics(object): 25 | """ 26 | A simplified physics class. Using a 'real' gravity function here, though 27 | it is questionable whether or not it is worth the effort. Compare to the 28 | effect of gravity in fall_rect and decide for yourself. 29 | """ 30 | def __init__(self): 31 | """You can experiment with different gravity here.""" 32 | self.x_vel = self.y_vel = self.y_vel_i = 0 33 | self.grav = 20 34 | self.fall = False 35 | self.time = None 36 | 37 | def physics_update(self): 38 | """If the player is falling, calculate current y velocity.""" 39 | if self.fall: 40 | time_now = pg.time.get_ticks() 41 | if not self.time: 42 | self.time = time_now 43 | self.y_vel = self.grav*((time_now-self.time)/1000.0)+self.y_vel_i 44 | else: 45 | self.time = None 46 | self.y_vel = self.y_vel_i = 0 47 | 48 | 49 | class Player(_Physics, pg.sprite.Sprite): 50 | """Class representing our player.""" 51 | def __init__(self,location,speed): 52 | """ 53 | The location is an (x,y) coordinate pair, and speed is the player's 54 | speed in pixels per frame. Speed should be an integer. 55 | """ 56 | _Physics.__init__(self) 57 | pg.sprite.Sprite.__init__(self) 58 | self.image = PLAYER_IMAGE 59 | self.mask = pg.mask.from_surface(self.image) 60 | self.speed = speed 61 | self.jump_power = 10 62 | self.rect = self.image.get_rect(topleft=location) 63 | 64 | def get_position(self, obstacles): 65 | """Calculate the player's position this frame, including collisions.""" 66 | if not self.fall: 67 | self.check_falling(obstacles) 68 | else: 69 | self.fall = self.check_collisions((0,self.y_vel), 1, obstacles) 70 | if self.x_vel: 71 | self.check_collisions((self.x_vel,0), 0, obstacles) 72 | 73 | def check_falling(self, obstacles): 74 | """If player is not contacting the ground, enter fall state.""" 75 | self.rect.move_ip((0,1)) 76 | collisions = pg.sprite.spritecollide(self, obstacles, False) 77 | collidable = pg.sprite.collide_mask 78 | if not pg.sprite.spritecollideany(self, collisions, collidable): 79 | self.fall = True 80 | self.rect.move_ip((0,-1)) 81 | 82 | def check_collisions(self, offset, index, obstacles): 83 | """ 84 | This function checks if a collision would occur after moving offset 85 | pixels. If a collision is detected position is decremented by one 86 | pixel and retested. This continues until we find exactly how far we can 87 | safely move, or we decide we can't move. 88 | """ 89 | unaltered = True 90 | self.rect.move_ip(offset) 91 | collisions = pg.sprite.spritecollide(self, obstacles, False) 92 | collidable = pg.sprite.collide_mask 93 | while pg.sprite.spritecollideany(self, collisions, collidable): 94 | self.rect[index] += (1 if offset[index]<0 else -1) 95 | unaltered = False 96 | return unaltered 97 | 98 | def check_keys(self, keys): 99 | """Find the player's self.x_vel based on currently held keys.""" 100 | self.x_vel = 0 101 | if keys[pg.K_LEFT] or keys[pg.K_a]: 102 | self.x_vel -= self.speed 103 | if keys[pg.K_RIGHT] or keys[pg.K_d]: 104 | self.x_vel += self.speed 105 | 106 | def jump(self): 107 | """Called when the user presses the jump button.""" 108 | if not self.fall: 109 | self.y_vel_i = -self.jump_power 110 | self.fall = True 111 | 112 | def update(self, obstacles, keys): 113 | """Everything we need to stay updated.""" 114 | self.check_keys(keys) 115 | self.get_position(obstacles) 116 | self.physics_update() 117 | 118 | def draw(self,surface): 119 | """Blit the player to the target surface.""" 120 | surface.blit(self.image, self.rect) 121 | 122 | 123 | class Block(pg.sprite.Sprite): 124 | """A class representing solid obstacles.""" 125 | def __init__(self, location): 126 | """The location argument is an (x,y) coordinate pair.""" 127 | pg.sprite.Sprite.__init__(self) 128 | self.make_image() 129 | self.mask = pg.mask.from_surface(self.image) 130 | self.rect = pg.Rect(location, (50,50)) 131 | 132 | def make_image(self): 133 | """Something pretty to look at.""" 134 | color = [random.randint(0,255) for _ in range(3)] 135 | self.image = pg.Surface((50,50)).convert_alpha() 136 | self.image.fill(color) 137 | self.image.blit(SHADE_IMG, (0,0)) 138 | 139 | 140 | class Control(object): 141 | """Class for managing event loop and game states.""" 142 | def __init__(self): 143 | """Nothing to see here folks. Move along.""" 144 | self.screen = pg.display.get_surface() 145 | self.clock = pg.time.Clock() 146 | self.fps = 60.0 147 | self.keys = pg.key.get_pressed() 148 | self.done = False 149 | self.player = Player((50,-25), 4) 150 | self.obstacles = self.make_obstacles() 151 | 152 | def make_obstacles(self): 153 | """Adds some arbitrarily placed obstacles to a sprite.Group.""" 154 | obstacles = [Block((400,400)), Block((300,270)), Block((150,170))] 155 | obstacles += [Block((500+50*i,220)) for i in range(3)] 156 | for i in range(12): 157 | obstacles.append(Block((50+i*50,450))) 158 | obstacles.append(Block((100+i*50,0))) 159 | obstacles.append(Block((0,50*i))) 160 | obstacles.append(Block((650,50*i))) 161 | return pg.sprite.Group(obstacles) 162 | 163 | def event_loop(self): 164 | """We can always quit, and the player can sometimes jump.""" 165 | for event in pg.event.get(): 166 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 167 | self.done = True 168 | elif event.type == pg.KEYDOWN: 169 | if event.key == pg.K_SPACE: 170 | self.player.jump() 171 | 172 | def update(self): 173 | """Update held keys and the player.""" 174 | self.keys = pg.key.get_pressed() 175 | self.player.update(self.obstacles, self.keys) 176 | 177 | def draw(self): 178 | """Draw all necessary objects to the display surface.""" 179 | self.screen.fill(BACKGROUND_COLOR) 180 | self.obstacles.draw(self.screen) 181 | self.player.draw(self.screen) 182 | 183 | def display_fps(self): 184 | """Show the programs FPS in the window handle.""" 185 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 186 | pg.display.set_caption(caption) 187 | 188 | def main_loop(self): 189 | """As simple as it gets.""" 190 | while not self.done: 191 | self.event_loop() 192 | self.update() 193 | self.draw() 194 | pg.display.update() 195 | self.clock.tick(self.fps) 196 | self.display_fps() 197 | 198 | 199 | if __name__ == "__main__": 200 | os.environ['SDL_VIDEO_CENTERED'] = '1' 201 | pg.init() 202 | pg.display.set_caption(CAPTION) 203 | pg.display.set_mode(SCREEN_SIZE) 204 | PLAYER_IMAGE = pg.image.load("smallface.png").convert_alpha() 205 | SHADE_IMG = pg.image.load("shader.png").convert_alpha() 206 | run_it = Control() 207 | run_it.main_loop() 208 | pg.quit() 209 | sys.exit() 210 | -------------------------------------------------------------------------------- /platforming/fall_rect.py: -------------------------------------------------------------------------------- 1 | """ 2 | This shows some basic platforming using only rectangle collision. The intent 3 | is to demonstrate what rectangle collision lacks. This version uses collision 4 | functions from pygame.sprite, but no pixel-perfect collision. 5 | 6 | -Written by Sean J. McKiernan 'Mekire' 7 | """ 8 | 9 | import os 10 | import sys 11 | import random 12 | import pygame as pg 13 | 14 | 15 | CAPTION = "Basic Platforming: Rectangle Collision" 16 | SCREEN_SIZE = (700, 500) 17 | BACKGROUND_COLOR = (50, 50, 50) 18 | 19 | 20 | class _Physics(object): 21 | """A simplified physics class. Psuedo-gravity is often good enough.""" 22 | def __init__(self): 23 | """You can experiment with different gravity here.""" 24 | self.x_vel = self.y_vel = 0 25 | self.grav = 0.22 26 | self.fall = False 27 | 28 | def physics_update(self): 29 | """If the player is falling, add gravity to the current y velocity.""" 30 | if self.fall: 31 | self.y_vel += self.grav 32 | else: 33 | self.y_vel = 0 34 | 35 | 36 | class Player(_Physics, pg.sprite.Sprite): 37 | """Class representing our player.""" 38 | def __init__(self, location, speed): 39 | """ 40 | The location is an (x,y) coordinate pair, and speed is the player's 41 | speed in pixels per frame. Speed should be an integer. 42 | """ 43 | _Physics.__init__(self) 44 | pg.sprite.Sprite.__init__(self) 45 | self.image = PLAYER_IMAGE 46 | self.speed = speed 47 | self.jump_power = -8.5 48 | self.rect = self.image.get_rect(topleft=location) 49 | 50 | def get_position(self, obstacles): 51 | """Calculate the player's position this frame, including collisions.""" 52 | if not self.fall: 53 | self.check_falling(obstacles) 54 | else: 55 | self.fall = self.check_collisions((0,self.y_vel) , 1, obstacles) 56 | if self.x_vel: 57 | self.check_collisions((self.x_vel,0), 0, obstacles) 58 | 59 | def check_falling(self, obstacles): 60 | """If player is not contacting the ground, enter fall state.""" 61 | self.rect.move_ip((0,1)) 62 | if not pg.sprite.spritecollideany(self, obstacles): 63 | self.fall = True 64 | self.rect.move_ip((0,-1)) 65 | 66 | def check_collisions(self, offset, index, obstacles): 67 | """This function checks if a collision would occur after moving offset 68 | pixels. If a collision is detected position is decremented by one 69 | pixel and retested. This continues until we find exactly how far we can 70 | safely move, or we decide we can't move.""" 71 | unaltered = True 72 | self.rect.move_ip(offset) 73 | while pg.sprite.spritecollideany(self, obstacles): 74 | self.rect[index] += (1 if offset[index]<0 else -1) 75 | unaltered = False 76 | return unaltered 77 | 78 | def check_keys(self, keys): 79 | """Find the player's self.x_vel based on currently held keys.""" 80 | self.x_vel = 0 81 | if keys[pg.K_LEFT] or keys[pg.K_a]: 82 | self.x_vel -= self.speed 83 | if keys[pg.K_RIGHT] or keys[pg.K_d]: 84 | self.x_vel += self.speed 85 | 86 | def jump(self): 87 | """Called when the user presses the jump button.""" 88 | if not self.fall: 89 | self.y_vel = self.jump_power 90 | self.fall = True 91 | 92 | def update(self, obstacles, keys): 93 | """Everything we need to stay updated.""" 94 | self.check_keys(keys) 95 | self.get_position(obstacles) 96 | self.physics_update() 97 | 98 | def draw(self,surface): 99 | """Blit the player to the target surface.""" 100 | surface.blit(self.image, self.rect) 101 | 102 | 103 | class Block(pg.sprite.Sprite): 104 | """A class representing solid obstacles.""" 105 | def __init__(self, location): 106 | """The location argument is an (x,y) coordinate pair.""" 107 | pg.sprite.Sprite.__init__(self) 108 | self.make_image() 109 | self.rect = pg.Rect(location, (50,50)) 110 | 111 | def make_image(self): 112 | """Something pretty to look at.""" 113 | color = [random.randint(0, 255) for _ in range(3)] 114 | self.image = pg.Surface((50,50)).convert() 115 | self.image.fill(color) 116 | self.image.blit(SHADE_IMG, (0,0)) 117 | 118 | 119 | class Control(object): 120 | """Class for managing event loop and game states.""" 121 | def __init__(self): 122 | """Nothing to see here folks. Move along.""" 123 | self.screen = pg.display.get_surface() 124 | self.clock = pg.time.Clock() 125 | self.fps = 60.0 126 | self.keys = pg.key.get_pressed() 127 | self.done = False 128 | self.player = Player((50,-25), 4) 129 | self.obstacles = self.make_obstacles() 130 | 131 | def make_obstacles(self): 132 | """Adds some arbitrarily placed obstacles to a sprite.Group.""" 133 | obstacles = [Block((400,400)), Block((300,270)), Block((150,170))] 134 | obstacles += [Block((500+50*i,220)) for i in range(3)] 135 | for i in range(12): 136 | obstacles.append(Block((50+i*50,450))) 137 | obstacles.append(Block((100+i*50,0))) 138 | obstacles.append(Block((0,50*i))) 139 | obstacles.append(Block((650,50*i))) 140 | return pg.sprite.Group(obstacles) 141 | 142 | def event_loop(self): 143 | """We can always quit, and the player can sometimes jump.""" 144 | for event in pg.event.get(): 145 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 146 | self.done = True 147 | elif event.type == pg.KEYDOWN: 148 | if event.key == pg.K_SPACE: 149 | self.player.jump() 150 | 151 | def update(self): 152 | """Update held keys and the player.""" 153 | self.keys = pg.key.get_pressed() 154 | self.player.update(self.obstacles, self.keys) 155 | 156 | def draw(self): 157 | """Draw all necessary objects to the display surface.""" 158 | self.screen.fill(BACKGROUND_COLOR) 159 | self.obstacles.draw(self.screen) 160 | self.player.draw(self.screen) 161 | 162 | def display_fps(self): 163 | """Show the programs FPS in the window handle.""" 164 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 165 | pg.display.set_caption(caption) 166 | 167 | def main_loop(self): 168 | """As simple as it gets.""" 169 | while not self.done: 170 | self.event_loop() 171 | self.update() 172 | self.draw() 173 | pg.display.update() 174 | self.clock.tick(self.fps) 175 | self.display_fps() 176 | 177 | 178 | if __name__ == "__main__": 179 | os.environ['SDL_VIDEO_CENTERED'] = '1' 180 | pg.init() 181 | pg.display.set_caption(CAPTION) 182 | pg.display.set_mode(SCREEN_SIZE) 183 | PLAYER_IMAGE = pg.image.load("smallface.png").convert_alpha() 184 | SHADE_IMG = pg.image.load("shader.png").convert_alpha() 185 | run_it = Control() 186 | run_it.main_loop() 187 | pg.quit() 188 | sys.exit() 189 | -------------------------------------------------------------------------------- /platforming/fall_rotate.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is identical to fall_mask, except that the sprite spins when moving 3 | while contacting the ground. Just for fun. Shows how to cache rotation frames 4 | for times when you find rotation is too expensive to repeat. 5 | 6 | -Written by Sean J. McKiernan 'Mekire' 7 | """ 8 | 9 | import os 10 | import sys 11 | import random 12 | import pygame as pg 13 | 14 | 15 | CAPTION = "Basic Platforming: Rotating our face" 16 | SCREEN_SIZE = (700, 500) 17 | BACKGROUND_COLOR = (50, 50, 50) 18 | 19 | 20 | class _Physics(object): 21 | """ 22 | A simplified physics class. Using a 'real' gravity function here, though 23 | it is questionable whether or not it is worth the effort. Compare to the 24 | effect of gravity in fall_rect and decide for yourself. 25 | """ 26 | def __init__(self): 27 | """You can experiment with different gravity here.""" 28 | self.x_vel = self.y_vel = self.y_vel_i = 0 29 | self.grav = 20 30 | self.fall = False 31 | self.time = None 32 | 33 | def physics_update(self): 34 | """If the player is falling, calculate current y velocity.""" 35 | if self.fall: 36 | time_now = pg.time.get_ticks() 37 | if not self.time: 38 | self.time = time_now 39 | self.y_vel = self.grav*((time_now-self.time)/1000.0)+self.y_vel_i 40 | else: 41 | self.time = None 42 | self.y_vel = self.y_vel_i = 0 43 | 44 | 45 | class Player(_Physics, pg.sprite.Sprite): 46 | """Class representing our player.""" 47 | rotation_cache = {} 48 | 49 | def __init__(self, location, speed): 50 | """ 51 | The location is an (x,y) coordinate pair, and speed is the player's 52 | speed in pixels per frame. Speed should be an integer. 53 | """ 54 | _Physics.__init__(self) 55 | pg.sprite.Sprite.__init__(self) 56 | self.angle = 0 57 | self.rect = BASEFACE.get_rect(topleft=location) 58 | self.image = self.make_image() 59 | self.mask = pg.mask.from_surface(BASEFACE) 60 | self.speed = speed 61 | self.jump_power = 10 62 | 63 | def make_image(self): 64 | """ 65 | Rotate the player's image and cache the surface so that we don't 66 | need to perform identical rotations more than once. 67 | """ 68 | if self.angle in Player.rotation_cache: 69 | image = Player.rotation_cache[self.angle] 70 | else: 71 | image = pg.Surface((self.rect.size)).convert_alpha() 72 | image_rect = image.get_rect() 73 | image.fill((0,0,0,0)) 74 | image.blit(BASEFACE, (0,0)) 75 | face = pg.transform.rotozoom(FACE, self.angle, 1) 76 | face_rect = face.get_rect(center=image_rect.center) 77 | image.blit(face, face_rect) 78 | Player.rotation_cache[self.angle] = image 79 | return image 80 | 81 | def get_position(self, obstacles): 82 | """Calculate the player's position this frame, including collisions.""" 83 | if not self.fall: 84 | self.check_falling(obstacles) 85 | else: 86 | self.fall = self.check_collisions((0,self.y_vel), 1, obstacles) 87 | if self.x_vel: 88 | self.check_collisions((self.x_vel,0), 0, obstacles) 89 | 90 | def check_falling(self, obstacles): 91 | """If player is not contacting the ground, enter fall state.""" 92 | self.rect.move_ip((0,1)) 93 | collisions = pg.sprite.spritecollide(self, obstacles, False) 94 | collidable = pg.sprite.collide_mask 95 | if not pg.sprite.spritecollideany(self, collisions, collidable): 96 | self.fall = True 97 | self.rect.move_ip((0,-1)) 98 | 99 | def check_collisions(self, offset, index, obstacles): 100 | """ 101 | This function checks if a collision would occur after moving offset 102 | pixels. If a collision is detected position is decremented by one 103 | pixel and retested. This continues until we find exactly how far we can 104 | safely move, or we decide we can't move. 105 | """ 106 | unaltered = True 107 | self.rect.move_ip(offset) 108 | collisions = pg.sprite.spritecollide(self, obstacles, False) 109 | collidable = pg.sprite.collide_mask 110 | while pg.sprite.spritecollideany(self, collisions, collidable): 111 | self.rect[index] += (1 if offset[index]<0 else -1) 112 | unaltered = False 113 | return unaltered 114 | 115 | def check_keys(self, keys): 116 | """Find the player's self.x_vel based on currently held keys.""" 117 | self.x_vel = 0 118 | if keys[pg.K_LEFT] or keys[pg.K_a]: 119 | self.x_vel -= self.speed 120 | if keys[pg.K_RIGHT] or keys[pg.K_d]: 121 | self.x_vel += self.speed 122 | 123 | def rotate(self): 124 | """Rotate the player's image if contacting the ground and moving.""" 125 | if not self.fall and self.x_vel: 126 | self.angle = (self.angle-self.x_vel)%360 127 | self.image = self.make_image() 128 | 129 | def jump(self): 130 | """Called when the user presses the jump button.""" 131 | if not self.fall: 132 | self.y_vel_i = -self.jump_power 133 | self.fall = True 134 | 135 | def update(self, obstacles, keys): 136 | """Everything we need to stay updated.""" 137 | self.check_keys(keys) 138 | self.get_position(obstacles) 139 | self.physics_update() 140 | self.rotate() 141 | 142 | def draw(self, surface): 143 | """Blit the player to the target surface.""" 144 | blit_rect = self.image.get_rect(center=self.rect.center) 145 | surface.blit(self.image, blit_rect) 146 | 147 | 148 | class Block(pg.sprite.Sprite): 149 | """A class representing solid obstacles.""" 150 | def __init__(self, location): 151 | """The location argument is an (x,y) coordinate pair.""" 152 | pg.sprite.Sprite.__init__(self) 153 | self.make_image() 154 | self.mask = pg.mask.from_surface(self.image) 155 | self.rect = pg.Rect(location, (50,50)) 156 | 157 | def make_image(self): 158 | """Something pretty to look at.""" 159 | color = [random.randint(0, 255) for _ in range(3)] 160 | self.image = pg.Surface((50,50)).convert_alpha() 161 | self.image.fill(color) 162 | self.image.blit(SHADE_IMG, (0,0)) 163 | 164 | 165 | class Control(object): 166 | """Class for managing event loop and game states.""" 167 | def __init__(self): 168 | """Nothing to see here folks. Move along.""" 169 | self.screen = pg.display.get_surface() 170 | self.clock = pg.time.Clock() 171 | self.fps = 60.0 172 | self.keys = pg.key.get_pressed() 173 | self.done = False 174 | self.player = Player((50,-25), 4) 175 | self.obstacles = self.make_obstacles() 176 | 177 | def make_obstacles(self): 178 | """Adds some arbitrarily placed obstacles to a sprite.Group.""" 179 | obstacles = [Block((400,400)), Block((300,270)), Block((150,170))] 180 | obstacles += [Block((500+50*i,220)) for i in range(3)] 181 | for i in range(12): 182 | obstacles.append(Block((50+i*50,450))) 183 | obstacles.append(Block((100+i*50,0))) 184 | obstacles.append(Block((0,50*i))) 185 | obstacles.append(Block((650,50*i))) 186 | return pg.sprite.Group(obstacles) 187 | 188 | def event_loop(self): 189 | """We can always quit, and the player can sometimes jump.""" 190 | for event in pg.event.get(): 191 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 192 | self.done = True 193 | elif event.type == pg.KEYDOWN: 194 | if event.key == pg.K_SPACE: 195 | self.player.jump() 196 | 197 | def update(self): 198 | """Update held keys and the player.""" 199 | self.keys = pg.key.get_pressed() 200 | self.player.update(self.obstacles, self.keys) 201 | 202 | def draw(self): 203 | """Draw all necessary objects to the display surface.""" 204 | self.screen.fill(BACKGROUND_COLOR) 205 | self.obstacles.draw(self.screen) 206 | self.player.draw(self.screen) 207 | 208 | def display_fps(self): 209 | """Show the programs FPS in the window handle.""" 210 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 211 | pg.display.set_caption(caption) 212 | 213 | def main_loop(self): 214 | """As simple as it gets.""" 215 | while not self.done: 216 | self.event_loop() 217 | self.update() 218 | self.draw() 219 | pg.display.update() 220 | self.clock.tick(self.fps) 221 | self.display_fps() 222 | 223 | 224 | if __name__ == "__main__": 225 | os.environ['SDL_VIDEO_CENTERED'] = '1' 226 | pg.init() 227 | pg.display.set_caption(CAPTION) 228 | pg.display.set_mode(SCREEN_SIZE) 229 | BASEFACE = pg.image.load("base_face.png").convert_alpha() 230 | FACE = pg.image.load("just_face.png").convert_alpha() 231 | SHADE_IMG = pg.image.load("shader.png").convert_alpha() 232 | run_it = Control() 233 | run_it.main_loop() 234 | pg.quit() 235 | sys.exit() 236 | -------------------------------------------------------------------------------- /platforming/just_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/platforming/just_face.png -------------------------------------------------------------------------------- /platforming/moving_platforms.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic moving platforms using only rectangle collision. 3 | 4 | -Written by Sean J. McKiernan 'Mekire' 5 | """ 6 | 7 | import os 8 | import sys 9 | import pygame as pg 10 | 11 | 12 | CAPTION = "Moving Platforms" 13 | SCREEN_SIZE = (700,500) 14 | 15 | 16 | class _Physics(object): 17 | """A simplified physics class. Psuedo-gravity is often good enough.""" 18 | def __init__(self): 19 | """You can experiment with different gravity here.""" 20 | self.x_vel = self.y_vel = 0 21 | self.grav = 0.4 22 | self.fall = False 23 | 24 | def physics_update(self): 25 | """If the player is falling, add gravity to the current y velocity.""" 26 | if self.fall: 27 | self.y_vel += self.grav 28 | else: 29 | self.y_vel = 0 30 | 31 | 32 | class Player(_Physics, pg.sprite.Sprite): 33 | """Class representing our player.""" 34 | def __init__(self,location,speed): 35 | """ 36 | The location is an (x,y) coordinate pair, and speed is the player's 37 | speed in pixels per frame. Speed should be an integer. 38 | """ 39 | _Physics.__init__(self) 40 | pg.sprite.Sprite.__init__(self) 41 | self.image = pg.Surface((30,55)).convert() 42 | self.image.fill(pg.Color("red")) 43 | self.rect = self.image.get_rect(topleft=location) 44 | self.speed = speed 45 | self.jump_power = -9.0 46 | self.jump_cut_magnitude = -3.0 47 | self.on_moving = False 48 | self.collide_below = False 49 | 50 | def check_keys(self, keys): 51 | """Find the player's self.x_vel based on currently held keys.""" 52 | self.x_vel = 0 53 | if keys[pg.K_LEFT] or keys[pg.K_a]: 54 | self.x_vel -= self.speed 55 | if keys[pg.K_RIGHT] or keys[pg.K_d]: 56 | self.x_vel += self.speed 57 | 58 | def get_position(self, obstacles): 59 | """Calculate the player's position this frame, including collisions.""" 60 | if not self.fall: 61 | self.check_falling(obstacles) 62 | else: 63 | self.fall = self.check_collisions((0,self.y_vel), 1, obstacles) 64 | if self.x_vel: 65 | self.check_collisions((self.x_vel,0), 0, obstacles) 66 | 67 | def check_falling(self, obstacles): 68 | """If player is not contacting the ground, enter fall state.""" 69 | if not self.collide_below: 70 | self.fall = True 71 | self.on_moving = False 72 | 73 | def check_moving(self,obstacles): 74 | """ 75 | Check if the player is standing on a moving platform. 76 | If the player is in contact with multiple platforms, the prevously 77 | detected platform will take presidence. 78 | """ 79 | if not self.fall: 80 | now_moving = self.on_moving 81 | any_moving, any_non_moving = [], [] 82 | for collide in self.collide_below: 83 | if collide.type == "moving": 84 | self.on_moving = collide 85 | any_moving.append(collide) 86 | else: 87 | any_non_moving.append(collide) 88 | if not any_moving: 89 | self.on_moving = False 90 | elif any_non_moving or now_moving in any_moving: 91 | self.on_moving = now_moving 92 | 93 | def check_collisions(self, offset, index, obstacles): 94 | """ 95 | This function checks if a collision would occur after moving offset 96 | pixels. If a collision is detected, the position is decremented by one 97 | pixel and retested. This continues until we find exactly how far we can 98 | safely move, or we decide we can't move. 99 | """ 100 | unaltered = True 101 | self.rect[index] += offset[index] 102 | while pg.sprite.spritecollideany(self, obstacles): 103 | self.rect[index] += (1 if offset[index]<0 else -1) 104 | unaltered = False 105 | return unaltered 106 | 107 | def check_above(self, obstacles): 108 | """When jumping, don't enter fall state if there is no room to jump.""" 109 | self.rect.move_ip(0, -1) 110 | collide = pg.sprite.spritecollideany(self, obstacles) 111 | self.rect.move_ip(0, 1) 112 | return collide 113 | 114 | def check_below(self, obstacles): 115 | """Check to see if the player is contacting the ground.""" 116 | self.rect.move_ip((0,1)) 117 | collide = pg.sprite.spritecollide(self, obstacles, False) 118 | self.rect.move_ip((0,-1)) 119 | return collide 120 | 121 | def jump(self, obstacles): 122 | """Called when the user presses the jump button.""" 123 | if not self.fall and not self.check_above(obstacles): 124 | self.y_vel = self.jump_power 125 | self.fall = True 126 | self.on_moving = False 127 | 128 | def jump_cut(self): 129 | """Called if player releases the jump key before maximum height.""" 130 | if self.fall: 131 | if self.y_vel < self.jump_cut_magnitude: 132 | self.y_vel = self.jump_cut_magnitude 133 | 134 | def pre_update(self, obstacles): 135 | """Ran before platforms are updated.""" 136 | self.collide_below = self.check_below(obstacles) 137 | self.check_moving(obstacles) 138 | 139 | def update(self, obstacles, keys): 140 | """Everything we need to stay updated; ran after platforms update.""" 141 | self.check_keys(keys) 142 | self.get_position(obstacles) 143 | self.physics_update() 144 | 145 | def draw(self, surface): 146 | """Blit the player to the target surface.""" 147 | surface.blit(self.image, self.rect) 148 | 149 | 150 | class Block(pg.sprite.Sprite): 151 | """A class representing solid obstacles.""" 152 | def __init__(self, color, rect): 153 | """The color is an (r,g,b) tuple; rect is a rect-style argument.""" 154 | pg.sprite.Sprite.__init__(self) 155 | self.rect = pg.Rect(rect) 156 | self.image = pg.Surface(self.rect.size).convert() 157 | self.image.fill(color) 158 | self.type = "normal" 159 | 160 | 161 | class MovingBlock(Block): 162 | """A class to represent horizontally and vertically moving blocks.""" 163 | def __init__(self, color, rect, end, axis, delay=500, speed=2, start=None): 164 | """ 165 | The moving block will travel in the direction of axis (0 or 1) 166 | between rect.topleft and end. The delay argument is the amount of time 167 | (in miliseconds) to pause when reaching an endpoint; speed is the 168 | platforms speed in pixels/frame; if specified start is the place 169 | within the blocks path to start (defaulting to rect.topleft). 170 | """ 171 | Block.__init__(self, color, rect) 172 | self.start = self.rect[axis] 173 | if start: 174 | self.rect[axis] = start 175 | self.axis = axis 176 | self.end = end 177 | self.timer = 0.0 178 | self.delay = delay 179 | self.speed = speed 180 | self.waiting = False 181 | self.type = "moving" 182 | 183 | def update(self, player, obstacles): 184 | """Update position. This should be done before moving any actors.""" 185 | obstacles = obstacles.copy() 186 | obstacles.remove(self) 187 | now = pg.time.get_ticks() 188 | if not self.waiting: 189 | speed = self.speed 190 | start_passed = self.start >= self.rect[self.axis]+speed 191 | end_passed = self.end <= self.rect[self.axis]+speed 192 | if start_passed or end_passed: 193 | if start_passed: 194 | speed = self.start-self.rect[self.axis] 195 | else: 196 | speed = self.end-self.rect[self.axis] 197 | self.change_direction(now) 198 | self.rect[self.axis] += speed 199 | self.move_player(now, player, obstacles, speed) 200 | elif now-self.timer > self.delay: 201 | self.waiting = False 202 | 203 | def move_player(self, now, player, obstacles, speed): 204 | """ 205 | Moves the player both when on top of, or bumped by the platform. 206 | Collision checks are in place to prevent the block pushing the player 207 | through a wall. 208 | """ 209 | if player.on_moving is self or pg.sprite.collide_rect(self,player): 210 | axis = self.axis 211 | offset = (speed, speed) 212 | player.check_collisions(offset, axis, obstacles) 213 | if pg.sprite.collide_rect(self, player): 214 | if self.speed > 0: 215 | self.rect[axis] = player.rect[axis]-self.rect.size[axis] 216 | else: 217 | self.rect[axis] = player.rect[axis]+player.rect.size[axis] 218 | self.change_direction(now) 219 | 220 | def change_direction(self, now): 221 | """Called when the platform reaches an endpoint or has no more room.""" 222 | self.waiting = True 223 | self.timer = now 224 | self.speed *= -1 225 | 226 | 227 | class Control(object): 228 | """Class for managing event loop and game states.""" 229 | def __init__(self): 230 | """Initalize the display and prepare game objects.""" 231 | self.screen = pg.display.get_surface() 232 | self.screen_rect = self.screen.get_rect() 233 | self.clock = pg.time.Clock() 234 | self.fps = 60.0 235 | self.keys = pg.key.get_pressed() 236 | self.done = False 237 | self.player = Player((50,875), 4) 238 | self.viewport = self.screen.get_rect() 239 | self.level = pg.Surface((1000,1000)).convert() 240 | self.level_rect = self.level.get_rect() 241 | self.win_text,self.win_rect = self.make_text() 242 | self.obstacles = self.make_obstacles() 243 | 244 | def make_text(self): 245 | """Renders a text object. Text is only rendered once.""" 246 | font = pg.font.Font(None, 100) 247 | message = "You win. Celebrate." 248 | text = font.render(message, True, (100,100,175)) 249 | rect = text.get_rect(centerx=self.level_rect.centerx, y=100) 250 | return text, rect 251 | 252 | def make_obstacles(self): 253 | """Adds some arbitrarily placed obstacles to a sprite.Group.""" 254 | walls = [Block(pg.Color("chocolate"), (0,980,1000,20)), 255 | Block(pg.Color("chocolate"), (0,0,20,1000)), 256 | Block(pg.Color("chocolate"), (980,0,20,1000))] 257 | static = [Block(pg.Color("darkgreen"), (250,780,200,100)), 258 | Block(pg.Color("darkgreen"), (600,880,200,100)), 259 | Block(pg.Color("darkgreen"), (20,360,880,40)), 260 | Block(pg.Color("darkgreen"), (950,400,30,20)), 261 | Block(pg.Color("darkgreen"), (20,630,50,20)), 262 | Block(pg.Color("darkgreen"), (80,530,50,20)), 263 | Block(pg.Color("darkgreen"), (130,470,200,215)), 264 | Block(pg.Color("darkgreen"), (20,760,30,20)), 265 | Block(pg.Color("darkgreen"), (400,740,30,40))] 266 | moving = [MovingBlock(pg.Color("olivedrab"), (20,740,75,20), 325, 0), 267 | MovingBlock(pg.Color("olivedrab"), (600,500,100,20), 880, 0), 268 | MovingBlock(pg.Color("olivedrab"), 269 | (420,430,100,20), 550, 1, speed=3, delay=200), 270 | MovingBlock(pg.Color("olivedrab"), 271 | (450,700,50,20), 930, 1, start=930), 272 | MovingBlock(pg.Color("olivedrab"), 273 | (500,700,50,20), 730, 0, start=730), 274 | MovingBlock(pg.Color("olivedrab"), 275 | (780,700,50,20), 895, 0, speed=-1)] 276 | return pg.sprite.Group(walls, static, moving) 277 | 278 | def update_viewport(self): 279 | """ 280 | The viewport will stay centered on the player unless the player 281 | approaches the edge of the map. 282 | """ 283 | self.viewport.center = self.player.rect.center 284 | self.viewport.clamp_ip(self.level_rect) 285 | 286 | def event_loop(self): 287 | """We can always quit, and the player can sometimes jump.""" 288 | for event in pg.event.get(): 289 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 290 | self.done = True 291 | elif event.type == pg.KEYDOWN: 292 | if event.key == pg.K_SPACE: 293 | self.player.jump(self.obstacles) 294 | elif event.type == pg.KEYUP: 295 | if event.key == pg.K_SPACE: 296 | self.player.jump_cut() 297 | 298 | def update(self): 299 | """Update the player, obstacles, and current viewport.""" 300 | self.keys = pg.key.get_pressed() 301 | self.player.pre_update(self.obstacles) 302 | self.obstacles.update(self.player, self.obstacles) 303 | self.player.update(self.obstacles, self.keys) 304 | self.update_viewport() 305 | 306 | def draw(self): 307 | """ 308 | Draw all necessary objects to the level surface, and then draw 309 | the viewport section of the level to the display surface. 310 | """ 311 | self.level.fill(pg.Color("lightblue")) 312 | self.obstacles.draw(self.level) 313 | self.level.blit(self.win_text, self.win_rect) 314 | self.player.draw(self.level) 315 | self.screen.blit(self.level, (0,0), self.viewport) 316 | 317 | def display_fps(self): 318 | """Show the programs FPS in the window handle.""" 319 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 320 | pg.display.set_caption(caption) 321 | 322 | def main_loop(self): 323 | """As simple as it gets.""" 324 | while not self.done: 325 | self.event_loop() 326 | self.update() 327 | self.draw() 328 | pg.display.update() 329 | self.clock.tick(self.fps) 330 | self.display_fps() 331 | 332 | 333 | if __name__ == "__main__": 334 | os.environ['SDL_VIDEO_CENTERED'] = '1' 335 | pg.init() 336 | pg.display.set_caption(CAPTION) 337 | pg.display.set_mode(SCREEN_SIZE) 338 | run_it = Control() 339 | run_it.main_loop() 340 | pg.quit() 341 | sys.exit() 342 | -------------------------------------------------------------------------------- /platforming/moving_platforms_ease.py: -------------------------------------------------------------------------------- 1 | """ 2 | This program is almost identical to moving_platforms.py, but demonstrates a 3 | different Control.update_viewport() method that allows us to ease in to 4 | scrolling. 5 | 6 | Here scrolling begins when the player crosses 1/3rd of the screen. 7 | Scrolling begins at half the speed of the player, but once the player reaches 8 | the halfway point scrolling matches the player's own speed. 9 | 10 | -Written by Sean J. McKiernan 'Mekire' 11 | """ 12 | 13 | import os 14 | import sys 15 | import pygame as pg 16 | 17 | 18 | CAPTION = "Moving Platforms" 19 | SCREEN_SIZE = (700,500) 20 | 21 | 22 | class _Physics(object): 23 | """A simplified physics class. Psuedo-gravity is often good enough.""" 24 | def __init__(self): 25 | """You can experiment with different gravity here.""" 26 | self.velocity = [0, 0] 27 | self.grav = 0.4 28 | self.fall = False 29 | 30 | def physics_update(self): 31 | """If the player is falling, add gravity to the current y velocity.""" 32 | if self.fall: 33 | self.velocity[1] += self.grav 34 | else: 35 | self.velocity[1] = 0 36 | 37 | 38 | class Player(_Physics, pg.sprite.Sprite): 39 | """Class representing our player.""" 40 | def __init__(self,location,speed): 41 | """ 42 | The location is an (x,y) coordinate pair, and speed is the player's 43 | speed in pixels per frame. Speed should be an integer. 44 | """ 45 | _Physics.__init__(self) 46 | pg.sprite.Sprite.__init__(self) 47 | self.image = pg.Surface((30,55)).convert() 48 | self.image.fill(pg.Color("red")) 49 | self.rect = self.image.get_rect(topleft=location) 50 | self.frame_start_pos = None 51 | self.total_displacement = None 52 | self.speed = speed 53 | self.jump_power = -9.0 54 | self.jump_cut_magnitude = -3.0 55 | self.on_moving = False 56 | self.collide_below = False 57 | 58 | def check_keys(self, keys): 59 | """Find the player's self.velocity[0] based on currently held keys.""" 60 | self.velocity[0] = 0 61 | if keys[pg.K_LEFT] or keys[pg.K_a]: 62 | self.velocity[0] -= self.speed 63 | if keys[pg.K_RIGHT] or keys[pg.K_d]: 64 | self.velocity[0] += self.speed 65 | 66 | def get_position(self, obstacles): 67 | """Calculate the player's position this frame, including collisions.""" 68 | if not self.fall: 69 | self.check_falling(obstacles) 70 | else: 71 | self.fall = self.check_collisions((0,self.velocity[1]),1,obstacles) 72 | if self.velocity[0]: 73 | self.check_collisions((self.velocity[0],0), 0, obstacles) 74 | 75 | def check_falling(self, obstacles): 76 | """If player is not contacting the ground, enter fall state.""" 77 | if not self.collide_below: 78 | self.fall = True 79 | self.on_moving = False 80 | 81 | def check_moving(self,obstacles): 82 | """ 83 | Check if the player is standing on a moving platform. 84 | If the player is in contact with multiple platforms, the prevously 85 | detected platform will take presidence. 86 | """ 87 | if not self.fall: 88 | now_moving = self.on_moving 89 | any_moving, any_non_moving = [], [] 90 | for collide in self.collide_below: 91 | if collide.type == "moving": 92 | self.on_moving = collide 93 | any_moving.append(collide) 94 | else: 95 | any_non_moving.append(collide) 96 | if not any_moving: 97 | self.on_moving = False 98 | elif any_non_moving or now_moving in any_moving: 99 | self.on_moving = now_moving 100 | 101 | def check_collisions(self, offset, index, obstacles): 102 | """ 103 | This function checks if a collision would occur after moving offset 104 | pixels. If a collision is detected, the position is decremented by one 105 | pixel and retested. This continues until we find exactly how far we can 106 | safely move, or we decide we can't move. 107 | """ 108 | unaltered = True 109 | self.rect[index] += offset[index] 110 | while pg.sprite.spritecollideany(self, obstacles): 111 | self.rect[index] += (1 if offset[index]<0 else -1) 112 | unaltered = False 113 | return unaltered 114 | 115 | def check_above(self, obstacles): 116 | """When jumping, don't enter fall state if there is no room to jump.""" 117 | self.rect.move_ip(0, -1) 118 | collide = pg.sprite.spritecollideany(self, obstacles) 119 | self.rect.move_ip(0, 1) 120 | return collide 121 | 122 | def check_below(self, obstacles): 123 | """Check to see if the player is contacting the ground.""" 124 | self.rect.move_ip((0,1)) 125 | collide = pg.sprite.spritecollide(self, obstacles, False) 126 | self.rect.move_ip((0,-1)) 127 | return collide 128 | 129 | def jump(self, obstacles): 130 | """Called when the user presses the jump button.""" 131 | if not self.fall and not self.check_above(obstacles): 132 | self.velocity[1] = self.jump_power 133 | self.fall = True 134 | self.on_moving = False 135 | 136 | def jump_cut(self): 137 | """Called if player releases the jump key before maximum height.""" 138 | if self.fall: 139 | if self.velocity[1] < self.jump_cut_magnitude: 140 | self.velocity[1] = self.jump_cut_magnitude 141 | 142 | def pre_update(self, obstacles): 143 | """Ran before platforms are updated.""" 144 | self.frame_start_pos = self.rect.topleft 145 | self.collide_below = self.check_below(obstacles) 146 | self.check_moving(obstacles) 147 | 148 | def update(self, obstacles, keys): 149 | """Everything we need to stay updated; ran after platforms update.""" 150 | self.check_keys(keys) 151 | self.get_position(obstacles) 152 | self.physics_update() 153 | start = self.frame_start_pos 154 | end = self.rect.topleft 155 | self.total_displacement = (end[0]-start[0], end[1]-start[1]) 156 | 157 | def draw(self, surface): 158 | """Blit the player to the target surface.""" 159 | surface.blit(self.image, self.rect) 160 | 161 | 162 | class Block(pg.sprite.Sprite): 163 | """A class representing solid obstacles.""" 164 | def __init__(self, color, rect): 165 | """The color is an (r,g,b) tuple; rect is a rect-style argument.""" 166 | pg.sprite.Sprite.__init__(self) 167 | self.rect = pg.Rect(rect) 168 | self.image = pg.Surface(self.rect.size).convert() 169 | self.image.fill(color) 170 | self.type = "normal" 171 | 172 | 173 | class MovingBlock(Block): 174 | """A class to represent horizontally and vertically moving blocks.""" 175 | def __init__(self, color, rect, end, axis, delay=500, speed=2, start=None): 176 | """ 177 | The moving block will travel in the direction of axis (0 or 1) 178 | between rect.topleft and end. The delay argument is the amount of time 179 | (in miliseconds) to pause when reaching an endpoint; speed is the 180 | platforms speed in pixels/frame; if specified start is the place 181 | within the blocks path to start (defaulting to rect.topleft). 182 | """ 183 | Block.__init__(self, color, rect) 184 | self.start = self.rect[axis] 185 | if start: 186 | self.rect[axis] = start 187 | self.axis = axis 188 | self.end = end 189 | self.timer = 0.0 190 | self.delay = delay 191 | self.speed = speed 192 | self.waiting = False 193 | self.type = "moving" 194 | 195 | def update(self, player, obstacles): 196 | """Update position. This should be done before moving any actors.""" 197 | obstacles = obstacles.copy() 198 | obstacles.remove(self) 199 | now = pg.time.get_ticks() 200 | if not self.waiting: 201 | speed = self.speed 202 | start_passed = self.start >= self.rect[self.axis]+speed 203 | end_passed = self.end <= self.rect[self.axis]+speed 204 | if start_passed or end_passed: 205 | if start_passed: 206 | speed = self.start-self.rect[self.axis] 207 | else: 208 | speed = self.end-self.rect[self.axis] 209 | self.change_direction(now) 210 | self.rect[self.axis] += speed 211 | self.move_player(now, player, obstacles, speed) 212 | elif now-self.timer > self.delay: 213 | self.waiting = False 214 | 215 | def move_player(self, now, player, obstacles, speed): 216 | """ 217 | Moves the player both when on top of, or bumped by the platform. 218 | Collision checks are in place to prevent the block pushing the player 219 | through a wall. 220 | """ 221 | if player.on_moving is self or pg.sprite.collide_rect(self,player): 222 | axis = self.axis 223 | offset = (speed, speed) 224 | player.check_collisions(offset, axis, obstacles) 225 | if pg.sprite.collide_rect(self, player): 226 | if self.speed > 0: 227 | self.rect[axis] = player.rect[axis]-self.rect.size[axis] 228 | else: 229 | self.rect[axis] = player.rect[axis]+player.rect.size[axis] 230 | self.change_direction(now) 231 | 232 | def change_direction(self, now): 233 | """Called when the platform reaches an endpoint or has no more room.""" 234 | self.waiting = True 235 | self.timer = now 236 | self.speed *= -1 237 | 238 | 239 | class Control(object): 240 | """Class for managing event loop and game states.""" 241 | def __init__(self): 242 | """Initalize the display and prepare game objects.""" 243 | self.screen = pg.display.get_surface() 244 | self.screen_rect = self.screen.get_rect() 245 | self.clock = pg.time.Clock() 246 | self.fps = 60.0 247 | self.keys = pg.key.get_pressed() 248 | self.done = False 249 | self.player = Player((50,875), 4) 250 | self.level = pg.Surface((1000,1000)).convert() 251 | self.level_rect = self.level.get_rect() 252 | self.viewport = self.screen.get_rect(bottom=self.level_rect.bottom) 253 | self.win_text,self.win_rect = self.make_text() 254 | self.obstacles = self.make_obstacles() 255 | 256 | def make_text(self): 257 | """Renders a text object. Text is only rendered once.""" 258 | font = pg.font.Font(None, 100) 259 | message = "You win. Celebrate." 260 | text = font.render(message, True, (100,100,175)) 261 | rect = text.get_rect(centerx=self.level_rect.centerx, y=100) 262 | return text, rect 263 | 264 | def make_obstacles(self): 265 | """Adds some arbitrarily placed obstacles to a sprite.Group.""" 266 | walls = [Block(pg.Color("chocolate"), (0,980,1000,20)), 267 | Block(pg.Color("chocolate"), (0,0,20,1000)), 268 | Block(pg.Color("chocolate"), (980,0,20,1000))] 269 | static = [Block(pg.Color("darkgreen"), (250,780,200,100)), 270 | Block(pg.Color("darkgreen"), (600,880,200,100)), 271 | Block(pg.Color("darkgreen"), (20,360,880,40)), 272 | Block(pg.Color("darkgreen"), (950,400,30,20)), 273 | Block(pg.Color("darkgreen"), (20,630,50,20)), 274 | Block(pg.Color("darkgreen"), (80,530,50,20)), 275 | Block(pg.Color("darkgreen"), (130,470,200,215)), 276 | Block(pg.Color("darkgreen"), (20,760,30,20)), 277 | Block(pg.Color("darkgreen"), (400,740,30,40))] 278 | moving = [MovingBlock(pg.Color("olivedrab"), (20,740,75,20), 325, 0), 279 | MovingBlock(pg.Color("olivedrab"), (600,500,100,20), 880, 0), 280 | MovingBlock(pg.Color("olivedrab"), 281 | (420,430,100,20), 550, 1, speed=3, delay=200), 282 | MovingBlock(pg.Color("olivedrab"), 283 | (450,700,50,20), 930, 1, start=930), 284 | MovingBlock(pg.Color("olivedrab"), 285 | (500,700,50,20), 730, 0, start=730), 286 | MovingBlock(pg.Color("olivedrab"), 287 | (780,700,50,20), 895, 0, speed=-1)] 288 | return pg.sprite.Group(walls, static, moving) 289 | 290 | def update_viewport(self, speed): 291 | """ 292 | Viewport enabling variable scroll speed based on player's location 293 | on the screen. Here scrolling begins when the player crosses 1/3rd of 294 | the screen. Scrolling begins at half the speed of the player, but once 295 | the player reaches the halfway point scrolling matches the player's own 296 | speed. 297 | """ 298 | for i in (0,1): 299 | first_third = self.viewport[i]+self.viewport.size[i]//3 300 | second_third = first_third+self.viewport.size[i]//3 301 | player_center = self.player.rect.center[i] 302 | mult = 0 303 | if speed[i] > 0 and player_center >= first_third: 304 | mult = 0.5 if player_center < self.viewport.center[i] else 1 305 | elif speed[i] < 0 and player_center <= second_third: 306 | mult = 0.5 if player_center > self.viewport.center[i] else 1 307 | self.viewport[i] += mult*speed[i] 308 | self.viewport.clamp_ip(self.level_rect) 309 | 310 | def event_loop(self): 311 | """We can always quit, and the player can sometimes jump.""" 312 | for event in pg.event.get(): 313 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 314 | self.done = True 315 | elif event.type == pg.KEYDOWN: 316 | if event.key == pg.K_SPACE: 317 | self.player.jump(self.obstacles) 318 | elif event.type == pg.KEYUP: 319 | if event.key == pg.K_SPACE: 320 | self.player.jump_cut() 321 | 322 | def update(self): 323 | """Update the player, obstacles, and current viewport.""" 324 | self.keys = pg.key.get_pressed() 325 | self.player.pre_update(self.obstacles) 326 | self.obstacles.update(self.player, self.obstacles) 327 | self.player.update(self.obstacles, self.keys) 328 | self.update_viewport(self.player.total_displacement) 329 | 330 | def draw(self): 331 | """ 332 | Draw all necessary objects to the level surface, and then draw 333 | the viewport section of the level to the display surface. 334 | """ 335 | self.level.fill(pg.Color("lightblue"), self.viewport) 336 | self.obstacles.draw(self.level) 337 | self.level.blit(self.win_text, self.win_rect) 338 | self.player.draw(self.level) 339 | self.screen.blit(self.level, (0,0), self.viewport) 340 | 341 | def display_fps(self): 342 | """Show the programs FPS in the window handle.""" 343 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 344 | pg.display.set_caption(caption) 345 | 346 | def main_loop(self): 347 | """As simple as it gets.""" 348 | while not self.done: 349 | self.event_loop() 350 | self.update() 351 | self.draw() 352 | pg.display.update() 353 | self.clock.tick(self.fps) 354 | self.display_fps() 355 | 356 | 357 | if __name__ == "__main__": 358 | os.environ['SDL_VIDEO_CENTERED'] = '1' 359 | pg.init() 360 | pg.display.set_caption(CAPTION) 361 | pg.display.set_mode(SCREEN_SIZE) 362 | run_it = Control() 363 | run_it.main_loop() 364 | pg.quit() 365 | sys.exit() 366 | -------------------------------------------------------------------------------- /platforming/shader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/platforming/shader.png -------------------------------------------------------------------------------- /platforming/smallface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/platforming/smallface.png -------------------------------------------------------------------------------- /punch_a_hole/frac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/punch_a_hole/frac.jpg -------------------------------------------------------------------------------- /punch_a_hole/punch.py: -------------------------------------------------------------------------------- 1 | """ 2 | This sample demonstrates two different methods of punching a hole in a surface. 3 | I then extend one of the methods in gradient_hole to show more complex effects. 4 | The hole will follow the mouse cursor. The three different methods are called 5 | in the update function; uncomment the one you would like to test, and comment 6 | out the other two. Various comments also indicate parameters that you are 7 | encouraged to experiment with. 8 | 9 | -Written by Sean J. McKiernan 'Mekire' 10 | """ 11 | 12 | import os 13 | import sys 14 | import pygame as pg 15 | 16 | 17 | CAPTION = "Punching a Hole" 18 | SCREEN_SIZE = (1000, 650) 19 | COLOR_KEY = (255, 0, 255) 20 | ELLIPSE_SIZE = (400, 400) 21 | 22 | 23 | class Control(object): 24 | """A class to maintain sanity.""" 25 | def __init__(self): 26 | """This should look pretty familiar.""" 27 | self.screen = pg.display.get_surface() 28 | self.screen_rect = self.screen.get_rect() 29 | self.clock = pg.time.Clock() 30 | self.fps = 60.0 31 | self.done = False 32 | self.keys = pg.key.get_pressed() 33 | self.background = FRACTAL 34 | self.ellipse_rect = pg.Rect((0,0), ELLIPSE_SIZE) 35 | self.hole = None 36 | 37 | def event_loop(self): 38 | """ 39 | We don't have much to do here, but it is still essential that this 40 | runs every frame. 41 | """ 42 | for event in pg.event.get(): 43 | self.keys = pg.key.get_pressed() 44 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 45 | self.done = True 46 | 47 | def make_hole(self): 48 | """ 49 | This method uses a convert() surface with a colorkey. For our 50 | purposes here, using a convert_alpha() surface actually performs far 51 | better than this one (see make_hole_alpha() below). 52 | """ 53 | hole = pg.Surface(self.screen_rect.size).convert() 54 | hole.set_colorkey(COLOR_KEY) 55 | hole.fill((0,0,0)) #Experiment with changing this color. 56 | pg.draw.ellipse(hole, COLOR_KEY, self.ellipse_rect) 57 | ## hole.set_alpha(200) #Experiment with uncommenting/changing this value. 58 | return hole 59 | 60 | def make_hole_alpha(self): 61 | """This method uses convert_alpha() and has no need for color keys.""" 62 | hole = pg.Surface(self.screen_rect.size).convert_alpha() 63 | hole.fill((255,255,255,200)) #Experiment with changing this color 64 | pg.draw.ellipse(hole, (0,0,0,0), self.ellipse_rect) 65 | return hole 66 | 67 | def gradient_hole(self): 68 | """ 69 | This method is a modification on the make_hole_alpha() method. 70 | It will draw a series of ellipses that decrease in size according to 71 | step, and decrease in alpha according to alpha_step. This allows us to 72 | create some fairly simple gradient style effects. 73 | """ 74 | hole = pg.Surface(self.screen_rect.size).convert_alpha() 75 | color = pg.Color(50, 0, 0, 255) #Experiment with changing this color 76 | hole.fill(color) 77 | step = (-50, -10) #Change ammount to shrink each rect by 78 | alpha_step = 40 #Change amount to change transparency per step 79 | tent_step_amount_x = self.ellipse_rect.width//abs(step[0]) 80 | tent_step_amount_y = self.ellipse_rect.height//abs(step[1]) 81 | number_of_steps = min(tent_step_amount_x, tent_step_amount_y) 82 | shrink_rect = self.ellipse_rect.copy() 83 | for _ in range(number_of_steps): 84 | color.a = max(color.a-alpha_step, 0) 85 | pg.draw.ellipse(hole, color, shrink_rect) 86 | #Inflate should auto-adjust the rect's center, 87 | #but in the case of a step of -1,-1 it fails. 88 | shrink_rect.inflate_ip(step) 89 | shrink_rect.center = self.ellipse_rect.center 90 | return hole 91 | 92 | def update(self): 93 | """Uncomment the version you want to test.""" 94 | self.ellipse_rect.center = pg.mouse.get_pos() 95 | self.hole = self.make_hole() 96 | ## self.hole = self.make_hole_alpha() 97 | ## self.hole = self.gradient_hole() 98 | 99 | def draw(self): 100 | """It is always a good idea to isolate drawing logic.""" 101 | self.screen.blit(self.background, (0,0)) 102 | self.screen.blit(self.hole, (0,0)) 103 | 104 | def display_fps(self): 105 | """Show the program's FPS in the window handle.""" 106 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 107 | pg.display.set_caption(caption) 108 | 109 | def main_loop(self): 110 | """Spin me right round.""" 111 | while not self.done: 112 | self.event_loop() 113 | self.update() 114 | self.draw() 115 | pg.display.update() 116 | self.clock.tick(self.fps) 117 | self.display_fps() 118 | 119 | 120 | if __name__ == "__main__": 121 | os.environ['SDL_VIDEO_CENTERED'] = '1' 122 | pg.init() 123 | pg.display.set_mode(SCREEN_SIZE) 124 | FRACTAL = pg.image.load("frac.jpg").convert() 125 | run_it = Control() 126 | run_it.main_loop() 127 | pg.quit() 128 | sys.exit() 129 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | This repository is an ongoing compilation of pygame examples. Most were originally created with the intent of helping people with questions on various forums. My goal is to write concise examples which demonstrate individual Pygame concepts, while still retaining a responsible coding style. The target audience are those who already have a firm grasp of Python and would like to give 2d graphics a try. 2 | 3 | -Mek 4 | -------------------------------------------------------------------------------- /resizable/resizable_aspect_ratio.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrates how to create a resizable display that maintains aspect ratio. 3 | Unless you have a good reason for doing so, it is generally smarter to detect 4 | the resolution of the user and scale all elements once at load time. This 5 | avoids the overhead with needing to scale while running. 6 | 7 | -Written by Sean J. McKiernan 'Mekire' 8 | """ 9 | 10 | import os 11 | import sys 12 | import pygame as pg 13 | 14 | 15 | CAPTION = "Resizable Display: Aspect Ratio Maintained" 16 | SCREEN_START_SIZE = (500, 500) 17 | 18 | 19 | class Control(object): 20 | """A simple control class.""" 21 | def __init__(self): 22 | """ 23 | Initialize all of the usual suspects. If the os.environ line is 24 | included, then the screen will recenter after it is resized. 25 | """ 26 | os.environ["SDL_VIDEO_CENTERED"] = '1' 27 | pg.init() 28 | pg.display.set_caption(CAPTION) 29 | self.screen = pg.display.set_mode(SCREEN_START_SIZE, pg.RESIZABLE) 30 | self.screen_rect = self.screen.get_rect() 31 | self.image = pg.Surface(SCREEN_START_SIZE).convert() 32 | self.image_rect = self.image.get_rect() 33 | self.clock = pg.time.Clock() 34 | self.fps = 60.0 35 | self.done = False 36 | self.keys = pg.key.get_pressed() 37 | 38 | def event_loop(self): 39 | """ 40 | We are going to catch pygame.VIDEORESIZE events when the user 41 | changes the size of the window. 42 | """ 43 | for event in pg.event.get(): 44 | self.keys = pg.key.get_pressed() 45 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 46 | self.done = True 47 | elif event.type == pg.VIDEORESIZE: 48 | self.screen = pg.display.set_mode(event.size, pg.RESIZABLE) 49 | self.screen_rect = self.screen.get_rect() 50 | 51 | def update(self): 52 | """ 53 | This time we use the pygame.Rect.fit() method to determine the 54 | largest rectangle that can fit on the current display without 55 | distortion. 56 | """ 57 | self.image.fill(pg.Color("black")) 58 | triangle_points = [(0,500), (500,500), (250,0)] 59 | pg.draw.polygon(self.image, pg.Color("red"), triangle_points) 60 | if self.screen_rect.size != SCREEN_START_SIZE: 61 | fit_to_rect = self.image_rect.fit(self.screen_rect) 62 | fit_to_rect.center = self.screen_rect.center 63 | scaled = pg.transform.smoothscale(self.image, fit_to_rect.size) 64 | self.screen.blit(scaled, fit_to_rect) 65 | else: 66 | self.screen.blit(self.image, (0,0)) 67 | 68 | def main(self): 69 | """I'm running in circles.""" 70 | while not self.done: 71 | self.event_loop() 72 | self.update() 73 | pg.display.update() 74 | self.clock.tick(self.fps) 75 | 76 | 77 | if __name__ == "__main__": 78 | run_it = Control() 79 | run_it.main() 80 | pg.quit() 81 | sys.exit() 82 | -------------------------------------------------------------------------------- /resizable/resizable_screen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrates how to create a resizable display. Aspect ratio is not maintained. 3 | Unless you have a good reason for doing so, it is generally smarter to detect 4 | the resolution of the user and scale all elements once at load time. This 5 | avoids the overhead with needing to scale while running. 6 | 7 | -Written by Sean J. McKiernan 'Mekire' 8 | """ 9 | 10 | import os 11 | import sys 12 | import pygame as pg 13 | 14 | 15 | CAPTION = "Resizable Display" 16 | SCREEN_START_SIZE = (500, 500) 17 | 18 | 19 | class Control(object): 20 | """A simple control class.""" 21 | def __init__(self): 22 | """ 23 | Initialize all of the usual suspects. If the os.environ line is 24 | included, then the screen will recenter after it is resized. 25 | """ 26 | os.environ["SDL_VIDEO_CENTERED"] = '1' 27 | pg.init() 28 | pg.display.set_caption(CAPTION) 29 | self.screen = pg.display.set_mode(SCREEN_START_SIZE, pg.RESIZABLE) 30 | self.screen_rect = self.screen.get_rect() 31 | self.image = pg.Surface(SCREEN_START_SIZE).convert() 32 | self.image_rect = self.image.get_rect() 33 | self.clock = pg.time.Clock() 34 | self.fps = 60.0 35 | self.done = False 36 | self.keys = pg.key.get_pressed() 37 | 38 | def event_loop(self): 39 | """ 40 | We are going to catch pygame.VIDEORESIZE events when the user 41 | changes the size of the window. 42 | """ 43 | for event in pg.event.get(): 44 | self.keys = pg.key.get_pressed() 45 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 46 | self.done = True 47 | elif event.type == pg.VIDEORESIZE: 48 | self.screen = pg.display.set_mode(event.size, pg.RESIZABLE) 49 | self.screen_rect = self.screen.get_rect() 50 | 51 | def update(self): 52 | """ 53 | Draw all objects on a surface the size of the start window just as 54 | we would normally draw them on the display surface. Check if the 55 | current resolution is the same as the original resolution. If so blit 56 | the image directly to the display; if not, resize the image first. 57 | """ 58 | self.image.fill(pg.Color("black")) 59 | screen_size = self.screen_rect.size 60 | triangle_points = [(0,500), (500,500), (250,0)] 61 | pg.draw.polygon(self.image, pg.Color("red"), triangle_points) 62 | if screen_size != SCREEN_START_SIZE: 63 | pg.transform.smoothscale(self.image, screen_size, self.screen) 64 | else: 65 | self.screen.blit(self.image, (0,0)) 66 | 67 | def main_loop(self): 68 | """Loop-dee-loop.""" 69 | while not self.done: 70 | self.event_loop() 71 | self.update() 72 | pg.display.update() 73 | self.clock.tick(self.fps) 74 | 75 | 76 | if __name__ == "__main__": 77 | run_it = Control() 78 | run_it.main_loop() 79 | pg.quit() 80 | sys.exit() 81 | -------------------------------------------------------------------------------- /rotation_animation/asteroid_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/rotation_animation/asteroid_simple.png -------------------------------------------------------------------------------- /rotation_animation/rotate_animate.py: -------------------------------------------------------------------------------- 1 | """ 2 | Showing the rotation of an animated body. 3 | 4 | -Sean McKiernan. 5 | """ 6 | 7 | import os 8 | import sys 9 | import pygame as pg 10 | 11 | 12 | SCREEN_SIZE = (500, 500) 13 | BACKGROUND_COLOR = (20, 20, 50) 14 | CAPTION = "Rotation Animation" 15 | 16 | 17 | class Asteroid(pg.sprite.Sprite): 18 | """A class for an animated spinning asteroid.""" 19 | rotation_cache = {} 20 | def __init__(self, location, frame_speed=50, angular_speed=0): 21 | """ 22 | The argument location is the center point of the asteroid; 23 | frame_speed is the speed in frames per second; angular_speed is the 24 | rotation speed in degrees per second. 25 | """ 26 | pg.sprite.Sprite.__init__(self) 27 | self.frame = 0 28 | self.frames = self.get_frames(ASTEROID, (96,80), 21, 7, missing=4) 29 | self.last_frame_info = None 30 | self.image = self.frames[self.frame] 31 | self.rect = self.image.get_rect(center=location) 32 | self.animate_fps = frame_speed 33 | self.angle = 0.0 34 | self.angular_speed = angular_speed #Degrees per second. 35 | 36 | def get_frames(self, sheet,size, columns, rows, missing=0): 37 | """ 38 | Creates a list of all our frames. Fun divmod trick. The missing 39 | argument specifies how many empty cells (if any) there are on the 40 | bottom row. 41 | """ 42 | total = rows*columns-missing 43 | frames = [] 44 | for frame in range(total): 45 | y,x = divmod(frame, columns) 46 | frames.append(sheet.subsurface((x*size[0],y*size[1]),size)) 47 | return frames 48 | 49 | def get_image(self, cache=True): 50 | """ 51 | Get a new image if either the frame or angle has changed. If cache 52 | is True the image will be placed in the rotation_cache. This can 53 | greatly improve speed but is only feasible if caching all the images 54 | would not cause memory issues. 55 | """ 56 | frame_info = angle, frame = (int(self.angle), int(self.frame)) 57 | if frame_info != self.last_frame_info: 58 | if frame_info in Asteroid.rotation_cache: 59 | image = Asteroid.rotation_cache[frame_info] 60 | else: 61 | raw = self.frames[frame] 62 | image = pg.transform.rotozoom(raw, angle, 1.0) 63 | if cache: 64 | Asteroid.rotation_cache[frame_info] = image 65 | self.last_frame_info = frame_info 66 | else: 67 | image = self.image 68 | return image 69 | 70 | def update(self,dt): 71 | """Change the angle and fps based on a time delta.""" 72 | self.angle = (self.angle+self.angular_speed*dt)%360 73 | self.frame = (self.frame+self.animate_fps*dt)%len(self.frames) 74 | self.image = self.get_image(False) 75 | self.rect = self.image.get_rect(center=self.rect.center) 76 | 77 | 78 | class Control(object): 79 | """Game loop and event loop found here.""" 80 | def __init__(self): 81 | """Prepare the essentials and setup an asteroid group.""" 82 | self.screen = pg.display.get_surface() 83 | self.screen_rect = self.screen.get_rect() 84 | self.done = False 85 | self.keys = pg.key.get_pressed() 86 | self.clock = pg.time.Clock() 87 | self.fps = 60.0 88 | self.asteroids = self.make_asteroids() 89 | 90 | def make_asteroids(self): 91 | """ 92 | Arbitrary method that creates a group with four asteroids. 93 | A static; a rotating; an animating; and a rotating-animating one. 94 | """ 95 | location_one = [loc//4 for loc in self.screen_rect.size] 96 | location_two = [loc*3 for loc in location_one] 97 | location_three = [location_two[0], location_one[1]] 98 | location_four = [location_one[0], location_two[1]] 99 | asteroids = [Asteroid(location_one, 0, 0), 100 | Asteroid(location_two, 50, 200), 101 | Asteroid(location_three, 0, 200), 102 | Asteroid(location_four)] 103 | return pg.sprite.Group(asteroids) 104 | 105 | def event_loop(self): 106 | """Bare bones event loop.""" 107 | for event in pg.event.get(): 108 | self.keys = pg.key.get_pressed() 109 | self.done = event.type == pg.QUIT or self.keys[pg.K_ESCAPE] 110 | 111 | def display_fps(self): 112 | """Show the program's FPS in the window handle.""" 113 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 114 | pg.display.set_caption(caption) 115 | 116 | def main_loop(self): 117 | """Clean main game loop.""" 118 | delta = 0 119 | while not self.done: 120 | self.event_loop() 121 | self.asteroids.update(delta) 122 | self.screen.fill(BACKGROUND_COLOR) 123 | self.asteroids.draw(self.screen) 124 | pg.display.update() 125 | delta = self.clock.tick(self.fps)/1000.0 126 | self.display_fps() 127 | 128 | 129 | def main(): 130 | """Initialize; load image; and start program.""" 131 | global ASTEROID 132 | pg.init() 133 | os.environ["SDL_VIDEO_CENTERED"] = "TRUE" 134 | pg.display.set_caption(CAPTION) 135 | pg.display.set_mode(SCREEN_SIZE) 136 | ASTEROID = pg.image.load("asteroid_simple.png").convert_alpha() 137 | Control().main_loop() 138 | pg.quit() 139 | sys.exit() 140 | 141 | 142 | if __name__ == "__main__": 143 | main() 144 | -------------------------------------------------------------------------------- /tank_turret/tank.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrates rotating an object without the center shifting and moving objects 3 | at angles using simple trigonometry. Left and Right arrows to rotate cannon. 4 | Spacebar to fire. 5 | 6 | -Written by Sean J. McKiernan 'Mekire' 7 | """ 8 | 9 | import os 10 | import sys 11 | import math 12 | import pygame as pg 13 | 14 | 15 | CAPTION = "Tank Turret: Keyboard" 16 | SCREEN_SIZE = (500, 500) 17 | BACKGROUND_COLOR = (50, 50, 50) 18 | COLOR_KEY = (255, 0, 255) 19 | 20 | SPIN_DICT = {pg.K_LEFT : 1, 21 | pg.K_RIGHT : -1} 22 | 23 | 24 | class Turret(object): 25 | """Shooting lasers with absolute precision.""" 26 | def __init__(self, location): 27 | """Location is an (x,y) coordinate pair.""" 28 | self.original_barrel = TURRET.subsurface((0,0,150,150)) 29 | self.barrel = self.original_barrel.copy() 30 | self.base = TURRET.subsurface((300,0,150,150)) 31 | self.rect = self.barrel.get_rect(center=location) 32 | self.base_rect = self.rect.copy() 33 | self.angle = 90 34 | self.spin = 0 35 | self.rotate_speed = 3 36 | self.rotate(True) 37 | 38 | def rotate(self, force=False): 39 | """ 40 | Rotate our barrel image and set the new rect's center to the 41 | old rect's center to ensure our image doesn't shift around. 42 | """ 43 | if self.spin or force: 44 | self.angle += self.rotate_speed*self.spin 45 | self.barrel = pg.transform.rotate(self.original_barrel, self.angle) 46 | self.rect = self.barrel.get_rect(center=self.rect.center) 47 | 48 | def get_event(self, event, objects): 49 | """Our turret is passed events from the Control event loop.""" 50 | if event.type == pg.KEYDOWN: 51 | if event.key == pg.K_SPACE: 52 | objects.add(Laser(self.rect.center, self.angle)) 53 | 54 | def update(self, keys): 55 | """Update our Turret and draw it to the surface.""" 56 | self.spin = 0 57 | for key in SPIN_DICT: 58 | if keys[key]: 59 | self.spin += SPIN_DICT[key] 60 | self.rotate() 61 | 62 | def draw(self, surface): 63 | """Draw base and barrel to the target surface.""" 64 | surface.blit(self.base, self.base_rect) 65 | surface.blit(self.barrel, self.rect) 66 | 67 | 68 | class Laser(pg.sprite.Sprite): 69 | """ 70 | A class for our laser projectiles. Using the pygame.sprite.Sprite class 71 | this time, though it is just as easily done without it. 72 | """ 73 | def __init__(self, location, angle): 74 | """ 75 | Takes a coordinate pair, and an angle in degrees. These are passed 76 | in by the Turret class when the projectile is created. 77 | """ 78 | pg.sprite.Sprite.__init__(self) 79 | self.original_laser = TURRET.subsurface((150,0,150,150)) 80 | self.angle = -math.radians(angle-135) 81 | self.image = pg.transform.rotate(self.original_laser, angle) 82 | self.rect = self.image.get_rect(center=location) 83 | self.move = [self.rect.x, self.rect.y] 84 | self.speed_magnitude = 5 85 | self.speed = (self.speed_magnitude*math.cos(self.angle), 86 | self.speed_magnitude*math.sin(self.angle)) 87 | self.done = False 88 | 89 | def update(self, screen_rect): 90 | """ 91 | Because pygame.Rect's can only hold ints, it is necessary to hold 92 | the real value of our movement vector in another variable. 93 | """ 94 | self.move[0] += self.speed[0] 95 | self.move[1] += self.speed[1] 96 | self.rect.topleft = self.move 97 | self.remove(screen_rect) 98 | 99 | def remove(self, screen_rect): 100 | """If the projectile has left the screen, remove it from any Groups.""" 101 | if not self.rect.colliderect(screen_rect): 102 | self.kill() 103 | 104 | 105 | class Control(object): 106 | """Why so controlling?""" 107 | def __init__(self): 108 | """ 109 | Prepare necessities; create a Turret; and create a Group for our 110 | laser projectiles. 111 | """ 112 | self.screen = pg.display.get_surface() 113 | self.screen_rect = self.screen.get_rect() 114 | self.done = False 115 | self.clock = pg.time.Clock() 116 | self.fps = 60.0 117 | self.keys = pg.key.get_pressed() 118 | self.cannon = Turret((250,250)) 119 | self.objects = pg.sprite.Group() 120 | 121 | def event_loop(self): 122 | """Events are passed on to the Turret.""" 123 | for event in pg.event.get(): 124 | self.keys = pg.key.get_pressed() 125 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 126 | self.done = True 127 | self.cannon.get_event(event, self.objects) 128 | 129 | def update(self): 130 | """Update turret and all lasers.""" 131 | self.cannon.update(self.keys) 132 | self.objects.update(self.screen_rect) 133 | 134 | def draw(self): 135 | """Draw all elements to the display surface.""" 136 | self.screen.fill(BACKGROUND_COLOR) 137 | self.cannon.draw(self.screen) 138 | self.objects.draw(self.screen) 139 | 140 | def display_fps(self): 141 | """Show the program's FPS in the window handle.""" 142 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 143 | pg.display.set_caption(caption) 144 | 145 | def main_loop(self): 146 | """"Same old story.""" 147 | while not self.done: 148 | self.event_loop() 149 | self.update() 150 | self.draw() 151 | pg.display.flip() 152 | self.clock.tick(self.fps) 153 | self.display_fps() 154 | 155 | 156 | if __name__ == "__main__": 157 | os.environ['SDL_VIDEO_CENTERED'] = '1' 158 | pg.init() 159 | pg.display.set_caption(CAPTION) 160 | pg.display.set_mode(SCREEN_SIZE) 161 | TURRET = pg.image.load("turret.png").convert() 162 | TURRET.set_colorkey(COLOR_KEY) 163 | run_it = Control() 164 | run_it.main_loop() 165 | pg.quit() 166 | sys.exit() 167 | -------------------------------------------------------------------------------- /tank_turret/turret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/tank_turret/turret.png -------------------------------------------------------------------------------- /tank_turret/turret_gamepad.py: -------------------------------------------------------------------------------- 1 | """ 2 | Control the turret with a gamepad. Rotate the turret with the left analog stick 3 | and fire with button 0 (should be the X-button on a Playstation controller). 4 | This does not perform particularly well with my gamepad but I have been assured 5 | by others that it works smoothly and is the fault of my controller and not my 6 | code; let me know if you have any problems. 7 | 8 | -Written by Sean J. McKiernan 'Mekire' 9 | """ 10 | 11 | import os 12 | import sys 13 | import math 14 | import pygame as pg 15 | 16 | 17 | CAPTION = "Tank Turret: Gamepad" 18 | SCREEN_SIZE = (500, 500) 19 | BACKGROUND_COLOR = (50, 50, 50) 20 | COLOR_KEY = (255, 0, 255) 21 | 22 | 23 | class Turret(object): 24 | """Gamepad guided lasers.""" 25 | def __init__(self, gamepad, location): 26 | """Location is an (x,y) coordinate pair.""" 27 | self.gamepad = gamepad 28 | self.id = gamepad.get_id() 29 | self.original_barrel = TURRET.subsurface((0,0,150,150)) 30 | self.barrel = self.original_barrel.copy() 31 | self.base = TURRET.subsurface((300,0,150,150)) 32 | self.rect = self.barrel.get_rect(center=location) 33 | self.base_rect = self.rect.copy() 34 | self.angle = 0 35 | 36 | def get_angle(self,tolerance=0.25): 37 | """ 38 | Get the current angle of the gampad's left analog stick. A tolerance 39 | is used to prevent angles recalcing when the stick is nearly 40 | centered. 41 | """ 42 | x, y = self.gamepad.get_axis(0), self.gamepad.get_axis(1) 43 | if abs(x) > tolerance or abs(y) > tolerance: 44 | self.angle = 135-math.degrees(math.atan2(float(y), float(x))) 45 | self.barrel = pg.transform.rotate(self.original_barrel, self.angle) 46 | self.rect = self.barrel.get_rect(center=self.rect.center) 47 | 48 | def get_event(self, event, objects): 49 | """Catch and process gamepad events.""" 50 | if event.type == pg.JOYBUTTONDOWN: 51 | if event.joy == self.id and event.button == 0: 52 | objects.add(Laser(self.rect.center, self.angle)) 53 | elif event.type == pg.JOYAXISMOTION: 54 | if event.joy == self.id: 55 | self.get_angle() 56 | 57 | def draw(self, surface): 58 | """Draw base and barrel to the target surface.""" 59 | surface.blit(self.base, self.base_rect) 60 | surface.blit(self.barrel, self.rect) 61 | 62 | 63 | class Laser(pg.sprite.Sprite): 64 | """ 65 | A class for our laser projectiles. Using the pygame.sprite.Sprite class 66 | this time, though it is just as easily done without it. 67 | """ 68 | def __init__(self, location, angle): 69 | """ 70 | Takes a coordinate pair, and an angle in degrees. These are passed 71 | in by the Turret class when the projectile is created. 72 | """ 73 | pg.sprite.Sprite.__init__(self) 74 | self.original_laser = TURRET.subsurface((150,0,150,150)) 75 | self.angle = -math.radians(angle-135) 76 | self.image = pg.transform.rotate(self.original_laser, angle) 77 | self.rect = self.image.get_rect(center=location) 78 | self.move = [self.rect.x, self.rect.y] 79 | self.speed_magnitude = 5 80 | self.speed = (self.speed_magnitude*math.cos(self.angle), 81 | self.speed_magnitude*math.sin(self.angle)) 82 | self.done = False 83 | 84 | def update(self, screen_rect): 85 | """ 86 | Because pygame.Rect's can only hold ints, it is necessary to preserve 87 | the real value of our movement vector in another variable. 88 | """ 89 | self.move[0] += self.speed[0] 90 | self.move[1] += self.speed[1] 91 | self.rect.topleft = self.move 92 | self.remove(screen_rect) 93 | 94 | def remove(self, screen_rect): 95 | """If the projectile has left the screen, remove it from any Groups.""" 96 | if not self.rect.colliderect(screen_rect): 97 | self.kill() 98 | 99 | 100 | class Control(object): 101 | """Why so controlling?""" 102 | def __init__(self): 103 | """ 104 | Prepare necessities; create a Turret; and create a Group for our 105 | laser projectiles. 106 | """ 107 | self.screen = pg.display.get_surface() 108 | self.screen_rect = self.screen.get_rect() 109 | self.joys = initialize_all_gamepads() 110 | self.done = False 111 | self.clock = pg.time.Clock() 112 | self.fps = 60 113 | self.keys = pg.key.get_pressed() 114 | self.cannon = Turret(self.joys[0], (250,250)) 115 | self.objects = pg.sprite.Group() 116 | 117 | def event_loop(self): 118 | """Events are passed on to the Turret.""" 119 | for event in pg.event.get(): 120 | self.keys = pg.key.get_pressed() 121 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 122 | self.done = True 123 | self.cannon.get_event(event, self.objects) 124 | 125 | def update(self): 126 | """Update all lasers.""" 127 | self.objects.update(self.screen_rect) 128 | 129 | def draw(self): 130 | """Draw all elements to the display surface.""" 131 | self.screen.fill(BACKGROUND_COLOR) 132 | self.cannon.draw(self.screen) 133 | self.objects.draw(self.screen) 134 | 135 | def display_fps(self): 136 | """Show the program's FPS in the window handle.""" 137 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 138 | pg.display.set_caption(caption) 139 | 140 | def main_loop(self): 141 | """"Same old story.""" 142 | while not self.done: 143 | self.event_loop() 144 | self.update() 145 | self.draw() 146 | pg.display.flip() 147 | self.clock.tick(self.fps) 148 | self.display_fps() 149 | 150 | 151 | def initialize_all_gamepads(): 152 | """Checks for gamepads and returns an initialized list of them if found.""" 153 | joysticks = [] 154 | for joystick_id in range(pg.joystick.get_count()): 155 | joysticks.append(pg.joystick.Joystick(joystick_id)) 156 | joysticks[joystick_id].init() 157 | return joysticks 158 | 159 | 160 | def main(): 161 | """Prepare display, load image, and start program.""" 162 | global TURRET 163 | os.environ['SDL_VIDEO_CENTERED'] = '1' 164 | pg.init() 165 | pg.display.set_caption(CAPTION) 166 | pg.display.set_mode(SCREEN_SIZE) 167 | TURRET = pg.image.load("turret.png").convert() 168 | TURRET.set_colorkey(COLOR_KEY) 169 | Control().main_loop() 170 | pg.quit() 171 | sys.exit() 172 | 173 | 174 | if __name__ == "__main__": 175 | main() 176 | -------------------------------------------------------------------------------- /tank_turret/turret_mouse.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is identical to the standard turret in tank.py, except the Turret 3 | now follows the mouse and fires with the left mouse button; instead of using 4 | the keyboard. 5 | 6 | -Written by Sean J. McKiernan 'Mekire' 7 | """ 8 | 9 | import os 10 | import sys 11 | import math 12 | import pygame as pg 13 | 14 | 15 | CAPTION = "Tank Turret: Mouse" 16 | SCREEN_SIZE = (500, 500) 17 | BACKGROUND_COLOR = (50, 50, 50) 18 | COLOR_KEY = (255, 0, 255) 19 | 20 | 21 | class Turret(object): 22 | """Mouse guided lasers.""" 23 | def __init__(self, location): 24 | """Location is an (x,y) coordinate pair.""" 25 | self.original_barrel = TURRET.subsurface((0,0,150,150)) 26 | self.barrel = self.original_barrel.copy() 27 | self.base = TURRET.subsurface((300,0,150,150)) 28 | self.rect = self.barrel.get_rect(center=location) 29 | self.base_rect = self.rect.copy() 30 | self.angle = self.get_angle(pg.mouse.get_pos()) 31 | 32 | def get_angle(self, mouse): 33 | """ 34 | Find the new angle between the center of the Turret and the mouse. 35 | """ 36 | offset = (mouse[1]-self.rect.centery, mouse[0]-self.rect.centerx) 37 | self.angle = 135-math.degrees(math.atan2(*offset)) 38 | self.barrel = pg.transform.rotate(self.original_barrel, self.angle) 39 | self.rect = self.barrel.get_rect(center=self.rect.center) 40 | 41 | def get_event(self, event, objects): 42 | """Fire lasers on left click. Recalculate angle if mouse is moved.""" 43 | if event.type == pg.MOUSEBUTTONDOWN and event.button == 1: 44 | objects.add(Laser(self.rect.center, self.angle)) 45 | elif event.type == pg.MOUSEMOTION: 46 | self.get_angle(event.pos) 47 | 48 | def draw(self, surface): 49 | """Draw base and barrel to the target surface.""" 50 | surface.blit(self.base, self.base_rect) 51 | surface.blit(self.barrel, self.rect) 52 | 53 | 54 | class Laser(pg.sprite.Sprite): 55 | """ 56 | A class for our laser projectiles. Using the pygame.sprite.Sprite class 57 | this time, though it is just as easily done without it. 58 | """ 59 | def __init__(self, location, angle): 60 | """ 61 | Takes a coordinate pair, and an angle in degrees. These are passed 62 | in by the Turret class when the projectile is created. 63 | """ 64 | pg.sprite.Sprite.__init__(self) 65 | self.original_laser = TURRET.subsurface((150,0,150,150)) 66 | self.angle = -math.radians(angle-135) 67 | self.image = pg.transform.rotate(self.original_laser, angle) 68 | self.rect = self.image.get_rect(center=location) 69 | self.move = [self.rect.x, self.rect.y] 70 | self.speed_magnitude = 5 71 | self.speed = (self.speed_magnitude*math.cos(self.angle), 72 | self.speed_magnitude*math.sin(self.angle)) 73 | self.done = False 74 | 75 | def update(self, screen_rect): 76 | """ 77 | Because pygame.Rect's can only hold ints, it is necessary to hold 78 | the real value of our movement vector in another variable. 79 | """ 80 | self.move[0] += self.speed[0] 81 | self.move[1] += self.speed[1] 82 | self.rect.topleft = self.move 83 | self.remove(screen_rect) 84 | 85 | def remove(self, screen_rect): 86 | """If the projectile has left the screen, remove it from any Groups.""" 87 | if not self.rect.colliderect(screen_rect): 88 | self.kill() 89 | 90 | 91 | class Control(object): 92 | """Why so controlling?""" 93 | def __init__(self): 94 | """ 95 | Prepare necessities; create a Turret; and create a Group for our 96 | laser projectiles. 97 | """ 98 | self.screen = pg.display.get_surface() 99 | self.screen_rect = self.screen.get_rect() 100 | self.done = False 101 | self.clock = pg.time.Clock() 102 | self.fps = 60.0 103 | self.keys = pg.key.get_pressed() 104 | self.cannon = Turret((250,250)) 105 | self.objects = pg.sprite.Group() 106 | 107 | def event_loop(self): 108 | """Events are passed on to the Turret.""" 109 | for event in pg.event.get(): 110 | self.keys = pg.key.get_pressed() 111 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 112 | self.done = True 113 | self.cannon.get_event(event, self.objects) 114 | 115 | def update(self): 116 | """Update all lasers.""" 117 | self.objects.update(self.screen_rect) 118 | 119 | def draw(self): 120 | """Draw all elements to the display surface.""" 121 | self.screen.fill(BACKGROUND_COLOR) 122 | self.cannon.draw(self.screen) 123 | self.objects.draw(self.screen) 124 | 125 | def display_fps(self): 126 | """Show the program's FPS in the window handle.""" 127 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 128 | pg.display.set_caption(caption) 129 | 130 | def main_loop(self): 131 | """"Same old story.""" 132 | while not self.done: 133 | self.event_loop() 134 | self.update() 135 | self.draw() 136 | pg.display.flip() 137 | self.clock.tick(self.fps) 138 | self.display_fps() 139 | 140 | 141 | if __name__ == "__main__": 142 | os.environ['SDL_VIDEO_CENTERED'] = '1' 143 | pg.init() 144 | pg.display.set_caption(CAPTION) 145 | pg.display.set_mode(SCREEN_SIZE) 146 | TURRET = pg.image.load("turret.png").convert() 147 | TURRET.set_colorkey(COLOR_KEY) 148 | run_it = Control() 149 | run_it.main_loop() 150 | pg.quit() 151 | sys.exit() 152 | -------------------------------------------------------------------------------- /topdown_scrolling/pond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/topdown_scrolling/pond.png -------------------------------------------------------------------------------- /topdown_scrolling/scrolling.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example shows a method of making a player stay centered on a scrolling 3 | map. When the player moves close to the edges of the map he will move off 4 | center. This implementation does not employ a tiled map (though the centering 5 | technique works identically). Using a single mask for collision of the entire 6 | level is not an efficient technique. This example is designed to show 7 | viewport centering; not efficient collision. 8 | 9 | -Written by Sean J. McKiernan 'Mekire' 10 | """ 11 | 12 | import os 13 | import sys 14 | import pygame as pg 15 | 16 | 17 | CAPTION = "Scrolling Background" 18 | SCREEN_SIZE = (500, 500) 19 | 20 | 21 | DIRECT_DICT = {pg.K_UP : ( 0,-1), 22 | pg.K_DOWN : ( 0, 1), 23 | pg.K_RIGHT: ( 1, 0), 24 | pg.K_LEFT : (-1, 0)} 25 | 26 | 27 | class Player(object): 28 | """Our user controllable character.""" 29 | def __init__(self, image, location, speed): 30 | """ 31 | The location is an (x,y) coordinate; speed is in pixels per frame. 32 | The location of the player is with respect to the map he is in; not the 33 | display screen. 34 | """ 35 | self.speed = speed 36 | self.image = image 37 | self.mask = pg.mask.from_surface(self.image) 38 | self.rect = self.image.get_rect(center=location) 39 | 40 | def update(self, level_mask, keys): 41 | """ 42 | Check pressed keys to find initial movement vector. Then call 43 | collision detection methods and adjust the vector appropriately. 44 | """ 45 | move = self.check_keys(keys) 46 | self.check_collisions(move, level_mask) 47 | 48 | def check_keys(self, keys): 49 | """Find the players movement vector from key presses.""" 50 | move = [0, 0] 51 | for key in DIRECT_DICT: 52 | if keys[key]: 53 | for i in (0, 1): 54 | move[i] += DIRECT_DICT[key][i]*self.speed 55 | return move 56 | 57 | def check_collisions(self, move, level_mask): 58 | """ 59 | Call collision_detail for the x and y components of our movement vector. 60 | """ 61 | x_change = self.collision_detail(move, level_mask, 0) 62 | self.rect.move_ip((x_change,0)) 63 | y_change = self.collision_detail(move, level_mask, 1) 64 | self.rect.move_ip((0,y_change)) 65 | 66 | def collision_detail(self, move, level_mask, index): 67 | """ 68 | Check for collision and if found decrement vector by single pixels 69 | until clear. 70 | """ 71 | test_offset = list(self.rect.topleft) 72 | test_offset[index] += move[index] 73 | while level_mask.overlap_area(self.mask, test_offset): 74 | move[index] += (1 if move[index]<0 else -1) 75 | test_offset = list(self.rect.topleft) 76 | test_offset[index] += move[index] 77 | return move[index] 78 | 79 | def draw(self, surface): 80 | """Basic draw function.""" 81 | surface.blit(self.image, self.rect) 82 | 83 | 84 | class Level(object): 85 | """ 86 | A class for our map. Maps in this implementation are one image; not 87 | tile based. This makes collision detection simpler but can have performance 88 | implications. 89 | """ 90 | def __init__(self, map_image, viewport, player): 91 | """ 92 | Takes an image from which to make a mask, a viewport rect, and a 93 | player instance. 94 | """ 95 | self.image = map_image 96 | self.mask = pg.mask.from_surface(self.image) 97 | self.rect = self.image.get_rect() 98 | self.player = player 99 | self.player.rect.center = self.rect.center 100 | self.viewport = viewport 101 | 102 | def update(self, keys): 103 | """ 104 | Updates the player and then adjust the viewport with respect to the 105 | player's new position. 106 | """ 107 | self.player.update(self.mask, keys) 108 | self.update_viewport() 109 | 110 | def update_viewport(self): 111 | """ 112 | The viewport will stay centered on the player unless the player 113 | approaches the edge of the map. 114 | """ 115 | self.viewport.center = self.player.rect.center 116 | self.viewport.clamp_ip(self.rect) 117 | 118 | def draw(self, surface): 119 | """ 120 | Blit actors onto a copy of the map image; then blit the viewport 121 | portion of that map onto the display surface. 122 | """ 123 | new_image = self.image.copy() 124 | self.player.draw(new_image) 125 | surface.fill((50,255,50)) 126 | surface.blit(new_image, (0,0), self.viewport) 127 | 128 | 129 | class Control(object): 130 | """We meet again.""" 131 | def __init__(self): 132 | """Initialize things; create a Player; create a Level.""" 133 | self.screen = pg.display.get_surface() 134 | self.screen_rect = self.screen.get_rect() 135 | self.clock = pg.time.Clock() 136 | self.fps = 60.0 137 | self.keys = pg.key.get_pressed() 138 | self.done = False 139 | self.player = Player(PLAY_IMAGE, (0,0), 7) 140 | self.level = Level(POND_IMAGE, self.screen_rect.copy(), self.player) 141 | 142 | def event_loop(self): 143 | """A quiet day in the neighborhood here.""" 144 | for event in pg.event.get(): 145 | self.keys = pg.key.get_pressed() 146 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 147 | self.done = True 148 | 149 | def display_fps(self): 150 | """Show the program's FPS in the window handle.""" 151 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 152 | pg.display.set_caption(caption) 153 | 154 | def update(self): 155 | """ 156 | Update the level. In this implementation player updating is taken 157 | care of by the level update function. 158 | """ 159 | self.screen.fill(pg.Color("black")) 160 | self.level.update(self.keys) 161 | self.level.draw(self.screen) 162 | 163 | def main_loop(self): 164 | """...and we run in circles.""" 165 | while not self.done: 166 | self.event_loop() 167 | self.update() 168 | pg.display.update() 169 | self.clock.tick(self.fps) 170 | self.display_fps() 171 | 172 | 173 | def main(): 174 | """Initialize, load our images, and run the program.""" 175 | global PLAY_IMAGE, POND_IMAGE 176 | os.environ['SDL_VIDEO_CENTERED'] = '1' 177 | pg.init() 178 | pg.display.set_caption(CAPTION) 179 | pg.display.set_mode(SCREEN_SIZE) 180 | PLAY_IMAGE = pg.image.load("smallface.png").convert_alpha() 181 | POND_IMAGE = pg.image.load("pond.png").convert_alpha() 182 | Control().main_loop() 183 | pg.quit() 184 | sys.exit() 185 | 186 | 187 | if __name__ == "__main__": 188 | main() 189 | -------------------------------------------------------------------------------- /topdown_scrolling/scrolling_mouse.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example shows scrolling a topdown map when the mouse nears the edges of 3 | the screen. 4 | 5 | -Written by Sean J. McKiernan 'Mekire' 6 | """ 7 | 8 | import os 9 | import sys 10 | import pygame as pg 11 | 12 | 13 | CAPTION = "Scrolling Background" 14 | SCREEN_SIZE = (500, 500) 15 | 16 | DIRECT_DICT = {"DOWN" : ( 0, 1), 17 | "UP" : ( 0,-1), 18 | "LEFT" : (-1, 0), 19 | "RIGHT": ( 1, 0)} 20 | 21 | 22 | class Player(object): 23 | """Our user controllable character.""" 24 | def __init__(self, image, location, speed): 25 | """ 26 | The location is an (x,y) coordinate; speed is in pixels per frame. 27 | The location of the player is with respect to the map he is in; not the 28 | display screen. 29 | """ 30 | self.speed = speed 31 | self.image = image 32 | self.mask = pg.mask.from_surface(self.image) 33 | self.rect = self.image.get_rect(center=location) 34 | self.controls = {pg.K_DOWN : "DOWN", 35 | pg.K_UP : "UP", 36 | pg.K_LEFT : "LEFT", 37 | pg.K_RIGHT: "RIGHT"} 38 | 39 | def update(self, level_mask, keys): 40 | """ 41 | Check pressed keys to find initial movement vector. Then call 42 | collision detection methods and adjust the vector appropriately. 43 | """ 44 | move = self.check_keys(keys) 45 | self.check_collisions(move, level_mask) 46 | 47 | def check_keys(self, keys): 48 | """Find the players movement vector from key presses.""" 49 | move = [0, 0] 50 | for key in self.controls: 51 | if keys[key]: 52 | direction = self.controls[key] 53 | vector = DIRECT_DICT[direction] 54 | for i in (0, 1): 55 | move[i] += vector[i]*self.speed 56 | return move 57 | 58 | def check_collisions(self, move, level_mask): 59 | """ 60 | Call collision_detail for the x and y components of our movement vector. 61 | """ 62 | x_change = self.collision_detail(move, level_mask, 0) 63 | self.rect.move_ip((x_change,0)) 64 | y_change = self.collision_detail(move, level_mask, 1) 65 | self.rect.move_ip((0,y_change)) 66 | 67 | def collision_detail(self, move, level_mask, index): 68 | """ 69 | Check for collision and if found decrement vector by single pixels 70 | until clear. 71 | """ 72 | test_offset = list(self.rect.topleft) 73 | test_offset[index] += move[index] 74 | while level_mask.overlap_area(self.mask, test_offset): 75 | move[index] += (1 if move[index]<0 else -1) 76 | test_offset = list(self.rect.topleft) 77 | test_offset[index] += move[index] 78 | return move[index] 79 | 80 | def draw(self, surface): 81 | """Basic draw function.""" 82 | surface.blit(self.image, self.rect) 83 | 84 | 85 | class Level(object): 86 | """ 87 | A class for our map. Maps in this implementation are one image; not 88 | tile based. This makes collision detection simpler but can have performance 89 | implications. 90 | """ 91 | def __init__(self, map_image, viewport, player): 92 | """ 93 | Takes an image from which to make a mask, a viewport rect, and a 94 | player instance. 95 | """ 96 | self.image = map_image 97 | self.mask = pg.mask.from_surface(self.image) 98 | self.rect = self.image.get_rect() 99 | self.player = player 100 | self.player.rect.center = self.rect.center 101 | self.viewport = viewport 102 | self.scroll_rects = self.make_scroll_rects() 103 | self.scroll_speed = 5 104 | 105 | def make_scroll_rects(self): 106 | """ 107 | The map will scroll in the appropriate direction if inside any of 108 | these rects. 109 | """ 110 | rects = {"UP" : pg.Rect(0, 0, self.viewport.w, 20), 111 | "DOWN" : pg.Rect(0, self.viewport.h-20, self.viewport.w, 20), 112 | "LEFT" : pg.Rect(0, 0, 20, self.viewport.h), 113 | "RIGHT": pg.Rect(self.viewport.w-20, 0, 20, self.viewport.h)} 114 | return rects 115 | 116 | def update(self, keys): 117 | """ 118 | Updates the player and then adjust the viewport with respect to the 119 | player's new position. 120 | """ 121 | self.player.update(self.mask, keys) 122 | self.update_viewport() 123 | 124 | def update_viewport(self): 125 | """ 126 | The viewport scrolls if the mouse is in any of the scroll rects. 127 | """ 128 | mouse = pg.mouse.get_pos() 129 | for direct,rect in self.scroll_rects.items(): 130 | if rect.collidepoint(mouse): 131 | self.viewport.x += DIRECT_DICT[direct][0]*self.scroll_speed 132 | self.viewport.y += DIRECT_DICT[direct][1]*self.scroll_speed 133 | self.viewport.clamp_ip(self.rect) 134 | 135 | def draw(self, surface): 136 | """ 137 | Blit actors onto a copy of the map image; then blit the viewport 138 | portion of that map onto the display surface. 139 | """ 140 | new_image = self.image.copy() 141 | self.player.draw(new_image) 142 | surface.fill((50,255,50)) 143 | surface.blit(new_image, (0,0), self.viewport) 144 | #Scroll rects filled for better visualization. 145 | for rect in self.scroll_rects.values(): 146 | temp = pg.Surface(rect.size).convert_alpha() 147 | temp.fill((0,0,0,100)) 148 | surface.blit(temp, rect) 149 | 150 | 151 | class Control(object): 152 | """We meet again.""" 153 | def __init__(self): 154 | """Initialize things; create a Player; create a Level.""" 155 | self.screen = pg.display.get_surface() 156 | self.screen_rect = self.screen.get_rect() 157 | self.clock = pg.time.Clock() 158 | self.fps = 60.0 159 | self.keys = pg.key.get_pressed() 160 | self.done = False 161 | self.player = Player(PLAY_IMAGE, (0,0), 7) 162 | self.level = Level(POND_IMAGE, self.screen_rect.copy(), self.player) 163 | self.level.viewport.center = self.level.rect.center 164 | 165 | def event_loop(self): 166 | """A quiet day in the neighborhood here.""" 167 | for event in pg.event.get(): 168 | self.keys = pg.key.get_pressed() 169 | if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]: 170 | self.done = True 171 | 172 | def display_fps(self): 173 | """Show the program's FPS in the window handle.""" 174 | caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps()) 175 | pg.display.set_caption(caption) 176 | 177 | def update(self): 178 | """ 179 | Update the level. In this implementation player updating is taken 180 | care of by the level update function. 181 | """ 182 | self.level.update(self.keys) 183 | self.level.draw(self.screen) 184 | 185 | def main_loop(self): 186 | """...and we run in circles.""" 187 | while not self.done: 188 | self.event_loop() 189 | self.update() 190 | pg.display.update() 191 | self.clock.tick(self.fps) 192 | self.display_fps() 193 | 194 | 195 | def main(): 196 | """Initialize, load our images, and run the program.""" 197 | global PLAY_IMAGE, POND_IMAGE 198 | os.environ['SDL_VIDEO_CENTERED'] = '1' 199 | pg.init() 200 | pg.display.set_caption(CAPTION) 201 | pg.display.set_mode(SCREEN_SIZE) 202 | PLAY_IMAGE = pg.image.load("smallface.png").convert_alpha() 203 | POND_IMAGE = pg.image.load("pond.png").convert_alpha() 204 | Control().main_loop() 205 | pg.quit() 206 | sys.exit() 207 | 208 | 209 | if __name__ == "__main__": 210 | main() 211 | -------------------------------------------------------------------------------- /topdown_scrolling/smallface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mekire/pygame-samples/1148743bcf77a7ec6831950f1e5eb7386a09c301/topdown_scrolling/smallface.png --------------------------------------------------------------------------------