привет! расскажи про эту игру на python
# Импорт необходимых библиотек
from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
import random
import time
import math

# Инициализация приложения
app = Ursina()
window.title = '3D Шутер'
window.borderless = False  # Вы можете закрыть игру через верхнюю панель
window.exit_button.visible = False  # Убираем кнопку выхода

# Глобальные переменные
game_duration = 300  # Длительность игры в секундах (5 минут)
start_time = time.time()
bots = []

# Класс игрока
class Player(Entity):
    def __init__(self, **kwargs):
        super().__init__()
        
        # Основные характеристики игрока
        self.speed = 5
        self.health = 100
        self.max_health = 100
        self.score = 0
        self.alive = True

        # Таймеры
        self.last_shot = 0
        self.respawn_timer = 0

        # Оружие
        self.weapons = ['pistol', 'shotgun', 'rifle']
        self.current_weapon = 'pistol'
        self.weapon_index = 0
        self.ammo = {'pistol': 50, 'shotgun': 20, 'rifle': 100}
        self.max_ammo = {'pistol': 50, 'shotgun': 20, 'rifle': 100}

        # Настройка контроллера от первого лица
        self.controller = FirstPersonController()
        self.controller.cursor.visible = False
        self.controller.gravity = 0.5
        self.controller.speed = self.speed
        mouse.visible = False  # Скрываем стандартный курсор
        mouse.locked = True    # Блокируем курсор в центре экрана

        # Модель оружия
        self.weapon_entity = Entity(
            parent=camera.ui,
            model='cube',
            scale=(0.2, 0.2, 1),
            color=color.red,
            position=(0.6, -0.6),
            rotation=(10, -10, 0)
        )

        # Шкала здоровья
        self.health_bar_background = Entity(
            parent=camera.ui,
            model='quad',
            color=color.gray,
            scale=(0.5, 0.05),
            position=(-0.5, 0.45)
        )
        self.health_bar = Entity(
            parent=self.health_bar_background,
            model='quad',
            color=color.green,
            scale=(self.health / self.max_health * 0.5, 0.05),
            position=(-0.25 + (self.health / self.max_health * 0.25), 0)
        )

        # Добавляем перекрестие
        self.crosshair = Crosshair()

        # Обработка дополнительных аргументов
        for key, value in kwargs.items():
            setattr(self, key, value)

    def update(self):
        # Проверка состояния игрока
        if self.alive:
            self.last_shot += time.dt  # Обновление таймера стрельбы

            # Обработка стрельбы
            if mouse.left:
                self.shoot()

            # Обновление шкалы здоровья
            self.update_health_bar()

            # Переключение оружия
            if held_keys['1']:
                self.switch_weapon(0)
            if held_keys['2']:
                self.switch_weapon(1)
            if held_keys['3']:
                self.switch_weapon(2)

            # Проверка падения с карты
            if self.controller.y <= -10:
                self.die()
        else:
            # Таймер возрождения
            self.respawn_timer -= time.dt
            if self.respawn_timer <= 0:
                self.respawn()

    def shoot(self):
        if not self.alive:
            return  # Не можем стрелять, если мертвы
        weapon = self.current_weapon
        if self.ammo[weapon] > 0:
            if self.can_shoot():
                self.ammo[weapon] -= 1
                bullet = Bullet(
                    position=self.controller.position + Vec3(0,1.5,0),
                    direction=camera.forward,
                    damage=self.get_weapon_damage(),
                    speed=self.get_weapon_speed()
                )
                self.last_shot = 0
            else:
                pass  # Оружие не готово к стрельбе
        else:
            print("Нет боеприпасов!")

    def can_shoot(self):
        weapon = self.current_weapon
        shoot_delays = {'pistol': 0.5, 'shotgun': 1, 'rifle': 0.1}
        return self.last_shot >= shoot_delays[weapon]

    def get_weapon_damage(self):
        weapon = self.current_weapon
        damages = {'pistol': 25, 'shotgun': 50, 'rifle': 10}
        return damages[weapon]

    def get_weapon_speed(self):
        weapon = self.current_weapon
        speeds = {'pistol': 50, 'shotgun': 40, 'rifle': 60}
        return speeds[weapon]

    def switch_weapon(self, index):
        if self.weapon_index != index:
            self.weapon_index = index
            self.current_weapon = self.weapons[self.weapon_index]
            # Обновить модель оружия
            self.weapon_entity.color = color.red if self.current_weapon == 'pistol' else (color.blue if self.current_weapon == 'shotgun' else color.green)
            print(f"Сменили оружие на {self.current_weapon}")

    def take_damage(self, amount):
        self.health -= amount
        if self.health <= 0 and self.alive:
            self.die()

    def die(self):
        self.alive = False
        self.health = 0
        self.update_health_bar()
        self.respawn_timer = 10  # Таймер возрождения
        self.controller.enabled = False  # Блокируем движение
        mouse.locked = False  # Разрешаем вращение камеры
        # Добавляем затемнение экрана
        self.death_overlay = Entity(
            parent=camera.ui,
            model='quad',
            scale=(2, 2),
            color=color.rgba(0, 0, 0, 100)
        )
        print("Вы погибли! Возрождение через 10 секунд.")

    def respawn(self):
        self.alive = True
        self.health = self.max_health
        self.update_health_bar()
        # Рандомное место возрождения
        self.controller.position = Vec3(
            random.uniform(-40, 40),
            2,
            random.uniform(-40, 40)
        )
        self.ammo = self.max_ammo.copy()
        self.controller.enabled = True  # Разрешаем движение
        mouse.locked = True  # Возвращаем управление
        # Убираем затемнение экрана
        destroy(self.death_overlay)
        print("Вы возродились!")

    def update_health_bar(self):
        # Обновление визуальной шкалы здоровья
        health_ratio = self.health / self.max_health
        self.health_bar.scale_x = health_ratio * 0.5
        self.health_bar.position_x = -0.25 + (health_ratio * 0.25)
        if health_ratio > 0.5:
            self.health_bar.color = color.green
        elif health_ratio > 0.25:
            self.health_bar.color = color.yellow
        else:
            self.health_bar.color = color.red

# Класс перекрестия
class Crosshair(Entity):
    def __init__(self):
        super().__init__()
        self.vertical_line = Entity(
            parent=camera.ui,
            model='quad',
            color=color.white,
            scale=(0.002, 0.02),
            position=(0, 0)
        )
        self.horizontal_line = Entity(
            parent=camera.ui,
            model='quad',
            color=color.white,
            scale=(0.02, 0.002),
            position=(0, 0)
        )

# Класс бота
class Bot(Entity):
    def __init__(self, bot_type='normal', position=(0,0,0)):
        super().__init__()

        # Характеристики бота в зависимости от его типа
        self.bot_type = bot_type
        if self.bot_type == 'normal':
            self.speed = 2
            self.health = 50
            self.damage = 10
            self.color = color.azure
        elif self.bot_type == 'fast':
            self.speed = 4
            self.health = 30
            self.damage = 5
            self.color = color.orange
        elif self.bot_type == 'strong':
            self.speed = 1
            self.health = 100
            self.damage = 20
            self.color = color.red

        self.max_health = self.health
        self.position = position
        self.model = 'cube'
        self.origin_y = -0.5
        self.scale_y = 2
        self.collider = 'box'
        self.gravity = 0.5  # Добавляем гравитацию
        self.mass = 1
        self.target = None  # Цель будет определяться динамически
        self.last_attack = 0
        self.alive = True

        # Шкала здоровья бота
        self.health_bar_background = Entity(
            parent=self,
            model='cube',
            color=color.gray,
            scale=(1, 0.1, 0.1),
            position=(0, 1.5, 0)
        )
        self.health_bar = Entity(
            parent=self.health_bar_background,
            model='cube',
            color=color.red,
            scale=((self.health / self.max_health), 1, 1),
            position=(-0.5 + (self.health / self.max_health) / 2, 0, 0)
        )

    def update(self):
        if self.alive:
            self.move_towards_target()
            self.update_health_bar()
            if self.y <= -10:
                self.die()
        else:
            destroy(self)

    def move_towards_target(self):
        # Поиск ближайшей цели (другого бота или игрока)
        potential_targets = [player] + [bot for bot in bots if bot != self and bot.alive]
        nearest_target = None
        min_distance = float('inf')

        for target in potential_targets:
            distance_to_target = distance(self.position, target.position)
            if distance_to_target < min_distance:
                min_distance = distance_to_target
                nearest_target = target

        if nearest_target and min_distance < 20:
            direction = (nearest_target.position - self.position).normalized()
            move = direction * self.speed * time.dt

            # Проверка столкновений
            self.position += move

            # Вычисляем угол между ботом и целью по оси XZ
            dx = nearest_target.x - self.x
            dz = nearest_target.z - self.z
            angle = math.degrees(math.atan2(dx, dz))
            self.rotation_y = angle

            if min_distance < 2:
                self.attack(nearest_target)
        else:
            self.random_movement()

    def random_movement(self):
        self.position += Vec3(random.uniform(-1,1), 0, random.uniform(-1,1)) * self.speed * time.dt

    def attack(self, target):
        self.last_attack += time.dt
        if self.last_attack >= 1:
            if target.alive:
                # Бот стреляет пулей в сторону цели
                bot_bullet = BotBullet(
                    position=self.position + Vec3(0, 1, 0),
                    direction=(target.position - self.position).normalized(),
                    damage=self.damage,
                    speed=40
                )
            self.last_attack = 0

    def take_damage(self, amount):
        self.health -= amount
        if self.health <= 0 and self.alive:
            self.die()

    def die(self):
        self.alive = False
        if self in bots:
            bots.remove(self)
        destroy(self.health_bar)
        destroy(self.health_bar_background)
        # Возрождение бота через 10 секунд
        invoke(respawn_bot, delay=10)

    def update_health_bar(self):
        health_ratio = self.health / self.max_health
        self.health_bar.scale_x = health_ratio
        self.health_bar.position_x = -0.5 + health_ratio / 2

# Функция возрождения бота
def respawn_bot():
    bot_type = random.choice(['normal', 'fast', 'strong'])
    bot_position = (
        random.uniform(-40, 40),
        1,
        random.uniform(-40, 40)
    )
    bot = Bot(bot_type=bot_type, position=bot_position)
    bots.append(bot)

# Класс пули игрока
class Bullet(Entity):
    def __init__(self, position=(0,0,0), direction=(0,0,0), damage=25, speed=50):
        super().__init__()
        self.model = 'sphere'
        self.color = color.yellow
        self.position = position
        self.scale = 0.1
        self.direction = direction.normalized()
        self.speed = speed
        self.collider = 'box'
        self.damage = damage
        self.life_time = 2

    def update(self):
        self.position += self.direction * self.speed * time.dt
        self.life_time -= time.dt
        if self.life_time <= 0:
            destroy(self)
        else:
            # Проверка столкновения с ботами
            for bot in bots:
                if bot.alive and self.intersects(bot).hit:
                    bot.take_damage(self.damage)
                    destroy(self)
                    break

# Класс пули бота
class BotBullet(Entity):
    def __init__(self, position=(0,0,0), direction=(0,0,0), damage=10, speed=30):
        super().__init__()
        self.model = 'sphere'
        self.color = color.red
        self.position = position
        self.scale = 0.1
        self.direction = direction.normalized()
        self.speed = speed
        self.collider = 'box'
        self.damage = damage
        self.life_time = 2

    def update(self):
        self.position += self.direction * self.speed * time.dt
        self.life_time -= time.dt
        if self.life_time <= 0:
            destroy(self)
        else:
            # Проверка столкновения с игроком
            if self.intersects(player.controller).hit:
                if player.alive:
                    player.take_damage(self.damage)
                destroy(self)
            else:
                # Проверка столкновения с другими ботами
                for bot in bots:
                    if bot.alive and self.intersects(bot).hit:
                        bot.take_damage(self.damage)
                        destroy(self)
                        break

# Класс бонусов
class Pickup(Entity):
    def __init__(self, pickup_type='health', position=(0,0,0)):
        super().__init__()
        self.pickup_type = pickup_type
        self.model = 'sphere'
        if self.pickup_type == 'health':
            self.color = color.green
        elif self.pickup_type == 'ammo':
            self.color = color.blue
        elif self.pickup_type == 'boost':
            self.color = color.yellow
        self.scale = 0.5
        self.position = position
        self.collider = 'sphere'
        self.rotation_speed = random.uniform(50, 100)

    def update(self):
        self.rotation_y += self.rotation_speed * time.dt
        if self.intersects(player.controller).hit:
            self.apply_effect()
            destroy(self)

    def apply_effect(self):
        if self.pickup_type == 'health':
            player.health = min(player.max_health, player.health + 50)
            player.update_health_bar()
            print("Подобрана аптечка!")
            self.show_message("Здоровье восстановлено!")
        elif self.pickup_type == 'ammo':
            player.ammo[player.current_weapon] = player.max_ammo[player.current_weapon]
            print("Боеприпасы пополнены!")
            self.show_message("Боеприпасы пополнены!")
        elif self.pickup_type == 'boost':
            player.speed += 2
            player.controller.speed = player.speed  # Обновить скорость контроллера
            invoke(self.remove_boost, delay=5)
            print("Скорость увеличена на 5 секунд!")
            self.show_message("Скорость увеличена!")

    def remove_boost(self):
        player.speed -= 2
        player.controller.speed = player.speed  # Вернуть исходную скорость

    def show_message(self, text):
        message = Text(
            text=text,
            position=(0, 0.2),
            origin=(0, 0),
            scale=2,
            background=True,
            duration=2
        )
        message.fade_out(duration=2, curve=curve.linear)

# Класс окружения
class Environment(Entity):
    def __init__(self):
        super().__init__()
        self.create_ground()
        self.create_elements()

    def create_ground(self):
        ground = Entity(
            model='plane',
            scale=(100, 1, 100),
            texture='white_cube',
            texture_scale=(50, 50),
            collider='box',
            color=color.green
        )

    def create_elements(self):
        # Создание деревьев
        for _ in range(30):
            x = random.uniform(-45, 45)
            z = random.uniform(-45, 45)
            tree_trunk = Entity(
                model='cube',
                color=color.brown,
                scale=(0.5, random.uniform(2, 4), 0.5),
                position=(x, 0, z),
                collider='box'
            )
            tree_leaves = Entity(
                model='sphere',
                color=color.lime,
                scale=(2, 2, 2),
                position=(x, tree_trunk.scale_y + 1, z)
            )

        # Создание зданий
        for _ in range(10):
            building = Entity(
                model='cube',
                color=color.gray,
                scale=(random.uniform(3, 5), random.uniform(3, 7), random.uniform(3, 5)),
                position=(random.uniform(-40, 40), 0, random.uniform(-40, 40)),
                collider='box'
            )

# Класс интерфейса игры
class GameHUD(Entity):
    def __init__(self):
        super().__init__()
        # Корректируем позиции и масштаб текстовых элементов
        self.timer_text = Text(
            text='Осталось времени: 5:00',
            position=(0.5, 0.45),
            scale=1.5,
            background=True,
            background_color=color.rgba(0,0,0,150),
            origin=(0, 0)
        )
        self.score_text = Text(
            text='Счёт: 0',
            position=(-0.7, 0.45),
            scale=1.5,
            background=True,
            background_color=color.rgba(0, 0, 0, 150),
            origin=(-0.5, 0)
        )
        self.message_text = Text(
            text='',
            position=(0, 0),
            scale=3,
            origin=(0, 0),
            background=True,
            color=color.yellow
        )
        self.weapon_text = Text(
            text=f'Оружие: {player.current_weapon.upper()}',
            position=(-0.7, 0.4),
            scale=1.5,
            background=True,
            background_color=color.rgba(0, 0, 0, 150),
            origin=(-0.5, 0)
        )
        self.ammo_text = Text(
            text=f'Патроны: {player.ammo[player.current_weapon]}',
            position=(-0.7, 0.35),
            scale=1.5,
            background=True,
            background_color=color.rgba(0, 0, 0, 150),
            origin=(-0.5, 0)
        )

    def update(self):
        # Обновление таймера
        elapsed_time = time.time() - start_time
        remaining_time = max(0, game_duration - elapsed_time)
        if remaining_time <= 0:
            self.timer_text.text = 'Осталось времени: 0:00'
            self.end_game()
        else:
            minutes = int(remaining_time // 60)
            seconds = int(remaining_time % 60)
            self.timer_text.text = f"Осталось времени: {minutes}:{seconds:02d}"
        # Обновление счёта
        self.score_text.text = f"Счёт: {player.score}"
        # Обновление информации об оружии и боеприпасах
        self.weapon_text.text = f'Оружие: {player.current_weapon.upper()}'
        self.ammo_text.text = f'Патроны: {player.ammo[player.current_weapon]}'

    def end_game(self):
        # Отображение итогового счёта
        self.message_text.text = f'Игра окончена!\nВаш счёт: {player.score}'
        application.pause()

# Создание окружения
environment = Environment()

# Инициализация игрока
player = Player()

# Создание ботов и бонусов
def spawn_bots():
    bot_types = ['normal', 'fast', 'strong']
    for _ in range(9):
        bot_type = random.choice(bot_types)
        bot_position = (
            random.uniform(-40, 40),
            1,
            random.uniform(-40, 40)
        )
        bot = Bot(bot_type=bot_type, position=bot_position)
        bots.append(bot)

def spawn_pickups():
    pickup_types = ['health', 'ammo', 'boost']
    for _ in range(10):
        pickup_type = random.choice(pickup_types)
        pickup_position = (
            random.uniform(-45, 45),
            0.5,
            random.uniform(-45, 45)
        )
        pickup = Pickup(pickup_type=pickup_type, position=pickup_position)

spawn_bots()
spawn_pickups()

# Интерфейс игры
game_hud = GameHUD()

# Основная функция обновления
def update():
    player.update()
    game_hud.update()
    for bot in bots:
        bot.update()

# Запуск приложения
app.run()