Статьи

Исследование в экспериментальной разработке игр

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

Основная идея

Основная идея заключалась в том, чтобы анимировать left и top позиции объекта, используя медленный переход, который игрок частично контролирует. Итак, нам понадобится игровая зона — назовем это доской и анимированным объектом — назовем это шаром :

 <body> <div id="board"> <span id="ball"></span> </div> </body> 

Доска имеет соотношение сторон 3: 2, в то время как шар составляет 5% от его ширины. Ни одно из этих значений не является особенно важным, они кажутся наиболее подходящими: соотношение сторон было выбрано таким образом, чтобы оно (в конечном итоге) могло поместиться на экране iPhone, а шарик стал относительно небольшим, поэтому в нем было достаточно места для перемещения. , Основная раскладка с мячом в верхнем левом углу доски показана в следующей демонстрации.

Мяч имеет отрицательные поля, чтобы сместить его на половину его собственной ширины и высоты, чтобы любая позиция, которую мы установили на шаре, была его центром в центре (например, шар в этой первой демонстрации был позиционирован в 0,0 ). Вот CSS для этой демонстрации:

 #board { position:relative; display:block; width:720px; height:480px; margin:24px auto 0 auto; border-radius:2px; background:#fff; box-shadow:0 0 16px -2px rgba(0,0,0, 0.5); } #ball { position:absolute; left:0; top:0; display:block; width:36px; height:36px; margin:-18px 0 0 -18px; border-radius:18px; background:#f00; box-shadow:inset 0 0 0 2px rgba(0,0,0, 0.35), 4px 10px 10px rgba(0,0,0, 0.15); } направо #board { position:relative; display:block; width:720px; height:480px; margin:24px auto 0 auto; border-radius:2px; background:#fff; box-shadow:0 0 16px -2px rgba(0,0,0, 0.5); } #ball { position:absolute; left:0; top:0; display:block; width:36px; height:36px; margin:-18px 0 0 -18px; border-radius:18px; background:#f00; box-shadow:inset 0 0 0 2px rgba(0,0,0, 0.35), 4px 10px 10px rgba(0,0,0, 0.15); } 

В идеале мы бы применяли размеры досок и шаров динамически, в зависимости от доступного окна или места на экране (это было бы важно для переноса игры на мобильные браузеры), но чтобы эти примеры были простыми, размеры были фиксированными — доска составляет 720 × 480 и мяч 36 × 36.

Диапазон возможного движения мяча теперь можно описать в процентных координатах — от 0%,0% в верхнем левом углу до 100%,100% в нижнем правом углу. Использование процентов проще, чем вычисление пикселей, и обеспечит гибкость размеров в будущем.

Теперь мы можем легко управлять позицией, применяя простой JavaScript, который устанавливает left или top позицию в соответствии с направленными нажатиями клавиш, т.е. если нажата стрелка влево, тогда установите style.left в значение "0" , или если стрелка вниз style.top затем установите style.top на "100%" :

 var ball = document.getElementById('ball'), positions = { 37 : ['left', '0'], 38 : ['top', '0'], 39 : ['left', '100%'], 40 : ['top', '100%'] }; document.addEventListener('keydown', function(e, data) { if(data = positions[e.keyCode]) { ball.style[data[0]] = data[1]; e.preventDefault(); } }, false); 

Массив positions определяет свойство и значение для каждого keyCode стрелки, а также используется в первом условии, чтобы узнать, была ли вообще нажата клавиша со стрелкой, и в этом случае мы должны использовать preventDefault() чтобы заблокировать его собственное действие (так что страница не может прокручиваться одновременно). Опять же, ради простоты, я не делал никаких функций для обнаружения старых браузеров. На практике мы хотели бы предварительно протестировать браузер, чтобы убедиться, что переходы полностью поддерживаются. Следующая демонстрация позволяет переместить мяч в любой угол.

Далее, давайте добавим правило медленного transition к анимации движений. Обратите внимание на включение префиксов поставщиков.

 #ball { -moz-transition:all 5s ease; -ms-transition:all 5s ease; -o-transition:all 5s ease; -webkit-transition:all 5s ease; transition:all 5s ease; } 

Теперь изменения клавиш со стрелками не вызывают мгновенного движения, они вызывают медленное и постепенное движение мяча по доске. И так как каждое нажатие клавиши изменяет только left или top позицию (никогда не обе), общий эффект — это новый и довольно элегантный вид движения — вид «эластичности», который будет гораздо сложнее для сценария:

Попробуйте, например, следующие действия в этой демонстрации:

  1. Обновите страницу, чтобы сбросить мяч
  2. Затем нажмите стрелку вправо один раз
  3. Подождите, пока мяч не достигнет середины (через 2,5 секунды)
  4. Затем нажмите стрелку вниз один раз

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

Уточнение игры

Теперь мы можем переместить мяч в любую точку доски, используя клавиши со стрелками, чтобы указать направление движения. Это обеспечивает контроль, но не полный контроль, и в этом заключается основная задача, которая создает игровую игру. Степень контроля, которую мы имеем, также варьируется в зависимости от способа применения переходов. Например, если при нажатии клавиши со стрелкой вправо мяч находится в положении "left:0" , то для достижения правого края потребуется пять секунд (как и ожидалось). Однако, если при нажатии клавиши со стрелкой вправо мяч уже находится в положении "left:80%" , ему все равно понадобится целых пять секунд, чтобы пройти гораздо меньшее расстояние до правого края. Другими словами, скорость мяча зависит от того, насколько близко оно находится к указанному вами направлению, при изменении этого направления.

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

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

Добавление реального вызова

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

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

 var ball = document.getElementById('ball'), positions = { 37 : ['left', '0'], 38 : ['top', '0'], 39 : ['left', '100%'], 40 : ['top', '100%'] }, colors = { 37 : '255,0,0', 38 : '255,255,0', 39 : '0,0,255', 40 : '0,255,255' }; document.addEventListener('keydown', function(e, data) { if(data = positions[e.keyCode]) { ball.style[data[0]] = data[1]; ball.style.backgroundColor = 'rgb(' + colors[e.keyCode] + ')'; e.preventDefault(); } }, false); 

И теперь, нажимая клавиши со стрелками, мы не только меняем положение шара, но и его основной цвет. Давайте также сместим положение шара по умолчанию в центр и установим его цвет по умолчанию на серый (т. Е. На светлый цвет, который он никогда не будет иметь во время игры):

Но, конечно, цвет не меняется мгновенно, он постепенно исчезает от одного к другому в течение одного перехода, проходя через различные промежуточные оттенки по пути. Например, если шар красный, а затем вы нажмете стрелку вправо , он изменит цвет с красного на синий через различные оттенки фиолетового (а также при перемещении вправо).

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

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

Финальный прототип игры

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

 var targets = [ { "color" : [220,180,40], "coords" : [5,5,12,35] }, { "color" : [210,80,80], "coords" : [45,2.5,10,40] }, { "color" : [160,90,60], "coords" : [65,5,20,20] }, { "color" : [100,100,150], "coords" : [2.5,75,35,15] }, { "color" : [150,70,100], "coords" : [55,65,10,20] }, { "color" : [70,230,150], "coords" : [87.5,60,10,20] } ]; for(var len = targets.length, i = 0; i < len; i ++) { var target = document.createElement('div'); target.className = 'target'; target.style.left = targets[i].coords[0] + '%'; target.style.top = targets[i].coords[1] + '%'; target.style.width = targets[i].coords[2] + '%'; target.style.height = targets[i].coords[3] + '%'; target.style.backgroundColor = 'rgb(' + targets[i].color.join(',') + ')'; targets[i].target = ball.parentNode.insertBefore(target, ball); } var tracking = window.setInterval(function() { var ballcolor = window.getComputedStyle(ball).backgroundColor.replace(/[^0-9,]/g, '').split(','); for(var n = 0; n < 3; n++) { ballcolor[n] = parseInt(ballcolor[n], 10); } for(var i = 0; i < targets.length; i ++) { if ( ball.offsetLeft > targets[i].target.offsetLeft && ball.offsetLeft + ball.offsetWidth < targets[i].target.offsetLeft + targets[i].target.offsetWidth && ball.offsetTop > targets[i].target.offsetTop && ball.offsetTop + ball.offsetHeight < targets[i].target.offsetTop + targets[i].target.offsetHeight ) { var match = 0; for(var n = 0; n < 3; n ++) { if(Math.abs(ballcolor[n] - targets[i].color[n]) < 40) { match ++; } } if(match === 3) { targets[i].target.parentNode.removeChild(targets[i].target); targets.splice(i, 1); if(targets.length === 0) { window.clearInterval(tracking); window.setTimeout(function(){ alert('Yay!'); }, 250); } } } } }, 62.5); 

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

 var match = 0; for(var n = 0; n < 3; n ++) { if(Math.abs(ballcolor[n] - targets[i].color[n]) < 40) { match ++; } } if(match === 3) { //... all three channels are sufficiently close } 

Сам код отслеживания обернут в один цикл setInterval() , который (насколько я знаю) является единственным способом постоянного контроля свойств шара — используя getComputedStyle() вместе со свойствами смещения, чтобы получить цвет и положение шара в каждая итерация. Интервал не должен быть таким быстрым, чтобы чрезмерно напрягать браузер, но он все же должен быть достаточно быстрым, чтобы быть точным — в зависимости от размера и скорости мяча. Поскольку шарик занимает 5% поля и перемещается на все расстояние за пять секунд, шарику потребуется в среднем 250ms для перемещения на собственную ширину. Поэтому, какую бы пропорцию мы не использовали для интервала, максимальный дрейф трекинга будет представлять собой пропорцию размера шара, то есть максимальное количество расхождений между рассчитанной по интервалу позицией шара и его фактической позицией. Скорость, которую я установил, составляет 62.5ms , что дает максимальный дрейф в четверть размера мяча. Честно говоря, это немного быстрее, чем мне бы хотелось, но любая медленнее, чем эта, не будет достаточно точной и может привести к невозможности обнаружить действительные совпадения.

Было бы намного проще, если бы было какое-то событие обратного вызова для кадров для переходов CSS, но это не так — единственное событие, которое у нас есть, это событие transitionend , которое запускается в конце перехода, но это бесполезно нам здесь.

Но в любом случае — у нас есть игра сейчас! Попробуйте готовый прототип ниже и посмотрите, как вы попадаете — цель игры — соответствовать каждой цели, пока доска не станет чистой :

За прототипом

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

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

А пока вы можете скачать zip-файл всех демонстраций этой статьи: