Python Pygame实战之塔防游戏的实现
作者:LexSaints 时间:2021-12-15 08:57:22
一、环境要求
windows系统,python3.6+
安装模块
pip install pyqt5
pip install pygame
二、游戏介绍
1、游戏目标
按照关卡,设计不同的塔防地图(博主只设计了三关,有兴趣的同学,学会之后,可以自己画地图),设置三种炮台,每种炮台发射不同的炮弹,每种炮弹对敌人杀伤力不一样,每种
炮台的价格也不一样。玩家通过钱币购买炮台,并设有剪出炮台的操作(有点类似植物大战僵尸里的铲子)。敌人成功抵达塔楼,游戏结束。
2、先上游戏效果图
三、完整开发流程
1、项目主结构
首先,先整理一下项目的主结构,其实看一下主结构,基本就清晰了
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、定义敌人、塔楼、 * 的类
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
即可 运行。
来源: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