You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
522 lines
20 KiB
522 lines
20 KiB
import pygame
|
|
import sys
|
|
import json
|
|
import os
|
|
import random
|
|
# pip3 install moviepy
|
|
from moviepy.editor import VideoFileClip
|
|
import threading
|
|
|
|
pygame.init()
|
|
|
|
# 设置窗口
|
|
WIDTH, HEIGHT = 800, 600
|
|
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
|
pygame.display.set_caption("坦克大战")
|
|
|
|
# 播放背景音乐
|
|
pygame.mixer.music.load('Resources/bg.mp3')
|
|
pygame.mixer.music.play(-1)
|
|
|
|
# 加载资源
|
|
start_ui = pygame.image.load("Resources/startui.png")
|
|
btn_00 = pygame.image.load("Resources/btn_00.png")
|
|
btn_01 = pygame.image.load("Resources/btn_01.png")
|
|
btn_10 = pygame.image.load("Resources/btn_10.png")
|
|
btn_11 = pygame.image.load("Resources/btn_11.png")
|
|
player_image = pygame.image.load("Resources/player.png")
|
|
bullet_images = [
|
|
pygame.image.load("Resources/zd_1.png"),
|
|
pygame.image.load("Resources/zd_2.png"),
|
|
pygame.image.load("Resources/zd_3.png")
|
|
]
|
|
enemy_images = [
|
|
pygame.transform.scale(pygame.image.load('Resources/tank_1.png'), (40, 40)),
|
|
pygame.transform.scale(pygame.image.load('Resources/tank_2.png'), (40, 40)),
|
|
pygame.transform.scale(pygame.image.load('Resources/tank_3.png'), (40, 40))
|
|
]
|
|
|
|
# 加载并调整图片大小
|
|
image_dict = {
|
|
1: pygame.transform.scale(pygame.image.load('Resources/1.png'), (40, 40)),
|
|
2: pygame.transform.scale(pygame.image.load('Resources/2.png'), (40, 40)),
|
|
3: pygame.transform.scale(pygame.image.load('Resources/3.png'), (40, 40)),
|
|
4: pygame.transform.scale(pygame.image.load('Resources/4.png'), (40, 40)),
|
|
5: pygame.transform.scale(pygame.image.load('Resources/5.png'), (40, 40)),
|
|
6: pygame.transform.scale(pygame.image.load('Resources/6.png'), (40, 40)),
|
|
7: pygame.transform.scale(pygame.image.load('Resources/7.png'), (40, 40)),
|
|
8: pygame.transform.scale(pygame.image.load('Resources/8.png'), (40, 40)),
|
|
9: pygame.transform.scale(pygame.image.load('Resources/9.png'), (40, 40))
|
|
}
|
|
|
|
# 读取JSON文件
|
|
with open('map/map1.json', 'r') as f:
|
|
map_data = json.load(f)
|
|
|
|
# 格子大小
|
|
tile_size = 40
|
|
# 按钮位置
|
|
btn1_rect = btn_00.get_rect(center=(WIDTH // 2, HEIGHT // 2 - 0))
|
|
btn2_rect = btn_10.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 80))
|
|
total_kills = 0
|
|
enemy_kills = {1: 0, 2: 0, 3: 0}
|
|
|
|
class Bullet:
|
|
def __init__(self, image, position, direction, speed=10):
|
|
self.image = image
|
|
self.rect = self.image.get_rect(center=position)
|
|
self.direction = direction
|
|
self.speed = speed
|
|
self.angle = 0
|
|
self.update_angle()
|
|
|
|
def update(self):
|
|
if self.direction == 'UP':
|
|
self.rect.y -= self.speed
|
|
elif self.direction == 'DOWN':
|
|
self.rect.y += self.speed
|
|
elif self.direction == 'LEFT':
|
|
self.rect.x -= self.speed
|
|
elif self.direction == 'RIGHT':
|
|
self.rect.x += self.speed
|
|
self.update_angle()
|
|
|
|
def update_angle(self):
|
|
if self.direction == 'UP':
|
|
self.angle = 0
|
|
elif self.direction == 'DOWN':
|
|
self.angle = 180
|
|
elif self.direction == 'LEFT':
|
|
self.angle = 90
|
|
elif self.direction == 'RIGHT':
|
|
self.angle = -90
|
|
self.image = pygame.transform.rotate(bullet_images[0], self.angle)
|
|
|
|
class EnemyTank:
|
|
def __init__(self, image, position, tank_type):
|
|
self.image = image
|
|
self.rect = self.image.get_rect(center=position)
|
|
self.direction = random.choice(['UP', 'DOWN', 'LEFT', 'RIGHT'])
|
|
self.speed = 40 * (2 if tank_type == 1 else 1.5 if tank_type == 2 else 1.0)
|
|
self.move_timer = 0
|
|
self.tank_type = tank_type
|
|
self.bullet_timer = 0
|
|
self.health = 1 if tank_type == 1 else 3 if tank_type == 2 else 5
|
|
self.dead = False
|
|
self.explode_frames = [pygame.image.load(f'Resources/effect/explode_{i}.png') for i in range(8)]
|
|
self.current_explode_frame = 0
|
|
self.explode_timer = 0
|
|
|
|
def can_move(self, new_rect):
|
|
if self.direction == 'UP':
|
|
grid_x = new_rect.x // tile_size
|
|
grid_y = new_rect.y // tile_size - 1
|
|
elif self.direction == 'DOWN':
|
|
grid_x = new_rect.x // tile_size
|
|
grid_y = new_rect.y // tile_size + 1
|
|
elif self.direction == 'LEFT':
|
|
grid_x = new_rect.x // tile_size - 1
|
|
grid_y = new_rect.y // tile_size
|
|
elif self.direction == 'RIGHT':
|
|
grid_x = new_rect.x // tile_size + 1
|
|
grid_y = new_rect.y // tile_size
|
|
|
|
if new_rect.x < 0 or (new_rect.x >= WIDTH - 40) or new_rect.y < 0 or (new_rect.y >= HEIGHT - 40):
|
|
return False
|
|
if map_data['layer_1'][grid_y][grid_x] == 2 or map_data['layer_1'][grid_y][grid_x] == 3 or map_data['layer_1'][grid_y][grid_x] == 4 or map_data['layer_1'][grid_y][grid_x] == 6:
|
|
return False
|
|
return True
|
|
|
|
def move(self):
|
|
new_rect = self.rect.copy()
|
|
if self.direction == 'UP':
|
|
new_rect.y -= self.speed / 60
|
|
elif self.direction == 'DOWN':
|
|
new_rect.y += self.speed / 60
|
|
elif self.direction == 'LEFT':
|
|
new_rect.x -= self.speed / 60
|
|
elif self.direction == 'RIGHT':
|
|
new_rect.x += self.speed / 60
|
|
|
|
if self.can_move(new_rect):
|
|
self.rect = new_rect
|
|
self.move_timer = 0
|
|
else:
|
|
self.move_timer += 1
|
|
if self.move_timer >= 60: # 每秒尝试改变方向
|
|
self.change_direction()
|
|
|
|
def change_direction(self):
|
|
self.direction = random.choice(['UP', 'DOWN', 'LEFT', 'RIGHT'])
|
|
|
|
def update(self):
|
|
if not self.dead:
|
|
self.move()
|
|
self.bullet_timer += 1
|
|
bullet_speed = 150 if self.tank_type == 1 else 100 if self.tank_type == 2 else 80
|
|
bullet_image = bullet_images[self.tank_type - 1]
|
|
|
|
if (self.bullet_timer >= (180 if self.tank_type == 1 else 240 if self.tank_type == 2 else 300)): # 每3/4/5秒发射一颗子弹
|
|
bullet = Bullet(bullet_image, (self.rect.centerx, self.rect.centery), self.direction, speed=bullet_speed / 60)
|
|
bullets.append(bullet)
|
|
self.bullet_timer = 0
|
|
self.rotate()
|
|
|
|
def rotate(self):
|
|
if self.direction == 'UP':
|
|
self.image = pygame.transform.rotate(enemy_images[self.tank_type - 1], 0)
|
|
elif self.direction == 'DOWN':
|
|
self.image = pygame.transform.rotate(enemy_images[self.tank_type - 1], 180)
|
|
elif self.direction == 'LEFT':
|
|
self.image = pygame.transform.rotate(enemy_images[self.tank_type - 1], 90)
|
|
elif self.direction == 'RIGHT':
|
|
self.image = pygame.transform.rotate(enemy_images[self.tank_type - 1], -90)
|
|
|
|
def take_damage(self, damage):
|
|
self.health -= damage
|
|
if self.health <= 0:
|
|
self.dead = True
|
|
self.explode()
|
|
|
|
def explode(self):
|
|
threading.Thread(target=self.play_explosion_sound).start()
|
|
|
|
def play_explosion_sound(self):
|
|
pygame.mixer.Sound('Resources/dead.wav').play()
|
|
|
|
def render_explosion(self):
|
|
if self.dead:
|
|
self.explode_timer += 1
|
|
if self.explode_timer >= 20:
|
|
self.current_explode_frame += 1
|
|
self.explode_timer = 0
|
|
if self.current_explode_frame < len(self.explode_frames):
|
|
screen.blit(self.explode_frames[self.current_explode_frame], self.rect.topleft)
|
|
|
|
class Tank:
|
|
def __init__(self, image, speed, position):
|
|
self.image = image
|
|
self.speed = speed
|
|
self.rect = self.image.get_rect(center=position)
|
|
self.direction = 'UP'
|
|
self.move_timer = 0
|
|
self.bullet_timer = 0
|
|
self.bullets = []
|
|
self.can_fire = True
|
|
self.health = 10
|
|
self.dead = False
|
|
self.invincible = False
|
|
self.invincible_timer = 0
|
|
self.invincibility_start_time = 0
|
|
|
|
def move(self, dx, dy):
|
|
new_rect = self.rect.move(dx, dy)
|
|
if self.can_move(new_rect):
|
|
self.rect = new_rect
|
|
grid_x = (new_rect.x + 40) // tile_size
|
|
grid_y = (new_rect.y + 40) // tile_size
|
|
if map_data['layer_1'][grid_y][grid_x] == 8:
|
|
self.invincible = True
|
|
self.invincibility_start_time = pygame.time.get_ticks()
|
|
map_data['layer_1'][grid_y][grid_x] = 0
|
|
elif map_data['layer_1'][grid_y][grid_x] == 9:
|
|
self.health = 10
|
|
map_data['layer_1'][grid_y][grid_x] = 0
|
|
|
|
def can_move(self, new_rect):
|
|
grid_x = (new_rect.x + 40) // tile_size
|
|
grid_y = (new_rect.y + 40) // tile_size
|
|
|
|
if new_rect.x < 0 or (new_rect.x >= WIDTH - 40) or new_rect.y < 0 or (new_rect.y >= HEIGHT - 40):
|
|
return False
|
|
if map_data['layer_1'][grid_y][grid_x] == 2 or map_data['layer_1'][grid_y][grid_x] == 3 or map_data['layer_1'][grid_y][grid_x] == 4 or map_data['layer_1'][grid_y][grid_x] == 6:
|
|
return False
|
|
return True
|
|
|
|
def rotate(self, direction):
|
|
self.direction = direction
|
|
if direction == 'UP':
|
|
self.image = pygame.transform.rotate(player_image, 0)
|
|
elif direction == 'DOWN':
|
|
self.image = pygame.transform.rotate(player_image, 180)
|
|
elif direction == 'LEFT':
|
|
self.image = pygame.transform.rotate(player_image, 90)
|
|
elif direction == 'RIGHT':
|
|
self.image = pygame.transform.rotate(player_image, -90)
|
|
|
|
def update(self):
|
|
if not self.dead:
|
|
if self.invincible:
|
|
if pygame.time.get_ticks() - self.invincibility_start_time >= 5000: # 5秒后失去无敌状态
|
|
self.invincible = False
|
|
|
|
self.move_timer += 1
|
|
if self.move_timer >= 1200: # 每2秒改变方向
|
|
self.move_timer = 0
|
|
self.change_direction()
|
|
|
|
if self.direction == 'UP':
|
|
self.move(0, -self.speed / 60)
|
|
elif self.direction == 'DOWN':
|
|
self.move(0, self.speed / 60)
|
|
elif self.direction == 'LEFT':
|
|
self.move(-self.speed / 60, 0)
|
|
elif self.direction == 'RIGHT':
|
|
self.move(self.speed / 60, 0)
|
|
|
|
self.rotate(self.direction)
|
|
|
|
def fire(self):
|
|
if self.can_fire:
|
|
bullet_image = bullet_images[random.randint(0, 2)]
|
|
bullet = Bullet(bullet_image, (self.rect.centerx, self.rect.centery), self.direction)
|
|
self.bullets.append(bullet)
|
|
self.can_fire = False
|
|
|
|
def reset_fire(self):
|
|
self.can_fire = True
|
|
|
|
def take_damage(self, damage):
|
|
if not self.invincible:
|
|
self.health -= damage
|
|
if self.health <= 0:
|
|
self.dead = True
|
|
self.game_over(False)
|
|
|
|
def game_over(self, victory):
|
|
finish_background = pygame.image.load("Resources/finish.png")
|
|
finish_background = pygame.transform.scale(finish_background, (WIDTH, HEIGHT))
|
|
while True:
|
|
screen.blit(finish_background, (0, 0))
|
|
show_result(victory)
|
|
pygame.display.flip()
|
|
start_menu()
|
|
|
|
def activate_invincibility(self):
|
|
self.invincible = True
|
|
self.invincible_timer = 0
|
|
|
|
def start_menu():
|
|
while True:
|
|
for event in pygame.event.get():
|
|
if event.type == pygame.QUIT:
|
|
pygame.quit()
|
|
sys.exit()
|
|
|
|
mouse_pos = pygame.mouse.get_pos()
|
|
|
|
if btn1_rect.collidepoint(mouse_pos):
|
|
screen.blit(start_ui, (0, 0))
|
|
screen.blit(btn_01, btn1_rect.topleft)
|
|
else:
|
|
screen.blit(start_ui, (0, 0))
|
|
screen.blit(btn_00, btn1_rect.topleft)
|
|
|
|
if btn2_rect.collidepoint(mouse_pos):
|
|
screen.blit(btn_11, btn2_rect.topleft)
|
|
else:
|
|
screen.blit(btn_10, btn2_rect.topleft)
|
|
|
|
if pygame.mouse.get_pressed()[0]:
|
|
if btn1_rect.collidepoint(mouse_pos):
|
|
battle_scene()
|
|
elif btn2_rect.collidepoint(mouse_pos):
|
|
pygame.quit()
|
|
sys.exit()
|
|
|
|
pygame.display.flip()
|
|
|
|
def display_video(video_path, screen):
|
|
video = VideoFileClip(video_path)
|
|
for frame in video.iter_frames(fps=30, dtype='uint8'):
|
|
surface = pygame.surfarray.make_surface(frame)
|
|
surface = pygame.transform.rotate(surface, -90)
|
|
|
|
screen.blit(surface, (0, 0))
|
|
pygame.display.flip()
|
|
|
|
for event in pygame.event.get():
|
|
if event.type == pygame.QUIT:
|
|
pygame.quit()
|
|
sys.exit()
|
|
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
|
|
break
|
|
|
|
def spawn_enemy():
|
|
rand = random.random()
|
|
if rand < 0.5:
|
|
return 1 # tank_1
|
|
elif rand < 0.8:
|
|
return 2 # tank_2
|
|
else:
|
|
return 3 # tank_3
|
|
|
|
def show_result(victory):
|
|
global total_kills
|
|
global enemy_kills
|
|
font = pygame.font.SysFont(None, 74)
|
|
if victory:
|
|
title = font.render("WIN", True, (255, 255, 255))
|
|
else:
|
|
title = font.render("GAME OVER", True, (255, 0, 0))
|
|
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, HEIGHT // 2 - title.get_height() // 2 - 50))
|
|
|
|
tank_icons = [pygame.transform.scale(enemy_images[i], (40, 40)) for i in range(3)]
|
|
for i in range(3):
|
|
screen.blit(tank_icons[i], (WIDTH // 2 - 60 + i * 60, HEIGHT // 2 + 20))
|
|
count_text = font.render(f"{enemy_kills[i + 1]}", True, (255, 255, 255))
|
|
screen.blit(count_text, (WIDTH // 2 - 60 + i * 60, HEIGHT // 2 + 70))
|
|
|
|
def draw_kills():
|
|
font = pygame.font.SysFont(None, 36)
|
|
kill_text = font.render(f"Kill: {total_kills}", True, (255, 255, 255))
|
|
screen.blit(kill_text, (10, 10))
|
|
|
|
def battle_scene():
|
|
video_path = 'Resources/story.mp4'
|
|
display_video(video_path, screen)
|
|
|
|
player_tank = Tank(player_image, 60, (8 * tile_size + tile_size // 2, 14 * tile_size + tile_size // 2))
|
|
enemy_count = 20
|
|
enemy_spawn_timer = 0
|
|
enemy_spawn_positions = [(20, 20), (WIDTH // 2, 20), (WIDTH - 60, 20)]
|
|
enemies = []
|
|
global bullets
|
|
bullets = []
|
|
|
|
while True:
|
|
for event in pygame.event.get():
|
|
if event.type == pygame.QUIT:
|
|
pygame.quit()
|
|
sys.exit()
|
|
|
|
enemy_spawn_timer += 1
|
|
if enemy_count > 0 and enemy_spawn_timer >= 240: # 每4秒生成
|
|
position = enemy_spawn_positions[enemy_count % 3]
|
|
tank_type = spawn_enemy()
|
|
enemy_image = enemy_images[tank_type - 1]
|
|
enemies.append(EnemyTank(enemy_image, position, tank_type))
|
|
enemy_count -= 1
|
|
enemy_spawn_timer = 0
|
|
|
|
keys = pygame.key.get_pressed()
|
|
if keys[pygame.K_UP]:
|
|
player_tank.rotate('UP')
|
|
player_tank.move(0, -player_tank.speed / 60)
|
|
if keys[pygame.K_DOWN]:
|
|
player_tank.rotate('DOWN')
|
|
player_tank.move(0, player_tank.speed / 60)
|
|
if keys[pygame.K_LEFT]:
|
|
player_tank.rotate('LEFT')
|
|
player_tank.move(-player_tank.speed / 60, 0)
|
|
if keys[pygame.K_RIGHT]:
|
|
player_tank.rotate('RIGHT')
|
|
player_tank.move(player_tank.speed / 60, 0)
|
|
|
|
if keys[pygame.K_SPACE]:
|
|
player_tank.fire()
|
|
else:
|
|
player_tank.reset_fire()
|
|
|
|
screen.fill((0, 0, 0))
|
|
for layer in range(2):
|
|
for row in range(15):
|
|
for col in range(20):
|
|
key = map_data['layer_' + str(layer)][row][col]
|
|
if key in image_dict:
|
|
screen.blit(image_dict[key], (col * tile_size, row * tile_size))
|
|
|
|
screen.blit(player_tank.image, player_tank.rect.topleft)
|
|
|
|
for bullet in player_tank.bullets:
|
|
bullet.update()
|
|
screen.blit(bullet.image, bullet.rect.topleft)
|
|
grid_x = bullet.rect.x // tile_size
|
|
grid_y = bullet.rect.y // tile_size
|
|
if grid_x >= 0 and grid_y >= 0:
|
|
if grid_y < len(map_data['layer_1']) and grid_x < len(map_data['layer_1'][0]):
|
|
check_enemy = False
|
|
for enemy in enemies:
|
|
if enemy.rect.colliderect(bullet.rect) and not enemy.dead:
|
|
enemy.take_damage(1)
|
|
if enemy.dead:
|
|
global total_kills
|
|
global enemy_kills
|
|
total_kills += 1
|
|
enemy_kills[enemy.tank_type] += 1
|
|
player_tank.bullets.remove(bullet)
|
|
check_enemy = True
|
|
break
|
|
if not check_enemy:
|
|
tile_id = map_data['layer_1'][grid_y][grid_x]
|
|
if tile_id == 2:
|
|
map_data['layer_1'][grid_y][grid_x] = 0
|
|
player_tank.bullets.remove(bullet)
|
|
elif tile_id == 3:
|
|
map_data['layer_1'][grid_y][grid_x] = 2
|
|
player_tank.bullets.remove(bullet)
|
|
elif tile_id == 4:
|
|
player_tank.bullets.remove(bullet)
|
|
elif tile_id == 6:
|
|
map_data['layer_1'][grid_y][grid_x] = random.choice([8, 9])
|
|
player_tank.bullets.remove(bullet)
|
|
elif tile_id == 7:
|
|
player_tank.game_over(False)
|
|
break
|
|
|
|
for enemy in enemies:
|
|
if not enemy.dead:
|
|
enemy.update()
|
|
screen.blit(enemy.image, enemy.rect.topleft)
|
|
health_bar_length = enemy.rect.width
|
|
health_bar_height = 5
|
|
health_bar_color = (255, 0, 0)
|
|
health_ratio = enemy.health / (1 if enemy.tank_type == 1 else 3 if enemy.tank_type == 2 else 5)
|
|
pygame.draw.rect(screen, health_bar_color, (enemy.rect.x, enemy.rect.y - health_bar_height, health_bar_length * health_ratio, health_bar_height))
|
|
else:
|
|
enemy.render_explosion()
|
|
|
|
for bullet in bullets[:]:
|
|
bullet.update()
|
|
screen.blit(bullet.image, bullet.rect.topleft)
|
|
grid_x = bullet.rect.x // tile_size
|
|
grid_y = bullet.rect.y // tile_size
|
|
if grid_x >= 0 and grid_y >= 0:
|
|
if grid_y < len(map_data['layer_1']) and grid_x < len(map_data['layer_1'][0]):
|
|
if player_tank.rect.colliderect(bullet.rect):
|
|
player_tank.take_damage(1)
|
|
bullets.remove(bullet)
|
|
else:
|
|
tile_id = map_data['layer_1'][grid_y][grid_x]
|
|
if tile_id == 2:
|
|
map_data['layer_1'][grid_y][grid_x] = 1
|
|
bullets.remove(bullet)
|
|
elif tile_id == 3:
|
|
map_data['layer_1'][grid_y][grid_x] = 2
|
|
bullets.remove(bullet)
|
|
elif tile_id == 4:
|
|
bullets.remove(bullet)
|
|
elif tile_id == 6:
|
|
map_data['layer_1'][grid_y][grid_x] = random.choice([8, 9])
|
|
bullets.remove(bullet)
|
|
elif tile_id == 7:
|
|
player_tank.game_over(False)
|
|
|
|
|
|
for row in range(15):
|
|
for col in range(20):
|
|
key = map_data['layer_2'][row][col]
|
|
if key in image_dict:
|
|
screen.blit(image_dict[key], (col * tile_size, row * tile_size))
|
|
|
|
# 绘制玩家坦克的生命值
|
|
player_health_bar_length = player_tank.rect.width
|
|
player_health_bar_height = 5
|
|
player_health_bar_color = (255, 0, 0)
|
|
player_health_ratio = player_tank.health / 10 # 默认生命值为10
|
|
pygame.draw.rect(screen, player_health_bar_color, (player_tank.rect.centerx - player_health_bar_length // 2, player_tank.rect.top - player_health_bar_height - 5, player_health_bar_length * player_health_ratio, player_health_bar_height))
|
|
draw_kills()
|
|
pygame.display.flip()
|
|
|
|
start_menu()
|