В этом уроке вы создадите экстремальную систему частиц, одновременно научившись извлекать из Flash Player более эффективное качество, чем вы когда-либо могли себе представить!
Каждые несколько недель мы пересматриваем некоторые из любимых постов нашего читателя на протяжении всей истории сайта. Этот учебник был впервые опубликован в декабре 2010 года.
Конечный результат
Вот несколько примеров того, над чем мы будем работать:
ЭПИЛЕПСИЯ ВНИМАНИЕ:
Пожалуйста, не просматривайте эту демонстрацию, если вы, вероятно, страдаете от эпилептических приступов или потери сознания, особенно когда смотрите на некоторые виды сильных мигающих огней, быстрое чередование изображений, простые геометрические фигуры, вспышки или взрывы.
Введение: что вы узнаете?
Многие пользователи, от начинающих до продвинутых, все еще можно увидеть, используя менее эффективный Actionscript 3.0. По всей вероятности, это связано с тем, что эффективные способы звучания немного сложнее и предназначены для высокопрофессиональных пользователей. Этот урок покажет вам, что эти методы могут использоваться всеми, и они более полезны, чем вы думаете!
Цель этого руководства — дать вам возможность быстро и легко выполнять те задачи, которые требуют очень большой и быстрой работы с данными.
Полезный совет: в этом руководстве будет много кодирования, поэтому я рекомендую использовать более удобный интерфейс кодирования. Я рекомендую FlashDevelop , он содержит некоторые из лучших намеков кода и, что самое приятное, он абсолютно бесплатный! Но я уверен, что большинство из вас просто скопирует и вставит, если вообще что-то сделает 🙂
Шаг 1 Проверьте, что FPS!
В основе повышения эффективности работы Flash Player лежит частота кадров в секунду (FPS). Предложенная цель вашего SWF-файла может быть установлена в интерфейсе Flash Professional или, в качестве новой функции в ActionScript 3.0, вы можете изменить FPS этапа во время выполнения.
1
2
|
stage.frameRate = 30;
trace(stage.frameRate);
|
Тем не менее, это только когда-либо получит и установит целевой FPS (то, во что Flash Player будет пытаться играть), что делает его довольно бесполезным для тестирования на преформанс. Вы можете подумать, что милые ребята из Adobe сделали бы хороший аккуратный способ найти вам настоящий FPS, но нет. Вы должны сделать математику для себя. Давайте взглянем.
FPS можно определить как разницу во времени между текущим и последним кадрами.
Таким образом, все, что нам нужно, это какой-то способ отследить разницу во времени от этого кадра до предыдущего кадра SWF. Это использует функцию класса документа. Если вы не знаете, как это использовать, ознакомьтесь с этим кратким советом о том, как использовать класс документа .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package {
//imports
import flash.events.Event;
import flash.utils.getTimer;
import flash.display.MovieClip;
public class FPSCalculator extends MovieClip {
//variable to hold the current time
private var currentTime:int = 0;
public function FPSCalculator() {
//add the enter frame listener, this is fired when the SWF updates to a new frame
stage.addEventListener(Event.ENTER_FRAME, onFrameLoop);
}
private function onFrameLoop (evt:Event):void{
//for the sanity of the fellow developers, try to put each task into a seperate function.
//this makes it infinitely easier to read for them and yourself on a large project or when you come back to and old one
//since the getTimer() function returns the played time in milliseconds and we want FPSecond, we divide it into 1000
var fps:Number = (1000 / timeDifference);
trace(fps);
}
//this is a get function so it can be referenced just like a variable, without the brackets on the end like a normal function
private function get timeDifference ():int{
//the getTimer() function returns the total played time of the SWF in milliseconds
var totalPlayedTime:int = getTimer();
//The difference in time from the previous frame to this frame will to calculated here
var timeDifference:int = (totalPlayedTime — currentTime);
//The currentTime is set to the total played time so it is ready for the next frame
currentTime = getTimer();
//return the difference in time
return timeDifference
}
}
}
|
Мы будем использовать эту функцию в качестве эталона для сравнения эффективности различных методов. Как вы могли заметить, мы используем функцию для вычисления разницы во времени, а не FPS. Это потому, что отслеживание разницы во времени на самом деле намного полезнее и легче для чтения, когда мы приступаем к тестам скорости. Вычисление FPS становится полезным только тогда, когда мы собираем все вместе в конце.
Совет № 1 массивы против векторов
Вам когда-нибудь нужно было хранить целую кучу чисел, строк или объектов в списке? Конечно есть! Но вопрос в том, правильно ли вы это делали?
Если первое, что вы думаете о создании списка в AS3, это массив, тогда этот совет для вас. Вектор точно такой же, как обычный массив, за исключением одного факта, это типизированный массив. Это означает, что вы можете заполнить его только одним типом предмета. Например, вы можете поместить число и строку в один и тот же массив, но не в вектор.
Давайте посмотрим на единственную разницу между массивом и вектором, объявляя вектор.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
private function Arrayvs.VectorDifferences ():void{
//delare the array and the vector, this is the only difference between the two
var myArray:Array = new Array();
var myVector:Vector.<String> = new Vector.<String>;
//populate them in the same way
myArray.push(«this», «is», «an», «Array»);
myVector.push(«this», «is», «a», «Vector»);
//call elements and length in the same way
trace( myArray[myArray.length — 1] );
trace( myVector[myVector.length — 1] );
//The following creates an error — «Access of possibly undefined property x through a reference with static type String.»
trace( myVector[myVector.length — 1].x );
//The Flash Player casts the movieclip as a string and a string does not have an ‘x’ value.
}
|
Как видно выше, объявление вектора — единственное различие между двумя типами. Чтобы Вектор содержал другой объект, просто замените String
вашим объектом. Для проведения MovieClips например:
1
|
var myVector.<MovieClip> = new Vector.<MovieClip>;
|
Как это делает его более полезным? Если все элементы в векторе имеют один и тот же тип, то Flash Player может их прокручивать намного быстрее, потому что он знает, что происходит, и не должен каждый раз проверять следующий элемент (это верно даже при объявлении типа переменной так что никогда не оставляйте это!).
Насколько велика разница? Удивительно большой на самом деле! Давайте посмотрим на наш первый тест скорости.
Тест скорости # 1 массив против вектора
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
|
package {
//imports
import flash.utils.getTimer;
import flash.display.MovieClip;
public class Arrayvs.Vector extends MovieClip {
private var currentTime:int = 0;
//we want to read and write into the array and vector 10,000,000 times.
//this will provide a good indication of the speed difference
//dont worry, it wont crash you computer but the FLash Player will pause for about 3 to 5 seconds
private var n:int = 10000000
//Declare the Array and Vector
private var myArray:Array = new Array();
private var myVector:Vector.<int> = new Vector.<int>;
public function Arrayvs.Vector() {
//time test for writing to the array and vector
write();
trace(«—-«);
//time test for reading from the array and vector
read();
}
private function write ():void{
trace(«Writing Times»);
timeDifference
//for n times, push i into the array
for(var i:int = 0; i < n; i++){
myArray.push(i);
}
//trace the time taken
trace(«Array: » + timeDifference + «ms»);
//for n times push j into the vector
for(var j:int = 0; j < n; j++){
myVector.push(j);
}
//trace the time taken
trace(«Vector: » + timeDifference + «ms»);
}
private function read():void{
var num:int = 0;
trace(«Reading Times»);
timeDifference
//for n times, set num to the corresponding array value
for(var i:int = 0; i < n; i++){
num = myArray[i];
}
//trace the time taken
trace(«Array: «+ timeDifference + «ms»);
//for n times, set num to the corresponding vector value
for(var j:int = 0; j < n; j++){
num = myVector[j];
}
//trace the time taken
trace(«Vector: » + timeDifference + «ms»);
}
private function get timeDifference ():int{
var totalPlayedTime:int = getTimer();
var timeDifference:int = (totalPlayedTime — currentTime);
currentTime = getTimer();
return timeDifference
}
private function Arrayvs.VectorDifferences ():void{
//delare the array and the vector, this is the only difference between the two
var myArray:Array = new Array();
var myVector:Vector.<String> = new Vector.<String>;
//populate them in the same way
myArray.push(«this», «is», «an», «Array»);
myVector.push(«this», «is», «a», «Vector»);
//call elements and length in the same way
trace( myArray[myArray.length — 1] );
trace( myVector[myVector.length — 1] );
//pushing in a non-string value
myVector.push(new MovieClip());
//The following creates an error — «Access of possibly undefined property x through a reference with static type String.»
//trace( myVector[myVector.length — 1].x );
//The Flash Player casts the movieclip as a string and a string does not have an ‘x’ value.
}
}
}
|
Это выводит следующее:
1
2
3
4
5
6
|
Array: 2073ms
Vector: 1476ms
—-
Reading Times
Array: 190ms
Vector: 134ms
|
Конечно, вы не должны получать точно такие же результаты, как у меня здесь, вы почти никогда сами не получите тот же набор значений, даже если это зависит от того, сколько усилий ваш ЦП может приложить к этому во время выполнения.
Первая пара значений происходит от записи в массив и вектор соответственно. Мы можем видеть, что Вектор сбрасывал почти 0,6 секунды, уменьшая материальную сумму, если нам нужно делать что-то подобное 24 (стандартный FPS для флэш-фильмов) раз в секунду. В конце концов, 1/24 секунды составляет чуть более 0,04 секунды.
Еще большая разница в процентах может быть найдена, когда вы читаете из массива по сравнению с вектором, и это, к счастью, вам нужно будет делать большую часть времени каждый кадр.
Надеемся, что после прочтения этого раздела вам будет удобно использовать Векторы в своих проектах для дополнительного повышения эффективности.
Совет № 2 слушателей событий
Как и все во Flash, есть несколько способов решения проблемы, прослушиватели событий не являются исключением. В этом уроке мы рассмотрим два способа присоединения слушателя Event.ENTER_FRAME
к вашим частицам.
Метод № 1 Один слушатель на частицу
Идея этого подхода заключается в том, что вы присоединяете слушателя к каждой из ваших частиц и направляете их к заданной функции. Это не идеально для того, что мы имеем в виду, но помните, что частица, с точки зрения программирования, не должна быть единой точкой. Например, этот метод может быть предпочтительным при переходе между веб-страницами на основе Flash или объектами, которые по-разному обрабатываются в функции слушателя. Давайте взглянем.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
private function createPages():void{
//create 10 webpages
for(var i:int = 0; i < 10; i++){
//create a new webpage as a movieclip
var webpage:MovieClip = new MovieClip();
//add the listener
webpage.addEventListener(Event.ENTER_FRAME, onWebpageLoop);
}
}
private function onWebpageLoop (evt:Event):void{
//all webpages call this function every frame
//evt.target is the webpage
}
|
Метод № 2 Один слушатель, чтобы управлять ими все …
Второй метод — тот, который мы будем использовать, и предпочтительный метод при работе со многими подобными объектами. Идея здесь состоит в том, чтобы прикрепить одного слушателя только к одному объекту — обычно к сцене — который затем проходит по каждой частице и говорит каждому, что делать в каждом кадре. Этот метод немного сложнее, так как нам нужен какой-то способ для ссылки на частицы, поэтому мы помещаем их в вектор. Давайте взглянем.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
private function createPages2():void{
//create 10 webpages
for(var i:int = 0; i < 10; i++){
//add a movieclip to the webpagesHolder
//reducing the number of variables and steps used increases the speed
webpagesHolder.push(new MovieClip());
}
//add the listener
stage.addEventListener(Event.ENTER_FRAME, onStageLoop);
}
private function onStageLoop (evt:Event):void{
//called only once when the stage changes frame
for(var i:int = 0; i < 10; i++){
//webpageHolder[i] is the webpage
}
}
|
Тест скорости # 2 Методы использования прослушивателей событий
К сожалению, нет точного и быстрого способа точно проверить разницу в скорости между этими двумя методами из-за несогласованности вызывающего процесса при использовании первого метода (по крайней мере, не точного метода, который не потребует своего полного учебного пособия!)
Поверьте мне, использование метода два гораздо лучше для использования в системах частиц из-за двух основных факторов:
- Огромное увеличение скорости
- Гораздо проще ссылаться на другие частицы из частиц
После прочтения этого раздела вы должны знать, как удобно отслеживать и ссылаться на многие частицы. Помните, метод один для объектов, которые должны по-разному относиться к своей функции слушателя, и метод два для многих объектов, которые все должны обрабатываться точно так же или очень похожим образом в своей функции слушателя.
Совет № 3 Создание вашей частицы
Следующим шагом к нашей полной системе частиц является создание правильной частицы для себя. Может случиться так, что вам понадобится полный MovieClip для каждой частицы, чтобы использовать кадры и тому подобное, но для нас MovieClip — это огромный перебор. Вся наша частица является заполнителем для множества значений, которые необходимо хранить вместе и соотносить друг с другом. Это позволяет нам резко уменьшить размер используемого класса.
Ниже приведен базовый класс, который мы можем использовать для нашей частицы.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package {
//imports
import flash.geom.Vector3D;
//notice that the class extends nothing because there is no need
public class Particle {
//define the Vector3D objects to hold the position and velocity values
private var pos:Vector3D = new Vector3D(0, 0, 0);
private var vel:Vector3D = new Vector3D(0, 0, 0);
public function Particle(stageRect:Rectangle) {
}
public function update ():void{
//update the position according to the velocity in that direction
pos.x += vel.x;
pos.y += vel.y;
}
//the getter methods that will be used to read the position of the particle
public function get x ():Number{ return pos.x }
public function get y ():Number{ return pos.y }
}
}
|
Обратите внимание, что у него нет базового класса (то есть он ничего не extend
), и поэтому он «рождается» без свойств, которые вы можете регулярно использовать, например, значения «x» и «y». Чтобы исправить это, мы используем наши собственные методы getter для чтения этих значений. Эти значения затем передаются в объект Vector3D. Объект Vector3D — это, в основном, вектор, который содержит три переменные и необязательную четвертую переменную. Разница в том, что вы можете ссылаться на эти значения как «x», «y», «z» и «w» соответственно. «W», который является необязательным, можно использовать, например, для старения значения вращения. Это делает этот тип держателя идеальным для того, что нам нужно.
(Мы могли бы даже создать эти свойства как открытые переменные Number внутри класса напрямую, вообще не используя объекты Vector3D … но давайте придерживаться того, что у нас есть.)
Но как именно помогает создание нашего собственного класса? Давайте сделаем быстрый тест памяти, чтобы узнать! Не забудьте сохранить этот класс документа и класс частиц в одном месте назначения.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package {
//imports
import flash.sampler.getSize;
import flash.display.MovieClip;
public class BuildingYourParticle extends MovieClip {
public function BuildingYourParticle() {
//simulate 100,000 of the respective object
var n:int = 100000
//define the object
var p:Particle = new Particle();
var m:MovieClip = new MovieClip();
//use the getSize() method the find the memory used for n of m and p.
var pSizeTotal:Number = (getSize(p) * n) / (1024 * 1024);
var mSizeTotal:Number = (getSize(m) * n) / (1024 * 1024);
//trace the respective sizes to two decimal places
trace(«Particle Memory: » + pSizeTotal.toFixed(2) + «mb»);
trace(«MovieClip Memory: » + mSizeTotal.toFixed(2) + «mb»);
}
}
}
|
Это выводит что-то вроде следующего:
1
2
|
Particle Memory: 1.53mb
MovieClip Memory: 40.05mb
|
Скоростной тест № 3 « Наши частицы против видеоклипов»
Как видите, существует огромная разница между размером нашего класса и видеоклипом. Освободив все это пространство, вы получите больше места в вашей оперативной памяти и, таким образом, весь Flash Player будет работать немного быстрее. Насколько быстрее? Давайте посмотрим на это тоже! Большая часть этого кода совпадает с нашим тестом Array vs. Vector Speed.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
package {
//imports
import flash.utils.getTimer;
import flash.display.MovieClip;
public class ParticleSpeedTest extends MovieClip {
private var n:int = 100000
private var currentTime:int = 0;
//Declare the respective holders
private var particleHolder:Vector.<Particle> = new Vector.<Particle>;
private var movieclipHolder:Vector.<MovieClip> = new Vector.<MovieClip>;
public function ParticleSpeedTest():void {
//time test for writing a new particle or movieclip
write();
trace(«—-«);
//time test for reading from a particle or movieclip
read();
}
private function write ():void{
trace(«Writing Times»);
timeDifference
//for n times, push a new particle into the vector
for(var i:int = 0; i < n; i++){
particleHolder.push(new Particle());
}
//trace the time taken
trace(«Particle: » + timeDifference + «ms»);
//for n times push a new movieclip into the vector
for(var j:int = 0; j < n; j++){
movieclipHolder.push(new MovieClip());
}
//trace the time taken
trace(«MovieClip: » + timeDifference + «ms»);
}
private function read():void{
var num:int = 0;
trace(«Reading Times»);
timeDifference
//for n times, set num to the corresponding particle’s ‘x’ value
for(var i:int = 0; i < n; i++){
num = particleHolder[i].x;
}
//trace the time taken
trace(«Particle: «+ timeDifference + «ms»);
//for n times, set num to the corresponding movieclip’s ‘x’ value
for(var j:int = 0; j < n; j++){
num = movieclipHolder[j].x;
}
//trace the time taken
trace(«MovieClip: » + timeDifference + «ms»);
}
private function get timeDifference ():int{
var totalPlayedTime:int = getTimer();
var timeDifference:int = (totalPlayedTime — currentTime);
currentTime = getTimer();
return timeDifference
}
}
}
|
Это должно вывести что-то похожее на следующее:
1
2
3
4
5
6
7
|
Writing Times
Particle: 334ms
MovieClip: 2096ms
—-
Reading Times
Particle: 16ms
MovieClip: 22ms
|
Как видно из этих результатов, реальная разница заключается во времени написания; это связано с тем, что Flash Player должен использовать больше ресурсов (базовые классы MovieClip и каждый последующий объект) для выполнения этого. В нашем основном проекте это не является проблемой, поскольку нам нужно только создавать новые частицы в начале, но позже мы увидим пример, когда все время нужно добавлять новые частицы. Разница во времени чтения практически не учитывается, так как оба класса используют свои методы получения для свойства ‘x’ одинаково.
После прочтения этого раздела вы должны освоить создание базового класса для нужд вашей частицы.
Совет № 4 Введение в растровые изображения
Битовые карты и их партнер по преступлению BitmapData, как правило, являются одними из двух самых запутанных шагов для новичка, в основном потому, что они обычно используются для вещей более высокого уровня. Здесь я кратко расскажу о некоторых основных и наиболее используемых методах классов Bitmap и BitmapData.
Кто они такие?
В самом быстром объяснении возможно:
- Класс Bitmap представляет экранные объекты, представляющие растровые изображения.
- Класс BitmapData Давайте работать с данными (пикселями) объекта Bitmap
По сути, класс Bitmap отображает то, что ему сообщает класс BitmapData. Они идут рука об руку практически всегда.
Рисование с помощью растровых изображений
Класс BitmapData не имеет собственного graphics
свойства, но остается одним из наиболее важных классов для графики Flash! Как? Он рисует фигуры других классов. Давайте посмотрим, как нарисовать простой круг, используя Bitmaps и BitmapData.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
public function SimpleCircle():void {
//define the radius of the circle
var radius:int = 30;
//draw a circle the normal way.
var circleShape:MovieClip = new MovieClip();
circleShape.graphics.beginFill(0x555555, 1);
circleShape.graphics.drawCircle(radius, radius, radius);
circleShape.graphics.endFill();
//create the bitmap and add it to the display list
var bmd:BitmapData = new BitmapData(radius * 2, radius * 2, true);
var bm:Bitmap = new Bitmap(bmd);
stage.addChild(bm);
//draw the shape
bmd.draw(circleShape);
}
|
Это приводит к простому серому кругу, касающемуся левого верхнего угла сцены при беге. Метод draw () — это просто моментальный снимок мувиклипа, поэтому теоретически мы могли бы перемещать объект circleShape вокруг продолжения рисования, чтобы создать эффект множества кругов. Вот как много рисования в растровых изображениях.
Метод setPixel ()
Поскольку в основе всей графики лежат необработанные пиксели, этот метод становится очень важным для создания эффектов с использованием классов Bitmap и BitmapData. Это позволяет вам изменить цвет пикселя внутри области BitmapData. Вот как это сделано.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package {
//imports
import flash.events.Event;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
public class SetPixelMethod extends MovieClip {
//define variables
private var n:int = 50;
private var w:int;
private var h:int;
private var bm:Bitmap
private var bmd:BitmapData;
public function SetPixelMethod ():void {
w = stage.stageWidth;
h = stage.stageHeight;
//create the bitmap data the width and height of the stage that is not transparent and grey in color
bmd = new BitmapData(w, h, false, 0x222222);
bm = new Bitmap(bmd);
addChild(bm);
addEventListener(Event.ENTER_FRAME, onFrameLoop);
}
private function onFrameLoop (evt:Event):void {
for (var i:int = 0; i < n; i++ ) {
//randomly pick the x,y coordinates to set the new pixel color
var px:int = Math.random() * bmd.width;
var py:int = Math.random() * bmd.height;
//give the pixel a random color
var pc:uint = Math.random() * 0xffffff;
//set the pixel at (px,py) to that color
bmd.setPixel(px, py, pc);
}
}
}
}
|
Вы должны получить что-то похожее на это, с большим количеством точек, появляющихся в каждом кадре:
Это графический стиль, который мы будем использовать для нашей системы. Не беспокойтесь, если сейчас это выглядит ужасно, в конце вы увидите, как мы можем сделать их намного лучше с целой кучей эффектов.
Методы lock () и unlock ()
Эти методы являются одними из наименее используемых, но наиболее полезными в отношении растрового изображения. На самом деле, я бы никогда не узнал о существовании там, если бы не большой сайт под названием WonderFl, где участники регулярно хвастаются массивными системами частиц, правда в том, что они были вдохновением этого урока!
Вам может быть трудно найти подобный набор методов для совместного использования, вот как они работают:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public function LockUnlockMethods ():void {
//Draw a basic circle, same in steps before this
var radius:int = 30;
var circleShape:MovieClip = new MovieClip();
circleShape.graphics.beginFill(0x555555, 1);
circleShape.graphics.drawCircle(radius, radius, radius);
circleShape.graphics.endFill();
//create the bitmap/bitmapdata
var bmd:BitmapData = new BitmapData(radius * 2, radius * 2, true);
var bm:Bitmap = new Bitmap(bmd);
stage.addChild(bm);
//lock the bitmap
bmd.lock();
//draw resources
bmd.draw(circleShape);
//unlock and update the bitmap
bmd.unlock();
}
|
По сути, вы заключаете методы lock () и unlock () в точку, в которой ваш код меняет внешний вид растрового изображения. Хотя для описанной выше ситуации они не так уж и полезны, в крупномасштабной системе с многими тысячами изменений в растровом изображении они значительно ускоряют процесс. Это связано с тем, что классы Bitmap и BitmapData любят проводить много времени одновременно, чем больше изменений вы можете втиснуть в один шаг, тем лучше они станут! Эти методы отлично подходят для этого, поскольку они приостанавливают все изменения, сделанные в заблокированном растровом изображении, до тех пор, пока он не будет разблокирован, а это означает, что он не рендерится в течение этого времени, что ускоряет процесс.
Очистка вашего растрового изображения
Класс BitmapData не предлагает четкого метода для создания очистки своих данных, поэтому здесь есть два распространенных способа сделать это. Мы можем повторно использовать весь код, узнав о методе setPixel.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package {
//imports
import flash.geom.Point;
import flash.events.Event;
import flash.geom.Rectangle;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.filters.BlurFilter;
public class ClearBitmap extends MovieClip {
//define variables
private var n:int = 50;
private var w:int;
private var h:int;
private var bm:Bitmap
private var bmd:BitmapData;
private var clearRect:Rectangle;
public function ClearBitmap ():void {
w = stage.stageWidth;
h = stage.stageHeight;
bmd = new BitmapData(w, h, false, 0x222222);
bm = new Bitmap(bmd);
addChild(bm);
addEventListener(Event.ENTER_FRAME, onFrameLoop);
//create the bitmap data the width and height of the stage that is not transparent and grey in color
clearRect = new Rectangle(0, 0, w, h);
}
private function onFrameLoop (evt:Event):void {
//use one of these methods to clear your bitmap
//bmd.fillRect(clearRect, 0x222222);
bmd.applyFilter(bmd, bmd.rect, new Point(0, 0), new BlurFilter(1.1, 1.1, 2));
for (var i:int = 0; i < n; i++ ) {
var px:int = Math.random() * bmd.width;
var py:int = Math.random() * bmd.height;
var pc:uint = Math.random() * 0xffffff;
bmd.setPixel(px, py, pc);
}
}
}
}
|
Если вы используете метод clearRect, он просто рисует совершенно новый серый прямоугольник поверх всего остального. Это не так небрежно, как кажется на первый взгляд, потому что помните, что все, что вы когда-либо делаете с растровым изображением, это изменение цвета набора пикселей; Перекрывающиеся элементы не имеют значения, что так всегда.
Второй вариант размывает точки, объединяя их в фоновом режиме. Мы будем использовать это позже, чтобы создавать более интересные эффекты. Вы должны заметить, что этот метод требует значительно большего времени вычислений, и его следует избегать, если эффективность — ваша единственная задача. Мы оставим это до последней части учебника, где мы не стремимся к максимальной эффективности.
Тест на скорость # 4 Рисование растровых изображений против рисования MovieClips
Это наш последний тест скорости, и здесь мы проверим разницу в скорости между рисованием нескольких фигур с использованием классов Bitmap и класса MovieClip. Вот это:
ВНИМАНИЕ: Обычный метод createMovieClipCircles () настолько неэффективен, что вы не должны запускать его дольше нескольких секунд. Flash Player будет продолжать замедляться, пока в конечном итоге не остановится .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
package {
//imports
import flash.events.Event;
import flash.display.Bitmap;
import flash.utils.getTimer;
import flash.display.BitmapData;
import flash.display.MovieClip;
public class Bitmapvs.MovieClip extends MovieClip {
//define variables
private var w:int;
private var h:int;
private var r:int = 5;
private var n:int = 500;
private var currentTime:int = 0;
private var bm:Bitmap;
private var bmd:BitmapData;
private var bmShape:MovieClip = new MovieClip();
public function Bitmapvs.MovieClip ():void {
w = stage.stageWidth;
h = stage.stageHeight;
bmd = new BitmapData(w, h, true, 0);
bm = new Bitmap(bmd);
addChild(bm);
addEventListener(Event.ENTER_FRAME, onFrameLoop);
}
private function onFrameLoop (evt:Event):void {
timeDifference;
//use one of these functions at a time
//this one is to create points using the movieclip method
//createMovieClipCircles();
//this one is to create points using the bitmap method
createBitmapCircles();
}
private function createMovieClipCircles ():void {
//this will create a new movieclip for each circle
for (var i:int = 0; i < n; i ++) {
var m:MovieClip = new MovieClip();
drawCircle(m);
addChild(m);
}
//trace the fps
trace(1000 / timeDifference);
}
private function createBitmapCircles ():void {
//this will draw the same movieclip in different places over and over
bmd.lock();
for (var j:int = 0; j < n; j ++) {
drawCircle(bmShape);
bmd.draw(bmShape);
}
bmd.unlock();
//trace the fps
trace(1000 / timeDifference);
}
private function drawCircle (m:MovieClip):void {
//create the same function to draw circles for both to keep it fair and organised
m.graphics.clear();
m.graphics.beginFill(Math.random() * 0xffffff);
m.graphics.drawCircle(Math.random() * w, Math.random() * h, r);
m.graphics.endFill();
}
private function get timeDifference ():int{
var totalPlayedTime:int = getTimer();
var timeDifference:int = (totalPlayedTime — currentTime);
currentTime = getTimer();
return timeDifference
}
}
}
|
После использования обоих методов вы должны найти следующее:
- Метод createMovieClipCircles () останавливается через несколько секунд
- CreateBitmapCircles () может работать весь день со скоростью 24/24 кадра в секунду и не замедляться
Метод createMovieClipCircles настолько неэффективен, потому что ему необходимо добавить каждый круг в список отображения, в результате чего Flash Player изо всех сил пытается удержать вес их всех. Вот почему мы должны использовать классы Bitmap и BitmapData в нашей системе частиц.
Прочитав этот раздел, вы должны быть знакомы со многими методами, которые мы можем использовать в классах Bitmap и BitmapData для построения нашей системы частиц. Я рассмотрел многое из того, что нам нужно знать, чтобы построить такую систему, поэтому теперь я думаю, что пришло время погрузиться в нее!
Сборка системы
Наша система не будет красивой, но черт возьми, это будет быстро! Он будет состоять из двух классов: класса Particle, который мы создали ранее, и класса контроллера, который контролирует все. Наша цель — создать систему, которая будет счищать частицы размером 100 тыс., Смеяться над частицами весом 150 тыс. И комфортно принимать 200 тыс. Частиц. Конечно, это зависит от вашей системы, но моей уже около шести лет, и она еще не взорвалась, поэтому я уверен, что с большинством из вас все будет в порядке.
Сначала мы начнем с базового класса Particle, большая часть кода, который вы увидите, будет такой же, как на ранее описанных шагах.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
package {
//imports — the less the better
import flash.geom.Vector3D;
import flash.geom.Rectangle;
public class Particle {
//define the position and velocity Vector3D objects
private var pos:Vector3D = new Vector3D(0,0);
//the velocity of the particle is between +3 and -3 for x and y
private var vel:Vector3D = new Vector3D(rand(3), rand(3));
//define the bounds and default color of the particle
private var bounds:Rectangle;
private var color:uint = 0x555555
public function Particle(stageRect:Rectangle) {
//the bounding area of the stage is passed into the constructor
//we do not need to pass the entire instance of the stage as this will require more memory
bounds = stageRect;
//spawn the particle at a random point within the bounds
pos.x = Math.random() * bounds.width;
pos.y = Math.random() * bounds.height;
//give a handful a different color so that we can see particles moving more easily
if(Math.random() < 0.005) color = 0xFFFFFF
}
private function rand(n:int):Number{
//this function returns a random number between -n and n
return n — (n * 2 * Math.random())
}
public function update ():void{
//add the respective velocities to the position
pos.x += vel.x;
pos.y += vel.y;
//check if the particle is outside the bounds of the rectangle
checkBounds();
}
private function checkBounds():void{
//this function simply checks the x,y position values and if they are
//bigger or greater than the bounds reverse the respective velocity (direction) is reversed
if(pos.x < 0 || pos.x > bounds.width) vel.x *= -1;
if(pos.y < 0 || pos.y > bounds.height) vel.y *= -1;
}
//the get methods for the color and x,y values of the particle
public function get c ():uint{ return color }
public function get x ():Number{ return pos.x }
public function get y ():Number{ return pos.y }
}
}
|
В соответствии с традициями ООП, наша частица как можно более инкапсулирована (автономна). Он определяет свои собственные x- и y-позиции, чтобы сохранить беспорядок и ненужные переменные из класса контроллера, и ему нужен только метод update (), чтобы он был готов к следующему кадру.
Далее идет класс Controller. Этот класс является мозгом операции, выполняющей все циклы и рисование каждого кадра.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
package {
//imports
import flash.events.Event;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.utils.getTimer;
import flash.display.MovieClip;
public class Controller extends MovieClip {
//define variables
private var w:int;
private var h:int;
private var bm:Bitmap;
private var bmd:BitmapData;
private var clearRect:Rectangle;
private var currentTime:int = 0;
private var holder:Vector.<Particle> = new Vector.<Particle>;
//most machines should be fine with 150,000 particles.
private var n:int = 150000;
public function Controller():void {
w = stage.stageWidth;
h = stage.stageHeight;
//create the blank rectangle we will use to clear the bitmapdata
clearRect = new Rectangle(0, 0, w, h);
//create the bitmap and bitmapdata
bmd = new BitmapData(w, h, false, 0);
bm = new Bitmap(bmd);
addChild(bm);
//populate the holder with n number of particles
for(var i:int = 0; i < n; i++ ){
//notice no temporary variable was used to store the particle before pushing it to the Vector
//this only uses unneeded memmory
holder.push(new Particle(stage.getRect(this)));
}
//add listener for every frame
addEventListener(Event.ENTER_FRAME, onFrameLoop);
}
private function onFrameLoop (evt:Event):void{
var p:Particle;
//lock the bitmap and clear it before drawing
bmd.lock();
bmd.fillRect(clearRect,0);
//for n times get the respective particle in the holder and set the
//corresponding pixel at px and py to the particles color
for(var i:int = 0; i < n; i++ ){
//notice a temporary variable was used here because otherwise the object
//would need to be read 3 times from the Vector — much slower
p = holder[i];
bmd.setPixel(px, py, pc);
//update the particle’s position
p.update();
}
//update the bitmap
bmd.unlock();
//trace the FPS
trace(1000 / timeDifference);
}
private function get timeDifference ():int {
var totalPlayedTime:int = getTimer();
var timeDifference:int = (totalPlayedTime — currentTime);
currentTime = getTimer();
return timeDifference
}
}
}
|
Это оно! У вас есть то, что вы видели в первом демо.
ЭПИЛЕПСИЯ ВНИМАНИЕ:
Пожалуйста, не просматривайте эту демонстрацию, если вы, вероятно, страдаете от эпилептических приступов или потери сознания, особенно когда смотрите на некоторые виды сильных мигающих огней, быстрое чередование изображений, простые геометрические фигуры, вспышки или взрывы.
К сожалению, есть проблема. Если вы запустите этот код в среде Flash Professional IDE, он никогда не будет работать со скоростью 24/24 FPS. Это потому, что когда вы запускаете его здесь, Flash IDE пытается соединиться с Flash Player, чтобы прочитать много разных вещей, именно так вы получаете отчеты об ошибках, когда что-то смешно. Однако, к счастью для вас, ваши клиенты, вероятно, никогда не должны видеть эту сторону вещей, и сам по себе Flash Player работает прекрасно, легко работая на 24/24 FPS. Его чуть сложнее читать FPS. Я рекомендую создать небольшое динамическое текстовое поле и вывести то, что мы должны отслеживать, это выходит за рамки этого урока и является довольно простым, поэтому я могу оставить это у вас.
Итак, теперь, когда вы создали свою систему частиц, разве не было бы неплохо показать ее своим друзьям и клиентам? Исходя из опыта, все, что вы получите от показа этого, — это несколько странных взглядов со сдержанной похвалой со стороны всех, кроме опытных программистов. Давайте сделаем что-нибудь красивое.
Свыше 9000?! Играем безопасно с таким количеством частиц.
Прежде чем двигаться дальше, я рекомендую немного поработать с вышеуказанной системой частиц и посмотреть, как далеко вы можете ее продвинуть. В моей системе со средними показателями я могу использовать примерно 200 000 при 24/24 FPS, а при 250 000 — около 18/24 FPS, просто для справки. Получите контроль над тем, как далеко вы можете продвинуть свою собственную систему и черт, даже хвастаться об этом в комментариях! 🙂
Давайте посмотрим на некоторые вещи, которых вы должны избегать, играя с такими системами, как эта.
Трассировка заявлений. Одна из самых полезных вещей в арсенале Flash Developer — это большая задача для Flash Player. Один раз за кадр это хорошо, но убедитесь, что вы не засунете его в одну из ваших частиц, когда у вас работает 200k их. Это просто мгновенно приведет к краху Flash IDE, и вы потратите следующие пару минут, нажимая все кнопки выхода. Хороший способ проверить что-то в частице — это просто уменьшить их число до одного-десяти.
Все имеет значение, когда вы делаете что-то 200 000 раз по 24 раза в секунду, поэтому обязательно продолжайте просматривать свой код и никогда не вносите никаких больших изменений, не сводя число частиц к одному числу.
Создание чего-то красивого — основной эффект водопада
Это будет очень простой пример создания чего-то, что выглядит мягко привлекательным.
Это обновленный класс частиц, который используется для создания водопада:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
package {
import flash.geom.Vector3D;
import flash.geom.Rectangle;
public class Particle {
private var pos:Vector3D = new Vector3D(0,0);
private var vel:Vector3D = new Vector3D(rand(3), rand(3));
private var bounds:Rectangle;
private var color:uint = 0x00FFFF
public function Particle(stageRect:Rectangle) {
bounds = stageRect;
//spawn the particle from the top left corner
pos.x = 0;
pos.y = 0;
}
private function rand(n:int):Number{
//this function returns a random number between 0 and n
return Math.random() * n
}
public function update ():void{
pos.x += vel.x;
pos.y += vel.y;
//add a small amount to the y velocity to simulate gravity
vel.y += 0.1;
//check if the particle is outside the bounds of the rectangle
checkBounds();
}
private function checkBounds():void{
//this time we need to decrease velocities in both directions to make sure they are all eventually removed
if(pos.x < 0 || pos.x > bounds.width){
vel.x *= -0.8;
vel.y *= 0.8;
}
if(pos.y < 0 || pos.y > bounds.height){
vel.x *= 0.9;
vel.y *= -Math.random() * 0.8;
}
}
//the get methods for the color and x,y values of the particle
public function get c ():uint{ return color }
public function get x ():Number{ return pos.x }
public function get y ():Number{ return pos.y }
//returns true if the particle does not have a lot of ‘energy’ left, false otherwise
public function get remove ():Boolean {
if(Math.abs(vel.y) + Math.abs(vel.x) < 0.1){
return true;
}
return false;
}
}
}
|
Как вы можете видеть, не должно быть никаких серьезных изменений в классе Particle, потому что он всегда будет просто заполнителем для группы связанных чисел и функций.
Далее идет класс Controller. Опять же, нет необходимости каких-либо серьезных изменений. Давайте взглянем:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
package {
//imports
import flash.geom.Point;
import flash.events.Event;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.utils.getTimer;
import flash.display.MovieClip;
import flash.filters.BlurFilter;
import flash.geom.ColorTransform;
public class Controller extends MovieClip {
//define variables
private var w:int;
private var h: int ; private var bm:Bitmap; private var bmd:BitmapData;
private var clearRect:Rectangle; private var currentTime: int = 0 ; private var holder:Vector.<Particle> = new Vector.<Particle>; //define the blur and color transformers private var blurFade:BlurFilter = new BlurFilter( 4 , 4 , 1 ); private var colorFade:ColorTransform = new ColorTransform( 0.7 , 0.7 , 0.999 ); //the number of particles pushed into the holder every frame private var n: int = 50 ; public function Controller(): void { w = stage.stageWidth; h = stage.stageHeight; bmd = new BitmapData(w, h, false , 0 ); bm = new Bitmap(bmd); addChild(bm); addEventListener(Event.ENTER_FRAME, onFrameLoop); }
private function onFrameLoop (evt:Event): void { //push new particles in n times every frame for ( var i: int = 0 ; i < n; i++ ){ holder.push( new Particle(stage.getRect( this ))); }
bmd.lock() //apply the BlurFilter which blurs the colors together and fades the particles out bmd.applyFilter(bmd, bmd.rect, new Point( 0 , 0 ), blurFade); //apply the color transformation to give the particle trails a blue tint bmd.colorTransform(bmd.rect, colorFade); for ( var j: int = 0 ; j < holder.length; j++ ){ var p:Particle = holder[j]; //check if the particle has a resonable amount of energy left through its getter method //if not remove it from the holder vector if (p.remove){ holder.splice(j, 1 ); j--; p = null ; }
else{
bmd.setPixel(px, py, pc); p.update(); }
}
bmd.unlock(); //trace the FPS //trace(1000 / timeDifference); }
private function get timeDifference (): int { var totalPlayedTime: int = getTimer(); var timeDifference: int = (totalPlayedTime - currentTime); currentTime = getTimer(); return timeDifference }
}
}
|
You should end up with something like this. If you’ve just found this, you might want to hit refresh in your browser to see the start of the waterfall to better understand how the particles move.
Needless to say that you can run and run with the basic ideas here and great some amazing effects, a lot of which pop up on WonderFl quite often, or even to create this kind of effect . (shameless self plug 🙂 ) A very ‘cool’ thing to try out is to have the particles interacting with the mouse in some way, the effect can be ne beautiful when done correctly. The perlinNoise() method of the BitmapData class might also be worth mentioning here as it sometimes used in particle systems to create a flowing effect, which can also be very beautiful.
Резюме
I hope after reading this that you can take away a number of things from how to get the most out of Flash Player in your code to an introduction to building particle effects. Most of all, I hope I ignited a little flame of curiosity somewhere and gave you a new set of boundaries for Flash itself. Hopefully, an ‘extreme’ particle system won’t seem too extreme anymore 🙂