Добра всем хаброчитателям!
В прошлой статье мы рассматривали способ создания карточных игр с помощью манипуляций с DOM, без использования canvas, на подобии HeartStone.
Сегодня мы продолжим эту тему, подключив к нашему делу полезнейшую в данном случае библиотеку Matreshka.js.
Кратко напомню, к чему мы пришли в прошлый раз. Общение с сервером осуществляется по WebSocket'ам, передаем JSON объекты вида: { «method»: метод, «args»: аргументы}.
Серверная сторона реализована с помощью php, скрипт запустили как демона (бесконечный цикл) в поток null.
Клиент принимает такого же вида JSON строки, вызываем методы объекта Actions (подробнее в прошлой статье).
Самое первое, где можно внедрить матрешку — это меню со списком игроков. Вообще списки в матрешке сделаны очень удобно, на мой взгляд.
Итак, задача: мы получаем список игроков в JSON, когда подключаемся, необходимо их отрисовать и назначить события.
Над дизайном не заморачиваемся.
Списки в матрешке состоят из модели и класса (ну и объекта класса).
В нашем случае
Давайте разберемся, что же тут произошло, что значит «биндим игрока»?
Для матрешки конструкция
означает, что мы связываем свойство name, доступное потом как свойство объекта (Obj.name) и некоторую html-сущность, в данном случае сущность с селектором ':sandbox .name', где sandbox — песочница, то есть тот самый элемент, который мы только что отрендрили. Напомню, что мы это событие рендера одного конкретного элемента списка.
В качестве третьего аргумента передает тип зависимости. То есть то как они (свойство и сущность) между собой связаны.
В матрешке есть стандартны набор биндеров, и в данном случае Matreshka.binders.innerHTML() ставит в зависимость значение свойства и содержимое html-контейнера ':sandbox .name'.
Какая меж ними конкретно зависимость? Самая очевидная: изменяем свойство объекта — изменяется содержимое html контейнера.
Основы модели разобрали, идем дальше к классу
В классе стоит заострить внимание на двух вещах, хоть и весьма несложных. Свойство itemRenderer показывает, как будет отрисовываться каждый элемент списка. В приведенном примере /> и есть :sandbox, от которого отсчитываем прочие селекотры.
Указание
говорит о том, что все элементы списка будут отрисовываться внутри контейнера '#players'.
Когда игроки соединились и начали игру, что мы имеем (чисто логически):
Осталось реализовать эти списки с помощью матрешки и задать им некоторые события.
Аналогичный список, не будем повторяться, рассмотрим, как здесь применены бинды.
Как мы рассматривали выше, эти строки связывают содержимое html узла и свойства объекта.
Связав их вышеуказанным способом мы легко можем создать карту, просто сделав push в наш список:
Крайне просто. Но еще проще то, как мы можем менять эти свойства:
Не только задаст показатель здоровья равным нулю, но и отрисует это в html в нужном объекте.
Но и это еще не все, нам же надо отслеживать изменения здоровья, и если оно станет меньшим единицы, инициировать смерть юнита. Для этого свяжем свойство health объекта с самой картой:
Третий аргумент, как я говорил, задает логику связи. В данном примере логика следующая:
Когда поменялось (установилось) значение health объекта, запускаем функцию
This указывает на карту целиком, на песочницу (второй аргумент: ':sandbox').
В сложных приложениях, где действительно нужно двусторонее и множественное связывание, матрешка великолепно облегчает жизнь и создает комфорт при разработке.
Ведь связывать можно как угодно, в одном случае ставим обработку только на принимаемое значание (setValue), в другом на изменение свойства по событию (on: 'click', getValue: function(){}).
В прошлой статье мы рассматривали способ создания карточных игр с помощью манипуляций с DOM, без использования canvas, на подобии HeartStone.
Сегодня мы продолжим эту тему, подключив к нашему делу полезнейшую в данном случае библиотеку Matreshka.js.
Введение
Кратко напомню, к чему мы пришли в прошлый раз. Общение с сервером осуществляется по WebSocket'ам, передаем JSON объекты вида: { «method»: метод, «args»: аргументы}.
Серверная сторона реализована с помощью php, скрипт запустили как демона (бесконечный цикл) в поток null.
Клиент принимает такого же вида JSON строки, вызываем методы объекта Actions (подробнее в прошлой статье).
socket.onmessage
socket.onmessage = function (e){
if (typeof e.data === "string"){
var request = JSON.parse(e.data);
console.log('Response: ' + request.function);
Actions[request.function](request.args);
};
}
Начинаем внедрять матрешку
Самое первое, где можно внедрить матрешку — это меню со списком игроков. Вообще списки в матрешке сделаны очень удобно, на мой взгляд.
Итак, задача: мы получаем список игроков в JSON, когда подключаемся, необходимо их отрисовать и назначить события.
Над дизайном не заморачиваемся.
Списки в матрешке состоят из модели и класса (ну и объекта класса).
В нашем случае
Модель списка
var listModel = Matreshka.Class({ // Модель списка
'extends': Matreshka.Object, // Наследуется от Matreshka.Object всегда
constructor: function(data){
this.jset(data);
this.on('render',function(){ // Что происходит после отрисовки
this.bindNode('name',':sandbox .name',Matreshka.binders.innerHTML()); // Биндим имя игрока
this.bindNode('letsFight',':sandbox .fightButton'); // Биндим кнопку вызова на бой
this.on('click::letsFight',function(){
Actions.figthRequest(this.name);
});
});
}
});
Давайте разберемся, что же тут произошло, что значит «биндим игрока»?
Для матрешки конструкция
this.bindNode('name',':sandbox .name',Matreshka.binders.innerHTML());
означает, что мы связываем свойство name, доступное потом как свойство объекта (Obj.name) и некоторую html-сущность, в данном случае сущность с селектором ':sandbox .name', где sandbox — песочница, то есть тот самый элемент, который мы только что отрендрили. Напомню, что мы это событие рендера одного конкретного элемента списка.
В качестве третьего аргумента передает тип зависимости. То есть то как они (свойство и сущность) между собой связаны.
В матрешке есть стандартны набор биндеров, и в данном случае Matreshka.binders.innerHTML() ставит в зависимость значение свойства и содержимое html-контейнера ':sandbox .name'.
Какая меж ними конкретно зависимость? Самая очевидная: изменяем свойство объекта — изменяется содержимое html контейнера.
Основы модели разобрали, идем дальше к классу
var listArray = Matreshka.Class({ // Класс списка
'extends': Matreshka.Array,
Model: listModel, // Наша модель
itemRenderer: '<li class="player"><span class="name"></span><span class="fightButton"></span></li>', // Как рендрится каждый элемент
constructor: function(){
this.bindNode('sandbox','#players'); // Засовываем в песочницу
}
});
В классе стоит заострить внимание на двух вещах, хоть и весьма несложных. Свойство itemRenderer показывает, как будет отрисовываться каждый элемент списка. В приведенном примере /> и есть :sandbox, от которого отсчитываем прочие селекотры.
Указание
constructor: function(){
this.bindNode('sandbox','#players'); // Засовываем в песочницу
}
говорит о том, что все элементы списка будут отрисовываться внутри контейнера '#players'.
Матрешка в режиме сражения
Когда игроки соединились и начали игру, что мы имеем (чисто логически):
- Список карт в моей руке
- Список карт в руке соперника
- Список моих карт на игровом поле
- Список карт противника на игровом поле
Осталось реализовать эти списки с помощью матрешки и задать им некоторые события.
Карты в моей руке
Карты в моей руке
var myCardsModel = Matreshka.Class({ // Модель списка
'extends': Matreshka.Object,
constructor: function(data){
this.jset(data);
this.on('render',function(){
this.bindNode('name',':sandbox .title',Matreshka.binders.innerHTML());
this.bindNode('attack',':sandbox .attack .value',Matreshka.binders.innerHTML());
this.bindNode('health',':sandbox .health .value',Matreshka.binders.innerHTML());
this.bindNode('mana',':sandbox .mana .value',Matreshka.binders.innerHTML());
this.bindNode('picture',':sandbox .picture',{
setValue: function(v){
this.innerHTML = '<img src="img/' + v + '">'
}
});
this.on('click::sandbox',function(){
myArenaCards.push(this);
myCards.splice(myCards.indexOf(this),1);
Actions.send('putCard',this.toJSON());
});
});
}
});
var myCardsArray = Matreshka.Class({ // Класс списка
'extends': Matreshka.Array,
Model: myCardsModel,
itemRenderer: '<div class="card">'
+'<div class="title"></div>'
+'<div class="health"><div class="svg">' + $b('#icons #heart')[0].innerHTML + '</div><div class="value"></div></div>'
+'<div class="attack"><div class="svg">' + $b('#icons #attack')[0].innerHTML + '</div><div class="value"></div></div>'
+'<div class="mana"><div class="svg">' + $b('#icons #diamond')[0].innerHTML + '</div><div class="value"></div></div>'
+'<div class="picture"></div>'
+'</div>',
constructor: function(){
this.bindNode('sandbox','#myhand'); // Засовываем в песочницу
}
});
var myCards = new myCardsArray; // Экземпляр класса списка
Аналогичный список, не будем повторяться, рассмотрим, как здесь применены бинды.
this.bindNode('name',':sandbox .title',Matreshka.binders.innerHTML());
this.bindNode('attack',':sandbox .attack .value',Matreshka.binders.innerHTML());
this.bindNode('health',':sandbox .health .value',Matreshka.binders.innerHTML());
this.bindNode('mana',':sandbox .mana .value',Matreshka.binders.innerHTML());
Как мы рассматривали выше, эти строки связывают содержимое html узла и свойства объекта.
Связав их вышеуказанным способом мы легко можем создать карту, просто сделав push в наш список:
var Actions = {
.........
cardToHand: function(card){
myCards.push({
name: card.name,
attack: card.attack,
health: card.health,
picture: card.picture,
mana: card.mana
});
}
.........
}
Крайне просто. Но еще проще то, как мы можем менять эти свойства:
this.health = 0;
Не только задаст показатель здоровья равным нулю, но и отрисует это в html в нужном объекте.
Но и это еще не все, нам же надо отслеживать изменения здоровья, и если оно станет меньшим единицы, инициировать смерть юнита. Для этого свяжем свойство health объекта с самой картой:
this.bindNode('health',':sandbox',{
setValue: function(v){
if (v < 1){
this.className += ' die';
var iot = myArenaCards.indexOf(this);
setTimeout(function(){
myArenaCards.splice(iot,1);
},2000);
};
}
});
Третий аргумент, как я говорил, задает логику связи. В данном примере логика следующая:
Когда поменялось (установилось) значение health объекта, запускаем функцию
function(v){
if (v < 1){
this.className += ' die';
var iot = myArenaCards.indexOf(this);
setTimeout(function(){
myArenaCards.splice(iot,1);
},2000);
};
}
This указывает на карту целиком, на песочницу (второй аргумент: ':sandbox').
Заключение
В сложных приложениях, где действительно нужно двусторонее и множественное связывание, матрешка великолепно облегчает жизнь и создает комфорт при разработке.
Ведь связывать можно как угодно, в одном случае ставим обработку только на принимаемое значание (setValue), в другом на изменение свойства по событию (on: 'click', getValue: function(){}).
Комментарии (9)
eme
09.06.2015 16:59+1Конечно, в примере вашей игры можно легко обойтись без канваса.
Сравнение очень сомнительное )
p.s. Для понимания, сравните любой из демо примеров html5gameengine.com с вашим.
chlp
Извините, но откуда вообще может возникнуть идея делать карточную игру на веб через canvas?
seokirill Автор
Речь не о карточных играх, а вообще. Тк темы именно такие поднимаются в основном: создание игр с помощью canvas.
Можно и не карточную, любую двухмерную — SVG в помощь. Просто захотелось именно эту.
Alexeyco
Надо flash?
chlp
Нет, надо юзать HTML. Очень здорово, что нам дали инструмент, позволяющий реализовать всю механику подобных игр нативными средствами, потом дали возможности всячески их украшать, анимировать и даже небольшие возможности 3D, а мы начинаем через канвас рендерить свой собственный механизм. Потом удивляемся, что браузер отжирает всю оперативку.
eme
посмотрите pixi.js и удивитесь!
chlp
Я говорю про конкретный пример игры из статьи. Для реализации идеи не нужен webGL. И говорю я о том, что если цель можно достичь явно меньшими затратами, то зачем делать иначе?