preface

The text and pictures in this article come from the network, only for learning, exchange, do not have any commercial purposes, copyright belongs to the original author, if you have any questions, please contact us to deal with.

Author: marble_xu

GitHub address: github.com/marblexu/Py…

PS: If you need Python learning materials, please click on the link below to obtain them

Note.youdao.com/noteshare?i…

Function is introduced

Recently, I have been adding new plants and zombies to this plant vs. Zombie game. Due to the limited picture resources on the Internet, there are few plants and zombies that can be added. The current progress is as follows.

The functions are as follows:

  • Supported plant types: sunflower, Pea Shooter, Ice Shooter, Nut, cherry bomb. New plants added: Double Pea Shooter, triple pea Shooter, cannibal flower, small mushroom, Potato mine, Japanese gourd.
  • Supported zombie types: Normal zombie, chess zombie, barricade zombie, iron bucket zombie. Added reading newspaper zombies.
  • Use JSON files to save level information and set the time and location of zombies.
  • Increased the selection of playing plants at the start of each level.
  • Add a lawn mower.

Here’s a screenshot of the game:

Plant card selection and planting

As you can see, there are 45 squares (5 rows, 9 columns per row) where you can grow plants.

This article will introduce:

  • Implementation of the top plant card bar.
  • Click the plant card to switch the mouse to the plant picture.
  • When the mouse moves, determine which square is currently in, and display translucent plants as a hint.

Code implementation

The names and attributes of all plant cards are kept in separate lists, with each list index corresponding to a plant.

For example, list index 0 is a sunflower:

  • Card_name_list [0] is the name of the sunflower card used to get the picture of the sunflower card.

  • Plant_name_list [0] is the name of the sunflower used to get the picture of the sunflower card.

  • Plant_sun_list [0] is the number of solar points it takes to grow a sunflower.

  • Plant_frozen_time_list [0] is the cooling time of sunflower.

Plant card

Each plant Card is a separate Card class that displays the plant.

  • The checkMouseClick function checks whether the mouse is clicked on the card.

  • CanClick: Determine if the card can be planted (has enough points and is still in the cooldown)

  • Update function: indicates whether the card is selectable by setting the transparency of the picture.

Card column class

The MenuBar class displays the plant card bar in Figure 3:

  • Self. sun_value: the number of sun points currently collected;

  • Self. card_list: list of plant cards;

  • SetupCards function: traversal initialization __init__ function passed in this level selected plant Card list, successively create Card class, set the display position of each Card;

  • CheckCardClick function: checks if the mouse clicked on a plant card on the card bar, and returns the result if a plantable card was selected.

Code:

import pygame as pg
from.import tool
from.import constants as c

PANEL_Y_START = 87
PANEL_X_START = 22
PANEL_Y_INTERNAL = 74
PANEL_X_INTERNAL = 53
CARD_LIST_NUM = 8

card_name_list = [c.CARD_SUNFLOWER, c.CARD_PEASHOOTER, c.CARD_SNOWPEASHOOTER, c.CARD_WALLNUT,
                  c.CARD_CHERRYBOMB, c.CARD_THREEPEASHOOTER, c.CARD_REPEATERPEA, c.CARD_CHOMPER,
                  c.CARD_PUFFSHROOM, c.CARD_POTATOMINE, c.CARD_SQUASH, c.CARD_SPIKEWEED,
                  c.CARD_JALAPENO, c.CARD_SCAREDYSHROOM, c.CARD_SUNSHROOM, c.CARD_ICESHROOM]
plant_name_list = [c.SUNFLOWER, c.PEASHOOTER, c.SNOWPEASHOOTER, c.WALLNUT,
                   c.CHERRYBOMB, c.THREEPEASHOOTER, c.REPEATERPEA, c.CHOMPER,
                   c.PUFFSHROOM, c.POTATOMINE, c.SQUASH, c.SPIKEWEED,
                   c.JALAPENO, c.SCAREDYSHROOM, c.SUNSHROOM, c.ICESHROOM]
plant_sun_list = [50.100.175.50.150.325.200.150.0.25.50.100.125.25.25.75]
plant_frozen_time_list = [7500.7500.7500.30000.50000.7500.7500.7500.7500.30000.30000.7500.50000.7500.7500.50000]
all_card_list = [0.1.2.3.4.5.6.7.8.9.10.11.12.13.14.15]

def getSunValueImage(sun_value):
    font = pg.font.SysFont(None.22)
    width = 32
    msg_image = font.render(str(sun_value), True, c.NAVYBLUE, c.LIGHTYELLOW)
    msg_rect = msg_image.get_rect()
    msg_w = msg_rect.width

    image = pg.Surface([width, 17])
    x = width - msg_w

    image.fill(c.LIGHTYELLOW)
    image.blit(msg_image, (x, 0), (0.0, msg_rect.w, msg_rect.h))
    image.set_colorkey(c.BLACK)
    return image

class Card(a):
    def __init__(self, x, y, name_index, scale=0.78):self.loadFrame(card_name_list[name_index], scale) self.rect = self.orig_image.get_rect() self.rect.x = x self.rect.y = y self.name_index = name_index self.sun_cost  = plant_sun_list[name_index] self.frozen_time = plant_frozen_time_list[name_index] self.frozen_timer = -self.frozen_time self.refresh_timer =0
        self.select = True

    def loadFrame(self, name, scale):
        frame = tool.GFX[name]
        rect = frame.get_rect()
        width, height = rect.w, rect.h

        self.orig_image = tool.get_image(frame, 0.0, width, height, c.BLACK, scale)
        self.image = self.orig_image

    def checkMouseClick(self, mouse_pos):
        x, y = mouse_pos
        if(x >= self.rect.x and x <= self.rect.right and
           y >= self.rect.y and y <= self.rect.bottom):
            return True
        return False

    def canClick(self, sun_value, current_time):
        if self.sun_cost <= sun_value and (current_time - self.frozen_timer) > self.frozen_time:
            return True
        return False

    def canSelect(self):
        return self.select

    def setSelect(self, can_select):
        self.select = can_select
        if can_select:
            self.image.set_alpha(255)
        else:
            self.image.set_alpha(128)

    def setFrozenTime(self, current_time):
        self.frozen_timer = current_time

    def createShowImage(self, sun_value, current_time):
        '''create a card image to show cool down status or disable status when have not enough sun value'''
        time = current_time - self.frozen_timer
        if time < self.frozen_time: #cool down status
            image = pg.Surface([self.rect.w, self.rect.h])
            frozen_image = self.orig_image.copy()
            frozen_image.set_alpha(128)
            frozen_height = (self.frozen_time - time)/self.frozen_time * self.rect.h
            
            image.blit(frozen_image, (0.0), (0.0, self.rect.w, frozen_height))
            image.blit(self.orig_image, (0,frozen_height),
                       (0, frozen_height, self.rect.w, self.rect.h - frozen_height))
        elif self.sun_cost > sun_value: #disable status
            image = self.orig_image.copy()
            image.set_alpha(192)
        else:
            image = self.orig_image
        return image

    def update(self, sun_value, current_time):
        if (current_time - self.refresh_timer) >= 250:
            self.image = self.createShowImage(sun_value, current_time)
            self.refresh_timer = current_time

    def draw(self, surface):
        surface.blit(self.image, self.rect)

class MenuBar(a):
    def __init__(self, card_list, sun_value):
        self.loadFrame(c.MENUBAR_BACKGROUND)
        self.rect = self.image.get_rect()
        self.rect.x = 10
        self.rect.y = 0
        
        self.sun_value = sun_value
        self.card_offset_x = 32
        self.setupCards(card_list)

    def loadFrame(self, name):
        frame = tool.GFX[name]
        rect = frame.get_rect()
        frame_rect = (rect.x, rect.y, rect.w, rect.h)

        self.image = tool.get_image(tool.GFX[name], *frame_rect, c.WHITE, 1)

    def update(self, current_time):
        self.current_time = current_time
        for card in self.card_list:
            card.update(self.sun_value, self.current_time)

    def createImage(self, x, y, num):
        if num == 1:
            return
        img = self.image
        rect = self.image.get_rect()
        width = rect.w
        height = rect.h
        self.image = pg.Surface((width * num, height)).convert()
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        for i in range(num):
            x = i * width
            self.image.blit(img, (x,0))
        self.image.set_colorkey(c.BLACK)
    
    def setupCards(self, card_list):
        self.card_list = []
        x = self.card_offset_x
        y = 8
        for index in card_list:
            x += 55
            self.card_list.append(Card(x, y, index))

    def checkCardClick(self, mouse_pos):
        result = None
        for card in self.card_list:
            if card.checkMouseClick(mouse_pos):
                if card.canClick(self.sun_value, self.current_time):
                    result = (plant_name_list[card.name_index], card.sun_cost)
                break
        return result
    
    def checkMenuBarClick(self, mouse_pos):
        x, y = mouse_pos
        if(x >= self.rect.x and x <= self.rect.right and
           y >= self.rect.y and y <= self.rect.bottom):
            return True
        return False

    def decreaseSunValue(self, value):
        self.sun_value -= value

    def increaseSunValue(self, value):
        self.sun_value += value

    def setCardFrozenTime(self, plant_name):
        for card in self.card_list:
            if plant_name_list[card.name_index] == plant_name:
                card.setFrozenTime(self.current_time)
                break

    def drawSunValue(self):
        self.value_image = getSunValueImage(self.sun_value)
        self.value_rect = self.value_image.get_rect()
        self.value_rect.x = 21
        self.value_rect.y = self.rect.bottom - 21
        
        self.image.blit(self.value_image, self.value_rect)

    def draw(self, surface):
        self.drawSunValue()
        surface.blit(self.image, self.rect)
        for card in self.card_list:
            card.draw(surface)

class Panel(a):
    def __init__(self, card_list, sun_value):
        self.loadImages(sun_value)
        self.selected_cards = []
        self.selected_num = 0
        self.setupCards(card_list)

    def loadFrame(self, name):
        frame = tool.GFX[name]
        rect = frame.get_rect()
        frame_rect = (rect.x, rect.y, rect.w, rect.h)

        return tool.get_image(tool.GFX[name], *frame_rect, c.WHITE, 1)

    def loadImages(self, sun_value):
        self.menu_image = self.loadFrame(c.MENUBAR_BACKGROUND)
        self.menu_rect = self.menu_image.get_rect()
        self.menu_rect.x = 0
        self.menu_rect.y = 0

        self.panel_image = self.loadFrame(c.PANEL_BACKGROUND)
        self.panel_rect = self.panel_image.get_rect()
        self.panel_rect.x = 0
        self.panel_rect.y = PANEL_Y_START

        
        self.value_image = getSunValueImage(sun_value)
        self.value_rect = self.value_image.get_rect()
        self.value_rect.x = 21
        self.value_rect.y = self.menu_rect.bottom - 21

        self.button_image =  self.loadFrame(c.START_BUTTON)
        self.button_rect = self.button_image.get_rect()
        self.button_rect.x = 155
        self.button_rect.y = 547

    def setupCards(self, card_list):
        self.card_list = []
        x = PANEL_X_START - PANEL_X_INTERNAL
        y = PANEL_Y_START + 43 - PANEL_Y_INTERNAL
        for i, index in enumerate(card_list):
            if i % 8= =0:
                x = PANEL_X_START - PANEL_X_INTERNAL
                y += PANEL_Y_INTERNAL
            x += PANEL_X_INTERNAL
            self.card_list.append(Card(x, y, index, 0.75))

    def checkCardClick(self, mouse_pos):
        delete_card = None
        for card in self.selected_cards:
            if delete_card: # when delete a card, move right cards to left
                card.rect.x -= 55
            elif card.checkMouseClick(mouse_pos):
                self.deleteCard(card.name_index)
                delete_card = card

        if delete_card:
            self.selected_cards.remove(delete_card)
            self.selected_num -= 1

        if self.selected_num == CARD_LIST_NUM:
            return

        for card in self.card_list:
            if card.checkMouseClick(mouse_pos):
                if card.canSelect():
                    self.addCard(card)
                break

    def addCard(self, card):
        card.setSelect(False)
        y = 8
        x = 78 + self.selected_num * 55
        self.selected_cards.append(Card(x, y, card.name_index))
        self.selected_num += 1

    def deleteCard(self, index):
        self.card_list[index].setSelect(True)

    def checkStartButtonClick(self, mouse_pos):
        if self.selected_num < CARD_LIST_NUM:
            return False

        x, y = mouse_pos
        if (x >= self.button_rect.x and x <= self.button_rect.right and
            y >= self.button_rect.y and y <= self.button_rect.bottom):
           return True
        return False

    def getSelectedCards(self):
        card_index_list = []
        for card in self.selected_cards:
            card_index_list.append(card.name_index)
        return card_index_list

    def draw(self, surface):
        self.menu_image.blit(self.value_image, self.value_rect)
        surface.blit(self.menu_image, self.menu_rect)
        surface.blit(self.panel_image, self.panel_rect)
        for card in self.card_list:
            card.draw(surface)
        for card in self.selected_cards:
            card.draw(surface)

        if self.selected_num == CARD_LIST_NUM:
            surface.blit(self.button_image, self.button_rect)
Copy the code

Mouse picture switching

The setupMouseImage function switches the mouse image to the selected plant:

  • Self. mouse_image: Retrieves the selected plant image according to plant_name;
  • Self. mouse_rect: Select the position of the plant image. In drawMouseShow, set the position of the plant image to the current mouse position.
  • Pg.mouse.set_visible (False) : Hides the default mouse display, so that the mouse image is switched to the selected plant.
   def setupMouseImage(self, plant_name, plant_cost):
        frame_list = tool.GFX[plant_name]
        if plant_name in tool.PLANT_RECT:
            data = tool.PLANT_RECT[plant_name]
            x, y, width, height = data['x'], data['y'], data['width'], data['height']
        else:
            x, y = 0.0
            rect = frame_list[0].get_rect()
            width, height = rect.w, rect.h


        if plant_name == c.POTATOMINE or plant_name == c.SQUASH:
            color = c.WHITE
        else:
            color = c.BLACK
        self.mouse_image = tool.get_image(frame_list[0], x, y, width, height, color, 1)
        self.mouse_rect = self.mouse_image.get_rect()
        pg.mouse.set_visible(False)
        self.drag_plant = True
        self.plant_name = plant_name
        self.plant_cost = plant_cost


    def drawMouseShow(self, surface):
        if self.hint_plant:
            surface.blit(self.hint_image, self.hint_rect)
        x, y = pg.mouse.get_pos()
        self.mouse_rect.centerx = x
        self.mouse_rect.centery = y
        surface.blit(self.mouse_image, self.mouse_rect)
Copy the code

Indicate which square to plant in

Take a look at the map class in source\ Component \map.py:

  • Self. map: A two-dimensional list that holds the state of each square. Each entry is initialized to 0, indicating that plants can be planted, and a value of 1 indicates that plants have been planted in the grid.
  • GetMapIndex function: The passed argument is the coordinate position in the game (such as the current mouse position), and returns which square of the map the position is in.
  • GetMapGridPos: passes in the index of a grid and returns the coordinate position of the plant in the grid.
  • ShowPlant function: according to the passed coordinate position, judge whether the grid in this position can plant, if it can plant, return to return the coordinate position of plant in this grid.
MAP_EMPTY = 0
MAP_EXIST = 1


class Map(a):
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.map = [[0 for x in range(self.width)] for y in range(self.height)]


    def isValid(self, map_x, map_y):
        if (map_x < 0 or map_x >= self.width or
            map_y < 0 or map_y >= self.height):
            return False
        return True

    def isMovable(self, map_x, map_y):
        return (self.map[map_y][map_x] == c.MAP_EMPTY)

    def getMapIndex(self, x, y):
        x -= c.MAP_OFFSET_X
        y -= c.MAP_OFFSET_Y
        return (x // c.GRID_X_SIZE, y // c.GRID_Y_SIZE)

    def getMapGridPos(self, map_x, map_y):
        return (map_x * c.GRID_X_SIZE + c.GRID_X_SIZE//2 + c.MAP_OFFSET_X,
                map_y * c.GRID_Y_SIZE + c.GRID_Y_SIZE//5 * 3 + c.MAP_OFFSET_Y)

    def setMapGridType(self, map_x, map_y, type):
        self.map[map_y][map_x] = type


    def getRandomMapIndex(self):
        map_x = random.randint(0, self.width- 1)
        map_y = random.randint(0, self.height- 1)
        return (map_x, map_y)


    def showPlant(self, x, y):
        pos = None
        map_x, map_y = self.getMapIndex(x, y)
        if self.isValid(map_x, map_y) and self.isMovable(map_x, map_y):
            pos = self.getMapGridPos(map_x, map_y)
        return pos
Copy the code

In source\state\level.py:

  • CanSeedPlant function: judge whether the current mouse position can plant;

  • SetupHintImage function: if the current mouse position can plant and a plant card is selected, set self.hint_image to show which grid the plant will be planted in. Self. hint_rect is the coordinate position of the plant.

    def canSeedPlant(self):
        x, y = pg.mouse.get_pos()
        return self.map.showPlant(x, y)

    def setupHintImage(self):
        pos = self.canSeedPlant()
        if pos and self.mouse_image:
            if (self.hint_image and pos[0] == self.hint_rect.x and
                pos[1] == self.hint_rect.y):
                return
            width, height = self.mouse_rect.w, self.mouse_rect.h
            image = pg.Surface([width, height])
            image.blit(self.mouse_image, (0.0), (0.0, width, height))
            image.set_colorkey(c.BLACK)
            image.set_alpha(128)
            self.hint_image = image
            self.hint_rect = image.get_rect()
            self.hint_rect.centerx = pos[0]
            self.hint_rect.bottom = pos[1]
            self.hint_plant = True
        else:
            self.hint_plant = False
Copy the code