HTML 5 в браузерах и HTML 5 для Windows 8 Metro в настоящее время серьезные кандидаты для разработки современных игр.
Используя Canvas, вы имеете доступ к аппаратным ускорениям пространства, и если использовать некоторые советы и приемы, то сможете достигнуть великолепных 60 кадров в секунду.
Это действительно важно в играх, потому что чем плавнее происходит игра, тем лучше ощущения игрока.
Целью данной статьи является показать вам некоторые трюки, которые помогут получить максимальную мощность от HTML 5 Canvas.
В следующих главах Я буду использовать примеры, чтобы продемонстрировать концепции о которых Я хочу рассказать. Образецом будет 2D туннельный эффект, который я написал для Coding4Fun и представил на TechDays 2012 году во Франции (http://video.fr.msn.com/watch/video/techdays-2012-session-technique-coding4fun/zqy7cm8l).
Этот эффект в основном вдохновлен кодом Commodore Amiga, который я написал, когда был молодым демомейкером в 80-х.
Используется только canvas и JavaScript (когда-то исходный код был только ассемблере 68000):
Готовый код можно взять тут: http://www.catuhe.com/msdn/canvas/tunnel.zip
Данная статья не направлена на объяснение того, как устроен тyнель, но Вы можете начать именно с него и посмотреть на достижения в области оптимизации в реальном времени.
Использование закадрового холста, для чтения данных изображения
Первое, о чем я хочу рассказать — это то, как Canvas, может помочь вам читать данные изображения. Действительно, каждая игра, использует графические спрайты или фон. Canvas имеет очень полезный метод, для отображения изображения: DrawImage. Эта функция может быть использована для создания спрайта на холсте, потому что вы можете определить начальные и конечные координаты.
Но иногда этого не достаточно. Например, этого недостаточно, если вы хотите применить некоторые эффекты на исходном изображении. Или когда исходное изображение не является простым растровым а более сложный ресурс для вашей игры (например, карту, где вы должны считывать данные с формы).
В таких случаях необходимо получить доступ к внутренним данным изображения. Но у изображения нет необходимых нам тегов. И тогда на помощь приходит Canvas!
Конечно, каждый раз, читая данные изображения, Вы можете использовать закадровый Canvas. Основная идея здесь заключается в загрузке изображения и когда оно загружено, Вы просто должны направить это в Canvas (не включено в DOM). Вы можете получить каждый пиксель исходного изображения, читая пиксель в Canvas (на самом деле всё просто).
Ниже продемонстрирован код для этого метода(используется 2D туннельный эффект для чтения текстуры данных):
var loadTexture = function (name, then) { var texture = new Image(); var textureData; var textureWidth; var textureHeight; var result = {}; // on load texture.addEventListener('load', function () { var textureCanvas = document.createElement('canvas'); // off-screen canvas // Setting the canvas to right size textureCanvas.width = this.width; //<-- "this" - это изображение textureCanvas.height = this.height; result.width = this.width; result.height = this.height; var textureContext = textureCanvas.getContext('2d'); textureContext.drawImage(this, 0, 0); result.data = textureContext.getImageData(0, 0, this.width, this.height).data; then(); }, false); // Loading texture.src = name; return result; };
Используя этот код, Вы должны обратить внимание, на то, что загрузка текстуры асинхронная и поэтому вы должны использовать функцию-параметр, чтобы продолжить ваш код:
// Texture var texture = loadTexture("soft.png", function () { // Launching the render QueueNewFrame(); });
Используйте функции аппаратного масштабирования
Современные браузеры и Windows 8 поддерживают аппаратное ускорение canvas. Это позволяет использовать нам, например GPU для масштабирования холста.
В случае 2D туннельного эффекта, алгоритм требует, обработки каждого пикселя холста. Так, например, для 1024×768, canvas необходимо обработать 786432 пикселей. А чтобы все происходило плавно, нужно повторять это 60 раз в секунду, что соответствует 47185920 пикселей в секунду!
Очевидно, что уменьшение размера полотна, поможет вам в сокращении количества пикселей которые нужно обработать, и это кардинально улучшает общую производительность.
И снова на помощь приходит canvas! В коде расположенном ниже, показано как использовать аппаратное ускорение для масштабирования рабочей области в буфер и взаимодействовать с внешними размеры объекта DOM:
canvas.width = 300; canvas.style.width = window.innerWidth + 'px'; canvas.height = 200; canvas.style.height = window.innerHeight + 'px';
Стоит отметить разницу между размером DOM объекта (canvas.style.width и canvas.style.height), и размером рабочего буфера хослта (canvas.width и canvas.height).
Когда есть разница между этими двумя размерами, оборудование используется для масштабирования рабочего буфера и в нашем случае это отличная вещь: мы можем работать на меньшем размере холста, позволив GPU изменяет масштаб, чтобы соответствовать объекту DOM (с использованием фильтров, для придания плавности).
В данном случае, операции производятся в разрешении 300x200px, а графический процессор будет масштабировать его до размеров вашего окна.
Эта особенность широко поддерживается всеми современными браузерами так что вы можете брать это в рассчёт.
Оптимизируйте ваши циклы отрисовки
Когда вы пишете игру, вы должны использовать цикл, который рисует компоненты игры (фон, спрайты, очки и т.д..). Этот цикл является основой вашего кода и должен быть наиболее оптимизированным, чтобы убедиться, что ваша игра шла без задержек и плавно.
RequestAnimationFrame
Одним из интересных добавлений в HTML 5 является функция window.requestAnimationFrame. Вместо использования window.setInterval создающей таймер, который вызывает рендеринг каждого цикла (1000/16) миллисекунд (для достижения хорошего 60 кадров в секунду), Вы можете позволять это браузеру с поддержкой requestAnimationFrame. Вызов этого метода указывает, на то, что вы хотите получить от браузера как можно быстрее связанные с графикой вещи.
Браузер будет включать ваш запрос внутри своего собственного рендеринга графики, и будет синхронизировать ваш рендеринг со своим, включая анимации (CSS, переходы и т.д. …). Это решение также интересно, потому что ваш код не будет вызван, если окно не отображается (свернуто, не входит в область видимости, и т.д.).
Это может помочь повысить производительность, так как браузер может оптимизировать параллельный рендеринг (например, если ваш цикл рендеринга слишком медленный), а также способствовать более плавной анимации.
Код довольно очевидный (обратите внимание на использование конкретных префиксов в браузерах):
var intervalID = -1; var QueueNewFrame = function () { if (window.requestAnimationFrame) window.requestAnimationFrame(renderingLoop); else if (window.msRequestAnimationFrame) window.msRequestAnimationFrame(renderingLoop); else if (window.webkitRequestAnimationFrame) window.webkitRequestAnimationFrame(renderingLoop); else if (window.mozRequestAnimationFrame) window.mozRequestAnimationFrame(renderingLoop); else if (window.oRequestAnimationFrame) window.oRequestAnimationFrame(renderingLoop); else { QueueNewFrame = function () { }; intervalID = window.setInterval(renderingLoop, 16.7); } };
Чтобы использовать эту функцию, Вы должны просто добавить его вызов в конце вашего цикла рендеринга, чтобы зарегистрировать следующий кадр:
var renderingLoop = function () { ... QueueNewFrame(); };
Доступ к DOM (Document Object Model)
Для оптимизации основных циклов, Вы должны следовать, по крайней мере, одному золотому правилу: не иметь доступа к DOM. Даже если современные браузеры оптимизированы в этой области, чтение свойства DOM объекта по-прежнему является тормозом в работе цикла.
Например, в моём коде, используя профайлер Internet Explorer 10(панель разработчика открывается по клавише F12), результат налицо:
Как вы можете видеть, основное время занимает доступ к ширине и высоте холста.
Начальный исходный кода был таким:
var renderingLoop = function () { for (var y = -canvas.height / 2; y < canvas.height / 2; y++) { for (var x = -canvas.width / 2; x < canvas.width / 2; x++) { ... } } };
Вы можете удалить canvas.width и canvas.height свойства с 2 переменными объявленными заранее.
var renderingLoop = function () { var index = 0; for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) { for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) { ... } } };
Просто, не так ли? Возможно это трудно понять, но Вам стоит это попробовать!
Предварительные вычисления
Судя по данным профайлера, функция Math.atan2 достаточно медленная. Все из-за того, что нет хорошей оптимизации со стороны CPU для JavaScript, чтобы ускорить выполнение нужно модифицировать код.
В общем случае, если вы можете заранее выполнять некоторые длительные операции — делайте это. Здесь, Я вычисляю результат Math.atan2:
var atans = []; var index = 0; for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) { for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) { atans[index++] = Math.atan2(y, x) / Math.PI; } }
Массив atans можно заполнять внутри цикла, это может существенно повысить производительность.
Избегайте использования Math.round, Math.floor и ParseInt
Последним стопорным моментом является использование ParseInt:
При использовании холста, вы должны оперировать пикселями, используя целочисленными координатами (х, у). Действительно, все ваши вычисления производятся с использованием чисел с плавающей точкой, и вы должны преобразовать их в целое число для того чтобы использовать их.
JavaScript позволяет использовать Math.round, Math.floor или даже ParseInt для преобразования числа в целое. Но эта функция делает некоторую лишнюю работу (например, чтобы проверить диапазоны или проверить, является ли это вообще числом. ParseInt вовсе в самом начале преобразует параметр в строку!). В моем цикле рендеринга, я хочу использовать наиболее быстрый способ для выполнения этого преобразования.
Вспоминая моих старый ассемблерный код, я использовал небольшую хитрость: вместо использования ParseInt, вы просто должны перенести ваш номер вправо со значением 0. Выполнение перемещения значения из дробного регистра в целочисленный и используется аппаратное преобразование. Сдвиг значения вправо с нулем 0 позволит сохранить его без изменений, и поэтому вы можете получить обратно своё целочисленное значение.
Изначальный исходный код был:
u = parseInt((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u);
А оптимизированный выглядит так:
u = ((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u) >> 0;
Конечно, используя данное решения, Вы должны быть уверены, что значение является числом.
Конечный результат
После всех оптимизаций вы получите подобный результат:
Как можно заметить, мы оптимизировали только основные функции.
Посмотрим как выглядит тунель в самом начале (без оптимизации):
И после применения всего вышеописанного:
Мы можем посторить график использования оптимизации, который отображает количество кадров, от оптимизации кода.
Заглядывая в будущее
С помощью этих ключевых моментов, можно проиховодить оптимизацию в режиме реального времени, а также достигать высокой производительности в современных браузеров или Windows 8!
Перевод статьи от David Catuhe, оригинал находится тут: http://blogs.msdn.com/b/eternalcoding/archive/2012/03/22/unleash-the-power-of-html-5-canvas-for-gaming-part-1.aspx
Все неточности, готов выслушать в личных сообщениях.
ссылка на оригинал статьи http://habrahabr.ru/post/166667/
Добавить комментарий