Pull to refresh

Создание Doodle jump на HTML5

Reading time7 min
Views15K
Doodle Jump
Здравствуйте, Хабражители!
В этом топике я хочу рассказать о том, как я создал браузерную игру «DoodleJump» на HTML5 без использования каких либо то фреймворков. Для тех кто не знает, DoodleJump — это популярная мобильная игра где главный герой «doodler» бесконечно прыгает вверх по платформам, преодолевая различные препятствия, и собирая бонусы. Эта игра широко распространена почти на всех мобильных платформах, но приличной браузерной версии этой игры нет, поэтому я и решили написать браузерную версию этой игры, пусть даже управляемую клавишами а не гироскопом.

Итак, начнем. Для начало обозначим сцену, где и будет все происходить:
<div id="stage"></div>

и применяем к ней стили:
    #stage{
    	position:absolute;
    	top:0px; left:0px;
    	background-color:#fff3f7;
    	background-image:url(grid.gif);
    	width:320px;
    	height:480px;
    }

где мы заливаем сцену клетчатым фоном (grid.gif), и делаем разрешение 320 на 480 пикселей. В эту сцену мы помещаем ещё несколько div'ов: верхняя панель (tray), где будут писаться набранные очки и распологатся кнопка паузы(pause), основной персонаж игры (doodler) и пулька (bullet), которой будет стрелять наш персонаж. Для таких объектов как платформы и бонусы мы создаем div'ы — контейнеры в которые, с помощью ява-цикла, мы поместим отдельно по диву для каждого объекта.

    <div id="stage">
    	<div id="header">
    		<div id="tray"></div>
    		<div id="tray-border"></div>
    		<div id="pause" onClick="gotoPause();"></div>
    	</div>
    	<div id="doodler"></div>
    	<div id="bullet"></div>
    	<div id="footer"></div>
    	<div id="obstacle"></div>
    	<div id="platforms">
    	    	<!--несколько дивов-->
    	</div>
    	<div id="objects">
    		<!--несколько дивов-->
    	</div>
    	<div id="doodler"></div>
    	<div id="ammunition"></div>
    	<div id="bullet"></div>
    	<div id="records"></div>
    </div>

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

спрайт заставки

главный спрайт

footer

Вот пример стилей к элементу doodler:
    #doodler{
    	position:absolute;
    	background:url(sprite.png);
    	width:62px;
    	height:59px;
    }

Зная координаты расположения элементов на спрайте, мы можем залить все объекты по образцу выше.
А вот стили к пружинками и платформам будут немного отличаться, так как они лежат в контейнере, и остальные параметры к ним будут задаваться из java-скрипта:
    #objects div{
    	position:absolute;
    	background:url(sprite.png);
    }

После того как мы создали все объекты на сцене и придал им стили, мы можем приступать к написанию java скрипта. Сначала для удобства написания кода игры, мы запихнем селекторы объектов в переменные:
    var d = document.getElementById('doodler');
    var tray = document.getElementById('tray');
    var header = document.getElementById('header');
    var ammun = document.getElementById('ammunition');
    // и другие...

Как я и говорил ранее, div'ы — контейнеры такие как: platforms и objects мы заполняем ява циклом:
    for(i=0;i'<numPlatform;i++){
    	document.getElementById('platforms').innerHTML+='<div class="platform" id="p'+i+'"></div>';
    }

где numPlatform — это количество платформ в контейнере (максимальное количество платформ, умещаемых на сцене, в нашем случае их 16). Для удобства можно создать функцию для перемещения платформ:
    function setPlatform(n,x,y,t){
    	p = document.getElementById('p'+n);
    	p.style.top = y+"px";
    	p.style.left = x+"px";
    	if(t==-1){p.style.backgroundPosition = "100px 100px";}
    	if(t>=0&&t<8){
    	   p.style.height=16+"px";
    	   p.style.backgroundPosition = "-399px -"+(16*t)+"px";
    	}
    	if(t==8){p.style.height=24+"px"; p.style.backgroundPosition = "-399px -128px";}
    	if(t==9){p.style.height=16+"px"; p.style.backgroundPosition = "-399px -151px";}
    	if(t==10){p.style.height=34+"px"; p.style.backgroundPosition = "-399px -168px";}
    }

Теперь можно легко переместить любую платформу на желаемое место на сцене, и изменять её тип:
setPlatform([номер платформы],[координаты платформы по иксу],[координаты по игрику],[тип платформы]);
setPlatform(9,50,100,0);

образец перемещенной платформы

создадим еще несколько подобных функций:
    setBullet(x,y);//перемещает пульку
    setObstacle(x,y,t);//перемещает монстров и дырки
    setObject(n,x,y,t);//перемещает пружинки и бонусы
    doodle(x,y,t,a,alpha);//перемещает Дудлера.


Создадим несколько глобальных переменных и массивов, которые нам понадобятся в дальнейшем.
Вот некоторые из них:
    var life = true;//жив ли дудлер
    var stageSpeed = 0;//скорость движения сцены
    var gravitation = 0.08;гравитация
    var ySpeed = 5;//начальная скорость дудлера
    var numPlatform = 16;//количество платформ
    var numObjects = numPlatform;//количество объектов
    var xObject = new Array;//X объекта
    var yObject = new Array;//Y объекта
    var tObject = new Array;//тип объекта
    var yFooter = 1000;//обрыв бумаги (footer.png)
    var tAmmunition = 0;//начальная амуниция на дудлере
    var xPlatform = new Array;//X платформы
    var yPlatform = new Array;//Y платформы
    var tPlatform = new Array;//тип платформы
    var xDoodler = 136;//начальный X дудлера
    var yDoodler = 136;//начальный Y дудлера
    var tDoodler = 136;//начальное направление дудлера
    var record = 0;//текущее количество очков
    var xBullet;//X пульки
    var yBullet;//Y пульки
    var xSpeedBullet;//горизонтальная скорость пули
    var ySpeedBullet;//вертикальная скорость пули
    var pause = false;//пауза
    ...

Теперь рассмотрим основную часть ява-скрипта самой игры, бóльшая часть кода будет находится в функции frame(), и запускается 100 раз в секунду. Внутри этой функции дудлер будет постоянно проверятся на столкновение с монстрами и бонусами, а так же там будет находится цикл, внутри которого мы будим проверять дудлера на столкновение с каждой платформой по отдельности:
    function frame(){//функция фрейма запускаемая 100 раз в секунду
       ySpeed -= gravitation;//уменьшаем скорость дудлера
       yDoodler -= ySpeed;//перемещаем дудлера
       if(xDoodler+46>=xObstacle&&xDoodler+16<=xObstacle+65&&yDoodler+59>=yObstacle&&
           yDoodler+59<=yObstacle+60&&tObstacle != 0){//проверяем на столкновение с припядствием
          if(tObstacle != 6){//если это не дырка
             if(tAmmunition>=1){//и на дудлере есть амуниция
                tObstacle = 0;//то убираем монстра
             }else if(ySpeed<0){//в противном случае, если дудлер падает на монстра
                tObstacle = 0;//монстр исчезает
                ySpeed = 10;//дудлер отпрыгивает
             }else{//если на монстре нет амуниции
                tAmmunition = 8;//даем дудлеру амуницию звездочек
                stageSpeed = 7;//смещаем все элементы сцены вверх
                ySpeed = 0;//дудлера вниз
                life = false;//обозначаем что он мертв
             }
          }else{//если это дырка
             if(tAmmunition == 0){//и на дудлере нет амуниции
                ySpeed = 0;//останавливаем дудлера
                gravitation = 0;//останавливаем падение дудлера
                yDoodler -= (yDoodler - yObstacle)/6;//засасываем дудллера в дыру по X
                xDoodler -= (xDoodler - (xObstacle+10))/6;//засасываем дудллера в дыру по Y
                alphaDoodler -= 3;//уменьшаем прозрачность дудлера
                life = false;//убиваем дудлера
             }
          }
       }
       yObstacle -= stageSpeed;//в случае падения монстры сместятся вверх 
       setObstacle(parseInt(xObstacle), parseInt(yObstacle+obstacleYPosition), tObstacle);//перемещаем монстра
       doodle(parseInt(xDoodler), parseInt(yDoodler), tDoodler, tAmmunition, alphaDoodler);//перемещаем дудлера
       ...
       for(i=0;i < numPlatform;i++){    
          yPlatform[i]-=stageSpeed;//в случае падения все блоки смещаются вверх 
          if(xDoodler+46>=xPlatform[i]&&xDoodler+16<=xPlatform[i]+65&&  
              yDoodler+59>=yPlatform[i]&&yDoodler+59<=yPlatform[i]+16&&tPlatform[i]!=-1&&life&&  
              tAmmunition == 0||tAmmunition == 7){//если живой дудлер столкнулся с активной платформой 
             if(tPlatform[i]==2){//если платформа белая 
                if(ySpeed<0){//и дудлер падает на нею 
                   ySpeed = 5;//дудлер отпрыгивает от платформы 
                   tPlatform[i]=-1;//платформа исчезает 
                } 
             }else if(tPlatform[i]==9){//если это не белая а коричневая платформа 
                if(ySpeed<0){ //и дудлер падает на неё 
                   tPlatform[i]=10;//коричневая платформа ломается 
                } 
             }else{//если это не коричневая и не белая 
                if(ySpeed<=0){//и дудлер падает на неё 
                   ySpeed = 5;//дудлер отпрыгивает от этой платформы 
                } 
             } 
          }  
          ...//проверяем на столкновение с пружинами и бонусами (см. далее)   
       }  
       if(tAmmunition>0&&tAmmunition<7&&ySpeed<2){ //если дудлер падает с амуницией
          gravitation = 0.08;
          ySpeed -= 2;
          tAmmunition=0;//убираем амуницию
       }
       if(yFooter > 434){//если обрыв бумаги не на сцене 
          yFooter -= stageSpeed;//смещаем его 
       }else{//как только он встал на нужное место 
          death();//вызываеи меню с результатами 
       } 
       footer.style.top = yFooter+"px";  
       if(xDoodler>296){lDoodler = xDoodler = -24;} //делаем сцену «безграничной»
       if(xDoodler<-24){lDoodler = xDoodler = 296;} 
    }
    fr = setInterval("frame()", 10);//интервал запуска функции frame() (100раз в секунду) 

Что бы обеспечить падение сломанных платформ пишем:
    if(tPlatform[i]==10){//если это сломанная платформа
       yPlatform[i]+=4;//смещаем ее вниз на 4 пикселя
    }

Все платформы которые уезжают за нижний край сцены должны появляется сверху, то есть если yPlatform[i] > 480 то yPlatform[i] = -30, но так как у нас есть коричневые платформы, которые могут падать вниз не в зависимости от других, нарушая порядок исчезновения — этот вариант приведет к произвольному расстоянию между платформ:
баг

Поэтому все платформы которые вышли за область видимости мы ставим на 30 пикселей выше предыдущей скрываемой платформы:
    if(yPlatform[i] > 480){
       xPlatform[i] = parseInt(Math.random()*260);
       yPlatform[i] = yPlatform[lastPlatform]-30;
       lastPlatform = i;
       ...
    }

В зависимости от того, на какой бонус натыкается дудлер — выполняется одно из следующих условий: например если дудлер падает на пружинку:
    if(tObject[i] == 0&&ySpeed<0){ //если дудлер падает на пружинку
       ySpeed = 10;//его скорость становится равна 10 (начальная скорость обычного прыжка = 5)
       tObject[i] = 1;//и пружинка меняет свой тип на раскрытый
    }

Вот что происходит когда дудлер натыкается на другие бонусы:
    if(tObject[i] == 2){//шапка
       gravitation = 0.01;
       ySpeed = 10;
       tObject[i] = -1;//скрываем шапку
       tAmmunition = 1;
    }
    if(tObject[i] == 3){//ранец
       gravitation = 0.008;
       ySpeed = 15;
       tObject[i] = -1;
       tAmmunition = 5;
    }
    if(tObject[i] == 4){//сфера
       tObject[i] = -1;
       tAmmunition = 7;
       setTimeout('stopBonus()',5000);//запускает функцию отключения бонуса через 5 секунд
    }

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

то как зависит сложность игры от набранных очков

далее мы добавили заставку (с возможностью пропуска), меню, рекорды, справку, меню паузы, звуковое сопровождение, и информацию о достижениях после падения дудлера:

то как зависит сложность игры от набранных очков

При запуске игры, она обращается к серверу и получает список результатов, который доступен по кнопке «Рекорды». Если пользователь преодолевает минимальный рекорд, (расположенный в списе на 16-ой строчке), то игра спрашивает его никнэйм, далее в зависимости от набранных очков располагает его на определенной позиции, смещая меньшие рекорды вниз.

Простой рабочий пример: тут
Tags:
Hubs:
+144
Comments72

Articles

Change theme settings