Утилитный класс для чтения настроек

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

  • все параметры и настройки локализованы в одном месте, а не хаотично разбросаны по классам проекта
  • возможность вносить изменения в настройки и смотреть изменения без перекомпиляции проекта

Хочу поделиться с сообществом небольшим утилитным классом для быстрого и удобного чтения настроек из xml-файла. Он позволяет имея файл настроек следующего вида —

<?xml version="1.0" encoding="UTF-8"?>
<settings>
        <app_settings>
                <property id="SCREEN_WIDTH" value="640" />
                <property id="SCREEN_HEIGHT" value="480" />
                <property id="FPS" value="50" />
                <property id="STAGE_SCALE_MODE" value="noScale" />
                <property id="PLAY_BUTTON_X" value="100" />
                ...................................................
        </app_settings>
</settings>


Легко читать его следующим образом —
var s:Settings = Settings.getInstance();
                        
stage.frameRate = s.FPS;
stage.scaleMode = s.STAGE_SCALE_MODE;
.......................................


Структура файла настроек может быть другой, для этого надо изменить выборку аттрибутов в методе getProperty. Т.е. класс по сути делает мэппинг своих фэйковых пропертей на одноименные аттрибуты файла настроек. Класс также можно легко модифицировать в релизной версии для чтения заэмбеженного xml-файла.

Собственно, сам класс —

package  
{
        import flash.events.ErrorEvent;
        import flash.events.Event;
        import flash.events.EventDispatcher;
        import flash.events.IEventDispatcher;
        import flash.net.URLLoader;
        import flash.net.URLRequest;
        import flash.utils.flash_proxy;
        import flash.utils.Proxy;

        public dynamic class Settings extends Proxy implements IEventDispatcher
        {
                
                public function Settings() 
                {
                        if (m_instance)
                                throw new Error("Use Settings.getInstance()");
                        
                        m_event_dispatcher = new EventDispatcher();
                        var urlRequest:URLRequest = new URLRequest(m_url);
                        m_url_loader = new URLLoader();
                        m_url_loader.addEventListener("complete", onDataLoaded)
                        m_url_loader.addEventListener("ioerror", onDataFailed)
                        m_url_loader.load(urlRequest);
                }
                
                public static function getInstance():Settings 
                {
                        if (m_instance == null) 
                        {
                                m_instance = new Settings();
                        }
                        
                        return m_instance;
                }               
                
                private function onDataLoaded(e:Event):void 
                {
                        m_data = XML(m_url_loader.data);
                        
                        dispatchEvent(new Event("SETTINGS_LOADED"));
                }               
                
                private function onDataFailed(e:ErrorEvent):void 
                {
                        trace(e);
                }
                
                override flash_proxy function getProperty(name:*):*
                {
                        var str:String = name; 

                        return m_data.app_settings.property.(@id == str).@value;
                }               
                
                public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void 
                {
                        m_event_dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
                }
                
                public function dispatchEvent(event:Event):Boolean 
                {
                        return m_event_dispatcher.dispatchEvent(event);
                }
                
                public function hasEventListener(type:String):Boolean 
                {
                        return m_event_dispatcher.hasEventListener(type);
                }       
                
                public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void 
                {
                        m_event_dispatcher.removeEventListener(type, listener, useCapture);
                }               
                
                public function willTrigger(type:String):Boolean 
                {
                        return m_event_dispatcher.willTrigger(type);
                }
                
                private static var m_instance:Settings;
                
                private var m_url:String = "settings.xml";    
                private var m_url_loader:URLLoader;
                private var m_data:XML;
                private var m_event_dispatcher:EventDispatcher;
        }
}


Буду рад если кому-то этот класс окажется полезным. Спасибо за внимание.
  • +8

Комментарии (14)

+3
Вот смотрю на современные тенденции в программировании и удивляюсь. Это не правильно. Так не должно быть.

Обычный ini формат в 100 раз лучше поддерживается вручную + элементарно парсится двумя сплитами:

SCREEN_HEIGHT=480
FPS=50
STAGE_SCALE_MODE=noScale
PLAY_BUTTON_X=100

Ну а в целом подход с вынесением настроек правильный, конечно. :)
+5
На самом деле всякие хранения настроек, данных итд во внешних файлах (а то и вынесение части логики в скрипты) это очень важно для жестко компилируемых проектов. Когда смена простых, но захардкоженных данных выливается в долгую пересборку проекта. В нашем же случае можно хардкодить все что хошь, главное чтобы было оно в понятном одном месте. Ведь если нормально настроил все — перекомпиляция это меньше секунды для самого навороченного проекта даже на слабом компе. А так — что xml лежил рядом, что as файл — какая разница? Я сначала тоже xml делал, что для уровней, что для дорог, что для текста. Но потом просто увидел, что это лишние промежуточные файлы. Так что плаг к флешу у меня сразу генерил нужный AS файл в нужное место. Не нужно никакого кода разборки xml итд.
На самом деле даже данные для баланса можно в игре прямо на ходу в консоли подтюнивать, а потом нажать педаль и сгенерить AS файл.
0
На счёт времени компиляции — это да. Но тут ещё, например, геймдизайнер, не имеющий у себя на компе инструментов для сборки, может «играться» параметрами и видеть сразу результат.
0
Да, а я уж привык что в 99% в нашем сегменте геймдиз==программер==одиночка-инди :)
0
В большинстве случаев — да, но процент, я думаю, всё-таки поменьше. :)
+1
INI слишком «плоский», он подходит только для хранения простых настроек.
XML универсальнее — им можно описать и уровни, и гуи, да все что угодно можно описать) Нужно извращаться, чтобы реализовать вложенность тегов и атрибуты в INI. Хотя вот в анриловском движке извратились все-таки :)
0
> INI слишком «плоский»…
Ну есть вариант задания разделов типа
[game]
a=1
[UI]
a=1
и т.д.

просто вместо A=1 писать
<DATASET><GAME><CONFIG><PROPERTIES><PUBLIC><PROPERTY id="A"><VALUE>1</VALUE><TYPE>Number</TYPE></PROPERTY></PUBLIC></PROPERTIES></CONFIG></GAME></DATASET>
это бесчеловечно! :)
+3
Обычный ини формат хорош, когда у тебя в конфиге четыре строчки. То есть для флеш игр, видимо, вполне подходит.

Что касается тенденций — загляни в конфиг софтины CruiseControl.NET — у нас на ней вертятся несколько проектов и параметров настроек там получается стопятьсот к кучей уровней вложенности и прочей чепухой. Юзать здесь ини было бы самоубийством.

XML хорош не только сам по себе для более-менее сложных данных, а больше скорее дополнительными плюшками — вроде трансформаций XSL и прочих.
0
Ну мы же говорим в контексте флэш игр, да? :)
Понятно, что ini формат не универсальное средство от всех болезней, просто то, что легче поддерживать и легче обрабатывать априори лучше.

А так можно вообще настройки в бинарник упаковать, на RAID 50 записать и использовать дополнительный софт для их изменения с встроенным контролем версий, желательно еще через веб. :) А вы XML, XML…
+5
Мне JSON показался наиболее удобным для хранения конфига, очень удобночитаем.
  • vinch
  • vinch
0
Поддерживаю. У JSON-a очень много плюсов. Еще пробовал среди прочего вариант с csv. Но json как по мне идеальнее всего для проекта в котором геймдизайнер не является программистом.
0
В 11-ом плеере появилась нативная поддержка JSON, что также играет в его пользу, кинул в parse() строку — на выходе экземпляр Object, красота.
0
мне этого очень не хватало, а левые классы использовать не хотелось, наконец-то =)
+1
Спасибо за информацию, — надо будет покопать в этом направлении.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.