Статьи

3D тетрис с Three.js Учебник — часть 1

Изучить Three.js довольно просто. Эта серия может быть не лучшим учебником, но я поделюсь своим опытом написания игры — 3D Tetris . Я надеюсь, что вы найдете это полезным.

подготовка

Сначала вам нужно скачать Three.js:  https://github.com/mrdoob/three.js

Я также использую статистику от mrdoob:  https://github.com/mrdoob/stats.js

В обоих случаях вам нужны только файлы из директории сборки.

Blox — хороший шрифт для меню и пунктов:  http://www.dafont.com/blox.font

Чтобы использовать его, вы должны конвертировать его с Cufon:  http://cufon.shoqolate.com/generate/

В моем проекте все файлы JS идут в папку js, музыка в папку music и html в корень.

HTML

Я ленивый, поэтому весь CSS идет прямо в заголовок HTML. Он не так плох, как кажется — он очень короткий и содержит только одну страницу, поэтому кэширование в любом случае не нужно. Там не так много, чтобы объяснить, просто немного CSS для вступления и счетчик очков. Также вы должны включить все js и init cufon.

<!DOCTYPE html>
<html>
  <head>
    <title>Three.js Tetris</title>
    <style>
        body {margin: 0; padding: 0; overflow: hidden;}
 
        #menu {
            position: absolute;
            width: 200px;
            height: 250px;
 
            top: 50%; left: 50%;
            margin: -125px 0 0 -100px;
 
            text-align: center;
 
            border: 3px solid #fff;
            border-radius: 15px;
 
            background-color: #E36B23;
            box-shadow: 2px 10px 5px #888;
        }
 
    #menu p {font-weight: bold; color: #fff;}
    #menu p a {color: #fff;}
 
        #menu button {
            width: 80px;
            height: 25px;
 
            background-color: #C44032;
 
            border: 3px solid #fff;
            border-radius: 5px;
 
            font-size: 14px;
            font-weight: bold;
            color: #fff;
        }
 
        #points {
            position: absolute;
            width: 120px;
            height: 16px;  
 
            padding: 12px;
 
            top: 20px;
            right: 80px;
 
            border: 3px solid #fff;
            border-radius: 15px;
 
            background-color: #E36B23;
            box-shadow: 2px 10px 5px #888; 
 
            font-size: 14px;
            font-weight: bold;
            color: #fff;   
 
            text-align: right;
 
            display: none;
        }
    </style>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
  <body>
    <div id="menu">
        <h1>Three.js Tetris</h1>
    <p>
      Movement: arrows<br>
      Rotation: AD/SW/QE<br>
      Author: <a href="http://www.smashinglabs.pl">smashinglabs.pl</a>
    </p>
        <button id="play_button">Play</button>
    </div>
 
    <div id="points">
        0
    </div>
 
    <script type="text/javascript" src="js/Three.js"></script>
    <script type="text/javascript" src="js/Stats.js"></script>
    <script type="text/javascript" src="js/tetris.js"></script>
 
    <script src="js/cufon-yui.js" type="text/javascript"></script>
    <script src="js/Blox_400.font.js" type="text/javascript"></script> 
 
    <script type="text/javascript">
        Cufon.replace('#menu h1');
        Cufon.replace('#points');
    </script>
  </body>
</html>

 

Базовая структура и инициализация Three.js

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


1

var Tetris = {};

Все объекты, функции и переменные будут членами Tetris. Это очень полезный способ сохранить ваш код и избежать использования «this». Чтобы сделать вещи еще лучше, каждый объект, который я использую, является одиночным. Есть некоторые недостатки — это классический объект бога и смесь всего, но пока он маленький, мне все равно, и вам тоже не следует.

Мы сосредоточимся на этой структуре позже, теперь давайте инициализируем Three.js. Я использовал урок от Aerotwist, который очень простой, но простой и достаточно хороший. Вы, вероятно, должны прочитать это, прежде чем продолжить.

Tetris.init = function() {
  // set the scene size
  var WIDTH = window.innerWidth,
      HEIGHT = window.innerHeight;
 
  // set some camera attributes
  var VIEW_ANGLE = 45,
      ASPECT = WIDTH / HEIGHT,
      NEAR = 0.1,
      FAR = 10000;
 
  // create a WebGL renderer, camera
  // and a scene
  Tetris.renderer = new THREE.WebGLRenderer();
  Tetris.camera = new THREE.PerspectiveCamera(  VIEW_ANGLE,
                                  ASPECT,
                                  NEAR,
                                  FAR  );
  Tetris.scene = new THREE.Scene();
 
  // the camera starts at 0,0,0 so pull it back
  Tetris.camera.position.z = 600;
  Tetris.scene.add(Tetris.camera);
 
  // start the renderer
  Tetris.renderer.setSize(WIDTH, HEIGHT);
 
  // attach the render-supplied DOM element
  document.body.appendChild(Tetris.renderer.domElement);
 
  // to be continued...

 

Представленный Tetris.init создает объекты Three.js и хранит их в глобальном пространстве имен. Есть рендер, сцена и камера. Я хочу, чтобы игра была полноэкранной, поэтому я использую window.innerWidth и window.innerHeight. Вы можете поэкспериментировать с полноэкранным API там. Камера должна быть отодвинута назад, а точное расстояние должно определяться размером и положением используемого вами игрового мира.

Наш игровой мир — это 3D каркасный блок с линиями, показывающими, куда можно опустить боксы. Когда вы работаете над игрой, важно понимать, что конечный эффект — это самое главное. Не бойся обманывать! В этом случае нет веских причин для рисования линий на ограничительной рамке. Мы также можем использовать обычную прямоугольную геометрию, размещать вершину на каждом пересечении линий и соединять вершины линиями вместо обычных треугольников. И, к счастью, для этого есть синтаксис Three.js!

// configuration object
var boundingBoxConfig = {
  width: 360,
  height: 360,
  depth: 1200,
  splitX: 6,
  splitY: 6,
  splitZ: 20
};
 
Tetris.boundingBoxConfig = boundingBoxConfig;
Tetris.blockSize = boundingBoxConfig.width/boundingBoxConfig.splitX;
 
var boundingBox = new THREE.Mesh(
  new THREE.CubeGeometry(
    boundingBoxConfig.width, boundingBoxConfig.height, boundingBoxConfig.depth,
    boundingBoxConfig.splitX, boundingBoxConfig.splitY, boundingBoxConfig.splitZ),
  new THREE.MeshBasicMaterial( { color: 0xffaa00, wireframe: true } )
);
Tetris.scene.add(boundingBox);
 
// first render
Tetris.renderer.render(Tetris.scene, Tetris.camera);
// to be continued...

 API Three.js описывает конструктор CubeGeometry следующим образом:

(width <Number>, height <Number>, depth <Number>, segmentsWidth <Number>, segmentsHeight <Number>, segmentsDepth <Number>, materials <Array>, sides <Object>)

 

и мы используем опции «gmentX », чтобы определить, сколько ящиков можно разместить на нашей игровой доске. Мы также используем каркас опции MeshBasicMaterial для рисования линий вместо треугольников.

Последние действия в функции init:

  Tetris.stats = new Stats();
  Tetris.stats.domElement.style.position = 'absolute';
  Tetris.stats.domElement.style.top = '10px';
  Tetris.stats.domElement.style.left = '10px';
  document.body.appendChild( Tetris.stats.domElement );
 
  document.getElementById("play_button").addEventListener('click', function (event) {
    event.preventDefault();
    Tetris.start();
  });
};

 Мы добавляем статистику FPS и привязываем Tetris.start () к кнопке воспроизведения. Что нужно сделать в start ()?

Tetris.start = function() {
   document.getElementById("menu").style.display = "none";
   Tetris.pointsDOM = document.getElementById("points");
   Tetris.pointsDOM.style.display = "block";
 
   Tetris.animate();
};

 

Скрыть инструкции, показать окно оценки и запустить первую функцию animate ().

Игровой цикл

Вы можете удивиться, почему не было setInterval для анимации. Для этого есть гораздо лучшая функция — requestAnimationFrame (). Вызывает указанные функции, когда браузер не занят, но не более 60 раз в секунду. Это означает, что у вас будет именно то количество FPS, которое возможно отобразить. Не нужно беспокоиться о расчете лучшего временного шага для setInterval или сгустков, если вы пытаетесь отобразить больше FPS, чем это возможно вычислить. Функция все еще является чем-то новым, поэтому поверх нашего скрипта мы разместим код совместимости:

if ( !window.requestAnimationFrame ) {
  window.requestAnimationFrame = ( function() {
    return window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {
      window.setTimeout( callback, 1000 / 60 );
    };
  })();
}

 Теперь нам нужно написать функцию animate (). Тетрис в режиме реального времени, но шаг игры довольно длинный, как один ход по оси Z в секунду. Нам нужно рассчитать, когда нужно переместить наш блок вперед, поэтому необходимо ввести некоторые переменные, связанные со временем:

Tetris.gameStepTime = 1000;
 
Tetris.frameTime = 0; // ms
Tetris.cumulatedFrameTime = 0; // ms
Tetris._lastFrameTime = Date.now(); // timestamp
 
Tetris.gameOver = false;

 Теперь функция animate в упрощенной версии должна вызвать метод рендеринга Three.js и снова вызвать себя, используя requestAnimationFrame. Это сделало бы нашу игру статичной, поэтому нам нужно рассчитать ход времени и действовать, если это было достаточно долго.

Tetris.animate = function() {
  var time = Date.now();
  Tetris.frameTime = time - Tetris._lastFrameTime;
  Tetris._lastFrameTime = time;
  Tetris.cumulatedFrameTime += Tetris.frameTime;
 
  while(Tetris.cumulatedFrameTime > Tetris.gameStepTime) {
    // block movement will go here
    Tetris.cumulatedFrameTime -= Tetris.gameStepTime;
  }
 
  Tetris.renderer.render(Tetris.scene, Tetris.camera);
 
  Tetris.stats.update();
 
  if(!Tetris.gameOver) window.requestAnimationFrame(Tetris.animate);
}

 

Если вы заметили обновление статистики, вы можете подумать, что это немного излишне. Статистика явно рассчитывает FPS, и мы делаем то же самое. Возможно, вы захотите покопаться в статистическом коде и повторно использовать некоторые данные, но я обнаружил, что проще написать свои собственные несколько строк кода. Статистика не была предназначена для такого использования и накладные расходы близки к нулю. Кроме того, если вы хотите удалить счетчик FPS из своей игры, когда она выйдет, это может быть проблемой. Чем меньше зависимостей, тем лучше.

И, наконец, мы должны вызвать init () при загрузке страницы:

window.addEventListener("load", Tetris.init);

 

После этого урока вы должны:

  • Знать, как настроить рендер и сцену с Three.js.
  • Понять, что такое игровой цикл и
  • почему requestAnimationFrame отличный.
  • Знайте, что если это выглядит хорошо, это хорошо.

Захватите источник с github
Если у вас возникли проблемы с любым из них, проверьте учебник еще раз или задайте вопрос в комментариях ниже.