четверг, 11 августа 2011 г.

Создаем html5 мини-бродилку на CraftyJS

Хочу раcсказать, как без особых сложностей сделать свою первую мини игру на html5 (если точнее: js, html5, css).

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

Выглядит это все будет так:


Подготовка каркаса



Итак, для нашей задачи я буду использовать js библиотеку craftyjs. Так как для того, что бы нарисовать самостоятельно sprites, у меня руки не оттуда растут, я позаимствую sprites из примера на сайте, все остальное будем делать с нуля, да и взятый sprite мы дополним врагами в красных шапочках и футболках:



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



Так же, давайте сразу создадим:

/index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <script type="text/javascript" src="js/jquery.js"></script>
 <script type="text/javascript" src="js/crafty.js"></script>
 <!-- objects -->
 <script type="text/javascript" src="js/objects/flower.js"></script>
 <script type="text/javascript" src="js/objects/bush.js"></script>
 <script type="text/javascript" src="js/objects/grass.js"></script>
 <script type="text/javascript" src="js/objects/unit.js"></script>
 <script type="text/javascript" src="js/objects/player.js"></script>
 <script type="text/javascript" src="js/objects/fourway_ai.js"></script>
 <script type="text/javascript" src="js/objects/monster.js"></script>
 <!-- scenes -->
 <script type="text/javascript" src="js/scenes/loading.js"></script>
 <script type="text/javascript" src="js/scenes/main.js"></script>
 <script type="text/javascript" src="js/scenes/win.js"></script>
 <script type="text/javascript" src="js/scenes/lose.js"></script>

 <script type="text/javascript" src="js/game.js"></script>
 <link rel="stylesheet" href="css/game.css" type="text/css" media="screen" charset="utf-8">
 <title>Simpe RPG</title>
</head>
<body></body>
</html>


* This source code was highlighted with Source Code Highlighter.


/css/game.css
body, html { margin:0; padding: 0; overflow:hidden; font-family:Arial; font-size:20px }
#cr-stage { border:2px solid black; margin:5px auto; color:white }


* This source code was highlighted with Source Code Highlighter.


/js/game.js
var Settings = {
 width: 400, // ширина игрового поля
 height: 320, // высота
 poligon: 16, // размер полигона 16x16
 level: 1, // текущий уровень
 flower_count: 0 // цветков на уровне
};

window.onload = function() {
 Crafty.init(Settings.width, Settings.height); // создаем игровое поле

 // подгружаем sprite
 Crafty.sprite(Settings.poligon, "images/sprite.png", {
   grass1: [0,0],
   grass2: [1,0],
   grass3: [2,0],
   grass4: [3,0],
   flower: [0,1],
   bush1: [0,2],
   bush2: [1,2],
   player: [0,3],
   monster: [0,4]
 });

 // запускаем первую сцену
 Crafty.scene("loading");
};

* This source code was highlighted with Source Code Highlighter.


В последнем файле мы создаем канву с заданной шириной и высотой, создаем sprites из нашего файла и запускаем первую сцену «loading»

Создание сцен



Давайте сделаем нашу сцену «loading»:

/js/scenes/loading.jd
Crafty.scene("loading", function() {
 Crafty.load(["images/sprite.png"], function() {
  // выполним это действие, после того как images/sprite.png будет загружен
  setTimeout(function() {
   Crafty.scene("main");
  }, 100);
 });

 // меняем цвет фона
 Crafty.background("#000");
 // выводим по центру текст
 Crafty.e("2D, DOM, Text").attr({w: 100, h: 20, x: 150, y: 120})
  .text("Loading... <br/>Level: " + Settings.level)
  .css({"text-align": "center"});
});


* This source code was highlighted with Source Code Highlighter.


Здесь мы просто подгружаем sprite, делаем черный фон и выводим на фоне текст. К тому как мы вывели текст мы еще вернемся, а пока давайте сразу сделаем еще 2 аналогичные сцены, для выигрыша и проигрыша.

/js/scenes/win.js
Crafty.scene("win", function() {
 Settings.level += 1;

 Crafty.background("#000");
 Crafty.e("2D, DOM, Text").attr({w: 100, h: 20, x: 150, y: 120})
  .text("You win! <br/>Level: " + Settings.level)
  .css({"text-align": "center"});

 setTimeout(function() {
  Crafty.scene("main");
 }, 1000);
});


* This source code was highlighted with Source Code Highlighter.


/js/scenes/lose.js
Crafty.scene("lose", function() {
 Settings.level = 1;

 Crafty.background("#000");
 Crafty.e("2D, DOM, Text").attr({w: 100, h: 20, x: 150, y: 120})
  .text("You lose! <br/>Level: " + Settings.level)
  .css({"text-align": "center"});

 setTimeout(function() {
  Crafty.scene("main");
 }, 1000);
});


* This source code was highlighted with Source Code Highlighter.


Главную сцену пока оставим на сладкое и перейдем к объектам

Создание компонентов



В craftyjs есть 2 основных типа, компоненты Crafty.c и сущности Crafty.e. Сущности аккумулируют в себе свойства компонентов. В нашей игре будет 6 сущностей: цветок, камень, трава (фон), unit (базовый класс человечка), игрок и монстр. Для каждой сущности мы создадим свой компонент.

Начнем с самого простого, трава:

/js/objects/grass.js
Crafty.c('Grass', {
 init: function() {
  this.requires("2D");
  this.requires("Canvas");
  this.requires("grass"+Crafty.randRange(1,2));

  this.attr({x: 0, y: 0});
 }
});


* This source code was highlighted with Source Code Highlighter.


Заметьте, что здесь мы подключили компонент Canvas, а в сценах, к тексту мы подключали компонент DOM, это дает нам разное поведение объектов, например в тексте сцен у нас появилась возможность использовать метод css. Так же тут мы подключили наш sprite, выбирая в случайном порядке какой из 2 рисунков травы нам использовать. Теперь, так же сделаем компонент для камня:

/js/objects/bush.js
Crafty.c('Bush', {
 init: function() {
  this.requires("2D");
  this.requires("Canvas");
  this.requires("bush"+Crafty.randRange(1,2));
  this.requires("hard_bush");

  this.attr({x: 0, y: 0, z: 2});
 }
});


* This source code was highlighted with Source Code Highlighter.


Здесь все тоже самое, обратите только внимание на hard_bush, оно нам скоро пригодится. Перейдем к цветам, они у нас будут развиваться на ветру:

/js/objects/flower.js
Crafty.c('Flower', {
 init: function() {
  this.requires("2D");
  this.requires("Canvas");
  this.requires("flower");
  this.requires("SpriteAnimation");

  this.attr({x: 0, y: 0});
  this.animate("wind", 0, 1, 3);

  this.bind("EnterFrame", function() {
   if(!this.isPlaying())
    this.animate("wind", 80);
  });
 },

 clear: function() {
  this.removeComponent('flower');
  this._visible = false;
 }
});


* This source code was highlighted with Source Code Highlighter.


Для создания анимации мы подключаем компонент SpriteAnimation, который дает нам методы:

public this .animate(String id, Number fromX, Number y, Number toX) — анимация по sprite
public Boolean .isPlaying([String reel]) — проверка, играет ли анимация

Далее по событию EnterFrame мы создаем ветер. Так же в этом компоненте есть модуль clear, который убирает цветок если мы его собрали.

Пора перейти к созданию unit, я опишу все в комментариях:

/js/objects/unit.js

Crafty.c('Unit', {
 init: function() {
  this.requires("2D");
  this.requires("Canvas");
  this.requires("SpriteAnimation");
  this.requires("Collision"); // компонент столкновения

  this.attr({x: 0, y: 0, z: 1});

  this.collision(); // подключаем компонент столкновения

  // отрабатываем событие столкновения с камнем

  this.onHit("hard_bush", function(e) {
   var object = e[0].obj;
   // left
   if (object.x > this.x && (this.x + Settings.poligon) > object.x) {
    this.x -= this._speed;
    this.stop();
   }
   // right
   if (object.x < this.x && this.x < (object.x + Settings.poligon)) {
    this.x += this._speed;
    this.stop();
   }
   // top
   if (object.y < this.y && (this.y + Settings.poligon) > object.y) {
    this.y += this._speed;
    this.stop();
   }
   // bottom
   if (object.y > this.y && this.y < (object.y + Settings.poligon)) {
    this.y -= this._speed;
    this.stop();
   }
  });

  // анимация движения, сами указатели на sprite
  // находятся в дочерних компонентах

  this.bind("Moved", function(e) {
   if(this.x < e.x) {
    if(!this.isPlaying("walk_left"))
     this.stop().animate("walk_left", 10);
   }
   if(this.x > e.x) {
    if(!this.isPlaying("walk_right"))
     this.stop().animate("walk_right", 10);
   }
   if(this.y < e.y) {
    if(!this.isPlaying("walk_up"))
     this.stop().animate("walk_up", 10);
   }
   if(this.y > e.y) {
    if(!this.isPlaying("walk_down"))
     this.stop().animate("walk_down", 10);
   }
  });
 }
});


* This source code was highlighted with Source Code Highlighter.


Тут вся магия заключается в компоненте Collision, который позволяет нам задать границы столкновений, и отрабатывать различные события, так же в этом компоненте обрабатывается событие Moved, данное событие мы будем генерировать в наших компонентах игрока и монстра, параметром данного события будет x и y предидущей позиции.

Создадим игрока:

/js/objects/player.js
Crafty.c('Player', {
 init: function() {
  this.requires("Unit"); // подключаем компонент unit
  this.requires("player"); // подключаем sprite игрока
  this.requires("Fourway"); // подключаем компонент движения

  this.attr({x: 0, y: 0, z: 1});

  this.animate("walk_left", 6, 3, 8);
  this.animate("walk_right", 9, 3, 11);
  this.animate("walk_up", 3, 3, 5);
  this.animate("walk_down", 0, 3, 2);

  this.fourway(1);

  this.onHit("flower", function(e) {
   var object = e[0].obj;
   object.clear();
   if ((Settings.flower_count -= 1) == 0) Crafty.scene("win");
  });

  this.onHit("monster", function(e) {
   var object = e[0].obj;
   object.clear();
   Crafty.scene("lose");
  });
 }
});


* This source code was highlighted with Source Code Highlighter.


Подключаем созданный ранее компонент Unit, sprite player и компонент движения Fourway. Fourway — это компонент который изменяет положение нашего sprite в зависимости от нажатой стрелки на клавиатуре, при создание принимает параметр скорости перемещения. Далее с помощью того же компонента столкновения мы отлавливаем 2 события, столкновение с цветком (тогда мы его собираем) и столкновение с монстром (тогда мы умираем).

Пора создать монстра:

/js/objects/monster.js
Crafty.c('Monster', {
 init: function() {
  this.requires("Unit");
  this.requires("monster");
  this.requires("FourwayAI");

  this.attr({x: 0, y: 0, z: 1});

  this.animate("walk_left", 6, 4, 8);
  this.animate("walk_right", 9, 4, 11);
  this.animate("walk_up", 3, 4, 5);
  this.animate("walk_down", 0, 4, 2);

  this.fourway_ai(1);
 },

 clear: function() {
  clearInterval(this.removeComponent('monster')._interval);
 }
});


* This source code was highlighted with Source Code Highlighter.


Обратите внимание на компонент FourwayAI, такого компонента нет, нам нужно будет создать его. Данный компонент будет отвечать за самостоятельное передвижение монстра:

/js/objects/fourwai_ai.js
Crafty.c('FourwayAI', {
  _speed: 3,
  _interval: null,
  
  init: function() {
  this._movement= { x: 0, y: 0};

  this.bind("EnterFrame",function() {
   if (this.disableControls) return;

   if(this._movement.x !== 0) {
    this.x += this._movement.x;
    this.trigger('Moved', {x: this.x - this._movement.x, y: this.y});
   }
   if(this._movement.y !== 0) {
    this.y += this._movement.y;
    this.trigger('Moved', {x: this.x, y: this.y - this._movement.y});
   }
  });
  },

 fourway_ai: function(speed) {
  this._speed = speed;

  this.make_step();

  var kclass = this;
  this._interval = setInterval(function() {
   kclass.make_step();
  }, 1000 * this._speed);
 },

 make_step: function() {
  step = Crafty.randRange(-1,1);

  if (Crafty.randRange(1,2) == 1) {
   this._movement.x = step;
    this._movement.y = 0;
  } else {
   this._movement.x = 0;
    this._movement.y = step;
  }

    this.trigger('NewDirection', this._movement);
 }
});


* This source code was highlighted with Source Code Highlighter.


this.trigger — как не сложно догадаться, создает событие, которое мы потом и отлавливаем для анимации.

Теперь нам осталось создать последнюю главную сцену, которая будет генерировать всю нашу карту и расставлять на ней игрока, монстров, камни и цветки.

Сново создание сцен



/js/scenes/main.js
Crafty.scene("main", function() {
 var flower_count = Settings.level + 1;
 Settings.flower_count = 0;

 //generate the grass along the x-axis
 for(var i = 0; i < 25; i++) {
  //generate the grass along the y-axis
  for(var j = 0; j < 20; j++) {
   Crafty.e("Grass").attr({x: i * Settings.poligon, y: j * Settings.poligon});
   if (i * Settings.poligon == 160 && j * Settings.poligon == 144) continue;

   if(i > 0 && i < 24 && j > 0 && j < 19 && Crafty.randRange(0, 50) > 40) {  
    if (Crafty.randRange(1,10) == 1 && (flower_count -= 1) > 0) {
     Crafty.e("Flower").attr({x: i * Settings.poligon, y: j * Settings.poligon});
     Settings.flower_count += 1
     // one monster for one flower
     Crafty.e("Monster").attr({x: i * Settings.poligon, y: j * Settings.poligon});
    } else {
     Crafty.e("Bush").attr({x: i * Settings.poligon, y: j * Settings.poligon});
    }
   }
  }
 }

 //create the bushes along the x-axis which will form the boundaries
 for(var i = 0; i < 25; i++) {
  Crafty.e("Bush").attr({x: i * Settings.poligon, y: 0});
  Crafty.e("Bush").attr({x: i * Settings.poligon, y: 304});
 }

 //create the bushes along the y-axis
 //we need to start one more and one less to not overlap the previous bushes
 for(var i = 1; i < 19; i++) {
  Crafty.e("Bush").attr({x: 0, y: i * Settings.poligon});
  Crafty.e("Bush").attr({x: 384, y: i * Settings.poligon});
 }

 Crafty.e("Player").attr({x: 160, y: 144, z: 1});
});


* This source code was highlighted with Source Code Highlighter.


Тут мы наконец создаем сущности для наших компонентов, рисуем стенку из камней по периметру, заполняем фон травой, а внутри для каждого квадрата 16x16 создаем пустоту, камень или цветок + врага. В конце мы размещаем нашего игрока.

Вот и все, мы сделали простую, бесконечную, игру — бродилку на html5. Исходный код доступен наgithub. Проверял только в chrome и firefox под mac os.

Если кто нибудь подскажет устойчивый сервис где можно выложить демку, буду благодарен.

Комментариев нет:

Отправить комментарий