Пока у нас есть бесконечный поток врагов, которых должен избегать наш аватар; одно касание, и игра окончена. Ну и что? Поскольку нет возможности отследить прогресс игрока, они понятия не имеют, добились ли они лучшего в своем последнем раунде, чем когда-либо прежде. В этом уроке вы узнаете, как вести счет, как отображать его на холсте и как сообщить игроку, когда он побил свои собственные рекорды.
напоминание
В первой и второй частях этого руководства мы рассмотрели несколько концепций: рисование изображений на холсте, обнаружение действий мыши, использование операторов if
и while
, сохранение переменных в массивах и идея области видимости переменных.
Вы можете скачать исходные файлы из этой серии до этого момента, если вы хотите погрузиться прямо в это, хотя я рекомендую прочитать все части по порядку.
Файл JavaScript нашей игры инициализирует набор переменных (включая два массива для хранения x- и y-координат противника) вне всех функций, так что их содержимое доступно для всех функций. Он также содержит функцию setUpGame()
которая запускается, когда игрок впервые нажимает на холст; это загружает изображения, начинает слушать любые движения мыши и устанавливает функцию «тик», которая будет запускаться каждые 25 миллисекунд.
Когда мышь перемещается, мы перемещаем положение аватара, как хранится в двух переменных — одна для координаты x и одна для координаты y — но мы не сразу перерисовываем изображение аватара в этом новом месте; все перерисовки обрабатываются в функции галочки.
Тиковая функция делает четыре вещи:
- Существует вероятность того, что один из двадцати добавит нового врага, вставив новые x- и y-координаты в соответствующие массивы.
- Это увеличивает y-координаты каждого врага, проходя через этот массив.
- Он перерисовывает аватар и изображения врагов в соответствии с их текущими координатами.
- Он проверяет наличие столкновения между аватаром и каждым врагом, давая предупреждение, если оно происходит.
Все чисто?
Разминка
Если прошло много времени с тех пор, как вы прочитали вторую часть серии (или если вы просто хотите проверить, понимаете ли вы, что происходит), попробуйте эти небольшие упражнения. Они совершенно необязательны и отделены от самого учебника, поэтому я рекомендую работать с копией вашего проекта, а не с оригиналом. Вы можете выполнить все эти упражнения, используя только информацию из серии.
Легко
Поменяйте местами аватар и изображения врагов, чтобы игрок мог контролировать улыбающееся лицо, избегая падающих черепов.
(Сколько разных способов вы можете придумать, как это сделать? Я могу подумать о трех, от макушки головы.)
средний
На данный момент существует один шанс из двадцати, что новый враг будет создан в любой данный момент. Я хочу, чтобы вы сделали так, чтобы на первом тике был шанс «один в один», на втором — «один на два», на третьем — «один на три» и так далее.
Чтобы сделать это более сложным, сделайте все наоборот: шанс один на 1000 на первом тике, шанс один на 999 на втором, шанс один на 998 на третьем и так далее. (После тысячного тика сделайте это постоянным шансом один на один.)
Жесткий
Вместо того, чтобы ждать, пока враги появятся один за другим, начните игру с двадцатью врагами на экране.
Чтобы сделать это более сложным, сделайте так, чтобы они распространились по холсту и не позволяли им перекрывать аватар или друг друга.
Держать время
Какой самый простой способ измерить, насколько хорошо игрок делает этот раунд? Самая простая вещь, о которой я могу подумать, это отслеживать, сколько времени прошло с тех пор, как они ударили врага. А поскольку удар по врагу означает, что игра окончена, нам нужно только отслеживать, как долго они играют.
Для этого мы просто создадим переменную, установим ее в 0 в начале раунда и будем увеличивать ее каждый тик. Давайте назовем эту переменную ticksSurvived
. И подумайте: поскольку нам нужно обращаться к нему снова и снова, он должен быть определен вне всех функций, в верхней части файла JS:
1
2
3
4
5
6
7
|
var enemyYPositions = [];
var enemyXPositions = [];
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
var ticksSurvived = 0;
|
Теперь мы дадим handleTick()
еще одну задачу: ticksSurvived
. Поместите это после обнаружения столкновения; в конце концов, если аватар поражает врага, он на самом деле не пережил тиканье:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
function handleTick() {
//…
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
if ( ( (avatarX < enemyXPositions[currentEnemyNumber] && enemyXPositions[currentEnemyNumber] < avatarX + 30) || (enemyXPositions[currentEnemyNumber] < avatarX && avatarX < enemyXPositions[currentEnemyNumber] + 30) ) && ( (avatarY < enemyYPositions[currentEnemyNumber] && enemyYPositions[currentEnemyNumber] < avatarY + 33) || (enemyYPositions[currentEnemyNumber] < avatarY && avatarY < enemyYPositions[currentEnemyNumber] + 30) ) ) {
alert(«You hit an enemy!»);
}
currentEnemyNumber = currentEnemyNumber + 1;
}
ticksSurvived = ticksSurvived + 1;
}
|
Чтобы отобразить это, сейчас мы просто изменим предупреждение, которое появляется, когда аватар поражает врага:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
function handleTick() {
//…
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
if ( ( (avatarX < enemyXPositions[currentEnemyNumber] && enemyXPositions[currentEnemyNumber] < avatarX + 30) || (enemyXPositions[currentEnemyNumber] < avatarX && avatarX < enemyXPositions[currentEnemyNumber] + 30) ) && ( (avatarY < enemyYPositions[currentEnemyNumber] && enemyYPositions[currentEnemyNumber] < avatarY + 33) || (enemyYPositions[currentEnemyNumber] < avatarY && avatarY < enemyYPositions[currentEnemyNumber] + 30) ) ) {
alert(«You hit an enemy! Ticks survived: » + ticksSurvived);
}
currentEnemyNumber = currentEnemyNumber + 1;
}
ticksSurvived = ticksSurvived + 1;
}
|
Это немного странно; мы использовали оператор +
чтобы добавить строку к числу. Мы можем добавить еще одну строку в конец:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
function handleTick() {
//…
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
if ( ( (avatarX < enemyXPositions[currentEnemyNumber] && enemyXPositions[currentEnemyNumber] < avatarX + 30) || (enemyXPositions[currentEnemyNumber] < avatarX && avatarX < enemyXPositions[currentEnemyNumber] + 30) ) && ( (avatarY < enemyYPositions[currentEnemyNumber] && enemyYPositions[currentEnemyNumber] < avatarY + 33) || (enemyYPositions[currentEnemyNumber] < avatarY && avatarY < enemyYPositions[currentEnemyNumber] + 30) ) ) {
alert(«You hit an enemy! You survived » + ticksSurvived + » ticks.»);
}
currentEnemyNumber = currentEnemyNumber + 1;
}
ticksSurvived = ticksSurvived + 1;
}
|
Возможно, это не кажется вам странным, но некоторые языки программирования ненавидят это. JavaScript не волнует Он знает, что "some" + "string"
равно "somestring"
, и предполагает, что вы хотите рассматривать ticksSurvived
как строку в этой ситуации.
(Некоторые вещи попробовать:
12 + 6
"12" + "6"
"12" + 6
12 + "6"
Все ли они делают то, что вы от них ожидаете?)
В любом случае, давайте попробуем этот новый код.
Большой!
Как и раньше, когда вы нажимаете кнопку ОК, окно предупреждения появляется снова, потому что все, что он делает, это приостанавливает тики, а не останавливает их. Но обратите внимание, что число в поле увеличивается на единицу; это доказательство того, что JavaScript не превращает число в строку навсегда ; он только использует ее как строку для целей добавления ее в другую строку.
Пока мы обсуждаем это окно с предупреждением: вас это не раздражает?
Попробуйте еще раз
На данный момент единственный способ начать новый раунд — это обновить страницу. Давайте сделаем проще и менее раздражающим, чтобы сделать еще один шаг, сделав кнопку «ОК» в окне оповещения перезагрузить игру.
Поскольку оповещение фактически приостанавливает игру, все, что мы поставим на линию после оповещения, будет запущено, как только игрок нажмет ОК. Давайте вызовем новую функцию startNewGame()
:
1
2
3
4
5
6
7
8
|
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
if ( ( (avatarX < enemyXPositions[currentEnemyNumber] && enemyXPositions[currentEnemyNumber] < avatarX + 30) || (enemyXPositions[currentEnemyNumber] < avatarX && avatarX < enemyXPositions[currentEnemyNumber] + 30) ) && ( (avatarY < enemyYPositions[currentEnemyNumber] && enemyYPositions[currentEnemyNumber] < avatarY + 33) || (enemyYPositions[currentEnemyNumber] < avatarY && avatarY < enemyYPositions[currentEnemyNumber] + 30) ) ) {
alert(«You hit an enemy! You survived » + ticksSurvived + » ticks.»);
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
|
Итак, что нам нужно сделать startNewGame()
?
Возможно, нам следует просто снова вызвать setUpGame()
— но нет, ничего в этом не нужно делать дважды: нам не нужно загружать изображения, добавлять прослушиватель событий мыши или снова устанавливать галочки.
Подумайте, что нужно, и опробуйте свои идеи. Мое решение ниже; посмотрите, когда будете готовы.
1
2
3
4
5
|
function startNewGame() {
enemyXPositions = [];
enemyYPositions = [];
ticksSurvived = 0;
}
|
Это все. Вы можете сделать больше, если хотите, но это необходимый минимум.
Обратите внимание, что нам не нужно ничего делать с холстом — нам не нужно очищать его, рисовать на нем что-либо или манипулировать изображениями — потому что все это делается в handleTick()
. Нам не нужно проходить все элементы в массивах, один за другим, потому что массивы — это просто список координат, которые мы используем, чтобы штамповать изображения врага на холсте; враги не существуют как реальные объекты.
Так что это здорово — игрок может играть в игру снова и снова, пытаясь получить лучший результат. Кроме … как они узнают, побили ли они свой предыдущий счет? На данный момент им просто нужно помнить, что они набрали наибольшее количество очков, или записать их. Мы можем сделать лучше, чем это.
Запомни лучший результат
Как мы должны хранить лучший результат? В другой переменной, конечно!
1
2
3
4
5
6
7
8
|
var enemyYPositions = [];
var enemyXPositions = [];
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
var ticksSurvived = 0;
var mostTicksSurvived = 0;
|
Естественно, он должен жить вне функций; к настоящему времени я уверен, что вы понимаете почему.
Начнем с того, что мы установили его на 0, конечно — игрок даже не закончил раунд. Когда аватар поражает врага, давайте обновим новый счет по мере необходимости:
01
02
03
04
05
06
07
08
09
10
|
while (currentEnemyNumber < numberOfEnemies) {
if ( ( (avatarX < enemyXPositions[currentEnemyNumber] && enemyXPositions[currentEnemyNumber] < avatarX + 30) || (enemyXPositions[currentEnemyNumber] < avatarX && avatarX < enemyXPositions[currentEnemyNumber] + 30) ) && ( (avatarY < enemyYPositions[currentEnemyNumber] && enemyYPositions[currentEnemyNumber] < avatarY + 33) || (enemyYPositions[currentEnemyNumber] < avatarY && avatarY < enemyYPositions[currentEnemyNumber] + 30) ) ) {
alert(«You hit an enemy! You survived » + ticksSurvived + » ticks.»);
if (ticksSurvived > mostTicksSurvived) {
mostTicksSurvived = ticksSurvived;
}
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
|
Давайте также скажем игроку, что он побил свой старый рекорд:
01
02
03
04
05
06
07
08
09
10
11
|
while (currentEnemyNumber < numberOfEnemies) {
if ( ( (avatarX < enemyXPositions[currentEnemyNumber] && enemyXPositions[currentEnemyNumber] < avatarX + 30) || (enemyXPositions[currentEnemyNumber] < avatarX && avatarX < enemyXPositions[currentEnemyNumber] + 30) ) && ( (avatarY < enemyYPositions[currentEnemyNumber] && enemyYPositions[currentEnemyNumber] < avatarY + 33) || (enemyYPositions[currentEnemyNumber] < avatarY && avatarY < enemyYPositions[currentEnemyNumber] + 30) ) ) {
alert(«You hit an enemy! You survived » + ticksSurvived + » ticks.»);
if (ticksSurvived > mostTicksSurvived) {
alert(«New high score!»);
mostTicksSurvived = ticksSurvived;
}
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
|
Взглянем:
Отлично. Теперь давайте дадим игроку немного больше информации о том, насколько лучше он сделал:
01
02
03
04
05
06
07
08
09
10
11
|
while (currentEnemyNumber < numberOfEnemies) {
if ( ( (avatarX < enemyXPositions[currentEnemyNumber] && enemyXPositions[currentEnemyNumber] < avatarX + 30) || (enemyXPositions[currentEnemyNumber] < avatarX && avatarX < enemyXPositions[currentEnemyNumber] + 30) ) && ( (avatarY < enemyYPositions[currentEnemyNumber] && enemyYPositions[currentEnemyNumber] < avatarY + 33) || (enemyYPositions[currentEnemyNumber] < avatarY && avatarY < enemyYPositions[currentEnemyNumber] + 30) ) ) {
alert(«You hit an enemy! You survived » + ticksSurvived + » ticks.»);
if (ticksSurvived > mostTicksSurvived) {
alert(«You beat your old high score by » + (ticksSurvived — mostTicksSurvived) + » ticks!»);
mostTicksSurvived = ticksSurvived;
}
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
|
Обратите внимание, что ticksSurvived
и mostTicksSurvived
обрабатываются как числа при вычитании одного из другого, но результирующее выражение (ticksSurvived - mostTicksSurvived)
обрабатывается как строка при добавлении в другие строки! Это может привести к путанице, если вы не будете осторожны.
Итак, теперь игрок знает, насколько хорошо он себя чувствует, после того, как они закончат игру, мы должны дать им представление о том, как хорошо они себя чувствуют, пока они еще играют.
Рисование партитуры на холсте
Это действительно легко написать текст на холсте — и нет, нам не нужно соединять его из разных изображений разных букв!
Помните, что для рисования изображения на холсте мы вызываем canvas.getContext("2d").drawImage()
. Написание текста очень похоже: мы вызываем canvas.getContext("2d").fillText()
.
Мы должны передать три аргумента для fillText()
:
- Строка, содержащая текст для записи.
- X-координата, в которой это записано.
- Y-координата, в которой это записано.
Есть необязательный четвертый аргумент:
- Максимальная ширина, которую текст может отображать на экране.
… но мы пока не будем об этом беспокоиться.
Чтобы проверить это, handleTick()
к handleTick()
, найдите линию, где мы рисуем аватар на холсте, и нарисуем образец строки текста сразу после этого:
1
2
3
|
gameCanvas.width = 400;
gameCanvas.getContext(«2d»).drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext(«2d»).fillText(«Testing, testing, one, two, three.», 100, 100);
|
Попробуйте это:
Обратите внимание, что, поскольку это рисуется после аватара и перед врагами, враги притягиваются сверху, а аватар — под ним:
Как я уже сказал, первый требуемый аргумент — это строка, и, как мы видели, мы можем построить строку, добавив строку к числу. Итак, это должно работать:
1
2
3
|
gameCanvas.width = 400;
gameCanvas.getContext(«2d»).drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 100, 100);
|
… и это действительно так!
Но это немного беспорядок, парящий там под врагами с этим крошечным шрифтом. Давайте разберемся с этим.
Привести в порядок текст
Сначала давайте переместим текст так, чтобы враги двигались под ним. Это просто означает нарисовать его после того, как все враги будут нарисованы, поэтому переместите соответствующую линию вниз:
01
02
03
04
05
06
07
08
09
10
|
gameCanvas.width = 400;
gameCanvas.getContext(«2d»).drawImage(avatarImage, avatarX, avatarY);
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
gameCanvas.getContext(«2d»).drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 100, 100);
|
Теперь давайте переместим его в верхний левый угол экрана. Это (0, 0)
так что это должно работать, верно?
01
02
03
04
05
06
07
08
09
10
|
gameCanvas.width = 400;
gameCanvas.getContext(«2d»).drawImage(avatarImage, avatarX, avatarY);
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
gameCanvas.getContext(«2d»).drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 0, 0);
|
Хм. Нет. Мы не можем видеть счет вообще. Это потому, что он использует (0, 0)
для размещения нижнего левого угла текста. Нам нужно немного сдвинуть текст, а затем:
01
02
03
04
05
06
07
08
09
10
|
gameCanvas.width = 400;
gameCanvas.getContext(«2d»).drawImage(avatarImage, avatarX, avatarY);
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
gameCanvas.getContext(«2d»).drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 0, 10);
|
Проверьте это:
Хорошо, пока все хорошо. Теперь давайте изменим сам шрифт. Мы делаем это, устанавливая свойство font
контекста холста равным строке CSS, представляющей шрифт. Если вы не знакомы с CSS, не волнуйтесь; на этом этапе все, что вам нужно знать, это то, что он содержит размер шрифта и шрифт:
01
02
03
04
05
06
07
08
09
10
11
|
gameCanvas.width = 400;
gameCanvas.getContext(«2d»).drawImage(avatarImage, avatarX, avatarY);
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
gameCanvas.getContext(«2d»).drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
gameCanvas.getContext(«2d»).font = «10px Impact»;
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 0, 10);
|
(«px» означает «пиксели».)
Обратите внимание, что мы должны установить шрифт перед рисованием текста!
Взглянем:
Это работает — но шрифт Impact действительно трудно читать при таком размере. Давайте сделаем это больше:
1
2
|
gameCanvas.getContext(«2d»).font = «18px Impact»;
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 0, 10);
|
К сожалению, это не очень хорошо работает, потому что шрифт по-прежнему 10 пикселей от верхней части холста, но теперь он 16 пикселей в высоту!
Мы могли бы изменить положение y шрифта, чтобы исправить это, но есть альтернатива: мы сделаем так, чтобы указанная позиция определяла верхний левый угол текста, а не нижний левый угол. Это просто:
1
2
3
|
gameCanvas.getContext(«2d»).font = «18px Impact»;
gameCanvas.getContext(«2d»).textBaseline = «top»;
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 0, 10);
|
Конечно, теперь между верхней частью текста и верхней частью холста есть разрыв в 10 пикселей:
… но это легко исправить сейчас:
1
2
3
|
gameCanvas.getContext(«2d»).font = «18px Impact»;
gameCanvas.getContext(«2d»).textBaseline = «top»;
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 0, 0);
|
Ну, на самом деле, мне нравится немного отступов, так что давайте сделаем это:
1
2
3
|
gameCanvas.getContext(«2d»).font = «18px Impact»;
gameCanvas.getContext(«2d»).textBaseline = «top»;
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 5, 5);
|
Намного лучше.
Разные шрифты
Возможно, вам интересно, есть ли список шрифтов, которые мы можем использовать. Ну да и нет. Смотрите, если вы выберете шрифт, который проигрыватель не установил на своем компьютере, текст будет отображаться шрифтом по умолчанию. Помните, JavaScript рисует текст на лету, используя ресурсы компьютера игрока.
Мы хорошо используем Impact, потому что он установлен на каждом компьютере — но значит ли это, что мы не можем использовать любой шрифт, кроме немногих в этом списке ?
К счастью, нет. Мы можем использовать любой понравившийся шрифт, если мы каким-то образом предоставляем ему доступ. И для этого мы будем использовать этот файл CSS — вы знаете, тот, который мы не трогали с самого начала серии.
Предположим, у нас есть шрифт «Really-Awesome». Это будет существовать на вашем компьютере где-то в виде файла — вероятно, файла .TFF («True Type Font»). Предположим, этот файл называется ReallyAwesomeFont.ttf
.
Теперь предположим, что вы загрузили этот шрифт на свой веб-сайт — действительноawesomewebsite.com — чтобы на него был прямой URL: http://reallyawesomewebsite.com/fonts/ReallyAwesomeFont.ttf
.
Затем вы можете сообщить об этом браузеру плеера, добавив его в свой файл CSS:
1
2
3
4
|
@font-face {
font-family: ‘Really-Awesome’;
src: url(‘http://reallyawesomewebsite.com/fonts/ReallyAwesomeFont.ttf’);
}
|
С помощью этой строки в вашем CSS вы можете изменить свой код следующим образом:
1
2
3
|
gameCanvas.getContext(«2d»).font = «18px Really-Awesome»;
gameCanvas.getContext(«2d»).textBaseline = «top»;
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 5, 5);
|
… и это сработает, потому что их браузер найдет шрифт «Really-Awesome» в таблице стилей и найдет URL для TTF. Большой!
Я не собираюсь демонстрировать это, потому что у меня нет прав на распространение любых шрифтов; если я загружу некоторые из них и дам вам ссылку на TTF как часть этого урока, это не совсем справедливо. Но есть альтернатива …
Google Web Fonts
Google собрал большую коллекцию шрифтов, которые вы можете использовать в своем проекте в своем CSS, используя тот же принцип, что и выше. Посмотрите на коллекцию .
Есть несколько критериев, по которым вы можете искать шрифты, и вы можете ввести пример текста, чтобы увидеть, как он будет отображаться:
Я собираюсь выбрать Исландию . Когда я нажимаю Quick Use, он дает мне этот HTML:
1
|
<link href=’http://fonts.googleapis.com/css?family=Iceland’ rel=’stylesheet’ type=’text/css’>
|
Если вы загрузите http://fonts.googleapis.com/css?family=Iceland в свой браузер, вы увидите, что это то же самое, что мы писали с нуля:
1
2
3
4
5
6
7
8
|
@media screen {
@font-face {
font-family: ‘Iceland’;
font-style: normal;
font-weight: 400;
src: local(‘Iceland’), local(‘Iceland-Regular’), url(‘http://themes.googleusercontent.com/static/fonts/iceland/v1/F6LYTZLHrG9BNYXRjU7RSw.woff’) format(‘woff’);
}
}
|
В нем есть еще несколько деталей, и шрифт в формате WOFF, а не TTF, но вы поняли идею.
Вы можете использовать любой понравившийся шрифт (и это не обязательно должен быть веб-шрифт Google), но для целей данного урока я предполагаю, что вы используете Исландию. Итак, отредактируйте game.html
и вставьте в него ссылку на шрифт:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src=»js/main.js»></script>
<link rel=»stylesheet» href=»css/style.css» />
<link href=’http://fonts.googleapis.com/css?family=Iceland’ rel=’stylesheet’ type=’text/css’>
</head>
<body>
<canvas id=»gameCanvas» onclick=»setUpGame();»
<p>Click inside the box to play.
<p>Make sure you have <a href=»http://google.com/chrome/» rel=»external»>Chrome</a>.</p>
<p>From <a href=»http://active.tutsplus.com/tutorials/html5/html5-avoider-game-tutorial-keeping-score»>HTML5 Avoider Game Tutorial: Keeping Score</a>.</p>
</body>
</html>
|
Теперь вернемся в main.js
и изменим шрифт с Impact на Iceland:
1
2
3
|
gameCanvas.getContext(«2d»).font = «18px Iceland»;
gameCanvas.getContext(«2d»).textBaseline = «top»;
gameCanvas.getContext(«2d»).fillText(«Score: » + ticksSurvived, 5, 5);
|
Взглянем:
Здорово!
Задача: показать лучший результат
Теперь, когда текущий счет постоянно отображается на экране, вполне естественно, что игрок захочет увидеть, что он пытается побить.
Используя то, что вы узнали, нарисуйте их лучший результат в правом верхнем углу холста. Это немного сложнее, чем кажется: вам нужно решить, что делать (если что-нибудь), когда текущий результат превосходит текущий рекорд!
Сохранение результатов между сессиями
Вы, наверное, уже заметили, что высокий балл сбрасывается при перезагрузке страницы. Это имеет смысл — в конце концов, мы запускаем эту строку в самом начале:
1
|
var mostTicksSurvived = 0;
|
… но даже без этой линии, высокий балл все равно не сохранится. Все переменные сбрасываются и не присваиваются, когда вы покидаете страницу.
Однако есть альтернатива: каждый браузер выделяет 5 МБ памяти для каждого домена веб-сайта. В этом хранилище объемом 5 МБ вы можете хранить любую понравившуюся вам строку, и она останется там, даже если пользователь закроет свой браузер и перезагрузит компьютер.
Он называется локальным хранилищем , и им действительно легко пользоваться! Чтобы что-то сохранить, достаточно указать две строки:
- название для предмета и
- стоимость предмета.
Чтобы получить его, вам просто нужно имя, которое вы изначально использовали.
Быстрый пример
Вот краткий пример: давайте добавим что-то в локальное хранилище прямо в начале функции setUpGame()
:
1
2
|
function setUpGame() {
localStorage.setItem(«exampleItem», «This is a great example.»);
|
Сохраните файл и загрузите свою игру. Затем закройте вкладку.
Теперь снова отредактируйте файл JS, удалите только что добавленную строку и замените ее строкой, которая должна получить элемент:
1
2
|
function setUpGame() {
alert(localStorage.getItem(«exampleItem»));
|
Так что, для ясности, в коде нет ничего, что устанавливало бы значение "exampleItem"
.
Когда вы загрузите игру в этот раз, вы должны увидеть предупреждение "This is a great example."
— доказательство того, что строка была сохранена между сессиями.
Мы можем удалить этот элемент из локального хранилища, используя localStorage.removeItem()
:
1
2
|
function setUpGame() {
localStorage.removeItem(«exampleItem»);
|
Я рекомендую вам сделать это сейчас, а затем полностью удалить строку.
Вы также можете очистить все данные в локальном хранилище одновременно, используя localStorage.clear()
(аргументы не требуются). Ладно — не совсем все . Ваша страница может влиять только на локальное пространство хранения, назначенное домену, на котором она размещена; Я не могу очистить ваше локальное хранилище для google.com со страницы, размещенной на tutsplus.com, и наоборот.
Сохранение лучшего результата
Теперь, когда мы увидели пример, давайте применим его на практике.
Всякий раз, когда устанавливается новый рекорд, давайте сохраним его в локальном хранилище. Он устанавливается только в одном месте: когда аватар сталкивается с врагом и текущий результат выше, чем лучший результат. Итак, добавьте вызов localStorage.setItem()
в соответствующем месте:
01
02
03
04
05
06
07
08
09
10
11
12
|
while (currentEnemyNumber < numberOfEnemies) {
if ( ( (avatarX < enemyXPositions[currentEnemyNumber] && enemyXPositions[currentEnemyNumber] < avatarX + 30) || (enemyXPositions[currentEnemyNumber] < avatarX && avatarX < enemyXPositions[currentEnemyNumber] + 30) ) && ( (avatarY < enemyYPositions[currentEnemyNumber] && enemyYPositions[currentEnemyNumber] < avatarY + 33) || (enemyYPositions[currentEnemyNumber] < avatarY && avatarY < enemyYPositions[currentEnemyNumber] + 30) ) ) {
alert(«You hit an enemy! You survived » + ticksSurvived + » ticks.»);
if (ticksSurvived > mostTicksSurvived) {
alert(«You beat your old high score by » + (ticksSurvived — mostTicksSurvived) + » ticks!»);
mostTicksSurvived = ticksSurvived;
localStorage.setItem(«bestScore», mostTicksSurvived);
}
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
|
Когда страница загружается впервые, мы должны проверить, сохранен ли высокий результат в локальном хранилище, и присвоить mostTicksSurvived
этому значению, если это так. (Вам не нужно повторять эту проверку после загрузки страницы, если вы не беспокоитесь о том, что пользователь играет в игру одновременно на двух отдельных вкладках.)
Как мы можем проверить, существует ли значение? Все, что нам нужно сделать, это поместить его в условие if
:
1
2
3
|
if (localStorage.getItem(«thisItemDoesNotExist»)) {
alert(«This alert will never appear!»);
}
|
Вышеприведенное alert()
никогда не localStorage.getItem("thisItemDoesNotExist")
потому что localStorage.getItem("thisItemDoesNotExist")
не существует. Легко, правда? Так что в начале setUpGame()
мы можем просто написать:
1
2
3
4
|
function setUpGame() {
if (localStorage.getItem(«bestScore»)) {
mostTicksSurvived = localStorage.getItem(«bestScore»);
}
|
Попробовать. Загрузите страницу, установите высокий балл, затем побейте его. Перезагрузите страницу, затем получите более низкий балл — он говорит вам, что вы побили свой старый балл?
Большой! Кстати, если вы приняли вызов раньше, вы сможете увидеть свой предыдущий лучший результат в правом верхнем углу холста, и теперь он будет переноситься с сессии на сессию.
Sidenote: сильная и слабая печать
Мы почти закончили для этой части серии, но я просто хочу отметить, что мы снова рассматривали строку как число, а число как строку: локальное хранилище сохраняет только строковые значения, но мы сохраните число, когда мы сохраним лучший результат, и используем значение, которое мы извлекаем из него, как число позже.
Это приемлемо, потому что JavaScript называется «слабо типизированным». Другие языки программирования являются «строго типизированными», что означает, что если вы говорите, что что-то является числом, то оно остается числом; если вы говорите, что-то является строкой, то это остается строкой.
В языке со строгой типизацией, если вы хотите добавить строку "Score: "
к числу 32
, вам нужно явно указать языку, чтобы он воспринимал 32
как строку, возможно, так:
"Score: " + (32 as String)
Кроме того, в строго типизированном языке, когда вы определяете переменную, вы также указываете, какой это тип:
1
2
|
var greeting:String = «Hello.»;
var score:Number = 1000;
|
Но JavaScript не беспокоится об этих вещах. Это не делает его лучше или хуже, чем строго типизированный язык, просто другой.
Завершение
Вот именно для этой части урока. Теперь ваша игра имеет как условие «игра закончена», так и средство ведения счета. Кроме того, вы узнали о рисовании текста на холсте, выборе шрифтов и использовании локального хранилища.
Если вы хотите испытать себя перед следующей частью, попробуйте внести следующие изменения:
- Легко: Нарисуйте лучший результат на холсте, прежде чем игрок щелкнет по нему.
- Средний: Храните лучшие пять очков игрока.
- Сложно: сохранить позиции всех врагов в локальном хранилище и восстановить их в следующем сеансе (возможно, вам придется провести дополнительное исследование здесь! Подсказка: посмотрите на JSON .)
Наслаждайтесь!