Создание игр без canvas: Matreshka.js

от автора

Добра всем хаброчитателям!

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

Сегодня мы продолжим эту тему, подключив к нашему делу полезнейшую в данном случае библиотеку Matreshka.js.
image

Введение

Кратко напомню, к чему мы пришли в прошлый раз. Общение с сервером осуществляется по 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(){}).

  • Пример (косяков море, цель — показать технологию)
  • Github

ссылка на оригинал статьи http://habrahabr.ru/post/259907/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *