Как-то поиграв в оффисе в hexbug, зародилась идея написать игрушку по схожим мотивам.
По текущему роду деятельности я веб разработчик и поэтому захотелось чтобы в игре использовался только HTML, JavaScript и CSS — средства знакомые каждому вебразработчику. Никакого вам flash или даже canvas. Звучит хардкорно, но на самом деле сейчас HTML + CSS3 это очень мощные и гибкие средства визуализации, а писать игровой код на JavaScript — одно удовольствие. Вдобавок захотелось чтобы игра была с сетевым мультиплеером, притом интерактивной — никаких там шашек, карточных игр, пошаговых стратегий, все должно быть в действии и движении.
Вот что получилось в итоге:
В статье я оставлю набор заметок возникших при написании прототипа игрушки, нацеленных больше на подход «как сделать попроще и побыстрее». Думаю статья может сгодиться как некое подспорье для новичков в этом увлекательном деле.
Задача игры — вырастить свою колонию жуков и уничтожить всех юнитов противника. Жуки хаотично бегают по игровому полю, а задача игрока — помогать им находить бонусы в виде различной еды и оказывать помощь своим юнитам, на которых было произведено нападение.
Бонусы в игре:
кекс — дает 5xp, при наборе 15xp жук размножается
яблоко — восстанавливает 50hp, если жук полностью здоров то добавляет 15 дополнительных hp
перец — увеличивает атаку на 5dm
желудь — дает 2xp и швыряется в ближайшего противника, при попадании наносит тройной урон
мухомор — дает 1хp и позволяет произвести ядовитый выстрел, при попадании наносит 1/2 урона и замедляет жертву
Играть могут от 2 до 4 человек. Можно также просто подключиться к серверу из разных вкладок браузера, и поиграть одному.
Попробовать поиграть можно здесь.
Исходники на github.
Архив с игрой.
HTML и CSS конечно весьма не шустрые в плане производительности, когда речь идет об отрисовке графики, требующейся в интерактивных играх. Но если наша цель написать прототип игрушки, то этот вариант вполне сойдет. В конечном итоге «узкие моменты» в виде отрисовки основной сцены игры можно в дальнейшем побыстрому перебросить на canvas.
Для работы с графикой в 2д игре нам понадобятся операции перемещения, вращения и маштабирования спрайтов.
Перемещаем спрайт устанавливая у него position: absolute и изменяя left и top
Для вращения спрайтов воспользуемся transform: rotate. А с помощью transform: origin можно задать ось вращения (по умолчанию она в центре спрайта).
Для маштабирования изменяем размреры спрайта с помощью свойств width и height, перед этим установив подходящее значение в background-size:
Для повышения производительности и соответственно плавности анимации можно заставить браузер использовать GPU для отрисовки анимаций. Для этого нужно работать со спрайтами как с трехмерными объектами. Теперь сделаем операции перемещения, вращения и маштабирования через translate3d, rotate3d и scale3d:
Всех этих операций вполне хватило чтобы собрать графику в игре из нескольких нарисованных в «пэйнте» спрайтов.
Помимо отрисовки игровых объектов, нужно также наладить их взаимодействие друг с другом.
В bugsarena все взаимодейсвие заключается в обработке столкновений спрайтов.
Так как планируется делать все максимально по простому, ограничимся школьной математикой.
Наверно одна из самых частых математических операций в играх — нахождение расстояния между двумя точками. По суте задача сводится к нахождению гипотенузы в треугольнике:
Получаем формулу:
Теперь благодаря этой простой формуле можно делать множество операций, таких как нахождения расстояния до объекта, нахождение самого ближайшего и самого удаленного объекта, нахождение объектов в заданном радиусе а так же обнаруживать столкновение объектов в форме круга.
Все объекты игры отрисовываются в достаточно небольшие спрайты размером 20х20, можно пренебречь их формой и расчитывать столкновения как-будто они все вписанны в окружность с диаметром 20. Тогда можно сказать что 2 объекта столкнулись когда растояние между их центрами меньше или равно сумме их радиусов.
И еще несколько заметок:
В нескольких словах игровую логику можно описать так: Есть объект класса «Game» описывающий игровой мир у которого есть массив объектов-наследников от класса «GameObject» — это все объекты игрового мира. Каждый игровой кадр Game проходится по всем игровым объектам и вызывает у каждого метод step. В методе step каждого объекта описывается что он должен сделать за этот кадр (переместиться, обработать столкновения, уничтожиться и тд.) Для реализации ООП в игре используется объект Class из Simple JavaScript Inheritance от John Resig, доработанный до поддержки миксинов и статических свойств.
Наверное один из самых удачных патернов для создания новых объектов в играх это использование фабричного метода. Суть в том что мы не будем напрямую через вызов new Создавать объекты, а воспользуемся методом который за нас это сделает. Фабричный метод избавит нас от возни с подключением нового объекта в игровой мир.
Например мы хотим создать объект класса Block включить его в игровой мир и расположить в заданном месте:
Код метода create:
Итак когда игра уже написанна хочется разнообразить ее несколькими игровыми картами. Создавать все игровые объекты кодом (вызывая метод за методом) очень утомительно и ненаглядно. Писать свой редактор карт займет достаточно много времени. Но есть простой способ — можно воспользоваться текстовым редактором или своей ide для наглядного создания следующим подходом:
Результат:
Сетевой код написан с использованием вебсокетов спомощью библиотеки socket.io, сервер игры написан на nodejs.
Сделать по простому реализацию интерактивной сетевой игры да и с условием что нам доступнен только протокол TCP та еще задачка.
Сейчас для таких игр используют быстрый протокол UDP который к сожалению недоступен через socket.io, правда если есть сильное желание можно посмотреть в сторону WebRTC. Важно чтобы игра шла плавно без рывков и была синхронизированна на всех клиентах. Сервер будет простой и будет заниматься только передачей сообщений клиентов, так как только их действия влияют на ход игры. Он не будет заниматься передачай состояний игровых объектов, и вобще ничего не будет знать об игровом мире, кроме состояния игры — ожидание игроков/идет игра
Всю временную ленту игры можно разбить на кадры. Клиенты посылают сообщения о своих действиях серверу, сервер накапливает эти сообщения и через определенное количество кадров рассылает накопленное клиетам. Это как некая вариация очень ускоренной пошаговой стратегии — всем игрокам дано всего несколько кадров чтобы сделать свой ход (отправить сообщения серверу). По истечении этих кадров сервер рассылает клиентам все действия за предыдущий ход, которые тут-же начинают воспроизводиться. В это же время игроки могут сделать новый ход.
Этот подход хорош тем что он прост и клиенты всегда знают в какой по счету кадр запускаются действия приходящие с сервера и могут спокойно продолжать игру до наступления этого кадра. Сервер же должен прислать действия клиентов за некоторое время до наступления этого ключевого кадра, чтобы клиенты не простаивали. Недостаток этого подхода — не слишком быстрая реакция на действия игроков, и какой-нибудь активный шутер было бы играть сложновато.
Можно задаться вопросом — если мы передаем только действия клиентов, то как синхронизировать поведение объектов основанное на случайности? Ведь различные бонусы появляются в совершенно случайных местах, но у всех клиентов это должны быть одни и теже места. Жуки бегают весьма хаотично, постоянно меняя направление своего бега, и приэтом весь этот «хаос» должен быть совершенно одинаковым и идти по одному и тому же сценарию у всех. Проблемму с синхронизацией такого поведения можно решить тем, чтобы везде где используются случайные величины, не использовать для этого Math.random, а использовать свой генератор псевдослучайных чисел (ГПСЧ). Суть в следующем — перед запуском игры сервер генерирует случайное число и передает его каждому присоединившемуся клиенту. С помощью этого числа клинет инициализирует ГПСЧ котрый на всех клиентах будет выдавать одинаковую последовательность псевдослучайных чисел. Простейшая реализация такого ГПСЧ — генератор парка-миллера
Реализация на js:
Использование:
Может немного не втему, но тоже полезная заметка. Когда сервер написан, неплохо бы запустить его на боевой машине в виде службы для постоянной работы. Опишу как это можно сделать на примере Ubuntu.
Переходим в /etc/init.d и создаем там шелл-скрипт с названием нашей службы, у меня будет bugsarena. Обращу внимание что блок начинающийся с «BEGIN INIT INFO» не просто коментарий, а настройки нашей службы и удалять его не стоит.
Делаем файл исполняемым.
Теперь можно воспользоваться командами
Также можно сделать чтобы игровой сервер стартовал при запуске системы выполнив
На последок просто необходимо напомнить об очень простой атаке свойственной для браузерных игр. Представим что у нас есть список игроков в каком-нибудь div'e. И к нам в игру заходит игрок с именем "<script>alert('В игру заходит Вася!')</script>". Его имя добавляется в div со списком игроков, и все клиенты получают назойливое сообщение alert'ом. И это еще цветочки. Через XSS уязвимость можно спокойно подгрузить любой скрипт с любого сайта. Так что не забываем об экранировании передаваемых с клиентов данных.
По текущему роду деятельности я веб разработчик и поэтому захотелось чтобы в игре использовался только HTML, JavaScript и CSS — средства знакомые каждому вебразработчику. Никакого вам flash или даже canvas. Звучит хардкорно, но на самом деле сейчас HTML + CSS3 это очень мощные и гибкие средства визуализации, а писать игровой код на JavaScript — одно удовольствие. Вдобавок захотелось чтобы игра была с сетевым мультиплеером, притом интерактивной — никаких там шашек, карточных игр, пошаговых стратегий, все должно быть в действии и движении.
Вот что получилось в итоге:
В статье я оставлю набор заметок возникших при написании прототипа игрушки, нацеленных больше на подход «как сделать попроще и побыстрее». Думаю статья может сгодиться как некое подспорье для новичков в этом увлекательном деле.
Геймплей
Задача игры — вырастить свою колонию жуков и уничтожить всех юнитов противника. Жуки хаотично бегают по игровому полю, а задача игрока — помогать им находить бонусы в виде различной еды и оказывать помощь своим юнитам, на которых было произведено нападение.
Бонусы в игре:
кекс — дает 5xp, при наборе 15xp жук размножается
яблоко — восстанавливает 50hp, если жук полностью здоров то добавляет 15 дополнительных hp
перец — увеличивает атаку на 5dm
желудь — дает 2xp и швыряется в ближайшего противника, при попадании наносит тройной урон
мухомор — дает 1хp и позволяет произвести ядовитый выстрел, при попадании наносит 1/2 урона и замедляет жертву
Играть могут от 2 до 4 человек. Можно также просто подключиться к серверу из разных вкладок браузера, и поиграть одному.
Попробовать поиграть можно здесь.
Исходники на github.
Архив с игрой.
Графика
HTML и CSS конечно весьма не шустрые в плане производительности, когда речь идет об отрисовке графики, требующейся в интерактивных играх. Но если наша цель написать прототип игрушки, то этот вариант вполне сойдет. В конечном итоге «узкие моменты» в виде отрисовки основной сцены игры можно в дальнейшем побыстрому перебросить на canvas.
Для работы с графикой в 2д игре нам понадобятся операции перемещения, вращения и маштабирования спрайтов.
Перемещаем спрайт устанавливая у него position: absolute и изменяя left и top
Для вращения спрайтов воспользуемся transform: rotate. А с помощью transform: origin можно задать ось вращения (по умолчанию она в центре спрайта).
Для маштабирования изменяем размреры спрайта с помощью свойств width и height, перед этим установив подходящее значение в background-size:
Аппаратное ускорение
Для повышения производительности и соответственно плавности анимации можно заставить браузер использовать GPU для отрисовки анимаций. Для этого нужно работать со спрайтами как с трехмерными объектами. Теперь сделаем операции перемещения, вращения и маштабирования через translate3d, rotate3d и scale3d:
Всех этих операций вполне хватило чтобы собрать графику в игре из нескольких нарисованных в «пэйнте» спрайтов.
Физика
Помимо отрисовки игровых объектов, нужно также наладить их взаимодействие друг с другом.
В bugsarena все взаимодейсвие заключается в обработке столкновений спрайтов.
Так как планируется делать все максимально по простому, ограничимся школьной математикой.
Наверно одна из самых частых математических операций в играх — нахождение расстояния между двумя точками. По суте задача сводится к нахождению гипотенузы в треугольнике:
Получаем формулу:
Теперь благодаря этой простой формуле можно делать множество операций, таких как нахождения расстояния до объекта, нахождение самого ближайшего и самого удаленного объекта, нахождение объектов в заданном радиусе а так же обнаруживать столкновение объектов в форме круга.
Все объекты игры отрисовываются в достаточно небольшие спрайты размером 20х20, можно пренебречь их формой и расчитывать столкновения как-будто они все вписанны в окружность с диаметром 20. Тогда можно сказать что 2 объекта столкнулись когда растояние между их центрами меньше или равно сумме их радиусов.
И еще несколько заметок:
- Для задания угловых значений используйте радианы, а не градусы. Все угловые значения из Math возвращаются именно в них. Напомню полный оборот равняется 2 * PI радиан
- Используйте понятие вектора для задания величин у которых есть направление. Даже положение спрайтов можно описывать вектором. Можно создать свой класс вектора или воспользоваться классом описанным в этой статье либо любым другим.
Для примера, вектором задается скорость объектов, так как она имеет величину и направление. В этом случае чтобы увеличить скорость в двое мы просто умножаем вектор на 2, а чтобы изменить скорость в обратное направление мы инвертируем вектор (умножаем на -1).
- Если в игре требуется сложная физика то можно посмотреть в сторону box2d-js. Эта библиотека позволит создать игровой мир с объектами различной формы, гравитацией, массой, инерцией, силой трения и прочими благами ньютоновской физики
Пример класса Вектор
// инициализация
function Vec (x_, y_) {
if (typeof x_ == 'object') {
this.setV(x_);
return;
}
this.x= typeof x_ == 'number' ? x_ : 0;
this.y= typeof y_ == 'number' ? y_ : 0;
}
Vec.prototype = {
// установка в 0
setZero: function() {
this.x = 0.0;
this.y = 0.0;
},
// установка значений x и y
set: function(x_, y_) {this.x=x_; this.y=y_;},
// установка значений из объекта
setV: function(v) {
this.x=v.x;
this.y=v.y;
},
// реверс вектора
negative: function(){
return new Vec(-this.x, -this.y);
},
// копия вектора
copy: function(){
return new Vec(this.x,this.y);
},
// сложение с вектором
add: function(v) {
this.x += v.x; this.y += v.y;
return this;
},
// вычетание вектора
mubtract: function(v) {
this.x -= v.x; this.y -= v.y;
return this;
},
// умножение на число
multiply: function(a) {
this.x *= a; this.y *= a;
return this;
},
// деление на число
div: function(a) {
this.x /= a; this.y /= a;
return this;
},
// получение длины вектора
length: function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
},
// нормализация вектора (приведение к вектору с длиной = 1)
normalize: function() {
var length = this.length();
if (length < Number.MIN_VALUE) {
return 0.0;
}
var invLength = 1.0 / length;
this.x *= invLength;
this.y *= invLength;
return length;
},
// получение угла вектора
angle: function () {
var x = this.x;
var y = this.y;
if (x == 0) {
return (y > 0) ? (3 * Math.PI) / 2 : Math.PI / 2;
}
var result = Math.atan(y/x);
result += Math.PI/2;
if (x < 0) result = result - Math.PI;
return result;
},
// получение растояния до другого вектора (полезно если вектором задается положение спрайта)
distanceTo: function (v) {
return Math.sqrt((v.x - this.x) * (v.x - this.x) + (v.y - this.y) * (v.y - this.y));
},
// получение вектора проведенного от вершины x,y данного вектора до вершины x,y другого вектора
vectorTo: function (v) {
return new Vec(v.x - this.x, v.y - this.y);
},
// поворот вектора на заданный угл
rotate: function (angle) {
var length = this.length();
this.x = Math.sin(angle) * length;
this.y = Math.cos(angle) * (-length);
return this;
}
};
Используемые паттерны разработки
В нескольких словах игровую логику можно описать так: Есть объект класса «Game» описывающий игровой мир у которого есть массив объектов-наследников от класса «GameObject» — это все объекты игрового мира. Каждый игровой кадр Game проходится по всем игровым объектам и вызывает у каждого метод step. В методе step каждого объекта описывается что он должен сделать за этот кадр (переместиться, обработать столкновения, уничтожиться и тд.) Для реализации ООП в игре используется объект Class из Simple JavaScript Inheritance от John Resig, доработанный до поддержки миксинов и статических свойств.
Наверное один из самых удачных патернов для создания новых объектов в играх это использование фабричного метода. Суть в том что мы не будем напрямую через вызов new Создавать объекты, а воспользуемся методом который за нас это сделает. Фабричный метод избавит нас от возни с подключением нового объекта в игровой мир.
Например мы хотим создать объект класса Block включить его в игровой мир и расположить в заданном месте:
game.create('Block', {x: 100, y: 150});
Код метода create:
create: function (objectName, params) {
// для удобства все классы доступные для создание через фабричный метод хранятся в Game.classes
// Создаем объект получая его класс из Game.classes
var object = new Game.classes[objectName](params);
// присваиваем ему уникальный идентефикатор
object.id = ++this.idx;
// задаем объекту ссылку на игровой мир
object.game = this;
// добавляем получившийся объект в массив игровых объектов
this.objects[object.id] = object;
// если объект может сталкиваться с другими объектами то дополнительно
// помещаем ссылку на него в соответствующий массив
if (object.isColliding) this.collidingObjects[object.id] = object;
// сообщаем объекту что он полностью подключен к игровому миру
// с помощью вызова метода birth, в котором он может завершить инициализацию
object.birth();
// возвращаем готовый объект
return object;
},
Создание игровых карт
Итак когда игра уже написанна хочется разнообразить ее несколькими игровыми картами. Создавать все игровые объекты кодом (вызывая метод за методом) очень утомительно и ненаглядно. Писать свой редактор карт займет достаточно много времени. Но есть простой способ — можно воспользоваться текстовым редактором или своей ide для наглядного создания следующим подходом:
!function () {
var WIDTH = 20;
var HEIGHT = 12;
var B = 'Block';
var P = 'Bonus';
var MAP = [
, , , , , , , , , , , , , , , , , , , ,
, B, , B, , B, B, B, , B, , , , B, , , , B, B, B,
, B, , B, , B, , , , B, , , , B, , , , B, , B,
, B, B, B, , B, B, B, , B, , , , B, , , , B, , B,
, B, , B, , B, , , , B, , , , B, , , , B, , B,
, B, , B, , B, B, B, , B, B, B, , B, B, B, , B, B, B,
, , , , , , , , , , , , , , , , , , , ,
, B, , B, , B, B, B, , B, B, B, , B, B, B, , , , ,
, B, , B, , B, , B, , B, , B, , B, , B, , , , ,
, B, B, B, , B, B, B, , B, B, , , B, B, , , , , ,
, B, , B, , B, , B, , B, , B, , B, , B, , , , ,
, B, , B, , B, , B, , B, B, B, , B, , B, , P, , ,
];
Game.maps['Hello'] = Game.Map.extend({
build: function () {
var blockSize = 20;
for (var i = 0; i < HEIGHT; i++) {
for (var j = 0; j < WIDTH; j++) {
var index = WIDTH * i + j;
if (MAP[index]) this.game.create(MAP[index], {x: blockSize * j, y: blockSize * i});
}
}
}
})
}();
Результат:
Сетевой код
Сетевой код написан с использованием вебсокетов спомощью библиотеки socket.io, сервер игры написан на nodejs.
Сделать по простому реализацию интерактивной сетевой игры да и с условием что нам доступнен только протокол TCP та еще задачка.
Сейчас для таких игр используют быстрый протокол UDP который к сожалению недоступен через socket.io, правда если есть сильное желание можно посмотреть в сторону WebRTC. Важно чтобы игра шла плавно без рывков и была синхронизированна на всех клиентах. Сервер будет простой и будет заниматься только передачей сообщений клиентов, так как только их действия влияют на ход игры. Он не будет заниматься передачай состояний игровых объектов, и вобще ничего не будет знать об игровом мире, кроме состояния игры — ожидание игроков/идет игра
Всю временную ленту игры можно разбить на кадры. Клиенты посылают сообщения о своих действиях серверу, сервер накапливает эти сообщения и через определенное количество кадров рассылает накопленное клиетам. Это как некая вариация очень ускоренной пошаговой стратегии — всем игрокам дано всего несколько кадров чтобы сделать свой ход (отправить сообщения серверу). По истечении этих кадров сервер рассылает клиентам все действия за предыдущий ход, которые тут-же начинают воспроизводиться. В это же время игроки могут сделать новый ход.
Этот подход хорош тем что он прост и клиенты всегда знают в какой по счету кадр запускаются действия приходящие с сервера и могут спокойно продолжать игру до наступления этого кадра. Сервер же должен прислать действия клиентов за некоторое время до наступления этого ключевого кадра, чтобы клиенты не простаивали. Недостаток этого подхода — не слишком быстрая реакция на действия игроков, и какой-нибудь активный шутер было бы играть сложновато.
Можно задаться вопросом — если мы передаем только действия клиентов, то как синхронизировать поведение объектов основанное на случайности? Ведь различные бонусы появляются в совершенно случайных местах, но у всех клиентов это должны быть одни и теже места. Жуки бегают весьма хаотично, постоянно меняя направление своего бега, и приэтом весь этот «хаос» должен быть совершенно одинаковым и идти по одному и тому же сценарию у всех. Проблемму с синхронизацией такого поведения можно решить тем, чтобы везде где используются случайные величины, не использовать для этого Math.random, а использовать свой генератор псевдослучайных чисел (ГПСЧ). Суть в следующем — перед запуском игры сервер генерирует случайное число и передает его каждому присоединившемуся клиенту. С помощью этого числа клинет инициализирует ГПСЧ котрый на всех клиентах будет выдавать одинаковую последовательность псевдослучайных чисел. Простейшая реализация такого ГПСЧ — генератор парка-миллера
Реализация на js:
var ParkMillerGenerator = function (initializer) {
this.a = 16807;
this.m = 2147483647;
this.val = initializer || Math.round(2147483647 / 3);
}
ParkMillerGenerator.prototype = {
next: function () {
this.val = (this.a * this.val) % this.m;
return (this.val / 1000000) % 1;
}
}
Использование:
var initializer = 333; // задаеем инициализирующее число, у всех клиентов оно должно быть одинаковое
var gen = new ParkMillerGenerator(initializer); // создаем ГПСЧ
gen.next(); // 0.5967310000000001
gen.next(); // 0.46109599999999773
gen.next(); // 0.07891199999994569;
Делаем сервис из nodejs приложения
Может немного не втему, но тоже полезная заметка. Когда сервер написан, неплохо бы запустить его на боевой машине в виде службы для постоянной работы. Опишу как это можно сделать на примере Ubuntu.
Переходим в /etc/init.d и создаем там шелл-скрипт с названием нашей службы, у меня будет bugsarena. Обращу внимание что блок начинающийся с «BEGIN INIT INFO» не просто коментарий, а настройки нашей службы и удалять его не стоит.
#!/bin/sh
### BEGIN INIT INFO
# Provides: bugsarena
# Required-Start: $local_fs $remote_fs $network $syslog
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the bugsarena servers
# Description: starts the bugsarena servers
### END INIT INFO
# задаем пути и параметры к исполняемым файлам (нужно указать свои)
NODE=/usr/bin/node
DAEMON_SERVER=/home/me/projects/bugs-arena/server/server.js
SERVER_PARAMS="name=Arena-Dogfight map=Dogfight port=8090"
NAME=bugsarena
DESC="bugsarena servers"
# сервис должен принимать 3 команды - start, stop и restart.
# опишем обработчики этих комманд
start() {
# запускаем nodejs приложение в качестве демона и сохраняем его pid в файл
start-stop-daemon --start --make-pidfile --background --pidfile /var/run/$NAME-server.pid \
--exec $NODE -- $DAEMON_SERVER $SERVER_PARAMS
}
stop() {
# останавливаем nodejs приложение
echo -n "Stopping $DESC: "
start-stop-daemon --stop --quiet --pidfile /var/run/$NAME-server.pid
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
sleep 1
start
;;
*)
echo "Usage: $NAME {start|stop|restart}" >&2
exit 1
;;
esac
exit 0
Делаем файл исполняемым.
sudo chmod +x bugsarena
Теперь можно воспользоваться командами
service bugsarena startи
service bugsarena stopдля запуска и остановки службы.
Также можно сделать чтобы игровой сервер стартовал при запуске системы выполнив
update-rc.d bugsarena defaults
Не забываем о XSS!
На последок просто необходимо напомнить об очень простой атаке свойственной для браузерных игр. Представим что у нас есть список игроков в каком-нибудь div'e. И к нам в игру заходит игрок с именем "<script>alert('В игру заходит Вася!')</script>". Его имя добавляется в div со списком игроков, и все клиенты получают назойливое сообщение alert'ом. И это еще цветочки. Через XSS уязвимость можно спокойно подгрузить любой скрипт с любого сайта. Так что не забываем об экранировании передаваемых с клиентов данных.