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
467 lines
16 KiB
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)
|
|
|