Статьи

Понимание событий DeviceOrientation: 3D-игра с Babylon.js

В Internet Explorer 11 добавлена ​​поддержка некоторых интересных новых событий DOM: событий DeviceOrientation . Эти события предоставляют информацию о физической ориентации и движении текущего оборудования.

W3C опубликовал спецификацию для этих событий: http://www.w3.org/TR/orientation-event/

В этой статье я покажу вам, как использовать эти события в небольшой 3D-игре с мячом «Амига» Sourire.

Окончательный результат будет выглядеть это .

Хотите попробовать это? Просто зайдите туда (вы можете использовать ориентацию устройства или клавиши курсора).

События DeviceOrientation и Babylon.js

Прежде чем подробно рассмотреть спецификацию DeviceOrientation , вы можете посмотреть это видео, показывающее использование ориентации устройства в сцене babylon.js. И, как вы можете видеть, это скалы !

Как работают события DeviceOrientation

Существует два типа данных, предоставляемых событиями DeviceOrientation:

  • Ориентация ( deviceorientation ) : это значение определяет ориентацию физического устройства относительно системы координат, центрированной на Земле. Это выражается в градусах. Три координаты предоставляются:
    • альфа : вращение вокруг оси z
    • бета : вращение вокруг оси х
    • гамма : вращение вокруг оси y

Ось определяется с помощью правостороннего соглашения:

образ

Чтобы понять эти значения, давайте начнем с устройства в ваших руках:

образ

Альфа ориентация изменяется при перемещении устройства вокруг оси Z:

образ

Бета ориентация изменяется при перемещении устройства вокруг оси х:

образ

Наконец, гамма- ориентация изменяется при перемещении устройства вокруг оси y:

образ

  • Движение ( devicemotion ) : это значение определяет ускорение по каждой оси (x, y, z). Значения выражены в м / с² и могут включать (или не включать) влияние силы тяжести). Это событие также может обеспечить скорость вращения (в градусах / с) вокруг каждой оси.

Для получения более подробной информации, пожалуйста, ознакомьтесь с документацией MSDN .

Ориентация извлекается с помощью события « deviceorientation », запускаемого в объекте окна:

window.addEventListener("deviceorientation", moveBall);
function moveBall(evt) {
    if (evt.alpha < 5 || evt.alpha > 355) {
        ball.position.z += 0.1;

    }
}

Движение извлекается с использованием события « devicemotion », запускаемого в объекте окна:

window.addEventListener("devicemotion", detectShake);
function detectShake(evt) {
    var accl = evt.acceleration;
    if (accl.x > 1.5 || accl.y > 1.5 || accl.z > 1.5) {
        // Tilt 🙂
        onLose();
    }
}

Первое очевидное использование этих событий — управление игрой. Вы также можете использовать их для распознавания жестов или для определения ориентации пользователя для использования с картой соответственно.

Создание игры с мячом

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

образ

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

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

Итак, прежде всего вам нужно создать простой HTML-файл со ссылкой на babylon.js (невероятно!):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Device orientation - ball game</title>
    <link href="index.css" rel="stylesheet" />
    <script src="babylon.js"></script>
</head>
<body>
    <canvas id="renderCanvas"></canvas>   
    <div id="score">Score: 0</div>
    <div id="speed">Speed: 1</div>
    <div id="gameOver" class="hidden">
        <div id="gameOverText">Game Over</div>
    </div>
    <script src="index.js"></script>
</body>
</html>

Затем внутри index.js мы можем создать трехмерную среду, необходимую для нашей игры. Первое, что нужно сделать, это создать движок и сцену ;

var canvas = document.getElementById("renderCanvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;

if (BABYLON.Engine.isSupported()) {

    var engine = new BABYLON.Engine(canvas, true);
    var scene = new BABYLON.Scene(engine);
    var light = new BABYLON.DirectionalLight("light", new BABYLON.Vector3(2, -10, 5), scene);
    var camera = new BABYLON.ArcRotateCamera("camera", 3 * Math.PI / 2.0, Math.PI / 4.0, 20.0, new BABYLON.Vector3(0, 0, 0), scene);

    scene.activeCamera = camera;

Вам также потребуется цикл рендеринга, чтобы убедиться, что кадры отрисованы на холсте

engine.runRenderLoop(function () {
    scene.render();
    
    if (!started) {
        return;
    }

});

На данный момент экран немного пуст:

образ

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

// Starfield
var starfield = new BABYLON.ParticleSystem("particles", 4000, scene);
starfield.particleTexture = new BABYLON.Texture("star.png", scene);
starfield.minAngularSpeed = -4.5;
starfield.maxAngularSpeed = 4.5;
starfield.minSize = 0.5;
starfield.maxSize = 1.0;
starfield.minLifeTime = 0.5;
starfield.maxLifeTime = 2.0;
starfield.minEmitPower = 0.5;
starfield.maxEmitPower = 1.0;
starfield.emitRate = 600;
starfield.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE;
starfield.minEmitBox = new BABYLON.Vector3(-25, 0, -25);
starfield.maxEmitBox = new BABYLON.Vector3(25, 0, 25);
starfield.direction1 = new BABYLON.Vector3(0, 1, 0);
starfield.direction2 = new BABYLON.Vector3(0, 1, 0);
starfield.color1 = new BABYLON.Color4(0, 0, 0, 1);
starfield.color2 = new BABYLON.Color4(1, 1, 1, 1);
starfield.gravity = new BABYLON.Vector3(0, 5, 0);
starfield.emitter = new BABYLON.Vector3(0, -2, 0);
starfield.start();

Для получения дополнительной информации о системе частиц, вы можете перейти сюда .

Теперь это начинает хорошо выглядеть:

образ

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

// Ball
var ball = BABYLON.Mesh.CreateSphere("ball", 16, 1.0, scene, false);
var ballMaterial = new BABYLON.StandardMaterial("ballMaterial", scene);
ballMaterial.diffuseColor = new BABYLON.Color3(1, 0, 0);
ballMaterial.diffuseTexture = new BABYLON.Texture("amiga.jpg", scene);
ballMaterial.diffuseTexture.uScale = 3;
ballMaterial.diffuseTexture.vScale = 4;
ball.material = ballMaterial;
ball.position = new BABYLON.Vector3(0, 0.5, 0);
ball.renderingGroupId = 1;
ball.rotationQuaternion = BABYLON.Quaternion.RotationYawPitchRoll(0, 0, 0);
var animationScale = new BABYLON.Animation("scale", "scaling", 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
animationScale.setKeys([{ frame: 0, value: new BABYLON.Vector3(1, 1, 1) }, { frame: 20, value: new BABYLON.Vector3(2.0, 2.0, 2.0) },
                        { frame: 40, value: new BABYLON.Vector3(1, 1, 1) }]);
ball.animations.push(animationScale);

Обратите внимание на использование ball.renderingGroupId = 1, которое позволит мячу (и игровая площадка находиться на другом слое, чем звездное поле, чтобы избежать попадания звезд через игровую площадку).

Более подробную информацию о том, как создавать простые объекты с помощью babylon.js, вы можете найти здесь .

Более подробную информацию о материалах вы можете получить здесь .

Для получения дополнительной информации об анимации, вы можете перейти сюда .

Мяч является центром вселенной:

образ

Детская площадка будет простой плоскостью, текстурированной с рисунком дерева:

// Playground
var ground = BABYLON.Mesh.CreateGround("ground", 20, 20, 1, scene, false);
var groundMaterial = new BABYLON.StandardMaterial("groundMaterial", scene);
groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
groundMaterial.diffuseTexture = new BABYLON.Texture("wood.png", scene);
groundMaterial.diffuseTexture.uScale = 2;
groundMaterial.diffuseTexture.vScale = 2;
ground.material = groundMaterial;
ground.receiveShadows = true;
ground.renderingGroupId = 1;

Игра почти готова:

образ

Чтобы добавить более реалистичный эффект, давайте добавим несколько теней:

// Shadows
var shadowCaster = new BABYLON.ShadowGenerator(1024, light);
light.position = new BABYLON.Vector3(-4, 14, -12.5);
shadowCaster.useVarianceShadowMap = true;
shadowCaster.getShadowMap().renderList.push(ball);

Вы можете получить больше информации о тенях здесь .

И выглядит отлично:

образ

Последнее, что нужно добавить, — это цель (то есть место, куда идти, чтобы получить одно очко). Мы также будем использовать систему частиц здесь:

// Target
var target = new BABYLON.ParticleSystem("particles", 4000, scene);
target.particleTexture = new BABYLON.Texture("star.png", scene);
target.minAngularSpeed = -4.5;
target.maxAngularSpeed = 4.5;
target.minSize = 0.5;
target.maxSize = 3.0;
target.minLifeTime = 0.5;
target.maxLifeTime = 2.0;
target.minEmitPower = 0.5;
target.maxEmitPower = 1.0;
target.emitRate = 200;
target.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE;
target.minEmitBox = new BABYLON.Vector3(-1, 0, -1);
target.maxEmitBox = new BABYLON.Vector3(1, 0, 1);
target.direction1 = new BABYLON.Vector3(0, 1, 0);
target.direction2 = new BABYLON.Vector3(0, 1, 0);
target.color1 = new BABYLON.Color4(1, 1, 0, 1);
target.color2 = new BABYLON.Color4(1, 1, 1, 1);
target.gravity = new BABYLON.Vector3(0, 5, 0);
target.emitter = new BABYLON.Vector3(8, 0, 8);
target.renderingGroupId = 1;
target.start();

Наша игра готова к игре:

образ

Добавление событий DeviceOrientation в нашу игру

Использовать события DeviceOrientation довольно просто. Мяч будет контролироваться с помощью устройства вращения. Для этого вам понадобятся переменные для хранения значений текущего и предыдущего поворота:

var orientationGamma = 0;
var orientationBeta = 0;
var initialOrientationGamma = 0;
var initialOrientationBeta = 0;

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

// Orientation
window.addEventListener("deviceorientation", moveBall);
function moveBall(evt) {
    if (!started) {
        return;
    }
    if (!initialOrientationGamma) {
        initialOrientationGamma = evt.gamma;
        initialOrientationBeta = evt.beta;
    }

    orientationGamma = evt.gamma;
    orientationBeta = evt.beta;
}

window.addEventListener("devicemotion", detectShake);
function detectShake(evt) {
    var accl = evt.acceleration;
    if (accl.x > 1.5 || accl.y > 1.5 || accl.z > 1.5) {
        // Tilt 🙂
        onLose();
    }
}

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

Для простоты я не буду включать код для функций onLoseonWin ), но вы можете найти их в исходном коде игры, доступном ниже.

Затем вы должны обновить renderLoop, чтобы использовать эти значения:

engine.runRenderLoop(function () {
    scene.render();

    
    // Compute direction
    if (orientationGamma) {
        var z = (initialOrientationBeta - orientationBeta) * 0.05;
        var x = (initialOrientationGamma - orientationGamma) * -0.05;
        direction.addInPlace(new BABYLON.Vector3(0, 0, z * speed * scale));
        direction.addInPlace(new BABYLON.Vector3(x * speed * scale, 0, 0));
    }

    // Moving and rotating ball
    ball.position.addInPlace(direction);
    var rotationToApply = BABYLON.Quaternion.RotationYawPitchRoll(0, direction.z * 1.5, -direction.x * 1.5);
    ball.rotationQuaternion = rotationToApply.multiply(ball.rotationQuaternion);

    direction.scaleInPlace(0.95);

    // Collisions
    checkCollisions();
});

Гамма вращение контролирует направление к x и бета к z. Затем шар перемещается соответствующим образом и немного вращается в правильном направлении, чтобы имитировать вращение.

Функция checkCollisions просто проверяет, находится ли мяч внутри игровой площадки и не достигнута ли цель (чтобы затем вызвать функцию onWin ):

// Collisions
var checkCollisions = function() {
    // Target met
    if (BABYLON.Vector3.Distance(ball.position, target.emitter) < 1.2) {
        onWin();
        return;
    }

    var point = ball.position.clone();
    point.y -= 0.5;
    if (!ground.intersectsPoint(point)) {
        onLose();
    }
};

И это все . Теперь у вас есть современная, красивая и управляемая игра!

Полная игра

Полная игра также поддерживает клавиши курсора. Вы можете найти код здесь . Не стесняйтесь использовать его в качестве основы для собственных приложений!

Идти дальше

Если вы хотите пойти дальше, вот несколько указателей: