Пользователь
0,0
рейтинг
12 ноября 2013 в 18:12

Разработка → Карта галактики на Three.js/WebGL


Доброго времени дня или ночи. В свободное время я занимаюсь разработкой игры на космическую тематику на Three.js/WebGL и задумал написать небольшую серию статей по некоторым компонентам игры, в этой статье мы поговорим о карте галактики. Рассказ пойдет уже привычным мне способом — по шагам.

Я не буду приводить код инициализации и подробности самого Three.js, в сети полно информации по этому поводу.
И наш первый шаг…

Шаг 1 — Черный-черный фон


Для начала нам надо сделать подложку. Делается это элементарно в одну строчку:
renderer.setClearColor(0x000000);

Это будет выглядеть так:

Очевидно это Прямоугольник Малевича и на этом мы первый шаг заканчиваем.
Ведь правда просто?

Шаг 2 — And the Sky Full of Stars


Прямоугольник Малевича — это просто замечательно, но надо всё таки добавить звезды.
Так как мы делаем карту галактики, нам нужна спираль похожая на галактику. Я выбрал логарифмическую спираль.
Сначала записываем все нужные нам переменные
//переменные для построения логарифмической спирали
var countStars = 20000;
var a = 1.1;
мar b = 0.17;
var windings = 3.7;
var tMax = 2.0 * Math.PI * windings;        
var drift = 0.3

Записываем алгоритм, который очень прост: проходимся в цикле и высчитываем координаты каждой звезды(формула в википедии) и немного рандомно их смещаем, чтобы было больше похоже на галактику.
//Строим логарифмическую спираль
for (var i = 0; i < countStars; i++) {
    //формула + рандомное смещение точек
    var t = tMax * Math.random();
    var x = a * Math.exp(b * t) * Math.cos(t);
    x = x + (drift*x*Math.random()) - (drift*x*Math.random());
    var y = a * Math.exp(b * t) * Math.sin(t);
    y = y + (drift*y*Math.random()) - (drift*y*Math.random())
    //Зеркально равномерно распределяем точки
    if (Math.random() > 0.5) {
        list.push({x:vec.x, y:vec.y});
    }
    else { //Отражение спирали
        list.push({x:-vec.x, y:-vec.y});
    }
}

Так как количество точек очень большое, их мы оформляем системой частиц, лагов будет куда меньше:
//геометрия
var geometry = new THREE.Geometry();
//Материал системы частиц
var material = new THREE.ParticleSystemMaterial({
      color: 0xeeeeee,
      size: 3
});
//Система частиц
var particleSystem = new THREE.ParticleSystem(
      geometry,
        material
);
//Добавляем звезды
for (var i = 0; i < list.length; i++) {
    addStar(list[i].x, list[i].y);  
}
scene.add(particleSystem);

Функция addStar на данном этапе:
var addStar = function(x, y) {
    var v = new THREE.Vector3();
    v.x = x * 10;
    v.y = y * 10;

    geometry.vertices.push(v);  
}

И у нас получилось…

Что-то в центре какая-то дырка, давайте поглядим поближе:

Отвратительно, но исправить не сложно, добавим два цикла.
Первым циклом генерим кольцо из точек:
for (var i = 0; i < 4000; i++) {
    var vec = {x:Math.sRandom(0.8, 1.7),y:0};
    var angle = Math.sRandom(0, Math.PI*2.5);
    vec = VectorRot(vec, angle);
     list.push({x:vec.x, y:vec.y});
}


Вторым циклом генерим круг из точек:
for (var i = 0; i < 4000; i++) {
    var vec = {x:Math.sRandom(0.001, 0.8),y:0};
    var angle = Math.sRandom(0, Math.PI*2.5);
    vec = VectorRot(vec, angle);
    list.push({x:vec.x, y:vec.y});
}


Отлично, у нас уже есть что-то похожее на галактику. Но нам нужны имена нашим звездам. У тебя %username% есть имя, а у звезды нет. Разве справедливо?

Шаг 3 — Звезда по имени %starname%


Ну давайте делать последовательно.
Нам нужно сделать функцию для генерирования названия. Нужен глобальный список звезд(координаты + название). Нужно модифицировать добавление звезд и включать туда генерирование название. Это по самому наличию названий. Также нужно функция для вывода названия звезды при событии mouseover. Проблема в том, что так как это система частиц просто так событие не повесить, значит нужно что-то придумать другое. Вариантов много, но я сделал следующее: нашел JS реализацию KDTree, и загнал в дерево все точки которые у нас есть(т.е. тот самый глобальный список), и написал в обработчике события mousemove следующее:
//класический способ перевода координат мыши в мировые координаты
var projector = new THREE.Projector();
var vector = new THREE.Vector3(
( e.pageX / window.innerWidth ) * 2 - 1,
- ( e.pageY / window.innerHeight ) * 2 + 1,
0.5 );

var pos = projector.unprojectVector( vector, e.data.self.camera );
//дальше пересоздаем отдельную сцену для названия
e.data.self.sceneNames = new THREE.Scene();
//Вытаскиваем из KDTree ближайшии звезды от позиции мыши
var items = e.data.self.tree.nearest({x:pos.x,y:pos.y}, 1, 100);
//Далее создаем собственно label
for (var i = 0; i < items.length; i++) {
    e.data.self.sceneNames.add(e.data.self.labelBasic(items[i][0].name,vector.x, vector.y, 60, "#f00"));
}

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


Шаг 4 — Вносим порядок в хаос


Итак, у нас уже есть галактика, названия звезд, мы можем их увидеть, но раз у нас карта, то нам нужно разбиение пространства. Если я скажу: «слухай, лети ка ты в систему TX-82 и купи мне кефира», то будет непонятно куда лететь, ибо а) не факт что система с названием TX-82 единственная, б) как найти систему среди over 20k звезд? в) не факт что кефир уже завезли.
Сделаем такое разбивку: есть квадранты, есть сектора. Вся галактика делится на 4*4=16 квадрантов, по 4 с каждой стороны. Каждый квадрант, в свою очередь, делиться на 4 сектора. Т.е. мы можем адресовать систему как квадрант #qX-qY — сектор (sx-sy) — система %starname%.
Делаем мы это банальными линиями, код опять же не привожу, он большой и не интересный — просто расчет координат начала и конца каждой линии. Кому интересно — добро пожаловать на гитхаб.
Результат, как вы могли догадаться, в шапке статьи находится. Но я приведу ещё одну картинку:

Только нужно ещё добавить обозначения — текст, вроде (1-1),(3-3),(2-3). Сетка есть, а обозначений нет. Добавляем.

Шаг 5 — Чип и Дейл


Спасем от непонимания, что это за числа выше на картинке. Или хотя бы попытаемся. Да. Две строчки HTML и CSS:
<span id="quad" style="position:absolute;left:100px;top:10px;color:white;font-family:Arial;font-size:19px">#x-y Quadrant</span>
<span id="sector" style="position:absolute;left:100px;top:30px;color:#555;font-family:Arial;font-size:19px">(sx-sy) Sector</span>


Шаг 6 — Где я?


И последнее что нам осталось — указать наше положении в галактики. Стукнул кирпич по голове, забыл где находишся. А нам ведь нужно сказать куда привезти кефир, что же нам делать? Открываем карту и спасибо технологиям:

Да, код такой:
//Добавить маркер
var addMarker = function(x, y) {
    //геометрия
    var g = new THREE.Geometry();
    //Материал системы частиц
    var m = new THREE.ParticleBasicMaterial({
          color: 0x550000,
          size: 35
    });
    for (var i = 0; i < 100; i++) {
        g.vertices.push({x:x,y:y});
    };
    
    //Система частиц
    var p = new THREE.ParticleSystem(
          g,
          m
    );

    this.sceneLabel.add(this.labelBasic(">>                ", x , y , 70, "#f00"));
    this.sceneLabel.add(this.labelBasic("                <<", x , y , 70, "#f00"));

    this.sceneLabel.add(this.labelBasic(this.points[this.here].name, x , y , 60, "#f00"));

    this.sceneLabel.add(p);
}

Заключение


Итак, мы сделали карту. Я не привел в статье многие вещи, например смещение карты по зажатой клавише, зум, не сильно детализировал работу с Three.js, на мой взгляд это вторично и не так интересно.
Гитхаб: github.com/MagistrAVSH/galmap
Демо работает в последних FF, Chrome, Opera. В IE11 работать будет плохо, вы не увидите надписей вообще, он криво поддерживает WebGL. magistravsh.github.io/galmap

На будущее есть идеи написать статьи про карту звездной системы, про генератор туманностей, генераторы различных объектов, и вообще на тему фантастического космоса :) Если вам это будет интересно — пишите.

Напоследок под спойлером ещё парочка скриншотов.
Скриншоты


MagistrAVSH Cats'A @Magistr_AVSH
карма
35,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • +1
    «в центре какую-то дырку» можно было обыграть как перемычку. Расскажите пожалуйста подробнее о самой игре и почему выбрали именно WebGL?
    • +1
      Ну хотелось браузерную игру, но не текстовую, а хоть с какой-то графикой. Выбор был между HTML 5 Canvas и WebGL. К сожалению производительности канвы мне не хватает, остается WebGL.
      А по самой игре пока ничего особенного не сказать. Был не очень удачный старт, так как было много багов, падал сервер постоянно, да и геймплея было мало. Сейчас работаю над обновлением и хочу к НГ запустить. А по смыслу кто-то дал хорошее определение «брутальные Космические Рейнджеры». Не знаю особо что рассказать.
  • 0
    Круто! Хотелось бы подробностей про саму игру. Может, у вас есть блог? И кстати, где записываться в тестеры?
    • 0
      Нет, блога у меня нет :) Опять же — игра в разработке, тот прототип что сейчас опубликован, не более чем прототип, и я совсем не хочу сюда давать ссылок, слишком он тормозной, только впечатление испортит.
      Как буду начинать второй старт(первый как я уже говорил был провальный), я думаю напишу ещё статью, где заодно попытаюсь разобрать свои ошибки, так как до этой разработки реального опыта в управлении проектами не было, только теоретический. И я допустил кучу ошибок.
      • 0
        Вы не ответили на последний вопрос.: Р
        • 0
          Не заметил) Как запуск приблизится напишу пост, где расскажу подробнее об игре и где и как тестить, так что кнопочка «Подписаться» поможет. Сейчас на игру лучше не смотреть, только впечатление портить.
  • 0
    Побуду немножно КО: сделать бы сетку масштабируемой (при зуме появляется более мелкая), а то в центральных секторах очень уж много звёзд.

    И звёздные системы подразумеваются все одинковые, или каждая звезда генерируется уникальной?
    • 0
      И звёздные системы подразумеваются все одинковые, или каждая звезда генерируется уникальной?

      Вообще здесь просто перечисляются точки-звезды как координаты + название, список передается просто параметром. Детализация об объектах звездной системы(планеты, луны, и т.п.) в отдельном экране.
  • 0
    Для космической игры буквенно-цифровые названия звездных систем несколько скучноваты. Очень интересный подход использовался при генерации названий планет в старой доброй игре Elite. Там был набор слогов: AL, LE, XE, GE, ZA, CE, BI, SO, US, ES, AR, MA, IN, DI, RE, A, ER, AT, EN, BE, RA, LA, VE, TI, ED, OR, QU, AN, TE, IS, RI, ON. Генерировалось несколько псевдослучайных чисел с заданным seed, и на их основе из некоторого количества слогов (от 1 до 4-5) генерировалось уникальное название планеты. Получались очень интересные и звучные названия. Например: Leanis, Zaisrage, Atri, Ceonria, Vele, Edorce, Rerave, Riaren, Uservete, Zazaus, Usqusois, Maquen…

    Можно расширить набор слогов, и тогда получатся названия вроде: Aferjefa, Ravaga, Kezu, Norode, Isid, Hiugarda, Zuviaz, Enzoko, Tiri, Fibaad…
    • 0
      Да, я знаю. У меня в игре есть условно две части пространства — изначальные 5-15 систем, которые считаются исследованными, и у них есть имена, таки как выше как раз таки. А вторая, большая часть пространства(98-99% я бы сказал) не исследовано, и у них как раз таки имена буквенно-цифирные. Примерно та же схема в EVE.
    • +1
      I hate you, RAXXLA.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.