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.

467 lines
16 KiB

7 months ago
import pygame
import random
from modules.TankGame import TankGame
from modules.sprites import groups
from modules.sprites.foods import Foods
from modules.sprites.bullet import Bullet
from enum import Enum
from pygame.sprite import spritecollide
class DIRECTION(Enum):
UP = (0, -1)
DOWN = (0, 1)
LEFT = (-1, 0)
RIGHT = (1, 0)
@classmethod
def list(cls):
return [DIRECTION.UP, DIRECTION.DOWN, DIRECTION.LEFT, DIRECTION.RIGHT]
@classmethod
def random(cls):
return random.choice([DIRECTION.UP, DIRECTION.DOWN, DIRECTION.LEFT, DIRECTION.RIGHT])
class COLLISION:
WITH_TANK = 0b00001
WITH_HOME = 0b00010
WITH_BORDER = 0b00100
WITH_SCENE_ELEMENTS = 0b01000
class Tank(pygame.sprite.Sprite):
def __init__(self, game_config):
super().__init__()
self.__game_config = game_config
# 坦克轮子转动效果
self._switch_count = 0
self._switch_time = 1
self._switch_pointer = False
# 移动缓冲
self._move_cache_time = 4
self._move_cache_count = 0
# 爆炸
self._boom_last_time = 5
self._boom_count = 0
self._booming_flag = False
self._level = 0
self._speed = 8
# 地图边缘宽度/屏幕大小
self._border_len = game_config.BORDER_LEN
self._screen_size = [game_config.WIDTH, game_config.HEIGHT]
self._init_resources()
self.bullet_count = 0
self._is_bullet_cooling = False
self._bullet_config = {
0: {
'speed': 8,
'enhanced': False
},
1: {
'speed': 10,
'enhanced': False
},
2: {
'speed': 10,
'enhanced': True
},
'count': 1,
'infinity': False
}
@property
def infinity_bullet(self):
return self._bullet_config['infinity']
@property
def bullet_limit(self):
return self._bullet_config['count']
@property
def _game_config(self):
return self.__game_config
def shoot(self):
if self._booming_flag:
return False
if not self._is_bullet_cooling:
if not self.infinity_bullet:
if self.bullet_count >= self.bullet_limit:
return False
else:
self.bullet_count += 1
self._is_bullet_cooling = True
position = (self.rect.centerx + self._direction.value[0], self.rect.centery + self._direction.value[1])
bullet = Bullet(direction=self._direction, position=position, tank=self, config=self._game_config)
configkey = self._level
if configkey >= 2:
configkey = 2
bullet.speed = self._bullet_config[configkey]['speed']
bullet.enhanced = self._bullet_config[configkey]['enhanced']
return bullet
return False
@property
def image(self):
if self._booming_flag:
return self._boom_image
return self._tank_direction_image.subsurface((48 * int(self._switch_pointer), 0), (48, 48))
def decrease_level(self):
if self._booming_flag:
return False
self._level -= 1
self._tank_image = pygame.image.load(self._level_images[self._level]).convert_alpha()
self._update_direction(self._direction)
# self.image = self._tank_direction_image.subsurface((48 * int(self._switch_pointer), 0), (48, 48))
if self._level < 0:
self._booming_flag = True
return True if self._level < 0 else False
'''设置坦克方向'''
def _update_direction(self, direction):
self._direction = direction
if self._direction == DIRECTION.UP:
self._tank_direction_image = self._tank_image.subsurface((0, 0), (96, 48))
elif self._direction == DIRECTION.DOWN:
self._tank_direction_image = self._tank_image.subsurface((0, 48), (96, 48))
elif self._direction == DIRECTION.LEFT:
self._tank_direction_image = self._tank_image.subsurface((0, 96), (96, 48))
elif self._direction == DIRECTION.RIGHT:
self._tank_direction_image = self._tank_image.subsurface((0, 144), (96, 48))
def _init_resources(self):
config = self._game_config
self._bullet_images = config.BULLET_IMAGE_PATHS
self._boom_image = pygame.image.load(config.OTHER_IMAGE_PATHS.get('boom_static'))
pass
def roll(self):
# 为了坦克轮动特效切换图片
self._switch_count += 1
if self._switch_count > self._switch_time:
self._switch_count = 0
self._switch_pointer = not self._switch_pointer
def move(self, direction, scene_elems, player_tanks_group, enemy_tanks_group, home):
# 爆炸时无法移动
if self._booming_flag:
return
# 方向不一致先改变方向
if self._direction != direction:
self._update_direction(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
new_position = (self._direction.value[0] * self._speed, self._direction.value[1] * self._speed)
old_rect = self.rect
self.rect = self.rect.move(new_position)
# --碰到场景元素
collisons = 0
cannot_passthrough = [scene_elems.brick_group, scene_elems.iron_group, scene_elems.river_group]
for i in cannot_passthrough:
if spritecollide(self, i, False, None):
self.rect = old_rect
collisons |= COLLISION.WITH_SCENE_ELEMENTS
# --碰到其他玩家坦克/碰到敌方坦克
if spritecollide(self, player_tanks_group, False, None) or spritecollide(self, enemy_tanks_group, False, None):
collisons |= COLLISION.WITH_TANK
self.rect = old_rect
# --碰到玩家大本营
if pygame.sprite.collide_rect(self, home):
collisons |= COLLISION.WITH_HOME
self.rect = old_rect
# --碰到边界
if self.rect.left < self._border_len:
self.rect.left = self._border_len
collisons |= COLLISION.WITH_BORDER
elif self.rect.right > self._screen_size[0] - self._border_len:
collisons |= COLLISION.WITH_BORDER
self.rect.right = self._screen_size[0] - self._border_len
elif self.rect.top < self._border_len:
collisons |= COLLISION.WITH_BORDER
self.rect.top = self._border_len
elif self.rect.bottom > self._screen_size[1] - self._border_len:
collisons |= COLLISION.WITH_BORDER
self.rect.bottom = self._screen_size[1] - self._border_len
if collisons == 0 and spritecollide(self, scene_elems.ice_group, False, None):
self.rect = self.rect.move(new_position)
return collisons
class PlayerTank(Tank):
def __init__(self, name, position, game_config, **kwargs):
super().__init__(game_config=game_config)
self._level_images = self._game_config.PLAYER_TANK_IMAGE_PATHS.get(name)
# 玩家1/玩家2
self.name = name
# 初始坦克方向
self.__init_direction = DIRECTION.UP
# 初始位置
self.__init_position = position
# 保护罩
self.__protected = False
self.__protected_mask_flash_time = 25
self.__protected_mask_flash_count = 0
self.__protected_mask_pointer = False
# 坦克生命数量
self.health = 3
# 重置
self.__reborn()
def _init_resources(self):
super()._init_resources()
# 保护罩
self.__protected_mask = pygame.image.load(self._game_config.OTHER_IMAGE_PATHS.get('protect'))
def update(self):
# 坦克子弹冷却更新
if self._is_bullet_cooling:
self._bullet_cooling_count += 1
if self._bullet_cooling_count >= self._bullet_cooling_time:
self._bullet_cooling_count = 0
self._is_bullet_cooling = False
# 无敌状态更新
if self.protected:
self.__protected_count += 1
if self.__protected_count > self.__protected_time:
self.protected = False
self.__protected_count = 0
# 爆炸状态更新
if self._booming_flag:
self._boom_count += 1
if self._boom_count > self._boom_last_time:
self._boom_count = 0
self._booming_flag = False
self.__reborn()
def improve_level(self):
# 提高坦克等级
if self._booming_flag:
return False
self._level = min(self._level + 1, len(self._level_images) - 1)
self._tank_image = pygame.image.load(self._level_images[self._level]).convert_alpha()
self._update_direction(self._direction)
return True
def decrease_level(self):
# 降低坦克等级
res = super().decrease_level()
if self._level < 0:
self.health -= 1
return res
def add_health(self):
# 增加生命值
self.health += 1
@property
def protected(self):
return self.__protected
@protected.setter
def protected(self, protected):
self.__protected = protected
def draw(self, screen):
# 画我方坦克
screen.blit(self.image, self.rect)
if self.protected:
self.__protected_mask_flash_count += 1
if self.__protected_mask_flash_count > self.__protected_mask_flash_time:
self.__protected_mask_pointer = not self.__protected_mask_pointer
self.__protected_mask_flash_count = 0
screen.blit(self.__protected_mask.subsurface((48 * self.__protected_mask_pointer, 0), (48, 48)), self.rect)
def __reborn(self):
# 重置坦克, 重生的时候用
# 移动缓冲, 用于避免坦克连续运行不方便调整位置
self._move_cache_time = 4
self._move_cache_count = 0
# 是否无敌状态
self.protected = False
self.__protected_time = 1500
self.__protected_count = 0
# 坦克移动速度
self._speed = 8
# 子弹冷却时间
self._bullet_cooling_time = 30
self._bullet_cooling_count = 0
self._is_bullet_cooling = False
# 坦克等级
self._level = 0
# 坦克图片
self._tank_image = pygame.image.load(self._level_images[self._level]).convert_alpha()
self._update_direction(self.__init_direction)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = self.__init_position
'''敌方坦克类'''
class EnemyTank(Tank):
def __init__(self, position, game_config, **kwargs):
super().__init__(game_config=game_config)
enemy_level_images = self._game_config.ENEMY_TANK_IMAGE_PATHS
self.__tank_type = random.choices(['0', '1', '2'], weights=[10, 10, TankGame().level+10])[0]#random.choice(list(enemy_level_images.keys()))
self._level_images = enemy_level_images.get(self.__tank_type)
self._bullet_config[2]['enhanced'] = False
self._level = int(self.__tank_type) #random.randint(0, len(self._level_images) - 2)
# 子弹冷却时间
self._bullet_cooling_time = 120 - self._level * 10
self._bullet_cooling_count = 0
self._is_bullet_cooling = False
# 用于给刚生成的坦克播放出生特效
self._is_borning = True
self._borning_left_time = 90
# 坦克是否可以行动(玩家坦克捡到食物clock可以触发为True)
self.is_keep_still = False
self.keep_still_time = 500
self.keep_still_count = 0
# 坦克移动速度,等级越高速度越低
self._speed = 10 - self._level * 3
self.__food = None
# 坦克出场特效
appear_image = pygame.image.load(self._game_config.OTHER_IMAGE_PATHS.get('appear')).convert_alpha()
self.__appear_images = [
appear_image.subsurface((0, 0), (48, 48)),
appear_image.subsurface((48, 0), (48, 48)),
appear_image.subsurface((96, 0), (48, 48))
]
if random.random() <= 0.3 * self._level:
# if (random.random() >= 0.6) and (self._level == len(self._level_images) - 2):
# self._level += 1
self.__food = Foods(food_image_paths=self._game_config.FOOD_IMAGE_PATHS, screensize=self._screen_size)
# 坦克图片路径
self._tank_image = pygame.image.load(self._level_images[self._level]).convert_alpha()
self._direction = DIRECTION.random()
self._update_direction(self._direction)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
# 坦克爆炸图
@property
def food(self):
return self.__food
def clear_food(self):
self.__food = None
@property
def image(self):
if self._is_borning:
return self.__appear_images[(90 - self._borning_left_time // 10) % 3]
return super().image
def update(self, scene_elems, player_tanks_group, enemy_tanks_group, home):
# 死后爆炸
remove_flag = False
bullet = None
if self._booming_flag:
self._boom_count += 1
if self._boom_count > self._boom_last_time:
self._boom_count = 0
self._booming_flag = False
remove_flag = True
return remove_flag, bullet
# 禁止行动时不更新
if self.is_keep_still:
self.keep_still_count += 1
if self.keep_still_count > self.keep_still_time:
self.is_keep_still = False
self.keep_still_count = 0
return remove_flag, bullet
# 播放出生特效
if self._is_borning:
self._borning_left_time -= 1
if self._borning_left_time < 0:
self._is_borning = False
# 出生后实时更新
else:
# --坦克移动
self.move(self._direction, scene_elems, player_tanks_group, enemy_tanks_group, home)
self.roll()
# --坦克子弹冷却更新
if self._is_bullet_cooling:
self._bullet_cooling_count += 1
if self._bullet_cooling_count >= self._bullet_cooling_time:
self._bullet_cooling_count = 0
self._is_bullet_cooling = False
# --能射击就射击
bullet = self.shoot()
return remove_flag, bullet
def random_change_direction(self, exclude_current_direction=False):
list = DIRECTION.list()
if exclude_current_direction:
list.remove(self._direction)
self._update_direction(random.choice(list))
self._switch_count = self._switch_time
self._move_cache_count = self._move_cache_time
def move(self, direction, scene_elems, player_tanks_group, enemy_tanks_group, home):
# 遇到障碍物考虑改变方向
collisions = super().move(direction, scene_elems, player_tanks_group, enemy_tanks_group, home)
if collisions is None or collisions == 0:
return
change_direction = False
if collisions & COLLISION.WITH_SCENE_ELEMENTS & COLLISION.WITH_BORDER:
change_direction = True
self.random_change_direction(change_direction)
def set_still(self):
self.is_keep_still = True
class TankFactory(object):
ENEMY_TANK = 0
PLAYER1_TANK = 1
PLAYER2_TANK = 2
def __init__(self, config):
self.__game_config = config
def create_tank(self, position, tank_type):
if tank_type == TankFactory.ENEMY_TANK:
return EnemyTank(position=position, game_config=self.__game_config)
elif tank_type == TankFactory.PLAYER1_TANK:
return PlayerTank(name='player1', position=position, game_config=self.__game_config)
elif tank_type == TankFactory.PLAYER2_TANK:
return PlayerTank(name='player2', position=position, game_config=self.__game_config)