current position:Home>Python game development, pyGame module, python takes you to realize a magic tower game from scratch (2)

Python game development, pyGame module, python takes you to realize a magic tower game from scratch (2)

2022-01-30 09:51:04 Dai mubai

Little knowledge , Great challenge ! This article is participating in “ A programmer must have a little knowledge ” Creative activities

Preface

In this issue, we will take you to further reproduce our magic tower game , The main content includes the definition of hero class and the realization of its basic actions , Trigger the switching of different layers in the process of action .

I don't say much nonsense , Let's start happily ~

development tool

Python edition : 3.7.4

Related modules :

pygame modular ;

As well as some python Built in modules .

Environment building

install Python And add to environment variable ,pip Install the relevant modules required .

Introduction of the principle

Last issue , We realized the basic picture definition of the game , Like this :

 Basic picture

Careful friends must have found , Why don't we have our warriors in the map ? How can we save the princess without him ~ Don't worry. , This issue brings you to realize this part .

First , Let's define our hero warrior class :

''' Define our hero Warrior '''
class Hero(pygame.sprite.Sprite):
    def __init__(self, imagepaths, blocksize, position, fontpath=None):
        pygame.sprite.Sprite.__init__(self)
        #  Set basic properties 
        self.font = pygame.font.Font(fontpath, 40)
        #  Load the corresponding picture 
        self.images = {}
        for key, value in imagepaths.items():
            self.images[key] = pygame.transform.scale(pygame.image.load(value), (blocksize, blocksize))
        self.image = self.images['down']
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = position
        #  Set level and other information 
        self.level = 1
        self.life_value = 1000
        self.attack_power = 10
        self.defense_power = 10
        self.num_coins = 0
        self.experience = 0
        self.num_yellow_keys = 0
        self.num_purple_keys = 0
        self.num_red_keys = 0
    ''' Bind the warrior to the screen '''
    def draw(self, screen):
        screen.blit(self.image, self.rect)
 Copy code 

After binding it to the main interface of the game, the effect is as follows :

 Game main interface

Does it look like something's wrong ? you 're right , There was text on the left to show the current status of the warrior ! It's all gone now ! But that's okay , No big problem , Let's write a few lines of code to display the hero's information on the left panel :

font_renders = [
    self.font.render(str(self.level), True, (255, 255, 255)),
    self.font.render(str(self.life_value), True, (255, 255, 255)),
    self.font.render(str(self.attack_power), True, (255, 255, 255)),
    self.font.render(str(self.defense_power), True, (255, 255, 255)),
    self.font.render(str(self.num_coins), True, (255, 255, 255)),
    self.font.render(str(self.experience), True, (255, 255, 255)),
    self.font.render(str(self.num_yellow_keys), True, (255, 255, 255)),
    self.font.render(str(self.num_purple_keys), True, (255, 255, 255)),
    self.font.render(str(self.num_red_keys), True, (255, 255, 255)),
]
rects = [fr.get_rect() for fr in font_renders]
rects[0].topleft = (160, 80)
for idx in range(1, 6):
    rects[idx].topleft = 160, 127 + 42 * (idx - 1)
for idx in range(6, 9):
    rects[idx].topleft = 160, 364 + 55 * (idx - 6)
for fr, rect in zip(font_renders, rects):
    screen.blit(fr, rect)
 Copy code 

The effect is like this :

 effect

Completed the most basic definition of warrior class , Then we should let him move , To be specific , We first implement a class function of warrior action :

''' action '''
def move(self, direction):
    assert direction in self.images
    self.image = self.images[direction]
    move_vector = {
        'left': (-self.blocksize, 0),
        'right': (self.blocksize, 0),
        'up': (0, -self.blocksize),
        'down': (0, self.blocksize),
    }[direction]
    self.rect.left += move_vector[0]
    self.rect.top += move_vector[1]
 Copy code 

Then write a key to detect , And determine the action direction of the warrior according to the key value pressed by the player :

key_pressed = pygame.key.get_pressed()
if key_pressed[pygame.K_w] or key_pressed[pygame.K_UP]:
    self.hero.move('up')
elif key_pressed[pygame.K_s] or key_pressed[pygame.K_DOWN]:
    self.hero.move('down')
elif key_pressed[pygame.K_a] or key_pressed[pygame.K_LEFT]:
    self.hero.move('left')
elif key_pressed[pygame.K_d] or key_pressed[pygame.K_RIGHT]:
    self.hero.move('right')
 Copy code 

If you think my code above is OK , It's done , There are two problems with this writing .

First , This writing will cause the player to press the up key once , Warriors move many squares , It makes it difficult for players to control the position of warriors , At this point, we can add an action cooling variable :

#  Action cooling 
self.move_cooling_count = 0
self.move_cooling_time = 5
self.freeze_move_flag = False
 Copy code 

Count while cooling :

if self.freeze_move_flag:
    self.move_cooling_count += 1
    if self.move_cooling_count > self.move_cooling_time:
        self.move_cooling_count = 0
        self.freeze_move_flag = False
 Copy code 

After counting, the hero can restore his action ability . therefore move Can be rewritten as :

''' action '''
def move(self, direction):
    if self.freeze_move_flag: return
    assert direction in self.images
    self.image = self.images[direction]
    move_vector = {
        'left': (-self.blocksize, 0),
        'right': (self.blocksize, 0),
        'up': (0, -self.blocksize),
        'down': (0, self.blocksize),
    }[direction]
    self.rect.left += move_vector[0]
    self.rect.top += move_vector[1]
    self.freeze_move_flag = True
 Copy code 

Interested partners can remove this code by themselves and actually feel whether there will be a difference when the keyboard operates our warriors .

Another question , And the most serious problem , That is, action will be illegal , For example, warriors will appear in such a position :

 Location

therefore , We need to add additional judgment on whether the movement is legal :

''' action '''
def move(self, direction, map_parser):
    if self.freeze_move_flag: return
    assert direction in self.images
    self.image = self.images[direction]
    move_vector = {'left': (-1, 0), 'right': (1, 0), 'up': (0, -1), 'down': (0, 1)}[direction]
    block_position = self.block_position[0] + move_vector[0], self.block_position[1] + move_vector[1]
    if block_position[0] >= 0 and block_position[0] < map_parser.map_size[1] and \
            block_position[1] >= 0 and block_position[1] < map_parser.map_size[0]:
        if map_parser.map_matrix[block_position[1]][block_position[0]] in ['0']:
            self.block_position = block_position
        elif map_parser.map_matrix[block_position[1]][block_position[0]] in ['24']:
            self.dealcollideevent(
                elem=map_parser.map_matrix[block_position[1]][block_position[0]],
                block_position=block_position,
                map_parser=map_parser,
            )
    self.rect.left, self.rect.top = self.block_position[0] * self.blocksize + self.offset[0], self.block_position[1] * self.blocksize + self.offset[1]
    self.freeze_move_flag = True
 Copy code 

here , For the convenience of judgment , We changed the original pixel coordinates into the element block coordinates in the game map ( In the game map designed in the last issue , The location index of each number in the map matrix ). in addition , Here we also need to think about the process of further reproducing the game in the future , We need to respond when warriors collide with some elements in the map , For example, warriors duel with monsters , Pick up the key and so on , So we are also in the above move Function is embedded in dealcollideevent To deal with such a situation , A simple effect is shown below :

 Effect display

Of course , In theory, according to the original game, there should be a dialog box with a background story , This part will be realized in the next issue , In this issue, we mainly realize some basic functions , For example, the triggering of some simple events , Including meeting the door , Pick up the key, etc :

''' Deal with impact events '''
def dealcollideevent(self, elem, block_position, map_parser):
    #  Encounter doors of different colors ,  If you have a key, open ,  Otherwise you can't move forward 
    if elem in ['2', '3', '4']:
        flag = False
        if elem == '2' and self.num_yellow_keys > 0:
            self.num_yellow_keys -= 1
            flag = True
        elif elem == '3' and self.num_purple_keys > 0:
            self.num_purple_keys -= 1
            flag = True
        elif elem == '4' and self.num_red_keys > 0:
            self.num_red_keys -= 1
            flag = True
        if flag: map_parser.map_matrix[block_position[1]][block_position[0]] = '0'
        return flag
    #  Picked up keys of different colors 
    elif elem in ['6', '7', '8']:
        if elem == '6': self.num_yellow_keys += 1
        elif elem == '7': self.num_purple_keys += 1
        elif elem == '8': self.num_red_keys += 1
        map_parser.map_matrix[block_position[1]][block_position[0]] = '0'
        return True
    #  I found a gem 
    elif elem in ['9', '10']:
        if elem == '9': self.defense_power += 3
        elif elem == '10': self.attack_power += 3
        map_parser.map_matrix[block_position[1]][block_position[0]] = '0'
        return True
    #  Meet a fairy ,  A dialogue ,  And move one space left 
    elif elem in ['24']:
        map_parser.map_matrix[block_position[1]][block_position[0] - 1] = elem
        map_parser.map_matrix[block_position[1]][block_position[0]] = '0'
        return False
 Copy code 

Last , Let's realize the effect of switching the current game map when the warrior goes up and down the stairs , This sounds a little difficult at first , But it's not , Just return the command of the event of going up and down stairs to the main game loop :

#  Up and down stairs 
elif elem in ['13', '14']:
    if elem == '13': events = ['upstairs']
    elif elem == '14': events = ['downstairs']
    return True, events

''' action '''
def move(self, direction, map_parser):
    #  Determine whether freezing action 
    if self.freeze_move_flag: return
    assert direction in self.images
    self.image = self.images[direction]
    #  Mobile warrior 
    move_vector = {'left': (-1, 0), 'right': (1, 0), 'up': (0, -1), 'down': (0, 1)}[direction]
    block_position = self.block_position[0] + move_vector[0], self.block_position[1] + move_vector[1]
    #  Judge whether the movement is legal ,  And trigger the corresponding event 
    events = []
    if block_position[0] >= 0 and block_position[0] < map_parser.map_size[1] and \
            block_position[1] >= 0 and block_position[1] < map_parser.map_size[0]:
        # -- Legal movement 
        if map_parser.map_matrix[block_position[1]][block_position[0]] in ['0']:
            self.block_position = block_position
        # -- Triggering event 
        elif map_parser.map_matrix[block_position[1]][block_position[0]] in ['2', '3', '4', '6', '7', '8', '9', '10', '13', '14', '24']:
            flag, events = self.dealcollideevent(
                elem=map_parser.map_matrix[block_position[1]][block_position[0]],
                block_position=block_position,
                map_parser=map_parser,
            )
            if flag: self.block_position = block_position
    #  Reset warrior position 
    self.rect.left, self.rect.top = self.block_position[0] * self.blocksize + self.offset[0], self.block_position[1] * self.blocksize + self.offset[1]
    #  Freezing operation 
    self.freeze_move_flag = True
    #  Returns the event that needs to be triggered in the main loop 
    return events
 Copy code 

Then respond in the main loop :

# -- Trigger game events 
for event in move_events:
    if event == 'upstairs':
        self.map_level_pointer += 1
        self.loadmap()
    elif event == 'downstairs':
        self.map_level_pointer -= 1
        self.loadmap()
 Copy code 

The effect is as follows :

 effect

I wonder if you have found a problem , It's that the warrior is in the wrong position when he goes upstairs , In theory, it should be near the lower staircase of the current map , Not where the warrior last went upstairs in the last game map , So how should this part be implemented ? It's very simple , A simple solution is to define the game map , Define a... At the upper and lower stairs 00 Variable :

 picture

Draw the game map according to 0 Elements to draw :

if elem in self.element_images:
    image = self.element_images[elem][self.image_pointer]
    image = pygame.transform.scale(image, (self.blocksize, self.blocksize))
    screen.blit(image, position)
elif elem in ['00', 'hero']:
    image = self.element_images['0'][self.image_pointer]
    image = pygame.transform.scale(image, (self.blocksize, self.blocksize))
    screen.blit(image, position)    
 Copy code 

But when switching game maps up and down stairs , We can use this identifier to reset the location of the role :

# -- Trigger game events 
for event in move_events:
    if event == 'upstairs':
        self.map_level_pointer += 1
        self.loadmap()
        self.hero.placenexttostairs(self.map_parser, 'down')
    elif event == 'downstairs':
        self.map_level_pointer -= 1
        self.loadmap()
        self.hero.placenexttostairs(self.map_parser, 'up')
 Copy code 

The function to reset the position is implemented as follows :

''' Place on / Next to the stairs '''
def placenexttostairs(self, map_parser, stairs_type='up'):
    assert stairs_type in ['up', 'down']
    for row_idx, row in enumerate(map_parser.map_matrix):
        for col_idx, elem in enumerate(row):
            if (stairs_type == 'up' and elem == '13') or (stairs_type == 'down' and elem == '14'):
                if row_idx > 0 and map_parser.map_matrix[row_idx - 1][col_idx] == '00':
                    self.block_position = col_idx, row_idx - 1
                elif row_idx < map_parser.map_size[0] - 1 and map_parser.map_matrix[row_idx + 1][col_idx] == '00':
                    self.block_position = col_idx, row_idx + 1
                elif col_idx > 0 and map_parser.map_matrix[row_idx][col_idx - 1] == '00':
                    self.block_position = col_idx - 1, row_idx
                elif col_idx < map_parser.map_size[1] - 1 and map_parser.map_matrix[row_idx][col_idx + 1] == '00':
                    self.block_position = col_idx + 1, row_idx
    self.rect.left, self.rect.top = self.block_position[0] * self.blocksize + self.offset[0], self.block_position[1] * self.blocksize + self.offset[1]
 Copy code 

Retest and see :

 test

To sum up , The main thing is to realize our warrior role , And some simple event responses after he meets some elements in the map .

copyright notice
author[Dai mubai],Please bring the original link to reprint, thank you.
https://en.pythonmana.com/2022/01/202201300951032977.html

Random recommended