我是一个典型的 80 后,年老时玩过了特地多的游戏,所以这几天用 Python3+pygame 实现了一个另外小游戏”坦克大战“(其余的游戏,请翻阅我的博客)
本实例代码量有些多,残缺的版本在 1000 行左右(当然了如果再次优化的话 会缩小一部分)
分享进去,心愿能帮忙到大家,毕竟本人做教育行业做了这么多年,还是教育情怀的,哈哈哈哈哈
一、显示成果
二、代码
上面代码用到了一些素材(游戏背景音乐、图片等等),能够到 https://www.itprojects.cn/detail.html?example_id=869a7cbfd9bd4d9b23e61a4d88e39b1c 下载,谢谢大家的反对
残缺代码如下(留神:为了不便下载以及编写更简略,没有采纳多模块的形式,全副代码全副放到 main.py 文件中)
"""
作者:it 我的项目实例网
网站:wwww.itprojects.cn
"""
import random
import sys
import pygame
# 屏幕的宽、高
WIDTH = 630
HEIGHT = 630
# 边界值
BORDER_LEN = 3
# 字体
FONTPATH = 'resources/font/font.ttf'
class Iron(pygame.sprite.Sprite):
"""铁墙类"""
# 定义精灵组,将所有的砖墙实例对象增加到外面
group = pygame.sprite.Group()
def __init__(self, position):
# 调用父类的初始化办法,这样才可能实现必要的初始化操作
super().__init__()
self.image = pygame.image.load("resources/images/scene/iron.png")
# 当应用碰撞判断办法时,pygame 就须要晓得以后要检测的物体的地位,所以这个 rect 属性肯定要设置
self.rect = self.image.get_rect()
self.rect.topleft = position
# 增加到精灵组
self.group.add(self)
@classmethod
def show(cls, screen):
for temp in cls.group:
screen.blit(temp.image, temp.rect)
class Ice(pygame.sprite.Sprite):
"""冰类"""
# 定义精灵组,将所有的实例对象增加到外面
group = pygame.sprite.Group()
def __init__(self, position):
# 调用父类的初始化办法,这样才可能实现必要的初始化操作
super().__init__()
# 因为是 12x12 的小图片,所以须要制作一个 24x24 的 image
image = pygame.Surface((24, 24))
for i in range(2):
for j in range(2):
image.blit(pygame.image.load("resources/images/scene/ice.png"), (12 * i, 12 * j))
self.image = image
# 当应用碰撞判断办法时,pygame 就须要晓得以后要检测的物体的地位,所以这个 rect 属性肯定要设置
self.rect = self.image.get_rect()
self.rect.topleft = position
# 增加到精灵组
self.group.add(self)
@classmethod
def show(cls, screen):
for temp in cls.group:
screen.blit(temp.image, temp.rect)
class River(pygame.sprite.Sprite):
"""河流类"""
# 定义精灵组,将所有的实例对象增加到外面
group = pygame.sprite.Group()
def __init__(self, position):
# 调用父类的初始化办法,这样才可能实现必要的初始化操作
super().__init__()
# 因为是 12x12 的小图片,所以须要制作一个 24x24 的 image
image = pygame.Surface((24, 24))
for i in range(2):
for j in range(2):
image.blit(pygame.image.load("resources/images/scene/river1.png"), (12 * i, 12 * j))
self.image = image
# 当应用碰撞判断办法时,pygame 就须要晓得以后要检测的物体的地位,所以这个 rect 属性肯定要设置
self.rect = self.image.get_rect()
self.rect.topleft = position
# 增加到精灵组
self.group.add(self)
@classmethod
def show(cls, screen):
for temp in cls.group:
screen.blit(temp.image, temp.rect)
class Tree(pygame.sprite.Sprite):
"""树类"""
# 定义精灵组,将所有的实例对象增加到外面
group = pygame.sprite.Group()
def __init__(self, position):
# 调用父类的初始化办法,这样才可能实现必要的初始化操作
super().__init__()
# 因为是 12x12 的小图片,所以须要制作一个 24x24 的 image
image = pygame.Surface((24, 24))
for i in range(2):
for j in range(2):
image.blit(pygame.image.load("resources/images/scene/tree.png"), (12 * i, 12 * j))
self.image = image
# 当应用碰撞判断办法时,pygame 就须要晓得以后要检测的物体的地位,所以这个 rect 属性肯定要设置
self.rect = self.image.get_rect()
self.rect.topleft = position
# 增加到精灵组
self.group.add(self)
@classmethod
def show(cls, screen):
for temp in cls.group:
screen.blit(temp.image, temp.rect)
class Brick(pygame.sprite.Sprite):
"""砖墙类"""
# 定义精灵组,将所有的砖墙实例对象增加到外面
group = pygame.sprite.Group()
def __init__(self, position):
# 调用父类的初始化办法,这样才可能实现必要的初始化操作
super().__init__()
self.image = pygame.image.load("resources/images/scene/brick.png")
# 当应用碰撞判断办法时,pygame 就须要晓得以后要检测的物体的地位,所以这个 rect 属性肯定要设置
self.rect = self.image.get_rect()
self.rect.topleft = position
# 增加到精灵组
self.group.add(self)
@classmethod
def show(cls, screen):
for temp in cls.group:
screen.blit(temp.image, temp.rect)
class Bullet(pygame.sprite.Sprite):
"""子弹类"""
# 定义精灵组,将所有的砖墙实例对象增加到外面
group = pygame.sprite.Group()
group_enemy = pygame.sprite.Group()
def __init__(self, _type, direction, position):
super().__init__()
# 子弹图片
if direction == "up":
image_path = "resources/images/bullet/bullet_up.png"
elif direction == "down":
image_path = "resources/images/bullet/bullet_down.png"
elif direction == "left":
image_path = "resources/images/bullet/bullet_left.png"
elif direction == "right":
image_path = "resources/images/bullet/bullet_right.png"
self.image = pygame.image.load(image_path)
# 子弹地位
self.rect = self.image.get_rect()
self.rect.center = position # 设置子弹的初始地位的中心点
# 子弹方向
self.direction = direction
# 子弹挪动速度
self.speed = 8
# 将子弹增加到精灵组
if _type == "player":
self.group.add(self)
else:
self.group_enemy.add(self)
@classmethod
def auto_move(cls):
for temp in cls.group:
if temp.rect.x < BORDER_LEN or temp.rect.x > WIDTH - BORDER_LEN or temp.rect.y < BORDER_LEN or temp.rect.y > HEIGHT - BORDER_LEN:
cls.group.remove(temp)
print("子弹超出边界,移除子弹")
continue
if temp.direction == "up":
temp.rect = temp.rect.move((0, -temp.speed))
elif temp.direction == "down":
temp.rect = temp.rect.move((0, temp.speed))
elif temp.direction == "left":
temp.rect = temp.rect.move((-temp.speed, 0))
elif temp.direction == "right":
temp.rect = temp.rect.move((temp.speed, 0))
for temp in cls.group_enemy:
if temp.rect.x < BORDER_LEN or temp.rect.x > WIDTH - BORDER_LEN or temp.rect.y < BORDER_LEN or temp.rect.y > HEIGHT - BORDER_LEN:
cls.group_enemy.remove(temp)
print("子弹超出边界,移除子弹")
continue
if temp.direction == "up":
temp.rect = temp.rect.move((0, -temp.speed))
elif temp.direction == "down":
temp.rect = temp.rect.move((0, temp.speed))
elif temp.direction == "left":
temp.rect = temp.rect.move((-temp.speed, 0))
elif temp.direction == "right":
temp.rect = temp.rect.move((temp.speed, 0))
# 子弹碰砖墙(如果相碰,那么就移除以后子弹以及砖墙)
pygame.sprite.groupcollide(cls.group, Brick.group, True, True)
pygame.sprite.groupcollide(cls.group_enemy, Brick.group, True, True)
# 子弹碰铁墙(如果相碰,那么只移除子弹)
for bullet in cls.group:
if pygame.sprite.spritecollide(bullet, Iron.group, False, None):
cls.group.remove(bullet)
for bullet in cls.group_enemy:
if pygame.sprite.spritecollide(bullet, Iron.group, False, None):
cls.group_enemy.remove(bullet)
@classmethod
def show(cls, screen):
"""显示子弹"""
for temp in cls.group:
screen.blit(temp.image, temp.rect)
for temp in cls.group_enemy:
screen.blit(temp.image, temp.rect)
@classmethod
def move_and_show(cls, screen):
"""挪动、显示子弹"""
cls.auto_move()
cls.show(screen)
class PlayerTank(pygame.sprite.Sprite):
"""我方坦克类"""
# 定义类属性,存储我方坦克(如果是单人模式就只有 1 个,如果是双人模式就有 2 个)player_group = list()
# 定义精灵组,用来碰撞等判断
group = pygame.sprite.Group()
def __init__(self, player, top_left):
"""实现初始化性能"""
# 调用父类的初始化办法,这样才可能实现必要的初始化操作
super().__init__()
# 坦克的图片
image_path = "resources/images/playerTank/tank_T1_0.png" if player == "player1" else "resources/images/playerTank/tank_T2_0.png"
self.tank_all_image = pygame.image.load(image_path).convert_alpha()
self.image = self.tank_all_image.subsurface((0, 0), (48, 48))
# 当应用碰撞判断办法时,pygame 就须要晓得以后要检测的物体的地位,所以这个 rect 属性肯定要设置
self.rect = self.image.get_rect()
self.rect.topleft = top_left
# 记录初始地位,以便在被击中后可能从新在默认地位呈现
self.origin_position = top_left
# 定义挪动的步长
self.step_length = 8
# 坦克的默认方向
self.direction = "up" # 默认朝上
# 挪动缓冲, 用于防止坦克间断挪动过快导致不不便调整地位
self.move_cache_time = 4
self.move_cache_count = 0
# 坦克轮子转动成果
self.switch_count = 0
self.switch_time = 2
self.switch_image_index = False
self.image_postion_index = 0
# 发射子弹的距离
self.is_bullet_cooling = False # 如果是第一次发射子弹,则不在冷却工夫内,能够失常发射
self.bullet_cooling_count = 0
self.bullet_cooling_time = 30
# 我方坦克生命次数
self.life_num = 3
# 标记此坦克是否显示
self.is_show_flag = True
# 将以后对象增加到类属性中,这样就能够通过类对象拜访到我方坦克
self.__class__.player_group.append(self) # 或者 self.player_group.append(self)也是能够的
# 增加到精灵组
self.group.add(self)
def update_direction(self):
"""更新坦克的朝向"""
if self.direction == 'up':
self.image = self.tank_all_image.subsurface((0, 0), (48, 48))
self.image_postion_index = 0
elif self.direction == 'down':
self.image = self.tank_all_image.subsurface((0, 48), (48, 48))
self.image_postion_index = 48
elif self.direction == 'left':
self.image = self.tank_all_image.subsurface((0, 96), (48, 48))
self.image_postion_index = 96
elif self.direction == 'right':
self.image = self.tank_all_image.subsurface((0, 144), (48, 48))
self.image_postion_index = 144
def move(self, direction, group_list):
"""依据键盘调整坦克方向,而后挪动"""
# 如果要挪动的方向与以后坦克的朝向不同,则先调整朝向
if self.direction != direction:
self.direction = direction
self.update_direction()
return
# 挪动缓冲
self.move_cache_count += 1
if self.move_cache_count < self.move_cache_time:
return
else:
self.move_cache_count = 0
# 挪动坦克
# 复制一份以后玩家坦克的坐标,如果碰到障碍物之后,能够进行复原
rect_ori = self.rect
if direction == "up":
self.rect = self.rect.move((0, -self.step_length))
elif direction == "down":
self.rect = self.rect.move((0, self.step_length))
elif direction == "left":
self.rect = self.rect.move((-self.step_length, 0))
elif direction == "right":
self.rect = self.rect.move((self.step_length, 0))
# 检测碰撞 "砖墙"、"铁墙"、"冰"、"河流"。"树" 无需查看
for group in group_list:
if pygame.sprite.spritecollide(self, group, False, None):
self.rect = rect_ori
# 判断碰撞到边界
if self.rect.top < BORDER_LEN:
self.rect.top = BORDER_LEN
elif self.rect.bottom > HEIGHT - BORDER_LEN:
self.rect.bottom = HEIGHT - BORDER_LEN
elif self.rect.left < BORDER_LEN:
self.rect.left = BORDER_LEN
elif self.rect.right > WIDTH - BORDER_LEN:
self.rect.right = WIDTH - BORDER_LEN
# 为坦克轮动特效切换图片
self.switch_count += 1
if self.switch_count > self.switch_time:
self.switch_count = 0
self.switch_image_index = not self.switch_image_index
self.image = self.tank_all_image.subsurface((48 * int(self.switch_image_index), self.image_postion_index), (48, 48))
def fire(self):
"""发射子弹"""
if not self.is_bullet_cooling:
if self.direction == "up":
position = (self.rect.centerx, self.rect.y)
elif self.direction == "down":
position = (self.rect.centerx, self.rect.y + 48)
elif self.direction == "left":
position = (self.rect.x, self.rect.centery)
elif self.direction == "right":
position = (self.rect.x + 48, self.rect.centery)
Bullet("player", self.direction, position)
print("我方坦克发射子弹")
@classmethod
def move_player_tank(cls, is_dual_mode, group_list):
"""管制我方坦克挪动"""
# 检查用户按键,从而管制坦克挪动
key_pressed = pygame.key.get_pressed()
# 定义挪动的步长
step_length = 8
# 复制一份以后玩家 1 的坐标,如果碰到障碍物之后,能够进行复原
# rect_ori = cls.player_group[0].rect
# 玩家一, ASWD 挪动
if key_pressed[pygame.K_w]:
cls.player_group[0].move("up", group_list)
elif key_pressed[pygame.K_s]:
cls.player_group[0].move("down", group_list)
elif key_pressed[pygame.K_a]:
cls.player_group[0].move("left", group_list)
elif key_pressed[pygame.K_d]:
cls.player_group[0].move("right", group_list)
elif key_pressed[pygame.K_SPACE]:
# 如果按下了空格键,那么就发射子弹
cls.player_group[0].fire()
# 查看玩家 1 是否碰撞到障碍物
# # 检测碰撞 "砖墙"
# if pygame.sprite.spritecollide(cls.player_group[0], brick_group, False, None):
# print("玩家 1 碰到了砖墙", cls.player_group[0].rect)
# cls.player_group[0].rect = rect_ori
# 玩家二, ↑↓←→挪动
if is_dual_mode:
# 复制一份以后玩家 2 的坐标,如果碰到障碍物之后,能够进行复原
# rect_ori = cls.player_group[1].rect
if key_pressed[pygame.K_UP]:
cls.player_group[1].move("up", group_list)
elif key_pressed[pygame.K_DOWN]:
cls.player_group[1].move("down", group_list)
elif key_pressed[pygame.K_LEFT]:
cls.player_group[1].move("left", group_list)
elif key_pressed[pygame.K_RIGHT]:
cls.player_group[1].move("right", group_list)
elif key_pressed[pygame.K_KP0]:
# 如果按下了数字 0,那么就发射子弹
cls.player_group[1].fire()
# 查看玩家 2 是否碰撞到障碍物
# 检测碰撞 "砖墙"
# if pygame.sprite.spritecollide(cls.player_group[1], brick_group, False, None):
# cls.player_group[1].rect = rect_ori
def bullet_cooling(self):
"""判断发射子弹的冷却工夫是否达到"""
# 对发射子弹的冷却工夫计数
self.bullet_cooling_count += 1
if self.bullet_cooling_count > self.bullet_cooling_time:
self.is_bullet_cooling = False # 不在冷却状态,即意味着能够发射子弹
self.bullet_cooling_count = 0
print("冷却结束...")
else:
self.is_bullet_cooling = True # 不能发射,正在冷却
def judge_bomb(self):
"""判断是否被击中"""
# 判断碰撞到敌方坦克子弹
if pygame.sprite.spritecollide(self, Bullet.group_enemy, True, None):
self.life_num -= 1 # 如果被击中,那么就生命值 -1
if self.life_num == 0:
self.is_show_flag = False # 如果曾经没有了生命值,那么就标记为不显示
# 从新设置地位为初始地位
self.rect.topleft = self.origin_position
@classmethod
def show(cls, screen, is_dual_mode):
"""显示我方坦克"""
if cls.player_group:
if cls.player_group[0].is_show_flag:
screen.blit(cls.player_group[0].image, cls.player_group[0].rect)
# 对发射子弹的冷却工夫计数
cls.player_group[0].bullet_cooling()
# 判断是否被击中
cls.player_group[0].judge_bomb()
if is_dual_mode and cls.player_group[1].is_show_flag:
# 如果是双人模式
screen.blit(cls.player_group[1].image, cls.player_group[1].rect)
# 对发射子弹的冷却工夫计数
cls.player_group[1].bullet_cooling()
# 判断是否被击中
cls.player_group[1].judge_bomb()
class PlayerHome(pygame.sprite.Sprite):
"""我方大本营"""
home = None
def __init__(self, position):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("resources/images/home/home1.png")
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
self.__class__.home = self
@classmethod
def show(cls, screen):
"""显示大本营"""
screen.blit(cls.home.image, cls.home.rect)
class EnemyTank(pygame.sprite.Sprite):
"""敌人坦克类"""
# 定义精灵组,将所有的实例对象增加到外面
group = pygame.sprite.Group()
def __init__(self, position):
# 调用父类的初始化办法,这样才可能实现必要的初始化操作
super().__init__()
# 坦克默认的朝向
self.direction = random.choice(["up", "down", "left", "right"])
self.tank_all_image = pygame.image.load("resources/images/enemyTank/enemy_1_0.png")
self.image = None
self.update_direction() # 依据随机朝向,计算出要应用的坦克图片
self.rect = self.image.get_rect()
self.rect.topleft = position
# 坦克默认的速度
self.speed = random.choice([2, 4])
# 发射子弹的距离
self.is_bullet_cooling = True
self.bullet_cooling_count = 0
self.bullet_cooling_time = 5
# 增加到精灵组
self.group.add(self)
def update_direction(self):
"""更新坦克的朝向"""
if self.direction == 'up':
self.image = self.tank_all_image.subsurface((0, 0), (48, 48))
elif self.direction == 'down':
self.image = self.tank_all_image.subsurface((0, 48), (48, 48))
elif self.direction == 'left':
self.image = self.tank_all_image.subsurface((0, 96), (48, 48))
elif self.direction == 'right':
self.image = self.tank_all_image.subsurface((0, 144), (48, 48))
@classmethod
def show(cls, screen):
for temp in cls.group:
screen.blit(temp.image, temp.rect)
def move(self, group_list):
"""
敌方坦克主动挪动
:return:
"""
# 记录地位,以便在碰到障碍物之后,可能复原
rect_ori = self.rect
if self.direction == "up":
self.rect = self.rect.move((0, -self.speed))
elif self.direction == "down":
self.rect = self.rect.move((0, self.speed))
elif self.direction == "left":
self.rect = self.rect.move((-self.speed, 0))
elif self.direction == "right":
self.rect = self.rect.move((self.speed, 0))
# 检测碰撞 "砖墙"、"铁墙"、"冰"、"河流"。"树" 无需查看
for group in group_list:
if pygame.sprite.spritecollide(self, group, False, None):
self.rect = rect_ori
# 随机失去新的朝向
self.direction = random.choice(["up", "down", "left", "right"])
self.update_direction()
# 碰撞到其余敌方坦克
# 先将本坦克从精灵组中移除
self.group.remove(self)
# 而后再判断是否碰撞到其余敌方坦克
if pygame.sprite.spritecollide(self, self.group, False, None):
self.rect = rect_ori
# 随机失去新的朝向
self.direction = random.choice(["up", "down", "left", "right"])
self.update_direction()
# 判断之后再将本坦克增加到精灵组
self.group.add(self)
# 碰撞到玩家坦克
if pygame.sprite.spritecollide(self, PlayerTank.group, False, None):
self.rect = rect_ori
# 随机失去新的朝向
self.direction = random.choice(["up", "down", "left", "right"])
self.update_direction()
# 碰撞到我方子弹
if pygame.sprite.spritecollide(self, Bullet.group, True, None):
self.group.remove(self)
# 判断碰撞到边界,而后再次随机新朝向
if self.rect.top < BORDER_LEN:
self.rect.top = BORDER_LEN
# 随机失去新的朝向
self.direction = random.choice(["up", "down", "left", "right"])
self.update_direction()
elif self.rect.bottom > HEIGHT - BORDER_LEN:
self.rect.bottom = HEIGHT - BORDER_LEN
# 随机失去新的朝向
self.direction = random.choice(["up", "down", "left", "right"])
self.update_direction()
elif self.rect.left < BORDER_LEN:
self.rect.left = BORDER_LEN
# 随机失去新的朝向
self.direction = random.choice(["up", "down", "left", "right"])
self.update_direction()
elif self.rect.right > WIDTH - BORDER_LEN:
self.rect.right = WIDTH - BORDER_LEN
# 随机失去新的朝向
self.direction = random.choice(["up", "down", "left", "right"])
self.update_direction()
@classmethod
def auto_move(cls, group_list):
for temp in cls.group:
temp.move(group_list)
@classmethod
def auto_move_and_show(cls, screen, group_list):
cls.auto_move(group_list)
cls.show(screen)
def judge_cooling_and_fire(self):
"""判断是否达到冷却工夫(即发射子弹要有距离),而后发射"""
self.bullet_cooling_count += 1
if self.bullet_cooling_count > self.bullet_cooling_time:
self.bullet_cooling_count = 0
if random.randint(1, 10) == 6: # 如果随机失去的数字恰巧是 6,那么就示意冷却工夫到
self.is_bullet_cooling = False
if not self.is_bullet_cooling:
# 创立子弹对象
if self.direction == "up":
position = (self.rect.centerx, self.rect.y)
elif self.direction == "down":
position = (self.rect.centerx, self.rect.y + 48)
elif self.direction == "left":
position = (self.rect.x, self.rect.centery)
elif self.direction == "right":
position = (self.rect.x + 48, self.rect.centery)
Bullet("enemy", self.direction, position)
# 发射结束后,立即设置为冷却状态
self.is_bullet_cooling = True
@classmethod
def fire(cls):
"""发射子弹"""
for temp in cls.group:
temp.judge_cooling_and_fire()
class Game(object):
"""游戏管制类"""
def __init__(self):
"""初始化工作"""
# 游戏初始化
pygame.init()
# 创立用来显示画面的对象(了解为相框)self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
def game_start_interface(self):
"""
显示游戏开始画面(让用户抉择游戏人数):return:False 为单人模式,True 为双人模式
"""
# 筹备用到的图片
background_img = pygame.image.load("resources/images/others/background.png")
logo_img = pygame.image.load("resources/images/others/logo.png")
logo_img = pygame.transform.scale(logo_img, (446, 70)) # 图片放大 1 倍,行将 892x140-->446x70
logo_rect = logo_img.get_rect() # 失去这个图片的左上角的坐标(默认是(0,0)点),以及宽高
# 为了可能让 logo 图片在适合的地位显示,咱们能够设置它的中心点的坐标,它会依据本身的宽高主动计算出左上角的坐标
logo_rect.centerx, logo_rect.centery = WIDTH / 2, HEIGHT // 4
# print(logo_rect.topleft) # 在终端中看到,此时输入的值是:(92, 122)
# 筹备要显示文本(1player、2players)# 字体
font = pygame.font.Font(FONTPATH, 60) # 60 示意要显示的字体大小
font_color_white = (255, 255, 255) # 要显示的字体色彩为红色
# 1player
one_player_text = font.render('1 PLAYER', True, font_color_white)
one_player_rect = one_player_text.get_rect()
one_player_rect.left, one_player_rect.top = WIDTH / 2 - 50, HEIGHT / 2 - 60
# 2players
two_players_text = font.render('2 PLAYERS', True, font_color_white)
two_players_rect = two_players_text.get_rect()
two_players_rect.left, two_players_rect.top = WIDTH / 2 - 50, HEIGHT / 2
# 游戏人数抉择时的图片
select_player_num_tank = pygame.image.load("resources/images/playerTank/tank_T1_0.png").convert_alpha().subsurface((0, 144), (48, 48))
select_player_num_tank_rect = select_player_num_tank.get_rect()
# 游戏开始提醒
game_tip = font.render('press <Enter> to start', True, font_color_white)
game_tip_rect = game_tip.get_rect()
game_tip_rect.centerx, game_tip_rect.top = WIDTH / 2, HEIGHT / 1.4
# 创立一个计时器,用来实现更加不便的延时(避免 while 循环过快,占用太多 CPU 的问题)clock = pygame.time.Clock()
# 存储游戏人数(False 单人,True 双人)is_dual_mode = False
# 主循环
while True:
# 事件检测(例如点击了键盘、鼠标点击等)for event in pygame.event.get():
if event.type == pygame.QUIT: # 如果用鼠标点击了❌,那么就退出程序
pygame.quit()
sys.exit() # 退出程序
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
return is_dual_mode
elif event.key == pygame.K_UP or event.key == pygame.K_DOWN or event.key == pygame.K_w or event.key == pygame.K_s:
is_dual_mode = not is_dual_mode
# print("以后抉择的游戏人数是(True 双人、False 单人)", is_dual_mode)
# 从 (0,0) 点(即左上角)开始贴一张图片(了解为在 screen 这个相框中从左上角开始贴一张照片)self.screen.blit(background_img, (0, 0))
# 显示 logo
self.screen.blit(logo_img, logo_rect)
# 显示游戏人数文字
self.screen.blit(one_player_text, one_player_rect)
self.screen.blit(two_players_text, two_players_rect)
# 显示标记抉择的人数的 tank
if is_dual_mode:
# 双人模式
select_player_num_tank_rect.right, select_player_num_tank_rect.top = two_players_rect.left - 10, two_players_rect.top
self.screen.blit(select_player_num_tank, select_player_num_tank_rect)
else:
# 单人模式
select_player_num_tank_rect.right, select_player_num_tank_rect.top = one_player_rect.left - 10, one_player_rect.top
self.screen.blit(select_player_num_tank, select_player_num_tank_rect)
# 显示提醒
self.screen.blit(game_tip, game_tip_rect)
# 显示 screen 这个相框的内容(此时在这个相框中的内容像照片、文字等会显示进去)pygame.display.update()
# FPS(每秒钟显示画面的次数)clock.tick(60) # 通过肯定的延时,实现 1 秒钟可能循环 60 次
def game_end_interface(self, is_win):
"""显示游戏完结画面"""
# 背景
background_img = pygame.image.load("resources/images/others/background.png")
# 游戏失败图
game_over_img = pygame.image.load("resources/images/others/gameover.png")
game_over_img = pygame.transform.scale(game_over_img, (150, 75))
game_over_img_rect = game_over_img.get_rect()
game_over_img_rect.midtop = WIDTH / 2, HEIGHT / 8
# 游戏胜利、失败字体
color_white = (255, 255, 255)
font = pygame.font.Font(FONTPATH, 60)
# 游戏胜利与否的提醒
if is_win:
font_render = font.render('Congratulations, You win!', True, color_white)
else:
font_render = font.render('Sorry, You fail!', True, color_white)
font_rect = font_render.get_rect()
font_rect.centerx, font_rect.centery = WIDTH / 2, HEIGHT / 3
# 用于抉择退出或从新开始
# 用于抉择的坦克光标
tank_cursor = pygame.image.load("resources/images/playerTank/tank_T1_0.png").convert_alpha().subsurface((0, 144), (48, 48))
tank_rect = tank_cursor.get_rect()
# 从新运行
restart_render_white = font.render('RESTART', True, color_white)
restart_rect = restart_render_white.get_rect()
restart_rect.left, restart_rect.top = WIDTH / 2.4, HEIGHT / 2
# 退出
quit_render_white = font.render('QUIT', True, color_white)
quit_rect = quit_render_white.get_rect()
quit_rect.left, quit_rect.top = WIDTH / 2.4, HEIGHT / 1.6
# 标记以后抉择的是退出还是持续游戏
is_quit_game = False
# 创立计时器对象,用于管制刷新频率
clock = pygame.time.Clock()
# 主循环
while True:
# 查看键盘事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
return is_quit_game
elif event.key == pygame.K_UP or event.key == pygame.K_DOWN or event.key == pygame.K_w or event.key == pygame.K_s:
is_quit_game = not is_quit_game
# 显示背景
self.screen.blit(background_img, (0, 0))
self.screen.blit(game_over_img, game_over_img_rect)
self.screen.blit(font_render, font_rect)
if not is_quit_game:
tank_rect.right, tank_rect.top = restart_rect.left - 10, restart_rect.top
self.screen.blit(tank_cursor, tank_rect)
self.screen.blit(quit_render_white, quit_rect)
self.screen.blit(restart_render_white, restart_rect)
else:
tank_rect.right, tank_rect.top = quit_rect.left - 10, quit_rect.top
self.screen.blit(tank_cursor, tank_rect)
self.screen.blit(quit_render_white, quit_rect)
self.screen.blit(restart_render_white, restart_rect)
# 刷新显示画面,此时才会真正的显示
pygame.display.update()
# 管制频率,FPS 为 60,每秒钟 60 次刷新
clock.tick(60)
def parse_game_level_file(self):
"""解析关卡文件"""
# 每个地图元素占用的像素(配置文件,例如 1.lvl 中正文里阐明了 Grid SIZE: 24 * 24 pixels)grid_size = 24
# 定义大本营
# home_dict = dict()
with open("./levels/3.lvl", errors='ignore') as f:
num_row = -1 # 用来标记地图元素是整个地图的第几行(总共 26 行,26 列)for line in f.readlines():
line = line.strip('\n') # 切除每行行尾的换行符
# 如果以后要解决的行是地图元素,那么就持续解决
if line[0] in ["S", "B", "I", "R", "C", "T"]:
# 地图元素
num_row += 1
# print("以后是第 %d 行" % num_row)
for num_col, elem in enumerate(line.split(' ')):
# print("以后是第 %d 行,第 %d 列" % (num_row, num_col))
position = BORDER_LEN + num_col * grid_size, BORDER_LEN + num_row * grid_size
if elem == 'B':
# 创立砖墙对象,而后增加到精灵组
Brick(position)
elif elem == 'I':
# 创立铁墙对象,而后增加到精灵组
Iron(position)
elif elem == 'R':
# 创立河流对象,而后增加到精灵组
River(position)
elif elem == 'C':
# 创立冰对象,而后增加到精灵组
Ice(position)
elif elem == 'T':
# 创立树对象,而后增加到精灵组
Tree(position)
elif line.startswith('%HOMEPOS'):
# 大本营地位
home_position = line.split(':')[-1]
home_position = int(home_position.split(',')[0]), int(home_position.split(',')[1])
home_position = (BORDER_LEN + home_position[0] * grid_size, BORDER_LEN + home_position[1] * grid_size)
# 创立大本营类
PlayerHome(home_position)
elif line.startswith('%PLAYERTANKPOS'):
# 我方坦克初始地位
player_tank_positions = line.split(':')[-1]
player_tank_positions = [(int(pos.split(',')[0]), int(pos.split(',')[1])) for pos in player_tank_positions.split(' ')]
player_tank_positions = [[BORDER_LEN + pos[0] * grid_size, BORDER_LEN + pos[1] * grid_size] for pos in player_tank_positions]
# 从这个图片中切除一部分来当做要应用的图片,即玩家 1 的坦克
# image = pygame.image.load("resources/images/playerTank/tank_T1_0.png").convert_alpha()
# image = image.subsurface((0, 0), (48, 48))
# player1_dict = {
# "image": image,
# "top_left": player_tank_positions[0]
# }
# 创立我方玩家 1 的坦克,而后增加到列表中
PlayerTank("player1", player_tank_positions[0])
# 从这个图片中切除一部分来当做要应用的图片,即玩家 2 的坦克
# image = pygame.image.load("resources/images/playerTank/tank_T2_0.png").convert_alpha()
# image = image.subsurface((0, 0), (48, 48))
# player2_dict = {
# "image": image,
# "top_left": player_tank_positions[1]
# }
# player_group.append(player1_dict)
# player_group.append(player2_dict)
# 创立我方玩家 2 的坦克,而后增加到列表中
PlayerTank("player2", player_tank_positions[1])
elif line.startswith('%ENEMYTANKPOS'):
# 敌方坦克初始地位
position = line.split(':')[-1]
position = [[int(pos.split(',')[0]), int(pos.split(',')[1])] for pos in position.split(' ')]
position = [(BORDER_LEN + pos[0] * grid_size, BORDER_LEN + pos[1] * grid_size) for pos in position]
# 依据敌方坦克的初始地位创立多个坦克
for pos in position:
EnemyTank(pos)
def game_run_level(self, is_dual_mode):
"""运行游戏"""
# 背景图片
background_img = pygame.image.load("resources/images/others/background.png")
# 调用解析关卡配置文件
self.parse_game_level_file()
# 帧率控制对象
clock = pygame.time.Clock()
# 运行游戏的主循环
is_win = False
is_running = True
while is_running:
# 事件检测(例如点击了键盘、鼠标点击等)for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 通过键盘管制坦克挪动
PlayerTank.move_player_tank(is_dual_mode, [Brick.group, River.group, Ice.group, Iron.group])
# 敌方坦克开发
EnemyTank.fire()
# 碰撞检测
# 子弹碰大本营(无论是我方还是敌方子弹,只有碰到都认为本关卡游戏完结)if pygame.sprite.spritecollide(PlayerHome.home, Bullet.group, True, None) or pygame.sprite.spritecollide(PlayerHome.home, Bullet.group_enemy, True, None):
is_running = False # 如果碰撞到大本营,那么通过批改这个变量为 False 让 while 循环完结,游戏行将完结运行
is_win = False
# 如果敌方坦克没有了,则认为我方胜利
if len(EnemyTank.group) == 0:
is_running = False
is_win = True
# 如果我方坦克没有了生命值,则认为游戏输了
if (not is_dual_mode and PlayerTank.player_group[0].life_num == 0) or \
(is_dual_mode and PlayerTank.player_group[0].life_num == 0 and PlayerTank.player_group[1].life_num == 0):
is_running = False
is_win = False
# 显示游戏背景
self.screen.blit(background_img, (0, 0))
# 显示砖墙
Brick.show(self.screen)
# 显示铁墙
Iron.show(self.screen)
# 显示河流
River.show(self.screen)
# 显示冰
Ice.show(self.screen)
# 显示树
Tree.show(self.screen)
# 显示大本营
PlayerHome.show(self.screen)
# 显示我方坦克
PlayerTank.show(self.screen, is_dual_mode)
# 显示我方坦克发射的子弹
Bullet.move_and_show(self.screen)
# 显示敌方坦克
EnemyTank.auto_move_and_show(self.screen, [Brick.group, River.group, Ice.group, Iron.group])
# 刷新要显示的内容,从而真正的显示
pygame.display.update()
# 每秒钟管制为 60 帧
clock.tick(60)
return is_win
def clean(self):
"""清理上一次游戏留下的残留"""
EnemyTank.group.empty()
PlayerTank.group.empty()
PlayerTank.player_group.clear()
Brick.group.empty()
Ice.group.empty()
River.group.empty()
Iron.group.empty()
Tree.group.empty()
Bullet.group.empty()
Bullet.group_enemy.empty()
def run(self):
# 显示游戏开始画面,让用户抉择游戏人数
is_dual_mode = self.game_start_interface()
# 调用游戏关卡函数,从而开始游戏
is_win = self.game_run_level(is_dual_mode)
# 接下来依据 is_win 来显示对应的输赢界面
while True:
if self.game_end_interface(is_win): # 如果返回为 True,那么就意味着退出游戏,否则持续游戏
break
# 持续从新游戏
# 清理上一次游戏的残留
self.clean()
# 创立用来显示画面的对象(了解为相框)pygame.display.set_mode((WIDTH, HEIGHT))
# 显示游戏开始画面,让用户抉择游戏人数
is_dual_mode = self.game_start_interface()
# 调用游戏关卡函数,从而开始游戏
is_win = self.game_run_level(is_dual_mode)
if __name__ == '__main__':
"""整体流程的管制"""
game = Game()
game.run()