Система диалогов, создаем подобие old School типа Fallout.
Запись от in4core размещена 07.05.2014 в 01:01
Привет !
Давай те сначала посмотрим на результат, о чем пойдет речь. Все схематично, никакого дизайна

И так, черные квадраты - это Персонажи нашей РПГ, по клику на которых мы открываем диалог и можем с ним работать.
1й квадрат - инфо персонаж, ничего полезного не делает, просто выдает инфу.
2й квадрат - персонаж, выбор веток диалога скрывает другие ветки.
4й квадрат - инфо персонаж
3й квадрат - персонаж, выбор веток диалога скрывает другие ветки 4го персонажа ( взаимодействие персонажей )
Диалог в XML
Код:
<?xml version="1.0" encoding="utf-8" ?> <data> <person name = "James"> <dialog title = "Эй бро, как делишки?"> <line title = "Здорова, все ок." lock = "false" id = "1"> <dialog title = "Куда напрвляешься?"> <line title = "Eду в СПБ." lock = "false" id = "10" > <dialog title = "Ну удачи бро."> <line title = "Бывай!" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "Хреново!" lock = "false" id = "2"> <dialog title = "Да не ссы ты, я сто раз так делал!!!"> <line title = "Ладно, но с тебя пивас!" lock = "false" id = "11"> <dialog title = "Заметано."> <line title = "Бывай!" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "Да пошел ты!" lock = "false" id = "3"> <dialog title = "Heh Fuck Himself!"> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </person> <person name = "Гнусный Орк"> <dialog title = "Бурлы мурлы, моя твоя не понимать...моя садись, твоя поехали..."> <line title = "Ты кто такой?" lock = "false" id = "1"> <dialog title = "Моя друг человека!"> <line title = "Я слежу за тобой..." lock = "false" id = "10" lockedid = "2"> <dialog title = "Да-да следить, следить..."> <line title = "[Про себя : ну и мурло же ты][Выход]" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "Выходи сражаться исчадие ада!" lock = "false" id = "2"> <dialog title = "[Орк сопит] Я боюсь тебя человека!"> <line title = "[достать незаметно нож] А ты не бойся, сейчас мы подружимся!" lock = "false" id = "11"> <dialog title = "Дружить, давай дружить!"> <line title = "[Напасть]" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Грозно зарычать на орка]" lock = "false" id = "3"> <dialog title = "[Орк испугался и убежал]"> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </person> <person name = "Гнусный Орк с пушкой из НКР"> <dialog title = "Бурлы мурлы, моя твоя не понимать...моя садись, твоя поехали..."> <line title = "Ты кто такой?" lock = "false" id = "1"> <dialog title = "Моя друг человека!"> <line title = "Я слежу за тобой..." lock = "false" id = "10"> <dialog title = "Да-да следить, следить..."> <line title = "[Про себя : ну и мурло же ты][Выход]" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "Выходи сражаться исчадие ада!" lock = "false" id = "2"> <dialog title = "[Орк сопит] Я боюсь тебя человека!"> <line title = "[достать НЕЗАМЕТНО бозар] А ты не бойся, сейчас мы подружимся!" lock = "false" id = "11" lockedperson = "Гнусный Орк с пушкой из НКР_connected" lockedpersonid = "11"> <dialog title = "Дружить, давай дружить!"> <line title = "[Напасть]" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Грозно зарычать на орка]" lock = "false" id = "3"> <dialog title = "[Орк испугался и убежал]"> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </person> <person name = "Гнусный Орк с пушкой из НКР_connected"> <dialog title = "Бурлы мурлы, моя твоя не понимать...моя садись, твоя поехали..."> <line title = "Ты кто такой?" lock = "false" id = "1"> <dialog title = "Моя друг человека!"> <line title = "Я слежу за тобой..." lock = "false" id = "10"> <dialog title = "Да-да следить, следить..."> <line title = "[Про себя : ну и мурло же ты][Выход]" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "Выходи сражаться исчадие ада!" lock = "false" id = "2"> <dialog title = "[Орк сопит] Я боюсь тебя человека!"> <line title = "[достать НЕЗАМЕТНО бозар] А ты не бойся, сейчас мы подружимся!" lock = "false" id = "11"> <dialog title = "Дружить, давай дружить!"> <line title = "[Напасть]" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Грозно зарычать на орка]" lock = "false" id = "3"> <dialog title = "[Орк испугался и убежал]"> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </person> </data>
Так же попрошу людей, кто хорошо разбирается в XMLnode - сообщить, если данные конструкции избыточны, так как сам я очень мало работал с такими запросами.
Поехали.
1. Общие понятия.
Прежде чем начать писать данный модуль, я пару дней в голове прокручивал все возможные удобства/неудобства/реализации и только потом присел за перо.
Первым делом создадим самый простейший класс холдер XML диалогов, пускай будет, при ините сразу пихнем туда весь листик.
package admin.models { /** * ... * @author in4core progression lab */ public class DialogsStatic { private static var _dialogs:XML = null; public static function insertDialogs(xml:XML):void { _dialogs = xml; } public static function getDialogsByPersonName(id:String):XMLList { return _dialogs.person.(@name == id) } } }
Далее создадим стандартный набор персонажа игры, а именно PersonView и PersonModel
View
package admin.views { import admin.models.PersonModel; import flash.display.Sprite; import flash.events.Event; /** * ... * @author in4core progression lab */ public class PersonView extends Sprite { private var _model:PersonModel; public function PersonView(model:PersonModel) { this._model = model; addEventListener(Event.ADDED_TO_STAGE , onAdded); } private function onAdded(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, onAdded); graphics.beginFill(0x0); graphics.drawRect(0, 0, 50, 50); } public function get model():PersonModel { return _model; } } }
Model
package admin.models { import flash.events.EventDispatcher; /** * ... * @author in4core progression lab */ public class PersonModel extends EventDispatcher { private var _name:String = "default"; private var _dialogues:XMLList = null; public function PersonModel() { } public function get isSpeakable():Boolean { if (_dialogues) return true; return false; } public function get name():String { return _name; } public function set name(value:String):void { _name = value; } public function get dialogues():XMLList { return _dialogues; } public function set dialogues(value:XMLList):void { _dialogues = value; } } }
Где нибудь на просторах BaseView потихоньку начнем собирать первого персонажа и структуру.
private function onAdded(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, onAdded); var xml:XML = new XML( new _xmls() ); DialogsStatic.insertDialogs( xml ); var model:PersonModel = new PersonModel(); model.name = "James"; model.dialogues = DialogsStatic.getDialogsByPersonName(model.name); var person:PersonView = new PersonView(model); person.addEventListener(MouseEvent.CLICK , createDialog); this.addChild(person); }
Теперь мы ясно представляем для чего был метод getDialogsByPersonName - а именно, чтобы ввести инъекцию тех диалогов в модельку, которые именно нужны ей, а не все подряд.
Ну чтож с общими понятиями структуры разобрались. Переходим к созданию ХМЛ первого персонажа ( инфо )
2. Создаем диалоги персонажей
Код:
<?xml version="1.0" encoding="utf-8" ?> <data> <person name = "James"> <dialog title = "Эй бро, как делишки?"> <line title = "Здорова, все ок." lock = "false" id = "1"> <dialog title = "Куда напрвляешься?"> <line title = "Eду в СПБ." lock = "false" id = "10" > <dialog title = "Ну удачи бро."> <line title = "Бывай!" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "Хреново!" lock = "false" id = "2"> <dialog title = "Да не ссы ты, я сто раз так делал!!!"> <line title = "Ладно, но с тебя пивас!" lock = "false" id = "11"> <dialog title = "Заметано."> <line title = "Бывай!" lock = "false" id = "0"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Назад]" lock = "false" id = "-1"/> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "Да пошел ты!" lock = "false" id = "3"> <dialog title = "Heh Fuck Himself!"> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </line> <line title = "[Выход]" lock = "false" id = "0"/> </dialog> </person> </data>
Для закрытия диалога я взял ID = 0 , для возврата на предыдущий диалог ID = -1. Далее вы можете регистрировать любые коды с -Х для действий, например -2 - начать бой из диалога, -3 получить квест, -4 отменить квест и т.п. Особо много тут тоже не предвидится.
Аттрибут lock - это самое главное во всей этой кухне. Данный аттрибут показывает - показывать строчку ответа или нет.
3. DialogView и Manager диалогов
И так все готово, чтобы показать на экран наш первый диалог, для этого нам потребуется вид диалога в первую очередь, ну , а чтобы работать с диалогами некий менеджер диалогов. Приступим.
package admin.views { import flash.display.Sprite; import flash.events.Event; /** * ... * @author in4core progression lab */ public class DialogView extends Sprite { public function DialogView() { addEventListener(Event.ADDED_TO_STAGE , onAdded); } private function onAdded(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, onAdded); graphics.beginFill(0xAA0000); graphics.drawRect(0, 0, 600 , 200); graphics.beginFill(0x880000); graphics.drawRect(1, 1, 598 , 198); } public function createDialog( list : XMLList , name : String ):void { this.removeChildren(); var header:DialogLine = new DialogLine(); header.create(name + " : " + list.@title , null); addChild(header); header.x = 10; header.y = 10; var lst:XMLList = list.line.(@lock == false); for (var i:int = 0; i < lst.length(); i++) { var line:DialogLine = new DialogLine(); line.create( (i+1) + ". " + lst[i].@title, lst[i].@id); addChild(line); line.y = line.height * i + header.height + 20; line.x = 10; } } public function destroyDialog():void { this.removeChildren(); } } }
package admin.views { import admin.utils.DialogsEvents; import flash.display.Sprite; import flash.events.MouseEvent; import flash.text.TextField; /** * ... * @author in4core progression lab */ public class DialogLine extends Sprite { private var _field:TextField = new TextField(); private var _id:String = null; public function DialogLine() { super(); addChild(this._field); } public function create(text:String , id:String):void { this._id = id; this._field.text = text; this._field.width = this._field.textWidth + 5; this._field.height = 30; this._field.selectable = false; if (id != null) { this._field.textColor = 0xFF0000; this._field.addEventListener(MouseEvent.CLICK , lineSelected); this._field.addEventListener(MouseEvent.MOUSE_OVER , onOver); this._field.addEventListener(MouseEvent.MOUSE_OUT , onOut); } else { this._field.textColor = 0xFFFFFF; } } private function onOut(e:MouseEvent):void { this._field.textColor = 0xFF0000; } private function onOver(e:MouseEvent):void { this._field.textColor = 0xFFFF00; } private function lineSelected(e:MouseEvent):void { this.dispatchEvent(new DialogsEvents(DialogsEvents.LINE_SELECTED , _id , true)); } public function get id():String { return this._id; } } }
Для создания заголовка так же используем DialogLine с id = null. Сделал как попроще, не плодя все новые и новые сущности. Ну , а далее пробегаясь по нодам ХМЛлиста - забиваем список. Примечаем, что при нажатии на вариант ответа мы отправляем событие LINE_SELECTED - наверх используя bubbles = true. Собственно говоря подписать на клик можно и внутри DialogView, но раз все события овера, аута тут - пускай и клик, как мышиное событие там же висит - нестрашно, на скорость явно не повлияет, ведь одновременно мы можем открыть только один диалог

Перейдем к созданию менеджера ( контроллера ) диалогов. Что он будет делать : работать с моделями персонажей по изменению диалог-листа, обрабатывать переходы между уровнями диалогов ( и т.п. в этом духе, в зависимости от ваших нужд, оценивать получение квестов например ).
На самом деле, все что описал до этого и так понятно, простейшие стандартные классы, вся кухня конечно же строится именно на данном менеджере, поэтому большая часть времени ушла конечно на его проектировку ( ну , а если честно, на вспоминание работы с XMLnode ).
Пожалуй сразу выложу весь листинг данного класса
package admin.controllers { import admin.models.PersonModel; import admin.utils.DialogsEvents; import admin.views.DialogView; import flash.events.EventDispatcher; /** * ... * @author in4core progression lab */ public class DialogManager extends EventDispatcher { private var _model:PersonModel; private var _lastDialog:Vector.<XMLList> = new Vector.<XMLList>(); private var _curDialog:XMLList; private var _personModels:Vector.<PersonModel> = new Vector.<PersonModel>(); public function DialogManager() { } public function addModel(model:PersonModel):void { this._personModels.push(model); } public function changeConnectedDialog(name:String , id:String) : void { for (var i:int = 0; i < this._personModels.length; i++) { if (this._personModels[i].name == name) { this._personModels[i].dialogues..*.line.(@id == id).@lock = true; } } } public function connect(model:PersonModel):void { this._model = model; this._lastDialog = new Vector.<XMLList>(); this._curDialog = model.dialogues.dialog; this._lastDialog.push(this._curDialog); } public function getCurrentDialog():XMLList { return this._curDialog; } public function logic(id:String):void { checkInLock(id); checkInConnected(id); if (id == "0") { this.dispatchEvent(new DialogsEvents(DialogsEvents.CLOSE_DIALOG)); } else if (id == "-1") { this._curDialog = this._lastDialog[this._lastDialog.length - 1]; this._lastDialog.pop(); this.dispatchEvent(new DialogsEvents(DialogsEvents.DIALOG_LEVEL_CHANGED)); } else { this._lastDialog.push(this._curDialog); this._curDialog = this._curDialog.line.(@id == id).dialog; this.dispatchEvent(new DialogsEvents(DialogsEvents.DIALOG_LEVEL_CHANGED)); } } private function checkInConnected(id:String):void { var search:String = this._model.dialogues..*.line.(@id == id).@lockedperson; var ids:String = this._model.dialogues..*.line.(@id == id).@lockedpersonid; if ( search != "" ) { dispatchEvent(new DialogsEvents(DialogsEvents.CONNECTED_PERSONS , [ search , ids ])); changeConnectedDialog(search , ids); } } private function checkInLock(id:String):void { var search:String = this._model.dialogues..*.line.(@id == id).@lockedid; if ( search != "" ) { this._model.dialogues..*.line.(@id == search).@lock = true; for (var i:int = 0 ; i < this._lastDialog.length; i++) { this._lastDialog[i]..*.line.(@id == search).@lock = true; } } } public function get model():PersonModel { return _model; } } }
Давай те дополним просторы BaseView , теперь он немного разросся.
package admin.views { import admin.controllers.DialogManager; import admin.models.BaseModel; import admin.models.DialogsStatic; import admin.models.PersonModel; import admin.utils.DialogsEvents; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; /** * ... * @author in4core progression lab */ public final class BaseView extends Sprite { [Embed(source = "../../../bin/dialogs.xml", mimeType="application/octet-stream")] private var _xmls:Class; private var _baseModel:BaseModel; private var _dview:DialogView = new DialogView(); private var _dmanager:DialogManager = new DialogManager(); public function BaseView() { addEventListener(Event.ADDED_TO_STAGE, onAdded); } public function set baseModel(model:BaseModel):void { this._baseModel = model; } private function onAdded(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, onAdded); var xml:XML = new XML( new _xmls() ); DialogsStatic.insertDialogs( xml ); addChild(this._dview); this._dview.y = stage.stageHeight - this._dview.height - 20; this._dview.x = stage.stageWidth / 2 - this._dview.width / 2; this._dview.addEventListener(DialogsEvents.LINE_SELECTED , onDialogLineSelected); this._dmanager.addEventListener(DialogsEvents.DIALOG_LEVEL_CHANGED , onUpdateDialogLevel); this._dmanager.addEventListener(DialogsEvents.CLOSE_DIALOG , onDialogCloses); this._dmanager.addEventListener(DialogsEvents.CONNECTED_PERSONS , onConnectedPersons); var model:PersonModel = new PersonModel(); model.name = "James"; model.dialogues = DialogsStatic.getDialogsByPersonName(model.name); var person:PersonView = new PersonView(model); person.addEventListener(MouseEvent.CLICK , createDialog); this.addChild(person); var model1:PersonModel = new PersonModel(); model1.name = "Гнусный Орк"; model1.dialogues = DialogsStatic.getDialogsByPersonName(model1.name); var person1:PersonView = new PersonView(model1); person1.addEventListener(MouseEvent.CLICK , createDialog); this.addChild(person1); person1.x = 100; var model2:PersonModel = new PersonModel(); model2.name = "Гнусный Орк с пушкой из НКР"; model2.dialogues = DialogsStatic.getDialogsByPersonName(model2.name); var person2:PersonView = new PersonView(model2); person2.addEventListener(MouseEvent.CLICK , createDialog); this.addChild(person2); person2.x = 200; var model3:PersonModel = new PersonModel(); model3.name = "Гнусный Орк с пушкой из НКР_connected"; model3.dialogues = DialogsStatic.getDialogsByPersonName(model3.name); var person3:PersonView = new PersonView(model3); person3.addEventListener(MouseEvent.CLICK , createDialog); this.addChild(person3); person3.x = 300; this._dmanager.addModel(model2); this._dmanager.addModel(model3); } private function onConnectedPersons(e:DialogsEvents):void { } private function onDialogCloses(e:DialogsEvents):void { this._dview.destroyDialog(); } private function onUpdateDialogLevel(e:DialogsEvents):void { this._dview.createDialog( this._dmanager.getCurrentDialog() , this._dmanager.model.name ); } private function onDialogLineSelected(e:DialogsEvents):void { this._dmanager.logic(e.params as String); } private function createDialog(e:MouseEvent):void { const person:PersonView = e.currentTarget as PersonView; const model:PersonModel = person.model; this._dmanager.connect(model); onUpdateDialogLevel(null); } } }
Перед тем, как закончить , хотелось бы сказать пару слов о сложных диалогах и взаимодействии. Выше я описал, что все взаимодействия , а так же скрытие веток и открытие происходят благодаря аттрибуту lock в XML. Когда мы хотим из одного диалога изменить часть диалога другого персонажа - я использовал следующие аттрибуты lockedperson и lockedpersonid, благодаря первому мы узнаем У КОГО будем менять, благодаря второму, узнаем ЧТО будем менять.
Безусловно чем больше будет аттрибутов в итоге, тем нагруженней у вас будет Manager - но это не так страшно, ведь в данной статье я уже показал диалогового функционала БОЛЬШЕ, чем - во всех современных играх вместе взятых ( шучу ). - но тем не менее, благодаря им, можно делать очень крутые взаимодействия.
Хотел показать еще пару примеров, например - получение квеста, отклонение квеста и заново его получение, но как оказалось и подумалось - смысла в этом нет, это всего лишь доп аттрибут и стандартная работа с lock, как в примере 2го-4го персонажа.
Если средним-начинающим что то непонятно, выложу полный листинг всех классов начиная с Main.
P.s. по коду прошу грозно не ругаться, статья вышла большая, если видите ошибку/недоделку/нонсенс - сообщите, культурно поправим.
P.p.s - про XMLnode - уже написал, прошу обратить на это особое внимание, если можно упростить записи, обязательно скажите, не молчите!
Всем спасибо за внимание!
Всего комментариев 34
Комментарии
![]() ![]() |
|
Мне кажется, что "создаем подобие old School типа Fallout" — вот эта часть названия лишняя.
|
![]() ![]() |
|
Hauts - может быть, но чем? Система диалогов точно такая же, скрытие, открытие веток, получение квестов, взаимодействие персонажей. Али я не прав?
|
![]() ![]() |
|
P.s.
Цитата:
К тому же old School, не old School
|
![]() ![]() |
|
Нет нет, для меня писать ничего не нужно. Мне просто интересно высказаться самому и послушать мнения других людей по этой теме, посмотреть варианты реализаций.
|
![]() ![]() |
|
Твой вопрос кстати не просто рассмотрен во втором ПЕРСОНАЖЕ - но еще и показан
![]() |
![]() ![]() |
|
Давайте вместо XML'а замутим YAML!
|
![]() ![]() |
|
Человекочитаемость у YAMLа выше, чем у XML'а с JSON'ом вместе взятых.
|
![]() ![]() |
|
Раздел News на офсайте yaml.org оставил чувство мертвечинки.
|
![]() ![]() |
|
А что, у http://www.xml.org/ выходят еженедельные обновления?
![]() |
![]() ![]() |
|
Цитата:
Система диалогов, создаем подобие old School типа Fallout.
![]() |
|
Обновил(-а) toFL 19.05.2014 в 19:22
|
![]() ![]() |
|
Цитата:
А что, у http://www.xml.org/ выходят еженедельные обновления?
|
![]() ![]() |
|
13 лет "свежатине".
|
![]() ![]() |
|
Цитата:
13 лет "свежатине".
|
![]() ![]() |
|
Цитата:
А хмлу сколько \? Это как деда с внуком сранивать!
|
![]() ![]() |
|
Я конечно хз, но 16 лет назад уже был as2 а xml тока появиолся? )))) Помоему языку больше лет явно, я бы дал 30. хотя кто знает. Откуда инфа ?
|
![]() ![]() |
|
Прочекал 1996 год. Странно, думал реально больше. Тогда сорр. закрыли
|
![]() ![]() |
|
Цитата:
16 лет назад уже был as2
|
![]() ![]() |
|
Очень интересная статья - начал писать код а потом заметил что нехватает import admin.utils.DialogsEvents; И толком ничего не протестил. Если можна, киньте классы на profik@rambler.ru или свяжитесь по скайпу profikus1982. Делаю неплохую рпг в стиле фаллаута, и фактически осталось систему квестов допилить на чем и застопорился.
ЗЫ: BaseModel тоже не наблюдаю |
|
Обновил(-а) Wells77 15.07.2015 в 22:57
|
Последние записи от in4core
- Система диалогов, создаем подобие old School типа Fallout. (07.05.2014)
- MVC в игорной индустрии (27.11.2012)
- Якорь мне .... ))) Или History API (06.11.2012)
- FSD - учим php/sql (28.06.2012)
- I4Logger - простой и компактный логгер (06.05.2012)