Статьи

Создайте гоночную игру без 3D движка

Этот учебник предоставит вам альтернативу 3D для гоночных игр в ActionScript 3. Для этого примера стиля старой школы не требуется никакой внешней среды.


Давайте посмотрим на конечный результат, к которому мы будем стремиться:


Создайте новый набор документов Flash для ActionScript 3.0. Я буду использовать размеры 480x320px, частоту кадров 30 кадров в секунду и светло-синий фон. Сохраните файл с именем по вашему выбору.

Создать документ .FLA

Помимо FLA, нам также нужно создать класс документа . Создайте новый файл Actionscript и добавьте этот код:

01
02
03
04
05
06
07
08
09
10
11
12
package
{
    import flash.display.MovieClip;
     
    public class Main extends MovieClip
    {
        public function Main()
        {
             
        }
    }
}

Сохраните этот файл в том же каталоге, что и наш FLA. Назовите это Main.as.


Чтобы скомпилировать код из класса Main , нам нужно связать его с FLA. На панели « Свойства» FLA рядом с « Класс» введите имя класса документа, в данном случае « Основной» .

Свяжите основной класс с FLA

Затем сохраните изменения в FLA.


Нам нужно начать с линии, представляющей один отрезок дороги. Нажмите R, чтобы выбрать инструмент Прямоугольник . В этом примере я собираюсь сделать серый прямоугольник для самой дороги, два маленьких красных прямоугольника на каждом краю серого и зеленые прямоугольники, чтобы заполнить остальную часть линии. Зеленые должны быть даже шире сцены, я делаю их шириной 1500 пикселей. Ширина дороги может варьироваться в зависимости от ваших потребностей, я буду использовать один из 245 пикселей в ширину. Они не обязательно должны быть очень высокими, так как мы будем использовать несколько экземпляров, чтобы нарисовать всю дорогу на экране. Я сделаю их высотой 10 пикселей.

Нарисуйте несколько прямоугольников рядом друг с другом для линии дороги

После того как вы нарисовали все прямоугольники, выделите их все ( Ctrl + A ) и нажмите F8, чтобы создать мувиклип из тех прямоугольников, которые вы только что создали. Назовите его «Дорога», убедитесь, что точка регистрации находится в центре, и установите флажок «Экспорт для ActionScript».

Создайте мувиклип из нарисованных вами прямоугольников.

В итоге вы получите дорожный видеоклип в библиотеке.

Road MovieClip в библиотеке.

Вам решать, хотите ли вы нарисовать каждый прямоугольник на разных слоях. Я просто собираюсь нанести серый на второй слой. Если у вас есть какой-либо экземпляр Road на сцене, удалите его. Мы добавим Road MovieClip по коду позже.


Вернемся к основному классу. Мы собираемся использовать этот Road MovieClip, чтобы создать иллюзию гоночной трассы.

Мы собираемся определить глубину видимой дороги, а также размеры игровой площадки. Кроме того, в нашем классе все экземпляры Road, которые мы добавляем на сцену, будут доступны из массива. Мы будем использовать другой массив ( zMap ) для определения глубины каждой линии.

В этом примере я установлю глубину 150 дорожных линий в игровой зоне 480×320 (необязательно иметь одинаковый размер сцены, но так как это все, что будет показано, я буду использовать эти цифры) ,

01
02
03
04
05
06
07
08
09
10
11
12
13
//Depth of the visible road
private const roadLines:int = 150;
//Dimensions of the play area.
private const resX:int = 480;
private const resY:int = 320;
//Line of the player’s car.
private const noScaleLine:int = 8;
//All the road lines will be accessed from an Array.
private var zMap:Array = [];
private var lines:Array = [];
private var halfWidth:Number;
private var lineDepth:int;
private const widthStep:Number = 1;

Мы будем использовать все предыдущие переменные и константы внутри функции Main . Мы будем масштабировать каждую строку в соответствии с их глубиной.

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
public function Main()
{
    //Populate the zMap with the depth of the road lines
    for (var i:int = 0; i < roadLines; i++)
    {
        zMap.push(1 / (i — resY / 2));
    }
    //We want the line at the bottom to be in front of the rest,
    //so we’ll add every line at the same position, bottom first.
    lineDepth = numChildren;
    for (i = 0; i < roadLines; i++)
    {
        var line = new Road();
        lines.push(line);
        addChildAt(line, lineDepth);
        line.x = resX / 2;
        line.y = resY — i;
    }
    //Scaling the road lines according to their position
    halfWidth = resX / 2;
    for (i = 0; i < roadLines; i++)
    {
        lines[i].scaleX = halfWidth / 60 — 1.2;
        halfWidth -= widthStep;
    }
}

Если вы опубликуете (Ctrl + Enter) документ на этом этапе, вы увидите прямую дорогу.

Прямая дорога, нарисованная серией дорожных видеороликов, которые мы создали ранее.

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


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

Перейдите на панель « Библиотека» и дважды щелкните дорожный видеоклип, чтобы вернуться к нарисованным прямоугольникам. Теперь нажмите F6, чтобы вставить новый ключевой кадр (если у вас более одного слоя, вы можете захотеть вставить новый ключевой кадр на каждый слой). Теперь, основываясь на первом кадре, вы можете изменить цвета прямоугольников или каким-то образом изменить их дизайн. Я поменяю их цвет и добавлю несколько полос на второй кадр.

Измените цвета или добавьте линии полосы на втором кадре.

Мы собираемся определить новую переменную в классе Main, чтобы сохранить последовательность на линии игрока (при условии, что в игре будет автомобиль, мы будем сохранять масштабирование до 1 на этой линии)

1
private var playerZ:Number;

Далее мы изменим функцию Main .


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

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
public function Main()
{
    for (var i:int = 0; i < roadLines; i++)
    {
        zMap.push(1 / (i — resY / 2));
    }
 
    playerZ = 100 / zMap[noScaleLine];
    for (i = 0; i < roadLines; i++)
    {
        zMap[i] *= playerZ;
    }
 
    lineDepth = numChildren;
    for (i = 0; i < roadLines; i++)
    {
        var line = new Road();
        lines.push(line);
        addChildAt(line, lineDepth);
        line.x = resX / 2;
        line.y = resY — i;
    }
     
    halfWidth = resX / 2;
    for (i = 0; i < roadLines; i++)
    {
        if (zMap[i] % 100 > 50)
            lines[i].gotoAndStop(1);
        else
            lines[i].gotoAndStop(2);
        lines[i].scaleX = halfWidth / 60 — 1.2;
        halfWidth -= widthStep;
    }
}

Возможно, нет необходимости умножать на 100, чтобы получить сегменты правильно, но это числа, которые я буду использовать в этом примере, вы можете изменять числа по своему вкусу (и если вы что-то напортачили, у вас есть это как ссылка).

Добавьте альтернативные линии к дороге.

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

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

1
2
private var speed:int = 20;
private var texOffset:int = 100;

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

1
import flash.events.Event;

Далее мы собираемся вырезать последнее условие в функции Main() и переместить его в новую функцию, которую мы создаем. Эта новая функция будет вызвана событием EnterFrame, поэтому мы будем постоянно двигаться по дороге. Давайте назовем это race() .

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
public function Main()
{
    for (var i:int = 0; i < roadLines; i++)
    {
        zMap.push(1 / (i — resY / 2));
    }
 
    playerZ = 100 / zMap[noScaleLine];
    for (i = 0; i < roadLines; i++)
    {
        zMap[i] *= playerZ;
    }
 
    lineDepth = numChildren;
    for (i = 0; i < roadLines; i++)
    {
        var line = new Road();
        lines.push(line);
        addChildAt(line, lineDepth);
        line.x = resX / 2;
        line.y = resY — i;
    }
     
    halfWidth = resX / 2;
    for (i = 0; i < roadLines; i++)
    {
        lines[i].scaleX = halfWidth / 60 — 1.2;
        halfWidth -= widthStep;
    }
     
    addEventListener(Event.ENTER_FRAME, race);
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function race(event:Event):void
{
    for (var i:int = 0; i < roadLines; i++)
    {
        if ((zMap[i] + texOffset) % 100 > 50)
            lines[i].gotoAndStop(1);
        else
            lines[i].gotoAndStop(2);
    }
    texOffset = texOffset + speed;
    while (texOffset >= 100)
    {
        texOffset -= 100;
    }
}

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


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

В этом примере я буду чередовать кривые справа с прямыми участками. Дальнейший путь будет сохранен в переменной nextStretch . Кроме того, мы будем перемещать положение х линий на кривых.

1
2
3
4
5
private var rx:Number;
private var dx:Number;
private var ddx:Number = 0.02;
private var segmentY:int = roadLines;
private var nextStretch = «Straight»;

Переменная rx будет хранить позицию x каждой строки, поэтому мы хотим, чтобы она начиналась с центра и брала кривые оттуда. Кроме того, ddx контролирует четкость кривых. В этом примере у меня это будет 0,02; Вы могли бы хотеть изменить его значение между кривыми. Вот как будет выглядеть новая функция race() :

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
private function race(event:Event):void
{
    rx = resX / 2;
    dx = 0;
    for (var i:int = 0; i < roadLines; i++)
    {
        if ((zMap[i] + texOffset) % 100 > 50)
            lines[i].gotoAndStop(1);
        else
            lines[i].gotoAndStop(2);
        lines[i].x = rx;
         
        if (nextStretch == «Straight»)
        {
            if (i >= segmentY)
                dx += ddx;
            else
                dx -= ddx / 64;
        }
        else if (nextStretch == «Curved»)
        {
            if (i <= segmentY)
                dx += ddx;
            else
                dx -= ddx / 64;
        }
        rx += dx;
    }
    texOffset = texOffset + speed;
    while (texOffset >= 100)
    {
        texOffset -= 100;
    }
    segmentY -= 1;
    while (segmentY < 0)
    {
        segmentY += roadLines;
        if (nextStretch == «Curved»)
            nextStretch = «Straight»;
        else
            nextStretch = «Curved»;
    }
}

На этот раз мы не будем касаться основной функции. Если вы публикуете его сейчас, вы должны получить что-то вроде этого:

Кривые.

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


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

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

Мы только что создали переменные для x- позиций дорожных линий, теперь давайте сделаем таковые для y- позиций.

1
2
3
private var ry:Number;
private var dy:Number;
private var ddy:Number = 0.01;

Для простоты в этом примере я собираюсь использовать одни и те же прямые сегменты как для прямолинейного эффекта с подъемом, так и кривых как для кривой, так и для наклонного эффекта.

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
private function race(event:Event):void
{
    rx = resX / 2;
    ry = resY;
    dx = 0;
    dy = 0;
    for (var i:int = 0; i < roadLines; i++)
    {
        if ((zMap[i] + texOffset) % 100 > 50)
            lines[i].gotoAndStop(1);
        else
            lines[i].gotoAndStop(2);
        lines[i].x = rx;
        lines[i].y = ry;
         
        if (nextStretch == «Straight»)
        {
            if (i >= segmentY)
            {
                dx += ddx;
                dy -= ddy;
            }
            else
            {
                dx -= ddx / 64;
                dy += ddy;
            }
        }
        else if (nextStretch == «Curved»)
        {
            if (i <= segmentY)
            {
                dx += ddx;
                dy -= ddy;
            }
            else
            {
                dx -= ddx / 64;
                dy += ddy;
            }
        }
        rx += dx;
        ry += dy — 1;
    }
    texOffset = texOffset + speed;
    while (texOffset >= 100)
    {
        texOffset -= 100;
    }
    segmentY -= 1;
    while (segmentY < 0)
    {
        segmentY += roadLines;
        if (nextStretch == «Curved»)
            nextStretch = «Straight»;
        else
            nextStretch = «Curved»;
    }
}

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

Hills.

Игры старой школы не могли использовать преимущества Flash, но мы можем. Что-то простое, например, добавление градиента к дорожным линиям, будет иметь большое значение. Если вы хотите, вы можете использовать любые фильтры и текстуры, которые вам нравятся, но в этом примере я просто добавляю несколько простых градиентов, поэтому давайте вернемся к Road MovieClip.

В кадре 1 выберите серый прямоугольник, затем перейдите на панель «Цвет» и выберите « Линейный градиент» в раскрывающемся меню, затем выберите « Отражать цвет как поток», чтобы градиент продолжался от первого до последнего цвета. Я не говорю вам выбирать те же цвета, что и я, но я буду использовать # 666666 и # 999999 здесь. Если вам нужно повернуть градиент, нажмите клавишу F, чтобы переключиться на инструмент «Преобразование градиента», который позволит вам перемещать, вращать и изменять размер градиента. В этом случае я перемещаю градиент на четверть прямоугольника и изменяю его размер до половины размера прямоугольника, чтобы центр был светлее, а края — темнее. Я использую аналогичный размер для зеленой части, поэтому он будет постоянно меняться от темно-зеленого (# 006600) до светло-зеленого (# 009900).

Добавьте градиенты для лучшей текстуры.

Теперь перейдите к кадру 2 и создайте новые градиенты с разными цветами. Для серого прямоугольника я сохранил более светлый цвет и изменил только более темный цвет на # 777777. В зеленой части я изменил размер градиента, чтобы попытаться избежать появления шахматной доски, и изменение цветов было очень тонким (# 007700 и # 008800).

Тонкое изменение цвета от кадра 1 до кадра 2.

Может быть, теперь вы захотите добавить хороший фон на горизонте или немного графики для неба.


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

Надеюсь, вы нашли этот урок полезным. Спасибо за прочтение!