Leaderboard
Popular Content
Showing content with the highest reputation on 03/26/16 in Posts
-
Ты так пишешь будто длинные названия классов - это что-то плохое. Ну и что, что их три? Главное ведь не длина атрибута class, а понятность чтения кода. Не понимаю чем это хуже той лапши на SASS, которую ты привёл в пример. Особенно забавно получается если приходится разбираться в таком коде, не имея исходников (был у меня такой случай). UPD: Дополню справедливости ради, что я тоже не в восторге от системы модификаторов, предложенной яндексом. Сам то я примерно так пишу: <div class="block-name__elem-name _mod-1 _mod-2"></div> философия БЭМ сохраняется, но код при этом читабельней. Однако это только потому, что ИЕ6 помер наконец. А ведь БЭМ придумывали ещё в эпоху актуальности ИЕ6, в котором (сюрприз-сюрприз) в CSS не работала запись такого вида: .block-name__elem-name._mod-1 { /* ... */ } Именно оттуда растут ноги такого длинного объявления модификаторов.2 points
-
1 point
-
1 point
-
1 point
-
1 point
-
1 point
-
Я очень часто замечаю, что многие фукают на БЭМ, но по сути используют его принципы в какой-либо интерпретации. БЭМ не умер и никуда не денется, даже после появления CSS Modules с его неймспейсами. Он позволяет писать css стили модульно и в этом его преимущество, особенно для внедрения в различные стеки по типу Angular 2 или React based архитектуры. Особенно учитывая, что все двигается в сторону компонентов, то ему заведомо приказано долго жить.1 point
-
Ну собственно твоя реализация ничем не отличается от классического БЭМ. Вопрос вкуса как писать модификаторы, через 2 подчёркивания или через одно. Например в твоей реализации элементов придётся в CSS использовать camel case, про который тоже куча народу скажет своё "фи". Так что вкусовщина всё это. Почему в яндексе твой подход не прокатил бы я уже написал собственно.1 point
-
Да это плохо. Потому что прочитать и понять один-два коротких класса проще чем три длинных, которые в себе тянут 90% повторяющейся инфы. В следствии я вынужден еще сидеть и разбирать глазами этот лишний хлам, чтобы найти где там модификатор, где состояние и какие. У меня как раз лапши и не получается, в html подобный пример я бы записал максимум так: <div class="block-elem_mod state">Some Text</div> Ничего лишнего, сразу понятно, что за элемент, к какому блоку относится, какую модификацию имеет, в каком состоянии находится. .block { padding: 10px; } .block-elem, .block-elem_mod { margin: 5px; } .block-elem_mod { color: #800; } .block-elem.state { background: #080; } Так примерно css будет выглядеть. .block-elem_mod стакается вместе с базовым block-elem, т.к. расширяется от него, дальше уточняется индивидуально mod версия. .state может быть как глобальным, так и индивидуальным, как в данном случае. Все вполне понятно. На sass это вообще будет выглядить цельным блоком .block { $root: &; padding: 10px; &-elem { margin: 5px; &_mod { @extend #{$root}-elem; color: #800; } &.state { background: #080; } } } На чем писать это уже выбор каждого (конечно на css) Ну это уже отдельная проблема, всяких препроцессоро-дротеров, которые не думают над тем, что получается на выходе. Тогда да, налепят такого, что никаких концов не найти1 point
-
Просто т.к. они предлагают делать (ну по крайней мере такое точно было раньше), если тебе нужно создать модифицированный элемент с каким-то состоянием, то в html это выливалось в нечто такое: <div class="block-name__elem-name block-name__elem-name_mod-name block-name__elem-name_mod-state">Some Text</div> То есть прописать целых три класса вместо одного. В итоге на выходе в коде получается какая-то нечитабельная помойка1 point
-
На самом деле нет. Просто надо уметь его готовить Товарищи из яндекса писали, что у них иногда по дню уходило просто на проектирование разметки (без написания кода). Т.е. наверное треть времени уходит на то чтобы просто понять как разбить макет на БЭМ-элементы. Опять же БЭМ идеально подходит только для больших порталов где есть 100500 повторяющихся элементов с разными скинами. Для лендингов БЕМ бесполезен чуть менее чем полностью.1 point
-
Согласен. Я сам люблю css таким какой он есть. SASS начал использовать больше для внесения разнообразия в работу) Я его использую минимально, только то что позволяет компоновать селекторы в стак, где нужно. Мне об этом просто думать не нужно и тратить на это время. А большую часть того что предлагается, я не использую, по факту, просто нет надобности. только добавил свалки в html xD1 point
-
Я согласен с этим утверждением на все сто. БЭМ дал главное - научил людей избавляться от мусорной свалки в CSS. А ещё я считаю, что все эти ваши SASS, SCSS и т.п. вместе с переменными должны умереть. Как только везде заюзают Web Components всё это станет ненужно. Вот тогда и наступит верстальческий рай1 point
-
Ну открой соглашение по именованию в БЭМ и посмотри... Модификатор элемента: .block-name__elem-name_mod-name Неосмысленное именование. Это пример вообще-то ничего не мешает, в БЭМ так и есть да. Но это неудобно. И потом, я не собираюсь навязывать что-то, все равно это неблагодарное занятие и никому не интересно. Используйте что хотите, мне то какое дело, в общем-то @nerv задал мне вопрос, я ответил как я к этому подхожу. Все.1 point
-
Делаем сапёра на движке Phaser. Часть 1: подготовка графики. Вообще говоря любая игра обязана начинаться с тетрадки. Описывая игру в тетрадке вы понимаете чего вы хотите добиться от игры и набрасываете план действий. Все мастодонты инди-игр советуют начинать именно с этого шага и я бы не стал им пренебрегать. С другой стороны когда мы делаем клон игры (особенно такой простой как сапёр), можно смело пропускать "этап тетрадки" и сразу начать придумывать внешний вид. Ведь это гораздо интересней Как я и обещал, я постараюсь описывать создание игры в мельчайших подробностях. Поэтому первым делом расчехляйте свои фотошопы или гимпы. Предупреждаю сразу, что я пользуюсь фотошопом версии CC 2014, так что у кого гимп тем придётся разбираться самим как там всё устроено. Шаг 1. Открываем фотошоп и создаём новый файл, единицы измерения пиксели, размер 640х1136: Почему такой размер? Потому что я решил делать мобильную игру. На своём опыте я выяснил, что самое "безопасное" разрешение для большинства смартфонов - это разрешение iPhone 5. Теперь откройте браузер Google Chrome и откройте средства разработчика (F12 если вы на Windows). Внизу вы увидите иконку смартфона с подсказкой Toggle device mode, после того кк вы кликните на неё экран уменьшится до размеров устройства, которое можно выбрать в выпадающем меню сверху, вам нужно найти в этом меню опцию Apple iPhone 5: Но ведь там 320х568? Да, но рядом вы можете заметить цифру 2, которая означает плотность пикселей выбранного устройтсва. Если бы мы сделали макет размером 320х568, то на телефоне все картинки выглядели бы размытыми. Чтобы добиться чёткости на необходимо создать макет в два раза больше по размерам: 640 (320 * 2) на 1136 (568 * 2). Шаг 2. Я верстальщик, поэтому я всей душой ненавижу абсолютно всех дизайнеров с которыми мне доводилось работать. Всё из-за того, что ни один из тех с кем мне довелось работать за свою десятилетнюю карьеру понятия не имел о сетках (а тем более о модульных сетках). Давайте не будем уподобляться плохим дизайнерам и сделаем всё красиво. Если вы хотите подробней узнать о том, что такое сетки (grids), то посмотрите эту презентацию. Я начинал своё знакомство с сетками именно с неё. Как выясняется, многие не знают, что такое модульные сетки (modular grids), и путают их с обычными сетками. Это разные вещи. Совсем недавно я нашел великолепную статью о модульных сетках. Так же могу порекомендовать вам замечательный конструктор модульных сеток. Сразу хочу сказать, что модульные сетки лучше всего подходят для мобильных сайтов и приложений. Мы с вами не будем углубляться в дебри проектирования интерфейсов, однако мы почерпнём одну очень важную для нашего проекта деталь из гайдлайнов самого гугла. А именно минимальный размер модуля - 8 пикселей. Оказывается, что разрешение которое мы выбрали (640х1136) идеально делится на 8 (что по ширине, что по высоте). Поэтому откройте в фотошопе ваш файл и зайдите в настройки сетки: в верхнем меню выберите Edit > Preferences > Guides, Grids & Slices... Затем, в меню сверху выберите View > Show > Grid (или нажмите CTRL+' если у вас Windows), теперь ваш документ будет выглядеть так: Теперь вам надо найти центр вашего макета. Наврядли кто-то из сдесь присутсвующих макеты делает, но если что, то я рекомендую это делать всегда. Для этого вам надо убедиться что у вас включено отображение линеек (rulers, это те которые сверху и справа у окна макета). Выберите в меню сверху View > Rulers (или просто нажмите CTRL+R если вы на Windows). Убедитесь, что единицы измерения у вас выставлены как пиксели (по умолчанию там сантиметры вроде), для этого кликните правой кнопкой мышки по любой линейке и выберите в выпадающем меню пиксели. Затем убедитесь, что у вас включена привязка (snap), для этого в вехнем меню выберите View > Snap To > Document Bounds. После всех настроек можете смело начинать находить центр макета. Для этого кликните левой кнопкой мыши по верхней линейке и, не отпуская кнопки, потяните мышку вниз. У вас должен появиться "гайдлайн": Если вы будете тянуть его достаточно медленно, то увидите как он "прилипнет" к середине макета (сработала привязка). Проделайте ту же операцию с левой линейкой. Ваш докумен теперь должен выглядеть так: Самое главное в сапёре - клетки. В тех же гайдлайнах гугла сказано, что идеальный размер иконки на смартфоне 48х48 пикселей. Почему, спросите вы? Гугл объясняет это очень просто - такую площадь занимает пятно контакта указательного пальца у среднестатистического пользователя. "Умно!", - подумал я когда впервые об этом прочитал. Так почему бы и нам не сделать размер клетки 48х48? Создайте новый слой, выбрав в верхнем меню Layer > New > Layer..., или просто кликнув по иконке на палитре слоёв: Затем переименуйте новый слой в "Helper Cells". Далее выберите инструметн прямоугольного выделения (Rectangular Marquee Tool - шорткат M). Чтобы выделение получилось квадратным, а не прямоугольным, зажмите Shift перед тем как тянуть. Выделите в любом месте экрана прямоугольник размером 48х48 пикселей. Если у вас до сих пор активен инструмент прямоугольного выделения, то вы сможете перетаскивать выделение. Перетащите его в центр макета. В форошопе CC 2014 гайдлайны при этом должны стать фиолетовыми (так вы поймёте, что попали в центр). После этого залейте выделение любым цветом (я залил чёрным): После этого я начал экспериментировать с количеством клеток. Экспериментальным путём я выяснил, что красиво получается если добавить ещё по 4 клетки справа и слева. Но только не вплотную, а с отступом в один модуль (мы помним, что размер модуля у нас минимален - 8px). Абсолютно через такие же эксперименты, я надобавлял клеток сверху и снизу: Вот и готово наше игровое поле Нужно убегать домой. Продолжение следует (после праздников). Надеюсь я вас заинтриговал. Для ленивых.1 point
-
Шаг 5. Чтобы написать код открытия клеток нам придётся провести небольшой рефакторинг. Для начала я лоханулся со спрайтом клеток, наверное вы уже давно поняли что не так Так что первым делом забираем новый спрайт: Вторым делом перепишем метод .placeNumbers(), нам нужно отделить от него метод поиска соседних клеток, так как он понадобится нам при открытии клеток (как водится, часть кода я опущу): var minesweeper = function(game) {}; minesweeper.prototype = { // ... // теперь соседние клетки ищет отдельный метод getNeighbours: function(cell) { var i = cell.index; var u = i - this.game.boardWidth; var d = i + this.game.boardWidth; var l = i - 1; var r = i + 1; var ul = u - 1; var ur = u + 1; var dl = d - 1; var dr = d + 1; // в этот массив будем собирать найденных соседей клетки var indexArr = []; if (cell.posX > 0) { indexArr.push(l); if (cell.posY > 0) { indexArr.push(ul); } if (cell.posY < this.game.boardHeight - 1) { indexArr.push(dl); } } if (cell.posX < this.game.boardWidth - 1) { indexArr.push(r); if (cell.posY > 0) { indexArr.push(ur); } if (cell.posY < this.game.boardHeight - 1) { indexArr.push(dr); } } if (cell.posY > 0) { indexArr.push(u); } if (cell.posY < this.game.boardHeight - 1) { indexArr.push(d); } // возвращаем соседей return indexArr; }, // метод расстановки цифер теперь совсем маленький placeNumbers: function(cell) { var neighbours = this.getNeighbours(cell); for (var i = 0; i < neighbours.length; i++) { this.addNumberToCell(neighbours[i]); } }, // ... }; Не забудьте сделать цифры невидимыми, для этого внутри .addNumberToCell() уберите строку cell.frame. Далее нам нужно немного изменить метод расстановки клеток. А именно добавить клеткам новые свойства: флаг, обозначающий, что наш рекурсивный алгоритм уже проверял данную клетку на наличие мины, а так же обработчик кликов. Приступим: var minesweeper = function(game) {}; minesweeper.prototype = { // ... placeCells: function() { var len = this.game.boardWidth * this.game.boardHeight; var x = 0; var y = 0; var cell = null; for (var i = 0; i < len; i++) { x = i % this.game.boardWidth; y = Math.floor(i / this.game.boardWidth); cell = this.game.add.sprite(x * 56, y * 56, 'cells', 10); cell.posX = x; cell.posY = y; cell.isBomb = false; cell.numBombs = 0; cell.index = i; // отмечаем клетку непроверенной cell.isChecked = false; // разрешаем клетке обрабатывать события // (без этого флага Phaser события не обрабатывает) cell.inputEnabled = true; // навешиваем обработчик клика на клетку cell.events.onInputDown.add(this.onClick, this); this.game.board.add(cell); this.game.board.addToHash(cell); } }, // обработчик клика по клетке onClick: function(cell) { // если мы кликнули не по мине if (!cell.isBomb) { // то открываем клетку this.openCell(cell); } }, // рекурсивный поиск пустых клеток openCell: function(cell) { // соседи текущей клетки var neighbours = []; // текущий сосед var neighbour = null; // если в клетке мина или мы уже проверяли такую клетку if (cell.numBombs > 0 || cell.isChecked) { // открываем её цифру cell.frame = cell.numBombs - 1; // и выходим из рекурсии return; } // отмечаем клетку как проверенную cell.isChecked = true; // открываем клетку (11 кадр - значит пустая, без цифр) cell.frame = 11; // получаем соседние клетки neighbours = this.getNeighbours(cell); // для каждого соседа for (var i = 0; i < neighbours.length; i++) { // находим его в группе neighbour = this.game.board.hash[neighbours[i]]; // если там нет мины if (!neighbour.isBomb) { // открываем его цифру neighbour.frame = neighbour.numBombs - 1; // и рекурсивно вызываем открытие его соседей this.openCell(neighbour); } } }, // ... }; Алгоритм, используемый при открытии клеток, называется Flood Fill. Я использовал связанность по 8 направлениям, но подозреваю, что хватит и 4-х. Однако нам ни к чему делать лишние усложнения в методе .getNeighbours(), всё-таки наша игра слишком проста, чтобы такие оптимизации как-то сильно повлияли на производительность. Вот что у меня получилось (кликнул в верхний левый угол и в нижний правый): Ссылка на архив с игрой.1 point
-
Работа с чужим кодом часто - боль. Тут ничего не поделаешь. Да в такой ситуации вынужденно приходится либо подстраиваться под стиль того, что уже написано, если там стиль хоть какой-то есть, либо создавать нечто вроде песочницы их кода и отстраняться от того что есть в принципе.1 point
-
а я вот наоборот взял в привычку добавлять свой префикс ко всем классам, надоело бороться с кодом других людей которые присваивают элементам классы с простыми именами типо "price" и задают им стили глобально, а не в предела модуля. переписывать их код без варианта ибо теряешь возможность безболезненного обновления, а переопределять это адовая фигня, особенно когда классы присваиваются через js.1 point
-
Я отделил тему про БЭМ, а то к теме что читают форумчане, это как-то не очень относится. Скажем так, модульность появляется в любой методологии, сама собой. Просто потому, что это логично и естественно. Ну правда. Тот же БЭМ элементарно разбивается на модули, более того они прямо к этому призывают. Другой вопрос в самом подходе организации имен селекторов для компонентов. Хоть они упорно пытаются навязать, что это прям не так уж важно, и писать нечто подобное .block--element__mod - это норм, хотя это нифига не норм. Ну да черт с ним с самим синтакисом, это не так принципиально, при том, что по тому же БЭМ можно прикрутить и менее адовое именование. Да в статье правильно написано все, вцелом. Потому что в БЭМ и правда слишком много жести, надо быть гибче. Но в статье не описан один важный момент, как и во всех про БЭМ. Не нужно в понятие модификатора пихать все подряд. Я искренне считаю, что это слишком очевидно. По БЭМ модификатор это чуть ли не все, начиная от просто чуть другого цвета фона в элементе, заканчивая какими-то опциональными вещами, вроде отображения элемента. А всего-то нужно разделить понятия модификатора на модификатор (mod) и состояине (state). Так жить становится на много проще. Это что касается моего отношения к БЭМ. Что касается меня, то у меня тут свой подход в организации css. Кстати, похож на OPoR. Но свой вариант я вывел намного раньше чем узнал об OPoR, честно У меня есть следующие понятия: block, element, mod, state и js-* Никаких лишних префиксов не нужно. Использую только .js-*, для навешивания js функционала, но это как отдельный слой. В частности тот же SASS позволяет избавиться от необходимости писать в HTML всякого рода дублирования вроде class="block-element block-element_mod", а просто class="block-element_mod". Потому что всей компановкой занимается SASS и мне не нужно об этом задумываться вообще. В SASS это выглядет примерно так: .block { $root: &; some: css; &-element { some: css; &_mod { @extend #{$root}-element; some: css; } &.state { somestate: css; } } } Все это отдельный самодостаточный блок, компонент, модуль, как угодно вообще. Хочешь пихай его в один общий файл стилей, хочешь сохраняй в отдельные файлики и подключай вместе с соответсвующим куском темплейта. То же самое можно организовать и с чистым CSS, просто будет слишком много ручной ненужной работы, не более. Относительно состояний в моем понимании могут быть глобальные состояния, вроде .hide, .show и тому подобное, и некоторые индивидуальные специфические для какого-то рода компонентов. Тогда они описываются внутри этого компонента, как показано выше. Так же в статье есть речь о том, что нужно под каждый компонент выделять свою ноду в html. В общем я с этим согласен. Кроме ситуаций с иконками. К ним я отношусь по разному, в большинстве случаев, я стараюсь вешать их на ПЭ, а не как отдельную ноду. Ну если уж другого выхода нет, то тогда это отдельная нода, хотя меня это невероятно бесит Для иконок у меня есть отдельный компонент, например .icon. И чтобы добавить к чему-то иконку просто достаточно добавить .icon_iconame <div class="block-element icon_iconame">Some Text</div> Но так как в самом .icon описано самое базовое поведение конки, можно выполнить уточнения в следсивии слияния двух компонент. .block { $root: &; some: css; &-element { some: css; &_mod { @extend #{$root}-element; some: css; } &.state { somestate: css; } &[class^="icon_"], &[class*=" icon_"] { somechange: forelem; &:before { somechange: foricons; change: size; some: color; } } } } Да это несколько нарушает понятие инкапсуляции, но все таки: "Сейчас к людям надо помягше. А на вопросы смотреть ширше." Вот в общем-то мои мысли по поводу БЭМ, модульности и вообще подходу к css. Для меня это работает, вероятно для кого-то нет, по каким-то причинам. Но тут уж я ничего поделать не могу1 point
-
Шаг 3. Теперь напишем расстановку мин. Для начала расставим 5 мин. Итак внутри minesweeper.js пишем: var minesweeper = function(game) {}; minesweeper.prototype = { create: function() { console.log('%cSTATE::GAME', 'color: #fff; background: #f0f;'); // вернул нормальные размеры игровому полю this.game.boardWidth = 9; this.game.boardHeight = 11; this.game.board = this.game.add.group(); // сдвигаем группу клеток так, чтобы они были как в макете (по центру) this.game.board.x = 72; this.game.board.y = 264; // максимальное количество мин (для начала 5 штук) this.game.maxBombs = 5; this.placeCells(); // после генерации поля расставляем мины this.placeBombs(); }, placeCells: function() { var len = this.game.boardWidth * this.game.boardHeight; var x = 0; var y = 0; var cell = null; for (var i = 0; i < len; i++) { x = i % this.game.boardWidth; y = Math.floor(i / this.game.boardWidth); cell = this.game.add.sprite(x * 56, y * 56, 'cells', 10); cell.posX = x; cell.posY = y; // есть ли в этой клетке мина cell.isBomb = false; // количество мин вокруг клетки cell.numBombs = 0; // запоминаем индекс клетки в массиве cell.index = i; this.game.board.add(cell); // добавляем клетку в хеш (массив), // для того чтобы мы могли в любой момент найти её по индексу this.game.board.addТоHash(cell); } }, placeBombs: function() { // сколько мин нам ещё осталось установить var bombsToPlace = this.game.maxBombs; var cell = null; do { // получаем рандомную клетку // .getRandom() - стандартный метод для любой группы в Phaser cell = this.game.board.getRandom(); // если в ней ещё нету мины if (cell.isBomb === false) { // то ставим спрайту 9-ый кадр (картинка с миной, для наглядности) cell.frame = 9; // и отмечаем, что в данной клетке бомба cell.isBomb = true; // нам осталось расставить на одну мину меньше bombsToPlace--; } } while (bombsToPlace > 0); // повтояем цикл до тех пор пока все мины не будут расставлены } }; Вот что получилось у меня в итоге: Шаг 4. Теперь первая нетривиальная часть игры - расстановка цифр вокруг мин. В своём старинном уроке я пошел немного неправильным путём. Я циклом обходил все клетки и смотрел есть ли вокруг выбранной клетки мины, если рядом с ней есь мина, то я прибавлял к клетке единичку. Немного поразмыслив, я догадался, что гораздо быстрей будет обойти все мины, ведь их сильно меньше чем пустых клеток. В нашем тестовом случае всего 5 штук. Теперь у меня осталась еще одна тема для размышлений: как в одномерном массиве получить соседние клетки? Давайте для начала вернёмся к полю 3х3. В нашей игре оно представляет собой массив, который упрощённо выглядит так: var cells = [0, 1, 2, 3, 4, 5, 6, 7, 8]; Давайте перепишем его немного, чтобы он стал похож на игровое поле: var cells = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; Допустим нам надо получить соседей, для клетки с индексом 4. Ну с соседями слева и справа всё просто: достаточно просто вычесть/прибавить единицу, чтобы получить соседа слева/справа. А вот с соседями сверху и снизу я сперва никак не мог догнать что делать. Подозреваю, что многие из вас не такие слоупоки как я, и уже давно догадались, что надо делать. Для тех же кто похож на меня поясню: нужно всего-лишь вычесть или прибавить к индексу клетки ширину нашего поля (в данном случае 3). Ну а с диагоналями всё снова становится элементарно. Итак распишу: клетка - 4 лево - 4 - 1 = 3 право - 4 + 1 = 5 верх - 4 - 3 = 1 низ - 4 + 3 = 7 лево верх - верх - 1 = 0 право верх - верх + 1 = 2 лево низ - низ - 1 = 6 право низ - низ + 1 = 8 Элементарная математика. Ну что ж, приступим к написанию кода: var minesweeper = function(game) {}; minesweeper.prototype = { // ... placeBombs: function() { var bombsToPlace = this.game.maxBombs; var cell = null; do { cell = this.game.board.getRandom(); if (cell.isBomb === false) { cell.frame = 9; cell.isBomb = true; // как только установили мину // сразу расставляем циферки вокруг неё this.placeNumbers(cell); bombsToPlace--; } } while (bombsToPlace > 0); }, placeNumbers: function(cell) { var i = cell.index; // верх var u = i - this.game.boardWidth; // низ var d = i + this.game.boardWidth; // лево var l = i - 1; // право var r = i + 1; // верх лево var ul = u - 1; // верх право var ur = u + 1; // низ лево var dl = d - 1; // низ право var dr = d + 1; // влево мы можем идти только если клетка не у края поля if (cell.posX > 0) { this.addNumberToCell(l); // добавляем цифру в клетку // вверх влево можно идти только если сверху от клетки что-то есть if (cell.posY > 0) { this.addNumberToCell(ul); } // вниз можно только если там есть место if (cell.posY < this.game.boardHeight - 1) { this.addNumberToCell(dl); } } if (cell.posX < this.game.boardWidth - 1) { this.addNumberToCell(r); if (cell.posY > 0) { this.addNumberToCell(ur); } if (cell.posY < this.game.boardHeight - 1) { this.addNumberToCell(dr); } } if (cell.posY > 0) { this.addNumberToCell(u); } if (cell.posY < this.game.boardHeight - 1) { this.addNumberToCell(d); } }, addNumberToCell: function(i) { var cell = null; cell = this.game.board.hash[i]; // добавляем цифру только туда где нет мины if (cell.isBomb === false) { cell.numBombs++; // для наглядности делаем цифры видимыми cell.frame = cell.numBombs - 1; } } }; Вот итог наших раздумий: А тут я увеличил количество мин:1 point
-
Часть 3. Генерация игрового поля и игра. Шаг 1. Перед тем как рассказать про генерацию игрового поля (или игровой доски) необходимо рассказать об одном важнейшем элементе движка Phaser - группах. Группы - это мощнейший инструмент управления массивами объектов. Если ваша игра основана на тайлах, если вам нужны несколько объектов с одинаковым поведением, то вы скорее всего не сможете обойтись без групп. Вот некоторые возможности групп: 1) позиционирование нескольких объектов одновременно 2) сортировка по определённому пользователем признаку 3) фильтрация (выборка) элементов по определённому признаку 4) итерация по элементам группы 5) навешивание обработчиков событий всем элементам группы сразу 6) возможность вкладывать группы в группы Чтобы создать группу достаточно вызвать метод game.add.group(). Шаг 2. Переходим к генерации игрового поля. Для начала начнём с простого поля 3х3 клетки. Как правило его представляют так: var gameBoard = [ [0, 0, 0], [0, 0, 0], [0, 0, 0] ]; Однако внутри группы у нас элементы располагаются в одномерном массиве, т.е. в нашем случае поле будет выглядеть так: var gameBoard = [0, 0, 0, 0, 0, 0, 0, 0, 0]; Мы можем пойти двумя путями: 1) создать два массива: первый группа клеток для отображения в игре, второй - чисто для хранения данных 2) превратить одномерный массив в двумерный В качестве тренировки, я пойду вторым путём. Для нашей маленькой игры это не критично, но заодно мы получим прирост в производительности, т.к. итерация по одномерному массиву быстрее, чем итерация по двуперному (ибо вложенные циклы - плохо). Как же нам превратить одномерный массив в двумерный? А вот взгляните на картинку: Надеюсь идея понятна? Давайте применим её на практике, откройте minesweeper.js: var minesweeper = function(game) {}; minesweeper.prototype = { create: function() { console.log('%cSTATE::GAME', 'color: #fff; background: #f0f;'); // размер поля для начала 3х3 // переменные будут доступны из любого места this.game.boardWidth = 3; this.game.boardHeight = 3; // создаём группу, для хранения клеток this.game.board = this.game.add.group(); // поле будем создавать внутри отдельной функции this.placeCells(); }, // конвертируем индекс массива в координату X indexToX: function(i) { return i % this.game.boardWidth; }, // конвертируем индекс массива в координату Y indexToY: function(i) { return Math.floor(i / this.game.boardWidth); }, placeCells: function() { // сколько всего клеток (площадь игровой доски) var len = this.game.boardWidth * this.game.boardHeight; var x = 0; var y = 0; var cell = null; for (var i = 0; i < len; i++) { x = this.indexToX(i); y = this.indexToY(i); // создаём клетку игрового поля // последний параметр - это номер кадра у спрайта (10 кадр - это закрытая клетка) cell = this.game.add.sprite(x * 56, y * 56, 'cells', 10); // запоминаем координаты клетки (чтобы не рассчитывать их каждый раз) cell.posX = x; cell.posY = y; // и добавляем клетку в группу this.game.board.add(cell); } } }; Вот и готово наше игровое поле:1 point
-
Шаг 7. Теперь давайте настроим масштабирование. Для нужд масштабирования в движке Phaser есть специальный класс ScaleManager. Настраивать его нужно внутри состояния boot.js для того, чтобы на этапе загрузки (preload.js) ресурсов игра уже имела необходимый масштаб. Итак открываем файл boot.js: var boot = function(game) {}; boot.prototype = { preload: function() { }, create: function() { console.log('%cSTATE::BOOT', 'color: #fff; background: #f00;'); this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; // включаем нужный режим мастабирования this.scale.pageAlignHorizontally = true; // выравнивание канваса по центру по горизонтали this.scale.pageAlignVertically = true; // ... и по вертикали this.game.state.start('Preload'); } }; Существуют следующие режимы масштабирования: 1) Phaser.ScaleManager.EXACT_FIT - канвас растягивается на 100% по ширине и высоте (контент внутри канваса пр этом деформируется) 2) Phaser.ScaleManager.NO_SCALE - отключает масштабирование 3) Phaser.ScaleManager.RESIZE - канвас растягивается на 100% по ширине и высоте (контент при этом не деформируется, но всё, что не влезает в экран обрезается) 4) Phaser.ScaleManager.SHOW_ALL - аналог background-size: contain; из CSS, т.е. канвас масштабируется с сохранением соотношения сторон и при этом не обрезается 5) Phaser.ScaleManager.USER_SCALE - канвас масштабируется согласно правилам, заданным в методе setUserScale Очевидно, что самым простым и подходящем методом масштабирования для нас будет SHOW_ALL. Шаг 8. Теперь можно подумать о шрифтах. Давайте посмотрим на интерфейс классического сапёра: В своём проекте я решил использовать шрифт, которым написаны красные цифры. Поискав в интернетах, я нашел вот такой бесплатный шрифт. Там 4 варианта начертания, я выбрал последний: DS-DIGIB.ttf (жирный без курсива). Зайдя на страницу с примерами, можно увидеть раздел про то как Phaser работает с текстом. Самым удобным для подключения шрифта мне показался вариант с подключением пиксельного шрифта (Bitmap Font). Для генерации пиксельного шрифта существуют специализированные программы для Windows и MacOS, но есть и совершенно бесплатный онлайн-генератор Littera (к слову, рекомендованный к использованию разработчиками фазера). Используя генератор Littera, я сгенерировал шрифт. Вы можете поэкспериментировать с генерацией самостоятельно или просто забрать готовый архив, который я для вас сделал. А вот такие настройки я использовал: В папке assets создайте папку font и распакуйте туда архив со шрифтом. Шаг 9. Теперь можно начинать делать экран загрузки, для которого я сделал супер крутой макет: 10 минусов снизу - это наш индикатор загрузки. Он будет заполняться по мере загрузки ресурсов. Чтобы вы не заморачивались, я приложу для вас готовую картинку: После того как скачаете её, не забудьте переименовать картинку в loading_bar.png и переложить её в папку assets/img. Затем снова откройте boot.js: var boot = function(game) {}; boot.prototype = { preload: function() { // загружаем картинку полосы загрузки this.game.load.image('loading', 'assets/img/loading_bar.png'); // и шрифт для надписи LOADING this.game.load.bitmapFont('ds_digital', 'assets/font/ds_digital.png', 'assets/font/ds_digital.fnt'); }, create: function() { console.log('%cSTATE::BOOT', 'color: #fff; background: #f00;'); this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; this.scale.pageAlignHorizontally = true; this.scale.pageAlignVertically = true; this.game.state.start('Preload'); } }; Затем откройте файл preload.js: var preload = function(game) {}; preload.prototype = { preload: function() { // Вставляем спрайт полоски загрузки var loadingBar = this.game.add.sprite(154, 588, 'loading'); loadingBar.anchor.setTo(0, 0.5); // говорим фазеру, какую картинку использовать для отображения процесса загрузки this.game.load.setPreloadSprite(loadingBar); // Вставляем текст LOADING // обратите внимание на то что написан он в нижнем регистре // ибо буквы для верхнего регистра я не генерил (в шрифте его всё равно нет), // а так спрайт шрифта весит существенно меньше var loadingText = this.game.add.bitmapText(320, 525, 'ds_digital', 'loading', 72); loadingText.anchor.setTo(0.5, 0); // Я навставлял рандомных картинок из гугла (большие панорамные изображения) // чтобы процесс загрузки был виден, иначе вы бы просто не увидели экран загрузки // т.к. все наши ресурсы весят всего пару килобайт this.game.load.image('test_0', 'https://upload.wikimedia.org/wikipedia/commons/c/cd/View_from_connors_hill_panorama.jpg'); this.game.load.image('test_1', 'http://photoblogstop.com/wp-content/uploads/2012/07/Sierra_HDR_Panorama_DFX8048_2280x819_Q40_wm_mini.jpg'); this.game.load.image('test_2', 'http://www.larkinweb.co.uk/panoramas/lake_placid/Lake_Placid_south_medium_res_panorama.jpg'); this.game.load.image('test_3', 'http://peraalto.se/wp-content/uploads/2013/03/PanoramaRaps.jpg'); this.game.load.image('test_4', 'http://parkerlab.bio.uci.edu/pictures/photography%20pictures/2008_12_19_select/Untitled_Panorama1.jpg'); // Загружаем ресурсы игры this.game.load.spritesheet('cells', 'assets/img/cells.png', 56, 56, 12); }, create: function() { console.log('%cSTATE::PRELOAD', 'color: #fff; background: #0f0;'); this.game.state.start('Game'); } }; .anchor - это якорь спрайта (текста), т.е. это точка относительно которой будет позиционироваться спрайт (или текст). Что такое якорь наглядней всего показано в этой демонстрации - спрайт позиционируется относительно зелёной точки, которая расположена в координатах 300, 300. Если вы теперь обновите страницу, то вы увидите сперва надпись LOADING, а затем наше игровое поле, состоящее пока из одной клетки.1 point
-
Шаг 6. Давайте попробуем загрузить в нашу игру спрайт. Откройте файл preload.js и напишите код загрузки спрайта: var preload = function(game) {}; preload.prototype = { preload: function() { // Setup loading bar // ... // Load all game assets this.game.load.spritesheet('cells', 'assets/img/cells.png', 56, 56, 12); }, create: function() { console.log('%cSTATE::PRELOAD', 'color: #fff; background: #0f0;'); this.game.state.start('Game'); } }; Параметры this.game.load.spritesheet: 1) ключ - идентификатор спрайта, по нему игра будет получать доступ к спрайту 2) url - путь до ресурса 3) ширина кадра 4) высота кадра 5) количество кадров - если вы помните у нас 12 разновидностей клеток, этот параметр необязательный, если его не указать, то фазер сам попытается расчитать количество кадров в спрайте Теперь надо добавить загруженный спрайт в игру. Для этого откройте основной скрипт игры - minesweeper.js и напишите код добавления спрайта: var minesweeper = function(game) {}; minesweeper.prototype = { create: function() { console.log('%cSTATE::GAME', 'color: #fff; background: #f0f;'); // добавим спрайт 'cells' в координаты 0, 0 (верхний левый угол) this.game.add.sprite(0, 0, 'cells'); } }; Если вы, как и я, попробуете открыть файл index.html просто с харда, то вас ожидает сюрприз: ожидаемой картинки вы не увидите, зато вместо этого в консоли выскочит ошибка: Дело в том, что политика безопасности хрома не позволяет грузить ресурсы с урла вида file:///. Чтобы избавиться от этой ошибки вам понадобится web-сервер. Этим сервером может быть WAMP, Endels (Духовный наследник Denwer) или что-то иное. Но я пошел самым простым путём: установил себе Node.js. После того как инсталляция завершилась, я нажал на клавиатуре комбинацию Win+R и в появившемся окне ввёл cmd, после нажатия Enter у меня открылась командная строка где я ввёл следующую команду: npm install http-server -g -g означает, что пакет http-server будет установлен глобально, т.е. вам не придётся заходить в папку с программой чтобы запустить её, вместо этого программу можно будет запускать из любого места. После этого нужно переключиться на папку где у вас лежит ваш файл index.html, для этого в консоли пишем: cd Tutorial А теперь запускаем сервер, для этого в консоли пишем следующее: http-server -o --cors -o означает, что после запуска сервера автоматом откроется браузер --cors означает, что будет включен CORS Вот теперь вы увидите наш спрайт:1 point
-
Шаг 1. Давайте проверим работает ли в нашем браузере игра (я использую Chrome последней версии). Для этого нам нужно вызвать конструктор Phaser.Game (часть кода я опущу для краткости): <!-- ... --> <body> <script> (function() { var game = new Phaser.Game(640, 1136, Phaser.AUTO); })(); // функция вызывает сама себя </script> </body> <!-- ... --> Параметры Phaser.Game: 1) ширина канваса 2) высота канваса 3) рендерер (можно не задавать). AUTO - означает, что фазер сам определит использовать WebGL или нет. После этого откройте консоль браузера (F12 на Windows) и проверьте, что у вас есть подобная строка: Если вы увидели на странице чёрный прямоугольник, а в консоли подобное сообщение и никаких ошибок, значит всё работает. Шаг 2. Теперь настало время поговорить про игровые состояния (States). Вообще мне кажется, что тут больше подходит термин сцена (который кстати используется в движке Unity 3D), пожалуй описание сцены больше подходит под термин "игрового состояния" в движке Phaser. Поэтому я приведу тут выдержку из документации Unity: Для управления игровыми состояниями в движке Phaser есть объект StateManager, к которому мы имеем доступ через game.state. Для каждого состояния есть предопределённый набор зарезервированных функций, которые выполняются в строго определённом порядке: 1) preload - вызывается саамой первой, в ней загружаются все ресурсы для вашей игры 2) loadUpdate - вызывается во время работы preload, нужна для обновления счётчика/ползунка загрузки 3) loadRender - нукжна для рендера счётчика/ползунка загрузки 4) create - вызывается как только отработает функция preload (обычно тут создаются все объекты необходимые на текущей сцене) 5) update - вызывается каждый кадр, в идеале 60 раз в секунду (собственно здесь работает вся механика игры) 6) render - вызывается после рендера в канвасе или WebGL, нужна для постэффектов или дебажной информации 7) resize - вызывается только если включен режим масштабирования и размеры окна просмотра меняются 8) shutdown - вызывается когда мы переключается на другое игровое состояние (на другую сцену) Для начала давайте сделаем три состояния: экран загрузки лоадера, сам лоадер и собственно игру. Для этого в папке assets/js создайте три файла: boot.js, preload.js и minesweeper.js: boot.js var boot = function(game) {}; boot.prototype = { preload: function() { // тут будем загружать каринку лоадера }, create: function() { // отладочная информация, если в консоли появилась эта надпись, // значит сцена создана успешно // %с - означает, что к инфе в консоли будут применены стили // которые передаются во втором параметре console.log('%cSTATE::BOOT', 'color: #fff; background: #f00;'); // запускаем следующую сцену (прелоадер) this.game.state.start('Preload'); } }; preload.js var preload = function(game) {}; preload.prototype = { preload: function() { // тут будем загружать спрайты для нашей игры // заодно тут мы инициалиализируем счётчи загрузки }, create: function() { // отладочная информация, если в консоли появилась эта надпись, // значит сцена создана успешно console.log('%cSTATE::PRELOAD', 'color: #fff; background: #0f0;'); // запускаем следующую сцену (игру) this.game.state.start('Game'); } }; minesweeper.js var minesweeper = function(game) {}; minesweeper.prototype = { // preload не нужна ибо загружать нам тут уже ничего не надо create: function() { // отладочная информация, если в консоли появилась эта надпись, // значит сцена создана успешно console.log('%cSTATE::GAME', 'color: #fff; background: #f0f;'); // тут будут создаваться объекты для игры (например игровое поле) } }; Затем подключаем созданные скрипты в наш index.html: <!-- ... --> <head> <meta charset="UTF-8" /> <title>Minesweeper</title> <style> body { margin: 0; } </style> <!-- Game Engine --> <script src="assets/js/phaser.min.js"></script> <!-- Game States --> <script src="assets/js/boot.js"></script> <script src="assets/js/preload.js"></script> <script src="assets/js/minesweeper.js"></script> </head> <body> <script> (function() { var game = new Phaser.Game(640, 1136, Phaser.AUTO); // добавляем в игру созданные сцены (игровые состояния) game.state.add('Boot', boot); game.state.add('Preload', preload); game.state.add('Game', minesweeper); // запускаем первое состояние game.state.start('Boot'); })(); // функция вызывает сама себя </script> </body> <!-- ... --> Параметры метода .add: 1) идентификатор состояния 2) объект состояния (они у нас в отдельных файлах скриптов) 3) автозапуск состояния (необязательный параметр, по умолчанию выключен), запускает состояние автоматом как только встречает его в коде Если вы всё сделали правильно, то в консоли вы увидите следующее: Поздравляю, наша игра работает1 point
-
Шаг 4. Теперь будем готовить спрайт для собственно игры. Создайте новый макет (CTRL+N) с размерами 672х56 пикселей. 56 потому, что это высота клетки, а 672 потому что вариаций клеток у нас 12 штрук, если считать пустую (56 * 12 = 672). Для удобства позиционирования измените размер грида до 56х56 (см. предыдущие шаги), жмём Edit > Preferences > Guides, Grids & Slices... и в поле Gridline Every вводим 56. Далее копируем все клетки по очереди, для удобства копирования можно на предыдущем макете слить слои в один (Layer > Merge Visible). Вот что получилось у меня: Мина, флажок и пустая клетка находятся в конце потому что так будет удобней раздавать индексы клеткам: единица будет означать едницу, двойка - двойку и т.п., индекс 12 будет означать пустую клетку. Часть 2: устанавливаем Phaser. Phaser - это игровой HTML5-движок, написанный на JS, который использует в качестве рендера другой движок Pixi. В принципе можно написать игру используя только Pixi, но поверьте, с фазером всё становится гораздо проще. Там где это возможно, Phaser использует для рендера WebGL, если браузер не поддерживает эту технологию, то используется тупо Canvas с 2D-контекстом. Тем, кто первый раз слышит про Phaser рекомендую пролистать примеры того, что на нём можно сделать. Самым большим минусом этого движка я считаю неудобную документацию, поиск по которой может быть очень затруднительным. Однако этот недостаток компенсируется отличным коммюнити (если вы понимаете английский конечно), мало того, на форуме частенько даёт ответы автор движка. Фазер из коробки имеет три физических движка: Arcade Physics, Ninja Physics и P2 Physics. Arcade Physics идеален для простых игр, если вам требуется обработка коллизий AABB, то это то что вам нужно. Ninja Physics идеален для платформеров и других игр, основанных на тайлах. P2 Physics - это уже полноценный опенсорсный физический движок, альтернатива известному Box2D, у него есть отдельный репозиторий на гитхабе, если вы решили делать клона Angry Birds, то это ваш выбор. Если вам этого показалось мало, то за деньги можно приобрести плагин, добавляющий поддержку Box2D. Для установки движка Phaser вам достаточно зайти на страницу закачки и либо клонировать проект с гитхаба, либо скачать архив, либо просто скачать файл .js (минифицированный или обычный). Я предпочитаю последний вариант, вы же делайте как привыкли. После того как вы закачали файл, подключаем его обычным способом: Моя структура файлов: Game ---- assets ----------- js ----------- img ---- index.html index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Minesweeper</title> <style> body { margin: 0; } </style> <!-- Game Engine --> <script src="assets/js/phaser.min.js"></script> </head> <body> </body> </html>1 point
-
Шаг 3. Теперь добавим цвета. Я не стал ничего изобретать, а просто зашел в поиск по картинкам гугла и вбил запрос "minesweeper number colors", первая же картинка мне идеально подошла. Открываем её в фотошопе и вырезаем клетку. Для этого выберите инструмент прямоугольного выделения (M), выделите клетку (ширина и высота выделения у меня получились 128х128) и скопируйте выделение (CTRL+C если у вас винда). Затем переключитесь назад на наш макет и вставьте скопированное (CTRL+V). Теперь макет должен выглядеть так: Очевидно, что клетка слишком большая, нам нужно её уменьшить. Для этого нажмите CTRL+T или выберите в меню Edit > Transform > Scale. Чтобы при уменьшении не потерялся пиксельный стиль клетки, в меню трансформации в выпадающем меню интерполяции выберите режим Nearest Neighbor. Уменьшите клетку до размера 56х56 пикселей и перетащите её в верхний левый угол нашего поля. Такой размер я выбрал потому, что в сапёре всё-таки нет зазоров между клетками и я тоже решил их не делать. После всех этих операций надобавляйте оставшихся клеток, копируя получившуюся, слейте полученные слои (Layer > Merge Down или CTRL+E) и переименуйте слой с клетками в "Cells": Теперь вернёмся к документу с картинкой из гугла и вырешем иконки флажка и цифр. Для этого воспользуемся инструментом Magic Wand (волшебная палочка - W). В настройках палочки снимите галку с чекбокса Anti-alias и установисе галку в чекбокс Contiguous: Иконки флажка и мины - двухцветные, поэтому кликать палочкой на них нужно зажав Shift, чтобы добавлять выделение к уже существующему. Выделите флажок и вырежте его на наше игровое поле (так же как клетку). После этого уменьшите его (CTRL+T). Чтобы сохранить пропорции нужно включить опцию Maintain aspect ratio. Я уменьшил иконку до сорока процентов от первоначального размера. Переместите иконку в левый верхний угол игрового поля. И проделайте всё тоже самое для остальных иконок. Теперь наш документ выглядит так: Ссылка на макет.1 point
This leaderboard is set to Kiev/GMT+03:00
-
Upcoming Events
No upcoming events found -
Сообщения форума
-
Актуальные контакты: Telegram: @Nikker_web E-Mail: tarasevich.email@gmail.com Портфолио https://www.behance.net/d4d4186e Разрабатываю дизайн групп в соц сетях, сайтов, приложений, другой дизайн под заказ
-
Актуальные контакты: Telegram: @Nikker_web E-Mail: tarasevich.email@gmail.com Разрабатываю дизайн групп в соц сетях, сайтов, приложений, другой дизайн под заказ Портфолио https://www.behance.net/d4d4186e
-
Доброго всем времени суток. Прошу помощи. Научите принципу изменения футера. Движок Xenforo. Версия 2.2.10. Стиль дефолтный. Что именно нужно в итоге на фото примере. Мой шаблон app.footer less имеет следующее значение. .p-footer { .xf-publicFooter(); a { .xf-publicFooterLink(); } } .p-footer-inner { .m-pageWidth(); .m-pageInset(); padding-top: @xf-paddingMedium; padding-bottom: @xf-paddingLarge; } .p-footer-row { .m-clearFix(); margin-bottom: -@xf-paddingLarge; } .p-footer-row-main { float: left; margin-bottom: @xf-paddingLarge; } .p-footer-row-opposite { float: right; margin-bottom: @xf-paddingLarge; } .p-footer-linkList { .m-listPlain(); .m-clearFix(); > li { float: left; margin-right: .5em; &:last-child { margin-right: 0; } a { padding: 2px 4px; border-radius: @xf-borderRadiusSmall; &:hover { text-decoration: none; background-color: fade(@xf-publicFooterLink--color, 10%); } } } } .p-footer-rssLink { > span { position: relative; top: -1px; display: inline-block; width: 1.44em; height: 1.44em; line-height: 1.44em; text-align: center; font-size: .8em; background-color: #4682B4; border-radius: 2px; } .fa-rss { color: white; } } .p-footer-copyright { margin-top: @xf-elementSpacer; text-align: center; font-size: @xf-fontSizeSmallest; } .p-footer-debug { margin-top: @xf-paddingLarge; text-align: right; font-size: @xf-fontSizeSmallest; .pairs > dt { color: inherit; } } @media (max-width: @xf-responsiveMedium) { .p-footer-row-main, .p-footer-row-opposite { float: none; } .p-footer-copyright { text-align: left; padding: 0 4px; // aligns with other links } }
-
Нужны сайты с примерами верстки, типа https://css-tricks.com/. Типовые приемы и нестандартные на все случаи жизни. Накидайте ссылок.
-
By Katerina23 · Posted
Да, подходит. Спасибо.
-