Python Pygame实战之塔防游戏的实现

作者:LexSaints 时间:2021-12-15 08:57:22 

一、环境要求

windows系统,python3.6+

安装模块

pip install pyqt5
pip install pygame

二、游戏介绍

1、游戏目标

按照关卡,设计不同的塔防地图(博主只设计了三关,有兴趣的同学,学会之后,可以自己画地图),设置三种炮台,每种炮台发射不同的炮弹,每种炮弹对敌人杀伤力不一样,每种

炮台的价格也不一样。玩家通过钱币购买炮台,并设有剪出炮台的操作(有点类似植物大战僵尸里的铲子)。敌人成功抵达塔楼,游戏结束。

2、先上游戏效果图

Python Pygame实战之塔防游戏的实现

三、完整开发流程

1、项目主结构

首先,先整理一下项目的主结构,其实看一下主结构,基本就清晰了

Python Pygame实战之塔防游戏的实现

2、详细配置

config.py

配置文件中,需要引入os模块,并且配置打开游戏的屏幕大小,并将资源中引用到的图片、音频插入到合适的位置。

因为我们的迷宫游戏,需要划开模块。

'''配置文件'''
import os

'''屏幕大小'''
SCREENSIZE = (800, 600)
'''图片路径'''
IMAGEPATHS = {
   'choice': {
       'load_game': os.path.join(os.getcwd(), 'resources/images/choice/load_game.png'),
       'map1': os.path.join(os.getcwd(), 'resources/images/choice/map1.png'),
       'map1_black': os.path.join(os.getcwd(), 'resources/images/choice/map1_black.png'),
       'map1_red': os.path.join(os.getcwd(), 'resources/images/choice/map1_red.png'),
       'map2': os.path.join(os.getcwd(), 'resources/images/choice/map2.png'),
       'map2_black': os.path.join(os.getcwd(), 'resources/images/choice/map2_black.png'),
       'map2_red': os.path.join(os.getcwd(), 'resources/images/choice/map2_red.png'),
       'map3': os.path.join(os.getcwd(), 'resources/images/choice/map3.png'),
       'map3_black': os.path.join(os.getcwd(), 'resources/images/choice/map3_black.png'),
       'map3_red': os.path.join(os.getcwd(), 'resources/images/choice/map3_red.png'),
   },
   'end': {
       'gameover': os.path.join(os.getcwd(), 'resources/images/end/gameover.png'),
       'continue_red': os.path.join(os.getcwd(), 'resources/images/end/continue_red.png'),
       'continue_black': os.path.join(os.getcwd(), 'resources/images/end/continue_black.png'),
   },
   'game': {
       'arrow1': os.path.join(os.getcwd(), 'resources/images/game/arrow1.png'),
       'arrow2': os.path.join(os.getcwd(), 'resources/images/game/arrow2.png'),
       'arrow3': os.path.join(os.getcwd(), 'resources/images/game/arrow3.png'),
       'basic_tower': os.path.join(os.getcwd(), 'resources/images/game/basic_tower.png'),
       'boulder': os.path.join(os.getcwd(), 'resources/images/game/boulder.png'),
       'bush': os.path.join(os.getcwd(), 'resources/images/game/bush.png'),
       'cave': os.path.join(os.getcwd(), 'resources/images/game/cave.png'),
       'dirt': os.path.join(os.getcwd(), 'resources/images/game/dirt.png'),
       'enemy_blue': os.path.join(os.getcwd(), 'resources/images/game/enemy_blue.png'),
       'enemy_pink': os.path.join(os.getcwd(), 'resources/images/game/enemy_pink.png'),
       'enemy_red': os.path.join(os.getcwd(), 'resources/images/game/enemy_red.png'),
       'enemy_yellow': os.path.join(os.getcwd(), 'resources/images/game/enemy_yellow.png'),
       'godark': os.path.join(os.getcwd(), 'resources/images/game/godark.png'),
       'golight': os.path.join(os.getcwd(), 'resources/images/game/golight.png'),
       'grass': os.path.join(os.getcwd(), 'resources/images/game/grass.png'),
       'healthfont': os.path.join(os.getcwd(), 'resources/images/game/healthfont.png'),
       'heavy_tower': os.path.join(os.getcwd(), 'resources/images/game/heavy_tower.png'),
       'med_tower': os.path.join(os.getcwd(), 'resources/images/game/med_tower.png'),
       'nexus': os.path.join(os.getcwd(), 'resources/images/game/nexus.png'),
       'othergrass': os.path.join(os.getcwd(), 'resources/images/game/othergrass.png'),
       'path': os.path.join(os.getcwd(), 'resources/images/game/path.png'),
       'rock': os.path.join(os.getcwd(), 'resources/images/game/rock.png'),
       'tiles': os.path.join(os.getcwd(), 'resources/images/game/tiles.png'),
       'unitfont': os.path.join(os.getcwd(), 'resources/images/game/unitfont.png'),
       'water': os.path.join(os.getcwd(), 'resources/images/game/water.png'),
       'x': os.path.join(os.getcwd(), 'resources/images/game/x.png'),
   },
   'pause': {
       'gamepaused': os.path.join(os.getcwd(), 'resources/images/pause/gamepaused.png'),
       'resume_black': os.path.join(os.getcwd(), 'resources/images/pause/resume_black.png'),
       'resume_red': os.path.join(os.getcwd(), 'resources/images/pause/resume_red.png'),
   },
   'start': {
       'play_black': os.path.join(os.getcwd(), 'resources/images/start/play_black.png'),
       'play_red': os.path.join(os.getcwd(), 'resources/images/start/play_red.png'),
       'quit_black': os.path.join(os.getcwd(), 'resources/images/start/quit_black.png'),
       'quit_red': os.path.join(os.getcwd(), 'resources/images/start/quit_red.png'),
       'start_interface': os.path.join(os.getcwd(), 'resources/images/start/start_interface.png'),
   },
}
'''地图路径'''
MAPPATHS = {
   '1': os.path.join(os.getcwd(), 'resources/maps/1.map'),
   '2': os.path.join(os.getcwd(), 'resources/maps/2.map'),
   '3': os.path.join(os.getcwd(), 'resources/maps/3.map'),
}
'''字体路径'''
FONTPATHS = {
   'Calibri': os.path.join(os.getcwd(), 'resources/fonts/Calibri.ttf'),
   'm04': os.path.join(os.getcwd(), 'resources/fonts/m04.ttf'),
   'Microsoft Sans Serif': os.path.join(os.getcwd(), 'resources/fonts/Microsoft Sans Serif.ttf'),
}
'''不同难度的settings'''
DIFFICULTYPATHS = {
   'easy': os.path.join(os.getcwd(), 'resources/difficulties/easy.json'),
   'hard': os.path.join(os.getcwd(), 'resources/difficulties/hard.json'),
   'medium': os.path.join(os.getcwd(), 'resources/difficulties/medium.json'),
}
'''音频路径'''
AUDIOPATHS = {
   'bgm': os.path.join(os.getcwd(), 'resources/audios/bgm.mp3'),
}

3、定义敌人、塔楼、 * 的类

Python Pygame实战之塔防游戏的实现

turrent.py 以炮塔类为例

炮塔首先需要初始化,即init函数,另外需要有射击属性、以及重置属性(即我们可以将已经建好的炮塔删除,然后重新添加)

import pygame
from .arrow import Arrow

'''炮塔类'''
class Turret(pygame.sprite.Sprite):
   def __init__(self, turret_type, cfg):
       assert turret_type in range(3)
       pygame.sprite.Sprite.__init__(self)
       self.cfg = cfg
       self.turret_type = turret_type
       self.imagepaths = [cfg.IMAGEPATHS['game']['basic_tower'], cfg.IMAGEPATHS['game']['med_tower'], cfg.IMAGEPATHS['game']['heavy_tower']]
       self.image = pygame.image.load(self.imagepaths[turret_type])
       self.rect = self.image.get_rect()
       # 箭
       self.arrow = Arrow(turret_type, cfg)
       # 当前的位置
       self.coord = 0, 0
       self.position = 0, 0
       self.rect.left, self.rect.top = self.position
       self.reset()
   '''射击'''
   def shot(self, position, angle=None):
       arrow = None
       if not self.is_cooling:
           arrow = Arrow(self.turret_type, self.cfg)
           arrow.reset(position, angle)
           self.is_cooling = True
       if self.is_cooling:
           self.cool_time -= 1
           if self.cool_time == 0:
               self.reset()
       return arrow
   '''重置'''
   def reset(self):
       if self.turret_type == 0:
           # 价格
           self.price = 500
           # 射箭的冷却时间
           self.cool_time = 30
           # 是否在冷却期
           self.is_cooling = False
       elif self.turret_type == 1:
           self.price = 1000
           self.cool_time = 50
           self.is_cooling = False
       elif self.turret_type == 2:
           self.price = 1500
           self.cool_time = 100
           self.is_cooling = False

4、游戏开始:选择难度地图

在这里我们把游戏主界面初始化,并把三个难度的地图列出来,让玩家选择。

import sys
import pygame

'''游戏选择主界面'''
class MainInterface(pygame.sprite.Sprite):
   def __init__(self, cfg):
       pygame.sprite.Sprite.__init__(self)
       self.image = pygame.image.load(cfg.IMAGEPATHS['choice']['load_game']).convert()
       self.rect = self.image.get_rect()
       self.rect.topleft = (0, 0)
   '''更新函数'''
   def update(self):
       pass

'''地图1'''
class MapButton1(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(175, 240)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.image.load(cfg.IMAGEPATHS['choice']['map1_black']).convert()
       self.image_2 = pygame.image.load(cfg.IMAGEPATHS['choice']['map1_red']).convert()
       self.image_3 = pygame.image.load(cfg.IMAGEPATHS['choice']['map1']).convert()
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''地图2'''
class MapButton2(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(400, 240)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.image.load(cfg.IMAGEPATHS['choice']['map2_black']).convert()
       self.image_2 = pygame.image.load(cfg.IMAGEPATHS['choice']['map2_red']).convert()
       self.image_3 = pygame.image.load(cfg.IMAGEPATHS['choice']['map2']).convert()
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''地图3'''
class MapButton3(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(625, 240)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.image.load(cfg.IMAGEPATHS['choice']['map3_black']).convert()
       self.image_2 = pygame.image.load(cfg.IMAGEPATHS['choice']['map3_red']).convert()
       self.image_3 = pygame.image.load(cfg.IMAGEPATHS['choice']['map3']).convert()
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''信息显示框'''
class InfoBox(pygame.sprite.Sprite):
   def __init__(self, position=(400, 475)):
       pygame.sprite.Sprite.__init__(self)
       self.ori_image = pygame.Surface((625, 200))
       self.ori_image.fill((255, 255, 255))
       self.ori_image_front = pygame.Surface((621, 196))
       self.ori_image_front.fill((0, 0, 0))
       self.ori_image.blit(self.ori_image_front, (2, 2))
       self.rect = self.ori_image.get_rect()
       self.rect.center = position
   '''更新函数'''
   def update(self, btns):
       self.image = self.ori_image
       mouse_pos = pygame.mouse.get_pos()
       for btn in btns:
           if btn.rect.collidepoint(mouse_pos):
               self.image.blit(btn.image_3, (225, 25))
               break

'''简单难度按钮'''
class EasyButton(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(400, 150)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.Surface((285, 100))
       self.image_1_front = pygame.Surface((281, 96))
       self.image_1.fill((255, 255, 255))
       self.image_1_front.fill((0, 0, 0))
       self.image_1.blit(self.image_1_front, (2, 2))
       self.image_2 = pygame.Surface((285, 100))
       self.image_2_front = pygame.Surface((281, 96))
       self.image_2.fill((255, 255, 255))
       self.image_2_front.fill((24, 196, 40))
       self.image_2.blit(self.image_2_front, (2, 2))
       self.text = 'easy'
       self.font = pygame.font.Font(cfg.FONTPATHS['m04'], 42)
       self.text_render = self.font.render(self.text, 1, (255, 255, 255))
       self.image_1.blit(self.text_render, (60, 29))
       self.image_2.blit(self.text_render, (60, 29))
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''中等难度按钮'''
class MediumButton(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(400, 300)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.Surface((285, 100))
       self.image_1_front = pygame.Surface((281, 96))
       self.image_1.fill((255, 255, 255))
       self.image_1_front.fill((0, 0, 0))
       self.image_1.blit(self.image_1_front, (2, 2))
       self.image_2 = pygame.Surface((285, 100))
       self.image_2_front = pygame.Surface((281, 96))
       self.image_2.fill((255, 255, 255))
       self.image_2_front.fill((24, 30, 196))
       self.image_2.blit(self.image_2_front, (2, 2))
       self.text = 'medium'
       self.font = pygame.font.Font(cfg.FONTPATHS['m04'], 42)
       self.text_render = self.font.render(self.text, 1, (255, 255, 255))
       self.image_1.blit(self.text_render, (15, 29))
       self.image_2.blit(self.text_render, (15, 29))
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''困难难度按钮'''
class HardButton(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(400, 450)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.Surface((285, 100))
       self.image_1_front = pygame.Surface((281, 96))
       self.image_1.fill((255, 255, 255))
       self.image_1_front.fill((0, 0, 0))
       self.image_1.blit(self.image_1_front, (2, 2))
       self.image_2 = pygame.Surface((285, 100))
       self.image_2_front = pygame.Surface((281, 96))
       self.image_2.fill((255, 255, 255))
       self.image_2_front.fill((196, 24, 24))
       self.image_2.blit(self.image_2_front, (2, 2))
       self.text = 'hard'
       self.font = pygame.font.Font(cfg.FONTPATHS['m04'], 42)
       self.text_render = self.font.render(self.text, 1, (255, 255, 255))
       self.image_1.blit(self.text_render, (60, 29))
       self.image_2.blit(self.text_render, (60, 29))
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''游戏地图和困难选择界面'''
class ChoiceInterface():
   def __init__(self, cfg):
       # part1
       self.main_interface = MainInterface(cfg)
       self.map_btn1 = MapButton1(cfg)
       self.map_btn2 = MapButton2(cfg)
       self.map_btn3 = MapButton3(cfg)
       self.info_box = InfoBox()
       # part2
       self.easy_btn = EasyButton(cfg)
       self.medium_btn = MediumButton(cfg)
       self.hard_btn = HardButton(cfg)
   '''外部调用'''
   def update(self, screen):
       clock = pygame.time.Clock()
       # part1
       self.map_btns = pygame.sprite.Group(self.map_btn1, self.map_btn2, self.map_btn3)
       map_choice, difficulty_choice = None, None
       while True:
           clock.tick(60)
           self.main_interface.update()
           self.map_btns.update()
           self.info_box.update(self.map_btns)
           screen.blit(self.main_interface.image, self.main_interface.rect)
           self.map_btns.draw(screen)
           screen.blit(self.info_box.image, self.info_box.rect)
           pygame.display.flip()
           for event in pygame.event.get():
               if event.type == pygame.QUIT:
                   pygame.quit()
                   sys.exit(0)
               if event.type == pygame.MOUSEBUTTONDOWN:
                   if event.button == 1:
                       mouse_pos = pygame.mouse.get_pos()
                       idx = 0
                       for btn in self.map_btns:
                           idx += 1
                           if btn.rect.collidepoint(mouse_pos):
                               map_choice = idx
           if map_choice:
               break
       # part2
       self.difficulty_btns = pygame.sprite.Group(self.easy_btn, self.medium_btn, self.hard_btn)
       while True:
           clock.tick(60)
           screen.fill((0, 0, 0))
           self.difficulty_btns.update()
           self.difficulty_btns.draw(screen)
           pygame.display.flip()
           for event in pygame.event.get():
               if event.type == pygame.QUIT:
                   pygame.quit()
                   sys.exit(0)
               if event.type == pygame.MOUSEBUTTONDOWN:
                   if event.button == 1:
                       mouse_pos = pygame.mouse.get_pos()
                       idx = 0
                       for btn in self.difficulty_btns:
                           idx += 1
                           if btn.rect.collidepoint(mouse_pos):
                               difficulty_choice = btn.text
           if difficulty_choice:
               break
       return map_choice, difficulty_choice

5、游戏开始界面

包括开始按钮,退出游戏等操作

start.py

import sys
import pygame

'''游戏开始主界面'''
class MainInterface(pygame.sprite.Sprite):
   def __init__(self, cfg):
       pygame.sprite.Sprite.__init__(self)
       self.image = pygame.image.load(cfg.IMAGEPATHS['start']['start_interface']).convert()
       self.rect = self.image.get_rect()
       self.rect.center = cfg.SCREENSIZE[0] / 2, cfg.SCREENSIZE[1] / 2
   '''更新函数'''
   def update(self):
       pass

'''开始游戏按钮'''
class PlayButton(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(220, 415)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.image.load(cfg.IMAGEPATHS['start']['play_black']).convert()
       self.image_2 = pygame.image.load(cfg.IMAGEPATHS['start']['play_red']).convert()
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''结束游戏按钮'''
class QuitButton(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(580, 415)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.image.load(cfg.IMAGEPATHS['start']['quit_black']).convert()
       self.image_2 = pygame.image.load(cfg.IMAGEPATHS['start']['quit_red']).convert()
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''游戏开始界面'''
class StartInterface():
   def __init__(self, cfg):
       self.main_interface = MainInterface(cfg)
       self.play_btn = PlayButton(cfg)
       self.quit_btn = QuitButton(cfg)
       self.components = pygame.sprite.LayeredUpdates(self.main_interface, self.play_btn, self.quit_btn)
   '''外部调用'''
   def update(self, screen):
       clock = pygame.time.Clock()
       while True:
           clock.tick(60)
           self.components.update()
           self.components.draw(screen)
           pygame.display.flip()
           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):
                           return False

6、游戏运行

gaming.py

import sys
import json
import math
import random
import pygame
from ..sprites import Enemy
from ..sprites import Turret
from .pause import PauseInterface
from collections import namedtuple

'''游戏进行中界面'''
class GamingInterface():
   def __init__(self, cfg):
       self.cfg = cfg
       # 游戏地图大小
       map_w = self.cfg.SCREENSIZE[0]
       map_h = 500
       # 按钮大小和位置
       button_w = 60
       button_h = 60
       button_y = 520
       # 间隙
       gap = 20
       # 按钮放在工具栏, 工具栏两端各有一个信息显示框
       toolbar_w = gap * 7 + button_w * 6
       info_w = (self.cfg.SCREENSIZE[0] - toolbar_w) // 2
       info_h = self.cfg.SCREENSIZE[1] - map_h
       toolbar_h = self.cfg.SCREENSIZE[1] - map_h
       # 界面布置
       self.map_rect = pygame.Rect(0, 0, map_w, map_h)
       self.map_surface = pygame.Surface((map_w, map_h))
       self.leftinfo_rect = pygame.Rect(0, map_h, info_w, info_h)
       self.rightinfo_rect = pygame.Rect(self.cfg.SCREENSIZE[0] - info_w, map_h, info_w, info_h)
       self.toolbar_rect = pygame.Rect(info_w, map_h, toolbar_w, toolbar_h)
       # 草
       self.grass = pygame.image.load(cfg.IMAGEPATHS['game']['grass'])
       # 岩石(铺路用的)
       self.rock = pygame.image.load(cfg.IMAGEPATHS['game']['rock'])
       # 污垢
       self.dirt = pygame.image.load(cfg.IMAGEPATHS['game']['dirt'])
       # 水
       self.water = pygame.image.load(cfg.IMAGEPATHS['game']['water'])
       # 灌木
       self.bush = pygame.image.load(cfg.IMAGEPATHS['game']['bush'])
       # 纽带
       self.nexus = pygame.image.load(cfg.IMAGEPATHS['game']['nexus'])
       # 洞穴
       self.cave = pygame.image.load(cfg.IMAGEPATHS['game']['cave'])
       # 获取地图元素的大小,请保证素材库里组成地图的元素图大小一致
       self.element_size = int(self.grass.get_rect().width)
       # 一些字体
       self.info_font = pygame.font.Font(cfg.FONTPATHS['Calibri'], 14)
       self.button_font = pygame.font.Font(cfg.FONTPATHS['Calibri'], 20)
       # 可以放炮塔的地方
       self.placeable = {0: self.grass}
       # 地图元素字典(数字对应.map文件中的数字)
       self.map_elements = {
           0: self.grass,
           1: self.rock,
           2: self.dirt,
           3: self.water,
           4: self.bush,
           5: self.nexus,
           6: self.cave
       }
       # 用于记录地图中的道路
       self.path_list = []
       # 当前的地图,将地图导入到这里面
       self.current_map = dict()
       # 当前鼠标携带的图标(即选中道具) -> [道具名, 道具]
       self.mouse_carried = []
       # 在地图上建造好了的炮塔
       self.built_turret_group = pygame.sprite.Group()
       # 所有的敌人
       self.enemies_group = pygame.sprite.Group()
       # 所有射出的箭
       self.arrows_group = pygame.sprite.Group()
       # 玩家操作用的按钮
       Button = namedtuple('Button', ['rect', 'text', 'onClick'])
       self.buttons = [
           Button(pygame.Rect((info_w + gap), button_y, button_w, button_h), 'T1', self.takeT1),
           Button(pygame.Rect((info_w + gap * 2 + button_w), button_y, button_w, button_h), 'T2', self.takeT2),
           Button(pygame.Rect((info_w + gap * 3 + button_w * 2), button_y, button_w, button_h), 'T3', self.takeT3),
           Button(pygame.Rect((info_w + gap * 4 + button_w * 3), button_y, button_w, button_h), 'XXX', self.takeXXX),
           Button(pygame.Rect((info_w + gap * 5 + button_w * 4), button_y, button_w, button_h), 'Pause', self.pauseGame),
           Button(pygame.Rect((info_w + gap * 6 + button_w * 5), button_y, button_w, button_h), 'Quit', self.quitGame)
       ]
   '''开始游戏'''
   def start(self, screen, map_path=None, difficulty_path=None):
       # 读取游戏难度对应的参数
       with open(difficulty_path, 'r') as f:
           difficulty_dict = json.load(f)
       self.money = difficulty_dict.get('money')
       self.health = difficulty_dict.get('health')
       self.max_health = difficulty_dict.get('health')
       difficulty_dict = difficulty_dict.get('enemy')
       # 每60s生成一波敌人
       generate_enemies_event = pygame.constants.USEREVENT + 0
       pygame.time.set_timer(generate_enemies_event, 60000)
       # 生成敌人的flag和当前已生成敌人的总次数
       generate_enemies_flag = False
       num_generate_enemies = 0
       # 每0.5秒出一个敌人
       generate_enemy_event = pygame.constants.USEREVENT + 1
       pygame.time.set_timer(generate_enemy_event, 500)
       generate_enemy_flag = False
       # 防止变量未定义
       enemy_range = None
       num_enemy = None
       # 是否手动操作箭塔射击
       manual_shot = False
       has_control = False
       # 游戏主循环
       while True:
           if self.health <= 0:
               return
           for event in pygame.event.get():
               if event.type == pygame.QUIT:
                   self.quitGame()
               if event.type == pygame.MOUSEBUTTONUP:
                   # --左键选物品
                   if event.button == 1:
                       # ----鼠标点击在地图上
                       if self.map_rect.collidepoint(event.pos):
                           if self.mouse_carried:
                               if self.mouse_carried[0] == 'turret':
                                   self.buildTurret(event.pos)
                               elif self.mouse_carried[0] == 'XXX':
                                   self.sellTurret(event.pos)
                       # ----鼠标点击在工具栏
                       elif self.toolbar_rect.collidepoint(event.pos):
                           for button in self.buttons:
                               if button.rect.collidepoint(event.pos):
                                   if button.text == 'T1':
                                       button.onClick()
                                   elif button.text == 'T2':
                                       button.onClick()
                                   elif button.text == 'T3':
                                       button.onClick()
                                   elif button.text == 'XXX':
                                       button.onClick()
                                   elif button.text == 'Pause':
                                       button.onClick(screen)
                                   elif button.text == 'Quit':
                                       button.onClick()
                                   break
                   # --右键释放物品
                   if event.button == 3:
                       self.mouse_carried = []
                   # --按中间键手动控制炮塔射箭方向一次,否则自由射箭
                   if event.button == 2:
                       manual_shot = True
               if event.type == generate_enemies_event:
                   generate_enemies_flag = True
               if event.type == generate_enemy_event:
                   generate_enemy_flag = True
           # --生成敌人, 生成的敌人随当前已生成敌人的总次数的增加而变强变多
           if generate_enemies_flag:
               generate_enemies_flag = False
               num_generate_enemies += 1
               idx = 0
               for key, value in difficulty_dict.items():
                   idx += 1
                   if idx == len(difficulty_dict.keys()):
                       enemy_range = value['enemy_range']
                       num_enemy = value['num_enemy']
                       break
                   if num_generate_enemies <= int(key):
                       enemy_range = value['enemy_range']
                       num_enemy = value['num_enemy']
                       break
           if generate_enemy_flag and num_enemy:
               generate_enemy_flag = False
               num_enemy -= 1
               enemy = Enemy(random.choice(range(enemy_range)), self.cfg)
               self.enemies_group.add(enemy)
           # --射箭
           for turret in self.built_turret_group:
               if not manual_shot:
                   position = turret.position[0] + self.element_size // 2, turret.position[1]
                   arrow = turret.shot(position)
               else:
                   position = turret.position[0] + self.element_size // 2, turret.position[1]
                   mouse_pos = pygame.mouse.get_pos()
                   angle = math.atan((mouse_pos[1] - position[1]) / (mouse_pos[0] - position[0] + 1e-6))
                   arrow = turret.shot(position, angle)
                   has_control = True
               if arrow:
                   self.arrows_group.add(arrow)
               else:
                   has_control = False
           if has_control:
               has_control = False
               manual_shot = False
           # --移动箭和碰撞检测
           for arrow in self.arrows_group:
               arrow.move()
               points = [(arrow.rect.left, arrow.rect.top), (arrow.rect.left, arrow.rect.bottom), (arrow.rect.right, arrow.rect.top), (arrow.rect.right, arrow.rect.bottom)]
               if (not self.map_rect.collidepoint(points[0])) and (not self.map_rect.collidepoint(points[1])) and \
                  (not self.map_rect.collidepoint(points[2])) and (not self.map_rect.collidepoint(points[3])):
                   self.arrows_group.remove(arrow)
                   del arrow
                   continue
               for enemy in self.enemies_group:
                   if pygame.sprite.collide_rect(arrow, enemy):
                       enemy.life_value -= arrow.attack_power
                       self.arrows_group.remove(arrow)
                       del arrow
                       break
           self.draw(screen, map_path)
   '''将场景画到游戏界面上'''
   def draw(self, screen, map_path):
       self.drawToolbar(screen)
       self.loadMap(screen, map_path)
       self.drawMouseCarried(screen)
       self.drawBuiltTurret(screen)
       self.drawEnemies(screen)
       self.drawArrows(screen)
       pygame.display.flip()
   '''画出所有射出的箭'''
   def drawArrows(self, screen):
       for arrow in self.arrows_group:
           screen.blit(arrow.image, arrow.rect)
   '''画敌人'''
   def drawEnemies(self, screen):
       for enemy in self.enemies_group:
           if enemy.life_value <= 0:
               self.money += enemy.reward
               self.enemies_group.remove(enemy)
               del enemy
               continue
           res = enemy.move(self.element_size)
           if res:
               coord = self.find_next_path(enemy)
               if coord:
                   enemy.reached_path.append(enemy.coord)
                   enemy.coord = coord
                   enemy.position = self.coord2pos(coord)
                   enemy.rect.left, enemy.rect.top = enemy.position
               else:
                   self.health -= enemy.damage
                   self.enemies_group.remove(enemy)
                   del enemy
                   continue
           # 画血条
           green_len = max(0, enemy.life_value / enemy.max_life_value) * self.element_size
           if green_len > 0:
               pygame.draw.line(screen, (0, 255, 0), (enemy.position), (enemy.position[0] + green_len, enemy.position[1]), 1)
           if green_len < self.element_size:
               pygame.draw.line(screen, (255, 0, 0), (enemy.position[0] + green_len, enemy.position[1]), (enemy.position[0] + self.element_size, enemy.position[1]), 1)
           screen.blit(enemy.image, enemy.rect)
   '''画已经建造好的炮塔'''
   def drawBuiltTurret(self, screen):
       for turret in self.built_turret_group:
           screen.blit(turret.image, turret.rect)
   '''画鼠标携带物'''
   def drawMouseCarried(self, screen):
       if self.mouse_carried:
           position = pygame.mouse.get_pos()
           coord = self.pos2coord(position)
           position = self.coord2pos(coord)
           # 在地图里再画
           if self.map_rect.collidepoint(position):
               if self.mouse_carried[0] == 'turret':
                   screen.blit(self.mouse_carried[1].image, position)
                   self.mouse_carried[1].coord = coord
                   self.mouse_carried[1].position = position
                   self.mouse_carried[1].rect.left, self.mouse_carried[1].rect.top = position
               else:
                   screen.blit(self.mouse_carried[1], position)
   '''画工具栏'''
   def drawToolbar(self, screen):
       # 信息显示框
       info_color = (120, 20, 50)
       # --左
       pygame.draw.rect(screen, info_color, self.leftinfo_rect)
       left_title = self.info_font.render('Player info:', True, (255, 255, 255))
       money_info = self.info_font.render('Money: ' + str(self.money), True, (255, 255, 255))
       health_info = self.info_font.render('Health: ' + str(self.health), True, (255, 255, 255))
       screen.blit(left_title, (self.leftinfo_rect.left + 5, self.leftinfo_rect.top + 5))
       screen.blit(money_info, (self.leftinfo_rect.left + 5, self.leftinfo_rect.top + 35))
       screen.blit(health_info, (self.leftinfo_rect.left + 5, self.leftinfo_rect.top + 55))
       # --右
       pygame.draw.rect(screen, info_color, self.rightinfo_rect)
       right_title = self.info_font.render('Selected info:', True, (255, 255, 255))
       screen.blit(right_title, (self.rightinfo_rect.left + 5, self.rightinfo_rect.top + 5))
       # 中间部分
       pygame.draw.rect(screen, (127, 127, 127), self.toolbar_rect)
       for button in self.buttons:
           mouse_pos = pygame.mouse.get_pos()
           if button.rect.collidepoint(mouse_pos):
               self.showSelectedInfo(screen, button)
               button_color = (0, 200, 0)
           else:
               button_color = (0, 100, 0)
           pygame.draw.rect(screen, button_color, button.rect)
           button_text = self.button_font.render(button.text, True, (255, 255, 255))
           button_text_rect = button_text.get_rect()
           button_text_rect.center = (button.rect.centerx, button.rect.centery)
           screen.blit(button_text, button_text_rect)
   '''显示被鼠标选中按钮的作用信息'''
   def showSelectedInfo(self, screen, button):
       if button.text in ['T1', 'T2', 'T3']:
           turret = Turret({'T1': 0, 'T2': 1, 'T3': 2}[button.text], self.cfg)
           selected_info1 = self.info_font.render('Cost: ' + str(turret.price), True, (255, 255, 255))
           selected_info2 = self.info_font.render('Damage: ' + str(turret.arrow.attack_power), True, (255, 255, 255))
           selected_info3 = self.info_font.render('Affordable: ' + str(self.money >= turret.price), True, (255, 255, 255))
           screen.blit(selected_info1, (self.rightinfo_rect.left + 5, self.rightinfo_rect.top + 35))
           screen.blit(selected_info2, (self.rightinfo_rect.left + 5, self.rightinfo_rect.top + 55))
           screen.blit(selected_info3, (self.rightinfo_rect.left + 5, self.rightinfo_rect.top + 75))
       elif button.text == 'XXX':
           selected_info = self.info_font.render('Sell a turret', True, (255, 255, 255))
           screen.blit(selected_info, (self.rightinfo_rect.left + 5, self.rightinfo_rect.top + 35))
       elif button.text == 'Pause':
           selected_info = self.info_font.render('Pause game', True, (255, 255, 255))
           screen.blit(selected_info, (self.rightinfo_rect.left + 5, self.rightinfo_rect.top + 35))
       elif button.text == 'Quit':
           selected_info = self.info_font.render('Quit game', True, (255, 255, 255))
           screen.blit(selected_info, (self.rightinfo_rect.left + 5, self.rightinfo_rect.top + 35))
   '''出售炮塔(半价)'''
   def sellTurret(self, position):
       coord = self.pos2coord(position)
       for turret in self.built_turret_group:
           if coord == turret.coord:
               self.built_turret_group.remove(turret)
               self.money += int(turret.price * 0.5)
               del turret
               break
   '''建造炮塔'''
   def buildTurret(self, position):
       turret = self.mouse_carried[1]
       coord = self.pos2coord(position)
       position = self.coord2pos(coord)
       turret.position = position
       turret.coord = coord
       turret.rect.left, turret.rect.top = position
       if self.money - turret.price >= 0:
           if self.current_map.get(turret.coord) in self.placeable.keys():
               self.money -= turret.price
               self.built_turret_group.add(turret)
               if self.mouse_carried[1].turret_type == 0:
                   self.mouse_carried = []
                   self.takeT1()
               elif self.mouse_carried[1].turret_type == 1:
                   self.mouse_carried = []
                   self.takeT2()
               elif self.mouse_carried[1].turret_type == 2:
                   self.mouse_carried = []
                   self.takeT3()
   '''拿炮塔1'''
   def takeT1(self):
       T1 = Turret(0, self.cfg)
       if self.money >= T1.price:
           self.mouse_carried = ['turret', T1]
   '''拿炮塔2'''
   def takeT2(self):
       T2 = Turret(1, self.cfg)
       if self.money >= T2.price:
           self.mouse_carried = ['turret', T2]
   '''拿炮塔3'''
   def takeT3(self):
       T3 = Turret(2, self.cfg)
       if self.money >= T3.price:
           self.mouse_carried = ['turret', T3]
   '''出售炮塔'''
   def takeXXX(self):
       XXX = pygame.image.load(self.cfg.IMAGEPATHS['game']['x'])
       self.mouse_carried = ['XXX', XXX]
   '''找下一个路径单元'''
   def find_next_path(self, enemy):
       x, y = enemy.coord
       # 优先级: 下右左上
       neighbours = [(x, y+1), (x+1, y), (x-1, y), (x, y-1)]
       for neighbour in neighbours:
           if (neighbour in self.path_list) and (neighbour not in enemy.reached_path):
               return neighbour
       return None
   '''将真实坐标转为地图坐标, 20个单位长度的真实坐标=地图坐标'''
   def pos2coord(self, position):
       return (position[0] // self.element_size, position[1] // self.element_size)
   '''将地图坐标转为真实坐标, 20个单位长度的真实坐标=地图坐标'''
   def coord2pos(self, coord):
       return (coord[0] * self.element_size, coord[1] * self.element_size)
   '''导入地图'''
   def loadMap(self, screen, map_path):
       map_file = open(map_path, 'r')
       idx_j = -1
       for line in map_file.readlines():
           line = line.strip()
           if not line:
               continue
           idx_j += 1
           idx_i = -1
           for col in line:
               try:
                   element_type = int(col)
                   element_img = self.map_elements.get(element_type)
                   element_rect = element_img.get_rect()
                   idx_i += 1
                   element_rect.left, element_rect.top = self.element_size * idx_i, self.element_size * idx_j
                   self.map_surface.blit(element_img, element_rect)
                   self.current_map[idx_i, idx_j] = element_type
                   # 把道路记下来
                   if element_type == 1:
                       self.path_list.append((idx_i, idx_j))
               except:
                   continue
       # 放洞穴和大本营
       self.map_surface.blit(self.cave, (0, 0))
       self.map_surface.blit(self.nexus, (740, 400))
       # 大本营的血条
       nexus_width = self.nexus.get_rect().width
       green_len = max(0, self.health / self.max_health) * nexus_width
       if green_len > 0:
           pygame.draw.line(self.map_surface, (0, 255, 0), (740, 400), (740 + green_len, 400), 3)
       if green_len < nexus_width:
           pygame.draw.line(self.map_surface, (255, 0, 0), (740 + green_len, 400), (740 + nexus_width, 400), 3)
       screen.blit(self.map_surface, (0, 0))
       map_file.close()
   '''暂停游戏'''
   def pauseGame(self, screen):
       pause_interface = PauseInterface(self.cfg)
       pause_interface.update(screen)
   '''退出游戏'''
   def quitGame(self):
       pygame.quit()
       sys.exit(0)

7、游戏暂停

import sys
import pygame

'''游戏暂停主界面'''
class MainInterface(pygame.sprite.Sprite):
   def __init__(self, cfg):
       pygame.sprite.Sprite.__init__(self)
       self.image = pygame.image.load(cfg.IMAGEPATHS['pause']['gamepaused']).convert()
       self.rect = self.image.get_rect()
       self.rect.center = cfg.SCREENSIZE[0] / 2, cfg.SCREENSIZE[1] / 2
   '''更新函数'''
   def update(self):
       pass

'''恢复游戏按钮'''
class ResumeButton(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(391, 380)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.image.load(cfg.IMAGEPATHS['pause']['resume_black']).convert()
       self.image_2 = pygame.image.load(cfg.IMAGEPATHS['pause']['resume_red']).convert()
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''游戏暂停界面'''
class PauseInterface():
   def __init__(self, cfg):
       self.main_interface = MainInterface(cfg)
       self.resume_btn = ResumeButton(cfg)
       self.components = pygame.sprite.LayeredUpdates(self.main_interface, self.resume_btn)
   '''外部调用'''
   def update(self, screen):
       clock = pygame.time.Clock()
       background = pygame.Surface(screen.get_size())
       count = 0
       flag = True
       while True:
           count += 1
           clock.tick(60)
           self.components.clear(screen, background)
           self.components.update()
           if count % 10 == 0:
               count = 0
               flag = not flag
           if flag:
               self.components.draw(screen)
           else:
               screen.blit(self.main_interface.image, self.main_interface.rect)
           pygame.display.flip()
           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.resume_btn.rect.collidepoint(mouse_pos):
                           return True

8、游戏结束及分数

import sys
import pygame

'''游戏结束主界面'''
class MainInterface(pygame.sprite.Sprite):
   def __init__(self, cfg):
       pygame.sprite.Sprite.__init__(self)
       self.image = pygame.image.load(cfg.IMAGEPATHS['end']['gameover']).convert()
       self.rect = self.image.get_rect()
       self.rect.center = cfg.SCREENSIZE[0] / 2, cfg.SCREENSIZE[1] / 2
   '''更新函数'''
   def update(self):
       pass

'''继续游戏按钮'''
class ContinueButton(pygame.sprite.Sprite):
   def __init__(self, cfg, position=(400, 409)):
       pygame.sprite.Sprite.__init__(self)
       self.image_1 = pygame.image.load(cfg.IMAGEPATHS['end']['continue_black']).convert()
       self.image_2 = pygame.image.load(cfg.IMAGEPATHS['end']['continue_red']).convert()
       self.image = self.image_1
       self.rect = self.image.get_rect()
       self.rect.center = position
   '''更新函数: 不断地更新检测鼠标是否在按钮上'''
   def update(self):
       mouse_pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(mouse_pos):
           self.image = self.image_2
       else:
           self.image = self.image_1

'''游戏结束类'''
class EndInterface():
   def __init__(self, cfg):
       self.main_interface = MainInterface(cfg)
       self.continue_btn = ContinueButton(cfg)
       self.components = pygame.sprite.LayeredUpdates(self.main_interface, self.continue_btn)
   '''外部调用'''
   def update(self, screen):
       clock = pygame.time.Clock()
       background = pygame.Surface(screen.get_size())
       count = 0
       flag = True
       while True:
           count += 1
           clock.tick(60)
           self.components.clear(screen, background)
           self.components.update()
           if count % 10 == 0:
               count = 0
               flag = not flag
           if flag:
               self.components.draw(screen)
           else:
               screen.blit(self.main_interface.image, self.main_interface.rect)
           pygame.display.flip()
           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.continue_btn.rect.collidepoint(mouse_pos):
                           return True

9、引入音频、图片、地图、难度json

启动游戏主程序

tower.py

import cfg
import pygame
from modules import *

'''主函数'''
def main():
   pygame.init()
   pygame.mixer.init()
   pygame.mixer.music.load(cfg.AUDIOPATHS['bgm'])
   pygame.mixer.music.play(-1, 0.0)
   pygame.mixer.music.set_volume(0.25)
   screen = pygame.display.set_mode(cfg.SCREENSIZE)
   pygame.display.set_caption("塔防游戏 —— hacklex")
   # 调用游戏开始界面
   start_interface = StartInterface(cfg)
   is_play = start_interface.update(screen)
   if not is_play:
       return
   # 调用游戏界面
   while True:
       choice_interface = ChoiceInterface(cfg)
       map_choice, difficulty_choice = choice_interface.update(screen)
       game_interface = GamingInterface(cfg)
       game_interface.start(screen, map_path=cfg.MAPPATHS[str(map_choice)], difficulty_path=cfg.DIFFICULTYPATHS[str(difficulty_choice)])
       end_interface = EndInterface(cfg)
       end_interface.update(screen)

'''run'''
if __name__ == '__main__':
   main()

四、游戏启动方法

1、开发工具启动

如果你配置了开发工具的环境VScode、sublimeText、notepad+、pycharm什么的,可以直接在工具中,运行游戏。

如果没配置,可以使用命令启动。

2、命令行启动 gif

进入代码根目录,按住shift+鼠标右键,选择 此处运行powershell,然后执行代码:python tower.py

即可 运行。

Python Pygame实战之塔防游戏的实现

来源:https://lexsaints.blog.csdn.net/article/details/115672270

标签:Python,Pygame,塔防,游戏
0
投稿

猜你喜欢

  • 如何利用PyQt5制作一个简单的登录界面

    2023-11-18 20:36:31
  • 在MySQL数据库中如何来复位根用户的密码

    2008-12-03 16:57:00
  • Python中Numpy的深拷贝和浅拷贝

    2021-10-04 21:08:43
  • Python自动化测试pytest中fixtureAPI简单说明

    2023-02-26 21:13:42
  • Python中xml和json格式相互转换操作示例

    2023-04-26 18:02:39
  • 银行账号输入格式化, 支持部分浏缆器

    2007-09-26 18:27:00
  • 在Pycharm中使用GitHub的方法步骤

    2021-04-27 04:46:48
  • Python pyautogui模块实现鼠标键盘自动化方法详解

    2023-07-25 09:16:42
  • Go pprof内存指标含义备忘录及案例分析

    2024-05-21 10:19:46
  • layui实现下拉复选功能的例子(包括数据的回显与上传)

    2024-02-24 17:37:10
  • PHP字符转义相关函数小结(php下的转义字符串)

    2023-11-16 14:29:18
  • Python+Opencv实现数字识别的示例代码

    2021-03-10 14:16:38
  • Javascript简写条件语句(推荐)

    2023-09-01 04:10:25
  • 详解Python中heapq模块的用法

    2022-01-15 05:58:35
  • 运行SQL Server的计算机间移动数据库

    2009-01-20 13:07:00
  • 详解javascript中的变量提升和函数提升

    2024-06-07 15:53:28
  • python学习之whl文件解释与安装详解

    2021-11-01 18:09:14
  • Bootstrap实现响应式导航栏效果

    2023-08-13 15:32:13
  • python中torch.nn.identity()方法详解

    2021-05-21 16:51:01
  • python实现的汉诺塔算法示例

    2023-09-21 11:28:17
  • asp之家 网络编程 m.aspxhome.com