Статьи

Построй Stage3D Shoot-‘Em-Up: Взаимодействие

В этой серии учебных пособий (частично бесплатно, частично Premium) мы создаем высокопроизводительную 2D-съемку с использованием нового аппаратно-ускоренного Stage3D рендеринга Stage3D . В этой части мы расширим нашу демонстрацию рендеринга, добавив анимированный титульный экран и меню, музыкальные и звуковые эффекты и корабль с управляемой клавиатурой, который может стрелять пулями.


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

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


Мы собираемся продолжить делать шутер с боковой прокруткой, вдохновленный ретро аркадными играми, такими как R-Type или Gradius, в AS3, используя API Stage3D Flash 11 и бесплатный инструмент FlashDevelop.

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

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

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


Если у вас его еще нет, обязательно загрузите исходный код из первой части ( скачать здесь ). Откройте файл проекта в FlashDevelop ( информация здесь ) и будьте готовы обновить игру!


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

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
// Stage3D Shoot-em-up Tutorial Part 2
// by Christer Kaitila — www.mcfunkypants.com
// Created for active.tutsplus.com
 
// GameControls.as
// A simple keyboard input class
package
{
 
import flash.display.Stage;
import flash.ui.Keyboard;
import flash.events.*;
 
public class GameControls
{
  // the current state of the keyboard controls
  public var pressing:Object =
  { up:0, down:0, left:0, right:0, fire:0, hasfocus:0 };
 
  // the game’s main stage
  public var stage:Stage;
   
  // class constructor
  public function GameControls(theStage:Stage)
  {
    stage = theStage;
    // get keypresses and detect the game losing focus
    stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressed);
    stage.addEventListener(KeyboardEvent.KEY_UP, keyReleased);
    stage.addEventListener(Event.DEACTIVATE, lostFocus);
    stage.addEventListener(Event.ACTIVATE, gainFocus);
  }

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

Для поддержки международных клавиатур особое внимание было уделено тому, чтобы игра «просто работала», когда игроки пытаются управлять игрой. Хотя большинство ваших игроков будут использовать английскую клавиатуру «QWERTY», значительная часть из них будет использовать альтернативные международные раскладки клавиатуры, и для них большое любопытство в играх — это когда они не могут использовать стандартное движение в стиле «WASD». Поэтому мы гарантируем, что клавиатуры «AZERTY» и «DVORAK» также поддерживаются.

Клавиатурные элементы управления, которые работают на QWERTY, AZERTY и DVORAK.
Клавиатурные элементы управления, которые работают на QWERTY, AZERTY и DVORAK.

Этот класс будет прослушивать события нажатия клавиш вверх и вниз и будет устанавливать состояние класса так, чтобы игра знала, когда определенная клавиша нажата или нет. Чтобы поддержать игроков из разных уголков мира, вместо того, чтобы просто использовать клавиши со стрелками, мы собираемся добавить альтернативные элементы управления, такие как W, A, S, D и аналогичные схемы, которые работают на международных клавиатурах.

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

Определите соответствующие ключевые события следующим образом:

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
private function keyPressed(event:KeyboardEvent):void
{
  keyHandler(event, true);
}
 
private function keyReleased(event:KeyboardEvent):void
{
  keyHandler(event, false);
}
 
// if the game loses focus, don’t keep keys held down
// we could optionally pause the game here
private function lostFocus(event:Event):void
{
  trace(«Game lost keyboard focus.»);
  pressing.up = false;
  pressing.down = false;
  pressing.left = false;
  pressing.right = false;
  pressing.fire = false;
  pressing.hasfocus = false;
}
 
// we could optionally unpause the game here
private function gainFocus(event:Event):void
{
  trace(«Game received keyboard focus.»);
  pressing.hasfocus = true;
}

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


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

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

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
  // used only for debugging
  public function textDescription():String
  {
    return («Controls: » +
      (pressing.up?»up «:»») +
      (pressing.down?»down «:»») +
      (pressing.left?»left «:»») +
      (pressing.right?»right «:»») +
      (pressing.fire?»fire»:»»));
  }
   
  private function keyHandler(event:KeyboardEvent, isDown:Boolean):void
  {
    //trace(‘Key code: ‘ + event.keyCode);
     
    // alternate «fire» buttons
    if (event.ctrlKey ||
      event.altKey ||
      event.shiftKey)
      pressing.fire = isDown;
 
    // key codes that support international keyboards:
    // QWERTY = WASD
    // AZERTY = ZQSD
    // DVORAK = , AOE
       
    switch(event.keyCode)
    {
      case Keyboard.UP:
      case 87: // W
      case 90: // Z
      case 188:// ,
        pressing.up = isDown;
      break;
       
      case Keyboard.DOWN:
      case 83: // S
      case 79: // O
        pressing.down = isDown;
      break;
       
      case Keyboard.LEFT:
      case 65: // A
      case 81: // Q
        pressing.left = isDown;
      break;
       
      case Keyboard.RIGHT:
      case 68: // D
      case 69: // E
        pressing.right = isDown;
      break;
       
      case Keyboard.SPACE:
      case Keyboard.SHIFT:
      case Keyboard.CONTROL:
      case Keyboard.ENTER:
      case 88: // x
      case 67: // c
        pressing.fire = isDown;
      break;
       
    }
  }
 
} // end class
} // end package

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


В inits, как определено в файле Main.as , добавьте две новые переменные класса в начало нашего существующего определения класса, перед всеми остальными переменными из теста спрайта . Нам нужна ссылка на наш новый класс ввода и флаг состояния, который мы будем использовать, чтобы меню не прокручивалось слишком быстро при удерживании клавиши.

1
2
3
4
5
6
public class Main extends Sprite
{
  // the keyboard control system
  private var _controls : GameControls;
  // don’t update the menu too fast
  private var nothingPressedLastFrame:Boolean = false;

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

1
2
3
private function init(e:Event = null):void
{
  _controls = new GameControls(stage);

В основном игровом цикле в файле Main.as мы хотим проверить состояние ввода игрока. В существующей функции onEnterFrame добавьте onEnterFrame строку кода:

1
2
3
4
5
6
7
// this function draws the scene every frame
private function onEnterFrame(e:Event):void
{
  try
  {
    // for debugging the input manager, update the gui
    _gui.titleText = _controls.textDescription();

С этим у нас теперь должен быть рабочий менеджер ввода. Если вы сейчас скомпилируете SWF и запустите его, вы должны увидеть следующее:

Дисплей отладки, показывающий используемые элементы управления.
Дисплей отладки, показывающий используемые элементы управления.

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


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

Наш титульный экран и отображение меню станут еще одним пакетным слоем LiteSprite, который находится поверх того, что мы сделали в первой части этой серии. Он также воспользуется разработанной нами системой дозирования спрайтов и геометрии, чтобы мы могли плавно вращать, масштабировать и перемещать наши спрайты, без каких-либо неровностей и сохраняя при этом наши гладкие и шелковистые 60 кадров в секунду. Запустите Photoshup, GIMP или любой другой графический редактор и создайте логотип и название для своей игры.

Я решил использовать имя «Кайдзен», сформировал первые три буквы в моей фамилии «Кай» и слово «дзен», что для меня подразумевает дзен-подобное состояние ума, которое вы получаете, когда вы «в зоне», уклоняясь от пуль и уничтожая врагов. Он также напоминает японский аркадный стиль и возвращает нас к названиям вроде Raiden.

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

Титульный экран

Теперь нам нужно создать класс меню quick-n-dirty. Создайте новый файл в вашем проекте под названием GameMenu.as и начните с импорта требуемой функциональности Stage3D и определения переменных класса, которые нам нужно обработать, следующим образом:

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
// Stage3D Shoot-em-up Tutorial Part 2
// by Christer Kaitila — www.mcfunkypants.com
 
// GameMenu.as
// A simple title screen / logo screen and menu
// that is displayed during the idle «attract mode»
 
package
{
  import flash.display.Bitmap;
  import flash.display3D.*;
  import flash.geom.Point;
  import flash.geom.Rectangle;
   
  public class GameMenu
  {
    // the sprite sheet image
    public var spriteSheet : LiteSpriteSheet;
    [Embed(source=»../assets/titlescreen.png»)]
    private var SourceImage : Class;
     
    // all the polygons that make up the scene
    public var batch : LiteSpriteBatch;
     
    // which menu item is active (0=none)
    public var menuState:int = 0;
     
    // pixel regions of the buttons
    public var menuWidth:int = 128;
    public var menuItemHeight:int = 32;
    public var menuY1:int = 0;
    public var menuY2:int = 0;
    public var menuY3:int = 0;
     
    // the sprites
    public var logoSprite:LiteSprite;
    // menu items when idle
    public var menuPlaySprite:LiteSprite;
    public var menuControlsSprite:LiteSprite;
    public var menuAboutSprite:LiteSprite;
    // menu items when active
    public var amenuPlaySprite:LiteSprite;
    public var amenuControlsSprite:LiteSprite;
    public var amenuAboutSprite:LiteSprite;
    // info screens
    public var aboutSprite:LiteSprite;
    public var controlsSprite:LiteSprite;
     
    public var showingAbout:Boolean = false;
    public var showingControls:Boolean = false;
    public var showingControlsUntil:Number = 0;
    public var showingAboutUntil:Number = 0;
       
    // where everything goes
    public var logoX:int = 0;
    public var logoY:int = 0;
    public var menuX:int = 0;
    public var menuY:int = 0;

Продолжая с GameMenu.as , реализуйте инициализации. Мы хотим, чтобы меню и логотип были в центре. В функции ниже, где мы создаем пакет геометрии и «нарезаем» спрайт-лист, мы определяем прямоугольные области для каждого из спрайтов, которые мы будем использовать.

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
public function GameMenu(view:Rectangle)
{
  trace(«Init the game menu..»);
  setPosition(view);
}
 
public function setPosition(view:Rectangle):void
{
  logoX = view.width / 2;
  logoY = view.height / 2 — 64;
  menuX = view.width / 2;
  menuY = view.height / 2 + 64;
  menuY1 = menuY — (menuItemHeight / 2);
  menuY2 = menuY — (menuItemHeight / 2) + menuItemHeight;
  menuY3 = menuY — (menuItemHeight / 2) + (menuItemHeight * 2);
}
 
public function createBatch(context3D:Context3D) : LiteSpriteBatch
{
  var sourceBitmap:Bitmap = new SourceImage();
 
  // create a spritesheet using the titlescreen image
  spriteSheet = new LiteSpriteSheet(sourceBitmap.bitmapData, 0, 0);
   
  // Create new render batch
  batch = new LiteSpriteBatch(context3D, spriteSheet);
 
  // set up all required sprites right now
  var logoID:uint = spriteSheet.defineSprite(0, 0, 512, 256);
  logoSprite = batch.createChild(logoID);
  logoSprite.position.x = logoX;
  logoSprite.position.y = logoY;
 
  var menuPlaySpriteID:uint = spriteSheet.defineSprite(0, 256, menuWidth, 48);
  menuPlaySprite = batch.createChild(menuPlaySpriteID);
  menuPlaySprite.position.x = menuX;
  menuPlaySprite.position.y = menuY;
 
  var amenuPlaySpriteID:uint = spriteSheet.defineSprite(0, 256+128, menuWidth, 48);
  amenuPlaySprite = batch.createChild(amenuPlaySpriteID);
  amenuPlaySprite.position.x = menuX;
  amenuPlaySprite.position.y = menuY;
  amenuPlaySprite.alpha = 0;
 
  var menuControlsSpriteID:uint = spriteSheet.defineSprite(0, 304, menuWidth, 32);
  menuControlsSprite = batch.createChild(menuControlsSpriteID);
  menuControlsSprite.position.x = menuX;
  menuControlsSprite.position.y = menuY + menuItemHeight;
 
  var amenuControlsSpriteID:uint = spriteSheet.defineSprite(0, 304+128, menuWidth, 32);
  amenuControlsSprite = batch.createChild(amenuControlsSpriteID);
  amenuControlsSprite.position.x = menuX;
  amenuControlsSprite.position.y = menuY + menuItemHeight;
  amenuControlsSprite.alpha = 0;
 
  var menuAboutSpriteID:uint = spriteSheet.defineSprite(0, 336, menuWidth, 48);
  menuAboutSprite = batch.createChild(menuAboutSpriteID);
  menuAboutSprite.position.x = menuX;
  menuAboutSprite.position.y = menuY + menuItemHeight + menuItemHeight;
 
  var amenuAboutSpriteID:uint = spriteSheet.defineSprite(0, 336+128, menuWidth, 48);
  amenuAboutSprite = batch.createChild(amenuAboutSpriteID);
  amenuAboutSprite.position.x = menuX;
  amenuAboutSprite.position.y = menuY + menuItemHeight + menuItemHeight;
  amenuAboutSprite.alpha = 0;
 
  var aboutSpriteID:uint = spriteSheet.defineSprite(128, 256, 384, 128);
  aboutSprite = batch.createChild(aboutSpriteID);
  aboutSprite.position.x = menuX;
  aboutSprite.position.y = menuY + 64;
  aboutSprite.alpha = 0;
 
  var controlsSpriteID:uint = spriteSheet.defineSprite(128, 384, 384, 128);
  controlsSprite = batch.createChild(controlsSpriteID);
  controlsSprite.position.x = menuX;
  controlsSprite.position.y = menuY + 64;
  controlsSprite.alpha = 0;
 
  return batch;
}

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

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
// our game will call these based on user input
public function nextMenuItem():void
{
  menuState++;
  if (menuState > 3) menuState = 1;
  updateState();
}
public function prevMenuItem():void
{
  menuState—;
  if (menuState < 1) menuState = 3;
  updateState();
}
 
public function mouseHighlight(x:int, y:int):void
{
  //trace(‘mouseHighlight ‘ + x + ‘,’ + y);
   
  // when mouse moves, assume it moved OFF all items
  menuState = 0;
   
  var menuLeft:int = menuX — (menuWidth / 2);
  var menuRight:int = menuX + (menuWidth / 2);
  if ((x >= menuLeft) && (x <= menuRight))
  {
    //trace(‘inside ‘ + menuLeft + ‘,’ + menuRight);
    if ((y >= menuY1) && (y <= (menuY1 + menuItemHeight)))
    {
      menuState = 1;
    }
    if ((y >= menuY2) && (y <= (menuY2 + menuItemHeight)))
    {
      menuState = 2;
    }
    if ((y >= menuY3) && (y <= (menuY3 + menuItemHeight)))
    {
      menuState = 3;
    }
  }
  updateState();
}
 
// adjust the opacity of menu items
public function updateState():void
{
  // ignore if menu is not visible:
  if (showingAbout || showingControls) return;
  // user clicked or pressed fire on a menu item:
  switch (menuState)
  {
    case 0: // nothing selected
      menuAboutSprite.alpha = 1;
      menuControlsSprite.alpha = 1;
      menuPlaySprite.alpha = 1;
      amenuAboutSprite.alpha = 0;
      amenuControlsSprite.alpha = 0;
      amenuPlaySprite.alpha = 0;
      break;
    case 1: // play selected
      menuAboutSprite.alpha = 1;
      menuControlsSprite.alpha = 1;
      menuPlaySprite.alpha = 0;
      amenuAboutSprite.alpha = 0;
      amenuControlsSprite.alpha = 0;
      amenuPlaySprite.alpha = 1;
      break;
    case 2: // controls selected
      menuAboutSprite.alpha = 1;
      menuControlsSprite.alpha = 0;
      menuPlaySprite.alpha = 1;
      amenuAboutSprite.alpha = 0;
      amenuControlsSprite.alpha = 1;
      amenuPlaySprite.alpha = 0;
      break;
    case 3: // about selected
      menuAboutSprite.alpha = 0;
      menuControlsSprite.alpha = 1;
      menuPlaySprite.alpha = 1;
      amenuAboutSprite.alpha = 1;
      amenuControlsSprite.alpha = 0;
      amenuPlaySprite.alpha = 0;
      break;
  }
}
 
// activate the currently selected menu item
// returns true if we should start the game
public function activateCurrentMenuItem(currentTime:Number):Boolean
{
  // ignore if menu is not visible:
  if (showingAbout || showingControls) return false;
  // activate the proper option:
  switch (menuState)
  {
    case 1: // play selected
      return true;
      break;
    case 2: // controls selected
      menuAboutSprite.alpha = 0;
      menuControlsSprite.alpha = 0;
      menuPlaySprite.alpha = 0;
      amenuAboutSprite.alpha = 0;
      amenuControlsSprite.alpha = 0;
      amenuPlaySprite.alpha = 0;
      controlsSprite.alpha = 1;
      showingControls = true;
      showingControlsUntil = currentTime + 5000;
      break;
    case 3: // about selected
      menuAboutSprite.alpha = 0;
      menuControlsSprite.alpha = 0;
      menuPlaySprite.alpha = 0;
      amenuAboutSprite.alpha = 0;
      amenuControlsSprite.alpha = 0;
      amenuPlaySprite.alpha = 0;
      aboutSprite.alpha = 1;
      showingAbout = true;
      showingAboutUntil = currentTime + 5000;
      break;
  }
  return false;
}

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


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

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
    // called every frame: used to update the animation
    public function update(currentTime:Number) : void
    {
      logoSprite.position.x = logoX;
      logoSprite.position.y = logoY;
      var wobble:Number = (Math.cos(currentTime / 500) / Math.PI) * 0.2;
      logoSprite.scaleX = 1 + wobble;
      logoSprite.scaleY = 1 + wobble;
      wobble = (Math.cos(currentTime / 777) / Math.PI) * 0.1;
      logoSprite.rotation = wobble;
       
      // pulse the active menu item
      wobble = (Math.cos(currentTime / 150) / Math.PI) * 0.1;
      amenuAboutSprite.scaleX =
      amenuAboutSprite.scaleY =
      amenuControlsSprite.scaleX =
      amenuControlsSprite.scaleY =
      amenuPlaySprite.scaleX =
      amenuPlaySprite.scaleY =
        1.1 + wobble;
       
      // show the about/controls for a while
      if (showingAbout)
      {
        if (showingAboutUntil > currentTime)
        {
          aboutSprite.alpha = 1;
        }
        else
        {
          aboutSprite.alpha = 0;
          showingAbout = false;
          updateState();
        }
      }
 
      if (showingControls)
      {
        if (showingControlsUntil > currentTime)
        {
          controlsSprite.alpha = 1;
        }
        else
        {
          controlsSprite.alpha = 0;
          showingControls = false;
          updateState();
        }
      }
    }
  } // end class
} // end package

Вот и все для нашего простого анимированного титульного экрана и главного меню.


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

Запустите ваш любимый звуковой редактор (например, SoundForge, CoolEditPro, Reason или даже микрофон и собственный голос) и создайте музыку и звуки, которые вы хотите использовать в своей игре. Если вы не знаете, как создавать звуковые эффекты или не являетесь композитором музыки, не волнуйтесь. Есть миллионы бесплатных и легальных звуковых эффектов и песен на вашем сайте.

Например, вы можете воспользоваться обширной библиотекой звуковых эффектов в FreeSound (просто убедитесь, что они лицензированы для коммерческого использования, и обязательно предоставьте кредит). Вы также можете генерировать звуковые эффекты, используя SFXR доктора Петтера (или его фантастический онлайн-порт BFXR ), который автоматически генерирует бесконечное разнообразие ретро-сигналов и звуковых сигналов. Наконец, для легальной музыки вы можете искать в гигантских каталогах с Jamendo и удивительным ccMixter , которые предназначены для инди как ресурс полностью легальной музыки.

Чтобы сохранить размер нашего SWF-файла как можно меньшим, после того, как вы загрузите или создадите нужные вам звуки и музыку, перекодируйте их с минимально допустимым качеством звука. Когда звучат взрывы и звуки оружия, игроки не заметят, что ваша музыка в режиме MONO и воспроизводится с низкой скоростью. Для примера игры вся музыка и звуки умещаются примерно в 500 Кб, сохраняя MP3-файлы в виде моно 22 кГц файлов.


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

Создайте новый файл в своем проекте под названием GameSound.as и вставьте файлы MP3 следующим образом:

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
// Stage3D Shoot-em-up Tutorial Part 2
// by Christer Kaitila — www.mcfunkypants.com
 
// GameSound.as
// A simple sound and music system for our game
 
package
{
 
import flash.media.Sound;
import flash.media.SoundChannel;
 
  public class GameSound
  {
     
    // to reduce .swf size these are mono 11khz
    [Embed (source = «../assets/sfxmusic.mp3»)]
    private var _musicMp3:Class;
    private var _musicSound:Sound = (new _musicMp3) as Sound;
    private var _musicChannel:SoundChannel;
 
    [Embed (source = «../assets/sfxgun1.mp3»)]
    private var _gun1Mp3:Class;
    private var _gun1Sound:Sound = (new _gun1Mp3) as Sound;
     
    [Embed (source = «../assets/sfxgun2.mp3»)]
    private var _gun2Mp3:Class;
    private var _gun2Sound:Sound = (new _gun2Mp3) as Sound;
     
    [Embed (source = «../assets/sfxgun3.mp3»)]
    private var _gun3Mp3:Class;
    private var _gun3Sound:Sound = (new _gun3Mp3) as Sound;

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

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
    // the different phaser shooting sounds
    public function playGun(num:int):void
    {
      switch (num)
      {
        case 1 : _gun1Sound.play();
        case 2 : _gun2Sound.play();
        case 3 : _gun3Sound.play();
      }
    }
     
    // the looping music channel
    public function playMusic():void
    {
      trace(«Starting the music…»);
      // stop any previously playing music
      stopMusic();
      // start the background music looping
      _musicChannel = _musicSound.play(0,9999);
    }
     
    public function stopMusic():void
    {
      if (_musicChannel) _musicChannel.stop();
    }
 
  } // end class
} // end package

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


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

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

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

Фон игры Звездное поле

Создайте в вашем проекте новый файл с именем GameBackground.as и начните с расширения класса EntityManager следующим образом:

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
// Stage3D Shoot-em-up Tutorial Part 2
// by Christer Kaitila — www.mcfunkypants.com
 
// GameBackground.as
// A very simple batch of background stars that scroll
 
package
{
  import flash.display.Bitmap;
  import flash.display3D.*;
  import flash.geom.Point;
  import flash.geom.Rectangle;
   
  public class GameBackground extends EntityManager
  {
    // how fast the stars move
    public var bgSpeed:int = -1;
    // the sprite sheet image
    public const bgSpritesPerRow:int = 1;
    public const bgSpritesPerCol:int = 1;
    [Embed(source=»../assets/stars.gif»)]
    public var bgSourceImage : Class;
 
    public function GameBackground(view:Rectangle)
    {
      // run the init functions of the EntityManager class
      super(view);
    }

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

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
override public function createBatch(context3D:Context3D) : LiteSpriteBatch
{
  var bgsourceBitmap:Bitmap = new bgSourceImage();
 
  // create a spritesheet with a single giant sprite
  spriteSheet = new LiteSpriteSheet(bgsourceBitmap.bitmapData, bgSpritesPerRow, bgSpritesPerCol);
   
  // Create new render batch
  batch = new LiteSpriteBatch(context3D, spriteSheet);
   
  return batch;
}
 
override public function setPosition(view:Rectangle):void
{
  // allow moving fully offscreen before looping around
  maxX = 256+512+512;
  minX = -256;
  maxY = view.height;
  minY = view.y;
}
 
// for this test, create random entities that move
// from right to left with random speeds and scales
public function initBackground():void
{
  trace(«Init background…»);
  // we need three 512×512 sprites
  var anEntity1:Entity = respawn(0)
  anEntity1 = respawn(0);
  anEntity1.sprite.position.x = 256;
  anEntity1.sprite.position.y = maxY / 2;
  anEntity1.speedX = bgSpeed;
  var anEntity2:Entity = respawn(0)
  anEntity2.sprite.position.x = 256+512;
  anEntity2.sprite.position.y = maxY / 2;
  anEntity2.speedX = bgSpeed;
  var anEntity3:Entity = respawn(0)
  anEntity3.sprite.position.x = 256+512+512;
  anEntity3.sprite.position.y = maxY / 2;
  anEntity3.speedX = bgSpeed;
}

Последний шаг в получении нашего простого, но привлекательного рендеринга фонового слоя — это кодирование функции update , которая будет прокручивать спрайты и, необязательно, «зацикливать» их на противоположном краю для повторного использования. Таким образом, фон может прокручиваться бесконечно. Продолжая с GameBackground.as реализуем наше поведение прокрутки следующим образом:

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
    // called every frame: used to update the scrolling background
    override public function update(currentTime:Number) : void
    {
      var anEntity:Entity;
       
      // handle all other entities
      for(var i:int=0; i<entityPool.length;i++)
      {
        anEntity = entityPool[i];
        if (anEntity.active)
        {
          anEntity.sprite.position.x += anEntity.speedX;
 
          if (anEntity.sprite.position.x >= maxX)
          {
            anEntity.sprite.position.x = minX;
          }
          else if (anEntity.sprite.position.x <= minX)
          {
            anEntity.sprite.position.x = maxX;
          }
        }
      }
    }
  } // end class
} // end package

Вот и все для нашего класса с прокруткой пространства. Теперь все, что нам нужно сделать, это добавить его в нашу основную игру и протестировать.


Есть одно маленькое обновление, которое мы должны сделать в нашем существующем классе EntityManager с прошлой недели Нам нужна уникальная сущность для корабля игрока, которая не следует правилам всех других спрайтов, которые летят в нашей демоверсии. Мы также хотим, чтобы несколько пуль стреляли. Во-первых, убедитесь, что изображение на вашем листе спрайтов содержит такие спрайты, поскольку на прошлой неделе в нем не было пуль.

Новый спрайт лист с пулями и взрывами
Новый спрайт лист с пулями и взрывами

Сначала отредактируйте Entity.as и добавьте одну общедоступную переменную, которая будет использоваться для хранения ссылки на функцию AI (искусственный интеллект). В будущих версиях нашей игры мы могли бы добавить специальные функции AI для различных типов врагов, самонаводящихся ракет и т. Д. Добавьте эту строку кода вместе с другими переменными класса сущностей, такими как скорость.

1
2
// if this is set, custom behaviors are run
public var aiFunction : Function;

Теперь отредактируйте EntityManager.as и добавьте следующую переменную класса в EntityManager.as где мы создадим все остальные переменные.

1
2
// the player entity — a special case
public var thePlayer:Entity;

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

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
// this entity is the PLAYER
public function addPlayer(playerController:Function):Entity
{
  thePlayer = respawn(10);
  thePlayer.sprite.position.x = 32;
  thePlayer.sprite.position.y = maxY / 2;
  thePlayer.sprite.rotation = 180 * (Math.PI/180);
  thePlayer.sprite.scaleX = thePlayer.sprite.scaleY = 2;
  thePlayer.speedX = 0;
  thePlayer.speedY = 0;
  thePlayer.aiFunction = playerController;
  return thePlayer;
}
 
// shoot a bullet (from the player for now)
public function shootBullet():Entity
{
  var anEntity:Entity;
  anEntity = respawn(39);
  anEntity.sprite.position.x = thePlayer.sprite.position.x + 8;
  anEntity.sprite.position.y = thePlayer.sprite.position.y + 4;
  anEntity.sprite.rotation = 180 * (Math.PI/180);
  anEntity.sprite.scaleX = anEntity.sprite.scaleY = 2;
  anEntity.speedX = 10;
  anEntity.speedY = 0;
  return anEntity;
}

Наконец, обновите функцию обновления менеджера сущностей, чтобы пропустить игрока на его стандартном этапе симуляции, проверив, имеет ли эта сущность определенную функцию ai (на данный момент, только игрок будет).

01
02
03
04
05
06
07
08
09
10
11
12
13
anEntity.sprite.position.x += anEntity.speedX;
anEntity.sprite.position.y += anEntity.speedY;
 
// the player follows different rules
if (anEntity.aiFunction != null)
{
  anEntity.aiFunction(anEntity);
}
else // all other entities use the «demo» logic
{
 
  anEntity.sprite.rotation += 0.1;
  // … and so on …

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

Пуля, черт возьми!

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

1
2
3
4
5
if (anEntity.sprite.position.x > maxX)
{
    anEntity.speedX *= -1;
    anEntity.sprite.position.x = maxX;
}

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


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

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
// Stage3D Shoot-em-up Tutorial Part2
// by Christer Kaitila - www.mcfunkypants.com
// Created for active.tutsplus.com
 
package
{
  [SWF(width = "600", height = "400", frameRate = "60", backgroundColor = "#000000")]
 
  import flash.display3D.*;
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageQuality;
  import flash.display.StageScaleMode;
  import flash.events.Event;
  import flash.events.ErrorEvent;
  import flash.events.MouseEvent;
  import flash.geom.Rectangle;
  import flash.utils.getTimer;
     
  public class Main extends Sprite
  {
    // the keyboard control system
    private var _controls : GameControls;
    // don't update the menu too fast
    private var nothingPressedLastFrame:Boolean = false;
    // timestamp of the current frame
    public var currentTime: int;
    // player one's entity
    public var thePlayer:Entity;
    // main menu = 0 or current level number
    private var _state : int = 0;
    // the title screen batch
    private var _mainmenu : GameMenu;
    // the sound system
    private var _sfx : GameSound;
    // the background stars
    private var _bg : GameBackground;
     
    private var _entities : EntityManager;
    private var _spriteStage : LiteSpriteStage;
    private var _gui : GameGUI;
    private var _width : Number = 600;
    private var _height : Number = 400;
    public var context3D : Context3D;

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

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
96
97
98
// constructor function for our game
public function Main():void
{
  if (stage) init();
  else addEventListener(Event.ADDED_TO_STAGE, init);
}
 
// called once flash is ready
private function init(e:Event = null):void
{
  _controls = new GameControls(stage);
  removeEventListener(Event.ADDED_TO_STAGE, init);
  stage.quality = StageQuality.LOW;
  stage.align = StageAlign.TOP_LEFT;
  stage.scaleMode = StageScaleMode.NO_SCALE;
  stage.addEventListener(Event.RESIZE, onResizeEvent);
  trace("Init Stage3D...");
  _gui = new GameGUI("Stage3D Shoot-em-up Tutorial Part 2");
  addChild(_gui);
  stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
  stage.stage3Ds[0].addEventListener(ErrorEvent.ERROR, errorHandler);
  stage.stage3Ds[0].requestContext3D(Context3DRenderMode.AUTO);
  trace("Stage3D requested...");   
  _sfx = new GameSound();
}
     
// this is called when the 3d card has been set up
// and is ready for rendering using stage3d
private function onContext3DCreate(e:Event):void
{
  trace("Stage3D context created! Init sprite engine...");
  context3D = stage.stage3Ds[0].context3D;
  initSpriteEngine();
}
 
// this can be called when using an old version of flash
// or if the html does not include wmode=direct
private function errorHandler(e:ErrorEvent):void
{
  trace("Error while setting up Stage3D: "+e.errorID+" - " +e.text);
}
 
protected function onResizeEvent(event:Event) : void
{
  trace("resize event...");
   
  // Set correct dimensions if we resize
  _width = stage.stageWidth;
  _height = stage.stageHeight;
   
  // Resize Stage3D to continue to fit screen
  var view:Rectangle = new Rectangle(0, 0, _width, _height);
  if ( _spriteStage != null ) {
    _spriteStage.position = view;
  }
  if(_entities != null) {
    _entities.setPosition(view);
  }
  if(_mainmenu != null) {
    _mainmenu.setPosition(view);
  }
}
 
private function initSpriteEngine():void
{
  // init a gpu sprite system
  var stageRect:Rectangle = new Rectangle(0, 0, _width, _height);
  _spriteStage = new LiteSpriteStage(stage.stage3Ds[0], context3D, stageRect);
  _spriteStage.configureBackBuffer(_width,_height);
   
  // create the background stars
  _bg = new GameBackground(stageRect);
  _bg.createBatch(context3D);
  _spriteStage.addBatch(_bg.batch);
  _bg.initBackground();
   
  // create a single rendering batch
  // which will draw all sprites in one pass
  var view:Rectangle = new Rectangle(0,0,_width,_height)
  _entities = new EntityManager(stageRect);
  _entities.createBatch(context3D);
  _spriteStage.addBatch(_entities.batch);
   
  // create the logo/titlescreen main menu
  _mainmenu = new GameMenu(stageRect);
  _mainmenu.createBatch(context3D);
  _spriteStage.addBatch(_mainmenu.batch);
   
  // tell the gui where to grab statistics from
  _gui.statsTarget = _entities;
   
  // start the render loop
  stage.addEventListener(Event.ENTER_FRAME,onEnterFrame);
 
  // only used for the menu
  stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);  
  stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);
}

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

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
public function playerLogic(me:Entity):void
{
  me.speedY = me.speedX = 0;
  if (_controls.pressing.up)
    me.speedY = -4;
  if (_controls.pressing.down)
    me.speedY = 4;
  if (_controls.pressing.left)
    me.speedX = -4;
  if (_controls.pressing.right)
    me.speedX = 4;
     
  // keep on screen
  if (me.sprite.position.x < 0)
    me.sprite.position.x = 0;
  if (me.sprite.position.x > _width)
    me.sprite.position.x = _width;
  if (me.sprite.position.y < 0)
    me.sprite.position.y = 0;
  if (me.sprite.position.y > _height)
    me.sprite.position.y = _height;
}
 
private function mouseDown(e:MouseEvent):void
{
  trace('mouseDown at '+e.stageX+','+e.stageY);
  if (_state == 0) // are we at the main menu?
  {
    if (_mainmenu && _mainmenu.activateCurrentMenuItem(getTimer()))
    { // if the above returns true we should start the game
      startGame();
    }
  }
}
 
private function mouseMove(e:MouseEvent):void  
{
  if (_state == 0) // are we at the main menu?
  {
    // select menu items via mouse
    if (_mainmenu) _mainmenu.mouseHighlight(e.stageX, e.stageY);
  }
}
 
// handle any player input
private function processInput():void
{
  if (_state == 0) // are we at the main menu?
  {
    // select menu items via keyboard
    if (_controls.pressing.down || _controls.pressing.right)
    {
      if (nothingPressedLastFrame)
      {
        _sfx.playGun(1);
        _mainmenu.nextMenuItem();
        nothingPressedLastFrame = false;
      }
    }
    else if (_controls.pressing.up || _controls.pressing.left)
    {
      if (nothingPressedLastFrame)
      {
        _sfx.playGun(1);
        _mainmenu.prevMenuItem();
        nothingPressedLastFrame = false;
      }
    }
    else if (_controls.pressing.fire)
    {
      if (_mainmenu.activateCurrentMenuItem(getTimer()))
      { // if the above returns true we should start the game
        startGame();
      }
    }
    else
    {
      // this ensures the menu doesn't change too fast
      nothingPressedLastFrame = true;
    }
  }
  else
  {
    // we are NOT at the main menu: we are actually playing the game
    // in future versions we will add projectile
    // spawning functinality here to fire bullets
    if (_controls.pressing.fire)
    {
      _sfx.playGun(1);
      _entities.shootBullet();
    }
  }
}

Приведенные выше обработчики мыши и клавиатуры вызывают activateCurrentMenuItemфункцию меню, которая возвращается, trueесли пришло время начать игру. Когда это так, мы удаляем меню и логотип со сцены спрайта, запускаем музыку игры и добавляем спрайт игрока, готовый для управления.

1
2
3
4
5
6
7
8
9
private function startGame():void
{
  trace("Starting game!");
  _state = 1;
  _spriteStage.removeBatch(_mainmenu.batch);
  _sfx.playMusic();
  // add the player entity to the game!
  thePlayer = _entities.addPlayer(playerLogic);    
}

Последний шаг в обновлении нашей игры Stage3D состоит в том, чтобы вызывать соответствующие функции обновления каждый кадр для всех наших новых классов, которые мы создали выше. Продолжая Main.as, обновите onEnterFrameфункцию следующим образом:

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
    // this function draws the scene every frame
    private function onEnterFrame(e:Event):void
    {
      try
      {
        // grab timestamp of current frame
        currentTime = getTimer();
 
        // erase the previous frame
        context3D.clear(0, 0, 0, 1);
         
        // for debugging the input manager, update the gui
        _gui.titleText = _controls.textDescription();
         
        // process any player input
        processInput();
 
        // scroll the background
        _bg.update(currentTime);
         
        // update the main menu titlescreen
        if (_state == 0)
          _mainmenu.update(currentTime);
         
        // keep adding more sprites - FOREVER!
        // this is a test of the entity manager's
        // object reuse "pool"
        _entities.addEntity();
         
        // move/animate all entities
        _entities.update(currentTime);
         
        // draw all entities
        _spriteStage.render();
 
        // update the screen
        context3D.present();
      }
      catch (e:Error)
      {
        // this can happen if the computer goes to sleep and
        // then re-awakens, requiring reinitialization of stage3D
        // (the onContext3DCreate will fire again)
      }
    }
  } // end class
} // end package

Наша супероптимизированная игра Flash 11 Stage3D Shoot-em-up действительно начинает обретать форму! Скомпилируйте ваш проект, исправьте все опечатки и запустите игру. Если у вас возникли проблемы с набранным кодом или вы просто хотите получить удовольствие от всего в одном месте, помните, что вы можете скачать полный исходный код здесь . Вы должны увидеть что-то похожее на это:

Скриншот обновлений этой недели в действии

Вот и все для учебника номер два в этой серии. Настройтесь на следующую неделю, чтобы посмотреть, как игра постепенно превращается в великолепно выглядящую, шелковисто-гладкую съемку со скоростью 60 кадров в секунду.

Этот проект нуждается в небольшом разрушении и хаосе, чтобы он выглядел как настоящая игра! В третьей части мы получим массу удовольствия от реализации всех приятных глаз: пуль, системы частиц и логики обнаружения столкновений, которые сообщат нашей игре, когда что-то должно взорваться.

Я хотел бы услышать от вас относительно этого урока. Я искренне приветствую всех читателей, чтобы они могли связаться со мной через твиттер: @McFunkypants , мой блог mcfunkypants.com или в любое время в Google + . В частности, я хотел бы видеть игры, которые вы делаете с использованием этого кода, и я всегда ищу новые темы для написания будущих уроков.

Удачи и получайте удовольствие!