Статьи

Сборка классической игры змея в AS3

В этом уроке я хотел бы показать вам, как легко создать классическую игру «Змея» во Flash. Я постараюсь объяснить все легко, шаг за шагом, чтобы вы могли развивать игру в соответствии с вашими потребностями! Игра будет разработана в AS3, и я буду использовать FlashDevelop IDE .


Игра не будет сложной. Всякий раз, когда мы ударяем стену, она перезапускает игру После употребления яблока змея вырастет, и появится «новое» яблоко. (На самом деле, это будет то же самое яблоко, но я объясню это позже.)

Одним из наиболее важных аспектов игры является реакция кода на события KEY_DOWN. Змея только тогда изменит свое направление после того, как галочка пройдет, а не сразу после нажатия клавиши. Это означает, что если змея движется направо, и вы очень быстро нажимаете и оставляете ее, змея будет падать, а не вниз И влево. Без этой «особенности» змея позволила бы нам идти налево, пока мы идем направо, что означало бы, что она ударила сама.


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


В FlashDevelop создайте новый проект, а внутри папки ‘src’ создайте папку ‘com’. В папке «com» ​​создайте новый класс и назовите его «Element.as».

Установите размеры проекта 600x600px.

Структура проекта FlashDevelop

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

Поэтому мы не будем создавать новый класс для яблока. (Но если вы действительно хотите, вы можете.)


Класс Element создает квадрат. Он не рисует это на сцене, он просто создает это. Точка регистрации элемента — позиция, на которую указывают его координаты x и y — находится в верхнем левом углу.

После открытия Element.as вы увидите что-то вроде этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package com
{
    /**
     * …
     * @author Fuszenecker Zsombor
     */
    public class Element
    {
         
        public function Element()
        {
             
        }
         
    }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package com
{
    import flash.display.Shape;
     
    public class Element extends Shape
    {
        protected var _direction:String;
        //IF IT IS AN APPLE ->
        protected var _catchValue:Number;
         
        //color,alpha,width,height
        public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
        {
 
        }
    }
}

Теперь заполните функцию некоторым кодом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com
{
    import flash.display.Shape;
     
    public class Element extends Shape
    {
        protected var _direction:String;
        //IF IT IS AN APPLE ->
        protected var _catchValue:Number;
         
        //color,alpha,width,height
        public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
        {
            graphics.lineStyle(0, _c, _a);
            graphics.beginFill(_c, _a);
            graphics.drawRect(0, 0, _w, _h);
            graphics.endFill();
             
            _catchValue = 0;
        }
    }
}

Теперь, всякий раз, когда мы создаем элемент, он будет рисовать прямоугольник и по умолчанию для значения элемента будет установлено значение 0. (Он не помещает прямоугольник на сцену, он просто рисует его внутри себя. Обратите внимание, что мы не вызвали функцию addChild() .)

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

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
package com
{
    import flash.display.Shape;
     
    public class Element extends Shape
    {
        protected var _direction:String;
        //IF IT IS AN APPLE ->
        protected var _catchValue:Number;
         
        //color,alpha,width,height
        public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
        {
            graphics.lineStyle(0, _c, _a);
            graphics.beginFill(_c, _a);
            graphics.drawRect(0, 0, _w, _h);
            graphics.endFill();
             
            _catchValue = 0;
        }
         
        //ONLY USED IN CASE OF A PART OF THE SNAKE
        public function set direction(value:String):void
        {
            _direction = value;
        }
        public function get direction():String
        {
            return _direction;
        }
         
        //ONLY USED IN CASE OF AN APPLE
        public function set catchValue(value:Number):void
        {
            _catchValue = value;
        }
        public function get catchValue():Number
        {
            return _catchValue;
        }
    }
 
}

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


Откройте Main.as сейчас.

Импортируйте класс com.Element и создайте элемент в функции init() :

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
{
    import flash.display.Sprite;
    import flash.events.Event;
    import com.Element;
         
    public class Main extends Sprite
    {
        public function Main()
        {
            if(stage)
                addEventListener(Event.ADDED_TO_STAGE, init);
            else
                init();
        }
         
        private function init(e:Event = null):void
        {
            var testElement:Element = new Element(0x00AAFF, 1, 10, 10);
            testElement.x = 50;
            testElement.y = 50;
            this.addChild(testElement);
             
        }
         
    }
}

Сначала мы создаем переменную testElement которая содержит наш элемент. Мы создаем новый элемент и присваиваем его нашей переменной testElement . Обратите внимание на аргументы, которые мы передали: сначала мы даем ему цвет, затем альфа, ширину и высоту. Если вы посмотрите в функцию Element класса Element, вы увидите, как она использует эти данные для рисования прямоугольника.

После создания Элемента мы позиционируем его и выводим на сцену!


Посмотрите на следующий код. Я написал функции переменных рядом с ними (обратите внимание, что мы также импортировали необходимые классы):

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
package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.ui.Keyboard;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.events.Event;
     
    import com.Element;
         
    public class Main extends Sprite
    {
         
        //DO NOT GIVE THESE VARS A VALUE HERE!
        //Give them their values in the init() function.
        private var snake_vector:Vector.<Element>;
        private var markers_vector:Vector.<Object>;
        private var timer:Timer;
        private var dead:Boolean;
        private var min_elements:int;
        private var apple:Element;
        private var space_value:Number;
        private var last_button_down:uint;
        private var flag:Boolean;
        private var score:Number;
        private var score_tf:TextField;
         
        public function Main()
        {
            if(stage)
                addEventListener(Event.ADDED_TO_STAGE, init);
            else
                init();
        }
         
        private function init(e:Event = null):void
        {
            snake_vector = new Vector.<Element>;
            markers_vector = new Vector.<Object>;
            space_value = 2;
            timer = new Timer(50);
            dead = false;
            min_elements = 10;
            apple = new Element(0xFF0000, 1, 10, 10);
            apple.catchValue = 0;
            last_button_down = Keyboard.RIGHT;
            score = 0;
            score_tf = new TextField();
            this.addChild(score_tf);
        }
    }
}

Наиболее важной переменной является snake_vector . Мы поместим каждый Элемент змеи в этот Вектор.

Тогда есть markers_vector . Мы будем использовать маркеры, чтобы установить направление частей змеи. Каждый объект в этом векторе будет иметь позицию и тип. Тип скажет нам, должна ли змея идти вправо, влево, вверх или вниз после «удара» по объекту. (Они не будут сталкиваться, будет проверяться только положение маркеров и частей змеи.)

Например, если мы нажмем ВНИЗ, объект будет создан. Х и у этого объекта будут координатами x и y головы змеи, а тип будет «Вниз». Всякий раз, когда положение одного из элементов змеи совпадает с положением этого объекта, направление элементов змеи будет установлено на «Вниз».

Пожалуйста, прочтите комментарии рядом с переменными, чтобы понять, что делают другие переменные!


Функция attachElement() будет принимать четыре параметра: новый элемент змеи, координаты x и y и направление последней части змеи.

1
2
3
4
private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = «R»):void
{
 
}

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = «R»):void
{
    if (dirOfLast == «R»)
    {
        who.x = lastXPos — snake_vector[0].width — space_value;
        who.y = lastYPos;
    }
    else if(dirOfLast == «L»)
    {
        who.x = lastXPos + snake_vector[0].width + space_value;
        who.y = lastYPos;
    }
    else if(dirOfLast == «U»)
    {
        who.x = lastXPos;
        who.y = lastYPos + snake_vector[0].height + space_value;
    }
    else if(dirOfLast == «D»)
    {
        who.x = lastXPos;
        who.y = lastYPos — snake_vector[0].height — space_value;
    }
    this.addChild(who);
}

Теперь мы можем использовать эту функцию в функции init() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
for(var i:int=0;i<min_elements;++i)
{
    snake_vector[i] = new Element(0x00AAFF,1,10,10);
    snake_vector[i].direction = «R»;
    if (i == 0)//first snake element
    {
        //you have to place the first element on a GRID.
        //[possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
        attachElement(snake_vector[i],0,0,snake_vector[i].direction)
        snake_vector[0].alpha = 0.7;
    }
    else
    {
        attachElement(snake_vector[i], snake_vector[i — 1].x, snake_vector[i — 1].y, snake_vector[i — 1].direction);
    }
}

Мы создаем первые 10 элементов и устанавливаем направление их «R» (справа). Если это первый элемент, мы вызываем attachElement() и немного меняем его альфа (поэтому «голова» немного светлее).

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

Установка позиции x: (snake_vector[0].width+space_value)*[UINT] , где вы должны заменить [UINT] положительным целым числом.

Установка позиции y: (snake_vector[0].height+space_value)*[UINT] , где вы должны заменить [UINT] положительным целым числом.

Давайте изменим это на это:

01
02
03
04
05
06
07
08
09
10
11
12
if (i == 0)//first snake element
{
    //you have to place the first element on a GRID.
    //[possible x positions: (snake_vector[0].width+space_value)*<UINT>]
    attachElement(
        snake_vector[i],
        (snake_vector[0].width+space_value)*20,
        (snake_vector[0].height+space_value)*10,
        snake_vector[i].direction
    );
    snake_vector[0].alpha = 0.7;
}

И первый элемент змеи установлен на 20-е место в х-сетке и 10-е место в у-сетке.

Это то, что у нас так далеко:

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
87
88
89
90
91
92
93
94
95
package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.ui.Keyboard;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.events.Event;
     
    import com.Element;
         
    public class Main extends Sprite
    {
         
        //DO NOT GIVE THEM A VALUE HERE!
        private var snake_vector:Vector.<Element>;
        private var markers_vector:Vector.<Object>;
        private var timer:Timer;
        private var dead:Boolean;
        private var min_elements:int;
        private var apple:Element;
        private var space_value:Number;
        private var last_button_down:uint;
        private var flag:Boolean;
        private var score:Number;
        private var score_tf:TextField;
         
        public function Main()
        {
            if(stage)
                addEventListener(Event.ADDED_TO_STAGE, init);
            else
                init();
        }
         
        private function init(e:Event = null):void
        {
            snake_vector = new Vector.<Element>;
            markers_vector = new Vector.<Object>;
            space_value = 2;
            timer = new Timer(50);
            dead = false;
            min_elements = 10;
            apple = new Element(0xFF0000, 1,10, 10);
            apple.catchValue = 0;
            last_button_down = Keyboard.RIGHT;
            score = 0;
            score_tf = new TextField();
            this.addChild(score_tf);
             
            for(var i:int=0;i<min_elements;++i)
            {
                snake_vector[i] = new Element(0x00AAFF,1,10,10);
                snake_vector[i].direction = «R»;
                if (i == 0)//first snake element
                {
                    //you have to place the first element on a GRID.
                    attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
                    snake_vector[0].alpha = 0.7;
                }
                else
                {
                    attachElement(snake_vector[i], snake_vector[i — 1].x, snake_vector[i — 1].y, snake_vector[i — 1].direction);
                }
            }
        }
         
        private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = «R»):void
        {
            if (dirOfLast == «R»)
            {
                who.x = lastXPos — snake_vector[0].width — space_value;
                who.y = lastYPos;
            }
            else if(dirOfLast == «L»)
            {
                who.x = lastXPos + snake_vector[0].width + space_value;
                who.y = lastYPos;
            }
            else if(dirOfLast == «U»)
            {
                who.x = lastXPos;
                who.y = lastYPos + snake_vector[0].height + space_value;
            }
            else if(dirOfLast == «D»)
            {
                who.x = lastXPos;
                who.y = lastYPos — snake_vector[0].height — space_value;
            }
            this.addChild(who);
        }
    }
}

Эта функция делает следующее:

  1. Он проверяет, было ли яблоко поймано. Для этого мы будем использовать параметр catch и установить его значение по умолчанию в true , если в будущем мы не передадим какое-либо значение в качестве параметров. Если он был пойман, он добавляет 10 к значению оценки яблока (так что следующее яблоко стоит больше).
  2. После этого яблоко должно быть перемещено (мы не создаем новые яблоки) в случайную позицию сетки.
  3. Если это помещено в змею, мы должны поместить это где-нибудь еще.
  4. Если это еще не на стадии, мы помещаем это там.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
private function placeApple(caught:Boolean = true):void
{
    if (caught)
        apple.catchValue += 10;
         
    var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
    var randomX:Number = Math.floor(Math.random()*boundsX);
         
    var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
    var randomY:Number = Math.floor(Math.random()*boundsY);
 
    apple.x = randomX * (apple.width + space_value);
    apple.y = randomY * (apple.height + space_value);
         
    for(var i:uint=0;i<snake_vector.length-1;i++)
    {
        if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
            placeApple(false);
    }
    if (!apple.stage)
        this.addChild(apple);
}

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

  • boundsX будет содержать количество элементов, которые можно нарисовать в одном ряду.
  • randomX принимает это boundsX , умножает его на число от нуля до единицы и наполняет его. Если boundsX равен 12, а случайное число равно 0,356 , то floor(12*0.356) равно 4, поэтому яблоко будет помещено в 4-е место на x-сетке.
  • boundsY будет содержать количество элементов, которые можно нарисовать в одном столбце.
  • randomY берет это boundsY , умножает его на число от нуля до единицы и наполняет его.
  • Затем мы устанавливаем позиции x и y для этих чисел.

В цикле for мы проверяем, идентичны ли новые позиции x и y яблока любому из элементов snake_vectors . Если это так, мы снова вызываем функцию placeApple() ( рекурсивная функция ) и устанавливаем для ее параметра значение false . (Это означает, что яблоко не было поймано, нам просто нужно переместить его)

(apple.stage) возвращает true, если яблоко находится на сцене. мы используем ‘!’ оператор, чтобы инвертировать это значение, поэтому, если оно НЕ на сцене, мы помещаем его туда.

Последнее, что нам нужно сделать, это вызвать функцию placeApple() в конце функции init() .

01
02
03
04
05
06
07
08
09
10
private function init(e:Event = null):void
{
    /*
    .
    .
    .
    */
     
    placeApple(false);
}

Обратите внимание, что мы передаем false в качестве параметра. Это логично, потому что мы еще не поймали яблоко в функции init() . Мы поймаем его только в функции moveIt() .

Теперь осталось написать только три функции: функции directionChanged() , moveIt() и gameOver() .


Функция moveIt() отвечает за все движение. Эта функция проверит границы и проверит, есть ли объект в положениях x и y головы змеи. Он также будет искать яблоко в этой позиции.

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

Добавьте еще две строки в конец функции init() :

1
2
timer.addEventListener(TimerEvent.TIMER,moveIt);
timer.start();

Посмотрите на комментарии в исходном коде, чтобы увидеть, какой блок кода что делает.

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
private function moveIt(e:TimerEvent):void
{
    if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
    {
        //This code runs if the snakes heads position and the apples position are the same
    }
     
    if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
    {
        //This block runs if the snakes head is out of the stage (hitting the walls)
    }
     
     
    for (var i:int = 0; i < snake_vector.length; i++)
    {
        /*
            START OF FOR BLOCK
            This whole ‘for’ block will run as many times, as many elements the snake has.
            If there are four snake parts, this whole for cycle will run four times.
        */
         
        if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
        {
            //If the snakes heads position is the same as any of the snake parts, this block will run (Checking the collision with itself).
        }
     
        if (markers_vector.length > 0)
        {
            //if there are direction markers, this code runs
        }
     
         
        var DIRECTION:String = snake_vector[i].direction;
        switch (DIRECTION)
        {
            //Sets the new position of the snakes part
        }
         
        /*
            END OF FOR BLOCK
        */
    }
     
}

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

Сначала нам нужно проверить направление текущего элемента.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
switch (DIRECTION)
{
    case «R» :
        //Here we need to set the new x position for the current part
        break;
    case «L» :
        //Here we need to set the new x position for the current part
        break;
    case «D» :
        //Here we need to set the new y position for the current part
        break;
    case «U» :
        //Here we need to set the new y position for the current part
        break;
}

Например, когда направление детали установлено в «R», нам нужно добавить что-то к его текущей позиции X ( space_value плюс ширина части змеи).

Имея это в виду, мы можем заполнить его:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
switch (DIRECTION)
{
    case «R» :
        snake_vector[i].x += snake_vector[i].width + space_value;
        break;
    case «L» :
        snake_vector[i].x -= snake_vector[i].width + space_value;
        break;
    case «D» :
        snake_vector[i].y += snake_vector[i].height + space_value;
        break;
    case «U» :
        snake_vector[i].y -= snake_vector[i].width + space_value;
        break;
}

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

Поэтому нам нужно остановить змею


Эта функция будет самой короткой. Мы просто очищаем сцену и перезапускаем ее:

1
2
3
4
5
6
7
8
9
private function gameOver():void
{
    dead = true;
    timer.stop();
    while (this.numChildren)
        this.removeChildAt(0);
    timer.removeEventListener(TimerEvent.TIMER,moveIt);
    init();
}

Вот и все. Мы устанавливаем для переменной dead значение true, останавливаем движение с помощью таймера, удаляем каждого дочернего элемента класса и вызываем функцию init() , как мы только что начали игру.

Теперь вернемся к функции moveIt() .


Мы будем использовать gameOver() в двух местах. Первый — когда мы проверяем, находится ли голова вне границ, а второй — когда змея бьет себя:

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
private function moveIt(e:TimerEvent):void
{
    if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
    {
        //This code runs if the snakes heads position and the apples position are the same
    }
     
    if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
    {
        gameOver();
    }
     
     
    for (var i:int = 0; i < snake_vector.length; i++)
    {
        /*
            START OF FOR BLOCK
            This whole ‘for’ block will run as many times, as many elements the snake has.
            If there are four snake parts, this whole for cycle will run four times.
        */
         
        if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
        {
            //If the snakes heads position is the same as any of the snake parts, this block will run
            gameOver();
        }
     
        if (markers_vector.length > 0)
        {
            //if there are direction markers, this code runs
        }
     
         
        var DIRECTION:String = snake_vector[i].direction;
        switch (DIRECTION)
        {
            case «R» :
                snake_vector[i].x += snake_vector[i].width + space_value;
                break;
            case «L» :
                snake_vector[i].x -= snake_vector[i].width + space_value;
                break;
            case «D» :
                snake_vector[i].y += snake_vector[i].height + space_value;
                break;
            case «U» :
                snake_vector[i].y -= snake_vector[i].width + space_value;
                break;
        }
         
        /*
            END OF FOR BLOCK
        */
    }
     
}

Вот код, который мы имеем сейчас:

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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.ui.Keyboard;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.events.Event;
     
    import com.Element;
         
    public class Main extends Sprite
    {
         
        //DO NOT GIVE THEM A VALUE HERE!
        private var snake_vector:Vector.<Element>;
        private var markers_vector:Vector.<Object>;
        private var timer:Timer;
        private var dead:Boolean;
        private var min_elements:int;
        private var apple:Element;
        private var space_value:Number;
        private var last_button_down:uint;
        private var flag:Boolean;
        private var score:Number;
        private var score_tf:TextField;
         
        public function Main()
        {
            if(stage)
                addEventListener(Event.ADDED_TO_STAGE, init);
            else
                init();
        }
         
        private function init(e:Event = null):void
        {
            snake_vector = new Vector.<Element>;
            markers_vector = new Vector.<Object>;
            space_value = 2;
            timer = new Timer(50);
            dead = false;
            min_elements = 10;
            apple = new Element(0xFF0000, 1,10, 10);
            apple.catchValue = 0;
            last_button_down = Keyboard.RIGHT;
            score = 0;
            score_tf = new TextField();
            this.addChild(score_tf);
             
            for(var i:int=0;i<min_elements;++i)
            {
                snake_vector[i] = new Element(0x00AAFF,1,10,10);
                snake_vector[i].direction = «R»;
                if (i == 0)//first snake element
                {
                    //you have to place the first element on a GRID.
                    attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
                    snake_vector[0].alpha = 0.7;
                }
                else
                {
                    attachElement(snake_vector[i], snake_vector[i — 1].x, snake_vector[i — 1].y, snake_vector[i — 1].direction);
                }
            }
             
            placeApple(false);
            timer.addEventListener(TimerEvent.TIMER, moveIt);
            timer.start();
        }
         
        private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = «R»):void
        {
            if (dirOfLast == «R»)
            {
                who.x = lastXPos — snake_vector[0].width — space_value;
                who.y = lastYPos;
            }
            else if(dirOfLast == «L»)
            {
                who.x = lastXPos + snake_vector[0].width + space_value;
                who.y = lastYPos;
            }
            else if(dirOfLast == «U»)
            {
                who.x = lastXPos;
                who.y = lastYPos + snake_vector[0].height + space_value;
            }
            else if(dirOfLast == «D»)
            {
                who.x = lastXPos;
                who.y = lastYPos — snake_vector[0].height — space_value;
            }
            this.addChild(who);
        }
         
        private function placeApple(caught:Boolean = true):void
        {
            if (caught)
                apple.catchValue += 10;
             
            var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
            var randomX:Number = Math.floor(Math.random()*boundsX);
             
            var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
            var randomY:Number = Math.floor(Math.random()*boundsY);
 
            apple.x = randomX * (apple.width + space_value);
            apple.y = randomY * (apple.height + space_value);
             
            for(var i:uint=0;i<snake_vector.length-1;i++)
            {
                if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
                    placeApple(false);
            }
            if (!apple.stage)
                this.addChild(apple);
        }
         
        private function moveIt(e:TimerEvent):void
        {
            if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
            {
                //This code runs if the snakes heads position and the apples position are the same
            }
             
            if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
            {
                gameOver();
            }
             
             
            for (var i:int = 0; i < snake_vector.length; i++)
            {
                /*
                    START OF FOR BLOCK
                    This whole ‘for’ block will run as many times, as many elements the snake has.
                    If there are four snake parts, this whole for cycle will run four times.
                */
                 
                if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
                {
                    //If the snakes heads position is the same as any of the snake parts, this block will run
                    gameOver();
                }
             
                if (markers_vector.length > 0)
                {
                    //if there are direction markers, this code runs
                }
             
                 
                var DIRECTION:String = snake_vector[i].direction;
                switch (DIRECTION)
                {
                    case «R» :
                        snake_vector[i].x += snake_vector[i].width + space_value;
                        break;
                    case «L» :
                        snake_vector[i].x -= snake_vector[i].width + space_value;
                        break;
                    case «D» :
                        snake_vector[i].y += snake_vector[i].height + space_value;
                        break;
                    case «U» :
                        snake_vector[i].y -= snake_vector[i].width + space_value;
                        break;
                }
                 
                /*
                    END OF FOR BLOCK
                */
            }
             
        }
         
        private function gameOver():void
        {
            dead = true;
            timer.stop();
            while (this.numChildren)
                this.removeChildAt(0);
            timer.removeEventListener(TimerEvent.TIMER,moveIt);
            //stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
            init();
        }
         
    }
}

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

Поместите это в конец функции init() (настройка функции слушателя):

1
stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);

И это в конце функции gameOver() :

1
stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);

Теперь создайте новую функцию:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function directionChanged(e:KeyboardEvent):void
{
    var m:Object = new Object();
    //this will be added to the markers_vector, and have the properties x,y, and type
    //the type property will show us the direction.
    //the direction of that snake’s part will be set to right also
     
    if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
    {
        //If we pressed the LEFT arrow,
        //and it was not the last key we pressed,
        //and the last key pressed was not the RIGHT arrow either…
        //Then this block of code will run
    }
    markers_vector.push(m);
}

Что входит в блок if?

  • Направление головы должно быть переписано.
  • Маркерный объект должен быть установлен правильно.
  • Переменная last_button должна быть установлена ​​на последнюю нажатую кнопку.

1
2
3
4
5
6
if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
{
    snake_vector[0].direction = «L»;
    m = {x:snake_vector[0].x, y:snake_vector[0].y, type:»L»};
    last_button_down = Keyboard.LEFT;
}

Повторите это еще три раза, и у нас будет это:

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
private function directionChanged(e:KeyboardEvent):void
        {
            var m:Object = new Object();
 
            if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
            {
                snake_vector[0].direction = «L»;
                m = {x:snake_vector[0].x, y:snake_vector[0].y, type:»L»};
                last_button_down = Keyboard.LEFT;
            }
            else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT)
            {
                snake_vector[0].direction = «R»;
                m = {x:snake_vector[0].x, y:snake_vector[0].y, type:»R»};
                last_button_down = Keyboard.RIGHT;
            }
            else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN)
            {
                snake_vector[0].direction = «U»;
                m = {x:snake_vector[0].x, y:snake_vector[0].y, type:»U»};
                last_button_down = Keyboard.UP;
            }
            else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP)
            {
                snake_vector[0].direction = «D»;
                m = {x:snake_vector[0].x, y:snake_vector[0].y, type:»D»};
                last_button_down = Keyboard.DOWN;
            }
            markers_vector.push(m);
        }

Нам нужна еще одна вещь, чтобы проверить это. В функции moveIt () у нас есть что-то вроде этого:

1
2
3
4
if (markers_vector.length > 0)
{
    //if there are direction markers, this code runs
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
if (markers_vector.length > 0)
{
    for(var j:uint=0;j < markers_vector.length;j++)
    {
        if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
        {
            //setting the direction
            snake_vector[i].direction = markers_vector[j].type;
            if(i == snake_vector.length-1)
            {
                //if its the last snake_part
                markers_vector.splice(j, 1);
            }
        }
    }
}

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

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

Как мы исправим это? Ну, это легко. У нас есть переменная flag , и мы будем использовать ее для этого. Мы сможем изменить направление змеи только в том случае, если для этого параметра установлено значение true (по умолчанию установлено значение false, проверьте для этого функцию init() ).

Поэтому нам нужно немного изменить функцию directionChanged() . Должны быть изменены && flag блоков if: добавьте предложение && flag в конце каждого «if».

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
private function directionChanged(e:KeyboardEvent):void
{
    var m:Object = new Object();
 
    if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
    {
        snake_vector[0].direction = «L»;
        m = {x:snake_vector[0].x, y:snake_vector[0].y, type:»L»};
        last_button_down = Keyboard.LEFT;
        flag = false;
    }
    else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
    {
        snake_vector[0].direction = «R»;
        m = {x:snake_vector[0].x, y:snake_vector[0].y, type:»R»};
        last_button_down = Keyboard.RIGHT;
        flag = false;
    }
    else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
    {
        snake_vector[0].direction = «U»;
        m = {x:snake_vector[0].x, y:snake_vector[0].y, type:»U»};
        last_button_down = Keyboard.UP;
        flag = false;
    }
    else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
    {
        snake_vector[0].direction = «D»;
        m = {x:snake_vector[0].x, y:snake_vector[0].y, type:»D»};
        last_button_down = Keyboard.DOWN;
        flag = false;
    }
    markers_vector.push(m);
}

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

Когда нам нужно установить его в true тогда?

После перемещения / тика мы можем позволить пользователям менять направление, мы просто не хотим менять его дважды за один тик. Поэтому поместите это в самый конец функции moveIt() :

1
flag = true;

Теперь протестируйте его, и ошибки больше нет.


Теперь единственное, что нам нужно сделать, это «проверка яблок»

Помните это в самом начале функции moveIt() ?

1
2
3
4
if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
{
    //This code runs if the snake’s head’s position and the apple’s position are the same
}

Вот что нам нужно сделать там:

  • Вызовите функцию placeApple (). (Мы не устанавливаем параметр в false; мы оставляем его как есть. По умолчанию установлено значение true.)
  • Показать текущий счет
  • Прикрепите новый элемент к последней части змеи.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
{
    //calling the placeApple() function
    placeApple();
    //show the current Score
    score += apple.catchValue;
    score_tf.text = «Score:» + String(score);
    //Attach a new snake Element
    snake_vector.push(new Element(0x00AAFF,1,10,10));
    snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction;
    //attachElement(who,lastXPos,lastYPos,lastDirection)
    attachElement(snake_vector[snake_vector.length-1],
                          (snake_vector[snake_vector.length-2].x),
                          snake_vector[snake_vector.length-2].y,
                          snake_vector[snake_vector.length-2].direction);
}

Теперь все должно работать нормально. Попробуйте это:

Вот снова весь Главный класс:

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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
            package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.ui.Keyboard;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.events.Event;
     
    import com.Element;
         
    public class Main extends Sprite
    {
        //DO NOT GIVE THEM A VALUE HERE!
        private var snake_vector:Vector.<Element>;
        private var markers_vector:Vector.<Object>;
        private var timer:Timer;
        private var dead:Boolean;
        private var min_elements:int;
        private var apple:Element;
        private var space_value:Number;
        private var last_button_down:uint;
        private var flag:Boolean;
        private var score:Number;
        private var score_tf:TextField;
         
        public function Main()
        {
            if(stage)
                addEventListener(Event.ADDED_TO_STAGE, init);
            else
                init();
        }
         
        private function init(e:Event = null):void
        {
            snake_vector = new Vector.<Element>;
            markers_vector = new Vector.<Object>;
            space_value = 2;
            timer = new Timer(50);
            dead = false;
            min_elements = 1;
            apple = new Element(0xFF0000, 1,10, 10);
            apple.catchValue = 0;
            last_button_down = Keyboard.RIGHT; //The starting direction of the snake (only change it if you change the 'for cycle' too.)
            score = 0;
            score_tf = new TextField();
            this.addChild(score_tf);
 
            //Create the first <min_elements> Snake parts
            for(var i:int=0;i<min_elements;++i)
            {
                snake_vector[i] = new Element(0x00AAFF,1,10,10);
                snake_vector[i].direction = "R"; //The starting direction of the snake
                if (i == 0)
                {
                    //you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
                    attachElement(snake_vector[i],0,0,snake_vector[i].direction)
                    snake_vector[0].alpha = 0.7;
                }
                else
                {
                    attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
                }
            }
             
            placeApple(false);
            timer.addEventListener(TimerEvent.TIMER,moveIt);
            stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
            timer.start();
        }
         
        private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
        {
            if (dirOfLast == "R")
            {
                who.x = lastXPos - snake_vector[0].width - space_value;
                who.y = lastYPos;
            }
            else if(dirOfLast == "L")
            {
                who.x = lastXPos + snake_vector[0].width + space_value;
                who.y = lastYPos;
            }
            else if(dirOfLast == "U")
            {
                who.x = lastXPos;
                who.y = lastYPos + snake_vector[0].height + space_value;
            }
            else if(dirOfLast == "D")
            {
                who.x = lastXPos;
                who.y = lastYPos - snake_vector[0].height - space_value;
            }
            this.addChild(who);
        }
         
        private function placeApple(caught:Boolean = true):void
        {
            if (caught)
                apple.catchValue += 10;
             
            var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
            var randomX:Number = Math.floor(Math.random()*boundsX);
             
            var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
            var randomY:Number = Math.floor(Math.random()*boundsY);
 
            apple.x = randomX * (apple.width + space_value);
            apple.y = randomY * (apple.height + space_value);
             
            for(var i:uint=0;i<snake_vector.length-1;i++)
            {
                if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
                    placeApple(false);
            }
            if (!apple.stage)
                this.addChild(apple);
        }
         
        private function moveIt(e:TimerEvent):void
        {
            if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
            {
                placeApple();
                //show the current Score
                score += apple.catchValue;
                score_tf.text = "Score:" + String(score);
                //Attach a new snake Element
                snake_vector.push(new Element(0x00AAFF,1,10,10));
                snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction; //lastOneRichtung
                attachElement(snake_vector[snake_vector.length-1],
                                      (snake_vector[snake_vector.length-2].x),
                                      snake_vector[snake_vector.length-2].y,
                                      snake_vector[snake_vector.length-2].direction);
            }
            if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
            {
                gameOver();
            }
             
            for (var i:int = 0; i < snake_vector.length; i++)
            {
                if (markers_vector.length > 0)
                {
                    for(var j:uint=0;j < markers_vector.length;j++)
                    {
                        if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
                        {
                            snake_vector[i].direction = markers_vector[j].type;
                            if(i == snake_vector.length-1)
                            {
                                markers_vector.splice(j, 1);
                            }
                        }
                    }
                }
                if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
                {
                    gameOver();
                }
             
                //Move the boy
                var DIRECTION:String = snake_vector[i].direction;
                switch (DIRECTION)
                {
                    case "R" :
                        snake_vector[i].x += snake_vector[i].width + space_value;
                        break;
                    case "L" :
                        snake_vector[i].x -= snake_vector[i].width + space_value;
                        break;
                    case "D" :
                        snake_vector[i].y += snake_vector[i].height + space_value;
                        break;
                    case "U" :
                        snake_vector[i].y -= snake_vector[i].width + space_value;
                        break;
                }
                 
            }
                         
            flag = true;
        }
         
        private function gameOver():void
        {
            dead = true;
            timer.stop();
            while (this.numChildren)
                this.removeChildAt(0);
            timer.removeEventListener(TimerEvent.TIMER,moveIt);
            stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
            init();
        }
     
        private function directionChanged(e:KeyboardEvent):void
        {
            var m:Object = new Object(); //MARKER OBJECT
 
            if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
            {
                snake_vector[0].direction = "L";
                m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
                last_button_down = Keyboard.LEFT;
                flag = false;
            }
            else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
            {
                snake_vector[0].direction = "R";
                m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
                last_button_down = Keyboard.RIGHT;
                flag = false;
            }
            else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
            {
                snake_vector[0].direction = "U";
                m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
                last_button_down = Keyboard.UP;
                flag = false;
            }
            else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
            {
                snake_vector[0].direction = "D";
                m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
                last_button_down = Keyboard.DOWN;
                flag = false;
            }
            markers_vector.push(m);
        }
         
    }
 
}

Поздравляем!Вы только что создали хорошую игру. Теперь вы можете развивать его дальше, и создать супер яблоко или что-то в этом роде. Для этого я рекомендую использовать другую функцию с именем placeSuperApple()и новый класс с именем SuperApple. Всякий раз, когда вы ловите супер яблоко, части змей, возможно, удлиняются на три элемента. Это может быть установлено с помощью сеттеров / геттеров в SuperAppleклассе.

Если вы хотите сделать это, и вы где-то застряли, просто оставьте мне комментарий здесь.

Спасибо за уделенное время!