Как сделать игру шарики javascript
Перейти к содержимому

Как сделать игру шарики javascript

  • автор:

Игра линии шарики на JavaScript

Игра линии — шарики на JavaScript, проверена работа в браузере MS IE и Mozilla Firefox. Для запуска игры броузер должен поддерживать javascript. Цель данной игры — убрать разноцветные шарики с игрового поля. Для этого вам предстоит выстраивать шарики одного цвета в линии по 6 и более штук по вертикали или диагонали. Набираете очки, чем больше, тем лучше. Для получения высоких результатов в данной логической игре рекомендую на поле выстраивать сразу несколько линий. Успехов в игре!

Ссылки на другие игры линии:

    Игра лайнс — всем известная игра в Линии на JavaScript.

JavaScript-анимации

С помощью JavaScript-анимаций можно делать вещи, которые нельзя реализовать на CSS.

Например, движение по сложному пути с временной функцией, отличной от кривой Безье, или canvas-анимации.

Использование setInterval

Анимация реализуется через последовательность кадров, каждый из которых немного меняет HTML/CSS-свойства.

Например, изменение style.left от 0px до 100px – двигает элемент. И если мы будем делать это с помощью setInterval , изменяя на 2px с небольшими интервалами времени, например 50 раз в секунду, тогда изменения будут выглядеть плавными. Принцип такой же, как в кино: 24 кадров в секунду достаточно, чтобы создать эффект плавности.

Псевдокод мог бы выглядеть так:

let timer = setInterval(function() < if (animation complete) clearInterval(timer); else increase style.left by 2px >, 20); // изменять на 2px каждые 20ms, это около 50 кадров в секунду

Более детальная реализация этой анимации:

let start = Date.now(); // запомнить время начала let timer = setInterval(function() < // сколько времени прошло с начала анимации? let timePassed = Date.now() - start; if (timePassed >= 2000) < clearInterval(timer); // закончить анимацию через 2 секунды return; >// отрисовать анимацию на момент timePassed, прошедший с начала анимации draw(timePassed); >, 20); // в то время как timePassed идёт от 0 до 2000 // left изменяет значение от 0px до 400px function draw(timePassed) < train.style.left = timePassed / 5 + 'px'; >

Для просмотра примера, кликните на него:

    #train      

Использование requestAnimationFrame

Теперь давайте представим, что у нас есть несколько анимаций, работающих одновременно.

Если мы запустим их независимо с помощью setInterval(. 20) , тогда браузеру будет необходимо выполнять отрисовку гораздо чаще, чем раз в 20ms .

Это происходит из-за того, что каждая анимация имеет своё собственное время старта и «каждые 20 миллисекунд» для разных анимаций – разные. Интервалы не выравнены и у нас будет несколько независимых срабатываний в течение 20ms .

setInterval(function() < animate1(); animate2(); animate3(); >, 20)

…Меньше нагружают систему, чем три независимых функции:

setInterval(animate1, 20); // независимые анимации setInterval(animate2, 20); // в разных местах кода setInterval(animate3, 20);

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

Существует ещё одна вещь, про которую надо помнить: когда CPU перегружен или есть другие причины делать перерисовку реже (например, когда вкладка браузера скрыта), нам не следует делать её каждые 20ms .

Но как нам узнать об этом в JavaScript? Спецификация Animation timing описывает функцию requestAnimationFrame , которая решает все описанные проблемы и делает даже больше.

let requestId = requestAnimationFrame(callback)

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

Если в callback происходит изменение элемента, тогда оно будет сгруппировано с другими requestAnimationFrame и CSS-анимациями. Таким образом браузер выполнит один геометрический пересчёт и отрисовку, вместо нескольких.

Значение requestId может быть использовано для отмены анимации:

// отмена запланированного запуска callback cancelAnimationFrame(requestId);

Функция callback имеет один аргумент – время прошедшее с момента начала загрузки страницы в миллисекундах. Это значение может быть получено с помощью вызова performance.now().

Как правило, callback запускается очень скоро, если только не перегружен CPU или не разряжена батарея ноутбука, или у браузера нет какой-то ещё причины замедлиться.

Код ниже показывает время между первыми 10 запусками requestAnimationFrame . Обычно оно 10-20 мс:

  

Структура анимации

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

function animate() < let start = performance.now(); requestAnimationFrame(function animate(time) < // timeFraction изменяется от 0 до 1 let timeFraction = (time - start) / duration; if (timeFraction >1) timeFraction = 1; // вычисление текущего состояния анимации let progress = timing(timeFraction); draw(progress); // отрисовать её if (timeFraction < 1) < requestAnimationFrame(animate); >>); >

Функция animate имеет три аргумента, которые описывают анимацию:

Продолжительность анимации. Например, 1000 .

Функция расчёта времени, как CSS-свойство transition-timing-function , которая будет вычислять прогресс анимации (как ось y у кривой Безье) в зависимости от прошедшего времени ( 0 в начале, 1 в конце).

Например, линейная функция значит, что анимация идёт с одной и той же скоростью:

function linear(timeFraction)

Это как если бы в transition-timing-function передать значение linear . Ниже будут представлены более интересные примеры.

Функция отрисовки, которая получает аргументом значение прогресса анимации и отрисовывает его. Значение progress=0 означает, что анимация находится в начале, и значение progress=1 – в конце.

Эта та функция, которая на самом деле и рисует анимацию.

Вот как она могла бы двигать элемент:

function draw(progress)

…Или делать что-нибудь ещё. Мы можем анимировать что угодно, как захотим.

Теперь давайте используем нашу функцию, чтобы анимировать свойство width от 0 до 100% .

Нажмите на элемент для того, чтобы посмотреть пример:

function animate() < let start = performance.now(); requestAnimationFrame(function animate(time) < let timeFraction = (time - start) / duration; if (timeFraction >1) timeFraction = 1; let progress = timing(timeFraction) draw(progress); if (timeFraction < 1) < requestAnimationFrame(animate); >>); >
     progress  elem.onclick = function() < animate(< duration: 1000, timing: function(timeFraction) < return timeFraction; >, draw: function(progress) < elem.style.width = progress * 100 + '%'; >>); >;  
animate(< duration: 1000, timing(timeFraction) < return timeFraction; >, draw(progress) < elem.style.width = progress * 100 + '%'; >>);

В отличие от CSS-анимаций, можно создать любую функцию расчёта времени и любую функцию отрисовки. Функция расчёта времени не будет ограничена только кривой Безье, а функция draw может менять не только свойства, но и создавать новые элементы (например, для создания анимации фейерверка).

Функции расчёта времени

Мы уже рассмотрели самый простой пример линейной функции расчёта времени выше.

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

Степень n

Если мы хотим ускорить анимацию, мы можем возвести progress в степень n .

Например, параболическая кривая:

function quad(timeFraction)

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

На графике выше красным цветом обозначена обычная функция и синим – после easeOut .

  • Обычный скачок – объект сначала медленно скачет внизу, а затем резко подпрыгивает вверх.
  • Обратный easeOut – объект вначале прыгает вверх, и затем скачет там.

easeInOut

Мы можем применить эффект дважды – в начале и конце анимации. Такая трансформация называется «easeInOut».

Для функции расчёта времени, анимация будет вычисляться следующим образом:

if (timeFraction else < // вторая половина анимации return (2 - timing(2 * (1 - timeFraction))) / 2; >
function makeEaseInOut(timing) < return function(timeFraction) < if (timeFraction < .5) return timing(2 * timeFraction) / 2; else return (2 - timing(2 * (1 - timeFraction))) / 2; >> bounceEaseInOut = makeEaseInOut(bounce);

В действии, bounceEaseInOut :

#brick < width: 40px; height: 20px; background: #EE6B47; position: relative; cursor: pointer; >#path
        

Функция «easeInOut» объединяет два графика в один: easeIn (обычный) для первой половины анимации и easeOut (обратный) – для второй половины.

Разница хорошо заметна, если сравнивать графики easeIn , easeOut и easeInOut для функции circ :

  • Красный обычный вариант circ ( easeIn ).
  • Зелёный – easeOut .
  • Синий – easeInOut .

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

Более интересная функция «draw»

Вместо передвижения элемента мы можем делать что-нибудь ещё. Всё, что нам нужно – это правильно написать функцию draw .

Вот пример «скачущей» анимации набирающегося текста:

textarea < display: block; border: 1px solid #BBB; color: #444; font-size: 110%; >button
           

Итого

JavaScript может помочь в тех случаях, когда CSS не справляется или нужен жёсткий контроль над анимацией. JavaScript-анимации должны быть сделаны с помощью requestAnimationFrame . Это встроенный метод браузера, который вызывает переданную в него функцию в тот момент, когда браузер готовится совершить перерисовку (обычно это происходит быстро, но конкретные задержки зависят от браузера).

Когда вкладка скрыта, на ней совсем не происходит перерисовок, и функция не будет вызвана: анимация будет приостановлена и не потратит ресурсы. Это хорошо.

Вспомогательная функция animate для создания анимации:

function animate() < let start = performance.now(); requestAnimationFrame(function animate(time) < // timeFraction изменяется от 0 до 1 let timeFraction = (time - start) / duration; if (timeFraction >1) timeFraction = 1; // вычисление текущего состояния анимации let progress = timing(timeFraction); draw(progress); // отрисовать её if (timeFraction < 1) < requestAnimationFrame(animate); >>); >
  • duration – общая продолжительность анимации в миллисекундах.
  • timing – функция вычисления прогресса анимации. Получается момент времени от 0 до 1, возвращает прогресс анимации, обычно тоже от 0 до 1.
  • draw – функция отрисовки анимации.

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

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

То же самое и с draw : мы можем анимировать всё что угодно, не только CSS-свойства.

Lines 9753: День Сурка

Глядя на название игры Lines 9753, можно подумать, что этот рассказ про 9753-ий клон известной игры Color Lines, в которой надо составлять по 5 шариков в ряд одного цвета. Да, так и есть, клон. Ну что ещё может быть нового? Я поделюсь своими идеями и некоторыми тонкостями реализации на HTML+CSS+JavaScript. Расскажу что значат цифры 9753. Предложу способ найти другую комбинацию цифр и посмотреть, как изменится игра. Станет она интереснее или скучнее — решать вам. Ещё не догадались, что значит 9753? Тогда, прошу под кат.

Многообразие вариантов игры

Никогда не задумывались, откуда в оригинальной игре взялась размерность поля 9×9? Удивительно, насколько хорошо подобраны не только размерность игры, но и другие параметры, так что играть интересно: количество цветов — 7, количество шариков в ряд — 5 и количество появляющихся шаров за ход — 3. Эти 4 параметра поставлены в название игры — Lines 9753. Именно эта четвёрка по-большому счёту определяет сложность игры, которая скорее всего, подходит не всем.

Возможно, кому-то придётся по душе другой набор. Например:

  • 3333 — легкотня типа пасьянса, можно не бояться зайти в тупик и постараться схлопывать как можно больше шариков за раз (5 ещё легко, 6 уже сложнее, 7 и 8 практически нереально), подойдёт в случае, когда совсем не хочется думать;
  • 5643 — на доске 5×5 клетки не такие мелкие как 9×9, поэтому удобно играть на телефоне, однако сложность, кажется чуть выше, чем в 9753;
  • 5543 — несколько более лёгкий вариант чем 5643, потому что количество различных цветов меньше, а значит больше вероятность составить ряд шаров одного цвета;
  • 4443 — по сложности сравнима с 5643, но шарики ещё крупнее — всего 4 шара по короткой стороне экрана.

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

Ход назад

Разрешается делать ходы назад. Давным-давно, когда деревья были высокими, а я был ещё студентом довелось, мне поиграть в Color Lines. Игра очень понравилась. Один раз, проиграв в неё полдня и, набрав несколько тысяч очков, я зашел в тупик. Пришло осознание того, что это была случайность, что шары выпадали удачно, и что в ближайшее время так далеко я пройти не смогу. Чтобы побить свой же рекорд, мне надо будет убить раз в десять больше времени, и то если повезёт. С тех пор я долго не играл в эту игру. Ощущение вселенской несправедливости засело глубоко в душе. И сидело оно там до тех пор, пока не выкристаллизовался коварный план мести — сделать такую же игру, но с возможностью отматывать ходы назад хоть до самого начала. Именно эта опция стала для меня главной мотивирующей причиной написать игру по-своему.

Фиксированная случайность

Некоторые игроки говорили, что иногда машина специально ставит шары в неудобные места, чтобы усложнить прохождение. Особенно, когда остаётся совсем мало свободных клеток, невозможно дождаться нужного шара в нужном месте. Другими, словами, случайность в игре совсем не случайна. С новой возможностью отматывать ходы назад, было бы не логично предлагать каждый раз новое место для новых шаров. Так можно было бы ходить вперёд и назад до тех пор, пока не появится нужный шар в нужном месте. Поэтому в игре все ходы машины детерминированы и зависят случайным образом от номера хода и расположения шаров на поле. Мало того, все играют одну и туже игру. То есть, игра начинается с одной и той же расстановки (первый ход машины), и далее ходы машины зависят от того, как сходит игрок: если игроки ходят одинаково, то и на поле у них будет одинаковая расстановка шаров. Это позволяет объективно и независимо от случайности сравнивать разные решения. Некоторые любят меряться кто дальше, кто быстрее,… Решения в виде последовательности ходов игрока отправляются на сервер. Ходы машины вычисляются всё по тому же случайному алгоритму. Обмануть сервер не получится, вся последовательность проверяется на корректность, и только потом результат заносится в таблицу. Все рекорды сохраняются вместе с последовательностью ходов игрока. Потом проанализируем и опубликуем лучшие решения тех кто наберет 9999 очков. Нужно будет придумать, по какому критерию выбирать лучших.

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

Подсказка

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

Подсчёт очков

Подсчёт очков сделан по классической формуле N * (1 + N — DIMENSION), где N — количество шаров в линии, DIMENSION — размерность игрового поля. То есть 5, 6, 7, 8 собранных вместе шаров дают соответственно 5, 12, 21 и 32 очка. Для варианта игры 3333 при схлопывании 3, 4, 5 и 6 шаров получается 3, 8, 15, 24.

Графика

Графика только векторная — не люблю артефакты масштабирования, особенно нечёткие границы. Основные анимированные графические элементы: винтажный магнитофонный счётчик и прыгающие шарики с бликами и градиентным освещением. Они выполнены с использованием CSS и приправлены кусочками JavaScript. Изначально была сделана тёмная цветовая схема. Её можно попробовать, включив в настройках. Однако всё выглядит мрачновато. Светлая мне больше нравится.

Трёхмерный шарик

Как ни парадоксально звучит, но из 3-х вложенных друг в друга прямоугольников получается трёхмерный шарик:

  1. контейнер, задающий размер и положение на экране
  2. круглый квадрат, создающий двухмерную форму, сжимающуюся и растягивающуюся во время подпрыгивания
  3. квадрат с эллиптической градиентной заливкой, определяющий цвет и освещение шарика, добавляющие третье измерение.
@keyframes flat < 0% 100% > @keyframes jumping < 0% 10% 40% 50% 60% 70% 90% 100% > 

Максимальное количество цветов 15.

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

Счётчик

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

Звук

Звуки (взрыв и прыжки) синтетические. Они генерируются по незамысловатым математическим формулам. Это создаёт некоторый привкус 80-х, когда большинство звуков в играх генерировалось с использованием одноголосного частотного генератора. К плюсам можно отнести компактность программы: простенький генератор таких звуковых эффектов, написанный на JavaScript, оказывается значительно меньше по размеру, чем mp3 файл. Для каждого шарика выбираем свою пару нот lowFreq и highFreq (см. wikipedia) и заполняем аудиобуфер примерно вот так:

 var soundUpBuffer = audioCtx.createBuffer(1, frameCount, sampleRate); for (var i = 0; i < frameCount; i++) < var x = i / frameCount; // from 0.0 to 1.0 var time = duration * x; var volume = Math.sin(x * Math.PI); var freq = lowFreq + Math.sqrt(x) * (highFreq - lowFreq); buffer[i] = volume * Math.sin(2 * Math.PI * freq * time); >

Здесь частота изменяется от lowFreq до highFreq по кривой x ½ , а громкость sin(π x).

У взрыва громкость затухает по формуле 1/(1+10*x+1000*x*x), а частота выбирается случайно с первой по пятую октаву.

Адаптивный размер диалогового окна

Реализован алгоритм подбора размера диалогового окна, так чтобы оно занимало как можно больше пространства и не выходило за границы видимой области. Проблема заключается в том, что на разных устройствах размер диалогов разный — иногда получается слишком мелко, а иногда не входит в экран. Я сделал размер диалогов зависимым от размера шрифта (CSS единицы em). Далее, перебираю размер шрифта, чтобы диалог занимал не более 95% любой из размерностей экрана. Так не пришлось городить кейсы для адаптивного CSS.

Небольшой размер программы

Вся игра умещается в одном html файле около 150Кб. Около 3000 строк исходного кода (конечно без серверной части). Никакие JavaScript библиотеки и фреймворки не использовались — всё натурально, как в каменном веке. Иногда просто не хочется разбираться со сторонним API, а иногда полезно понять, насколько сильно фреймворки упрощают жизнь изобретателя велосипедов и испытателя новых граблей. Полезно бывает сначала придумать самому как можно реализовать ту или иную идею, а уж потом сравнить с тем как это сделали другие. Например, версия под Андроид занимает всего 250Кб. Она состоит из тонкой WebView обёртки и пачки png-шек под разные разрешения телефонов. Кстати, png-шки можно было бы заменить одной векторной xml-кой. Получилось бы точно в духе минимализма.

Форум

Форум работает на phpBB. Эккаунт позволяет:

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

i18n и l10n

Интернационализация и локализация. Игра переведена на русский и английский. Всего 35 коротких фраз. Я бы не отказался от бесплатной помощи по переводу ещё на несколько популярных языков (Китайский, Немецкий, . ).

Никакой рекламы

Часто можно услышать мнение, что если программа бесплатная и без рекламы, то значит троян, бэкдор, вирус, … Другими словами, бесплатный сыр только в мышеловке. Иначе зачем? Почему кто-то готов делать что-то для других безвозмездно? Я общего ответа не знаю. Мало того, не знаю точно, почему сам это делаю. Мне просто интересно. Если что-то нравится, то я часто задумываюсь, как автору это удалось. Хочется попробовать сделать так же или даже лучше. Иногда всё нравится, за исключением каких-то деталей, и возникает желание сделать подобное, но со своим блэк джеком и финтифлюшками.

Заключение

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

Ссылки

  • Интерактивный оригинал этих заметок можно найти здесь
  • Lines 9753
  • https://ru.wikipedia.org/wiki/Color_Lines
  • http://4pda.ru/forum/lofiversion/index.php?t394525
  • http://www.softholm.com/igra/lines/
  • http://lines-98.ru/
  • https://gamefile.ru/games/lines/#
  • http://linesgame.ru/online.html
  • https://habrahabr.ru/post/41775/

Пишем игру Lines (Линии, Шарики)

Еще одна простенькая игрушка «Шарики» или «Линии», кому как удобней, в общем. Смысл в том, что есть игровое поле, девять на девять клеток, иногда делают больше или меньше. В случайных клетках появляются шарики, игроку надо собрать по вертикали/горизонтали, подряд пять шариков (либо больше), одинакового цвета. За каждый ход, кроме как когда собрана линия из пяти шаров, на поле будут добавляться по три шара. Когда поле полностью заполнится, засчитывается проигрыш. Задача игрока, продержаться как можно дольше и набрать наибольшее количество очков. Как не трудно догадаться, для создания этой игры, придется поработать с двумерными массивами. Ранее мы уже делали подобные игры, но в этот раз попробуем несколько иной подход.

Создаем в Unity новый 2D проект.
Но перед этим в Photoshop, либо другом редакторе, надо сделать пару картинок. Первая, это обычный квадрат белого цвета, ширина и высота, должны быть одинаковые. Размеры например, 150х150. Другая картинка это будет шарик, тоже белого цвета и с прозрачным фоном, чуть меньшего размера, чем квадрат.

Итак, импортируем изображения в проект и добавляем на сцену.
На спрайт квадрата, надо добавить Box Collider 2D и небольшой скрипт LinesCell:

using UnityEngine; using System.Collections; public class LinesCell : MonoBehaviour < public int cellID; public Lines _lines; void onmousedown() < _lines.FindBall(cellID); >>

Суть в том, что каждый квадратик имеет свой номер, и кликом ЛКМ по нему, мы будем сообщать главному скрипту, к какой клетки обращаемся.

На спрайт шарика, цепляем Balls:

using UnityEngine; using System.Collections; public class Balls : MonoBehaviour

Данный скрипт нужен лишь для хранения цвета шарика.

Перетаскиваем спрайты в папку Prefab. Готово.

Теперь, добавляем на сцену пустой объект и вешаем на него скрипт Lines.
Преступаем к редактированию. Первое, в самом начале подключаем:

using UnityEngine; using UnityEngine.UI; using System.Collections; using System.Collections.Generic;
public GameObject cellSprite; public GameObject ballSprite; public Color[] colors; public float shift; public int size = 9; public Text scoreText; private int id; private GameObject[,] cells; private GameObject[,] tmp; private GameObject ball; private bool gameOver; private int score; private int scoreOld;

Здесь у на пара префабов, квадрат cellSprite и шарик ballSprite. Так же, массив цветов colors. В переменной shift указывается смещение для квадратов, чтобы найти нужное число, помещаем квадрат по центру (позиция по нулям), заме дублируем его или кидаем второй, и один из них надо сместить по Х или Y так, чтобы между ними получился небольшой зазор. Далее, size — количество клеток по вертикали и горизонтали. А в scoreText обычный текстовый UI.

Продолжаем, старт сцены:

void Start () < shift = Mathf.Abs(shift); tmp = new GameObject[size,size]; id = 0; float posX = -shift*(size-1)/2-shift; float posY = Mathf.Abs(posX); float Xreset = posX; cells = new GameObject[size,size]; for(int y = 0; y < size; y++) < posY -= shift; for(int x = 0; x < size; x++) < posX += shift; cells[x,y] = Instantiate(cellSprite, new Vector3(posX, posY, 0), Quaternion.identity) as GameObject; cells[x,y].GetComponent()._lines = this; cells[x,y].GetComponent().cellID = id; cells[x,y].transform.parent = transform; id++; > posX = Xreset; > gameOver = false; AddBalls(); >

Создается игровое поле, формула расчета позиции такая, что сетка будет размещена по центру, относительно нулевых координат. Иначе говоря, нам остается только после запуска сцены, отрегулировать размер камеры, чтобы вместилась вся конструкция:

Пишем игру Lines (Линии, Шарики)

Сделаем вывод текстовой информации:

void OnGUI ()

Публичная функция, к которой мы обращаемся по клику:

public void FindBall(int ball_id) < if(!gameOver) < for(int y = 0; y < size; y++) < for(int x = 0; x < size; x++) < if(ball == null) < if(cells[x,y].GetComponent().cellID == ball_id && tmp[x,y]) < tmp[x,y].GetComponent().color = tmp[x,y].GetComponent().color; ball = tmp[x,y]; tmp[x,y] = null; > > else < if(cells[x,y].GetComponent().cellID == ball_id && tmp[x,y] == null) < Color color = ball.GetComponent().color; color.a = 0.5f; ball.GetComponent().color = color; ball.transform.position = cells[x,y].transform.position; tmp[x,y] = ball; ball = null; FindLines(true); > > > > > >

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

Очистка массива с шарами, если засчитан проигрыш:

IEnumerator ClearField() < yield return new WaitForSeconds (1); gameOver = false; int j = 0; while(j < id) < for(int y = 0; y < size; y++) < for(int x = 0; x < size; x++) < j++; Destroy(tmp[x,y].gameObject); tmp[x,y] = null; >> > Debug.Log("New Game"); AddBalls(); >

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

void FindLines(bool isBall) < Listarr = new List(); bool getBall = true; int z = 0; int index = 0; while(z < id) < for(int y = 0; y < size; y++) < for(int x = 0; x < size; x++) < z++; if(x < size-1) < if(tmp[x,y] && tmp[x+1,y] && tmp[x,y].GetComponent().color == tmp[x+1,y].GetComponent().color && getBall) < if(index == 0) < index = 2; arr.Add(tmp[x,y]); arr.Add(tmp[x+1,y]); >else < index++; arr.Add(tmp[x+1,y]); >> else if(index < 5) < index = 0; arr = new List(); > else < getBall = false; >> > > > if(index == 0) < arr = new List(); getBall = true; z = 0; while(z < id) < for(int y = 0; y < size; y++) < for(int x = 0; x < size; x++) < z++; if(x < size-1) < if(tmp[y,x] && tmp[y,x+1] && tmp[y,x].GetComponent().color == tmp[y,x+1].GetComponent().color && getBall) < if(index == 0) < index = 2; arr.Add(tmp[y,x]); arr.Add(tmp[y,x+1]); >else < index++; arr.Add(tmp[y,x+1]); >> else if(index < 5) < index = 0; arr = new List(); > else < getBall = false; >> > > > > if(isBall) < if(index < 5) AddBalls(); else StartCoroutine (WaitDestroy(arr)); >else < if(index >= 5) StartCoroutine (WaitDestroy(arr)); > >

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

Уничтожение линий, с небольшой задержкой:

IEnumerator WaitDestroy(List item) < yield return new WaitForSeconds (0.2f); foreach(GameObject obj in item) < Destroy(obj.gameObject); score++; >>

Добавление новых шариков:

void AddBalls() < int ballCount = 0; int e = 0; while(e < id) < for(int y = 0; y < size; y++) < for(int x = 0; x < size; x++) < if(tmp[x,y] == null) ballCount++; e++; >> > if(ballCount > 3) ballCount = 3; int i = 0; while(i < ballCount) < int j = Random.Range(0, id); for(int y = 0; y < size; y++) < for(int x = 0; x < size; x++) < if(cells[x,y].GetComponent().cellID == j && tmp[x,y] == null) < tmp[x,y] = Instantiate(ballSprite, cells[x,y].transform.position, Quaternion.identity) as GameObject; Color _color = colors[Random.Range(0, colors.Length)]; tmp[x,y].GetComponent().color = _color; _color.a = 0.5f; tmp[x,y].GetComponent().color = _color; i++; > > > > ballCount = 0; e = 0; while(e < id) < for(int y = 0; y < size; y++) < for(int x = 0; x < size; x++) < if(tmp[x,y] == null) ballCount++; e++; >> > if(ballCount == 0) < gameOver = true; Debug.Log("Game Over"); scoreOld = score; score = 0; StartCoroutine (ClearField()); >FindLines(false); >

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

smile

Вот и всё!

Скачать проект, если лень делать с нуля:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *