Исходя из моего предыдущего урока по обнаружению комбинаций клавиш , мы увидим, как создать полноценную игру, которая проверяет ваши навыки набора текста.
Окончательный результат предварительного просмотра
Давайте посмотрим на конечный результат, к которому мы будем стремиться:
Используйте буквы на экране для ввода английских слов! Неверные слова будут терять очки, так что следите.
Шаг 1: Введение
В этом уроке мы будем работать над очень классной игрой на машинке с использованием этого очень полезного класса Combo Detection . Перед продолжением здесь очень рекомендуется прочитать это руководство, чтобы понять, что будет делать этот класс во время нашей игры.
В нашей игре у нас будет много блоков с буквами на экране, и игрок должен будет ввести слово, сформированное из буквенных блоков. Если это слово верно, блоки удаляются, и игрок получает очки и больше времени для игры. Игра закончится, когда время достигнет нуля.
Если вы планируете полностью следовать этому руководству, вам следует взять исходные файлы .
Шаг 2. Добавление изображений в Flash Professional
Как это было сделано в ранее упомянутом руководстве , мы добавим все изображения для нашей игры в файл Flash Professional .fla, а затем сгенерируем файл .swc, который будет добавлен в наш проект FlashDevelop для использования.
Для этого урока нам понадобится фоновое изображение (спасибо Петру Ковару за великолепный деревянный фон!), Универсальное изображение блока с выбранным кадром и поле с набранными на данный момент клавишами. Вы можете найти все настройки в наших исходных файлах .
Будут добавлены и другие изображения, такие как предварительный загрузчик и игра поверх экрана, но они будут сделаны во время обучения, поскольку это имеет больше смысла.
Шаг 3: Почтовый блок
Прежде чем мы сможем начать работу над нашим кодом, давайте настроим наш проект FlashDevelop. Это будет проект AS3 с Preloader, поэтому выберите эту опцию на FlashDevelop! Предполагая, что вы прочитали руководство по комбо , вы, вероятно, знаете, как добавить файл .swc в библиотеку FlashDevelop. Если вы этого не сделаете, просто щелкните правой кнопкой мыши и выберите «Добавить в библиотеку». Возьмите файл .swc из источника и добавьте его. Вот и все. Время действовать!
Наш блок букв будет простым объектом с TextField и изображением блока, показанным на шаге 2. Простое кодирование. Создайте класс LetterBlock
:
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
|
package
{
import ArtAssets.LetterBlockImage;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
public class LetterBlock extends Sprite
{
private var _image:MovieClip;
private var _letterText:TextField;
public function LetterBlock()
{
_image = new LetterBlockImage();
_image.stop();
_letterText = new TextField();
_letterText.defaultTextFormat = new TextFormat(«Verdana», 40, 0xFFFFFF, true, null, null, null, null, TextFormatAlign.CENTER);
_letterText.width = 60;
_letterText.x = -30;
_letterText.y = -26.3;
_letterText.selectable = false;
_letterText.multiline = false;
addChild(_image);
addChild(_letterText);
}
public function setLetter(letter:String):void
{
_letterText.text = letter;
}
}
}
|
Строки 21-28 создают текстовое поле для хранения текста нашей буквы, а также располагают его на экране и присваивают ему шрифт, цвет и размер шрифта. Функция setLetter
используется только для установки буквы поля.
Шаг 4: Загрузка слов
В каждой игре для набора текста нужны слова. На этом шаге мы загрузим внешний файл с помощью тега [Embed]
и поработаем с данными файла. Очевидно, этот файл содержит слова для использования в нашей игре. Он доступен в исходных файлах. Кроме того, на этом этапе мы начинаем работать с классом ComboHandler
, поэтому добавьте его и в свой проект FlashDevelop!
Давайте посмотрим на некоторый код:
В Main.as:
1
2
3
4
5
6
7
8
|
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
ComboHandler.initialize(stage);
DictionaryWords.loadWords();
}
|
Строка 5 выше инициализирует ComboHandler
, как требуется, а строка 7 вызывает метод loadWords()
из класса DictionaryWords
. Этот класс будет создан нами, и его код находится прямо ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package
{
public class DictionaryWords
{
[Embed(source = «../src/Words.txt», mimeType = «application/octet-stream»)]
private static var _Words:Class;
public static function loadWords():void
{
var words:String = new _Words();
var wordsArray:Array = words.split(«\n»);
var i:int;
var length:int = wordsArray.length;
for (i = 0; i < length; i++)
{
ComboHandler.registerCombo(wordsArray[i], turnIntoLetters(wordsArray[i]));
}
}
private static function turnIntoLetters(word:String):Array
{
var letters:Array = word.split(«»);
if (letters[0] == «»)
{
letters.shift();
}
if (letters[letters.length — 1] == «»)
{
letters.pop();
}
var i:int;
for (i = 0; i < letters.length; i++)
{
letters[i] = String(letters[i]).charCodeAt(0);
}
return letters;
}
}
}
|
Строка 5 — это строка, которая загружает внешний файл и помещает его в игру во время компиляции. Это все возможно благодаря тегу [Embed]
. Если вы хотите получить больше информации об этом, я рекомендую эту замечательную статью о Adobe Livedocs .
Вот раздел Words.txt , чтобы вы могли увидеть, с чем мы работаем:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
ABBREVIATOR
ABC
ABCOULOMB
ABCS
ABDIAS
ABDICABLE
ABDICATE
ABDICATION
ABDICATOR
ABDOMEN
ABDOMINAL
ABDOMINOCENTESIS
ABDOMINOPLASTY
|
Строка 12 (из DictionaryWords.as ) является очень важной строкой. По сути, он превращает все слова из Words.txt, которые хранились в строке, в элементы массива. Поскольку каждое слово отделяется символом новой строки, все, что нам нужно было сделать, это вызвать метод split()
класса String
.
Функция turnIntoLetters
просто превращает слово в Array
с кодами клавиш каждой буквы. Таким образом, наш класс ComboHandler
может работать с ним.
Шаг 5: Добавление буквенных блоков на экран
Теперь, когда у нас есть готовые слова в игре, пришло время начать работу над выводом букв на экран. Это очень просто. Прежде всего, нам нужен игровой экран. Класс GameScreen
будет содержать всю логику нашей игры.
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
|
package
{
import ArtAssets.BackgroundImage;
import flash.display.Sprite;
import adobe.utils.CustomActions;
public class GameScreen extends Sprite
{
private var _background:BackgroundImage;
private var _blocksOnScreen:Vector.<LetterBlock>;
public function GameScreen()
{
_background = new BackgroundImage();
_background.x = 275;
_background.y = 200;
addChild(_background);
_blocksOnScreen = new Vector.<LetterBlock>();
populateBlocks();
}
private function populateBlocks():void
{
var i:int;
var tempBlock:LetterBlock;
for (i = 0; i < 8; i++)
{
tempBlock = new LetterBlock();
tempBlock.x = 130 + ((i % 4) * 95);
tempBlock.y = 80 + int(i / 4) * 80;
tempBlock.setLetter(randomLetter());
addChild(tempBlock);
_blocksOnScreen.push(tempBlock);
tempBlock = null;
}
}
private function randomLetter():String
{
return String.fromCharCode((int(Math.random() * 26) + 65));
}
}
}
|
Вектор _blocksOnScreen
является основным элементом в этом коде: он будет содержать все блоки на экране, что позволит нам работать с ними в любое время. Обратите внимание, что в строке 14 мы добавляем BackgroundImage
на экран, который является графикой из файла .swc.
Внутри функции LetterBlock
populateBlocks()
все, что мы делаем, это добавляем новый LetterBlock
в определенной позиции, присваиваем ему случайную букву (которая генерируется randomLetter()
) и добавляем ее на экран.
Теперь нам нужно добавить игровой экран у ребенка Main
. Внутри Main.as:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
private var _gameScreen:GameScreen;
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
ComboHandler.initialize(stage);
DictionaryWords.loadWords();
_gameScreen = new GameScreen();
addChild(_gameScreen);
}
|
Скомпилируйте проект, и вы сможете увидеть блоки на экране!
Шаг 6: Выбор блока после нажатия клавиши
В нашей игре мы хотим, чтобы блок переходил в свое «выбранное» изображение, когда соответствующая клавиша была нажата. Это очень легко сделать. Перейдите в файл LetterBlock.as и добавьте этот код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
private var _selected:Boolean;
public function select():void
{
_selected = !_selected;
_image.gotoAndStop(_selected == true ? «Selected» : «Unselected»);
}
public function get letter():String
{
return _letterText.text;
}
public function get selected():Boolean
{
return _selected;
}
|
Переменная _selected
будет использоваться в логике игры, чтобы проверить, выбран блок или нет. То же самое происходит с letter
.
Осталось только переключить блок между «выбранным» и «невыбранным». Внутри GameScreen.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
|
import flash.events.Event;
import flash.events.KeyboardEvent;
// ** snip **
public function GameScreen()
{
_background = new BackgroundImage();
_background.x = 275;
_background.y = 200;
addChild(_background);
_blocksOnScreen = new Vector.<LetterBlock>();
populateBlocks();
addEventListener(Event.ADDED_TO_STAGE, onStage);
}
private function onStage(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, onStage);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
private function onKeyDown(e:KeyboardEvent):void
{
var i:int;
for (i = 0; i < _blocksOnScreen.length; i++)
{
if (_blocksOnScreen[i].letter == String.fromCharCode(e.keyCode) && !_blocksOnScreen[i].selected)
{
_blocksOnScreen[i].select();
break;
}
}
}
|
Учитывая, что мы должны добавить наших слушателей KeyboardEvent
к сцене, в конструкторе GameScreen
мы добавим слушатель для Event.ADDED_TO_STAGE
, который приведет нас к функции onStage()
. Эта функция добавляет слушателя на сцену. Функция onKeyDown()
отвечает за просмотр всех наших блоков на экране и проверку наличия какого-либо блока с нажатой буквой. Если это так, то мы должны выбрать его, что делается в строке 36.
После компиляции проекта вот что мы получаем:
(Нажмите клавиши на клавиатуре!)
Шаг 7: Модификации класса ComboHandler
Чтобы наша игра работала так, как мы хотим, нам нужно сделать несколько модификаций класса ComboHandler
из предыдущего урока. Первая модификация — сделать так, чтобы она могла проверять комбо только тогда, когда пользователь прекратил печатать. Это будет обнаружено с MAX_INTERVAL
константы MAX_INTERVAL
и функции update()
. Кроме того, поскольку пользователь должен вводить точные буквы слова, чтобы «завершить» его, мы изменим то, как мы проверяем, соответствует ли комбинация клавишам внутри массива pressedKeys
. Последняя модификация — отправка события, даже если введенное слово неверно. Это позволит нашей игре обнаружить ошибку игрока и наказать его за это.
Весь код ниже делает то, что было объяснено:
Внутри ComboHandler.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
|
private static const MAX_INTERVAL:int = 500;
private static var checkComboAfterClearing:Boolean;
public static function initialize(stageReference:Stage, checkComboAfterClearing:Boolean = false):void
{
combos = new Dictionary();
interval = 0;
dispatcher = new EventDispatcher();
ComboHandler.checkComboAfterClearing = checkComboAfterClearing;
pressedKeys = [];
stageReference.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
private static function onKeyDown(e:KeyboardEvent):void
{
if (getTimer() — interval > MAX_INTERVAL)
{
pressedKeys = [];
}
interval = getTimer();
pressedKeys.push(e.keyCode);
if (!checkComboAfterClearing)
{
checkForCombo();
}
}
public static function update():void
{
if (getTimer() — interval > MAX_INTERVAL)
{
checkForCombo();
pressedKeys = [];
}
}
private static function checkForCombo():void
{
if (pressedKeys.length == 0)
{
return;
}
var i:int;
var comboFound:String = «»;
for (var comboName:String in combos)
{
if ((combos[comboName] as Array).length == 0)
{
continue;
}
if (pressedKeys.join(» «) == (combos[comboName] as Array).join(» «))
{
comboFound = comboName;
break;
}
}
// Combo Found
//if (comboFound != «»)
//{
//pressedKeys = [];
dispatcher.dispatchEvent(new ComboEvent(ComboEvent.COMBO_FINISHED, {comboName: comboFound} ));
//}
}
|
Мы изменили объявление конструктора нашего ComboHandler, чтобы мы могли проверять комбинации только после того, как пользователь прекратил печатать. Это проверяется в функции update()
с использованием константы MAX_INTERVAL
. Кроме того, функция checkForCombo()
была изменена для использования более подходящей комбинированной проверки.
Теперь мы также должны изменить класс GameScreen
чтобы обновить класс ComboHandler
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
public function GameScreen()
{
_background = new BackgroundImage();
_background.x = 275;
_background.y = 200;
addChild(_background);
_blocksOnScreen = new Vector.<LetterBlock>();
populateBlocks();
addEventListener(Event.ADDED_TO_STAGE, onStage);
addEventListener(Event.ENTER_FRAME, gameLoop);
}
private function gameLoop(e:Event):void
{
ComboHandler.update();
}
|
Обновление выполняется через Event.ENTER_FRAME
событий Event.ENTER_FRAME
, поскольку это простой и хороший подход.
Последняя модификация — изменить способ инициализации класса ComboHandler
внутри класса Main
:
01
02
03
04
05
06
07
08
09
10
11
12
|
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
ComboHandler.initialize(stage, true);
DictionaryWords.loadWords();
_gameScreen = new GameScreen();
addChild(_gameScreen);
}
|
Собирая игру, мы получаем следующее:
Шаг 8: добавление Word Box
Пришло время дать игроку что-то еще, на что можно положиться. Прямо сейчас игрок не может видеть последовательность букв, которые уже были напечатаны, поэтому давайте добавим слово в игре. Это поле будет содержать набранные в данный момент буквы, упорядоченные по порядку, и будет очищено при получении комбо-события. Создайте класс WordBox
и добавьте в него этот код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package
{
import ArtAssets.TypedLettersBoxImage;
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
public class WordBox extends Sprite
{
private var _image:Sprite;
private var _textField:TextField;
public function WordBox()
{
_image = new TypedLettersBoxImage();
_textField = new TextField();
_textField.defaultTextFormat = new TextFormat(«Verdana», 30, 0xFFFFFF, true, null, null, null, null, TextFormatAlign.CENTER);
_textField.width = 500;
_textField.x = -250;
_textField.y = -25;
_textField.selectable = false;
_textField.multiline = false;
_textField.text = «»;
addChild(_image);
addChild(_textField);
}
public function addLetter(letter:String):void
{
_textField.appendText(letter);
}
public function clear():void
{
_textField.text = «»;
}
}
}
|
Этот класс почти такой же, как класс LetterBlock
, поэтому подробное объяснение не требуется. Единственное, что заслуживает внимания — это строка 35, которая содержит вызов метода appendText()
из класса String
. Эта функция добавляет текст в конец текущего текста, что позволяет нам отображать набранные буквы всегда в конце текущего текста из поля слова.
Теперь пришло время добавить слово в игру. Перейдите на GameScreen.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
|
private var _wordBox:WordBox;
public function GameScreen()
{
_background = new BackgroundImage();
_background.x = 275;
_background.y = 200;
addChild(_background);
_blocksOnScreen = new Vector.<LetterBlock>();
populateBlocks();
_wordBox = new WordBox();
_wordBox.x = 275;
_wordBox.y = 350;
addChild(_wordBox);
addEventListener(Event.ADDED_TO_STAGE, onStage);
addEventListener(Event.ENTER_FRAME, gameLoop);
}
private function onKeyDown(e:KeyboardEvent):void
{
var i:int;
for (i = 0; i < _blocksOnScreen.length; i++)
{
if (_blocksOnScreen[i].letter == String.fromCharCode(e.keyCode) && !_blocksOnScreen[i].selected)
{
_blocksOnScreen[i].select();
_wordBox.addLetter(_blocksOnScreen[i].letter);
break;
}
}
}
|
Строки 1, 15-17 и 19 создают текстовое поле и выводят его на экран. Строка 36 вызывает addLetter()
для добавления буквы в поле.
Результат этого шага ниже. Мы добавим код для очистки поля слова на следующем шаге.
Шаг 9: Интеграция нашего ComboHandler с нашим GameScreen
Прямо сейчас, единственные изменения, которые были сделаны в ComboHandler
только когда и как проверять комбо. Это та часть, где это становится забавным: мы интегрируем наш ComboHandler
в игру. Это означает, что наш ComboHandler
будет зависеть от GameScreen
для запуска. Давайте перейдем к коду и посмотрим объяснения после него!
Внутри GameScreen.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
|
public function GameScreen()
{
_background = new BackgroundImage();
_background.x = 275;
_background.y = 200;
addChild(_background);
_blocksOnScreen = new Vector.<LetterBlock>(8);
populateBlocks();
_wordBox = new WordBox();
_wordBox.x = 275;
_wordBox.y = 350;
addChild(_wordBox);
addEventListener(Event.ADDED_TO_STAGE, onStage);
addEventListener(Event.ENTER_FRAME, gameLoop);
ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
ComboHandler.setGameInstance(this);
}
private function onWordFinished(e:ComboEvent):void
{
_wordBox.clear();
}
public function isKeyAvailable(key:String):Boolean
{
var i:int;
for (i = 0; i < _blocksOnScreen.length; i++)
{
if (_blocksOnScreen[i].letter == key && !_blocksOnScreen[i].selected)
{
return true;
}
}
return false;
}
private function populateBlocks():void
{
var i:int;
var tempBlock:LetterBlock;
for (i = 0; i < 8; i++)
{
tempBlock = new LetterBlock();
tempBlock.x = 130 + ((i % 4) * 95);
tempBlock.y = 80 + int(i / 4) * 80;
tempBlock.setLetter(randomLetter());
addChild(tempBlock);
_blocksOnScreen[i] = tempBlock;
tempBlock = null;
}
}
|
Внутри конструктора GameScreen
мы добавили прослушиватель событий в ComboHandler
диспетчера ComboHandler
и вызвали setGameInstance()
этого класса (которая будет добавлена ниже). Это передаст текущий экземпляр игрового экрана классу ComboHandler
и очистит поле слова.
Также создана функция isKeyAvailable
. Это будет вызвано в комбинированном обработчике, чтобы проверить, может ли он добавить клавишу в список нажатых клавиш.
Строка 63 — это просто исправление изменения, _blocksInScreen
в _blocksInScreen
в конструкторе.
Посмотрите на код для добавления внутри ComboHandler.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
|
private static var gameInstance:GameScreen;
public static function setGameInstance(gameInstance:GameScreen):void
{
ComboHandler.gameInstance = gameInstance;
}
private static function onKeyDown(e:KeyboardEvent):void
{
if (getTimer() — interval > MAX_INTERVAL)
{
pressedKeys = [];
}
if (gameInstance.isKeyAvailable(String.fromCharCode(e.keyCode)))
{
interval = getTimer();
pressedKeys.push(e.keyCode);
}
if (!checkComboAfterClearing)
{
checkForCombo();
}
}
|
В выделенной строке мы добавляем вызов isKeyAvailable()
, который находится в экземпляре игрового экрана, хранящемся в классе ComboHandler
. Это конец интеграции между игрой и комбо-обработчиком.
После компиляции вы заметите, что игра теперь очищает поле слова после того, как интервал прошел:
Шаг 10: Действуй, когда слово набрано
На этом шаге мы заставим игру предпринять некоторые действия, когда слово будет обнаружено. Прежде всего, нам нужно знать, когда слово было обнаружено. Давайте узнаем, как:
Внутри GameScreen.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
|
public function GameScreen()
{
_background = new BackgroundImage();
_background.x = 275;
_background.y = 200;
addChild(_background);
_blocksOnScreen = new Vector.<LetterBlock>(8);
populateBlocks();
_wordBox = new WordBox();
_wordBox.x = 275;
_wordBox.y = 350;
addChild(_wordBox);
addEventListener(Event.ADDED_TO_STAGE, onStage);
addEventListener(Event.ENTER_FRAME, gameLoop);
ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
ComboHandler.setGameInstance(this);
}
private function onWordFinished(e:ComboEvent):void
{
if (e.params.comboName != «»)
{
removeSelectedLetters();
populateBlocks();
_wordBox.clear();
}
}
private function removeSelectedLetters():void
{
var i:int;
for (i = 0; i < 8; i++)
{
if (_blocksOnScreen[i].selected)
{
removeChild(_blocksOnScreen[i]);
_blocksOnScreen[i] = null;
}
}
}
private function populateBlocks():void
{
var i:int;
var tempBlock:LetterBlock;
for (i = 0; i < 8; i++)
{
if (_blocksOnScreen[i] == null)
{
tempBlock = new LetterBlock();
tempBlock.x = 130 + ((i % 4) * 95);
tempBlock.y = 80 + int(i / 4) * 80;
tempBlock.setLetter(randomLetter());
addChild(tempBlock);
_blocksOnScreen[i] = tempBlock;
}
tempBlock = null;
}
}
|
В строке 23 находится прослушиватель событий для ComboEvent.COMBO_FINISHED
. Это событие будет срабатывать каждый раз, когда слово сформировано или игрок пропустил. Если вы вернетесь к ComboHandler.as, вы заметите, что, если игрок пропустит, имя комбо в событии сгорает будет нулевым, или «». В связи с этим мы делаем проверку в строках 28-34. Функция removeSelectedLetters()
удалит все выбранные буквы, так как проигрыватель сформировал слово при его вызове. Мы также изменили функцию populateBlocks()
чтобы она помещала новый блок только в те места, где нет блока — это соответствует тому, что мы делаем внутри функции для удаления блоков.
Скомпилируйте игру, и вот результат:
Шаг 11: Сделай что-нибудь, когда игрок введет неверное слово
Вы хоть представляете, что делать, когда игрок пропускает слово? Я думал о том, чтобы забрать время и очки у игрока (это будет сделано на более поздних этапах), а также дать 30% шанс изменить выбранную букву из неправильно введенного слова. Код ниже делает именно последнее. Перейти к GameScreen.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
|
private function onWordFinished(e:ComboEvent):void
{
if (e.params.comboName != «»)
{
removeSelectedLetters(false);
}
else
{
removeSelectedLetters(true);
}
populateBlocks();
_wordBox.clear();
}
private function removeSelectedLetters(wasFromFailure:Boolean):void
{
var i:int;
for (i = 0; i < 8; i++)
{
if (_blocksOnScreen[i].selected)
{
if ((wasFromFailure && Math.random() < 0.3) || !wasFromFailure)
{
removeChild(_blocksOnScreen[i]);
_blocksOnScreen[i] = null;
}
else
{
_blocksOnScreen[i].select();
}
}
}
}
|
Прежде чем смотреть на строки 5 и 9, давайте перейдем к строке 25: в этой строке wasFromFailure
— это переменная, которая будет определять, должна ли игра «вычислять» 30-процентную вероятность (через Math.random()
) или просто заменять блок. И как это значение передается? Посмотрите на строки 5 и 9: если имя слова removeSelectedLetters()
или «», это означает, что игрок пропустил слово, что означает, что мы должны передать true
для removeSelectedLetters()
.
Скомпилируйте проект и попробуйте ввести неверное слово!
Шаг 12: Добавить оценку
Пришло время добавить счет в игру! Сначала мы создадим изображение и разместим его на экране. На следующем этапе мы дадим счет игроку. Для оценки нам нужно создать класс Score
, и вот код для него:
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
|
package
{
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
public class Score extends Sprite
{
private var _text:TextField;
private var _score:int;
public function Score()
{
_text = new TextField();
_text.defaultTextFormat = new TextFormat(«Verdana», 40, 0xFFFFFF, true, null, null, null, null, TextFormatAlign.CENTER);
_text.width = 500;
_text.selectable = false;
_text.multiline = false;
addChild(_text);
_score = 0;
_text.text = «Score: » + _score.toString();
}
public function addToScore(value:int):void
{
_score += value;
_text.text = «Score: » + _score.toString();
}
}
}
|
Я полагаю, что об этом особо нечего сказать — мы уже делали такой текст два раза раньше. Теперь мы должны добавить эту оценку на экране. Внутри GameScreen.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
|
private var _score:Score;
public function GameScreen()
{
_background = new BackgroundImage();
_background.x = 275;
_background.y = 200;
addChild(_background);
_blocksOnScreen = new Vector.<LetterBlock>(8);
populateBlocks();
_wordBox = new WordBox();
_wordBox.x = 275;
_wordBox.y = 350;
addChild(_wordBox);
_score = new Score();
_score.x = 25;
_score.y = 210;
addChild(_score);
addEventListener(Event.ADDED_TO_STAGE, onStage);
addEventListener(Event.ENTER_FRAME, gameLoop);
ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
ComboHandler.setGameInstance(this);
}
|
Вот и ты. Скомпилируйте проект, и теперь вы можете увидеть счет!
Шаг 13: Дай и возьми счет
Теперь, когда счет уже добавлен на экран, все, что нам нужно сделать, это дать счет игроку, когда он заканчивает слово, и убрать счет, когда он пропустил его. Код в GameScreen.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
|
private function removeSelectedLetters(wasFromFailure:Boolean):void
{
var i:int;
var count:int = 0;
for (i = 0; i < 8; i++)
{
if (_blocksOnScreen[i].selected)
{
count++;
if ((wasFromFailure && Math.random() < 0.3) || !wasFromFailure)
{
removeChild(_blocksOnScreen[i]);
_blocksOnScreen[i] = null;
}
else
{
_blocksOnScreen[i].select();
}
}
}
if (wasFromFailure)
{
_score.addToScore( -(count * 10));
}
else
{
_score.addToScore(count * 30);
}
}
|
Как видите, все очень просто: мы даем игроку в 30 раз больше букв, а в 10 раз убираем количество букв опечаток. Скомпилированная игра ниже для тестирования!
Шаг 14: добавь таймер
Добавление таймера будет почти таким же, как оценка. Разница будет только одна: нам нужно будет постоянно обновлять таймер, а у таймера будет другой текст. Посмотрите на код для Timer.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
|
package
{
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
public class Timer extends Sprite
{
private var _text:TextField;
private var _value:int;
public function Timer()
{
_text = new TextField();
_text.defaultTextFormat = new TextFormat(«Verdana», 20, 0xFF0000, true, null, null, null, null, TextFormatAlign.LEFT);
_text.width = 200;
_text.selectable = false;
_text.multiline = false;
addChild(_text);
_value = 30;
_text.text = «Time: » + timeString();
}
private function timeString():String
{
var minutes:int = _value / 60;
var seconds:int = _value % 60;
return minutes.toString() + «:» + seconds.toString();
}
public function addToTime(value:int):void
{
_value += value;
_text.text = «Time: » + timeString();
}
}
}
|
Как вы могли заметить в строках 33, 35 и 37, мы создаем другой текст для таймера. Он будет состоять из минут и секунд. Чтобы это работало, _value
будет временем, оставшимся в игре в секундах. Теперь код для добавления таймера, в GameScreen.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
|
private var _timer:Timer;
public function GameScreen()
{
_background = new BackgroundImage();
_background.x = 275;
_background.y = 200;
addChild(_background);
_blocksOnScreen = new Vector.<LetterBlock>(8);
populateBlocks();
_wordBox = new WordBox();
_wordBox.x = 275;
_wordBox.y = 350;
addChild(_wordBox);
_score = new Score();
_score.x = 25;
_score.y = 210;
addChild(_score);
_timer = new Timer();
addChild(_timer);
addEventListener(Event.ADDED_TO_STAGE, onStage);
ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
ComboHandler.setGameInstance(this);
}
|
Никаких объяснений в коде не требуется, поэтому нажмите кнопку компиляции и посмотрите на свой таймер!
Но ведь это не обратный отсчет? Давайте сделаем это!
Шаг 15: Уменьшение и увеличение оставшегося времени
Как обсуждалось в предыдущем шаге, таймер должен постоянно обновляться. Это потому, что оно постоянно уменьшается, секунда за секундой. Кроме того, мы должны наградить игрока больше секунд, когда слово успешно завершено, и отнять некоторое время, когда слово напечатано неправильно. Внутри GameScreen.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
|
private var _updateTimer:Number;
public function GameScreen()
{
_background = new BackgroundImage();
_background.x = 275;
_background.y = 200;
addChild(_background);
_blocksOnScreen = new Vector.<LetterBlock>(8);
populateBlocks();
_wordBox = new WordBox();
_wordBox.x = 275;
_wordBox.y = 350;
addChild(_wordBox);
_score = new Score();
_score.x = 25;
_score.y = 210;
addChild(_score);
_timer = new Timer();
addChild(_timer);
addEventListener(Event.ADDED_TO_STAGE, onStage);
addEventListener(Event.ENTER_FRAME, gameLoop);
ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
ComboHandler.setGameInstance(this);
_updateTimer = 0;
}
private function gameLoop(e:Event):void
{
ComboHandler.update();
_updateTimer += 1 / 30;
if (_updateTimer >= 1)
{
_updateTimer -= 1;
_timer.addToTime(-1);
}
}
private function removeSelectedLetters(wasFromFailure:Boolean):void
{
var i:int;
var count:int = 0;
for (i = 0; i < 8; i++)
{
if (_blocksOnScreen[i].selected)
{
count++;
if ((wasFromFailure && Math.random() < 0.3) || !wasFromFailure)
{
removeChild(_blocksOnScreen[i]);
_blocksOnScreen[i] = null;
}
else
{
_blocksOnScreen[i].select();
}
}
}
if (wasFromFailure)
{
_score.addToScore( -(count * 10));
_timer.addToTime( -count);
}
else
{
_score.addToScore(count * 30);
_timer.addToTime(count * 2);
}
}
|
Строки 1, 27, 29 и 33 создают таймер, помещают его на экран и добавляют прослушиватель событий для Event.ENTER_FRAME
. Этот слушатель будет добавлять 1/30 секунды к _updateTimer
в каждом кадре (здесь мы предполагаем, что игра работает со скоростью 30 кадров в секунду. Если она работает, например, со скоростью 24 кадра в секунду, она должна добавить 1/24 секунды) , Когда _updateTimer
достигает одной или нескольких секунд, значение 1 уменьшается по сравнению со значением таймера. В строках 84 и 89 мы уменьшаем и увеличиваем время соответственно в зависимости от того, сформировал ли игрок «приемлемое» слово или нет.
Кроме того, когда пользователь вводит слово неправильно, количество секунд, равное количеству букв, уменьшается от таймера игры. Если слово верно, игра присваивает игроку вдвое больше букв, чем секунд.
Это ваш результат:
Шаг 16: улучшить наш процесс создания случайных писем
В текущей игре иногда на экране появляется слишком мало гласных. Это крайне затрудняет создание слов. На этом этапе мы изменим это. Решение очень простое: мы будем использовать массив, основанный на весе.
Этот массив содержит все буквы алфавита, и мы получим букву, получив случайный индекс в пределах массива. Однако «вес» каждой буквы будет разным. «Вес» — это просто количество букв в массиве, поэтому, если буква «R» имеет вес, например, 2, в массиве есть две буквы «R». Если смотреть с вероятностной точки зрения, чем больше у вас буквы, тем больше у вас шансов получить к ней доступ.
Код, безусловно, поможет объяснить это. Этот код должен быть размещен внутри GameScreen.as:
1
2
3
4
5
6
|
private var _letters:Array = [«A», «A», «A», «B», «C», «D», «E», «E», «E», «F», «G», «H», «I», «I», «I», «J», «K», «L», «M», «N», «O», «O», «O», «P», «Q», «R», «R», «S», «T», «U», «U», «U», «V», «W», «X», «Y», «Z»];
private function randomLetter():String
{
return _letters[int(Math.random() * _letters.length)];
}
|
В строке 1 вы можете увидеть массив букв. Каждый гласный имеет вес 3, что означает, что их всегда 3, а буква «R» имеет вес 2. Это чрезвычайно упрощенный массив, но с его идеей можно было бы сделать гораздо больше.
Вот еще один способ выразить относительные веса:
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
|
private var _letters:Array = [
«A», «A», «A»,
«B»,
«C»,
«D»,
«E», «E», «E»,
«F»,
«G»,
«H»,
«I», «I», «I»,
«J»,
«K»,
«L»,
«M»,
«N»,
«O», «O», «O»,
«P»,
«Q»,
«R», «R»,
«S»,
«T»,
«U», «U», «U»,
«V»,
«W»,
«X»,
«Y»,
«Z»
];
private function randomLetter():String
{
return _letters[int(Math.random() * _letters.length)];
}
|
Хотя это не совсем «видно», вы можете проверить скомпилированный проект ниже:
Шаг 17: Preloader Создание графики
Прямо сейчас база нашей игры готова. Теперь мы добавим прелоадер и экран игры поверх следующих шагов. Прежде всего, нам нужно создать графику предзагрузчика. Это будет просто прямоугольник с закругленными углами с надписью «Загрузка». Вы можете получить исходные файлы, чтобы получить его.
Шаг 18: Preloader Напишите код
Если вы знакомы с FlashDevelop, это не составит труда. Мы Preloader
класс Preloader
чтобы поместить туда нашу графику. Переход к коду:
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
|
import ArtAssets.LoadingImage;
import flash.display.Sprite;
// *** snip ***
private var _loadingImage:LoadingImage;
private var _loadingMask:Sprite;
public function Preloader()
{
if (stage) {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
}
addEventListener(Event.ENTER_FRAME, checkFrame);
loaderInfo.addEventListener(ProgressEvent.PROGRESS, progress);
loaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioError);
_loadingImage = new LoadingImage();
_loadingImage.x = 275;
_loadingImage.y = 200;
addChild(_loadingImage);
_loadingMask = new Sprite();
_loadingMask.x = 75;
_loadingMask.y = 175;
_loadingImage.mask = _loadingMask;
}
private function progress(e:ProgressEvent):void
{
_loadingMask.graphics.clear();
_loadingMask.graphics.beginFill(0x000000);
_loadingMask.graphics.drawRect(0, 0, _loadingImage.width * (e.bytesLoaded / e.bytesTotal), 50);
_loadingMask.graphics.endFill();
}
private function loadingFinished():void
{
removeEventListener(Event.ENTER_FRAME, checkFrame);
loaderInfo.removeEventListener(ProgressEvent.PROGRESS, progress);
loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, ioError);
removeChild(_loadingImage);
_loadingImage.mask = null;
_loadingImage = null;
_loadingMask = null;
startup();
}
|
В конструкторе Preloader
мы создали экземпляр загрузочного изображения в файле .swc, а также спрайт, который будет использоваться в качестве маски. Эта маска создаст визуальное представление процесса «загрузки».
Функция progress()
является ключевой функцией здесь: в ней мы обновляем маску, создавая прямоугольник шириной e.bytesLoaded / e.bytesTotal
умноженный на ширину изображения полосы загрузки. Свойства bytesLoaded
и bytesTotal
класса ProgressEvent
показывают нам, сколько байт из игры было загружено и каковы общие байты. Таким образом, погружаясь в них, мы получаем процент загруженной игры.
В функции loadingFinished()
мы должны очистить каждую ссылку на загрузочное изображение и его маску. Таким образом, они могут собирать мусор и больше не будут использовать память, отведенную для игры.
Взгляните на работающий прелоадер!
Шаг 19: Экран Game Over Создайте графику
Теперь нам нужно работать над игрой поверх экрана. Я хотел, чтобы это было очень просто, только чтобы показать, как добавить экран после завершения игры. Графика очень простая: тот же игровой фон с текстом «Game Over» и затухающей анимацией. Вы можете увидеть середину анимации ниже:
Возьмите исходные файлы, чтобы использовать его!
Шаг 20: Экран Game Over Напиши код
Настало время добавить игру поверх экрана в игру. Прежде чем сделать это, нам нужно будет узнать, когда таймер достигнет 0 (что означает, что игра окончена). Для этого необходимо создать функцию получения в Timer.as:
1
2
3
4
|
public function get value():int
{
return _value;
}
|
Теперь мы можем создать класс GameOverScreen
:
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
|
package
{
import ArtAssets.GameOverScreenImage;
import flash.display.Sprite;
import flash.events.Event;
public class GameOverScreen extends Sprite
{
private var _image:GameOverScreenImage;
public function GameOverScreen()
{
_image = new GameOverScreenImage();
_image.x = 275;
_image.y = 200;
addChild(_image);
addEventListener(Event.ENTER_FRAME, update);
}
public function update(e:Event):void
{
if (_image.currentFrame == _image.totalFrames)
{
_image.stop();
}
}
}
}
|
Код внутри update()
был создан, чтобы анимация воспроизводилась только один раз. Таким образом, эффект затухания не будет повторяться.
Переходя на Main.as, добавьте этот код:
01
02
03
04
05
06
07
08
09
10
|
private var _gameOverScreen:GameOverScreen;
public function gameOver():void
{
removeChild(_gameScreen);
_gameOverScreen = new GameOverScreen();
addChild(_gameOverScreen);
}
|
Этот код будет вызван GameScreen
когда он обнаружит, что игра была потеряна. Теперь внутри GameScreen.as:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
private function gameLoop(e:Event):void
{
ComboHandler.update();
_updateTimer += 1 / 30;
if (_updateTimer >= 1)
{
_updateTimer -= 1;
_timer.addToTime(-1);
}
if (_timer.value < 0 && parent)
{
Main(parent).gameOver();
}
}
|
Выделенные строки — единственное изменение этой функции. Они обнаружат, когда таймер достиг меньше 0 и есть ли у него родительский элемент (что означает, что он еще не был удален Main
). В этот момент он вызовет gameOver()
объекта Main
.
Скомпилируйте проект, дайте таймеру достичь 0 и посмотрите, что вы получите:
Вывод
Отличная работа — вы создали простую, но полную игру на машинке! Что дальше? Я предлагаю вам попытаться сделать лучший таймер, добавив переходы печатных букв и добавив больше эффектов.