05 июля 2010

Ведение лога в играх.


Всем привет. Решил я немного наполнить журнал всякой писаниной по поводу программирования и всего такого. Но здесь меня поджидала сложность: с одной стороны текст хочется сделать интересным, а код прозрачным; с другой - у меня уже довольно много самописных модулей, от которых не хочется отказываться при написании демок или при разъяснении работы тех или иных хитростей программирования. Потому, чтобы всем было просто и уютно, я решил сначала начать с маленьких модулей, расписать их назначение и работу, а затем уже с чистой совестью подключать при необходимости в демонстрационных программах не боясь вопросов "а что это за модуль ты подключил?" Итак, начну с самого простого - ведения лога в играх. Что это?


По-простому: файл в который программа записывает информацию о своей работе. Зачем это нужно? Не знаю, бывал ли у вас случай (если нет - вам в какой-то степени повезло) слышать фразу: "твоя программа у меня не запустилась, вывелось сообщение что-то вроде Access violation" - очень и очень неприятно. В такие моменты чувствуешь свое бессилие - ведь по такой информации определить почему программа "упала" практически невозможно. Начинаешь задавать вопросы что-то вроде "а в какой момент? что при этом нажимал?" и т.д. Как вы все понимаете - юзер меньше всего знает, почему программа не работает и что вообще она делала в этот момент. Грамотным в такой момент будет ответ вроде: "пришли мне лог-файл, лежащий в папке трам-пам-пам".
Когда-то давно и я сделал для себя модуль, умеющий сохранять всякую информацию на диск.
Его можно найти по ссылке.
В этом  модуле вы найдете один-единственный класс TLogManager:

TLogManager = class
  protected
    fFileName: String;
    fLogStrings: TStringList;
    fLastLogString: Integer;

    fLastSaveTime: Single;
    fReSaveTime: Single;
    Procedure SaveLogToStream(aFileStream: TStream);
    Procedure ReCreateFile;
  public
    Procedure SaveLogToFile;
    Procedure AddStringToLog(aString: String);
    Procedure DropStringToLog(aString: String);
    Procedure AddTimeToLog;
    Procedure DoProgress(const DeltaTime: Single);
    Constructor Create(const aFileName: String; const aReCreateFile: Boolean);
    Destructor Destroy; override;
  end; 
 
и три функции:
 
  Procedure Drop_StringToLog(aString: String);
  Procedure Add_StringToLog(aString: String);
  Procedure Set_LogManager(aLogManager: TLogManager);

Как это все работает? Очень просто!
Первым дело необходимо создать основной объект, который будет заниматься логом:

fLog := TLogManager.Create('log.txt', True); 
 
Думаю, здесь все ясно. Мы создаем объект класса TLogManager, указываем файл log.txt в качестве файла для записи и выставляем флаг пересоздания файла (а не дописывания в конец) в значение "правда".
Теперь осталось установить этот объект в качестве основного объекта логов:

Set_LogManager(fLog)

Все! Настройка закончена. 
Также важно позаботиться о регулярном вызове метода fLog.DoProgress(DeltaTime). Для чего? Штука в том, что доступ к файлам на жестком диске не всегда быстрое дело. Поэтому в данном случае используется следующая схема: сначала весь текст лога хранится в оперативной памяти, и только изредка (сейчас в коде жестко прописаны 30 секунд) вся накопившаяся информация записывается в файл лога. 

Теперь из любого места программы можно вызвать:
  • Drop_StringToLog(), если мы хотим тут же записать данные в лог-файл (выполняется перед критическими секциями, в которых возможен "вылет программы")
  • Add_StringToLog(), если мы хотим отложить запись данных в лог.
Вот и всё. Просто, быстро и удобно. Не забудьте также уничтожить объект перед закрытием программы. Этот модуль я писал довольно давно и до сих пор им пользуюсь. Он более-менее безопасный: при отсутствие объекта для логирования информации, функции по добавлению информации просто не будут ничего выполнять.
Что же можно записывать в лог-файл? Да что угодно! ФПС вашей игры; уровни, в которые играл юзер в эту сессию; время прохождения каждого уровня; конфигурацию компьютера; отсутствие текстур/звуков/моделей игры; лицензионный ключ, используемый для игры и т.д!
Были у меня идеи и по созданию объекта в инициализационной секции, также витала идея насчет обработки сохранения информации по тикам внутреннего таймера - но все это по тем или иным причинам было откинуто. Возможно, когда-то наступит необходимость усовершенствования этого класса, но пока, вроде бы, все уютно. А вам как кажется?

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

Рад любым идеям, предложениям и пожеланиям.

    Сообщения, схожие по тематике: