Гид по технологиям

Система сохранения и загрузки для игры на Arcade (Python)

6 min read Game Dev Обновлено 20 Dec 2025
Сохранение и загрузка в игре на Arcade (Python)
Сохранение и загрузка в игре на Arcade (Python)

Экран игры с рекордом и индикатором здоровья

Добавление системы сохранения и загрузки в игру значительно улучшает опыт игрока. Она позволяет сохранять прогресс, возобновлять сессии и экспериментировать с разными стратегиями, не теряя достижений. С помощью Arcade это сделать проще, чем кажется.

Что мы создаём

В примерах ниже мы реализуем простую игру, где игрок — синий прямоугольник — двигается влево и вправо. Затем добавим класс состояния, методы сохранения/загрузки в формате JSON, автосохранение, хранение рекордов и проверку данных.

Код в статье можно адаптировать под вашу игру. Все примеры показаны на Python 3 и библиотеке arcade.

Быстрая демонстрация: простая игра

Создайте файл simple-game.py и добавьте следующий код. Этот код создаёт окно, рисует игрока и позволяет двигать его стрелками влево/вправо.

import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
PLAYER_SPEED = 5
BLUE = arcade.color.BLUE

class GameWindow(arcade.Window):
    def __init__(self, width, height):
        super().__init__(width, height)
        # Позиция игрока по X
        self.player_x = width // 2

    def on_draw(self):
        arcade.start_render()
        arcade.draw_rectangle_filled(self.player_x, 50, 50, 50, BLUE)

    def update(self, delta_time):
        pass

    def on_key_press(self, key, modifiers):
        if key == arcade.key.LEFT:
            self.player_x -= PLAYER_SPEED
        elif key == arcade.key.RIGHT:
            self.player_x += PLAYER_SPEED

def main():
    window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT)
    arcade.run()

if __name__ == '__main__':
    main()

Результат: окно с синим прямоугольником, который вы двигаете стрелками.

Простая игра Arcade с игровым объектом игрока

Управление состоянием игры

Состояние игры — это данные, которые нужно сохранить (позиции, очки, уровни и т.д.). В простом примере это только координата игрока по X. Вынесем эти данные в отдельный класс GameState.

class GameState:
    def __init__(self):
        self.player_x = 0

Вынос состояния в отдельный объект упрощает сериализацию, тестирование и расширение набора сохраняемых полей.

Сохранение данных игры

Добавим метод save_game в класс окна. Для простоты используем JSON и файл save.json. Метод собирает словарь с нужными полями и сериализует его.

import json

class GameWindow(arcade.Window):
    def __init__(self, width, height):
        super().__init__(width, height)
        self.game_state = GameState()

    def save_game(self):
        data = {
            'player_x': self.game_state.player_x
        }
        with open('save.json', 'w', encoding='utf-8') as file:
            json.dump(data, file, ensure_ascii=False, indent=2)
            print('Saved:', data)

    def on_draw(self):
        arcade.start_render()
        arcade.draw_rectangle_filled(self.game_state.player_x, 50, 50, 50, BLUE)

    def update(self, delta_time):
        pass

    def on_key_press(self, key, modifiers):
        if key == arcade.key.LEFT:
            self.game_state.player_x -= PLAYER_SPEED
        elif key == arcade.key.RIGHT:
            self.game_state.player_x += PLAYER_SPEED
        elif key == arcade.key.S:
            self.save_game()

Нажмите клавишу S, чтобы сохранить текущую позицию игрока в save.json.

Загрузка данных игры

Добавим метод load_game, который читает save.json и восстанавливает состояние. Если файла нет, метод аккуратно игнорирует ошибку.

class GameWindow(arcade.Window):
    def __init__(self, width, height):
        super().__init__(width, height)
        self.game_state = GameState()
        self.load_game()

    def load_game(self):
        try:
            with open('save.json', 'r', encoding='utf-8') as file:
                data = json.load(file)
                self.game_state.player_x = data.get('player_x', self.game_state.player_x)
                print('Loaded:', data)
        except FileNotFoundError:
            # Файл отсутствует — продолжаем с дефолтным состоянием
            pass

    def on_key_press(self, key, modifiers):
        if key == arcade.key.L:
            self.load_game()

Теперь можно нажать L, чтобы загрузить состояние из файла.

Игровой экран Arcade с загруженными данными игры

Дополнительные возможности

Ниже — часто используемые расширения системы сохранения.

Сохранение рекордов

Полезно хранить рекорды вместе с состоянием. Добавим поле high_score и сохраним его вместе с координатой.

class GameWindow(arcade.Window):
    def __init__(self, width, height):
        super().__init__(width, height)
        self.player_x = width // 2
        self.high_score = 0

    def load_game(self):
        try:
            with open('save.json', 'r', encoding='utf-8') as file:
                data = json.load(file)
                print('Loaded:', data)
                self.player_x = data.get('player_x', self.player_x)
                self.high_score = data.get('high_score', self.high_score)
        except FileNotFoundError:
            pass

    def save_game(self):
        data = {
            'player_x': self.player_x,
            'high_score': self.high_score
        }
        with open('save.json', 'w', encoding='utf-8') as file:
            json.dump(data, file, ensure_ascii=False, indent=2)
            print('Saved:', data)

    def on_key_press(self, key, modifiers):
        if key == arcade.key.LEFT:
            self.player_x -= PLAYER_SPEED
        elif key == arcade.key.RIGHT:
            self.player_x += PLAYER_SPEED
            # Пример обновления рекорда
            self.high_score = max(self.high_score, int(self.player_x))

Пример выше показывает простую логику для демонстрации. В реальной игре рекорд рассчитывается иначе.

Автосохранение

Автосохранение снижает риск потери прогресса. Метод update проверяет прошедшее время и вызывает save_game.

import time

class GameWindow(arcade.Window):
    def __init__(self, width, height):
        super().__init__(width, height)
        self.game_state = GameState()
        # Сохранять каждые 6 секунд
        self.autosave_interval = 6
        self.last_save_time = time.time()

    def update(self, delta_time):
        current_time = time.time()
        time_diff = current_time - self.last_save_time
        if time_diff >= self.autosave_interval:
            self.save_game()
            print('Autosaved')
            self.last_save_time = current_time

Настройте интервал автосохранения под ваш тип игры.

Валидация данных

Важно проверять загруженные данные, чтобы избежать падений и читинга. Включим простые проверки в класс GameState.

class GameState:
    def __init__(self):
        self.player_x = 0

    def save_state(self):
        if self.is_valid_state():
            data = {'player_x': self.player_x}
            with open('save.json', 'w', encoding='utf-8') as file:
                json.dump(data, file, ensure_ascii=False, indent=2)

    def load_state(self):
        with open('save.json', 'r', encoding='utf-8') as file:
            data = json.load(file)
            if self.validate_loaded_data(data):
                self.player_x = data['player_x']
            else:
                print('Ошибка: некорректные данные')

    def is_valid_state(self):
        # Пример: позиция игрока должна быть числом
        return isinstance(self.player_x, (int, float))

    def validate_loaded_data(self, data):
        if not isinstance(data, dict):
            return False
        px = data.get('player_x')
        return isinstance(px, (int, float))

Валидация должна учитывать диапазоны, типы и логические зависимости (например, нельзя восстановить уровень, который ещё не открыт).

Лучшие практики

  • Шаблон сериализации: держите отдельный слой для преобразования состояния в JSON и обратно.
  • Версионирование: добавляйте поле save_version в файл, чтобы поддерживать обратную совместимость.
  • Бэкапы: перед перезаписью файла сохраняйте старую версию как save.json.bak.
  • Защита от одновременного доступа: в многопоточной игре используйте блокировки при записи.
  • Юнит-тесты: тестируйте save/load для каждого поля.

Шифрование чувствительных данных

Если сохраняете личные данные или покупки — шифруйте файл. Используйте проверенные библиотеки (например, cryptography) и храните ключи безопасно. Не изобретайте собственный криптографический протокол.

Обработка ошибок

При операциях ввода-вывода ловите FileNotFoundError, PermissionError и JSONDecodeError. Информируйте игрока дружелюбным сообщением и логируйте подробности для разработчика.

Тестирование

Покройте сценарии: сохранение/загрузка в разных состояниях, загрузка повреждённого файла, миграция версий, автосохранение при прерывании приложения.

Критерии приёмки

  • При сохранении создаётся файл save.json и содержит ожидаемые поля.
  • При загрузке состояние точно восстанавливается для тестовых случаев.
  • При отсутствии файла загрузка не вызывает исключений.
  • При повреждённом JSON приложение продолжает работать и показывает понятное сообщение.
  • Автосохранение запускается через заданный интервал.

Тестовые случаи и приёмочные сценарии

  1. Сохранение базового состояния
    • Действие: установить player_x = 123, нажать S.
    • Ожидаемо: в save.json поле player_x: 123.
  2. Загрузка существующего файла
    • Действие: нажать L при наличии корректного save.json.
    • Ожидаемо: позиция игрока обновлена, в консоли вывод Loaded.
  3. Отсутствие файла
    • Действие: удалить save.json, запустить загрузку.
    • Ожидаемо: приложение не падает, состояние остаётся по умолчанию.
  4. Повреждённый файл
    • Действие: записать в save.json текст “not json” и выполнить загрузку.
    • Ожидаемо: ловится JSONDecodeError, выводится сообщение об ошибке.
  5. Автосохранение
    • Действие: запустить игру и подождать интервал автосохранения.
    • Ожидаемо: файл save.json обновляется автоматически.

Альтернативные подходы

  • Бинарные файлы: быстрее и компактнее, но менее удобны для отладки. Подходит для больших состояний.
  • SQLite: удобно, если вам нужно хранить множество слотов сохранений или метаданные (даты, пользовательские профили).
  • Серверное хранение: сохраняйте прогресс в облаке для кросс-платформенной синхронизации. Учитывайте безопасность и приватность.

Когда простое решение не подходит

  • Если у вас много объектов и сложная сцена, flat JSON станет громоздким. Используйте базы данных или бинарную сериализацию.
  • Если важна защита от читинга (мультиплеер с прогрессом), местные файлы легко подделать — нужен серверный контроль.

Роль‑ориентированные чек-листы

Для разработчика:

  • Отделить модель состояния от UI и логики.
  • Добавить версионирование формата сохранения.
  • Реализовать бэкап перед перезаписью.
  • Написать юнит-тесты для сериализации и валидации.

Для QA:

  • Проверить сценарии из раздела тестовые случаи.
  • Сымитировать потерю доступа к файлам (PermissionError).
  • Проверить поведение при обновлении формата (миграция).

Mini-методология внедрения

  1. Выделите GameState и интерфейсы save/load.
  2. Реализуйте базовый JSON с минимальным набором полей.
  3. Добавьте ручные клавиши для проверки (S/L).
  4. Напишите тесты для save/load.
  5. Добавьте автосохранение и бэкапы.
  6. Введите версионирование и миграцию при изменениях схемы.

Совместимость и миграция

  • Добавляйте поле save_version: 1, 2 и т.д.
  • При загрузке выполняйте проверку версии и, при необходимости, функцию миграции, которая преобразует старый формат в новый.
  • Храните старые файлы как .bak перед изменением формата.

Пример простой миграции

def migrate(data):
    version = data.get('save_version', 1)
    if version == 1:
        # пример трансформации из версии 1 в 2
        data['save_version'] = 2
        data['new_field'] = default_value
    return data

Приватность и GDPR

  • Не сохраняйте персональные данные без согласия.
  • Если сохраняете email или идентификаторы, обеспечьте шифрование и доступ только по необходимости.
  • Позволяйте пользователю удалять локальные профили и связанные данные.

Риски и способы смягчения

  • Риск: потеря прогресса. Смягчение: автосохранение и бэкапы.
  • Риск: повреждённый файл. Смягчение: валидация и откат на .bak.
  • Риск: читинг. Смягчение: хранить критичные проверки на сервере.

Краткое резюме

Система сохранения и загрузки повышает удобство игры и удержание игроков. Для простых проектов достаточно JSON-файлов и небольшой модели состояния. Для более сложных проектов используйте базы данных, версионирование, шифрование и серверную синхронизацию. Всегда добавляйте валидацию, обработку ошибок и тесты.

Внедряя описанные практики, вы получите надёжную и расширяемую систему сохранения, которую легко адаптировать к росту проекта.

Важно: начинайте с простой реализации, а затем ступенчато добавляйте функции (рекорды, автосохранение, шифрование, миграции). Это уменьшает риск ошибок и упрощает тестирование.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

10 известных фотографов в Instagram для вдохновения
Фотография

10 известных фотографов в Instagram для вдохновения

MetaMask — как начать и обезопасить кошелёк
Криптовалюта

MetaMask — как начать и обезопасить кошелёк

Установка обновлений Windows 11 — 4 способа
Windows

Установка обновлений Windows 11 — 4 способа

Как блокировать СМС на Android
Android.

Как блокировать СМС на Android

Как поменять обои на Chromebook
Инструкции

Как поменять обои на Chromebook

Как изменить IP‑адрес на iPhone — два способа
iOS

Как изменить IP‑адрес на iPhone — два способа