Статьи

AS3 101: петли – Basix

Добро пожаловать в AS3 101, пятое издание! На этом этапе вы должны быть знакомы с понятиями переменных, функций, условий (ветвления) и массивов. Вы не только должны быть знакомы с концепциями, но также должны свободно владеть тем, как ActionScript 3 реализует эти концепции.

В этом пятом эпизоде ​​мы добавим в этот каталог навыков различные циклические структуры, найденные в AS3. Вы найдете некоторые зависимости от информации, изученной в AS3 101, Часть 4, о массивах, поэтому, если вы пропустили это или нуждаетесь в обновлении, просмотрите это руководство, прежде чем продолжить с этим.

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


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

Цикл в программировании – это управляющий блок, по сути похожий на оператор if или функцию в том смысле, что между фигурными скобками находится немного кода, и этот блок управляется некоторой формой управляющей структуры. В этом случае элемент управления позволяет выполнять блок снова и снова, скорее всего, с небольшими изменениями, пока ему не будет приказано остановиться.

В ActionScript есть три вида циклов: цикл for, цикл while и цикл do . Есть также два других типа, которые являются циклами, используемыми специально для обхода коллекции, такими как Массив, и не могут использоваться никаким другим способом. Это цикл for … in и цикл for … each .

О трех «стандартных» циклах нужно понимать, что они будут работать вечно, если вы позволите им. Неправильно закодированный цикл приведет к так называемому бесконечному циклу , благодаря которому компьютер будет послушно выполнять то, что вы ему сказали, и это будет обрабатывать один и тот же блок снова и снова до конца времени. К счастью, цикл for сложно испортить (хотя это случается довольно часто), и, кроме того, ActionScript 3 имеет встроенную защиту от бесконечных циклов. Если сценарий выполняется более 15 секунд, не выходя в эфир, вы увидите предупреждение о том, что «сценарий заставляет этот фильм работать медленно. Хотите прервать?» Если вы видите, что во время работы с этим учебным пособием, нажмите «Да», потому что есть вероятность, что вы сделали что-то не так, и вы также можете воспользоваться предложенным вам аварийным люком. Без этого выхода цикл будет просто запускаться, запускаться и выполняться, эффективно блокируя вас от повторного прикосновения к Flash, пока вы не прекратите его вручную (принудительное завершение на Mac, завершение задачи в Windows) и перезапустите Flash.

С учетом сказанного, петли чрезвычайно полезны. На самом деле, они незаменимые инструменты программирования. Они могут снять скуку, настроив кучу похожих кнопок. Они необходимы для работы с динамическими данными, например, когда через XML будет поступать произвольное количество элементов. Вы можете обрабатывать системы частиц с помощью петель. Подавляющее большинство игр требуют использования петель. И вы можете достичь такой логики, как «дать мне любое случайное число, кроме последнего, которое вы мне дали» или «построить строку с указанным числом пробелов в ней».


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

1
2
3
for (var i:int = 0; i < 10; i++) {
    trace(“Hello.”);
}

И давайте разберем это на части. Прежде всего, это ключевое слово for (как и многие другие наши программные конструкции, эта начинается с ключевого слова). Сразу после этого идет набор скобок. Внутри этих скобок на самом деле три целых выражения ActionScript. Мы вернемся к ним через секунду, но обратите внимание, что есть две точки с запятой. Они разделяют три выражения. Далее идет открывающаяся фигурная скобка. Тогда у нас есть некоторый код и закрывающая фигурная скобка. Код между фигурными скобками – это то, что выполняется во время итерации цикла.

Теперь эти три выражения. Они есть:

  1. Инициализатор
  2. Тест
  3. Счетчик

Инициализатор – это на самом деле просто оператор, который выполняется как самая первая вещь в цикле. Он запускается один раз и запускается первым. Итак, в этом примере первое, что мы делаем, это создаем переменную с именем «i» и устанавливаем ее в 0.

Тест – это выражение, которое должно принимать значение true или false . Думайте об этом как о чем-то, что было бы дома как условие заявления if . Цикл for использует это условие перед каждой итерацией цикла, чтобы увидеть, следует ли продолжать его выполнение. Пока условие истинно, цикл продолжит выполняться. Как только это ложно, оно вырвется из цикла. В нашем примере это означает, что пока переменная i имеет значение меньше 10, мы продолжим цикл. Если оно будет равно 10 или больше 10, цикл остановится.

Наконец, счетчик – это еще один оператор, который будет выполняться один раз для каждой итерации цикла после выполнения всего кода между фигурными скобками. Глядя на наш пример, это последний кусок головоломки, и мы можем видеть, что после того, как мы проследим «Hello», мы возьмем переменную i и увеличим ее на 1.

Полезно шагать по жизни цикла в медленном темпе. Вот что происходит:

  1. Создайте переменную с именем «i» и установите ее в 0.
  2. Проверьте, не является ли переменная i меньше 10. Сейчас она равна 0, так что это правда, поэтому мы …
  3. Проследите «Привет».
  4. Инкремент i. мне сейчас 1.
  5. Проверьте, не является ли переменная i меньше 10. Теперь она равна 1, так что это все еще так, поэтому мы …
  6. Проследите «Привет».
  7. Инкремент i. мне сейчас 2.
  8. Проверьте, не является ли переменная i меньше 10. Теперь это 2, так что это все еще верно, поэтому мы …
  9. Проследите «Привет».
  10. Инкремент i. мне сейчас 3.
  11. … и это продолжается … давайте перейдем к тому, когда мне будет 9 …
  12. Инкремент i. мне сейчас 9.
  13. Проверьте, не является ли переменная i меньше 10. Теперь это 9, так что это все еще верно, поэтому мы …
  14. Проследите «Привет».
  15. Инкремент i. мне сейчас 10
  16. Проверьте, не меньше ли переменной i 10. Теперь 10 … ага! 10 не меньше 10. Условие ложно, поэтому теперь мы выходим из цикла и возобновляем выполнение кода сразу после закрывающей фигурной скобки.

Если вы вели счет, вы заметили, что мы должны были отследить «Привет». 10 раз. Имейте в виду, что в то время как мы останавливаемся, когда i равен 10, мы начинаем i с 0, поэтому i увеличивается от 0 до 9, что позволяет 10 итераций. Обратите внимание на то, как это повторяет тот факт, что массивы имеют индексы, которые начинаются с 0. В то время как вы могли бы легко писать для циклов, которые начинаются с 42 и выполняются, когда мне меньше 52 – таким образом, достигая 10 итераций – есть очень веские причины, чтобы убедиться, что вы пытаетесь продолжайте с нуля. Мне нужно «доверять мне», но в основном, пока вы последовательны, вы можете избежать многих неожиданных результатов в долгосрочной перспективе.

Идите и запустите этот фрагмент. Вы должны увидеть десять «Привет» на панели «Вывод».

Это само по себе не очень полезно. Если я хотел сэкономить время, когда я пишу строки после школы («Я не оставлю переменные нетипизированными. Я не оставлю переменные нетипизированными. Я не буду …»), тогда это здорово. Но почти всегда вы захотите немного изменить то, что происходит в каждой итерации. Конечно, должен быть способ сделать это?

Там точно есть! Вы знаете ту переменную “i”, которую мы создали, чтобы отслеживать цикл? Вы поверите, что эта переменная точно такая же, как и любая другая переменная в ActionScript? Помните, что хотя эти три выражения в скобках выглядят немного по-разному, они все еще являются выражениями ActionScript. Мы можем технически поместить туда любой действующий ActionScript. Я не рекомендую делать это, по крайней мере, не для начинающих. Вместо этого я хочу сказать, что эта переменная «i» доступна для нас. Давайте изменим тело цикла так, чтобы вместо трассировки «Hello». мы отслеживаем я:

1
2
3
for (var i:int = 0; i < 10; i++) {
    trace(i);
}

И запустить его. Вы увидите, что у нас есть список целых чисел от 0 до 9. Наличие этого параметра открывает мир возможностей, о чем мы расскажем чуть позже.

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

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

1
2
3
for (var i:int = 0; i < 10; i–) {
    trace(i);
}

Можете ли вы определить, почему этот цикл никогда не закончится?


Есть два других «стандартных» цикла, которые на самом деле являются просто вариациями одной и той же идеи. Мы рассмотрим их кратко для полноты картины.

Сначала давайте посмотрим на цикл while. На самом деле это конструкция основного цикла. Цикл for компилируется в цикл while. Время от времени, тем не менее, цикл while может делать что-то, для чего цикл for слишком громоздок. Цикл while выглядит следующим образом:

1
2
3
4
5
var i:int = 0;
while (i < 10) {
    trace(i);
    i++;
}

На самом деле это то же самое, что и наш цикл for с последнего шага, который был написан некоторое время . Сам цикл while начинается с ключевого слова while и содержит фигурные скобки. Первая строка – это просто наш инициализатор, который необходим для того, чтобы подготовиться к циклу, но он не обрабатывается автоматически, как с циклом for . Часть между круглыми скобками после ключевого слова – это тест. Пока это правда , цикл будет выполняться. Тело нашего цикла состоит не только из того, чего мы хотим (трассировка i), но также из приращения переменной i. Опять же, необходимо посвятить этому строку, хотя цикл for обеспечивает удобство для этой очень распространенной идиомы.

Другими словами, этот код точно такой же, как в цикле for из предыдущего, только с большим количеством строк. Технически это не больше кода , просто больше строк .

Так зачем использовать цикл while? Обычно это потому, что у нас нет четкого представления о том, сколько раз что-то должно произойти. Представьте, что игровой персонаж движется в определенном направлении, и он должен остановиться, когда стена преграждает свой путь. Мы могли бы написать что-то вроде:

1
2
3
while (!character.hitTest(wall)) {
    character.forward();
}

Цикл do – часто пропускаемый двоюродный брат цикла while. Они практически идентичны, с двумя небольшими исключениями. В цикле do ключевое слово do (дух!). А также, время наступает после закрывающей фигурной скобки. Это выглядит так:

1
2
3
4
5
var i:int = 0;
do {
    trace(i);
    i++;
} while(i < 10);

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

Представьте себе специализированный генератор случайных чисел. Вы хотите выбрать случайное число от 1 до 10 при каждом нажатии кнопки, но вы хотите убедиться, что число не совпадает с последним выбранным числом. С помощью цикла do вы можете выбрать случайное число, сравнить его с последним числом, а затем повторить процесс по мере необходимости. В этом случае вам нужно выбрать хотя бы одно новое случайное число. Это может быть все, что вам нужно, но это должно произойти хотя бы один раз. Представьте, что переменная “randomNumber” содержит текущее случайное число и что этот код находится, скажем, в функции, которая подключается к нажатию кнопки.

1
2
3
4
var lastNumber:int = randomNumber;
do {
    randomNumber = Math.ceil(Math.random() * 10);
} while (randomNumber == lastNumber);

Этот код создает временную переменную с именем «lastNumber», в которой хранится текущее значение «randomNumber». Затем мы входим в цикл do . Так как это do , мы выполняем тело, затем заботимся о состоянии, поэтому мы присваиваем «randomNumber» новое случайное значение (беря некоторое значение, сгенерированное с помощью Math.random (), которое равно 0 к 1, умножается на 10 и затем округление с Math.ceil ()). Затем мы проверяем условие: если текущее значение «randomNumber» (которое мы только что изменили) совпадает со значением, хранящимся в «lastNumber» (которое было последним действительным значением «randomNumber»), то продолжайте цикл. Попробуйте еще раз, другими словами. Таким образом, если «randomNumber» был равен 7, и в первый раз в цикле также генерировалось 7, то мы повторяем цикл снова, пока не будет получено что-то отличное от 7. Результатом является случайное число, которое никогда не совпадает с последним, которое у нас было.


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

continue – ключевое слово, которое заставляет цикл немедленно начать итерацию снова. Это может привести к тому, что код не будет выполнен в теле, и в этом и заключается смысл продолжения цикла. Вы используете это так:

1
2
3
4
5
6
7
for (var i:int = 0; i < 10; i++) {
    trace(i);
    if (i > 5 && i < 8) {
        continue;
    }
    trace(“The value of i is: ” + i);
}

Это не очень полезный пример, но он иллюстрирует использование и эффекты continue . Это типичный цикл for , где «i» идет от 0 до 9. Первое, что нужно сделать в блоке – это отследить «i». Ничего страшного. Затем мы проверяем, находится ли «i» в определенном диапазоне: выше 5 и ниже 8. Если это так, то мы продолжаем . Последняя строка – еще один след. Таким образом, хотя «i» равно 0, 1, 2, 3, 4 или 5, мы видим две трассы для каждого значения «i». Но затем «i» превращается в 6, что больше 5 и меньше 8, поэтому мы продолжаем. Следовательно, эта итерация цикла немедленно прекращается, и мы начинаем следующую итерацию с «i», увеличиваемым и равным 7. 7 также соответствует критериям, и мы применяем аналогичный прием. В обоих случаях, поскольку мы продолжили работу до последней строки блока, мы видим только одну трассировку для этих двух значений «i».


break – это своего рода кнопка паники для петель. Его использование приведет к тому, что цикл просто завершит свое выполнение, независимо от того, насколько верно условие. break может быть полезен для создания отказоустойчивых сейфов в циклах while и do , поскольку они могут быть подвержены бесконечным циклам. Рассмотреть возможность:

1
2
3
4
var num:Number;
do {
    num == Math.random();
} while (num != 0.42);

Этот цикл do , хотя технически и не является бесконечным циклом, скорее всего, вызовет ограничение времени ожидания сценария до того, как будет сгенерирован точно 0.42. Мы можем построить механизм безопасности следующим образом:

1
2
3
4
5
6
7
8
9
var num:Number;
var loopCounter:int = 0;
do {
    num == Math.random();
    loopCounter++;
    if (loopCounter > 500) {
        break;
    }
} while (num != 0.42);

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

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


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

Во-первых, давайте предположим, что у нас был массив:

1
var ary:Array = [“one”, “two”, “three”, “four”, “five”];

Теперь мы можем перебрать этот массив. То есть мы знаем, что массивы неявно являются коллекциями значений. Мы хотим принимать каждое индивидуальное значение и что-то делать с каждым по очереди. Это очень распространенная задача, поэтому удобно иметь два типа цикла, посвященного этому действию.

Мы можем написать цикл for … in вот так:

1
2
3
for (var index:String in ary) {
    trace(index + “: ” + ary[index]);
}

for … in снова начинается с ключевого слова for , но использование ключевого слова in отличает его от обычного цикла for . То, что справа от in – это то, что мы хотим зациклить (массив). Материал слева от in является объявлением переменной. Эта переменная доступна для цикла (и, между прочим, в другом месте) и будет содержать для каждой итерации цикла индекс / ключ / имя каждого элемента, содержащегося в коллекции. В случае массива мы получаем числовые индексы. Обратите внимание, что они преобразуются в строки.

Итак, в переменной «index» хранится ключ. Это означает, что мы можем получить доступ к фактическим значениям, используя этот ключ в квадратных скобках в коллекции ( ary [index] ).

for … каждый цикл очень похож, с одним ключевым отличием: вместо создания переменной для хранения ключа, вы создаете переменную для хранения фактического значения элемента в коллекции.

1
2
3
for each (var item:String in ary) {
    trace(item);
}

Обратите внимание, что тип переменной String для переменной “item”. Это совпадает с тем, что было в цикле for … in . Однако в этом случае тип данных определяется тем, какие значения на самом деле хранятся в массиве. Если бы у нас был этот массив:

1
var ary:Array = [1, 2, 3, 4, 5];

Наш цикл может выглядеть так:

1
for each (var item:int in ary) {…

В теле цикла объявленная нами переменная будет содержать значение из коллекции в каждой итерации. Нам пришлось пройти через небольшой обруч, чтобы получить это значение в цикле for … in . For … каждая петля имеет тенденцию быть немного чище. Однако у вас нет доступа к ключу в этом цикле, поэтому, если это важно, вам нужно использовать цикл другого типа.

for … каждый цикл не только быстрее набирается, но и быстрее выполняется. Поэтому, если этот тип цикла соответствует вашим требованиям (то есть вам не нужно знать ключ), вы можете предпочесть его.


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

1
2
3
4
5
for (var i:int = 0; i < 5; i++) {
    for (var j:int = 0; j < 5; j++) {
        trace(i + “, ” + j);
    }
}

Если вы запустите этот фрагмент, вы увидите что-то вроде этого:

01
02
03
04
05
06
07
08
09
10
11
12
0, 0
0, 1
0, 2
0, 3
0, 4
1, 0
1, 1
1, 2
1, 3
1, 4
2, 0
… etc.

Это дает матрицу значений. В этом случае мы получаем все комбинации от 0, 0 до 4, 4.

Обратите внимание, однако, что крайне важно использовать другое имя переменной для вложенного цикла. Мы использовали «i» для первого цикла (известный как внешний цикл) и «j» для второго цикла ( внутренний цикл). Если бы мы использовали «я», как мы обычно делаем, мы бы просто с этим:

1
2
3
4
5
0, 0
1, 1
2, 2
3, 3
4, 4

В каждый момент времени может быть только одна переменная «i», и поэтому «i» достигает 5 во внутреннем цикле и, следовательно, соответствует условию во внешнем цикле, завершая все это раньше, чем мы планировали. К счастью, ActionScript 3 имеет некоторые встроенные средства защиты, которые помогают предотвратить эту ошибку.

Можно создать многомерные массивы следующим образом:

1
2
3
4
5
6
7
var grid:Array = [];
for (var i:int = 0; i < 5; i++) {
    grid[i] = [];
    for (var j:int = 0; j < 5; j++) {
        grid[i][j] = i + “, ” + j;
    }
}

Также стоит отметить, что мы можем разорвать (или продолжить) вложенные циклы по имени. Если вы используете разрыв во внутреннем цикле, он разрывает только текущий (внутренний) цикл, а не внешний цикл. Если вам нужно разорвать внешний цикл из внутреннего цикла, вы можете пометить свой внешний цикл следующим образом:

1
2
3
4
5
6
7
8
outerLoop: for (var i:int = 0; i < 5; i++) {
    for (var j:int = 0; j < 5; j++) {
        trace(i + “, ” + j);
        if (i == 3 && j == 2) {
            break outerLoop;
        }
    }
}

Обратите внимание на самый первый бит: метка , двоеточие, а затем оператор for . Это дает внешнему циклу метку, которую мы можем использовать с break и continue . Затем во внутреннем цикле, при определенном произвольном условии, мы вырываемся из всего цикла, определяя, какой цикл разорвать.


Мы видели, как использовать циклы for … in и for … each для циклического перебора массивов. Тем не менее, очень распространено использование цикла for для перебора массивов. Преимущества этого состоят в том, что у нас есть доступ к индексу (в отличие от … каждого ), и мы получаем индекс в виде int (в отличие от … в ).

В общем, это выглядит так:

1
2
3
4
var ary:Array = [“one”, “two”, “three”, “four”, “five”];
for (var i:int = 0; i < ary.length; i++) {
    trace(ary[i]);
}

Однако при этом рекомендуется придерживаться нескольких правил во имя оптимизации. Для этого простого 5-итерационного цикла это не имеет большого значения, но я призываю вас использовать привычку просто всегда использовать эти идеи.

Во-первых, поиск ary.length каждый раз в этом состоянии отнимает много времени. Если мы на самом деле не изменяем длину массива в цикле, мы должны получить это значение один раз, сохранить его в переменной и использовать переменную в проверке:

1
2
3
4
5
var ary:Array = [“one”, “two”, “three”, “four”, “five”];
var len:int = ary.length;
for (var i:int = 0; i < len; i++) {
    trace(ary[i]);
}

Во-вторых, убедитесь, что ваши числовые типы данных совпадают между вашей переменной “i” и вашей переменной “len”. Сравнения бывают быстрыми между и int и другим int, или Number и другим Number, но если вы сделаете «i» int и сделаете «len» Number, это сравнение будет значительно медленнее.

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

1
2
3
4
5
6
7
var ary:Array = [“one”, “two”, “three”, “four”, “five”];
var len:int = ary.length;
var item:String;
for (var i:int = 0; i < len; i++) {
    item = ary[i];
    trace(item);
}

Теперь, когда мы справились с сухой теорией, давайте создадим что-то забавное: простую игру в жанре «стреляй в них», основанную на лоусли ( очень свободно) на классических астероидах. Мы будем использовать циклы для перебора коллекций пуль и врагов, а также будем выполнять тесты на попадание в каждую из них. Не ожидайте слишком многого с точки зрения игрового процесса; Наша цель – использовать петли в практической обстановке, а не изучать все тонкости игрового дизайна.

Стартер FLA обеспечен основными художественными работами. Как обычно для серии AS3 101, мы больше заинтересованы в обучении программированию, а не в графической разработке проекта. В интересах времени, мы предположим, что вы начали с FLA, предоставленного в исходных файлах .

Короткий тур по FLA в порядке, однако. В библиотеке есть несколько предметов, большинство из которых находятся на сцене. Есть корабль игрока:

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

Есть символ под названием Астероид, представляющий что-то, что игрок должен сражаться.

Это не размещено на сцене, а подготовлено для использования с ActionScript. Вы можете заметить в библиотеке, что этот символ имеет запись под «Связывание». Это позволяет нам работать с символом и настраивать отдельные экземпляры с помощью кода. Астероиды будут двигаться в произвольном направлении и обернуты по краям сцены.

Есть еще один символ под названием Bullet:

Это не на сцене, но имеет идентификатор Связи, как символ Астероида. Пули будут прикреплены к сцене программно и будут стрелять с корабля игрока вправо. Если есть столкновение между любой пулей и любым астероидом, они оба удаляются.

На самом деле это приводит к одному из приемов программирования массивов и циклов. Мы скоро разберемся с деталями, но следите за несколькими ошибками в нашем цикле.

Наконец, есть “Вы выиграли!” знак, размещенный на сцене в центре с именем экземпляра win .

Это будет невидимо до тех пор, пока игрок не выстрелит во все четыре астероида, после чего игра прекратится, и это появится.


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

Когда ваш слой создан и выбран, нажмите F9 (Opt-F9 на Mac), чтобы открыть панель «Действия». Нажмите значок булавки внизу, чтобы он не терял контекста, если щелкнуть вокруг приложения.

Первое, что мы хотим сделать, это убедиться, что экран выигрыша скрыт. Итак, самый первый бит, который мы напишем:

1
win.visible = false;

Следующим элементом нашего сценария будет создание двух массивов, которые будут содержать все экранные астероиды (в одном массиве) и все экранные маркеры (в другом). Оба массива пока пусты. Мы будем создавать пули и астероиды по мере необходимости и помещать их в массивы. Наши следующие несколько строк выглядят так:

1
2
var asteroids:Array = [];
var bullets:Array = [];

Мы будем немного манипулировать этими массивами, поэтому, если вам нужен учебник по массивам, обратитесь к моему последнему руководству по массивам .


Мы знаем, что хотим создать 4 астероида, поэтому мы будем использовать простой цикл while для их создания и настройки.

1
2
3
4
5
6
7
while (asteroids.length < 4) {
    var asteroid:MovieClip = new Asteroid();
    asteroid.x = 450 + Math.random() * 200 – 100;
    asteroid.y = Math.random() * (stage.stageHeight – asteroid.height);
    addChild(asteroid);
    asteroids.push(asteroid);
}

Здесь мы делаем новый экземпляр символа астероида. Затем мы размещаем его случайным образом (мы размещаем x в пределах 100 пикселей от 450, а y – в пределах высоты сцены). Затем мы используем addChild (), чтобы увидеть его, а затем сохранить его в массиве астероидов с помощью push.

Если новый материал Asteroid () для вас новый, не волнуйтесь, мы рассмотрим темы списка отображения в следующем уроке по AS3 101.

Обратите внимание, почему этот цикл while работает: он продолжает работать до тех пор, пока длина массива не станет равной 4 (или выше). И в каждой итерации массива мы добавляем один элемент в массив. Поэтому цикл запускается 4 раза.

Почему я использовал цикл while, а не цикл for ? Отчасти потому, что это казалось более чистым подходом. В конце концов, это на самом деле не имеет значения, но поскольку нам действительно не нужна переменная counter, которая обычно используется для циклов for , мы могли бы сэкономить небольшую типизацию с помощью цикла while. Я признаю, хотя; это отчасти потому, что для учебника было неплохо использовать цикл while. Просто помните, что всегда есть несколько способов достижения одного и того же результата, и путь, который вы выбираете, имеет такое же отношение к личным предпочтениям, как и к «правильному» способу делать вещи (о, и, кстати, есть нет “правильного способа” делать вещи в программировании).


Чтобы вести игру, нам нужно будет делать много вещей каждый раз, когда обновляется стадия, с частотой кадров. Мы настроим слушатель события для события ENTER_FRAME и выполним большую часть нашей логики в функции слушателя. А пока просто настройте скелет:

1
2
3
4
5
addEventListener(Event.ENTER_FRAME, onTick);
 
function onTick(e:Event):void {
    trace(“game going…”);
}

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


С настроенной функцией onTick мы можем начать перемещать вещи вокруг. Начнем с корабля.

Мы просто сопоставим y корабля с у мыши:

1
2
3
function onTick(e:Event):void {
    ship.y = stage.mouseY;
}

Это запрашивает у сцены координату y мыши. Это отличается от запроса другого MovieClip для координат мыши; вы получите значения обратно в координатном пространстве запрошенного вами фрагмента ролика. Используя сцену, мы знаем, что находимся в основном координатном пространстве.

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


Мы также можем включить логику движения астероидов в функцию onTick, но это немного сложнее. Во-первых, мы будем использовать петлю для перемещения по массиву астероидов. Затем для каждого астероида мы добавим (или вычтем) текущие x и y на определенную величину. Это приводит к постепенному перемещению объектов с каждым кадром, создавая иллюзию плавного движения во времени. Поместите следующий код после кода для перемещения корабля:

1
2
3
4
for each (var asteroid:MovieClip in asteroids) {
    asteroid.x += 3;
    asteroid.y += 4;
}

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


В нашей игре прямо сейчас мир плоский, и вы просто исчезаете с края и никогда не вернетесь, если зайдете слишком далеко. Давайте сделаем мир круглым, так что уход в одном направлении означает, что в конечном итоге вы снова окажетесь в другом направлении. Мы сделаем это, проверив x и y каждого астероида после их перемещения. Если они расположены так, что весь астероид находится вне экрана, мы отрегулируем x или y так, чтобы они возвращались на другую сторону. Заполните для каждого цикла из последнего шага с этим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
for each (var asteroid:MovieClip in asteroids) {
    asteroid.x += 3;
    asteroid.y += 4;
    if (asteroid.x < -asteroid.width) {
        asteroid.x = stage.stageWidth;
    }
    else if (asteroid.x > stage.stageWidth) {
        asteroid.x = -asteroid.width;
    }
    if (asteroid.y < -asteroid.height) {
        asteroid.y = stage.stageHeight;
    }
    else if (asteroid.y > stage.stageHeight) {
        asteroid.y = -asteroid.height;
    }
}

Мы делаем четыре проверки, используя операторы if . Мы проверяем положение x каждого астероида дважды, один раз для левого края и один раз для правого края. Затем мы проверяем положение у дважды, для верха и низа. Во всех случаях мы проверяем, полностью ли сорван мувиклип, а затем перемещаем его так, чтобы он полностью отклонялся от противоположного края.

Например, возьмем первый оператор if . Если значение x меньше 0, астероид начинает исчезать с экрана. Но он все еще может быть частично видимым, поэтому мы должны проверить, что x меньше 0 минус ширина клипа. То есть, если клип имеет ширину 100 пикселей, нам нужно убедиться, что астероид по крайней мере х -100, прежде чем он полностью затенен, и в этот момент мы можем предпринять действия. Помните, точка регистрации должна быть в левом верхнем углу клипа.

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

Если вы посмотрите на оставшиеся три условия, вы увидите, как из этой логики появляется шаблон.

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


Чтобы астероиды имели независимое движение, каждый из них должен иметь свою собственную скорость, а не «3» и «4», которые мы подключили ранее. Для этого мы будем использовать объект Point для представления скоростей x и y одного астероида. Мы свяжем каждую точку с ее астероидным клипом, используя словарь. Для начала создайте словарь вне функции onTick и перед циклом while, который создает астероиды.

1
var velocities:Dictionary = new Dictionary();

Затем, сразу после этого, мы поместим значения в словарь. Мы зациклим массив астероидов (снова используем их для каждого ) и используем клип астероидов в качестве ключа, а новую точку – в качестве значения. Значения x и y Точки будут определены с использованием случайных чисел.

Затем мы поместим значения в словарь. Мы добавим строку в цикл while, который устанавливает астероиды. Ниже полный цикл; три новые строки в конце.

01
02
03
04
05
06
07
08
09
10
while (asteroids.length < 4) {
    var asteroid:MovieClip = new Asteroid();
    asteroid.x = 450 + Math.random() * 200 – 100;
    asteroid.y = Math.random() * (stage.stageHeight – asteroid.height);
    addChild(asteroid);
    asteroids.push(asteroid);
    var xVel:Number = Math.random() * 6 – 3;
    var yVel:Number = Math.random() * 6 – 3;
    velocities[asteroid] = new Point(xVel, yVel);
}

Теперь в функции onTick мы можем получить эти скорости обратно из Словаря, используя клавишу астероида, и применить значения скорости к позиции:

1
2
3
4
5
for each (var asteroid:MovieClip in asteroids) {
   var vel:Point = velocities[asteroid];
   asteroid.x += vel.x;
   asteroid.y += vel.y;
   // Asteroids loop continues...

Если этот шаг немного сбивает с толку, помните, что я рассмотрел словари в моем учебнике по условным обозначениям .

Вот весь сценарий так, как он должен стоять на данный момент:

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
win.visible = false;
 
var asteroids:Array = [];
var bullets:Array = [];
var velocities:Dictionary = new Dictionary();
 
while (asteroids.length < 4) {
    var asteroid:MovieClip = new Asteroid();
    asteroid.x = 450 + Math.random() * 200 - 100;
    asteroid.y = Math.random() * (stage.stageHeight - asteroid.height);
    addChild(asteroid);
    asteroids.push(asteroid);
    var xVel:Number = Math.random() * 6 - 3;
    var yVel:Number = Math.random() * 6 - 3;
    velocities[asteroid] = new Point(xVel, yVel);
}
 
addEventListener(Event.ENTER_FRAME, onTick);
 
function onTick(e:Event):void {
    ship.y = stage.mouseY;
  
    for each (var asteroid:MovieClip in asteroids) {
        var vel:Point = velocities[asteroid];
        asteroid.x += vel.x;
        asteroid.y += vel.y;
        if (asteroid.x < -asteroid.width) {
            asteroid.x = stage.stageWidth;
        }
        else if (asteroid.x > stage.stageWidth) {
            asteroid.x = -asteroid.width;
        }
        if (asteroid.y < -asteroid.height) {
            asteroid.y = stage.stageHeight;
        }
        else if (asteroid.y > stage.stageHeight) {
            asteroid.y = -asteroid.height;
        }
    }
}

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


Теперь перейдем к фрагментации! Когда игрок нажимает кнопку мыши, мы запускаем пулю. Нам нужно начать с настройки прослушивателя событий для MOUSE_DOWN. Добавьте следующую строку туда, куда вы добавляете слушателя ENTER_FRAME:

1
stage.addEventListener(MouseEvent.MOUSE_DOWN, fire);

А затем добавьте эту функцию огня в самом конце скрипта:

1
2
3
4
5
6
7
function fire(me:MouseEvent):void {
    var b:Bullet = new Bullet();
    bx = 110;
    by = ship.y;
    addChild(b);
    bullets.push(b);
}

Это будет вызывать функцию «огонь» при каждом нажатии кнопки мыши. Почему мы добавили слушателя на сцену? Поскольку событие MOUSE_DOWN возникает только в том случае, если курсор мыши находится над экранным объектом, когда событие происходит. Итак, если бы мы добавили событие на корабль, мы бы получили много пропущенных пожаров, каждый раз, когда мышь не находилась прямо над кораблем. Использование сцены гарантирует, что мышь всегда будет находиться над чем-то (если только вы не выйдете за пределы Flash Player).

Функция огня немного длинная, но относительно прямолинейная. Здесь мы используем преимущества идентификатора связи, установленного для символа Bullet в библиотеке. Мы создаем новый клип Bullet, позиционируем его, а затем используем addChild (), чтобы убедиться, что мы действительно его видим. И наконец, мы следим за пулями, помещая их в массив пуль.

Если вы находите эту логику немного запутанной, не беспокойтесь, мы рассмотрим темы списка отображения в нашей следующей версии AS3 101. Этот код будет иметь смысл, если вы подождете несколько недель.

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


Вернувшись в функцию onTick, нам нужно перебрать массив маркеров и переместить каждую пулю. Это будет напоминать о перемещении астероидов, но нам нужно будет относиться к этому немного иначе. Во-первых, мы точно знаем, как перемещать каждую пулю, поэтому нет необходимости отслеживать скорости. Поскольку пули движутся горизонтально вправо, нет необходимости проверять какие-либо границы сцены, кроме правого края. По причинам, которые будут понятнее на следующем шаге, мы также перейдем к «традиционному» циклу for вместо каждого цикла. Нам понадобится индекс Array и более точное управление циклом, поэтому мы будем придерживаться классики.

Добавьте этот блок в конец вашей функции onTick:

1
2
3
4
5
6
7
for (var i:int = 0; i < bullets.length; i++) {
    var bullet:Bullet = bullets[i];
    bullet.x += 3;
    if (bullet.x > stage.stageWidth + 2) {
        trace("Need to remove bullet: " + bullet);
    }
}

Это должно быть довольно очевидно; все, что мы делаем, это вытащим каждую пулю из массива маркеров и переместим ее на 3 пикселя вправо. Затем, если х настолько высоко, что он справа, мы прослеживаем сообщение. Мы сделаем нашу реальную уборку на следующем шаге.

Проверьте фильм, и пули должны двигаться. Но только потому, что пули покидают сцену, они не ушли! Следы должны доказать это. Они все еще в Массиве, все еще на сцене и все еще в движении. Нам нужно удалить эти тезисы как со сцены, так и из массива.


Удалите этот след и замените его следующим:

1
removeChild(bullet);

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

Удалить элемент из массива легко, если вы знаете его индекс (и вы всегда можете использовать метод Array indexOf, чтобы найти его). Мы просто используем метод сплайсинга. Второй параметр сращивания – это количество элементов, которые нужно удалить из массива, начиная с индекса, указанного в первом параметре. Так…

1
bullets.splice(i, 1);

… при добавлении чуть ниже линии removeChild удалит тот же самый фрагмент маркера из массива.

Тем не менее, есть проблема, и это распространенная проблема даже для опытных разработчиков. Проблема тонкая, но достаточно, чтобы скинуть более сложные программы. Допустим, у нас есть 10 маркеров в массиве (индексы от 0 до 9), и мы определили, что маркер с индексом 2 необходимо удалить. Я переменная 2. Таким образом , мы удаляем индекс 2. Индексы 3 по 9 слайду вперед , чтобы заполнить разрыв, став индексы 2 до 8.

Тем не менее, цикл продолжается, и я увеличивается. мне сейчас 3, а затем мы получаем третий элемент из массива. Если вы обратили внимание, вы заметите, что элемент сразу после удаленного элемента никогда не привлекает внимания. Это было по индексу 3, когда мне было 2, а затем он опустился до индекса 2, как и я стал 3. Теперь, в нашем случае, он просто будет обнаружен при следующем выполнении функции onTick, но это сложная вещь это блокировало многих программистов.

Чтобы это исправить, мы можем просто уменьшить i в самом конце цикла, после удаления маркера.

1
i--

Используя наш предыдущий пример, это поместило бы меня из 2 обратно в 1, сразу после того, как элемент 3 стал элементом 2. Затем я увеличивался при повторном запуске цикла и теперь равен 2. Затем его можно использовать для выбора следующего элемента. ,

Также обратите внимание, что для того, чтобы это работало, нам нужно делать именно то, что я рекомендовал, и использовать длину массива непосредственно в объявлении цикла. Если бы мы сохранили длину в переменной, как я предположил, она не смогла бы зафиксировать изменения в длине массива. В большинстве случаев вы не изменяете длину массива, когда зацикливаетесь на нем, поэтому совет, который я дал, является здравым. В этом случае, однако, это вызовет больше проблем, чем решит.

Вся петля пули должна выглядеть так:

1
2
3
4
5
6
7
8
9
for (var i:int = 0; i < bullets.length; i++) {
    var bullet:Bullet = bullets[i];
    bullet.x += 3;
    if (bullet.x > stage.stageWidth - 100 + 2) {
        removeChild(bullet);
        bullets.splice(i, 1);
        i–;
    }
}

Проверьте фильм, чтобы убедиться, что все работает. Если все прошло хорошо, все выглядит именно так, как они делали в прошлый раз, только теперь мы знаем, что наше управление памятью в порядке.


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

Мы еще раз добавим код в функцию onTick. После цикла перемещения пули, над которым мы работали на последних двух шагах, добавьте этот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
for (i = 0; i < asteroids.length; i++) {
    asteroid = asteroids[i];
    for (var j:int = 0; j < bullets.length; j++) {
        bullet = bullets[j];
        if (asteroid.hitTestObject(bullet)) {
            removeChild(asteroid);
            removeChild(bullet);
            asteroids.splice(i, 1);
            bullets.splice(j, 1);
            i–;
            break;
        }
    }
}

Это довольно много кода, поэтому давайте разберем его.

Мы начнем с цикла по массиву астероидов. Поскольку мы проверяем наличие коллизий и, возможно, удаляем астероиды в ходе цикла, мы используем стандарт цикла for , исключая переменную для хранения длины массива.

И тогда мы получим ссылку на астероид для этой итерации. И тогда мы начинаем вложенный цикл. Обратите внимание на использование переменной j, чтобы отделить переменную счетчика этого вложенного цикла от внешней. И первое, что нужно сделать во вложенном цикле, это получить ссылку на маркер.

До сих пор это достигается способностью смешивать и сочетать каждую комбинацию астероида и пули. Нас не волнует пуля-пуля или астероид-астероид, поэтому между каждой комбинацией i и j мы должны быть в состоянии сравнить каждую пулю с каждым астероидом.

И сравнение – следующий шаг: мы используем простой вызов hitTestObject на астероиде, чтобы увидеть, поражает ли он пулю. Если это так, мы выполняем остальную часть кода, принадлежащего условию. В противном случае мы просто продолжаем цикл и проверяем следующую комбинацию.

Если мы получаем удар, мы используем removeChild, чтобы визуально удалить оба элемента. И затем мы используем сплайс, чтобы удалить их из соответствующих массивов.

Наконец, мы уменьшаем i , как обсуждалось ранее. Но почему мы не уменьшаем j ? Потому что если мы нашли удар по астероиду, то, очевидно, никакая другая пуля не сможет его ударить. Нет смысла продолжать перебирать массив пуль, чтобы искать попадания между уже объявленным мертвым астероидом и другой пулей. Поэтому вместо этого мы используем ключевое слово break и останавливаем цикл маркера там, где он есть. Это позволяет нам снова взять внешний цикл и проверить следующий астероид.


Один последний шаг, и это проверить, выиграли ли мы. Мы можем сказать, проверив длину массива астероидов после прохождения цикла предыдущего шага. Если длина равна 0, то у нас больше нет астероидов, и мы выиграли!

1
2
3
4
5
if (asteroids.length == 0) {
    win.visible = true;
    setChildIndex(win, numChildren - 1);
    removeEventListener(Event.ENTER_FRAME, onTick);
}

На этом этапе мы можем удалить слушателя ENTER_FRAME и показать поздравительный плакат, установив для свойства visible значение true и убедившись, что он находится поверх всех других визуальных элементов, с помощью setChildIndex.

Ниже приведен полный сценарий, для справки:

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
83
84
85
86
win.visible = false;
 
var asteroids:Array = [];
var bullets:Array = [];
var velocities:Dictionary = new Dictionary();
 
while (asteroids.length < 4) {
    var asteroid:MovieClip = new Asteroid();
    asteroid.x = 450 + Math.random() * 200 - 100;
    asteroid.y = Math.random() * (stage.stageHeight - asteroid.height);
    addChild(asteroid);
    asteroids.push(asteroid);
    var xVel:Number = Math.random() * 6 - 3;
    var yVel:Number = Math.random() * 6 - 3;
    velocities[asteroid] = new Point(xVel, yVel);
}
 
 
addEventListener(Event.ENTER_FRAME, onTick);
stage.addEventListener(MouseEvent.MOUSE_DOWN, fire);
 
function onTick(e:Event):void {
    ship.y = stage.mouseY;
  
    for each (var asteroid:MovieClip in asteroids) {
        var vel:Point = velocities[asteroid];
        asteroid.x += vel.x;
        asteroid.y += vel.y;
        if (asteroid.x < -asteroid.width) {
            asteroid.x = stage.stageWidth;
        }
        else if (asteroid.x > stage.stageWidth) {
            asteroid.x = -asteroid.width;
        }
        if (asteroid.y < -asteroid.height) {
            asteroid.y = stage.stageHeight;
        }
        else if (asteroid.y > stage.stageHeight) {
            asteroid.y = -asteroid.height;
        }
    }
  
  
    for (var i:int = 0; i < bullets.length; i++) {
        var bullet:Bullet = bullets[i];
        bullet.x += 3;
        if (bullet.x > stage.stageWidth + 2) {
            removeChild(bullet);
            bullets.splice(i, 1);
            i–;
        }
    }
  
    for (i = 0; i < asteroids.length; i++) {
        asteroid = asteroids[i];
        for (var j:int = 0; j < bullets.length; j++) {
            bullet = bullets[j];
            if (asteroid.hitTestObject(bullet)) {
                removeChild(asteroid);
                removeChild(bullet);
                asteroids.splice(i, 1);
                bullets.splice(j, 1);
                i–;
                break;
            }
        }
    }
  
    if (asteroids.length == 0) {
        win.visible = true;
        setChildIndex(win, numChildren - 1);
        removeEventListener(Event.ENTER_FRAME, onTick);
    }
  
  
}
 
 
 
function fire(me:MouseEvent):void {
    var b:Bullet = new Bullet();
    bx = 110;
    by = ship.y;
    bullets.push(b);
    addChild(b);
}

К настоящему времени вы должны быть постоянным профессионалом в циклах. Объедините это со своими знаниями по массивам, и вы уже на пути к обработке динамического контента. На самом деле, это то, куда идут дела с AS3 101. Далее следует список отображения, а затем XML. Из всего, что вы узнали до сих пор в этой серии, а также из следующих двух, вы удивитесь, как раньше вы создавали Flash-сайты. Контент на основе данных – это освобождающий опыт.

Мы никогда не сталкивались с темой, известной как итераторы , которые являются объектами, предназначенными для обхода массивов и других коллекций без зацикливания, как мы это делали здесь. У них есть преимущество, заключающееся в том, что сам Массив остается вне поля зрения и защищен от разрушительных рук, но они немного продвинуты. Я упоминаю об этом, потому что другие языки предоставляют объекты итераторов или перечислителей, и в этих языках более распространено циклическое переключение по коллекции с использованием итератора. Возможно (и даже желательно, на мой взгляд) создавать свои собственные итераторы в ActionScript. Но нам нужно было провести черту где-то для этого урока.