📜 ⬆️ ⬇️

Writing a python platformer using pygame. Part 2 subpart 2. Level editor


Hello friends! Today, we finally finish our marioboya . Start here and here . But we will not reinvent your bike as a level editor, but use a ready-made powerful tool . For acquaintance with whom I am grateful to the gentlemen (comrades) sourcerer and Tarvitz

Why is that?


There are several reasons for this


We create troubles and barriers for our hero


You can read about working with the Tiled map editor, for example, here .
I will describe the main points of creating the level for our game.
Our map consists of at least 5 layers:
  1. BackGround - background
  2. Platforms - blocks you can run
  3. DieBlocks -blocks with which the hero causes instant death
  4. Monsters - a layer of objects, here our monsters, as well as the princess and the hero himself
  5. Teleports - a layer of objects, for which it is clear by name



Background

Here you can draw anything and anything, tiles from this layer do not affect the hero or the gameplay, except the aesthetic appearance of the game :)

')
The blocks that you can run

On this layer are tiles that create objects of the Platform class in the game.


Deadly dangerous blocks

All tiles, regardless of appearance, be it spikes or a brick wall, create BlockDie objects in the game


Monsters

This is a layer of objects, which means that it does not display tiles in the game and every object added to it must have some properties.
Objects of the Monster class, whose constructor has the following form
class Monster(sprite.Sprite): def __init__(self, x, y, left, up, maxLengthLeft,maxLengthUp): 

Must have such properties as: left , maxLeft , up , maxUp - manually filled and x , y - transmitted by the location of the object.


The character object must be named Player


Princess object must be named Princess


Layer view:


Teleports

The objects of this layer should have the properties of the final destination of the hero's move: goX and goY

Layer:

How to find out the final coordinates?

Easy! Place the mouse cursor on the place where you want the hero to teleport and look at the coordinates of the place to the left below.


Game card


Slightly Modify the main game file in order to open the above created map.
First, download the necessary libraries from here and throw them into the folder with the source codes of the game.
And import them
 import tmxreader #   tmx  import helperspygame #  tmx     pygame 

Next, we clear the loadLevel () procedure, we will rewrite it.
loadlevel
 def loadLevel(name): global playerX, playerY #   ,    global total_level_height, total_level_width global sprite_layers #    world_map = tmxreader.TileMapParser().parse_decode('%s/%s.tmx' % (FILE_DIR, name)) #   resources = helperspygame.ResourceLoaderPygame() #    resources.load(world_map) #      pygame  sprite_layers = helperspygame.get_layers_from_map(resources) #     #     0 -  , 1-  , 2 -    # 3 -   , 4 -    platforms_layer = sprite_layers[1] dieBlocks_layer = sprite_layers[2] for row in range(0, platforms_layer.num_tiles_x): #     for col in range(0, platforms_layer.num_tiles_y): if platforms_layer.content2D[col][row] is not None: pf = Platform(row * PLATFORM_WIDTH, col * PLATFORM_WIDTH)#       Platform platforms.append(pf) if dieBlocks_layer.content2D[col][row] is not None: bd = BlockDie(row * PLATFORM_WIDTH, col * PLATFORM_WIDTH) platforms.append(bd) teleports_layer = sprite_layers[4] for teleport in teleports_layer.objects: try: #       goX = int(teleport.properties["goX"]) * PLATFORM_WIDTH goY = int (teleport.properties["goY"]) * PLATFORM_HEIGHT x = teleport.x y = teleport.y - PLATFORM_HEIGHT tp = BlockTeleport(x, y, goX, goY) entities.add(tp) platforms.append(tp) animatedEntities.add(tp) except: #    ,       print(u"   ") monsters_layer = sprite_layers[3] for monster in monsters_layer.objects: try: x = monster.x y = monster.y if monster.name == "Player": playerX = x playerY = y - PLATFORM_HEIGHT elif monster.name == "Princess": pr = Princess(x, y - PLATFORM_HEIGHT) platforms.append(pr) entities.add(pr) animatedEntities.add(pr) else: up = int(monster.properties["up"]) maxUp = int(monster.properties["maxUp"]) left = int(monster.properties["left"]) maxLeft = int(monster.properties["maxLeft"]) mn = Monster(x, y - PLATFORM_HEIGHT, left, up, maxLeft, maxUp) entities.add(mn) platforms.append(mn) monsters.add(mn) except: print(u"   ") total_level_width = platforms_layer.num_tiles_x * PLATFORM_WIDTH #     total_level_height = platforms_layer.num_tiles_y * PLATFORM_HEIGHT #  


What do we see here?

To begin with, the procedure now accepts the name input parameter, which is used to load the level map. This was done in order to make the transition between levels.
Next comes the loading and transformation of the map, and by the same principle that we are parsing the array with the map, parsing the layers with the tiles. Please note that now created objects of the Platform and BlockDie classes are not placed in the group of entities , which means that we will not display them ie they will exist, but not be displayed. Instead, we will display tiles from the map layers.

Continue

Now let's do the main procedure
Add a renderer of the map layers
 renderer = helperspygame.RendererPygame() #  

For what he - see below

Change the call to the loadLevel procedure
 for lvl in range(1,4): loadLevel("map_%s" % lvl) 

And further, all the code will be in this loop.

In the unit for displaying images on the screen, add the work of the visualizer
 for sprite_layer in sprite_layers: #    if not sprite_layer.is_object_group: #       renderer.render_layer(screen, sprite_layer) #   *** center_offset = camera.reverse(CENTER_OF_SCREEN) #      renderer.set_camera_position_and_size(center_offset[0], center_offset[1], \ WIN_WIDTH, WIN_HEIGHT, "center") 

Notice that the renderer displays its images in the center of the screen, inside the moving focus of the camera, for this we needed to add a procedure to the Camera class
Camera
 class Camera(object): def __init__(self, camera_func, width, height): self.camera_func = camera_func self.state = Rect(0, 0, width, height) def apply(self, target): return target.rect.move(self.state.topleft) def update(self, target): self.state = self.camera_func(self.state, target.rect) def reverse(self, pos):#      return pos[0] - self.state.left, pos[1] - self.state.top 



Remove what was transferred to the loadlevel procedure, add some new and get the following view:
main
 def main(): pygame.init() #  PyGame,   screen = pygame.display.set_mode(DISPLAY) #   pygame.display.set_caption("Super Mario Boy") #    bg = Surface((WIN_WIDTH, WIN_HEIGHT)) #    #     renderer = helperspygame.RendererPygame() #  for lvl in range(1,4): loadLevel("levels/map_%s" % lvl) bg.fill(Color(BACKGROUND_COLOR)) #     left = right = False #   -  up = False running = False try: hero = Player(playerX, playerY) #    (x,y)  entities.add(hero) except: print (u"     ,   -") hero = Player(65, 65) entities.add(hero) timer = pygame.time.Clock() camera = Camera(camera_configure, total_level_width, total_level_height) while not hero.winner: #    timer.tick(60) for e in pygame.event.get(): #   if e.type == QUIT: raise SystemExit, "QUIT" if e.type == KEYDOWN and e.key == K_UP: up = True if e.type == KEYDOWN and e.key == K_LEFT: left = True if e.type == KEYDOWN and e.key == K_RIGHT: right = True if e.type == KEYDOWN and e.key == K_LSHIFT: running = True if e.type == KEYUP and e.key == K_UP: up = False if e.type == KEYUP and e.key == K_RIGHT: right = False if e.type == KEYUP and e.key == K_LEFT: left = False if e.type == KEYUP and e.key == K_LSHIFT: running = False for sprite_layer in sprite_layers: #    if not sprite_layer.is_object_group: #       renderer.render_layer(screen, sprite_layer) #   for e in entities: screen.blit(e.image, camera.apply(e)) animatedEntities.update() # a  monsters.update(platforms) #    camera.update(hero) #     center_offset = camera.reverse(CENTER_OF_SCREEN) renderer.set_camera_position_and_size(center_offset[0], center_offset[1], \ WIN_WIDTH, WIN_HEIGHT, "center") hero.update(left, right, up, running, platforms) #  pygame.display.update() #        screen.blit(bg, (0, 0)) #      for sprite_layer in sprite_layers: if not sprite_layer.is_object_group: renderer.render_layer(screen, sprite_layer) #    for e in entities: screen.blit(e.image, camera.apply(e)) #     font=pygame.font.Font(None,38) text=font.render(("Thank you MarioBoy! but our princess is in another level!"), 1,(255,255,255))#   screen.blit(text, (10,100)) pygame.display.update() time.wait(10000) #  10    -     


What's so interesting?

In addition to what we have discussed in this and the previous parts of the article, there is an event that occurs when our hero touches the princess. At this moment we display a message about its failure, and we give time to read it and go to the next iteration, loading the next level.

That's all. So, we quickly and easily reworked the game to load levels from tmx files.

Sources on github

Source: https://habr.com/ru/post/204112/


All Articles