原標題:用Python實現坦克大戰遊戲 | 乾貨貼
作者 | 李秋鍵
出品 | AI科技大本營(rgznai100)
《坦克大戰》是1985年日本南夢宮Namco遊戲公司在任天堂FC平台上,推出的一款多方位平面射擊遊戲。遊戲以坦克戰鬥及保衛基地為主題,屬於策略型聯機類。同時也是FC平台上少有的內建關卡編輯器的幾個遊戲之一,玩家可自己創建獨特的關卡,並通過獲取一些道具使坦克和基地得到強化。而今天我們就將利用python還原以下坦克大戰的製作。
實驗前的準備
首先我們使用的Python版本是3.6.5所用到的模塊如下:
- Pygame模塊用來創建遊戲整體框架、精靈等基本架構;
- OS 模塊用來加載本地文件(包括音樂,背景、圖片等素材)。
精靈類程序
其中精靈類設置作為基本程序框架用來主函數的調用,其中包括子彈類程序、食物類、家類、磚牆樹木等障礙物類、坦克類。具體程序布局如下:
其中子彈類程序,首先需要建立bullet.py程序,建立類包括子彈位置、方向、圖片加載、子彈速度等基本信息。具體代碼如下:
'''子彈'''
class Bullet(pygame.sprite.Sprite):
def __init__(self, bullet_image_paths, screensize, direction, position, border_len, is_stronger=False, speed=8, **kwargs):
pygame.sprite.Sprite.__init__(self)
self.bullet_image_paths = bullet_image_paths
self.width, self.height = screensize
self.direction = direction
self.position = position
self.image = pygame.image.load(self.bullet_image_paths.get(direction))
self.rect = self.image.get_rect
self.rect.center = position
# 地圖邊緣寬度
self.border_len = border_len
# 是否為加強版子彈(加強版可碎鐵牆)
self.is_stronger = is_stronger
# 子彈速度
self.speed = speed
'''移動子彈, 若子彈越界, 則返回True, 否則為False'''
def move(self):
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)
if (self.rect.top < self.border_len) or (self.rect.bottom > self.height) or (self.rect.left < self.border_len) or (self.rect.right > self.width):
return True
return False
食物獎勵類,建立food.py作為坦克吃到食物時增加生命等基本獎勵:
'''食物類. 用於獲得獎勵'''
class Foods(pygame.sprite.Sprite):
def __init__(self, food_image_paths, screensize, **kwargs):
pygame.sprite.Sprite.__init__(self)
self.name = random.choice(list(food_image_paths.keys))
self.image = pygame.image.load(food_image_paths.get(self.name))
self.rect = self.image.get_rect
self.rect.left, self.rect.top = random.randint(100, screensize[0]-100), random.randint(100, screensize[1]-100)
self.exist_time = 1000
def update(self):
self.exist_time -= 1
return True if self.exist_time < 0 else False
坦克家類,建立home.py存儲家基本信息(包括是否存活、圖片加載、位置尺寸等)。
'''大本營類'''
class Home(pygame.sprite.Sprite):
def __init__(self, position, imagepaths, **kwargs):
pygame.sprite.Sprite.__init__(self)
self.imagepaths = imagepaths
self.image = pygame.image.load(self.imagepaths[0])
self.rect = self.image.get_rect
self.rect.left, self.rect.top = position
self.alive = True
'''被摧毀'''
def setDead(self):
self.image = pygame.image.load(self.imagepaths[1])
self.alive = False
'''畫到螢幕上'''
def draw(self, screen):
screen.blit(self.image, self.rect)
磚牆等障礙物類,建立scenes.py其中也是主要位置尺寸的布局:
'''磚牆'''
class Brick(pygame.sprite.Sprite):
def __init__(self, position, imagepath, **kwargs):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(imagepath)
self.rect = self.image.get_rect
self.rect.left, self.rect.top = position
'''鐵牆'''
class Iron(pygame.sprite.Sprite):
def __init__(self, position, imagepath, **kwargs):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(imagepath)
self.rect = self.image.get_rect
self.rect.left, self.rect.top = position
'''冰'''
class Ice(pygame.sprite.Sprite):
def __init__(self, position, imagepath, **kwargs):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((24, 24))
for i in range(2):
for j in range(2):
self.image.blit(pygame.image.load(imagepath), (12*i, 12*j))
self.rect = self.image.get_rect
self.rect.left, self.rect.top = position
'''河流'''
class River(pygame.sprite.Sprite):
def __init__(self, position, imagepath, **kwargs):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((24, 24))
for i in range(2):
for j in range(2):
self.image.blit(pygame.image.load(imagepath), (12*i, 12*j))
self.rect = self.image.get_rect
self.rect.left, self.rect.top = position
'''樹'''
class Tree(pygame.sprite.Sprite):
def __init__(self, position, imagepath, **kwargs):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((24, 24))
for i in range(2):
for j in range(2):
self.image.blit(pygame.image.load(imagepath), (12*i, 12*j))
self.rect = self.image.get_rect
self.rect.left, self.rect.top = position
坦克類,建立tanks.py包括坦克數量名稱、初始位置等信息:
'''玩家坦克類'''
class PlayerTank(pygame.sprite.Sprite):
def __init__(self, name, player_tank_image_paths, position, border_len, screensize, direction='up', bullet_image_paths=None, protected_mask_path=None, boom_image_path=None, **kwargs):
pygame.sprite.Sprite.__init__(self)
# 玩家1/玩家2
self.name = name
# 坦克圖片路徑
self.player_tank_image_paths = player_tank_image_paths.get(name)
# 地圖邊緣寬度
self.border_len = border_len
# 螢幕大小
self.screensize = screensize
# 初始坦克方向
self.init_direction = direction
# 初始位置
self.init_position = position
# 子彈圖片
self.bullet_image_paths = bullet_image_paths
# 保護罩圖片路徑
self.protected_mask = pygame.image.load(protected_mask_path)
self.protected_mask_flash_time = 25
self.protected_mask_flash_count = 0
self.protected_mask_pointer = False
# 坦克爆炸圖
self.boom_image = pygame.image.load(boom_image_path)
self.boom_last_time = 5
self.booming_flag = False
self.boom_count = 0
# 坦克生命數量
self.num_lifes = 3
# 重置
self.reset
'''移動'''
def move(self, direction, scene_elems, player_tanks_group, enemy_tanks_group, home):
# 爆炸時無法移動
if self.booming_flag:
return
# 方向不一致先改變方向
if self.direction != direction:
self.setDirection(direction)
self.switch_count = self.switch_time
self.move_cache_count = self.move_cache_time
# 移動(使用緩衝)
self.move_cache_count += 1
if self.move_cache_count < self.move_cache_time:
return
self.move_cache_count = 0
if self.direction == 'up':
speed = (0, -self.speed)
elif self.direction == 'down':
speed = (0, self.speed)
elif self.direction == 'left':
speed = (-self.speed, 0)
elif self.direction == 'right':
speed = (self.speed, 0)
rect_ori = self.rect
self.rect = self.rect.move(speed)
# --碰到場景元素
for key, value in scene_elems.items:
if key in ['brick_group', 'iron_group', 'river_group']:
if pygame.sprite.spritecollide(self, value, False, None):
self.rect = rect_ori
elif key in ['ice_group']:
if pygame.sprite.spritecollide(self, value, False, None):
self.rect = self.rect.move(speed)
# --碰到其他玩家坦克
if pygame.sprite.spritecollide(self, player_tanks_group, False, None):
self.rect = rect_ori
# --碰到敵方坦克
if pygame.sprite.spritecollide(self, enemy_tanks_group, False, None):
self.rect = rect_ori
# --碰到玩家大本營
if pygame.sprite.collide_rect(self, home):
self.rect = rect_ori
# --碰到邊界
if self.rect.left < self.border_len:
self.rect.left = self.border_len
elif self.rect.right > self.screensize[0]-self.border_len:
self.rect.right = self.screensize[0] - self.border_len
elif self.rect.top < self.border_len:
self.rect.top = self.border_len
elif self.rect.bottom > self.screensize[1]-self.border_len:
self.rect.bottom = self.screensize[1] - self.border_len
# 為了坦克輪動特效切換圖片
self.switch_count += 1
if self.switch_count > self.switch_time:
self.switch_count = 0
self.switch_pointer = not self.switch_pointer
self.image = self.tank_direction_image.subsurface((48*int(self.switch_pointer), 0), (48, 48))
遊戲介面設置
遊戲介面設置包括:開始介面設置、結束介面設置和關卡切換介面設置:
其中遊戲開始介面包括玩家數的選擇和圖片音樂的加載:
'''遊戲開始介面'''
def gameStartInterface(screen, cfg):
background_img = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get('background'))
color_white = (255, 255, 255)
color_red = (255, 0, 0)
font = pygame.font.Font(cfg.FONTPATH, cfg.WIDTH//12)
logo_img = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get('logo'))
logo_img = pygame.transform.scale(logo_img, (446, 70))
logo_rect = logo_img.get_rect
logo_rect.centerx, logo_rect.centery = cfg.WIDTH/2, cfg.HEIGHT//4
tank_cursor = pygame.image.load(cfg.PLAYER_TANK_IMAGE_PATHS.get('player1')[0]).convert_alpha.subsurface((0, 144), (48, 48))
tank_rect = tank_cursor.get_rect
# 玩家數量選擇
player_render_white = font.render('1 PLAYER', True, color_white)
player_render_red = font.render('1 PLAYER', True, color_red)
player_rect = player_render_white.get_rect
player_rect.left, player_rect.top = cfg.WIDTH/2.8, cfg.HEIGHT/2.5
players_render_white = font.render('2 PLAYERS', True, color_white)
players_render_red = font.render('2 PLAYERS', True, color_red)
players_rect = players_render_white.get_rect
players_rect.left, players_rect.top = cfg.WIDTH/2.8, cfg.HEIGHT/2
# 遊戲提示
game_tip = font.render('press to start', True, color_white)
game_tip_rect = game_tip.get_rect
game_tip_rect.centerx, game_tip_rect.top = cfg.WIDTH/2, cfg.HEIGHT/1.4
game_tip_flash_time = 25
game_tip_flash_count = 0
game_tip_show_flag = True
# 主循環
clock = pygame.time.Clock
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
screen.blit(background_img, (0, 0))
screen.blit(logo_img, logo_rect)
game_tip_flash_count += 1
if game_tip_flash_count > game_tip_flash_time:
game_tip_show_flag = not game_tip_show_flag
game_tip_flash_count = 0
if game_tip_show_flag:
screen.blit(game_tip, game_tip_rect)
if not is_dual_mode:
tank_rect.right, tank_rect.top = player_rect.left-10, player_rect.top
screen.blit(tank_cursor, tank_rect)
screen.blit(player_render_red, player_rect)
screen.blit(players_render_white, players_rect)
else:
tank_rect.right, tank_rect.top = players_rect.left-10, players_rect.top
screen.blit(tank_cursor, tank_rect)
screen.blit(player_render_white, player_rect)
screen.blit(players_render_red, players_rect)
pygame.display.update
clock.tick(60)
遊戲結束介面包括遊戲勝利與失敗情況判斷和是否退出遊戲或重新開始的設置:
'''遊戲結束介面'''
def gameEndIterface(screen, cfg, is_win=True):
background_img = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get('background'))
color_white = (255, 255, 255)
color_red = (255, 0, 0)
font = pygame.font.Font(cfg.FONTPATH, cfg.WIDTH//12)
# 遊戲失敗圖
gameover_img = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get('gameover'))
gameover_img = pygame.transform.scale(gameover_img, (150, 75))
gameover_img_rect = gameover_img.get_rect
gameover_img_rect.midtop = cfg.WIDTH/2, cfg.HEIGHT/8
gameover_flash_time = 25
gameover_flash_count = 0
gameover_show_flag = True
# 遊戲勝利與否的提示
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 = cfg.WIDTH/2, cfg.HEIGHT/3
# 用於選擇退出或重新開始
tank_cursor = pygame.image.load(cfg.PLAYER_TANK_IMAGE_PATHS.get('player1')[0]).convert_alpha.subsurface((0, 144), (48, 48))
tank_rect = tank_cursor.get_rect
restart_render_white = font.render('RESTART', True, color_white)
restart_render_red = font.render('RESTART', True, color_red)
restart_rect = restart_render_white.get_rect
restart_rect.left, restart_rect.top = cfg.WIDTH/2.4, cfg.HEIGHT/2
quit_render_white = font.render('QUIT', True, color_white)
quit_render_red = font.render('QUIT', True, color_red)
quit_rect = quit_render_white.get_rect
quit_rect.left, quit_rect.top = cfg.WIDTH/2.4, cfg.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
screen.blit(background_img, (0, 0))
gameover_flash_count += 1
if gameover_flash_count > gameover_flash_time:
gameover_show_flag = not gameover_show_flag
gameover_flash_count = 0
if gameover_show_flag:
screen.blit(gameover_img, gameover_img_rect)
screen.blit(font_render, font_rect)
if not is_quit_game:
tank_rect.right, tank_rect.top = restart_rect.left-10, restart_rect.top
screen.blit(tank_cursor, tank_rect)
screen.blit(restart_render_red, restart_rect)
screen.blit(quit_render_white, quit_rect)
else:
tank_rect.right, tank_rect.top = quit_rect.left-10, quit_rect.top
screen.blit(tank_cursor, tank_rect)
screen.blit(restart_render_white, restart_rect)
screen.blit(quit_render_red, quit_rect)
pygame.display.update
clock.tick(60)
遊戲介面切換主要是利用進度條加載:
'''關卡切換介面'''
def switchLevelIterface(screen, cfg, level_next=1):
background_img = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get('background'))
color_white = (255, 255, 255)
color_gray = (192, 192, 192)
font = pygame.font.Font(cfg.FONTPATH, cfg.WIDTH//20)
logo_img = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get('logo'))
logo_img = pygame.transform.scale(logo_img, (446, 70))
logo_rect = logo_img.get_rect
logo_rect.centerx, logo_rect.centery = cfg.WIDTH/2, cfg.HEIGHT//4
# 遊戲加載提示
font_render = font.render('Loading game data, You will enter Level-%s' % level_next, True, color_white)
font_rect = font_render.get_rect
font_rect.centerx, font_rect.centery = cfg.WIDTH/2, cfg.HEIGHT/2
# 遊戲加載進度條
gamebar = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get('gamebar')).convert_alpha
gamebar_rect = gamebar.get_rect
gamebar_rect.centerx, gamebar_rect.centery = cfg.WIDTH/2, cfg.HEIGHT/1.4
tank_cursor = pygame.image.load(cfg.PLAYER_TANK_IMAGE_PATHS.get('player1')[0]).convert_alpha.subsurface((0, 144), (48, 48))
tank_rect = tank_cursor.get_rect
tank_rect.left = gamebar_rect.left
tank_rect.centery = gamebar_rect.centery
# 加載所需時間
load_time_left = gamebar_rect.right - tank_rect.right + 8
# 主循環
clock = pygame.time.Clock
while True:
for event in pygame.event.get:
if event.type == pygame.QUIT:
pygame.quit
sys.exit
if load_time_left <= 0:
return
screen.blit(background_img, (0, 0))
screen.blit(logo_img, logo_rect)
screen.blit(font_render, font_rect)
screen.blit(gamebar, gamebar_rect)
screen.blit(tank_cursor, tank_rect)
pygame.draw.rect(screen, color_gray, (gamebar_rect.left+8, gamebar_rect.top+8, tank_rect.left-gamebar_rect.left-8, tank_rect.bottom-gamebar_rect.top-16))
tank_rect.left += 1
load_time_left -= 1
pygame.display.update
clock.tick(60)
完整代碼:
https://pan.baidu.com/s/1BUh9M73AAGkZeDN0IEKdKA
提取碼:09bl
作者:李秋鍵
CSDN博客專家,CSDN達人課作者。碩士在讀於中國礦業大學,開發有taptap競賽獲獎等。