Графика и разработка браузерных игр

Технопарк, весна, 2019 г.

Графика в браузере
Слайды доступны по ссылке
frontend-park-mailru.firebaseapp.com

Графика в браузере

Элемент <canvas>

Элемент <canvas> (англ. canvas — «холст», рус. канва́с) — это HTML5 элемент, предназначенный для создания растрового двухмерного изображения на web-страницах с помощью программирования на JavaScript

Подробный туториал по canvas — на MDN

<canvas id="canvas" width="300" height="150"></canvas>

Контекст рендеринга

		const canvas = document.createElement( 'canvas' );
		document.appendChild(canvas);
		 
		// Объект двумерного контекста
		const context = canvas.getContext( '2d' );
		// Объект WebGL (3D) контекста
		const webgl = canvas.getContext( 'webgl' );
		 
	

Рисование примитивов

Рисование примитивов:
прямоугольник

		// обводит прямоугольную область
		ctx.strokeRect(x, y, width, height);
		// заливает прямоугольную область
		ctx.fillRect(x, y, width, height);
		// очищает прямоугольную область
		ctx.clearRect(x, y, width, height);
		 
	

Рисование примитивов:
прямоугольник

		// толстая рамка
		ctx.fillRect(25, 25, 250, 250);
		ctx.clearRect(100, 100, 100, 100);
		 
		// квадраты в центре
		ctx.strokeRect(110, 110, 80, 80);
		ctx.strokeRect(120, 120, 60, 60);
		ctx.strokeRect(130, 130, 40, 40);
		ctx.strokeRect(140, 140, 20, 20);
		 
	

Рисование примитивов: линии

		ctx.beginPath();     // создаёт новую линию (новый путь)
		 
		ctx.moveTo(x1, y1);  // передвигает перо в нужную точку
		ctx.lineTo(x2, y2);  // проводит линию до другой точки
		ctx.stroke();        // обводит созданный путь
		ctx.fill();          // заливает область, обведённую путём
		                     // "nonzero" or "evenodd"
		 
		ctx.closePath();     // завершает редактирование пути
		 
	

Рисование примитивов: линии

		// верхний треугольник
		ctx.beginPath();
		ctx.moveTo(50, 50);
		ctx.lineTo(50, 200); ctx.lineTo(200, 50); ctx.lineTo(50, 50);
		ctx.closePath();
		ctx.fill();
		 
		// нижний треугольник
		ctx.beginPath();
		ctx.moveTo(250, 250);
		ctx.lineTo(250, 100); ctx.lineTo(100, 250); ctx.lineTo(250, 250);
		ctx.closePath();
		ctx.stroke();
		 
	

Рисование примитивов: дуги

		// создаёт путь-дугу
		ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
		// x, y - центр дуги
		// radius - радиус дуги (в радианах)
		// startAngle - начальный угол
		// endAngle - конечный угол
		// anticlockwise - проводить против часовой стрелки
		 
	

Рисование примитивов: дуги

		ctx.beginPath();
		ctx.arc(100, 100, 50, -Math.PI, 0);
		ctx.closePath(); ctx.fill();
		 
		ctx.beginPath();
		ctx.arc(100, 110, 40, 0, Math.PI);
		ctx.closePath(); ctx.fill();
		 
		ctx.beginPath();
		ctx.arc(200, 200, 50, -Math.PI, 0);
		ctx.arc(220, 200, 30, 0, Math.PI);
		ctx.closePath(); ctx.stroke();
		 
	

Рисование примитивов:
кривые-Безье

		// квадратичная кривая
		ctx.quadraticCurveTo(cp1x, cp1y, x, y);
		// кубическая кривая
		ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
		// cpx, cpy - опорные точки
		// x, y - куда вести
		 
	

Рисование примитивов:
кривые-Безье

		ctx.beginPath();
		ctx.moveTo(75, 25);
		 
		ctx.quadraticCurveTo(25, 25, 25, 62.5);
		ctx.quadraticCurveTo(25, 100, 50, 100);
		ctx.quadraticCurveTo(50, 120, 30, 125);
		ctx.quadraticCurveTo(60, 120, 65, 100);
		ctx.quadraticCurveTo(25, 25, 25, 62.5);
		ctx.quadraticCurveTo(125, 100, 125, 62.5);
		ctx.quadraticCurveTo(125, 25, 75, 25);
		 
		ctx.stroke();
		 
	

Рисование примитивов:
прямоугольные пути

		// добавляет прямоугольный путь к текущему пути (не рисует)
		ctx.rect(x, y, width, height);
		// x, y - координаты левого верхнего угла
		 
	

Рисование примитивов: текст

		ctx.fillText(text, x, y, maxWidth);
		ctx.strokeText(text, x, y, maxWidth);
		 
		// text - текст
		// x, y - координаты точки начала
		// maxWidth - опциональное ограничение по ширине
		 
	

Рисование примитивов: Path2D

		new Path2D('M10 10 h 80 v 80 h -80 Z');
		// - создать из SVG

		Path2D.addPath(path [, transform])
		// path - добавить путь
		 
		const rectangle = new Path2D();
		rectangle.rect(10, 10, 50, 50);
		ctx.stroke(rectangle);
	

Стилизация

Стилизация линий

		// ширина линий
		ctx.lineWidth = value;
		 
		// пунктир
		ctx.setLineDash(segments);  // шаблон (массив чисел)
		ctx.getLineDash();          // получить текущий шаблон
		ctx.lineDashOffset = value; // установить смещение (число)
		 
	

Стилизация линий

			ctx.beginPath();
			[1, 5, 10, 20, 50].forEach((v, i) => {
			    ctx.beginPath();
			    ctx.lineWidth = v; ctx.setLineDash([5, 5, 50, 5, 5, v]);
			    ctx.moveTo(20, 30 + 55 * i); ctx.lineTo(280, 30 + 55 * i);
			    ctx.stroke();
			});
			 
		

Стилизация линий

		// окончание линий
		ctx.lineCap = 'butt';    // обрезанные концы
		ctx.lineCap = 'round';   // скруглённые концы
		ctx.lineCap = 'square';  // квадраты
		 
		// оформление углов
		ctx.lineJoin = 'bevel';  // срезанные углы
		ctx.lineJoin = 'round';  // скруглённые углы
		ctx.lineJoin = 'miter';  // острые углы
		 
	

Стилизация линий

		const lineJoin = ['round', 'bevel', 'miter'];
		ctx.lineWidth = 30;
		lineJoin.forEach((v, i) => {
		    ctx.lineJoin = v; ctx.beginPath();
		    ctx.moveTo(70, 110 + i * 80);
		    ctx.lineTo(150, 30 + i * 80);
		    ctx.lineTo(230, 110 + i * 80);
		    ctx.stroke();
		});
		 
	

Стилизация линий

		ctx.lineWidth = 20;
		const lineCap = ['butt', 'round', 'square'];
		// Рисуем линии
		lineCap.forEach((v, i) => {
		    ctx.lineCap = v; ctx.beginPath();
		    ctx.moveTo(50 + i * 100, 20);
		    ctx.lineTo(50 + i * 100, 280);
		    ctx.stroke();
		});
		 
	

Использование цветов

		ctx.fillStyle = color;   // задаёт стиль заливки
		ctx.strokeStyle = color; // задаёт стиль обводки
		ctx.globalAlpha = value; // задаёт уровень прозрачности (0.0 .. 1.0)
		 
		ctx.fillStyle = 'orange';
		ctx.fillStyle = '#FFA500';
		ctx.fillStyle = 'rgb(255, 165, 0)';
		ctx.fillStyle = 'rgba(255, 165, 0, 1)';
		 
	

Использование цветов

		ctx.fillStyle = 'yellow';        ctx.fillRect(0, 0, 150, 150);
		ctx.fillStyle = '#6C0';          ctx.fillRect(150, 0, 150, 150);
		ctx.fillStyle = '#0099FF';       ctx.fillRect(0, 150, 150, 150);
		ctx.fillStyle = 'rgb(255,48,0)'; ctx.fillRect(150, 150, 150, 150);
		ctx.fillStyle = 'white';
		 
		for (let i = 0; i < 7; i++) {
		    ctx.beginPath();
		    ctx.fillStyle = 'rgba(255,255,255,' + (i + 1) / 12 + ')';
		    ctx.arc(150, 150, 10 + 20 * i, 0, Math.PI * 2, true);
		    ctx.fill();
		}
		 
	

Использование градиентов

		// создание линейного градиента
		let gradient = ctx.createLinearGradient(x1, y1, x2, y2);
		// создание радиального градиента
		gradient = ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
		// добавление контрольной точки
		gradient.addColorStop(position, color);
		 
		ctx.fillStyle = gradient;
		ctx.strokeStyle = gradient;
		 
	

Использование градиентов

		const linear = ctx.createLinearGradient(0, 150, 0, 0);
		const radial = ctx.createRadialGradient(150, 150, 0, 150, 150, 151);
		[linear, radial].forEach(grad => {
		    grad.addColorStop(0, 'yellow');
		    grad.addColorStop(0.5, 'green');
		    grad.addColorStop(0.99, 'blue');
		    grad.addColorStop(1, 'white');
		});
		 
		ctx.fillStyle = linear;
		ctx.fillRect(0, 0, 300, 150);
		ctx.fillStyle = radial;
		ctx.fillRect(0, 150, 300, 150);
		 
	

Прочее

		// создание паттерна
		let pattern = ctx.createPattern(image, type);
		 
		// тени
		ctx.shadowOffsetX = float;
		ctx.shadowOffsetY = float;
		ctx.shadowBlur = float;
		ctx.shadowColor = color;
		 
	

Трансформации

Простые трансформации

		// перенос точки отсчёта
		ctx.translate(x, y);
		 
		// поворот осей
		ctx.rotate(angle);
		 
		// масштабирование по осям
		ctx.scale(x, y);
		 
		 
	

Комплексные трансформации

		// перенос точки отсчёта
		ctx.transform(a, b, c, d, e, f);
		// a - горизонтальный масштаб
		// b - горизонтальный скос
		// c - вертикальный скос
		// d - вертикальный масштаб
		// e - горизонтальное смещение
		// f - вертикальное смещение
		 
	

Комплексные трансформации

		// сброс текущей трансформации
		ctx.resetTransform();
		 
		// сброс текущей и установка новой трансформации
		ctx.setTransform(a, b, c, d, e, f);
		 
	

Сохранение данных

		// сохранить все настройки, в том числе трансформации!
		ctx.save();
		
		// вернуть предыдущие из стека
		ctx.restore();
	

А так же

Практика! Ветка lesson-7-canvas

Анимации

Алгоритм работы с анимациями

ПРИМЕР

Правильный алгоритм работы
с анимациями

ПРИМЕР

Как ждать?

setInterval

		function animation() {
		    redraw(); // перерисовываем кадр
		}
		 
		const id = window.setInterval(animation, 1000 / 60); // 60 fps
		 
		// хотим прервать анимацию
		window.clearInterval(id);
		 
	

setTimeout

		let id = null;
		 
		function animation() {
		    redraw(); // перерисовываем кадр
		    id = window.setTimeout(animation, 1000 / 60); // 60 fps
		}
		 
		animation();
		 
		// хотим прервать анимацию
		window.clearTimeout(id);
		 
	

requestAnimationFrame

		let animationFrameId = null;
		 
		function animation() {
		    redraw(); // перерисовываем кадр
		    animationFrameId = window.requestAnimationFrame(animation);
		}
		 
		animation();
		 
		// хотим прервать анимацию
		window.cancelAnimationFrame(animationFrameId)
		 
	

PRO TIPs хорошей анимации

requestAnimationFrame

		let last = perfomance.now(); // точное время в наносекундах
		 
		function animation(now) { // передаётся текущее perfomance.now()
		    const delay = now - last;
		    redraw(delay); // перерисовываем кадр
		 
		    window.requestAnimationFrame(animation);
		}
		 
		animation(perfomance.now());
		 
	

Оптимизации производительности

"Пишем физику на JavaScript" open_in_new
доклад с конференции

Практика! Ветка lesson-7-canvas

Перерыв

Как делать игры?

Секрет написания крутой, масштабируемой игры — правильное планирование архитектуры

Основные абстракции


"Категории" игровых механик

Игровые режимы

Режимы, обязательные к реализации

Распространённый, но
неправильный подход

Сначала реализуем singleplayer на js, а потом доработаем и добавим туда многопользовательность...

SinglePlayer — это специальная версия MultiPlayer

Правильный подход

Итак, делаем хорошо

Мультиплеер в быстрых играх open_in_new
цикл статей об особенностях реализации многопользовательских online-игр

Практика
Ветка lesson-7-game

Дополнительные возможности
WebGL, FullScreen API, PointerLock API, WebVR

"Играем в браузере"open_in_new
доклад с конференции

Pointer Lock API

Pointer Lock API — это браузерный API, позволяющий создавать интерфейсы для работы с мышкой на основе ее относительных перемещений , а не только абсолютной позиции курсора. Это дает нам доступ к необработанным движениям мыши, прикрепляет курсор мыши к любому элементу в окне браузера, предоставляет возможность вычислять координаты мыши не ограниченной областью окна проекции, и скрывает курсор из поля зрения

Pointer Lock API

		document.addEventListener('pointerlockchange', lockStatusChange);
		canv.onclick = canv.requestPointerLock;
		function lockStatusChange() {
		    if (document.pointerLockElement === canv){
		        document.addEventListener('mousemove', update);
		    } else {
		        document.removeEventListener('mousemove', update);
		    }
		}
		 
	

Pointer Lock API

		// обработчик события
		function update(event) {
		    // событие содержит смещение
		    const dx = event.movementX;
		    const dy = event.movementY;
		    animate(dx, dy);
		}
		 
	

Request Full Screen API

Request Full Screen API — позволяет отображать элемент в полноэкранном режиме. Метод может применяться к любому элементу, даже, например, к простому блоку

			element.requestFullscreen();
			document.exitFullscreen();
			 
		
Пример

Quake

WebGl

WebGL (Web Graphics Library) — программная библиотека для языка JavaScript предназначенная для визуализации интерактивной трехмерной графики и двухмерной графики в пределах совместимости веб-браузера без использования плагинов. WebGL приносит в веб трехмерную графику, вводя API, который построен на основе OpenGL ES 2.0 , что позволяет его использовать в элементах canvas HTML5

Преимущества WebGl

Шейдеры

Шейдеры — программы, которые работают на GPU. Шейдеры пишутся на специальном языке: OpenGL ES Shader Language (известный как ES SL). ES SL имеет переменные своих собственных типов данных и свои специфические встроенные функции. В свою очередь ES SL основан на C++. ES SL также называют GLSL, что означает Graphics Library Shader Language (язык программирования шейдеров графической библиотеки)

Виды шейдеров

Растеризация

Вершинный шейдер

		<script type="x-shader/x-vertex" id="vshader">
		  varying vec2 vUv;
		  void main() {
		    vUv = uv;
		    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
		    gl_Position = projectionMatrix * mvPosition;
		  }
		</script>
		 
	

Фрагментный шейдер

		<script type="x-shader/x-fragment" id="fshader">
		  uniform sampler2D u_Sampler;
		  varying vec2 vUv;
		  void main() {
		    gl_FragColor = texture2D(u_Sampler, vUv);
		  }
		</script>
		 
	

Практическое применение WebGL

Примеры использования шейдеров

Примеры 3D

experiments.withgoogle.com open_in_new

Device orientation

		if (window.innerHeight > window.innerWidth) {
		  // do smth
		}
		
	

Device orientation and motion

Пример

WebVR

WebVR обеспечивает поддержку для использования устройств виртуальной реальности — например, шлемы виртуальной реальности, таких как Oculus Rift или HTC Vive — для веб-приложений, позволяя разработчикам передавать информацию о местоположении и движения с дисплея в движение вокруг 3D-сцены.

Примеры Web VR

AR

Stay tuned...

Звук
Web Audio & Web Speech

HTML5 audio

		<audio
			src="/music.ogg"
			autoplay
		>
			Ваш браузер не поддерживает audio элемент.
		</audio>
	

Web Audio

Web audio API — мощный и многогранный инструмент для манипуляции звуковой составляющей на веб-странице, что дает возможность разработчикам выбрать источники, добавить к ним специальные звуковые эффекты (такие как panning), визуализировать их и многое другое.

Примеры Web Audio API

Web Speech

Web Speech API позволяет взаимодействовать с голосовыми интерфейсами в ваших веб приложениях. Web Speech API состоит из двух частей: SpeechSynthesis (Текст-в-Речь), и SpeechRecognition (Асинхронное распознавание речи)

Примеры Speech API

Полезные ссылки

Всем спасибо!