Framework: GameConfig

Прочитал статью flashgameblogs.ru/blog/actionscript/751.html и решил показать свой конфиг.

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

Вот так выглядит файл конфига:
<config>
        <player>
                <spells type="array">
                        <item>
                                <type type="string">fireball</type>
                                <power type="int">80</power>
                                <delay type="number">1.2</delay>
                        </item>
                        <item>
                                <type type="string">airwave</type>
                                <power type="int">50</power>
                                <delay type="number">0.7</delay>
                        </item>
                </spells>
        </player>
        <enemies type="array">
                <item>
                        <name type="string">Sharick</name>
                        <type type="string">Dog</type>
                        <agressive type="number">0.6</agressive>
                        <health type="int">100</health>
                </item>
                <item>
                        <name type="string">Murka</name>
                        <type type="string">Cat</type>
                        <agressive type="number">0.4</agressive>
                        <health type="int">60</health>
                </item>
        </enemies>
</config>

Вложенность может быть любой.

GameConfig

elmortem.game.config.GameConfig
package elmortem.game.config {
        import elmortem.loaders.DataLoader;
        import flash.events.Event;
        import flash.events.EventDispatcher;
        import flash.events.IOErrorEvent;
        /**
         * ...
         * @author elmortem
         */
        public class GameConfig extends EventDispatcher {
                static public const EVENT_ERROR:String = "GameConfig.Error";
                static public const EVENT_LOADED:String = "GameConfig.Loaded";
                
                public var data:Object; // здесь будут лежать все данные из конфига
                public var loaded:Boolean; // готов ли конфиг к работе
                
                public function GameConfig() {
                        data = { };
                        loaded = false;
                }
                // загрузка данных из файла
                public function loadFromUrl(url:String):void {
                        DataLoader.loadData(url, null, onLoad, null, onError);
                }
                private function onLoad(e:Event):void {
                        loadFromString(e.target.data);
                }
                private function onError(e:IOErrorEvent):void {
                        trace(e);
                        dispatchEvent(new Event(EVENT_ERROR));
                }

                // загрузка данных из строки
                public function loadFromString(str:String):void {
                        var xml:XML;
                        try {
                                xml = new XML(str);
                        } catch (err:Error) {
                                dispatchEvent(new Event(EVENT_ERROR));
                                trace(err.message);
                                trace(str);
                                return;
                        }
                        
                        // парсим данные
                        data = parse(xml);
                        loaded = true;
                        
                        dispatchEvent(new Event(EVENT_LOADED));
                }
                
                // Самая главная функция!
                // разбор данных и генерация значений конфига
                private function parse(xml:XML):Object {
                        //trace(xml);
                        var i:int;
                        var c:XMLList;
                        var type:String = String(xml.@type);
                        
                        // Данные могут быть нескольких типов:
                        // Object, Array, int, Number и String

                        if(type == "" || type == "object" || type == "obj" || type == "o") {
                                c = xml.child("*");
                                var obj:Object = { };
                                for (i = 0; i < c.length(); i++) {
                                        obj[c[i].localName()] = parse(c[i]);
                                }
                                return obj;
                        } else if(type == "array" || type == "arr" || type == "a") {
                                c = xml.child("*");
                                var arr:Array = [];
                                // имя ноды (item) для массива не играет роли, но в конфиге можно использовать логически понятное имя
                                for (i = 0; i < c.length(); i++) {
                                        arr.push(parse(c[i]));
                                }
                                return arr;
                        } else if (type == "string" || type == "str" || type == "s") {
                                // тут индейская хитрость, чтобы получить строку в виде xml, если это требуется
                                c = xml.child("*");
                                if(c == null) {
                                        return String(xml);
                                } else {
                                        var s:String = "";
                                        for (i = 0; i < c.length(); i++) {
                                                s += String(c[i]);
                                        }
                                        return s;
                                }
                        } else if (type == "int" || type == "i") {
                                return int(xml);
                        } else if (type == "number" || type == "num" || type == "n") {
                                return Number(xml);
                        } else if (type == "boolean" || type == "bool") {
                                // гибкое задание булевых значений (:
                                return toBool(String(xml));
                        }
                        return null;
                }
                
                static public function toBool(d:*):Boolean {
                        if (d is String && d == "true") return true;
                        if (d is int && d == 1) return true;
                        return false;
                }
        }
}


Использование класса в игре. Создаём объект класса и подписываемся на событие GameConfig.EVENT_LOADED, чтобы знать, когда уже можно создавать игру.
config = new GameConfig();
config.addEventListener(GameConfig.EVENT_LOADED, onStartGame);


В период разработки игры используется код загрузки конфига из внешнего файла:
config.loadFromUrl("config.xml");


Когда игра уже готова, мы передаём в конфиг непосредственно текст конфига:
config.loadFromString("<config>...</config>");


В функции onStartGame уже можно обращаться к данным из конфига.
private function onStartGame(e:Event):void {
        trace(config.data.player.spells[0].power);
}


P.S. Идея с динамическим классом (из вышеупомянутой статьи) мне нравится, так что скорее всего переделаю свой конфиг так же, чтобы убрать лишнее звено «data» и обращаться напрямую к конфигу.
  • +6

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

0
Не помешал бы
  • Vel
  • Vel
+2
Используй кат. :)
+1
Кат потерял :)
+2
Я бы еще что-то в таком духе в класс добавил, чтоб вручную не копировать настройки:
public var USE_EXTERNAL_CONFIG:Boolean = true;
....
if(USE_EXTERNAL_CONFIG)
{
[Embed(source="config.xml",mimeType="application/octet-stream")]
private var ConfigClass:Class;
var configByteArray:ByteArray = new ConfigClass();
var configString:String = configByteArray.readUTFBytes(configByteArray.length);
config.loadFromString(configString);
}
0
только смысл флага наоборот перепутал…
0
В сам класс это не нужно добавлять, при использовании вполне можно сделать так, отличное решение.
0
На самом деле в идеале конечно добавить в сам класс, а флаг вынести в настройки :-))))
0
А если я хочу 2 файла настроек сделать?
0
А что мешает?
Ну кроме невозможности вынести эту настройку в файл в любом случае?

Вообще же, я предполагаю, что наиболее удобным будет вариант, когда таким образом берутся не только настройки, а вообще всё: графика, музыка и т.д. При работе с внешним художником (и не только) будет очень удобно дать ему возможность сразу пробовать нарисованное в игре.
+1
Можно и с графикой такое провернуть, но нам пока так извращаться не требовалось. Да и нет особой надобности именно в игре графику смотреть. К тому же вряд ли уважающий себя художник будет копаться в каких-то текстовых файлов настроек. (:
0
Кстати, я вот очень хочу 2 файла настроек сделать. Можно порезать?
0
Можно. «Во второй части». (:
+3
Меня прям подмывает написать огромный пост о командной работе
+1
Дык давай, я бы почитал с удовольствием.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.