Как стать автором
Обновить

Комментарии 61

дак скачать можно
Ссылка в конце статьи, там архив. Распаковываем и пробуем.
Добавил live пример
Вот мой пример. Код грязноват, ибо для теста делалось.
Собирался написать движок для браузерной игры, но решил что 2d-canvas слишком медленный для этого.
Теперь пишу под WebGL. В целом доволен.
Карту каждый кадр перерисовываете или только объекты?
Каждый кадр.
Карту имело смысл отрисовать один раз в отдельном слое или в буфер. Скорость была бы вполне приемлимая.
Я специально этого не сделал. Придется иметь дело с системами частиц, а с ними такие трюки не сработают.
Тест он и есть тест.
ЭЭ. С какими системами частиц? Вы могли карту кешировать в буфере и отрисовывать её однажны как картинку. Было бы вполне производительно.
Я некорректно выразился. В будущем предполагается использование систем частиц, которые будут отрисовываться динамически. К карте они отношения не имеют, но нагрузку создадут соизмеримую.
Интересно, как тогда реализовать, например, реакцию на наведение курсора на объект. Мельница имеет сложный контур и простая прямоугольная область не очень подходит. Рисовать для каждого объекта его приблизительный контур и смотреть внутри него через isPointInPath?
Обычно в играх как раз по ректанглу смотрят.
В случае с мельницей — я за прямоугольный баундинг бокс.
Если контур будет сложнее, то выделение будет мерцать при наведении курсора на движущиеся лопасти.
Почему интервал 1000/16? Перерисовка происходит постоянно? Тормозить не будет на слабеньких машинах?
В нете находил, что критическая отметка ниже 15 (1000/60). После нее начинается разброд в браузерах по выполнению Интервала. В статьях, как заметил, чаще используется 60. В принципе значение можно варьировать и подгонять скорость анимации через speed параметр.
Просто 16 fps — вообще нестандартно как-то. Можно было воспользоваться requestAnimationFrame — это было бы идеальное решение.
Меня смутило, что requestAnimationFrame еще находится в доработке.
Для мультипликации и аниме стандарт вроде бы вообще 14 кадров в секунду…
А можно пруф? Не вижу смысла особого, просто.
Наверное, все слышали про 25 кадров в секунду (этого достаточно для кинематографа). Но аниме раньше рисовали вручную, например, карандашами. Чем меньше кадров в секунду — тем меньше рисунков, очевидно.

Кусок текста с одного из случайных сайтов:
С технической точки зрения "Акира" для конца 1980-х был настоящим аниме-совершенством. Это было первое аниме с частотой 24 кадра в секунду (промышленный аниме-стандарт - 12-15 кадров в секунду).

Возможно, я и ошибаюсь.
Конечно слышал. В кинематографе применяется приблизительно 24(PAL) или 30(NTSC) fps. Также есть 25 fps, 29,970 и т.д. (в кинематографе на самом деле со стандартами даже хуже, чем в браузерах). Раньше использовалось 18 или 12 кадров в секунду.
Я просто не понимаю смысла в 14/16 fps)
Они даже непропорциональны ни одному из существующих стандартов)
Уже давно мультипликацию делают в 3D пакетах, например южный парк делаю в Maya, и вот зачем им 14 кадров?
Те, кто делает анимацию в Maya, могут позволить себе делать и 60, и 120 кадров в секунду. А те, кто по-старинке рисует карандашом или кистями, не могут себе такую роскошь позволить.

15 кадров в секунду — это половина от 30 кадров для NTSC, 12 — половина от PAL.
И верно, я погорячился. 14 fps незачем.
Имхо на глаз у них 10 FPS + keyframe анимация. Южный парк не пример гладкости отображения :)
Если судить по играм, то 13-15 кадров вполне хватает для ощущения плавности.
Хватает. Но плавность какая-то дёрганая.
У меня подобные анимации есть в LibCanvas. Они используются в Asteroids для анимации взрыва, огня и корабля.

Корабль анимируется вообще просто. Так инициируется анимация:

this.animation = new Animation()
	.addSprites(this.libcanvas.getImage('ship'), 60)
	.run({
		line : Array.range(0,8),
		delay: 40,
		loop : true
	});


А так отрисовывается корабль:

draw: function () {
	if (this.hidden) return;

	this.drawEngines();

	this.libcanvas.ctx.drawImage({
		image : this.animation.getSprite(),
		center: this.position,
		angle : this.angle + (90).degree()
	});
},


Анимация корабля создаётся приблизительно так:

var image = this.libcanvas.getImage('fire');
return new LibCanvas.Animation()
	.addSprites({
		small : image.sprite(  0, 0, 20, 140),
		med1  : image.sprite( 20, 0, 20, 140),
		med2  : image.sprite( 40, 0, 20, 140),
		med3  : image.sprite( 60, 0, 20, 140),
		big1  : image.sprite( 80, 0, 20, 140),
		big2  : image.sprite(100, 0, 20, 140),
		big3  : image.sprite(120, 0, 20, 140)
	})
	.add({
		name : 'start',
		line: ['small', 'small', 'med1', 'med2'],
		delay: 40
	})
	.add({
		name : 'moving',
		loop : true, // Анимация зацикливается и меняется до ручной остановки
		line : ['big1', 'big2', 'big3'],
		delay : 40
	})
	.add({
		name : 'end',
		line: ['med2', 'med1', 'small', 'small'],
		delay: 40
	});


Потом она используется как-то так:
onMoveStart: function () {
	this.fireAnimation
		.stop(true)
		.run('start')
		.run('moving');
},
onMoveStop: function () {
	this.fireAnimation
		.stop(true)
		.run('end');
}


Аналогично сделана анимация взрыва, при этом, при окончании анимации — элемент удаляется, чтобы мог быть убран сборщиком мусора

this.animation = new Animation()
	.addSprites(this.libcanvas.getImage('explosion'), 162)
	.run({
		delay: 40,
		line : Array.range(0, 9)
	})
	.addEvent('stop', function () {
		this
			.stopDrawing()
			.fireEvent('stop');
	}.bind(this))
Вместо «Анимация корабля создаётся приблизительно так» необходимо читать «Анимация огня создаётся приблизительно так:»
если в drawImage передать Animation — оно само перерисовываться будет, или оно мне только текущий активный спрайт выдаст? А вызывать с нужным( и не известным мне) интервалом — моя забота?
При чём тут интервал? Вы вообще про интервалы не думаете. Если ваш объект необходимо перерисовать — у него вызывается мeтод draw. drawImage — это тот же метод .getContext('2d').drawImage, но с возможностью задавать именованные параметры, а также вращать изображение. Минимальный код выглядит как-то так:

var Explosion = atom.Class({
	Extends : Drawable,

	position : null,
	animation: null,

	initialize : function (position, image) {
		this.position = position;

		this.animation = new Animation()
			.addSprites(image, 162)
			.run({
				delay: 40,
				line : Array.range(0, 9)
			});
	},

	draw : function () {
		this.libcanvas.ctx.drawImage({
			image : this.animation.getSprite(),
			center: this.position
		});
	}
});


Подключаем такой файл и у нас есть анимация взрыва:
Ну прости меня конечно, но как я узнаю что обьект надо перерисовать, если он даже сам явно не знает когда у него спрайт обновляется.
Или ты противоречишь своим же подколкам и перерисовываешь всю сцену?
По ссылке на подколки, а простое любопытство.
Иногда — перерисовываю всю сцену, иногда раскладываю на слои и перерисовываю только нужный слой, иногда — стираю старый объект и рисую новый.
В частности в Asteroids нету смысла что-то оптимизировать в этом плане — абсолютно все объекты игры — динамические, изменяются каждый кадр и просчитывать изменившиеся участки — бесполезно, изменилось всё, что есть на экране.

Если у нас только есть какая-либо анимация и есть возможность изменять только один объект, то можно прицепится к событию смены кадра (например, у нас есть пятнашки, где каждая пятнашка — драгоценный камень, который сверкает раз в 3 секунды):

var MyObject = atom.Class({
	[...],

	initialize : function () {
		this.animation = new Animation()
			[...]
			.addEvent( 'changed', this.redraw.bind(this) );
	},

	redraw: function () {
		this.clear().draw();
	},

	draw : function () {
		this.libcanvas.ctx.drawImage(
			this.animation.getSprite(),
			this.shape
		);
	}
});


Класс Animation вообще не должен знать ни про обновление холста, ни про того, кто его вызывает. Он должен получать список кадров, выводить нужные анимации и дергать привязанные события.
Умная чтука у тебя, осталось прикрутить на это нормальный SceneGraph и клипинг
Можете помочь, если есть желание)
А тебе лучше помогу создав конкуренцию :)
А есть наработки уже, посмотреть чо?
Пока нет, иначально была немного другая ниша, только недели две назад начал собирать по сусекам нужные куски, скажем так, «из твоей области»
Подвигли на это дело, как не странно, твои танки
Но автомагия SceneGraph это было первое что начал портировать их запасов бытности геймдевелопером.
Именно танки — не мои, а форк)
В чём суть SceneGraph? Нету ли смысла объединиться, тем более, я так понял, у нас наработки из разных областей. ОпенСорс сообществу лучше держаться вместе, а не врозь.
SceneGraph это в общем случае обычное дерево, хотя может иметь и циклы.
Его задача правильно отрисовать сцену.
Тут подразумевается и порядок объектов и отработка скрытых поверхностей(pbuffer в смысле), в случае WebGL еще и выбор оптимального порядка наложение шейдеров\данных чтобы DIP не словить.
В случае с 2д канвасом это по сути — эмулятор Z индекса и проверка пересечения.
В частности если у тебя GUI на канвасе — один из узлов задает клип рест чтобы дети за его пределы не вылазили( или нинзя трик — предоставляет детям другой канвас, а на фронт кидает только нужную область, скролинг говорит спасибо)

Одновременно по этой структуре можно определяет что нужно обновить когда что-то другое обновилось.
Тоесть если твои астероиды запустились на 60FPS, а скорость движения астероида — 10 пикселей в секунду — его можно перерисовывать не каждый раз, а только 1\6 кадр( ну ладно 1\12, алиасинг ).
Ну или если два астероида перекрываются, и только у одного меняется спрайт — перерисовать оба.

В общем та хреновина через которую ты читаешь данный текст так и работает. Особо хорошо видно в MozAfterPaint :)

Остаются проблемы вроде что «академическому» графу на клиенте делать нечего, и те моменты что 90% различных «чтук» делаются через различные фейки.
В том плане что быстрее нарисовать, чем понять что рисовать не надо.

А с объединением пока не смогу — двое детей, работа с 8ми до 20 и тройка собственных проектов которые надо закончить.
Понятно.
Тоесть если твои астероиды запустились на 60FPS, а скорость движения астероида — 10 пикселей в секунду — его можно перерисовывать не каждый раз, а только 1\6 кадр( ну ладно 1\12, алиасинг ).

А разве не будет дёрганно? А как определить, когда можно его запустить медленнее, а когда — нет?

дин из узлов задает клип рест чтобы дети за его пределы не вылазили

Тут есть проблема. Например, у нас ректангл с толстой границей. Или, даже, не ректангл, а путь, состоящий из arc, curve и т.п.. Плюс ещё с толстой границей, скажем strokeWidth = 30px. Как определить Rectangle, внутри которого этот путь находится, чтобы не обрезать ничего лишнего? Я думал делать подобное, но так и не придумал вменяемого решения вышеназванных проблем. Решил, что будет лучше оставить это на совести приложения (как в случае с пятнашками).

Ну а остальное у меня есть, да.
сори, забыл ответить
Для начала вычисляешь активный bound зоны, создаешь канвас нужных размеров и рисует там внутренности.
Потом в отдельном канвасе рисуешь внешнюю фигуру, например чисто белым цветом.
Потом на нее накладываешь данные из первого канваса с композитом and.
Потом конечную «обрезанную» перекидываешь куда надо.
Таким макаром можно управлять не только обрезкой по внешнему контуру, но и «выжигать» дырки или накладывать градиенты прозрачности.
Не выгоднее ли всё перерисовать? Ведь, может, такое придётся делать для каждого элемента, а в итоге все-равно перерисовывать.
А можно подробнее про танчики?
Меня пригласил ognevsky, показал такой скрин и предложил присоединиться к ним:


Я посмотрел, счёл проект интересным и предложил переписать клиентскую часть на LibCanvas, на что получил согласие.

За несколько часов я полностью перевёл клиент на LibCanvas с временной графикой из Asteroids:


Основной идеолог был недоволен и отказался использовать смерджить мой форк.

В итоге я решил делать свою игру на node.js+LibCanvas, другого жанра, экшн.

А BattleField, как это обычно бывает с такими проектами — скоропостижно загнулся



Первая публичная альфа-версия моей игры (она open-source) планируется где-то летом. Если есть желание присоединиться — welcome.
Я создавал для тестирования производительности сцену с постоянно увеличивающимся количество объектов тут и оставлял на время. В принципе даже когда было больше 500 я не заметил каких либо проблем в скорости.
404 :(
У меня в Хроме тоже не сразу открылась, при это в ФФ без проблем. Видать какая то хитрая логика у народа (или Хрома).
хех, чудеса.
Действительно в хроме 404, а в ff открывает.
Canvas не такой медленный, как кажется на первый взгляд ;)
ну чай уже не Спектрумы ж. Тогда пишешь код и видишь как все прорисовывается, линия за линий. А увеличивающийся круг это особое эстетство.
Немного потестил ваши шарики.

Chrome 11, Core 2Duo 2.66Ггц, 4Гб RAM, GeForce 9400 256:
Что 100, что 500, что 1000 — разницы нет, тормозов тоже. Дальше тысячи ждать уже не хотелось (:
[889, 9063, 20: 217, 1004]
Скриншот

HTC Hero, CM7, 2.3.3, родной браузер:
По-моему, одинаково тормозит и с двумя шариками и с сотней. C сотней чууть медленнее цифры меняются.
[223, 5116, 2: 103, 100]

В общем, canvas с анимацией одной тысячи простых объектов справляется прекрасно
У меня есть предположение, что на мобилках тормозит не отрисовка, а что-то другое. Что — я пока не выяснил.

Интересно было бы потестить 1000 шариков на SVG ;)
Очень интересные вещи вы творите, давно слежу за вашим фреймворком, сейчас потихоньку вчитываюсь в документацию.

Но, серьезно заняться чем-либо времени нету. Итак 2 больших проекта веду, семья уже забыла как я выгляжу.

Спасибо)
Интересно как реализован алгоритм слоев, ведь перерисовываем сцену каждый раз заново? Или слои это части сцены, а не лежащие друг на друге объекты?
Несколько элементов Canvas, ничего особого) Как выше уже сказал — зависит от приложения. Могу и перерисовывать сцену, а могу перерисовывать только изменившиеся части, как в пятнашках. Из всех примеров на libcanvas.github.com/ ни один не использует слои на полную. У меня есть два проекта в разработке, где слоёв много и активно используются.
Несколько Canvas заинтересовало, когда смотрел принцип работы этой игрушки. В ихсоднике две канвы и они постоянно меняются. Т.е. отрисовка происходит в скрытой и по окончании происходит смена. Прям как doom с его 4 видео страницами.
Ну это двойная буферизация. В таком виде она, имхо, не имеет смысла, т.к. вызывает тяжёлый reflow, а браузер сам по себе уже имеет бекбуфер.
НЛО прилетело и опубликовало эту надпись здесь
А на что это Вы намекаете то так, а?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории