Часть 1 » Часть 2 » Часть 3 » Часть 4 » Часть 5 » Часть 6 » Часть 7 // Конец )


Сегодня займёмся тем, что увеличим длину тела змеи и создадим систему управления её перемещением.


Создаём тело змеи в виде цепочки фрагментов


Змея состоит из множества частей. Это позволяет увеличивать её длину, но делает задачу перемещения игрового объекта непростой. Ведь нам нужно перемещать сущность, состоящую из множества частей, как единое целое. Сделаем это за несколько шагов. Для начала – создадим змею, которая может перемещаться как единое целое.

1. В коде слоя SnakeLayer замените переменную snakeHead на snakeParts.

2. Код snakeHead: null измените на такой:

snakeParts: null, // содержит части тела змеи

3. Замените метод ctor на нижеприведённый:

ctor: function () {
	
/* Получим размер окна */
var winSize = cc.view.getDesignResolutionSize();
	
/* Вызовем конструктор суперкласса */
this._super();
	
// Новый код расположен ниже
/* Инициализируем массив snakeParts */
this.snakeParts = [];    
	
/* Создадим голову змеи */
var snakeHead = new SnakePart(asset.SnakeHead_png);
	
/* Установим координаты для головы змеи */
snakeHead.x = winSize.width / 2;
snakeHead.y = winSize.height / 2;
	
/* Добавим объект в качестве потомка слоя и добавим его в массив snakeParts */
this.addChild(snakeHead);
	
// Новая строка
this.snakeParts.push(snakeHead);

 /* Запланируем обновления */	
this.scheduleUpdate();
},

Здесь мы добавили в код возможность работы с телом змеи, состоящим из отдельных фрагментов. Сделано это с помощью массива, раньше мы рассматривали голову змеи как отдельный объект. Однако, это пока не решило проблему организации частей змеи в «единый организм». Для того, чтобы это сделать, будем назначать предыдущие позиции частей змеи тем частям, которые следуют за ними.

4. Переделайте код SnakeParts так, как показано ниже:

var SnakePart = cc.Sprite.extend({
    // Добавьте строки ниже
    prevX: this.x,
    prevY: this.y,    
    ctor: function(sprite) {
        /* Вызов конструктора суперкласса с передачей спрайта, символизирующего фрагмент тела змеи */
        this._super(sprite);
    },
    move: function(posX, posY) {
        // Добавьте строки ниже
        /* Установим предыдущее расположение */
        this.prevX = this.x;
        this.prevY = this.y;
        /* Обновим текущее расположения */
        this.x = posX;        
        this.y = posY;
    },
});

5. Добавьте в код SnakeLayer следующий метод:

addPart: function() {
var newPart = new SnakePart(asset.SnakeBody_png),
size = this.snakeParts.length,
tail = this.snakeParts[size - 1];

/* Изначально новая часть расположена в хвосте */
newPart.x = tail.x;
newPart.y = tail.y;
	
/* Добавляем объект в качестве потомка слоя */
this.addChild(newPart);
this.snakeParts.push(newPart);
},

По умолчанию позиция спрайта – (0, 0). Это означает, что объект SnakePart, до начала перемещения, появится в нижнем левом углу экрана. Мы эту проблему решаем, размещая новый спрайт в хвосте змеи.

6. Измените метод moveSnake в коде SnakeLayer для того, чтобы в нём использовался новый член класса snakeParts и с его помощью можно было перемещать все части змеи:

moveSnake: function(dir) {
	
/* Набор значений, задающих направление перемещения */
var up = 1, down = -1, left = -2, right = 2,
step = 20;

/* Запишем snakeHead в первый элемент массива */	
var snakeHead = this.snakeParts[0];

/* Сопоставление направлений и реализующего перемещения кода */
var dirMap = {};
dirMap[up] = function() {snakeHead.move(snakeHead.x, snakeHead.y + step);};
dirMap[down] = function() {snakeHead.move(snakeHead.x, snakeHead.y - step);};
dirMap[left] = function() {snakeHead.move(snakeHead.x - step, snakeHead.y);};	
dirMap[right] = function() {snakeHead.move(snakeHead.x + step, snakeHead.y);};

/* Перемещаем голову в заданном направлении */
if (dirMap[dir] !== undefined) {
dirMap[dir]();	
}

// Добавьте код ниже	
/* Сохраняем текущую позицию головы для следующего фрагмента змеи */
var prevX = snakeHead.prevX;
var prevY = snakeHead.prevY;

/* Перемещаем остальные части змеи */
for (var part = 1; part < this.snakeParts.length; part++) {
var curPart = this.snakeParts[part];

/* Перемещаем текущую часть, сохраняем её предыдущую позицию для следующей итерации */
curPart.move(prevX, prevY);
prevX = curPart.prevX;
prevY = curPart.prevY;
}
},

7. Добавьте следующий код в конструктор SnakeLayer. Это код временный, нужен он лишь для того, чтобы проверить, верно ли всё сделано на данном этапе разработки:

ctor: function () {
...	
	
for (var parts = 0; parts < 10; parts++) {
this.addPart();
}	
},

8. Запустите эмулятор. Если всё сделано правильно, вы увидите змею, которая медленно ползёт вверх, и, по мере движения, удлиняется, а потом уходит за пределы экрана.


Движущаяся змея на игровом экране

Теперь пришло время управлять этой змеёй.

События


Стандартная система ввода данных в Cocos2d-JS основана на событиях. Здесь имеется централизованный менеджер событий (Event Manager), который выполняет обработку всех событий в движке. Менеджер событий позволяет регистрировать функции обратного вызова, которые будут срабатывать в ответ на определённые события.

Для регистрации функции обратного вызова используется следующая конструкция:

cc.eventManager.addListener({
event: cc.EventListener.LISTENER, // прослушиватель 
onEventType: callBackFunction // тип события и функция обратного вызова
}, this);

А вот – реальный пример реализации этой конструкции:

cc.eventManager.addListener({
event: cc.EventListener.KEYBOARD,
onKeyDown: callBackFunction1,
onKeyUp: callBackFunction2
}, this);

Функция обратного вызова


Функция обратного вызова, которую передали при настройке прослушивателя, вызывается в другой области видимости. Это означает, что мы в ней не можем пользоваться ключевым словом «this». К счастью, Cocos2d-JS предоставляет некоторые вспомогательные механизмы для того, чтобы это обойти. Делается это с помощью объекта события.

Создавая функцию обратного вызова, можно указать несколько аргументов для неё. Второй аргумент – это всегда объект события. Он позволяет узнать тип события, которое представляет, и то, что является целью события. Это даёт нам доступ к узлу, который связан с событием.

Первый аргумент функции обратного вызова зависит от типа события. Например, если речь идёт об обработке событий клавиатуры, то первым аргументом будет код клавиши, задействованной в событии. В событии касания сенсорного экрана это будет объект касания, который несёт в себе сведения о координатах касания. Знание всего этого поможет нам наладить систему управления змейкой.

1. Добавьте в SnakeLayer следующий код:

curDir: 0, /* направление перемещения, соответствующее заданным ранее переменным */

2. Измените параметр «up», который использовался в moveSnake на «this.curDir» в методе update. Приведите его к такому виду:

this.moveSnake(this.curDir);

Теперь переменная curDir используется для изменения направления движения змеи.

3. Добавьте код для взаимодействия с менеджером событий в метод ctor слоя SnakeLayer:

/* Регистрируем прослушиватель событий клавиатуры */
cc.eventManager.addListener({
  event: cc.EventListener.KEYBOARD,
  onKeyPressed: function(keyCode, event) {
	
  var targ = event.getCurrentTarget();   
	
  /* Набор значений, задающих направление перемещения */
  var up = 1, down = -1, left = -2, right = 2;
	
  /* Объект, в котором клавишам поставлены в соответствие направления */            
  var keyMap = {};
  keyMap[87] = up; // w
  keyMap[83] = down; // s
  keyMap[65] = left; // a
  keyMap[68] = right; // d

  /* Обработка нажатий на клавиши */
  if (keyMap[keyCode] !== undefined) {
  targ.curDir = keyMap[keyCode];
  }                           
  }            
}, this);

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

Добавьте в метод ctor следующий код для того, чтобы змейкой можно было управлять касаниями экрана:

/* Прослушиватель для организации сенсорного управления */
cc.eventManager.addListener({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
onTouchBegan: function() {
	
/* Позволяет задействовать onTouchMoved, если возвращено true */
return true;
},
	
onTouchMoved: function(touch, event) {
var targ = event.getCurrentTarget();
var up = 1, down = -1, left = -2, right = 2;
	
/* Получаем расстояние перемещения */
var delta = touch.getDelta();

/* Если было касание с протягиванием */
if (delta.x !== 0 && delta.y !== 0) {
if (Math.abs(delta.x) > Math.abs(delta.y)) {                    

/* Определяем направление, получая знак */
targ.curDir = Math.sign(delta.x) * right;                        

} else if (Math.abs(delta.x) < Math.abs(delta.y)) {
	
/* Определяем направление, получая знак */	
targ.curDir = Math.sign(delta.y) * up;                        
}                            
}
/* Если было простое касание, без протягивания, не делаем ничего */            
}                    
}, this);

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

Теперь змейкой можно управлять! Однако, есть одна проблема. Игрок может неожиданно проиграть (с учётом той логики, которую мы ещё реализуем), если, например, змейка двигается вверх, а он нажмёт на клавишу «вниз». С этим мы справимся в продолжении.

Выводы


Подведём итоги сегодняшнего занятия:

  • Части тела змеи движутся синхронно, так как при перемещении каждая из них передаёт следующей свою предыдущую позицию.
  • При реализации управления игровым объектом задействована система событий.

Освоив сегодняшний материал, вы смогли следующее:
  • Создать тело змеи и настроить его длину.
  • Создать систему управления.

Змейка двигается по экрану, реагирует на нажатия клавиш на клавиатуре и на касания сенсорного экрана. Но пока в её перемещениях нет особого смысла. Поэтому в следующий раз мы добавим в игру угощение для неё и поговорим о распознавании и обработке столкновений.


Часть 1 » Часть 2 » Часть 3 » Часть 4 » Часть 5 » Часть 6 » Часть 7 // Конец )

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


  1. 27cm
    15.04.2016 16:45
    +2

    Думал, скрин из игры, а оказалось просто КДПВ.


    1. semenyakinVS
      15.04.2016 18:24

      Да. Тоже так подумал. Реально крутое развитие идеи змейки было бы.


  1. VitaZheltyakov
    17.04.2016 16:46

    Вы лучше скажите — Intel XDK исправил проблему «9 мая»?


    1. domix32
      17.04.2016 22:42

      Что за проблема?


      1. VitaZheltyakov
        18.04.2016 00:27

        Intel XDK использует старые версии Cordova для постороения приложений, в которых есть какая-то уязвимость. А Google 9 мая этого года удалит все приложения созданные с этой уязвимостью. Ну, и как результат все приложения постоенные с помощью Intel XDK будут удалены.

        Как я понимаю, вы не в курсе этой проблемы…


        1. domix32
          18.04.2016 01:03

          Я не связан с авторами XDK/Cordova/Google, если вы об этом.


        1. SvetlanaGEm
          19.04.2016 10:48

          Это коснется только выкладывания новых версий приложений, старые удалять никто не будет. Вот здесь подробности: https://software.intel.com/en-us/forums/intel-xdk/topic/609583