Создаем 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.

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

    UPD: По совету RiderSx выложил демо тут, надеюсь выдержит.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 42

      0
      Все что без серверной части можно выложить на narod.ru/, думаю яндекс точно вытянет)) Ну или прямо на гитхабе можно выкладывать тоже :)
        –4
         <!-- 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>
        

          0
          ваши предложения, не используя серверные языки, и не гавнокодя один огромный файл?
            +2
            Есть ещё вариант — require.js
              +3
              Каюсь, не видел такого кросбраузерного решения, хотя для туториала, ИМХО, перечисление более наглядно.
              +1
                0
                а что плохого в использовании серверных языков?

                в любом случае удобнее подключать 1 скрипт, который уже подключит все остальные.
                  +2
                  это немного не по теме, речь идет о создание игр на html5, например для дальнейшего портирования на мобильные устройства через phonegap
                    –2
                    что плохого в использовании серверных/десктопных скриптов во время разработки?
                  0
                  как бы продакшен версия одно, девелоперская — другое.
                  для первой гораздо лучше один огромный файл, который можно собрать различными тулзами, тем же Closure Compiler, например.
                  Для второго, конечно, удобнее модульная структура.
                  +1
                  С чего бы вдруг? Во время разработки я тоже подключаю по файлу на каждый класс.
                    0
                    Я тоже обычно включаю по файлу на класс, но когда это доходит до больших масштабов стараюсь избежать длинного списка файлов.
                      0
                      А нужны ли подгрузки во время игры? По моему надежнее во время лоадинга загрузить все, чтобы не тревожить геймплей подгрузками (конечно до тех пор пока память позволяет). В рамках игры да еще и такой мелкой, facepalm'ы по поводу классического подключения скриптов это просто придирки :)
                  0
                  bush — это куст :)
                    +2
                    блин, вы меня раскусили, плохо владею английским да еше и дальтоник =)
                    0
                    Вы не поверите: пару дней назад делал для nds игру с абсолютно той же задумкой, только там пещеры, вместо цветочков — сокровища.

                    Может это знак ..?)
                      +1
                      Знак, сообщающий, что рогалики достаточно популярны среди разработчиков?
                      +3
                      Пару раз вылетел за карту и вернуться уже не получилось. А так забавная игра, ну и конечно интересная реализация.
                        0
                        Подскажите пожалуйста как пройти 5й уровень.
                          +3
                          там очень сложный бос, в него нужно камнями кидаться
                          +2

                          спрятался :(
                            +2
                            Вы забыли добавить описание фичи: нажимаем одновременно две стрелки и получаем способность хождения сквозь и внутри стен.
                              +1
                              Возможность выхода за карту тоже есть.
                            0
                            нашел баг, если нажать одновременно вверx и W, то персонаж проскакивает через стены и ускоряеться.
                              0
                              Тоже самое если нажимать: A + левая стрелка, D + правая стрелка, S + стрелка вниз.
                                0
                                это всего лишь базовый пример, не на что не претендующий, все исходники открыты github.com/arion/simplerpg так что «Fork & Go»
                                0

                                не продумано :(
                                  +2
                                  кто просмотрел тему для того чтобы проиграть? )
                                    0
                                    10k.aneventapart.com/
                                    Афтор у тебя все шансы есть поучаствовать в 10k конкурсе =)

                                    Тогда придется весь JS в один скрипт, дальше closure или yui compressor, дальше пожать в PNG и сделать загразчик.
                                      0
                                      Конкурс по html5 играм уже прошел, этот по сайтам и задача там иная.
                                        0
                                        Пруф 10k.aneventapart.com/ ДО 12 Сентября
                                        habrahabr.ru/company/microsoft/blog/125641/

                                        Всё еще только начинается
                                          0
                                          Ииии? прочитайте описание конкурса. Я в той теме даже отписывался так что в курсе как никак. К играм этот конкурс не имеет никакого отношения а приведенный в данном топике проект с конкурсом связан разве что используемыми технологиями. Там речь идет о создании сайтов с «отзывчивым» интерфейсом независим от платформы на которой он открывается, адаптируясь под пользовательское окружение, и используя в своей функциональности все его преимущества и тд.

                                          Другими словами это конкурс от той же компании но совсем с другой темой.
                                      0
                                      NOD32 говорит, что на демо страничке вирус.
                                        0
                                        Однако…

                                          0
                                          может он все сайты на народе считает опасными? все исходники открыты, в статье описано что и что делает, так что смотрите сами, вирусов там точно нет…
                                            0
                                            Да, похоже, что все сайты на народе в черном списке. Жесть.
                                          0
                                          А такой вопрос, интереса ради попробовал во всех объектах заменить Canvas на DOM — и ни фига. За z-индексами и стилями компонентов надо «вручную» следить?
                                            0
                                            в Canvas тоже нужно следить за слоями, за это отвечает атрибут z, а не заработало у вас потому, что в коде используются методы, которые присущи только для компонента Canvas, необходимо заменить их на аналогичные для DOM
                                              0
                                              ага, покопавшись глубже, понял
                                              спасибо
                                            0
                                            > This source code was highlighted
                                            Вы серьёзно? Весь код чёрно-белый! Да и хабр поддерживает тег <source lang=...>, пользуйтесь им.
                                              0
                                              пользуюсь ХабраРедактором и этот текст писал в нем же
                                                0
                                                И что? Код не подсвечен (а там где подсвечен, подсвечен криво: посмотрите на строки подключения скриптов в html), вывод — редактор кривой. Пользуйтесь пожалуйста, тегами хабра: они работают, в отличие от.

                                            Only users with full accounts can post comments. Log in, please.