Childhood classic memories | take you to play a magic tower game in Python from scratch

Posted by GrexP on Wed, 09 Feb 2022 14:43:57 +0100

Relevant documents

Relevant game materials (pictures and audio, etc.) come from the network and are subject to invasion and deletion.

Small partners who need source code can add QQ group: 773162165

development tool

Python version: 3.7.4
Related modules:
pygame module;
And some python built-in modules.

Environment construction

Install Python and add it to the environment variable. pip can install the relevant modules required.

Principle introduction

First, we open the start interface of the original game and find that:

Specifically, our idea is to define a button class to simulate the functions of the three buttons of "start game", "Game Description" and "leave game" in the original game:

'''Button class'''
class Button(pygame.sprite.Sprite):
    def __init__(self, text, fontpath, fontsize, position, color_selected=(255, 0, 0), color_default=(255, 255, 255)):
        pygame.sprite.Sprite.__init__(self)
        self.text = text
        self.color_selected = color_selected
        self.color_default = color_default
        self.font = pygame.font.Font(fontpath, fontsize)
        self.font_render = self.font.render(text, True, color_default)
        self.rect = self.font_render.get_rect()
        self.rect.center = position
    '''Update function: Constantly update to detect whether the mouse is on the button'''
    def update(self):
        mouse_pos = pygame.mouse.get_pos()
        if self.rect.collidepoint(mouse_pos):
            self.font_render = self.font.render(self.text, True, self.color_selected)
        else:
            self.font_render = self.font.render(self.text, True, self.color_default)
    '''Bind to screen'''
    def draw(self, screen):
        screen.blit(self.font_render, self.rect)

The main principle is to constantly detect whether the mouse is in the corresponding button area. If it is, change the color of the button (turn red here), otherwise the button uses the default color (white here) to show the player that it is a clickable button.

Then, we instantiate it three times to add these three buttons to the game start interface:

'''Game start interface'''
class StartGameInterface():
    def __init__(self, cfg):
        self.cfg = cfg
        self.play_btn = Button('Start the game', cfg.FONTPATH_CN, 50, (cfg.SCREENSIZE[0]//2, cfg.SCREENSIZE[1] - 400))
        self.intro_btn = Button('Game Description', cfg.FONTPATH_CN, 50, (cfg.SCREENSIZE[0]//2, cfg.SCREENSIZE[1] - 300))
        self.quit_btn = Button('Leave the game', cfg.FONTPATH_CN, 50, (cfg.SCREENSIZE[0]//2, cfg.SCREENSIZE[1] - 200))
    '''External call'''
    def run(self, screen):
        # Magic tower
        font = pygame.font.Font(self.cfg.FONTPATH_CN, 80)
        font_render_cn = font.render('Magic tower', True, (255, 255, 255))
        rect_cn = font_render_cn.get_rect()
        rect_cn.center = self.cfg.SCREENSIZE[0] // 2, 200
        # Magic Tower
        font = pygame.font.Font(self.cfg.FONTPATH_EN, 80)
        font_render_en = font.render('Magic Tower', True, (255, 255, 255))
        rect_en = font_render_en.get_rect()
        rect_en.center = self.cfg.SCREENSIZE[0] // 2, 350
        # (Ver 1.12)
        font = pygame.font.Font(self.cfg.FONTPATH_CN, 40)
        font_render_version = font.render('(Ver 1.12)', True, (255, 255, 255))
        rect_ver = font_render_version.get_rect()
        rect_ver.center = self.cfg.SCREENSIZE[0] // 2, 400
        clock = pygame.time.Clock()
        while True:
            screen.fill((0, 0, 0))
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit(0)
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    if event.button == 1:
                        mouse_pos = pygame.mouse.get_pos()
                        if self.play_btn.rect.collidepoint(mouse_pos):
                            return True
                        elif self.quit_btn.rect.collidepoint(mouse_pos):
                            pygame.quit()
                            sys.exit(0)
                        elif self.intro_btn.rect.collidepoint(mouse_pos):
                            self.showgameintro(screen)
            for btn in [self.intro_btn, self.play_btn, self.quit_btn]:
                btn.update()
                btn.draw(screen)
            for fr, rect in zip([font_render_cn, font_render_en, font_render_version], [rect_cn, rect_en, rect_ver]):
                screen.blit(fr, rect)
            pygame.display.flip()
            clock.tick(self.cfg.FPS)
    '''Show game introduction'''
    def showgameintro(self, screen):
        font = pygame.font.Font(self.cfg.FONTPATH_CN, 20)
        font_renders = [
            font.render('Magic Tower Games.', True, (255, 255, 255)),
            font.render('Game material from: http://www.4399.com/flash/1749_1.htm.', True, (255, 255, 255)),
            font.render('The background story of the game is that the princess is captured by the demon king, Warriors need to go to the magic tower to rescue them.', True, (255, 255, 255)),	
            font.render('copyright, Please do not delete or reprint at will.', True, (255, 255, 255)),
        ]
        rects = [fr.get_rect() for fr in font_renders]
        for idx, rect in enumerate(rects):
            rect.center = self.cfg.SCREENSIZE[0] // 2, 50 * idx + 100
        clock = pygame.time.Clock()
        while True:
            screen.fill((0, 0, 0))
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit(0)
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    if event.button == 1:
                        mouse_pos = pygame.mouse.get_pos()
                        if self.play_btn.rect.collidepoint(mouse_pos):
                            return True
                        elif self.quit_btn.rect.collidepoint(mouse_pos):
                            pygame.quit()
                            sys.exit(0)
                        elif self.intro_btn.rect.collidepoint(mouse_pos):
                            return
            for btn in [self.intro_btn, self.play_btn, self.quit_btn]:
                btn.update()
                btn.draw(screen)
            for fr, rect in zip(font_renders, rects):
                screen.blit(fr, rect)
            pygame.display.flip()
            clock.tick(self.cfg.FPS)

Other additional codes mainly display the title of the game and other information. They are very simple and will not be discussed in detail. The little partners who will pygame can certainly write them. The final effects are as follows:

Next, let's look at what the interface looks like after the game starts, We can find that it is generally like the following (why do I say general? Because in order to write this game, I played it again and found that I couldn't pass it for a while and a half. When a little partner who has a strategy or casually passes through the customs sees that I reproduce something wrong, he can leave me a message and attach the corresponding screenshot. Maybe I play the game and compare the pictures of dishes):

Specifically, we can first define the appearance of the game map in the text file, similar to the following figure, in which each number represents a game element:

I have also collected the picture materials in the game (this is the game materials sorted out by others found on the Internet, not my own T_T):
Therefore, we can write an analysis class of game map file, like this:

'''Game map analysis class'''
class MapParser():
    def __init__(self, blocksize, filepath, element_images, offset=(0, 0), **kwargs):
        self.count = 0
        self.switch_times = 15
        self.image_pointer = 0
        self.offset = offset
        self.blocksize = blocksize
        self.element_images = element_images
        self.map_matrix = self.parse(filepath)
    '''analysis'''
    def parse(self, filepath):
        map_matrix = []
        with open(filepath, 'r') as fp:
            for line in fp.readlines():
                line = line.strip()
                if not line: continue
                map_matrix.append([c.strip() for c in line.split(',')])
        return map_matrix
    '''Draw the game map to the screen'''
    def draw(self, screen):
        self.count += 1
        if self.count == self.switch_times:
            self.count = 0
            self.image_pointer = int(not self.image_pointer)
        for row_idx, row in enumerate(self.map_matrix):
            for col_idx, elem in enumerate(row):
                position = col_idx * self.blocksize + self.offset[0], row_idx * self.blocksize + self.offset[1]
                if elem+'.png' in self.element_images:
                    image = self.element_images[elem+'.png'][self.image_pointer]
                    image = pygame.transform.scale(image, (self.blocksize, self.blocksize))
                    screen.blit(image, position)

The parse function actually reads the text file storing the game map information, and then the draw function actually binds the corresponding game element picture to the map for display according to the read map information. In addition, image_pointer,switch_ The three variables of times and count are used to achieve the flickering effect of scene elements in the original map, like this:

According to this principle, we can easily draw the original map in all layers of the magic tower. The defined game map file is shown in the figure below


The effect is shown in the figure below

ok, to sum up, this issue mainly realizes the initial picture of each layer in the magic tower game. The complete source code of this issue can be obtained here: QQ group: 773162165

Tomorrow I will take you to further reproduce the magic tower game. Interested partners can pay more attention to it~

Topics: Python Game Development pygame