├── 43510_1256969973.PNG ├── GRS2ROC.bmp ├── README.md ├── background.py ├── consts.py ├── dpnpcsq.png ├── game.py ├── main.py ├── map ├── __init__.py ├── builder.py └── level1.map ├── move.py ├── player.py └── sprite.py /43510_1256969973.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpietka/python-rpg-engine/f44d1adbae605d5c16f3b7c14979514be82e1deb/43510_1256969973.PNG -------------------------------------------------------------------------------- /GRS2ROC.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpietka/python-rpg-engine/f44d1adbae605d5c16f3b7c14979514be82e1deb/GRS2ROC.bmp -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://scrutinizer-ci.com/g/fpietka/python-rpg-engine/badges/build.png?b=master)](https://scrutinizer-ci.com/g/fpietka/python-rpg-engine/build-status/master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fpietka/python-rpg-engine/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/fpietka/python-rpg-engine/?branch=master) [![Code Health](https://landscape.io/github/fpietka/python-rpg-engine/master/landscape.svg?style=flat)](https://landscape.io/github/fpietka/python-rpg-engine/master) 2 | 3 | Python RPG game engine 4 | -------------------------------------------------------------------------------- /background.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pygame 3 | import consts 4 | import operator 5 | from map.builder import Builder 6 | from move import move 7 | 8 | 9 | class Background(object): 10 | LAYER_GROUND = 0 11 | LAYER_CHARACTERS = 1 12 | 13 | def __init__(self, (x, y), fouraxis=True): 14 | # Set movement type 15 | self.fouraxis = fouraxis 16 | self.movesquare = False # XXX will be used to move full squares 17 | 18 | self.builder = Builder() 19 | self.sprites = dict() 20 | self.mainSprite = None 21 | #coordinates (top left point) of the camera view in the world 22 | self.xCamera, self.yCamera = 0, 0 23 | 24 | def update(self, screen_size): 25 | for l in self.sprites: 26 | for s in self.sprites[l]: 27 | if s.IS_STATIC: 28 | continue 29 | 30 | oldPosition = s.getPosition() 31 | 32 | #~ If the sprite has a movement pattern, move it according to 33 | #~ its current position in the pattern 34 | if s.movePattern is not None: 35 | move.getNextPosition(s, s.movePattern) 36 | 37 | s.updatePosition( 38 | s.calculatePosition( 39 | (self.builder.width, self.builder.height) 40 | ) 41 | ) 42 | s.drawHitBox() 43 | colliding = pygame.sprite.spritecollide( 44 | s.hitBox, 45 | [s2.hitBox for s2 in self.sprites[l]], 46 | False 47 | ) 48 | if len(colliding) > 1: 49 | s.updatePosition(oldPosition) 50 | s.moving = False 51 | s.drawHitBox() 52 | elif s.movePattern is not None: 53 | # Get sprites with move patterns move again 54 | s.moving = True 55 | 56 | s.updateFrame(pygame.time.get_ticks()) 57 | 58 | if s == self.mainSprite: 59 | self.updateFocus() 60 | 61 | for s in self.sprites[l]: 62 | s.draw(self.xCamera, self.yCamera) 63 | 64 | self.fond = self.builder.update( 65 | (self.xCamera, self.yCamera), screen_size 66 | ) 67 | 68 | # use operator since it's faster than lambda 69 | cmpfun = operator.attrgetter("yPos") 70 | 71 | for group in self.sprites.itervalues(): 72 | # to debug, display the hitbox 73 | #~ for sprite in group: 74 | #~ sprite.image.blit( 75 | #~ sprite.hitBox.image, 76 | #~ sprite.spriteset['hitbox']['positionInSprite'] 77 | #~ ) 78 | pygame.sprite.OrderedUpdates( 79 | sorted(group, key=cmpfun) 80 | ).draw(self.fond) 81 | 82 | def updateFocus(self): 83 | #get mainSprite (new) coordinates in the world 84 | xMainSprite, yMainSprite = self.mainSprite.xPos, self.mainSprite.yPos 85 | 86 | #move camera if not out of world boundaries 87 | self.xCamera = max( 88 | 0, 89 | min( 90 | self.builder.width - consts.RESOLUTION[0], 91 | xMainSprite - consts.RESOLUTION[0] / 2 92 | ) 93 | ) 94 | self.yCamera = max( 95 | 0, 96 | min( 97 | self.builder.height - consts.RESOLUTION[1], 98 | yMainSprite - consts.RESOLUTION[1] / 2 99 | ) 100 | ) 101 | 102 | def setMainSprite(self, sprite, layer=LAYER_CHARACTERS): 103 | self.addSprite(sprite, layer) 104 | self.mainSprite = sprite 105 | 106 | def addSprite(self, sprite, layer=LAYER_GROUND): 107 | if layer not in self.sprites: 108 | self.sprites[layer] = pygame.sprite.OrderedUpdates() 109 | if sprite not in self.sprites[layer]: 110 | self.sprites[layer].add(sprite) 111 | -------------------------------------------------------------------------------- /consts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | RESOLUTION = 800, 600 4 | FPS = 60 5 | 6 | JOYSTICK = { 7 | "Xbox 360 Wireless Receiver": { 8 | "up": 13, 9 | "down": 14, 10 | "left": 11, 11 | "right": 12, 12 | "A": 0, 13 | "B": 1, 14 | "X": 2, 15 | "Y": 3, 16 | "LB": 4, 17 | "RB": 5, 18 | "Back": 6, 19 | "Start": 7, 20 | "Xbox": 8, 21 | "Left stick": 9, 22 | "Right stick": 10 23 | } 24 | } 25 | 26 | hitbox_types_rect = 1 27 | hitbox_types_circle = 2 28 | 29 | tiles = { 30 | 'scholar': { 31 | 'name': 'dpnpcsq.png', 'height': 32, 'width': 32, 'map': ( 32 | (0, 4), 33 | (0, 5), 34 | (0, 6), 35 | (0, 7), 36 | (1, 4), 37 | (1, 5), 38 | (1, 6), 39 | (1, 7), 40 | (2, 4), 41 | (2, 5), 42 | (2, 6), 43 | (2, 7) 44 | ), 45 | 'hitbox': { 46 | 'type': hitbox_types_rect, 47 | 'positionInSprite': (8, 24), 48 | 'size': (16, 8), 49 | } 50 | }, 51 | 'umbrella': { 52 | 'name': 'dpnpcsq.png', 'height': 32, 'width': 32, 'map': ( 53 | (0, 0), 54 | (0, 1), 55 | (0, 2), 56 | (0, 3), 57 | (1, 0), 58 | (1, 1), 59 | (1, 2), 60 | (1, 3), 61 | (2, 0), 62 | (2, 1), 63 | (2, 2), 64 | (2, 3) 65 | ), 66 | 'hitbox': { 67 | 'type': hitbox_types_rect, 68 | 'positionInSprite': (8, 24), 69 | 'size': (16, 8), 70 | } 71 | }, 72 | 'new': { 73 | 'name': '43510_1256969973.PNG', 'height': 32, 'width': 24, 'map': ( 74 | (0, 0), 75 | (1, 3), 76 | (0, 3), 77 | (2, 3), 78 | (0, 1), 79 | (1, 1), 80 | (2, 1), 81 | (2, 0), 82 | (1, 0), 83 | (0, 2), 84 | (1, 2), 85 | (2, 2) 86 | ), 87 | 'hitbox': { 88 | 'type': hitbox_types_rect, 89 | 'positionInSprite': (8, 24), 90 | 'size': (16, 8), 91 | } 92 | } 93 | } 94 | 95 | 96 | movementsPatterns = { 97 | 'rect': { 98 | 'attributes': ('width', 'height') 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /dpnpcsq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpietka/python-rpg-engine/f44d1adbae605d5c16f3b7c14979514be82e1deb/dpnpcsq.png -------------------------------------------------------------------------------- /game.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pygame 3 | import consts 4 | from background import Background 5 | import player 6 | 7 | 8 | class Game(): 9 | def __init__(self): 10 | """ 11 | Initialize the game 12 | """ 13 | pygame.init() 14 | self.screen = pygame.display.set_mode(consts.RESOLUTION) 15 | pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN, pygame.KEYUP]) 16 | # Blit background 17 | self.background = Background((0, 0)) 18 | # TODO avoid acting on sprite and do actions on group? 19 | self.player = player.Player({ 20 | 'tilesGroup': 'new', 21 | 'x': 400, 22 | 'y': 300 23 | }) 24 | self.background.setMainSprite(self.player) 25 | self.init_joystick() 26 | 27 | def init_joystick(self): 28 | """ 29 | Initialize a joystick if available 30 | """ 31 | pygame.joystick.init() 32 | if pygame.joystick.get_count() == 0: 33 | return 34 | for joystick_id in range(0, pygame.joystick.get_count()): 35 | joystick = pygame.joystick.Joystick(joystick_id) 36 | # Stop on first matching joystick 37 | if joystick.get_name() in consts.JOYSTICK: 38 | print "Initializing Joystick id:%d" % joystick.get_id() 39 | joystick.init() 40 | self.joystick_mapping = consts.JOYSTICK[joystick.get_name()] 41 | self.joystick = joystick 42 | joystick_info = (joystick.get_name(), joystick.get_numaxes()) 43 | print "%s (%d axis)" % joystick_info 44 | break 45 | 46 | def run(self): 47 | """ 48 | Main loop 49 | """ 50 | running = True 51 | # run until an event tells us to stop 52 | while running: 53 | pygame.time.Clock().tick(consts.FPS) 54 | running = self.handleEvents() 55 | self.background.update(self.screen.get_size()) 56 | 57 | camera = - self.background.xCamera, - self.background.yCamera 58 | self.screen.blit(self.background.fond, (0, 0)) 59 | # update screen 60 | rect = pygame.Rect( 61 | 0, 62 | 0, 63 | consts.RESOLUTION[0], 64 | consts.RESOLUTION[1] 65 | ) 66 | pygame.display.update(rect) 67 | 68 | def handleEvents(self): 69 | """ 70 | Poll for pygame events 71 | """ 72 | for event in pygame.event.get(): 73 | if event.type == pygame.QUIT: 74 | return False 75 | 76 | # handle user input 77 | elif event.type == pygame.KEYDOWN: 78 | # if the user presses escape or 'q', quit the event loop. 79 | if event.key in (pygame.K_ESCAPE, pygame.K_q): 80 | return False 81 | # handle speed 82 | if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT): 83 | self.player.speed = 6 84 | # movement control 85 | if event.key == pygame.K_UP: 86 | self.player.moveVertical(-1) 87 | if event.key == pygame.K_DOWN: 88 | self.player.moveVertical(1) 89 | if event.key == pygame.K_LEFT: 90 | self.player.moveHorizontal(-1) 91 | if event.key == pygame.K_RIGHT: 92 | self.player.moveHorizontal(1) 93 | if event.key == pygame.K_c: 94 | #~ create new sprite 95 | s = player.Player({ 96 | 'tilesGroup': 'scholar', 97 | 'x': 300, 98 | 'y': 300, 99 | 'movePattern': { 100 | 'type': 'rect', 101 | 'attributes': { 102 | 'width': 200, 103 | 'height': 200 104 | } 105 | } 106 | }) 107 | self.background.addSprite(s, Background.LAYER_CHARACTERS) 108 | 109 | elif event.type == pygame.JOYBUTTONDOWN: 110 | # Joystick control 111 | # handle speed 112 | if event.button == self.joystick_mapping['B']: 113 | self.player.speed = 6 114 | # movement control 115 | if event.button == self.joystick_mapping['up']: 116 | self.player.moveVertical(-1) 117 | if event.button == self.joystick_mapping['down']: 118 | self.player.moveVertical(1) 119 | if event.button == self.joystick_mapping['left']: 120 | self.player.moveHorizontal(-1) 121 | if event.button == self.joystick_mapping['right']: 122 | self.player.moveHorizontal(1) 123 | 124 | elif event.type == pygame.KEYUP: 125 | # handle speed 126 | if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT): 127 | self.player.speed = 3 128 | # stop movement control 129 | if event.key == pygame.K_UP: 130 | self.player.moveVertical(1) 131 | if event.key == pygame.K_DOWN: 132 | self.player.moveVertical(-1) 133 | if event.key == pygame.K_LEFT: 134 | self.player.moveHorizontal(1) 135 | if event.key == pygame.K_RIGHT: 136 | self.player.moveHorizontal(-1) 137 | 138 | elif event.type == pygame.JOYBUTTONUP: 139 | # Joystick control 140 | # handle speed 141 | if event.button == self.joystick_mapping['B']: 142 | self.player.speed = 3 143 | # stop movement control 144 | if event.button == self.joystick_mapping['up']: 145 | self.player.moveVertical(1) 146 | if event.button == self.joystick_mapping['down']: 147 | self.player.moveVertical(-1) 148 | if event.button == self.joystick_mapping['left']: 149 | self.player.moveHorizontal(1) 150 | if event.button == self.joystick_mapping['right']: 151 | self.player.moveHorizontal(-1) 152 | 153 | if event.type == pygame.JOYAXISMOTION: 154 | # We won't need to slow diagonals 155 | self.player.axismove = True 156 | if abs(round(event.value, 1)) > 0.1: 157 | # Reduce steps 158 | value = round(event.value, 1) 159 | else: 160 | # Try to handle back to the middle 161 | value = 0 162 | # We must not add up those values, but use them as they are 163 | if event.axis == 1: 164 | self.player.moveVertical(value, absolute=True) 165 | elif event.axis == 0: 166 | self.player.moveHorizontal(value, absolute=True) 167 | pygame.event.pump() 168 | else: 169 | self.player.axismove = False 170 | 171 | # TODO make the sprite move too 172 | return True 173 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import pygame 4 | 5 | from game import Game 6 | 7 | # create a game and run it 8 | if __name__ == '__main__': 9 | game = Game() 10 | game.run() 11 | -------------------------------------------------------------------------------- /map/__init__.py: -------------------------------------------------------------------------------- 1 | import builder 2 | -------------------------------------------------------------------------------- /map/builder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pygame 3 | import math 4 | 5 | tileset_config = { 6 | 'name': 'GRS2ROC.bmp', 'height': 40, 'width': 40, 'map': ( 7 | (80, 40), 8 | (80, 120), 9 | (80, 200), 10 | (80, 280), # full grass 11 | (160, 40), 12 | (160, 120), # full stone 13 | (160, 200), 14 | (160, 280), 15 | (160, 360), 16 | (240, 40), 17 | (240, 120), 18 | (240, 200), 19 | (240, 280), 20 | (240, 360) 21 | )} 22 | 23 | 24 | class Builder(): 25 | def __init__(self): 26 | self.level_map = filter( 27 | None, 28 | open("map/level1.map", "rb").read().split("\n") 29 | ) 30 | (self.width, self.height) = ( 31 | len(self.level_map[0]) * tileset_config['width'], 32 | len(self.level_map) * tileset_config['height'] 33 | ) 34 | self.build_tileset() 35 | 36 | def build_tileset(self): 37 | "Build tile set" 38 | # load the image with the tiles 39 | resource = pygame.image.load(tileset_config['name']).convert() 40 | # get width/height from config 41 | width = tileset_config['width'] 42 | height = tileset_config['height'] 43 | self.tileset = list() 44 | # cut out tiles from tileset 45 | for index, mapcells in enumerate(tileset_config['map']): 46 | top, left = mapcells 47 | rect = pygame.Rect(left, top, width, height) 48 | subSurface = resource.subsurface(rect) 49 | # add subsurface to the collection 50 | self.tileset.append(subSurface) 51 | 52 | def update(self, (x, y), screen_size): 53 | "Build visible map" 54 | (width, height) = screen_size 55 | fond = pygame.Surface((screen_size)) 56 | 57 | # @TODO lamba or function for that 58 | #~ Position in the current cell (px) 59 | # get position in the world (x, y) and figure out 60 | # where to start blitting the cell itself 61 | xPosInCell = x % tileset_config['width'] 62 | yPosInCell = y % tileset_config['height'] 63 | #~ number of cells of the area to display 64 | # according to their size and the display size 65 | nbCellsWidth = int( 66 | math.ceil( 67 | float(xPosInCell + width) / float(tileset_config['width']) 68 | ) 69 | ) 70 | nbCellsHeight = int( 71 | math.ceil( 72 | float(yPosInCell + height) / float(tileset_config['height']) 73 | ) 74 | ) 75 | #~ index of the first cell to display 76 | startCellIndexX = x / tileset_config['width'] 77 | startCellIndexY = y / tileset_config['height'] 78 | 79 | lines = enumerate( 80 | range(startCellIndexY, startCellIndexY + nbCellsHeight), 0 81 | ) 82 | for index_y, line in lines: 83 | columns = enumerate( 84 | range(startCellIndexX, startCellIndexX + nbCellsWidth), 0 85 | ) 86 | for index_x, column in columns: 87 | square = self.level_map[line][column] 88 | if square == '#': 89 | tile = self.tileset[3] 90 | elif square == '.': 91 | tile = self.tileset[5] 92 | 93 | fond.blit( 94 | tile, 95 | ( 96 | index_x * tileset_config['width'] - xPosInCell, 97 | index_y * tileset_config['height'] - yPosInCell 98 | ) 99 | ) 100 | 101 | return fond 102 | -------------------------------------------------------------------------------- /map/level1.map: -------------------------------------------------------------------------------- 1 | #################################################################################################### 2 | ##.##.##....##.####.####....######################################################################## 3 | ##.##.##.#####.####.####.##.######################################################################## 4 | ##....##..####.####.####.##.######################################################################## 5 | ##.##.##.#####.####.####.##.######################################################################## 6 | ##.##.##....##....#....#....######################################################################## 7 | #############################################....................################################### 8 | ##.###.#....#....#.####...##################..##################.################################### 9 | ##.###.#.##.#.##.#.####.##.################..###################.################################### 10 | ##.#.#.#.##.#....#.####.##.###############..####################.################################### 11 | ##..#..#.##.#.#.##.####.##.##############..#####################.################################### 12 | ##.###.#....#.##.#....#...##############..######################.################################### 13 | #######################################..#######################.################################### 14 | ######################################..########################.################################### 15 | ###################.#################..#########################.################################### 16 | ###################.################..##########################.################################### 17 | ###################.###############..###########################.################################### 18 | ###################.##############..############################.################################### 19 | ###################.#############..#############################.################################### 20 | ###################.....########..#################..............################################### 21 | #######################.....###..##################.################################################ 22 | ###########################.....###################.################################################ 23 | ###################################################.################################################ 24 | ###################################################.################################################ 25 | ###################################################.################################################ 26 | ###################################################.################################################ 27 | ###################################################.################################################ 28 | ###################################################.################################################ 29 | ###################################################.################################################ 30 | ###################################################.################################################ 31 | ###################################################.################################################ 32 | #################################################################################################### 33 | #################################################################################################### 34 | #################################################################################################### 35 | #################################################################################################### 36 | #################################################################################################### 37 | #################################################################################################### 38 | #################################################################################################### 39 | #################################################################################################### 40 | #################################################################################################### 41 | #################################################################################################### 42 | #################################################################################################### 43 | #################################################################################################### 44 | #################################################################################################### 45 | #################################################################################################### 46 | #################################################################################################### 47 | #################################################################################################### 48 | #################################################################################################### 49 | #################################################################################################### 50 | #################################################################################################### 51 | #################################################################################################### 52 | #################################################################################################### 53 | #################################################################################################### 54 | #################################################################################################### 55 | #################################################################################################### 56 | #################################################################################################### 57 | #################################################################################################### 58 | #################################################################################################### 59 | #################################################################################################### 60 | #################################################################################################### 61 | #################################################################################################### 62 | #################################################################################################### 63 | #################################################################################################### 64 | #################################################################################################### 65 | #################################################################################################### 66 | #################################################################################################### 67 | #################################################################################################### 68 | #################################################################################################### 69 | #################################################################################################### 70 | #################################################################################################### 71 | #################################################################################################### 72 | #################################################################################################### 73 | #################################################################################################### 74 | #################################################################################################### 75 | #################################################################################################### 76 | #################################################################################################### 77 | #################################################################################################### 78 | #################################################################################################### 79 | #################################################################################################### 80 | #################################################################################################### 81 | #################################################################################################### 82 | #################################################################################################### 83 | #################################################################################################### 84 | #################################################################################################### 85 | #################################################################################################### 86 | #################################################################################################### 87 | #################################################################################################### 88 | #################################################################################################### 89 | #################################################################################################### 90 | #################################################################################################### 91 | #################################################################################################### 92 | #################################################################################################### 93 | #################################################################################################### 94 | #################################################################################################### 95 | #################################################################################################### 96 | #################################################################################################### 97 | #################################################################################################### 98 | #################################################################################################### 99 | #################################################################################################### 100 | -------------------------------------------------------------------------------- /move.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import consts 4 | 5 | 6 | class move: 7 | @staticmethod 8 | def getNextPosition(character, movePattern): 9 | 10 | if movePattern['type'] == 'rect': 11 | move.getNextPositionRect(character, movePattern['attributes']) 12 | elif movePattern['type'] == 'circle': 13 | pass 14 | else: 15 | raise move.exception('Unknown move') 16 | 17 | @staticmethod 18 | def getNextPositionRect(character, attributes): 19 | xInMove = character.xPos - attributes['topLeft'][0] 20 | yInMove = character.yPos - attributes['topLeft'][1] 21 | 22 | if xInMove <= 0 and yInMove <= 0: 23 | character.stop() 24 | #~ move right 25 | character.moveHorizontal(1) 26 | elif xInMove >= attributes['width'] and yInMove <= 0: 27 | character.stop() 28 | #~ move down 29 | character.moveVertical(1) 30 | elif xInMove >= attributes['width']\ 31 | and yInMove >= attributes['height']: 32 | character.stop() 33 | #~ move left 34 | character.moveHorizontal(-1) 35 | elif xInMove <= 0 and yInMove >= attributes['height']: 36 | character.stop() 37 | #~ move up 38 | character.moveVertical(-1) 39 | 40 | 41 | class exception(BaseException): 42 | pass 43 | -------------------------------------------------------------------------------- /player.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pygame 3 | import consts 4 | import math 5 | import move 6 | import sprite 7 | 8 | 9 | class Player(sprite.DynamicSprite): 10 | def __init__(self, options): 11 | super(Player, self).__init__() 12 | # Spriteset parameters 13 | self.spriteset = consts.tiles[options['tilesGroup']] 14 | self.characterSizeX, self.characterSizeY =\ 15 | self.spriteset['width'], self.spriteset['height'] 16 | 17 | # Animation parameters 18 | self._start = pygame.time.get_ticks() 19 | self._delay = 10000 / consts.FPS 20 | self._last_update = 0 21 | self.frame = 0 22 | self.animation = .5 23 | # Handle sprite set 24 | self.build_spriteset() 25 | self.direction = 'down' 26 | self.updateDirection() 27 | self.rect = self.image.get_rect() 28 | self.xPos, self.yPos = options['x'], options['y'] 29 | self.rect.center = [dimension / 2 for dimension in consts.RESOLUTION] 30 | self.moveX, self.moveY = 0, 0 31 | self.speed = 3 32 | self.moving = False 33 | self._createHitBox() 34 | self.axismove = False 35 | 36 | if 'movePattern' not in options: 37 | self.movePattern = None 38 | else: 39 | if 'type' not in options['movePattern']: 40 | raise move.exception('A move pattern type is required') 41 | if 'attributes' not in options['movePattern']: 42 | raise move.exception('Move pattern attributes are required') 43 | self.movePattern = options['movePattern'] 44 | self.movePattern['attributes']['topLeft'] = (self.xPos, self.yPos) 45 | 46 | def updateDirection(self): 47 | direction = self.spritesetDirections[self.direction][self.frame] 48 | self.image = direction.convert() 49 | 50 | def _createHitBox(self): 51 | self.hitBox = pygame.sprite.Sprite() 52 | self.hitBox.image = pygame.Surface(self.spriteset['hitbox']['size']) 53 | self.hitBox.image.fill((0, 0, 0)) 54 | #handle other hitbox types 55 | self.hitBox.rect = self.hitBox.image.get_rect() 56 | 57 | def calculatePosition(self, mapSize): 58 | if self.moveX == 0 or self.moveY == 0 or self.axismove: 59 | x_velocity = int(self.moveX * self.speed) 60 | y_velocity = int(self.moveY * self.speed) 61 | else: 62 | x_velocity = int(self.moveX * self.speed / math.sqrt(2)) 63 | y_velocity = int(self.moveY * self.speed / math.sqrt(2)) 64 | 65 | return ( 66 | min( 67 | mapSize[0] - self.characterSizeX / 2, 68 | max( 69 | self.characterSizeX / 2, 70 | self.xPos + x_velocity 71 | ) 72 | ), 73 | min( 74 | mapSize[1] - self.characterSizeY / 2, 75 | max( 76 | self.characterSizeY / 2, 77 | self.yPos + y_velocity 78 | ) 79 | ) 80 | ) 81 | 82 | def updatePosition(self, position): 83 | self.xPos, self.yPos = position 84 | # Set moving 85 | self.moving = not (self.moveX, self.moveY) == (0, 0) 86 | 87 | def draw(self, x, y): 88 | self.rect.center = (self.xPos - x, self.yPos - y) 89 | 90 | def drawHitBox(self): 91 | self.hitBox.rect.center = map( 92 | sum, 93 | zip( 94 | (self.xPos, self.yPos), 95 | self.spriteset['hitbox']['positionInSprite'] 96 | ) 97 | ) 98 | 99 | def getPosition(self): 100 | return (self.xPos, self.yPos) 101 | 102 | def updateFrame(self, tick): 103 | if not self.moving: 104 | self.frame = 0 105 | elif tick - self._last_update > self._delay: 106 | self._last_update = tick 107 | # frame should go 0, 1, 0, 2, 0, 1, ... 108 | if not self.animation == int(self.animation): 109 | self.frame = 0 110 | elif self.animation / 2 == int(self.animation / 2): 111 | self.frame = 1 112 | else: 113 | self.frame = 2 114 | self.animation += .5 115 | 116 | if self.moveX < 0: 117 | self.direction = 'left' 118 | if self.moveX > 0: 119 | self.direction = 'right' 120 | if self.moveY < 0: 121 | self.direction = 'up' 122 | if self.moveY > 0: 123 | self.direction = 'down' 124 | 125 | self.updateDirection() 126 | 127 | def build_spriteset(self): 128 | "Cut and build sprite set" 129 | width = self.spriteset['width'] 130 | height = self.spriteset['height'] 131 | fond = pygame.image.load(self.spriteset['name']).convert() 132 | pixel = self.spriteset['map'][0] 133 | transparent_pixel = (pixel[0] * height, pixel[1] * width) 134 | fond.set_colorkey( 135 | fond.get_at(transparent_pixel) 136 | ) 137 | # use map to cut parts 138 | spriteset = list() 139 | for (left, top) in self.spriteset['map']: 140 | rect = pygame.Rect(left * width, top * height, width, height) 141 | spriteset.append(fond.subsurface(rect).convert()) 142 | # build direction there 143 | self.spritesetDirections = { 144 | 'up': (spriteset[0], spriteset[8], spriteset[7]), 145 | 'down': (spriteset[9], spriteset[10], spriteset[11]), 146 | 'left': (spriteset[2], spriteset[1], spriteset[3]), 147 | 'right': (spriteset[4], spriteset[5], spriteset[6]) 148 | } 149 | 150 | def moveVertical(self, coef=1, absolute=False): 151 | if absolute: 152 | self.moveY = coef 153 | else: 154 | self.moveY += coef 155 | 156 | def moveHorizontal(self, coef=1, absolute=False): 157 | if absolute: 158 | self.moveX = coef 159 | else: 160 | self.moveX += coef 161 | 162 | def stop(self): 163 | self.moveX = self.moveY = 0 164 | -------------------------------------------------------------------------------- /sprite.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pygame 3 | 4 | 5 | class StaticSprite(pygame.sprite.Sprite): 6 | IS_STATIC = True 7 | 8 | 9 | class DynamicSprite(pygame.sprite.Sprite): 10 | IS_STATIC = False 11 | --------------------------------------------------------------------------------