论坛

由用户创建的信息 -Vampire-
02 August 2015 05:17
Всем привет!! Сегодня расскажу про клиентскую часть, а именно про модули которые я реализовал и про то как они между собой взаимодействуют. Ну, поехали.

На данный момент я смог выделить несколько самостоятельных, не знаю как это правильно назвать, сущностей что ли… Хотя нет, наверное правильней будет сказать "классов"…. По аналогии с ООП. Ведь эти объекты или сущности имеют своё уникальное поведение и внешний вид. Ну да что я всё вокруг да около… Вот же они:

1. Сцена (или локация). 3D сцена окружающей среды
2. Персонаж. Управление человечком
3. Объект на локации. Загрузка всяких домиков и настройка их поведением и отображением
4. Карта. Собсна гугломап на которой отображаются локации, маршруты и т.п
5. Миссии. Модуль управляет получением/выполнением миссий. Казалось бы…
6.Технологии. Модуль управляет всякой научной деятельностью и собиранием велосипедов.
7. Профиль. То , что касается аккаунта игрока
8. Камера. Скорее всего Deprecated. Код этого модуля перекочует в другие модули

Для каждого пункта я написал модуль. Кроме них есть еще два модуля, которые не относятся к игровой части. Это главный модуль приложения и модуль в котором реализована работа с сокетом. Давайте пойдём по порядку и начнем с самого важного.

Главный модуль приложения APPLICATION
Этот модуль координирует работу всех остальных модулей. Его основная задача - подгрузка модулей и установка слушателей на события. Так же этот модуль рисует GUI (ну главный поток же вродь =))))
Подгрузка модулей динамическая. Сделано потому, что мне было лень писать много тэгов скрипт, потому что это не тру, и потому, что мы никак не можем узнать какой скрипт (а в нашем случае модуль) загрузится раньше, а какой позже и это грозит нам проблемами. Посему я написал функцию импорта

var imported = [];
var _import = function(moduleName, callback){
	if(!imported[moduleName]){
		imported[moduleName] = {cb:[callback], status:STATUS_LOADING};
		var s = document.createElement('script');
		s.src = '/' + moduleName + '.js?t=' + date.getTime();
		s.onload = function(){
			imported[moduleName].status = STATUS_READY;
			for(var i = 0; i < imported[moduleName].cb.length; i++)
				imported[moduleName].cb[i](require(moduleName));
		}
		document.body.appendChild(s);
	}
	else{
		if(imported[moduleName].status === STATUS_LOADING){
			imported[moduleName].cb.push(callback);
		}
		else{
			callback(require(moduleName));
		}
	}
}


Как только код модуля будет загружен, вызовется колбэк, в котором мы можем попросить загрузить следующий модуль. Ну и так по цепочке, пока все необходимые модули не будут загружены. Так же после того как модуль загружен, с ним надо бы произвести какие-нибудь действия. Например подружить один модуль с другим. Дело в том, что в своих модулях я инклюдю только модули движка, и ни в один из моих модулей не передаётся экземпляр какого либо другого моего модуля. Но работать же модулям как-то надо совместно…. И вот это как раз и есть вторая важная задача модуля APPLICATION - установка обработчиков событий, генерируемыми модулями. Такой подход мне кажется вполне удобным и простым.

Давайте рассмотрим простой пример. Пользователь кликнул по иконке локации на карте с целью перейти в неё. После загрузки окружающей среды нам необходимо показать пользователю всё, что лежит, ходит, спит и ест на этой локации. За отображение карты отвечает модуль MAP, за отрисовку окружающей среды модуль SCENE, за объекты на локации - LOCATION_OBJECT. Что бы всё это зарабтало, нам необходимо установить слушателя на клик по иконке и слушателя на загрузку среды. Сделать это мы можем примерно вот таким образом

SCENE.onLocationLoaded(function(id){
    LOCATION_OBJECT.loadOdjects(id);
});
MAP.onLocationClick(function(_location){
    SCENE.load(_location.id);
});


В коде всех модулей, установка слушателей выглядит одинаково

var _onSomeEvent;
var onSomeEvent = function(f){
    _onSomeEvent = f;
}


Ничего, да простит меня Дин Винчестер, сверхъестественного, но сколько событий можно генерировааааааать…. И обработку события делать легко и приятно.

Второй неигровой модуль. SOCKET
Название говорит само за себя. Модуль отвечает за общение с сервером и чертовски прост. У него есть всего несколько методов:
1. bindEvents - на входе принимает массив объектов вида {eventName:'someEvent', callback:someFunction}; Аналогично главному модулю, назначает обработчиков на события сокета. Например: пользователю предложили заключить альянс и модуль PROFILE должен об этом как-то сигнализировать.
2. trigger - оболочка для socket.emit
Так же модуль обрабатывает зарезервированные события socket.io а-ля connect, disconnect и т.п.

"Игровые" модули
Подробно рассказывать о каждом модуле наверное стоит отдельно ибо каждый из них специфичен. Но кое-что общее у них всё же есть.
1. GUI. Каждый модуль представлен в приложении какой-нибудь кнопкой или целым виджетом. За генерацию своих кнопок отвечает модуль. За управление тоже. За рендер GUI отвечает модуль APPLICATION
2. Подписка на события. Каждый модуль сообщает модулю SOCKET на какие события и как он будет реагировать
3. В модулях инкапсулируется только то, что касается самого модуля.

Вот как-то так. Скоро ёще о чем-нибудь расскажу. Пока не придумал о чем… Рассказать есть много чего, но надо это как-то последовательно делать. А то я сам путаюсь .
02 August 2015 03:53
Так ведь есть же Статическая физика. Важно только помнить, что её переопределяет физика объекта, если включена опция Object Physics.
Ухты… а я думал, что эта галочка активирует всю физику. Спасибо, попробую.

Проект и правда очень серьезный. Хотя, если посмотреть, то при аккуратном подходе, тут всё может получиться довольно автоматизированно
Может =) Но пока еще четкой картины у меня нет. Тыкаюсь в попытках хоть как-то изучить блендер и изучаю движок. Нехватка знаний\опыта в моделировании ощущается остро. Так что всё получается через пробы и ошибки… Но я пытаюсь систематизировать как-то всё то, что у меня там в голове крутится. Отчасти этот "блог" веду из-за этого - чтоб хоть самому понять что я делаю и зачем

Вот пожалуй и продолжу это дело прям ща
31 July 2015 04:29
Как уже упоминалось выше, все локации игры существуют в реальном мире. И в игровом мире, локации располагаются относительно друг друга абсолютно так же как и тут. Ну а где же еще стырить цифровую копию нашей планеты, как не в гугломапсах?

Для того, чтобы это реализовать, я написал простенький скрипт, который умеет:
1. указывать центр локации. Это точка, которая на сцене будет являться началом системы координат. В БД сохраняются географические координаты точки
2. указывать границы локации. Это полигон абсолютно произвольной формы. Ну скучно же делать все локации одинаково-квадратными. В БД сохраняются границы полигона в формате JSON (массив географических координат)

После того, как центр и границы локации указаны на карте, хорошо бы создать и её 3D-модель. Начинаю я конечно же с ландшафта. Для этого я делаю скриншот полигона границ локации и кладу его в блендере как бэкграунд рабочей области. Вещь полезная для наглядности, но не решающая, ибо как ты ручками не растягивай вершины плоскости в соответствии с этим бэкграундом, идеально повторить его всё равно не получится. И в этом моменте на помощь нам приходят те самые географические координаты границ полигона и центра локации. Используя несложную функцию, код которой приведён ниже, мы можем получить смещение "ключевых" точек полигона относительно центра локации в метрической системе. Вот код

var  coords2distance = function(lat1, long1, lat2, long2) {
	//радиус Земли
	var R = 6372795;

	//перевод коордитат в радианы
	lat1 *= Math.PI / 180;
	lat2 *= Math.PI / 180;
	long1 *= Math.PI / 180;
	long2 *= Math.PI / 180;

	//вычисление косинусов и синусов широт и разницы долгот
	var cl1 = Math.cos(lat1);
	var cl2 = Math.cos(lat2);
	var sl1 = Math.sin(lat1);
	var sl2 = Math.sin(lat2);
	var delta = long2 - long1;
	var cdelta = Math.cos(delta);
	var sdelta = Math.sin(delta);

	//вычисления длины большого круга
	var y = Math.sqrt(Math.pow(cl2 * sdelta, 2) + Math.pow(cl1 * sl2 - sl1 * cl2 * cdelta, 2));
	var x = sl1 * sl2 + cl1 * cl2 * cdelta;

	var ad = Math.atan2(y, x);
	var dist = ad * R; //расстояние между двумя координатами в метрах

	return dist;
}


Эта функция принимает координаты двух точек и возвращает расстояние в метрах между ними. Таким образом мы можем включить в блендере метрическую систему координат, просабдивайдить плоскость и растащить нужные вершины по нужным нам местам, придав тем самым этой плоскости форму нашего полигона.
Примечание: граничащие локации должны иметь либо общие точки либо идти внахлёст. Иначе наши человечки рискуют провалиться под землю.

Теперь о модели локации
Так как все домики, машинки и прочие интерактивные объекты будут подгружаться на локацию динамически, всё что нам сейчас нужно сделать - это только создать окружающую среду. Для этого я добавляю на сцену:
1. несколько спикеров с шумами окружающей среды: птички там всякие, шум дождя, ветер и т.п
2. солнышко с dynamic intensity т.к. темнеть в игре должно тогда, когда и за окном
3. насколько источников ветра. Имя каждого из них соответствует направлению (S, SE, NW и т.д.). В последствии хотелось бы спрашивать у информера погоду и программно изменять силу ветра по каждому из направлений.
4. меш-эмиттер дождя/снега.
5. сферу с системой частиц для звёздного неба
6. портал. Это меш, который будет служить для перехода из одной локации на другую. Подробнее ниже.

Единственное с чем у меня сейчас возникают серьёзные проблемы, это с ландшафтом. Я хоть убейте не пойму, как сделать так, чтоб мои человечки ходили по земле а не по collision bounds меша, вися в воздухе или проваливаясь по пояс. Ну не моделер я от бога. Для того, чтобы придать ландшафту неровности, я использую модификатор displace,который изменяет геометрию плоскости. Всё круто и даже похоже на правду, но в результате этого collision bounds растягиваются по высоте в соответствии с верхней и нижней точкой новой геометрии меша. И увы

Перемещение между локациями
Сейчас у меня есть аж целых три способа попасть с одной локации на другую.
1.Клик по карте. Всё просто. Жмакнул - локация загрузилась
2. Портал. Порталы это области на локации, войдя в соприкосновение с которыми, игрок может перейти на локацию, с которой связан портал. В базе данных у каждой локации создано отношение с граничащими с ней локациями. В свойствах этого отношения (или ребра, раз уж мы используем графы) указываются координаты точки на сцене в которую необходимо этот портал поместить. После загрузки сцены мы находим все локации, на которые мы можем попасть с текущей, копируем меш портала, помещаем его в нужное место и вешаем на него сенсоры соприкосновения с персонажем
3. Третий способ пока только в проекте. Это автоматическая подгрузка/выгрузка локаций в зависимости от текущего расположения персонажа. То есть если мы подошли на 50 метров к западной границе текуще локации, то загрузить локацию, граничащую с запада. Штука классная, пока думаю

Есть еще оч классная штука, которая может очень сильно усложнить мне жизнь при написании сценария игры, но и одновременно с этим придать игре крутизны, кмк. Благодаря тому, что графы бывают ориентированные (а именно такой граф используется в игре), можно сделать так, что из локации А в локацию Б попасть можно, а из Б в А - нет. Хвала дискретной математике.

Вот такой вот рассказ на много букв получился. Спасибо за внимание, буду рад критике и идеям. В следующем посте расскажу о структуре клиентской части приложения и почему инкапсуляция - наше всё. Обещаю код
31 July 2015 02:54
Про Neo4j слышу впервые, надо будет ознакомиться
Обязательно ознакомьтесь!!!! Язык запросов Cypher практически не отличается от SQL. Есть REST, есть транзакции, есть индекс и еще много всяких наворотов прям из коробки… Ощем всё как у взрослых =)) Разработчики обещают, что она неимоверно шустрая (не смотря на то, что крутится всё это дело на яве). Реальных цифр пока привести не могу, не анализировал. Но самое главное это графы! Что может быть прекрасней графа?.Вот кратенький экскурс, наслаждайтесь, а я пока перейду к рассказу о подготовке локаций
29 July 2015 15:39
Итак, первое о чем хочу рассказать, это о лошадках, на которых едет серверная часть. Простите, за оффтоп. Этот момент не сильно касается движка, но считаю, что рассказать об этом всё же стоит.

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

Представим ситуацию. Наш игрок\пользователь\персонаж знает о существовании 5 локаций, имеет в рюкзаке 4 бутерброда с колбасой, не очень сильно дружит с другим игроком и в данный момент выполняет 4 миссии. Для того, чтобы отобразить всё это дело, при использовании MySQL (или иной реляционной БД) нам потребуется 4 запроса с джойнами. Не так много, но я параноик. Neo4j же позволяет выбрать все эти данные за один запрос. А всё потому, что Neo4j - графовая база данных.

Второй аспект - при написании кода, мы не знаем из каких таблиц нам нужно будет на этапе выполнения программы выбирать данные. Я говорю об отношениях. Допустим, мы на локации встречаем человечка, который даёт нам задание и обещает, что если мы это сделаем, то он нам подарит какую-нибудь плюшку. А эта плюшка может быть как новой локацией, новым пистолетом или новым навыком. А может быть так, что наградой буду все эти плюшки сразу. Что локации, что пистолеты, что навыки - сущности с абсолютно разным набором свойств и абсолютно разным набором действий, которые с ними можно произвести. И хранить экземпляры этих сущностей по фен-шую надо бы в разных таблицах. И где-то бы пришлось писать из какой таблицы (или таблиц) нам нужно достать плюшку и подарить её игроку. Опять не комильфо, опять лишние запросы. В Neo4j эта задача решается вытаскиванием всех узлов, связанных ребром с определённой меткой. Всё красиво, элегантно и в один запрос.

И в процессе разработки этой игры, я всё больше и больше влюбляюсь в эту бд.

Сервер приложения

В качестве сервера приложения выступает nodejs с подключенным модулем soсket.io. Выбор на него пал по нескольким причинам
Во-первых - многозадачность и один программный поток.
Во-вторых - событийная модель
В-третьих - javascript. Последнее время я очень сильно полюбил js и полючаю эстетическое наслаждение когда пишу на нём. Даже jquery уже не так сильно хочется использовать. И наверное процентов на 80 это благодаря вам
И наверное самая основная причина, по которой я выбрал nodejs - realtime. Игра предполагается как многопользовательская. Я не хочу по интервалу слать ajax-запросы на пхп-скрипт и проверять что изменилось. Да и всё равно временная погрешность будет. Да и если это не realtime< то это всего лишь еще одна браузерная игра, а не полноценная десктопная игра в браузере.

Вот как-то так. В следующем посте расскажу как готовлю локации в соответствии с нашей реальностью
29 July 2015 14:17
Да, в идеале весь мир. Сейчас на данный момент подгружается локация и все объекты на ней. При переходе на другую локацию, текущая локация выгружается и подгружается новая. Но я думаю, что такой подход будет использоваться только на этапе разработки, чтобы проще было учиться и тестировать.

Но на продакшне у игрока скорее всего будет "радар" - некий круг, который перемещается вместе с игроком. А так расположение абсолютно всех объектов можно выразить как в географических координатах, так и в координатах сцены, то фактически на сцене будет присутствовать только точ, что игрок "видит", то есть то, что попало в область радара
29 July 2015 13:42
Интро
Когда-то давно начал я играть в ночные городские игры aka Дозор, Энкаунтер и т.п. Суть игры - разгадать загадку, понять куда приехать (всякие там забросы, недострои и т.п.) и найти на локации все спрятанные там авторами игры коды. И так несколько уровней. Штука знаете ли заразная и можно уйти в неё с головой. И как-то однажды родилась у меня мысль портировать эту игровую реальность в браузер. Со временем в этой идее от ночных игр ничего практически не осталось и превратилось это дело в РПГ

Суть

Легенда не нова. Как обычно постапокалипсис, анархия и прочие прелести Fallout и подобных ему. В игре ходим человечком, встречаем других человечков, получаем и выполняем миссии, открываем новые локации, находим всякие плюшки, строим домики, воюем, торгуем и все остальные прелести реальной жизни. Виртуальный мир игры тесно связан с нашим. Локации вполне реальны, т.е. есть возможность в игре попасть в свой родной двор или даже квартиру и посмотреть что с ней стало спустя N лет.

Платформа
BackEnd nodejs+socket.io, neo4j
FrontEnd blend4web, Google Maps

Резюме
Надеюсь, что на этом проекте я познаю все тонкости замечательного blend4web, ибо искренне считаю что будущее интернета - трёхмерный интернет. Судя по всему в игре будут задействованы все возможности движка.
Буду тут писать что сделал, как сделал и как это работает/не работает, вдруг кто подскажет советом, а кто-то может и сам чему научится. А если уж кто и присоединиться захочет, то это прям вообще праздник. Делается это все пока на локальной машине. Как только разверну в сети обязательно выложу ссыль. Вот
29 July 2015 10:42
Уиииииии! Заработало!!! Спасибо! С меня пиво
29 July 2015 08:22
Спасибо, начала проигрываться анимация, но человечек по-прежнему шагает/дрыгается на месте . Влияет ли на это тот факт, что человечек был подгружен на сцену динамически? Просто создаётся впечатление, что человечек и рад бы переместиться, но ему что-то мешает. Выглядит это вот так

ПЫ.СЫ. Документация кажется не поправилась
28 July 2015 11:45
Добрый день.

В каком состоянии находится модуль npc_ai? В документации описано всего три метода. Два из них enable_enimation и disable_animation, если я всё правильно понял, запускают npc и останавливают соответственно. а вот на третий метод npc_ai(graph) консоль ругается мол нет такого метода.

Следующий код подтвердил это, сказав однако, что есть метод new_event_track

var npc_ai = require('npc_ai');
console.log(npc_ai);


Окей, уговорил, так и быть буду использовать его. Но по-прежнему возникает несколько вопросов. Модель персонажа выглядит следующим образом

-character_collider:куб, на котором выставлена галочка character, actor и динамический тип физики
–metarig: арматура. на ней анимация персонажа
–Soldier:геометрия персонажа со всякими материалами, текстурами и модификатором armature

что куда указывать?


                        var path = [
				[0,0,0],
				[10,0,0],
				[20,0,0],
				[30,0,0],
				[30,0,10],
				[30,0,20],
				[30,0,30],
			];

			npc_ai.new_event_track({
					path:path, 
					delay:5, 
					actions:что сюда и в каком виде?,
					obj:что сюда? , 
					rig:rig, // Арматура?
					collider:character_collider,//Верно?   
					empty:что это?,
					speed:3, 
					rot_speed:1.5, 
					random:false, 
					type:npc_ai.NT_WALKING
				});
			
			npc_ai.enable_animation();

свойство obj. :в документациинаписано Animated object ID.. В модели анимация на арматуре. указывать арматуру? Тогда зачем свойство rig? и что должно быть в empty?

Управлять персонажем ручками получается, он шевелится и анимация проигрывается. Но максимум, чего у меня получилось добиться при использовании нпц, так это дрыганья на месте. Неправильно указал path? и в каком виде должен быть массив actions?

Спасибо