Русский
Русский
English
Статистика
Реклама

Games

Фронтендер пишет нейронки. Уровень сложности хочу на ручки

05.01.2021 22:19:51 | Автор: admin

Рано или поздно это должно произойти

Рано или поздно, фронтенд - разработчик устает играть со своими фреймворками, устает докучать коллегам - бэкендерам, устает играть в девопс и начинает смотреть в сторону машинного обучения, дата - саенс и вот это вот все. Благо, каждый второй курс для тех кто хочет войти вайти способствует этому, крича на всех платформах, как это легко. Я тоже, насытившись перекладыванием данных из базы в API, а потом из API в таблицы и формы, решил взять небольшой отпуск и попробовать применить свои скилы фронтендера в машинном обучении. Благо, существуют такие люди какDaniel ShiffmanиCharlie Gerard, которые своим примером помогают не бросить начатое, увидев первые страницы с математическими формулами.

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

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

Схема нейронной сетиСхема нейронной сети

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

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

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

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

Говоря черный ящик - выдаю в себе любовь к тестам. Именно так. Сейчас нам не особо важно какие именно внутренние алгоритмы использует та или иная сеть. Мы просто бросаем некоторые рандомные факты в нее и получаем ответ как нам поступить.

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

Да, это так. Поэтому если вы посмотрите на картинку нейронной сети еще раз, то увидите, что каждый нейрон имеет связь с каждым другим, это неспроста. Эти связи называют весами, или если сказать по-другому, коэффициенты, с которыми мы должны воспринимать тот или иной факт.

Веса нейронной сетиВеса нейронной сети

Допустим, как мы уже сказали, нам не страшен лед если мы в ботинках. Тогда важность этого факта будет минимальной, мы можем учитывать только0.1*X1(W1== 0.1)от любого значенияX1. (Да, забыл сказать, так как у нас искусственная нейронная сеть, каждый факт должен быть выражен каким либо числом, но лучше, конечно, нормированным от -1 до 1). В этом случае какое бы значение мы не получили, мы будем принижать его важность в 10 раз. И наоборот, важность падения сосулек для нашей жизни максимальная, поэтому вес для такой связи будет1*X2(W2== 1).

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

Теперь, когда мы немного ближе рассмотрели нашу картинку, напоминает ли она еще что-то? По мне, так это самая настоящая функция (или более правильное название - функция активации).f(Х)=У. ГдеХ- это матрица всех входных данных или input слой,У- это матрица всевозможных вариантов исхода или output слой, и некоторый алгоритмf, который по какому-то паттерну преобразовывает входной слой в выходной.

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

Собственно, как и настоящая нейронная сеть, искусственная никогда не может быть на сто процентов уверена в выходном варианте, поэтому выходной слой обычно представлен в видематрицы вероятностей, что какой-либо изУiбудет ответом на паттерн входящих данных.

И уже мы сами можем дать определенныйlabelэтим вероятностям. Допустим, если нейронка говорит, что наиболее вероятный маневр - этоУ3, а мы ранее дали этому выходному вариантуlabel=поверни налево, то в этой ситуации мы говорим, что нейронная сеть предложила повернуть налево. И не смотря на то, что нейронка предлагала еще два других варианта, мы ими пренебрегли, поскольку их вероятность была меньше.

Тут вы можете еще раз возразить. Жизнь все еще намного сложнее! И снова вы правы. Как же тогда люди учатся делать что-то, находить паттерны, если их веса никто не исправляет, никто не регулирует?

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

Пока мы далеко не ушли очень рекомендую к просмотру видео с каналаthecodingtrain, где Дэниель Шифман рассказывает и великолепно показывает на живых примерах как работают нейронные сети. Как они обучаются, как они обрабатывают ошибки и так далее.

Собственно, существует две больших группы методов обучения нейронной сети. Обучение с учителем и обучение без учителя.

Пример с погодой, который мы рассмотрели выше, относится к группе методов обучения с подкреплением (без учителя). Данная группа считается наиболее естесственной и близкой к настоящим нейронным сетям.

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

Для искусственных сетей такой стимул приходится придумывать самому человеку, и выражается он также в некотором числовом эквиваленте. Чем большеscore, тем удачнее был шаг.

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

Итак, собрав эту небольшую информацию, как и зачем работают нейронки, давайте немного отдохнем и поиграемв raid shadow legends. Играть мы будем вdino gameот создателейgoogle chrome. Но нажимать пробел было бы очень просто, давайте напишем игру с нуля и нейронную сеть, которая сама будет играть в эту игру?

Dino game

В написании игры нам будет помогать такой редактор какp5.js. Данный инструмент уже заточен на реализацию подобных задач, когда необходимо реализовывать игровой цикл, работу с канвасом и обработкой событий во время самой игры. Любой скетч на р5 имеет две функции:setup, где мы инициализируем все наши переменные, рисуем канвас определенного размера и прочие вещи; иdraw- функция, которая вызывается на каждой итерации игрового цикла, здесь мы можем обновлять наши анимации и прочее.

В чем идея? Мы создаем канвас, помещаем на него изображения дино и кактусов, и на каждой итерации игры мы перерисовываем их, создавая иллюзию движения. Попутно мы проверяем коллизии дино и кактусов, и если она есть, то игра заканчивается.

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

class Cactus {  constructor() {}}class Dino {  constructor() {}}

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

...onTick(cb = () => { }) {  cb(this);}...

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

Далее мы вводим такие понятия как состояние дино (бег, прыжок, падение). У дино будет максимальная высота, на которую он сможет прыгнуть и скорость прыжка. После этого, при каждой итерации игрового цикла мы проверяем состояние дино: если дино бежит - мы пропускаем итерацию, если находится в прыжке, то инкрементируем текущую высоту, если приземляется, то декрементируем, проверяя при этом, максимальную высоту и уровень земли. Таким образом, мы только изменяем координатуУ, имитируя прыжок.

switch (this.state) {  case DinoStateEnum.run: break;  case DinoStateEnum.jump: {    this.currH += this.jumpSpeed;    if (this.currH == this.maxH) {      this.state = DinoStateEnum.fall;    }    break;  }  case DinoStateEnum.fall: {    this.currH -= this.jumpSpeed;    if (this.currH == 0) {      this.state = DinoStateEnum.run;    }    break;  }}

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

function updateCactuses() {  const copy = cactuses.slice();  for (let i = 0; i < copy.length; i++) {    let c = copy[i];    c.onTick(cactus => {      drawCactus(cactus)      cactus.currDistance -= dinoVelocitySlider.value();      if (cactus.currDistance + cactusW < initialDinoW && !cactus.passDinoPosition) {        updateDinoScore()        cactus.passDinoPosition = true;      }      if (cactus.currDistance < 0) {        cactuses.splice(i, 1);      }    })  }}

Понимаю, что это прямо очень сжатое описание того что сделано, поэтому оставляюссылочку на исходники.

Нейроэволюция

В этом проекте мы будем использоватьtensorflowjsи первое, что нам нужно сделать - это подключить скрипт с официального сайта. Далее, чтобы оптимизировать работу в браузере нам нужно установить бэкенд для вычислений. Теперь мы готовы писать нейронки.

<script  src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script><script>  tf.setBackend('cpu') // tf глобальная переменная</script>

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

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

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

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

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

В чем идея? Мы генерируем популяцию рандомно - настроенных дино размером 200-300 особей, и смотрим насколько они способны выживать. Далее мы выбираем несколько особей, у которых продолжительность жизни (best score) наибольшая, пытаемся немного их мутировать, как если бы это делала настоящая эволюция и создаем новое поколение. То есть имитируем настоящую эволюцию, поощряя продолжительность жизни. В итоге через несколько поколений преобладающим качеством наших дино должна стать долгая жизнь. Это как выводить пшеницу с наибольшими зернами (селекция).

Теперь когда с теорией, наконец, закончили, давайте перейдем к имплементации.

Мы будем реализовывать простуюнейронную сеть из трех слоев.

  • Входной слой - это наши значимые условия окружающей среды.

  • Cкрытый слой - наш черный ящик.

  • Выходной слой - решения, которые принимает дино.

В качествезначимых условий, я предлагаю рассматривать:

  • Tекущее положение дино по осиУ.

  • Скорость дино.

  • Расстояние до ближайшего кактуса.

  • Условие, когда дино приближается сразу к нескольким кактусам подряд.

Всего 4 входных узла или нейрона.

Настройку скрытого слоя доверим великому рандому и просто выберем 8 нейронов. Выходных нейронов будет 2, два решения:прыгать или не прыгать.

Чтобы создать модель нейронной сети нам нужно сделать следующее:

createModel() {  const model = tf.sequential();  const hiddenLayer = tf.layers.dense({    units: this.hidden_nodes, // кол-во нейронов в скрытом слое (8)    inputShape: [this.input_nodes], // кол-во нейронов во входном слое (4)    activation: "sigmoid" // функция активации  });  model.add(hiddenLayer);  const outputLayer = tf.layers.dense({    units: this.output_nodes, // кол-во нейронов в выходном слое (2)    activation: "sigmoid"  });  model.add(outputLayer);  return model;}

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

Добавим слой в модель и создадим таким же образом выходной слой, при этом заметьте, теперь нам нет необходимости указывать кол-во нейронов в прошлом слое, так какtensorflowсделает это за нас.

Модель готова, теперь нам нужно научить ее думать и принимать решения. И мы умышленно не создавали входной слой, потому что этот слой мы получаем из входящих параметров.

predict(inputs) {  return tf.tidy(() => {    const xs = tf.tensor([inputs]); // создание тензора из массива (входной слой)    const ys = this.model.predict(xs); // предсказание сети    const output = ys.dataSync(); // превращение тензора в массив    return output;  });}

Посколькуtensorflowпо своей природе иммутабельный, то на каждую операцию создается новыйtensor(массив определенной размерности) и, чтобы после нескольких операций у нас не текла память, участки кода, в которых мы обрабатываем тензоры, принято оборачивать в специальный колбэкtidy.

Как уже сказано,tensorflowработает только с тензорами, поэтому в нашу функцию предсказания мы посылаем тензор, полученный путем преобразования одномерного массива. После обработки входящих данных мы также получаем тензор и, чтобы превратить его в удобочитаемый формат, вызываем специальный метод. (Можем читать эти данные, как синхронно так и асинхронно). На выходе мы также получим одномерный массив длиной 2, поскольку мы указали 2 выходных нейрона. И, если мы вспомним начало, то поймем, что массив заполнен вероятностями того или иного решения, то есть прыгать или нет. Нам теперь достаточно проверить, чтоoutput[0] > output[1], чтобы дино прыгнул.

Вот так в несколько строчек можно реализовать простую нейронную сеть дляdino npc.

Следующий шаг - реализовать генетический алгоритм, создание популяции, отбор лучших и мутацию.

Сначала мы устанавливаем размер популяции, потом генерируем это кол-во дино и в цикле отрисовываем каждого.

function drawDino(dino) {  if (dino.isDead) return;  if (dino.state != DinoStateEnum.run) {    // если дино прыгает, то рисуем его на текущей высоте    image(dino2, initialDinoW, initialDinoH - dino.currH, dinoW, dinoH); // р5 специальный метод добавления изображения на канвас  } else if (iteration % 7 == 0)    // иначе имитируем бег и перебирание ножками    image(dino1, initialDinoW, initialDinoH, dinoW, dinoH);  else    image(dino2, initialDinoW, initialDinoH, dinoW, dinoH);}

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

function updateGenerationIfNeeded() {  if (dinos.every(d => d.isDead)) {    cactuses = [];    dinoVelocitySlider.value(initDinoVelocity);    dinos = newGeneration(dinos)  }}

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

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

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

mutate(rate) {  tf.tidy(() => {    const weights = this.model.getWeights(); // берем веса модели    const mutatedWeights = [];    for (let i = 0; i < weights.length; i++) {      let tensor = weights[i]; // каждый вес - это тензор      let shape = weights[i].shape;      let values = tensor.dataSync().slice();      for (let j = 0; j < values.length; j++) {        if (Math.random() < rate) { // мутируем если нам повезло          let w = values[j];          values[j] = w + this.gaussianRandom(); // рандомное нормальное изменение в интервале от -1 до 1        }      }      let newTensor = tf.tensor(values, shape);      mutatedWeights[i] = newTensor;    }    this.model.setWeights(mutatedWeights); // ставим мутировавшие веса  });}

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

const calculateFitness = (dinos) => {  let sum = 0;  dinos.map(d => sum += d.score)  dinos.map(d => d.fitness = d.score / sum)}

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

Функция для такого действия может выглядеть так:

const pickOne = (dinos) => { // на входе дино отсортированные по убыванию fitness  let index = 0;  let r = Math.random();  while (r > 0) {    r = r - dinos[index].fitness;    index++;  }  index--;  let dino = dinos[index] // берем дино где-то из начала списка, как повезет с rate  const dinoBrain = dino.brain.copy();  dinoBrain.mutate(0.2) // делаем мутировавшую копию  let newDino = new Dino(dinoBrain) // дино для нового поколения  return newDino;}

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

for (let i = 0; i < TOTAL; i++) {  newDinos.push(pickOne(oldDinos));}console.log(currentGeneration++);return newDinos;

Запустив симуляцию мы можем увидеть несколько стратегий, которые появляются в первом поколении, дино либо прыгает постоянно, либо прыгает далеко перед кактусом и приземляется сразу за ним, либо прыгает прямо перед кактусом. К десятому поколению эти стратегии усредняются и дино способен прожить 50+ кактусов. По ссылке можно проверить симуляцию самому.

Исходники проекта на гитхабе.

P.S. Если возникли вопросы к материалу или заметили ошибку, welcome to PR's. Или напишите мне в твиттерv_hadoocken

Подробнее..

Фронтендер пишет нейронки. Уровень сложности мартышка и уравнение Беллмана

20.01.2021 22:15:38 | Автор: admin

Привет.

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

В комментариях к прошлой статье поднялся вопрос про reinforcement learning. Почему бы и нет. Давайте подробнее рассмотрим что это такое.

Как и в прошлый раз, я постараюсь не использовать сложные математические формулы и объяснения, так как сам понимаю на своем примере, что нежные ушки и глазки фронтендера к этому не приспособлены. И как завещал Хокинг, любая формула в популярной работе сокращает аудиторию вдвое, поэтому в этой статье она будет всего одна - уравнение Беллмана, но это уже понятно из названия.

Итак, reinforcement learning, или обучение с подкреплением - это такая группа методов машинного обучения, подходы которой сначала выглядят как методы обучения без учителя, но со временем (время обучения) становятся методами обучения с учителем, где учителем становится сама нейронная сеть. Скорее всего, ничего непонятно. Это не страшно, мы все рассмотрим на примере.

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

Наш условный лабиринтНаш условный лабиринт

Представляя эту картину, мы можем увидеть все основные составляющие любого проекта с использованием обучения с подкреплением.Во-первых, у нас есть крыса - Агент (agent), наша нейронная сеть, которая мыслит и принимает решения.Во-вторых, у нас есть Окружение или среда (environment) агента - лабиринт, который имеет свое Состояние (state), расположение проходов, мест с котами, финальный островок и так далее. Крыса может принимать решения и совершать определенные Действия (actions), которые могут приводить к разным последствиям. Крыса либо получает Вознаграждение (reward), либо Санкции (penalty or -reward) за свои действия.

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

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

Семейство методов обучения с подкреплениемСемейство методов обучения с подкреплением

Вообще, пусть меня поправят эксперты, Q-learning может не иметь ничего общего с нейронными сетями. Вся цель этого алгоритма - максимизировать значение Q (вознаграждение, полученное за проход от начального состояния до конечного), используя конечные автоматы.

Мы имеем некоторое кол-во состояний (s1, s2 ...), наш агент может находится в одном из этих состояний в каждый момент времени. Цель агента достичь финального состояния.

Пример конечного автоматаПример конечного автомата

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

---

s1

s2

s3

s4

s5

s6

s7

s1

0

1

-1

---

---

---

---

s2

---

0

---

-10

1

---

---

s3

---

---

0

---

---

100

1

Но здесь появляется проблема, как только мы начнем изменять кол-во состояний, наша таблица становится бесполезной и мы должны заполнять ее заново. Здесь нам на помощь приходят нейронные сети. Зачем заполнять таблицы? Давайте просто предсказывать значение перехода.

В чем идея? Как нейронные сети нам помогут?

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

Итак, мы имеем некоторые входные данные и знаем как должны выглядеть обработанные данные. Например, у нас есть картинка котика и мы точно знаем, что это котик. Допустим, у нас 1 000 разных картинок котиков. Мы отдаем это все в наш черный ящик и он возвращает нам некоторый алгоритм того, как определить что на картинке котик.

Получение алгоритма распознавания котиковПолучение алгоритма распознавания котиков

Далее, если мы достанем еще несколько картинок котиков, которые отличны от тех 1000 штук, возьмем наш алгоритм, и все это отправим в черный ящик, он вернет нам правильное название для этих картинок, хотя этих картинок нейронная сеть еще не видела. Это нейронные сети 101 (базовый курс).

Распознавание новых котиковРаспознавание новых котиков

Так вот, с Q-обучением тоже самое. Зная только часть переходов между состояниями таблицы, используя нейронные сети, мы можем предсказывать новые состояния для новой таблицы с максимальным вознаграждением. Поэтому на свет появился Deep Q-learning алгоритм. Deep потому что deep neural networks, глубокие нейронные сети, глубокие - потому что много слоев.

Наверняка появились вопросы или я что-то упустил. Поэтому давайте перейдем к практической части.

Реализация окружения и агента

В этот раз мы также будем использовать р5 редактор, поскольку снова будем делать аркадный проект, поэтому р5 будет незаменим в этом.

Начнем мы с того, что определим наши объектные модели. Самыми главными классами для нас будут класс Environment и класс Agent.

class Agent {  constructor(b, r, s, w, h) {    this.network = b;    this.rect = r;    this.speed = s;    this.width = w;    this.height = h;  }}class Environment {  constructor(w, h, r, c, es, as) {    this.width = w;    this.height = h;    this.rows = r;    this.columns = c;    this.enemySpeed = es;    this.agentSpeed = as;    this.agent = this.resetAgent();    this.eps = Environment.MAX_EPS;    this.discount = Environment.DISCOUNT;  }}

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

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

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

Итак, давайте обозначим, что является состоянием. Я решил выбрать 9 параметров, которые могут изменяться в течение времени:

  • положение агента по оси Х

  • положение агента по оси У

  • наличие врага впереди на оси Х

  • наличие врага впереди на оси У

  • дистанция до врага по оси Х

  • дистанция до врага по оси У

  • наличие цели на оси Х

  • наличие цели на оси У

  • дистанция до цели

Параметры состояния окружения агентаПараметры состояния окружения агента

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

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

const ACTIONS = [MOVE_RIGHT, MOVE_DOWN, MOVE_LEFT, MOVE_UP];chooseAction(state, eps) {  if (random(0, 1) < eps) {    return ACTIONS[random([0, 1, 2, 3])]; // рандомный шаг  } else {    return tf.tidy(() => {      // сеть возвращает массив из 4 значений      const probs = this.network.predict(state).dataSync();// шаг с максимальным значением      return ACTIONS[probs.indexOf(Math.max(...probs))];     });  }}

Агент получает текущее состояние и выдает ответ в какую сторону стоит сделать шаг. Здесь мы уже можем ответить на возможный вопрос, что за eps и зачем он нужен.

Это, так называемый, коэффициент исследования.

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

Почему? Это называется проблемой компромисса исследования и использования (не знаю как правильно перевести exploration-exploition trade-off).

Давайте рассмотрим на примере.

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

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

Вернемся обратно к агенту.

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

update(action) {  switch (action) {    case MOVE_UP:    this.rect.top = this.rect.top - this.speed;    break;    case MOVE_DOWN:    this.rect.top = this.rect.top + this.speed;    break;    case MOVE_RIGHT:    this.rect.left = this.rect.left + this.speed;    break;    case MOVE_LEFT:    this.rect.left = this.rect.left - this.speed;    break;  }}

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

createModel(inputShape) {  const model = tf.sequential();  model.add(tf.layers.dense({ inputShape: [inputShape], units: 36, activation: 'relu' }));  model.add(tf.layers.dense({ units: 36, activation: 'relu' }));  model.add(tf.layers.dropout({ rate: 0.20 }));  model.add(tf.layers.dense({ units: Agent.ACTIONS.length }));  model.compile({ optimizer: 'adam', loss: 'meanSquaredError' });  return model;}

У нас появился необычный слой (5 строка), который называется dropout. Его советуют использовать, если есть возможность того, что нейронка может перетренероваться (явление, когда сеть не предсказывает выходные данные, а просто запоминает связки инпут-аутпут из тренировочных данных). Но также нашел на хабре статью, в комментариях которой говорят, что у этого слоя куда больше применений, хотя их автор не упомянул. Не суть. Что делает этот слой? Он просто игнорирует некоторые нейроны с заданной вероятностью, то есть обнуляет их веса, чтобы те не влияли на ответ.

И вторая строчка, которая нас будет интересовать чуть позже (8 строка), метод компиляции модели. Здесь мы указываем то, как нейронка будет обрабатывать свои ошибки (разницы между предсказанными значениями и теми, которые должны быть). Оставим настройку этих параметров интернету и будем воспринимать их как черный ящик.

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

Обучение агента

Каков наш алгоритм обучения? На самом деле, в интеренете очень много примеров и реализаций DQN алгоритма, в частности на js, но, чтобы понять этот алгоритм мне пришлось потратить пару дней непрерывного чтения различных статей и обрывков книг, чтобы просто прочитать код. Я даже отчаялся и пошел просить помощи на stackoverflow. В итоге я не уверен, что полностью понимаю, что я сделал. Наверное, поэтому и пишу эту статью, но эй! Мы за этим здесь и собрались - учиться на ошибках. Поэтому буду очень рад обратной связи.

Итак, как же мы научим агента искать сыр?

Во-первых, мы разобьем наше обучение на несколько игр, 60-100 должно хватить. Установим кол-во шагов в каждой игре, пусть будет 1000, чтобы игра не шла вечно и агент не крутился на месте. Если агент израсходует свои шаги, игра начинается заново. Если агент натыкается на кота или находит сыр, игра начинается заново. Чтобы мотивировать агента избегать котов и искать сыр введем метод подсчета его вознаграждения и штрафов. За каждый шаг будем бить агента током, мотивируя его быстрее добраться до цели, причем, чем ближе агент к цели, тем меньше он получит разряд (в числах это от -0.2 до 0). Если агент натыкается на кота то умирает и получает -10. Если находит сыр, то его награда +100.

calcReward() {  // находим нормализованную дистанцию до цели (от 0 до 1) и умножаем на -0.2  let reward = distance(  this.agent.rect.left,  this.width,  this.agent.rect.top,  this.height) / distance(0, this.width, 0, this.height) * -0.2;  const agentRect = toRect(this.agent.rect);  const enemiesRects = this.enemies.map(e => toRect(e));  const goalRect = toRect(this.goal);  const intersected = enemiesRects.filter(e => rectsIntersected(e, agentRect));  reward += intersected.length && -10;  if (rectsIntersected(agentRect, goalRect)) reward = 100;  return reward;}

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

В-третьих, соберем все вместе.

Мы инициализируем игру и наше хранилище.

function setup() {  mem = new ReplayMemory();  env = new Environment(450, 300, 4, 6, 4, 2);  createCanvas(...env.dims);}

Реализуем отрисовку всех составляющих игры на каждой итерации игрового цикла.

async function draw() {  CURRENT_STEP++;  background(220);  drawGoal(env.goal);  drawNet(env.net);  drawEnemies(env.enemies);  drawAgent(env.agent.rect);  ...

Отдаем агенту текущее состояние и в ответ получаем шаг, который он сделал, новое состояние, которое наступило после его шага и флаг, который нам говорит закончилась игра или нет.

// environmentupdateAgent(STATE = this.getStateTensor()) {  const action = this.agent.chooseAction(STATE, this.eps);  this.agent.update(action);  const nextState = this.getStateTensor();  return [nextState, action, this.isDone()];}

Далее мы считаем награду за шаг агента и добавляем все эти данные в нашу память. Они нам потом понадобятся.

// draw in sketch fileconst [nextState, action, done] = env.updateAgent(STATE);const reward = env.calcReward();mem.append([STATE, action, reward, nextState, done]);STATE = nextState;...

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

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

Теперь к самой реализации. Думаю, я довольно потомил вас в ожидании, вот эта формула - уравнение Беллмана. Что же она нам говорит?

Если попробовать прочитать что тут написано, то получится нечно следующее: одно кольцо, чтоб править всеми максимально возможное вознаграждение (Q) агента в состоянии s равно сумме моментального вознаграждения r за его шаг а и максимально возможноного вознаграждения агента из состояния s помноженное на коэффициент понижения gamma. На слух - это точно эльфийский.

Мы еще не рассмотрели что вознаграждения могут быть моментальными и долгосрочными. Что это значит? В нашем проекте удар тока после каждого шага - это моментальное вознаграждение со знаком минус, а большой бонус за сыр - это долгосрочное вознаграждение. Но, мне кажется, чтобы до конца понять что имеется в виду в формуле нам нужно вернуться к автоматам.

Допустим, у нас есть начальное состояние агента s1, далее агент имеет возможность перейти в состояние s2 и s3 при помощи действий (шагов) а1 и а2, после этого вариации выбора еще раз расширяются. И из состояния s2, все еще при помощи а1 и а2, агент может попасть в состояния s4 и s5, а из состояния s3 в состояния s6 и s7. За каждый переход агент будет получать моментальное вознаграждение, ну или штраф, а чтобы посчитать долгосрочное вознаграждение из состояния s1 нам нужно проверить все ветви нашего автомата. Собственно, становится понятно, что max Q для состояния s1 == 99 (-1 + 100), а для состояния s3 == 100 (100 - финальный переход) и логично отсюда вывести, что max Q для s1 равно вознаграждение за переход a2 плюс max Q из состояния, в которое мы попали (s3).

Но что это значит? Это значит, что нам вручную нужно ходить туда-сюда по состояниям и правильно настраивать Q значения - заполнять нашу таблицу, как мы уже поняли. И как мы уже решили, мы не будем этого делать, пусть нейронка сама нам считает эти значения. Поэтому вот так уравнение Беллмана выглядит на javascript.

async function replay() {  let miniBatch = mem.sample(500);  const filtered = miniBatch.filter(Boolean);  // фильтруем если очень мало шагов сделали  if (!filtered.length) return;  let currentStates = filtered.map((dp) => { return dp[0].dataSync() });  // предсказываем Q для каждого текущего состояния s в памяти  let currentQs = await env.agent.network.predict(tf.tensor(currentStates)).array();    let newCurrentStates = filtered.map((dp) => { return dp[3].dataSync() });  // предсказываем Q для каждого состояния s', в которое мы попали из s  let futureQs = await env.agent.network.predict(tf.tensor(newCurrentStates)).array();  let X = [];  let Y = [];  for (let index = 0; index < filtered.length; index++) {    // берем один слайс    const [state, action, reward, newState, done] = filtered[index];    let newQ;    let currentQ;    // уравнение Беллмана    if (!done) {      let maxFutureQ = Math.max(...futureQs[index]);      // находим максимальный Q для следующего состояния (s')       // и складываем с моментальным вознаграждением (r)      newQ = reward + (env.discount * maxFutureQ);    }    // если финальный переход, просто учитываем сам переход    else { newQ = reward }    currentQ = currentQs[index];    // корректируем текущее значение Q на то, которое посчитали    currentQ[action] = newQ;    X.push(state.dataSync()); // 9 параметров нашего состояния    Y.push(currentQ); // массив из 4 значений Q для наших шагов (вправо, вниз, влево, вверх)  }  // учим нашу сеть скорректированными данными  await env.agent.network.fit(tf.tensor(X), tf.tensor(Y), { verbose: 0 });}

Когда мы попадаем в реплай игры мы берем небольшой слайс записей наших шагов.

Мы достаем все стартовые состояния из этих записей. И просим нашу нейронку посчитать значение Q для каждого состояния.

Делаем то же самое для всех состояний, в которых агент оказался после своего шага.

Теперь мы считаем разницу между тем, что нам подсказала нейронка и тем, что мы сами посчитали.

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

После этого вызываем специальный метод fit, который помогает нейронке переосмыслить свои шаги с нашими корректировками.

И чуть не забыл, зачем нам вообще нужен коэффициент понижения? Нам нужен еще один пример.

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

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

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

Единственное, что я хотел бы добавить, так это почему эта формула вызвала у меня сложности ее понимания. Возможно, кому-то тоже пригодится. Изначально у меня была стойкая ассоциация, что мы пытаемся предугадать именно шаг нашего агента, поэтому на выходе мы получаем вероятности каждого шага и, когда потом мы стали складывать их с вознаграждениями я поплыл. Здесь стоит сразу запомнить, что мы ищем именно вознаграждение для конкретного состояния, и наши шаги лишь способ трансформировать максимальное вознаграждение в направление движения.

Давайте уже запускать симуляцию.

Первые несколько игр Джери тупит в углу либо суицидиться об котов.

Позже он начинает выходить немного далее своих привычных рубежей и где-то к 60-й игре уже умеет находить сыр. Всего на обучение ушло примерно 10 минут, учитывая медленный рендер сцены во время обучения.

Оставляю исходники проекта, ссылку на симуляцию. И открытый вопрос - как можно это все улучшить? Был бы очень рад совету эксперта по топологии сети или подкручиванию параметров. Или пересмотру основных методов взятия текущего состояния и подсчета вознаграждения.

PS. если есть желание поконтрибьютить, welcome to PRs или напишите мне в твиттер: v_hadoocken

Подробнее..
Категории: Javascript , Tensorflow , Neural networks , Games , Q-learning , P5

Перевод Игры по спецификации обратная сторона изобретательности ИИ

22.12.2020 16:08:30 | Автор: admin
Игры по спецификации это поведение, удовлетворяющее буквальной спецификации цели без достижения намеченного результата. У всех нас есть опыт игры по спецификации, даже если не под этим названием. Возможно, читатели слышали миф о царе Мидасе и о золотом прикосновении, в котором царь просит, чтобы всё, к чему он прикасается, превращалось в золото, но вскоре обнаруживает, что даже еда и напитки превращаются в металл в его руках. В реальной жизни, когда учащийся получает вознаграждение за хорошую работу над домашним заданием, он может скопировать другого студента, чтобы получить правильные ответы, вместо того чтобы изучать материал и таким образом использовать лазейку в спецификации задания.




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

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


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

Мы можем рассмотреть игру по спецификации с двух точек зрения. В рамках разработки алгоритмов обучения с подкреплением (RL) цель заключается в создании агентов, которые учатся достигать поставленной цели. Например, когда мы используем игры Atari в качестве эталона для обучения алгоритмам RL, цель состоит в том, чтобы оценить, способны ли наши алгоритмы решать сложные задачи. Решает агент задачу, используя лазейку или нет в этом контексте не важно. С этой точки зрения, игра по спецификации хороший знак: агент нашёл новый способ достижения поставленной цели. Такое поведение демонстрирует изобретательность и мощь алгоритмов, позволяющих находить способы делать именно то, что мы им говорим.

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


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

Мы используем термин спецификация задачи в широком смысле, чтобы охватить многие аспекты процесса разработки агентов. При настройке RL спецификация задач включает в себя не только дизайн вознаграждения, но и выбор среды обучения и вспомогательных вознаграждений. Правильность постановки задачи может определить, соответствует ли изобретательность агента предполагаемому результату или нет. Если спецификация верна, творческий подход агента даёт желаемое новое решение. Именно это позволило AlphaGo совершить знаменитый 37 ход, который застал врасплох людей-экспертов в го, но сыграл ключевую роль во втором матче с Ли Седолем. Если спецификация неверна, это может привести к нежелательному игровому поведению, например к переворачиванию блока. Такие решения возможны, и у нас нет объективного способа замечать их.


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


Ошибочные функции вознаграждения в действии.

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

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


Подкрепление глубокого обучения на основе человеческих предпочтений.

Обученная модель вознаграждения также может быть неправильно определена по другим причинам, таким как, например, плохое обобщение. Дополнительная обратная связь может использоваться для исправления попыток агента использовать неточности в модели вознаграждения.

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


ИИ учится ходить.

На первый взгляд, подобные примеры могут показаться забавными, но менее интересными и не имеющими отношения к разёртыванию агентов в реальном мире, где нет ошибок симулятора. Однако основная проблема заключается не в самой ошибке, а в отказе абстракции, который может быть использован агентом. В приведённом выше примере задача робота была неправильно определена из-за неверных предположений о физике симулятора. Аналогично задача оптимизации трафика в реальном мире может быть неправильно определена, если предположить, что инфраструктура маршрутизации трафика не содержит программных ошибок или уязвимостей безопасности, которые мог бы обнаружить достаточно умный агент. Такие предположения не нужно делать явно скорее, это детали, которые просто никогда не приходили в голову проектировщику. И поскольку задачи становятся слишком сложными, чтобы учитывать каждую деталь, исследователи с большей вероятностью вводят неверные предположения при разработке спецификации. Это ставит вопрос: возможно ли проектировать архитектуры агентов, которые исправляют такие ложные предположения, вместо того чтобы пользоваться ими?

Одно из предположений, обычно применяемых в спецификации задачи, состоит в том, что на спецификацию не могут влиять действия агента. Это верно для агента, работающего в изолированном симуляторе, но не для агента, действующего в реальном мире. Любая спецификация задачи имеет физическое проявление: функцию вознаграждения, хранящуюся в компьютере, или предпочтения человека. Агент, развёрнутый в реальном мире, потенциально может манипулировать этими представлениями о цели, создавая проблему подделки вознаграждения. Для нашей гипотетической системы оптимизации трафика нет чёткого различия между удовлетворением предпочтений пользователя (например, путём предоставления полезных указаний) и влиянием на пользователей таким образом, чтобы они имели предпочтения, которые легче удовлетворить (например, подталкивая их выбирать пункты назначения, до которых легче добраться). Первый удовлетворяет задаче, в то время как второй манипулирует представлением цели в мире (предпочтениями пользователя), и оба приводят к высокому вознаграждению системы ИИ. В качестве другого, более экстремального, примера очень продвинутая система ИИ может захватить компьютер, на котором она работает, самостоятельно установив своё вознаграждение на высокое значение.


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

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

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



Хотите узнать больше про машинное и глубокое обучение заглядывайте к нам на соответствующий курс, будет непросто, но увлекательно. А промокод HABR поможет в стремлении освоить новое, добавив 10% к скидке на баннере.
image



Подробнее..

Единороги врываются в RTS анализируем исходный код OpenRA

13.08.2020 12:11:08 | Автор: admin
image1.png

Данная статья посвящена проверке проекта OpenRA с помощью статического анализатора PVS-Studio. Что такое OpenRA? Это игровой движок с открытым исходным кодом, предназначенный для создания стратегий в реальном времени. В статье рассказывается о том, как проводился анализ, какие особенности самого проекта были обнаружены и какие интересные срабатывания выдал PVS-Studio. Ну и, конечно же, здесь будут рассмотрены некоторые особенности анализатора, которые позволили сделать процесс проверки проекта более комфортным.

OpenRA


image2.png

Проект, выбранный для проверки, представляет собой игровой движок для RTS в стиле таких игр, как Command & Conquer: Red Alert. Более подробную информацию можно найти на сайте. Исходный код написан на C# и доступен для просмотра и использования в репозитории.

OpenRA был выбран для проверки по 3 причинам. Во-первых, он, судя по всему, представляет интерес для многих людей. Во всяком случае, это касается обитателей GitHub, так как репозиторий собрал более 8 тысяч звёзд. Во-вторых, кодовая база OpenRA насчитывает 1285 файлов. Обычно такого количества вполне достаточно, чтобы надеяться найти в них интересные срабатывания. Ну и в-третьих Игровые движки это круто :)

Лишние срабатывания


Я анализировал OpenRA с помощью PVS-Studio и поначалу был воодушевлён результатами:

image3.png

Я решил, что среди такого количества High-предупреждений уж точно смогу найти целую уйму самых разных срабатываний. И, конечно же, на их основе напишу самую крутую и интересную статью :) Но не тут-то было!

Стоило лишь взглянуть на сами предупреждения, и всё сразу встало на свои места. 1277 из 1306 предупреждений уровня High были связаны с диагностикой V3144. Она выдаёт сообщения вида "This file is marked with copyleft license, which requires you to open the derived source code". Более подробно данная диагностика описана здесь.

Очевидно, что срабатывания подобного плана меня совершенно не интересовали, ведь OpenRA и так является open-source проектом. Поэтому их необходимо было скрыть, чтобы они не мешали просмотру остальной части лога. Так как я пользовался плагином для Visual Studio, то сделать это было легко. Нужно было просто кликнуть правой кнопкой по одному из срабатываний V3144 и в открывшемся меню выбрать "Hide all V3144 errors".

image5.png

Также можно выбрать, какие предупреждения будут отображены в логе, перейдя в раздел "Detectable Errors (C#)" в опциях анализатора.

image7.png

Для того, чтобы перейти к ним, используя плагин для Visual Studio 2019, нужно кликнуть в верхнем меню Extensions->PVS-Studio->Options.

Результаты проверки


После того как срабатывания V3144 были отфильтрованы, предупреждений в логе стало значительно меньше:

image8.png

Тем не менее среди них удалось найти интересные моменты.

Бессмысленные условия


Достаточно многие срабатывания указывали на лишние проверки. Подобное может свидетельствовать об ошибке, ведь намеренно такой код обычно люди не пишут. Однако в OpenRA довольно часто всё выглядит так, будто эти ненужные условия добавлены специально. Например:

public virtual void Tick(){  ....  Active = !Disabled && Instances.Any(i => !i.IsTraitPaused);  if (!Active)    return;  if (Active)  {    ....  }}

Предупреждение анализатора: V3022 Expression 'Active' is always true. SupportPowerManager.cs 206

PVS-Studio вполне справедливо подмечает, что вторая проверка бессмысленна, ведь если Active будет false, то до неё выполнение не дойдёт. Может быть тут и правда ошибка, но я думаю, что это написано намеренно. Зачем? Ну а почему бы и нет?

Возможно, перед нами некое временное решение, доработка которого оставлена "на потом". В таких случаях достаточно удобно, что анализатор будет напоминать разработчику о подобных недоделках.

Давайте рассмотрим ещё одну проверку "на всякий случай":

Pair<string, bool>[] MakeComponents(string text){  ....  if (highlightStart > 0 && highlightEnd > highlightStart)  // <=  {    if (highlightStart > 0)                                 // <=    {      // Normal line segment before highlight      var lineNormal = line.Substring(0, highlightStart);      components.Add(Pair.New(lineNormal, false));    }      // Highlight line segment    var lineHighlight = line.Substring(      highlightStart + 1,       highlightEnd - highlightStart  1    );    components.Add(Pair.New(lineHighlight, true));    line = line.Substring(highlightEnd + 1);  }  else  {    // Final normal line segment    components.Add(Pair.New(line, false));    break;  }  ....}

Предупреждение анализатора: V3022 Expression 'highlightStart > 0' is always true. LabelWithHighlightWidget.cs 54

Опять же, очевидно, что повторная проверка полностью лишена смысла. Значение highlightStart проверяется дважды, причём в соседних строчках. Ошибка? Возможно, в одном из условий выбраны не те переменные для проверки. Так или иначе, сложно сказать наверняка, в чём тут дело. Ясно одно код надо изучить и поправить или оставить пояснение, если дополнительная проверка всё-таки зачем-то нужна.

Вот ещё один подобный момент:

public static void ButtonPrompt(....){  ....  var cancelButton = prompt.GetOrNull<ButtonWidget>(    "CANCEL_BUTTON"  );  ....  if (onCancel != null && cancelButton != null)  {    cancelButton.Visible = true;    cancelButton.Bounds.Y += headerHeight;    cancelButton.OnClick = () =>    {      Ui.CloseWindow();      if (onCancel != null)        onCancel();    };    if (!string.IsNullOrEmpty(cancelText) && cancelButton != null)      cancelButton.GetText = () => cancelText;  }  ....}

Предупреждение анализатора: V3063 A part of conditional expression is always true if it is evaluated: cancelButton != null. ConfirmationDialogs.cs 78

cancelButton действительно может быть null, ведь в эту переменную записывается значение, возвращаемое методом GetOrNull. Однако логично посчитать, что в теле условного оператора cancelButton никаким образом не превратится в null. Тем не менее проверка всё равно есть. Если не обратить внимание на внешнее условие, то вообще получается странная ситуация: сначала производится обращение к свойствам переменной, а потом разработчик решил убедиться всё-таки null там или нет? :)

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

В игровом движке Unity, например, оператор "==" переопределён для класса UnityEngine.Object. В официальной документации, доступной по ссылке, показано, что сравнение экземпляров этого класса с null работает не так, как обычно. Что ж, наверняка у разработчиков были причины для реализации подобной необычной логики.

В OpenRA я чего-то такого не нашёл :). Так что если в рассмотренных ранее проверках на null и есть какой-то смысл, то состоит он в чём-то другом.

PVS-Studio смог обнаружить ещё несколько аналогичных моментов, но ни к чему перечислять здесь их все. Всё же скучновато смотреть одни и те же срабатывания. К счастью (или нет) анализатор смог отыскать и другие странности.

Недостижимый код


void IResolveOrder.ResolveOrder(Actor self, Order order){  ....  if (!order.Queued || currentTransform == null)    return;    if (!order.Queued && currentTransform.NextActivity != null)    currentTransform.NextActivity.Cancel(self);  ....}

Предупреждение анализатора: V3022 Expression '!order.Queued && currentTransform.NextActivity != null' is always false. TransformsIntoTransforms.cs 44

И вновь перед нами бессмысленная проверка. Правда, в отличие от предыдущих, здесь представлено уже не просто лишнее условие, а самый настоящий недостижимый код. Рассмотренные ранее always true проверки по сути не влияли на работу программы. Их можно убрать из кода, а можно оставить ничего не изменится.

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

Неинициализированная переменная в конструкторе


public class CursorSequence{  ....  public readonly ISpriteFrame[] Frames;  public CursorSequence(    FrameCache cache,     string name,     string cursorSrc,     string palette,     MiniYaml info  )  {    var d = info.ToDictionary();    Start = Exts.ParseIntegerInvariant(d["Start"].Value);    Palette = palette;    Name = name;    if (      (d.ContainsKey("Length") && d["Length"].Value == "*") ||       (d.ContainsKey("End") && d["End"].Value == "*")    )       Length = Frames.Length - Start;    else if (d.ContainsKey("Length"))      Length = Exts.ParseIntegerInvariant(d["Length"].Value);    else if (d.ContainsKey("End"))      Length = Exts.ParseIntegerInvariant(d["End"].Value) - Start;    else      Length = 1;    Frames = cache[cursorSrc]      .Skip(Start)      .Take(Length)      .ToArray();    ....  }}

Предупреждение анализатора: V3128 The 'Frames' field is used before it is initialized in constructor. CursorSequence.cs 35

Весьма неприятный момент. Попытка получения значения свойства Length у неинициализированной переменной неизбежно приведёт к выбрасыванию NullReferenceException. В обычной ситуации вряд ли такая ошибка осталась бы незамеченной всё же невозможность создания экземпляра класса легко себя обнаруживает. Но здесь исключение будет выброшено только в том случае, если условие

(d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*")

будет истинным.

Трудно судить о том, как нужно поправить код, чтобы всё было хорошо. Я могу лишь предположить, что функция должна выглядеть как-то так:

public CursorSequence(....){  var d = info.ToDictionary();  Start = Exts.ParseIntegerInvariant(d["Start"].Value);  Palette = palette;  Name = name;  ISpriteFrame[] currentCache = cache[cursorSrc];      if (    (d.ContainsKey("Length") && d["Length"].Value == "*") ||     (d.ContainsKey("End") && d["End"].Value == "*")  )     Length = currentCache.Length - Start;  else if (d.ContainsKey("Length"))    Length = Exts.ParseIntegerInvariant(d["Length"].Value);  else if (d.ContainsKey("End"))    Length = Exts.ParseIntegerInvariant(d["End"].Value) - Start;  else    Length = 1;  Frames = currentCache    .Skip(Start)    .Take(Length)    .ToArray();  ....}

В данном варианте указанная проблема отсутствует, однако сказать, насколько он соответствует изначальной задумке, сможет только разработчик.

Потенциальная опечатка


public void Resize(int width, int height){  var oldMapTiles = Tiles;  var oldMapResources = Resources;  var oldMapHeight = Height;  var oldMapRamp = Ramp;  var newSize = new Size(width, height);  ....  Tiles = CellLayer.Resize(oldMapTiles, newSize, oldMapTiles[MPos.Zero]);  Resources = CellLayer.Resize(    oldMapResources,    newSize,    oldMapResources[MPos.Zero]  );  Height = CellLayer.Resize(oldMapHeight, newSize, oldMapHeight[MPos.Zero]);  Ramp = CellLayer.Resize(oldMapRamp, newSize, oldMapHeight[MPos.Zero]);    ....}

Предупреждение анализатора: V3127 Two similar code fragments were found. Perhaps, this is a typo and 'oldMapRamp' variable should be used instead of 'oldMapHeight' Map.cs 964

Анализатор обнаружил подозрительный момент, связанный с передачей аргументов в функции. Взглянем на вызовы отдельно:

CellLayer.Resize(oldMapTiles,     newSize, oldMapTiles[MPos.Zero]);CellLayer.Resize(oldMapResources, newSize, oldMapResources[MPos.Zero]);CellLayer.Resize(oldMapHeight,    newSize, oldMapHeight[MPos.Zero]);CellLayer.Resize(oldMapRamp,      newSize, oldMapHeight[MPos.Zero]);

Достаточно странно, что в последнем вызове производится передача oldMapHeight, а не oldMapRamp. Конечно, далеко не все подобные случаи действительно являются ошибками. Вполне возможно, что тут всё написано правильно. Но согласитесь, что выглядит это место необычно. Я склоняюсь к тому, что здесь действительно допущена ошибка.

Примечание коллеги Андрея Карпова. А я не вижу в данном коде ничего странного :). Это же классическая ошибка последней строки!

Если же ошибки тут всё-таки нет, то стоит добавить какое-нибудь пояснение. Ведь если момент похож на ошибку, то его обязательно кому-нибудь захочется исправить.

True, true and nothing but true


В проекте нашлись весьма своеобразные методы, возвращаемое значение которых имеет тип bool. Их своеобразность заключается в том, что при любых условиях они возвращают true. Например:

static bool State(  S server,   Connection conn,   Session.Client client,   string s){  var state = Session.ClientState.Invalid;  if (!Enum<Session.ClientState>.TryParse(s, false, out state))  {    server.SendOrderTo(conn, "Message", "Malformed state command");    return true;  }  client.State = state;  Log.Write(    "server",     "Player @{0} is {1}",    conn.Socket.RemoteEndPoint,     client.State  );  server.SyncLobbyClients();  CheckAutoStart(server);  return true;}

Предупреждение анализатора: V3009 It's odd that this method always returns one and the same value of 'true'. LobbyCommands.cs 123

Всё ли в порядке в этом коде? Закралась ли тут ошибка? Выглядит крайне странно. Почему разработчик не использовал void?

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

Я решил посмотреть, где вызывается этот метод и используется ли его возвращаемое always true значение. Оказалось, что на него присутствует лишь одна-единственная ссылка в том же классе в словаре commandHandlers, который имеет тип

IDictionary<string, Func<S, Connection, Session.Client, string, bool>>

При инициализации в него добавляются значения

{"state", State},{"startgame", StartGame},{"slot", Slot},{"allow_spectators", AllowSpectators}

и т.д.

Перед нами представлен редкий (мне хочется в это верить) случай, когда статическая типизация создаёт нам проблемы. Ведь сделать словарь, в котором значениями будут функции с различными сигнатурами как минимум проблематично. commandHandlers используется лишь в методе InterpretCommand:

public bool InterpretCommand(  S server, Connection conn, Session.Client client, string cmd){  if (    server == null ||     conn == null ||     client == null ||     !ValidateCommand(server, conn, client, cmd)  )  return false;  var cmdName = cmd.Split(' ').First();  var cmdValue = cmd.Split(' ').Skip(1).JoinWith(" ");  Func<S, Connection, Session.Client, string, bool> a;  if (!commandHandlers.TryGetValue(cmdName, out a))    return false;  return a(server, conn, client, cmdValue);}

Судя по всему, целью разработчика была универсальная возможность сопоставления строкам тех или иных выполняемых операций. Я думаю, что выбранный им способ далеко не единственный, однако предложить что-то более удобное/правильное в такой ситуации не так уж просто. Особенно, если не использовать какой-нибудь dynamic или что-то подобное. Если у вас есть идеи на этот счёт, оставляйте комментарии. Мне было бы интересно посмотреть на различные варианты решения данной задачи :).

Получается, что предупреждения, связанные с always true методами в этом классе, скорее всего ложные. И всё же Пугает вот это вот "скорее всего" :) Нужно быть осторожным с такими штуками, ведь среди них и правда может оказаться ошибка.

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

static bool State(....) //-V3009

Есть и другой способ: можно выделить срабатывания, которые необходимо пометить как ложные, и в контекстном меню кликнуть на "Mark selected messages as False Alarms".

image10.png

Подробнее эту тему можно изучить в документации.

Лишняя проверка на null?


static bool SyncLobby(....){  if (!client.IsAdmin)  {    server.SendOrderTo(conn, "Message", "Only the host can set lobby info");    return true;  }  var lobbyInfo = Session.Deserialize(s);   if (lobbyInfo == null)                    // <=  {    server.SendOrderTo(conn, "Message", "Invalid Lobby Info Sent");    return true;  }  server.LobbyInfo = lobbyInfo;  server.SyncLobbyInfo();  return true;}

Предупреждение анализатора: V3022 Expression 'lobbyInfo == null' is always false. LobbyCommands.cs 851

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

Метод Deserialize никогда не возвращает null в этом можно легко убедиться, взглянув на его код:

public static Session Deserialize(string data){  try  {    var session = new Session();    ....    return session;  }  catch (YamlException)  {    throw new YamlException(....);  }  catch (InvalidOperationException)  {    throw new YamlException(....);  }}

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

Что же мы видим в нижней части? Deserialize не возвращает null, если что-то пошло не так, он бросает исключения. Разработчик, написавший после вызова проверку на null, думал иначе, судя по всему. Скорее всего в исключительной ситуации метод SyncLobby должен выполнять код, который сейчас выполняется да никогда он не выполняется, ведь lobbyInfo никогда не null:

if (lobbyInfo == null){  server.SendOrderTo(conn, "Message", "Invalid Lobby Info Sent");  return true;}

Полагаю, что вместо этой "лишней" проверки всё-таки нужно использовать try-catch. Ну или зайти с другой стороны и написать какой-нибудь TryDeserialize, который в случае исключительной ситуации будет возвращать null.

Possible NullReferenceException


public ConnectionSwitchModLogic(....){  ....  var logo = panel.GetOrNull<RGBASpriteWidget>("MOD_ICON");  if (logo != null)  {    logo.GetSprite = () =>    {      ....    };  }  if (logo != null && mod.Icon == null)                    // <=  {    // Hide the logo and center just the text    if (title != null)    title.Bounds.X = logo.Bounds.Left;    if (version != null)      version.Bounds.X = logo.Bounds.X;    width -= logo.Bounds.Width;  }  else  {    // Add an equal logo margin on the right of the text    width += logo.Bounds.Width;                           // <=  }  ....}

Предупреждение анализатора: V3125 The 'logo' object was used after it was verified against null. Check lines: 236, 222. ConnectionLogic.cs 236

Что-то мне подсказывает, что здесь стопроцентно допущена ошибка. Перед нами уже точно не "лишние" проверки, ведь метод GetOrNull, скорее всего, действительно может вернуть нулевую ссылку. Что же будет, если logo будет null? Обращение к свойству Bounds приведёт к выбрасыванию исключения, что явно не входило в планы разработчика.

Возможно, фрагмент нужно переписать как-то так:

if (logo != null){  if (mod.Icon == null)  {    // Hide the logo and center just the text    if (title != null)    title.Bounds.X = logo.Bounds.Left;    if (version != null)      version.Bounds.X = logo.Bounds.X;    width -= logo.Bounds.Width;  }  else  {    // Add an equal logo margin on the right of the text    width += logo.Bounds.Width;  }}

Данный вариант достаточно прост для восприятия, хотя дополнительная вложенность выглядит не слишком здорово. В качестве более ёмкого решения можно использовать null-conditional operator:

// Add an equal logo margin on the right of the textwidth += logo?.Bounds.Width ?? 0; // <=

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

Быть может, всё-таки OrDefault?


public MapEditorLogic(....){  var editorViewport = widget.Get<EditorViewportControllerWidget>("MAP_EDITOR");  var gridButton = widget.GetOrNull<ButtonWidget>("GRID_BUTTON");  var terrainGeometryTrait = world.WorldActor.Trait<TerrainGeometryOverlay>();  if (gridButton != null && terrainGeometryTrait != null) // <=  {    ....  }  var copypasteButton = widget.GetOrNull<ButtonWidget>("COPYPASTE_BUTTON");  if (copypasteButton != null)  {    ....  }  var copyFilterDropdown = widget.Get<DropDownButtonWidget>(....);  copyFilterDropdown.OnMouseDown = _ =>  {    copyFilterDropdown.RemovePanel();    copyFilterDropdown.AttachPanel(CreateCategoriesPanel());  };  var coordinateLabel = widget.GetOrNull<LabelWidget>("COORDINATE_LABEL");  if (coordinateLabel != null)  {    ....  }  ....}

Предупреждение анализатора: V3063 A part of conditional expression is always true if it is evaluated: terrainGeometryTrait != null. MapEditorLogic.cs 35

Давайте проанализируем данный фрагмент. Можно обратить внимание, что каждый раз, когда используется метод GetOrNull класса Widget, производится проверка на равенство null. В то же время, если используется Get, то проверки нет. Это логично метод Get не возвращает null:

public T Get<T>(string id) where T : Widget{  var t = GetOrNull<T>(id);  if (t == null)    throw new InvalidOperationException(....);  return t;}

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

В коде, приведённом выше, на равенство null проверяется значение, возвращённое методом Trait. На самом деле внутри метода Trait и вызывается Get класса TraitDictionary:

public T Trait<T>(){  return World.TraitDict.Get<T>(this);}

Может ли быть такое, что этот Get ведёт себя не так, как рассмотренный ранее? Всё же классы разные. Давайте же проверим:

public T Get<T>(Actor actor){  CheckDestroyed(actor);  return InnerGet<T>().Get(actor);}

Метод InnerGet возвращает экземпляр TraitContainer<T>. Реализация Get в этом классе очень напоминает Get класса Widget:

public T Get(Actor actor){  var result = GetOrDefault(actor);  if (result == null)    throw new InvalidOperationException(....);  return result;}

Главное сходство состоит именно в том, что и здесь никогда не возвращается null. В случае, если что-то пошло не так, аналогично выбрасывается InvalidOperationException. Следовательно, метод Trait ведёт себя так же.

Да, здесь может быть просто лишняя проверка, которая ни на что не влияет. Разве что выглядит странно, но нельзя сказать, что такой код сильно запутает читающего. А вот если проверка тут как раз нужна, то в некоторых случаях будет неожиданно выбрасываться исключение. Это печально.

В этом месте кажется более подходящим вызов какого-нибудь TraitOrNull. Однако такого метода нет :). Зато есть TraitOrDefault, который и является аналогом GetOrNull для данного случая.

Есть ещё один подобный момент, связанный уже методом Get:

public AssetBrowserLogic(....){  ....  frameSlider = panel.Get<SliderWidget>("FRAME_SLIDER");  if (frameSlider != null)  {    ....  }  ....}

Предупреждение анализатора: V3022 Expression 'frameSlider != null' is always true. AssetBrowserLogic.cs 128

Как и в коде, рассмотренном ранее, здесь что-то не в порядке. Либо проверка действительно лишняя, либо вместо Get всё-таки нужно вызывать GetOrNull.

Потерянное присваивание


public SpawnSelectorTooltipLogic(....){  ....  var textWidth = ownerFont.Measure(labelText).X;  if (textWidth != cachedWidth)  {    label.Bounds.Width = textWidth;    widget.Bounds.Width = 2 * label.Bounds.X + textWidth; // <=  }  widget.Bounds.Width = Math.Max(                         // <=    teamWidth + 2 * labelMargin,     label.Bounds.Right + labelMargin  );  team.Bounds.Width = widget.Bounds.Width;  ....}

Предупреждение анализатора: V3008 The 'widget.Bounds.Width' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 78, 75. SpawnSelectorTooltipLogic.cs 78

Похоже, что в случае истинности условия textWidth != cachedWidth в widget.Bounds.Width должно записываться некоторое специфичное для данного случая значение. Однако присваивание, производимое ниже вне зависимости от истинности данного условия, лишает строку

widget.Bounds.Width = 2 * label.Bounds.X + textWidth;

всякого смысла. Вполне вероятно, что здесь просто забыли поставить else:

if (textWidth != cachedWidth){  label.Bounds.Width = textWidth;  widget.Bounds.Width = 2 * label.Bounds.X + textWidth;}else{  widget.Bounds.Width = Math.Max(    teamWidth + 2 * labelMargin,    label.Bounds.Right + labelMargin  );}

Проверка default-значения


public void DisguiseAs(Actor target){  ....  var tooltip = target.TraitsImplementing<ITooltip>().FirstOrDefault();  AsPlayer = tooltip.Owner;  AsActor = target.Info;  AsTooltipInfo = tooltip.TooltipInfo;  ....}

Предупреждение анализатора: V3146 Possible null dereference of 'tooltip'. The 'FirstOrDefault' can return default null value. Disguise.cs 192

В каких случаях обычно используется FirstOrDefault вместо First? Если выборка пуста, то First выбросит InvalidOperationException. FirstOrDefault же не выбросит исключение, а вернёт null для ссылочного типа.

В проекте интерфейс ITooltip реализуют различные классы. Таким образом, если target.TraitsImplementing<ITooltip>() вернёт пустую выборку, в tooltip будет записан null. Обращение к свойствам этого объекта, которое производится далее, приведёт к NullReferenceException.

В случаях, когда разработчик уверен, что выборка не будет пустой, правильнее будет использовать First. Если же такой уверенности нет, то стоит проверять значение, возвращаемое FirstOrDefault. Довольно странно, что здесь этого нет. Ведь значения, возвращаемые рассмотренным ранее методом GetOrNull, всегда проверялись. Отчего же тут этого не сделали?

Да кто его знает А, точно! Наверняка на эти вопросы может ответить разработчик. В конце концов ему этот код и править :)

Заключение


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

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

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

image11.png

Статический анализ как раз и является удобным дополнением к другим способам проверки качества исходного кода, таких как code-review. PVS-Studio найдёт "простые" (а иногда и не только) ошибки вместо разработчика, позволяя людям сосредоточиться на более серьёзных вопросах.

Да, анализатор иногда выдаёт ложные срабатывания и не способен найти вообще все ошибки. Но его использование сэкономит кучу времени и нервов. Да, он не идеален и иногда ошибается и сам. Однако, в общем и целом PVS-Studio делает процесс разработки намного проще, приятнее и даже (неожиданно!) дешевле :).

На самом деле не нужно верить мне на слово куда лучше будет убедиться в правдивости вышесказанного самостоятельно. По ссылке можно загрузить анализатор и получить триальный ключ. Куда уж проще? :)

Ну а на этом всё. Спасибо за внимание! Желаю вам чистого кода и такого же чистого лога ошибок!


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Nikita Lipilin. Unicorns break into RTS: analyzing the OpenRA source code.
Подробнее..

Перевод Поиск инвестиций. Часть 1 если ищете средства на разработку игры

18.09.2020 16:04:09 | Автор: admin

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


Изучая материалы по этой теме им попалась серия статей, создаваемая при поддержке Epic Games, Майком Фаттером. Оценив качество материала и потенциальную пользу для русскоязычного коммьюнити, команда Rummy Games решила перевести статью и выложить ее на нашем ресурсе.


Создание видеоигры это совокупность непростых творческих задач.



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


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


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



Русский инвестор питчит стартаперов и геймдевелоперов


Венчурный капитал, бизнес-ангелы, привлечение займа, частные инвестиции Что все это значит?


Частные инвестиции (ЧИ)


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


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


Providence Equity Partners, Insight Venture Partners и Lightspeed Venture Partners объединения частных инвесторов, которые поддерживают индустрию видеоигр в том числе. Существуют также фонды, которые специализируются исключительно на видеоиграх, например: Makers Fund, Kowloon Nights и Hiro Capital. Такие фонды создаются для поддержки студий и новаторов геймдева.


Венчурный капитал (ВК)


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


К венчурным компаниям, инвестирующим в игры, относятся Sequoia Capital, Kleiner Perkins Caufield & Byers и Andreessen Horowitz.


Бизнес-ангелы


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


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


Краудфандинг или народное финансирование


Double Fine Adventure 2012 года (позже переименованная в Broken Age) это не первый краудфандинговый проект для видеоигр, но именно благодаря ему платформа Kickstarter стала популярной среди разработчиков. Идея проста презентуйте свою игру аудитории и предложите профинансировать ее заранее, например, за несколько лет до даты выхода. На практике краудфандинг тяжелый и напряженный способ финансирования. Этот пузырь давно лопнул. В 2019 видеоигры получили всего 16,9 млн долларов на Kickstarter против 50 млн в 2012 году. На сегодняшний день на платформе размещено более 50 тысяч проектов в категории игры, из которых успешно финансируются только 20 тысяч (стоит отметить, что в это число входят кампании по созданию настольных игр, которые составляют основную часть в этой категории). Краудфандинг не стал панацеей в вопросах финансирования проектов по созданию видеоигр, но остается одним из возможных вариантов для некоторых разработчиков.


Помимо Kickstarter-а существуют и другие площадки. Например, Indiegogo, которая предлагает полезную опцию в виде передачи собранных средств проектам, не достигшим поставленной цели.


Краудинвестинг или акционерный краудфандинг


Модель краудинвестинга сочетает в себе краудфандинг и классический подход к финансированию проектов по созданию видеоигр. Fig платформа для краудфандинга и микроинвестиций, созданная в 2015 году, модель которой оказалась довольно успешной 25 из 39 кампаний привлекли финансирование (64%). Благодаря краудинвестингу, свет увидил такие проекты, как Psychonauts 2 от Double Fine и Soundfall от Drastic Games. Платформа также принесла инвесторам прибыль в пяти из одиннадцати выпущенных игр. Fig продолжает развивать свою бизнес-модель, недавно расширившись до концепции Открытый доступ (Open Access). Open Access это возможность финансирования игровых проектов на стадии раннего доступа.


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


На самом деле, результативность гораздо выше, когда мы стараемся усилить то, что уже есть у разработчиков, поясняет Бейли. Все хотят быть сопричастными к истории успеха. И всем нравится, когда проект уже набрал обороты. Ведь сложнее всего это сдвинуть дело с мертвой точки. Поэтому, приходя к нам, разработчику следует думать так: Через эту платформу я смогу привлечь, до половины трафика и финансирования, которые нужны для успеха моей кампании.


Мы обсудим краудфандинг и краудинвестинг более подробно в следующих статьях.


Издатели


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


Гранты и налоговые льготы


В зависимости от географического положения вашей студии, вы можете иметь право на получение определенных видов финансирования. Канадский фонд СМИ (Canadian Media Fund) и Игровой фонд Великобритании (UK Games Fund) два примера государственной финансовой поддержки местных компаний, работающих с цифровыми интерактивными медиа. Штат Джорджия в США и канадская провинция Квебек являются примерами региональных правительств, предлагающих налоговые льготы.


Точно так же некоторые компании предлагают премиальные выплаты или возможности финансирования для использования определенных инструментов. Выделив 100 млн. долларов, Epic Games создали инициативу под названием Epic Mega Grants, в рамках которой предоставляется финансирование для широкого круга проектов и авторов. Включая разработчиков игр и инструментов для них, студентов, преподавателей, работающих в академических учреждениях, создателей средств массовой информации, медиа-развлечений и прочего. Если вы работаете с Unreal Engine или предоставляете инструменты и ресурсы с открытым исходным кодом, подача заявки на Epic Mega Grants может стать перспективным шагом.


Финансы не проблема, а возможность


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


Я наблюдаю это каждый день, обычно это выглядит так: Слушай, мне просто нужны деньги. Мне неважно, будут они от венчурных капиталистов, издателей или банков. Мне просто нужен мешок денег, говорит Джейсон Делла Рокка, соучредитель Execution Labs. Но так не пойдет.
Делла Рокка утверждает, что инвесторы ищут возможности по заработку, а не кому бы помочь. Позиционирование финансовой потребности вашей студии или игры как проблемы вряд ли привлечет инвестора.


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


В конечном итоге это приводит к серьезной дилемме: привлекать займы или использовать собственные средства. Об этом мы поговорим в следующей статье.


Ваши принципы путеводная звезда при поиске финансирования


Привлечение инвестиций дело непростое. Здесь важно помнить, что частные инвестиции или займы это долгосрочное предложение. Вы собираетесь работать со своим инвестором в течение нескольких лет, и очень важно, чтобы вы нашли подходящего партнера.


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


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


Студии-разработчику Polyarc (Moss) потребовалось несколько лет на поиск правильного инвестора. Генеральный директор и соучредитель Тэм Армстронг рассказал, что он и его команда потратили годы, выбирая между продажей доли компании и займом.


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


Армстронг отметил, что существуют соглашения о привлечении займов, которые позволяют разработчикам сохранять контроль над своей интеллектуальной собственностью. Лучше обратиться к юристу за проверкой условий договоров и консультацией по вопросам собственности, контроля и использования ИС в каждом конкретном случае. В конечном итоге Polyarc привлекла частные инвестиции, сохранив контроль над своей интеллектуальной собственностью.


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



Что дальше


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


P.S. Статья переведена на русский язык и отредактирована командой разработки игры Saturated Outer Space, выпускниками образовательной программы Менеджмент игровых проектов. В настоящее время ребята ищут финансирование для своего проекта. Увидев эту статью на портале www.unrealengine.com, они решили поделиться ей с русскоязычным комьюнити. Если вы захотите отблагодарить их за перевод, то добавляйте игру в свой вишлист в Steam.


Майк Фаттер журналист-фрилансер и консультант в игровой индустрии. Автор книги Gamedev Business Handbook (Руководство по ведению бизнеса в сфере разработки видеоигр), а также соавтор подкаста Virtual Economy.

Подробнее..

Перевод Поиск инвестиций. Часть 2 в нужном месте в нужное время

21.09.2020 18:14:10 | Автор: admin

Добро пожаловать во вторую часть нашего цикла про поиск инвестиций в игровой индустрии. В первой части мы обсуждали различные типы инвестиций, а также первые шаги на этом пути.


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



Инвестиции в акционерный капитал или финансирование проекта


Начать стоит с того, чтобы установить, что же вы ищете: финансирование игры или инвестиции в вашу компанию?


Венчурные капиталисты, бизнес-ангелы и другие крупные инвесторы чаще всего заинтересованы в долевом участии (вложение в уставной капитал компании). Вряд ли они согласятся профинансировать производство всего одной игры.


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


Сложность как раз в том, чтобы определить, что именно вам подходит.


Практика показывает: если вы делаете игру премиум-класса или что-либо линейное с акцентом на повествование выбирайте финансирование проекта, рассказывает сооснователь Execution Labs Джейсон Дела Рокка, Но если у вас в планах free-to-play проект или игра-сервис, которые подразумевают большую базу пользователей и многоразовое масштабирование проекта, конечно, лучше идти по пути продажи акций.



The Darwin Project от Scavengers Studios


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


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


Большая часть диалогов с разработчиками начинается с вопросов:


Что конкретно вы делаете? Какими возможностями располагаете? Какую игру или студию вы создаете?


Лиха беда начало


Методы поиска источников финансирования эволюционируют вместе с вашей компанией.


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


Большое количество новых студий существуют за счет привлечения на ранней стадии так называемых love money инвестиции от друзей и родственников. Это не мешает им зачастую просить взамен акции или часть прибыли. Love money могут стать мощной поддержкой на старте проекта, особенно если учесть, что получить финансирование под прототип довольно трудно.
Другой способ преодолеть трудности на старте проекта собственные сбережения и прочие источники дохода.


Вам предстоит занимать, тратить сбережения и просить помощи до тех пор, пока вы не доведете свой прототип до нужного уровня. До того, который подойдет для презентации издателю или инвестору. А это задача не из легких, отвечает Каллум Андервуд, сооснователь Robot Teddy и менеджер портфеля проектов Kowloon Nights, Представьте, как ограничены возможности людей из бедных слоев населения или резидентов стран с малым доступом к технологиям и к индустрии в целом. У них нет денег и связей с таким количеством разработчиков, как, например, у жителей Лос Анджелеса.


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


Мы знали, что не найдем финансирования в самом начале, но решили, что нам помогут связи, Тэм Армстронг, генеральный директор Polyarc.


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


Как надо и как не надо презентовать проект


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


Составлять такое письмо это как ходить по минному полю,- говорит Андервуд.


Например, зачем в письме спрашивать меня: Хотите ли вы посмотреть наш билд? Просто пришлите его. Если не захочу, то и открывать не стану. Решению о заключении договора это никак не препятствует.


Если слишком сложно решить, что написать, предлагаю краткое содержание:


  • Вот билд;
  • Вот видео;
  • Вот что я хочу;
  • А вот сроки разработки.

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


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


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



Godfall от Counterplay Games and Gearbox Publishing


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


Будет отлично, если в ходе презентации покажете короткое видео последней версии играбельного билда.


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


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


Поймите, что вы хотите от будущего партнера, и каковы его цели. Как только вы будете полностью уверены в этом, идите к нему, а не ко всем подряд,- призывает Андервуд.


Я нахожусь в уникальной позиции: сотрудничаю с Kowloon Nights в сферах бизнеса и поиска стартапов, работаю с Superhot подготавливаю Superhot Presents, консультирую и ищу стартапы для Oculus. Но нет смысла идти с одинаковой презентацией проекта во все эти компании.


Осознание этого факта является камнем преткновения для многих разработчиков.


Если вы просто зайдете на сайт Kowloon Nights, вы не найдете там слов VR и AR. Так зачем идти с VR игрой к Kowloon? То же самое мы увидим на сайте Superhot там нет ни VR, ни AR. Остается очевидный выбор Oculus, а не предыдущие 2 компании. Удивительно, как часто люди упускают это. Ведь можно потратить 10 минут на изучение потенциального партнера, а не биться о стену


Во время презентации проекта с издателем или инвестором важно знать, какими финансовыми возможностями они располагают.


Приходя на встречу с несоответствием желаемого и возможного, можно уйти ни с чем.


Если вы хотите 3 миллиона долларов на не-VR игру, то узнайте, кто из издателей сможет позволить себе такие траты или измените запрашиваемую сумму. В Superhot Presents мы получаем удивительное количество подобных заявок, что печалит, так как разработчики впустую тратят на их подготовку свое время.


При поиске инвестора есть еще один важный аспект. Проектное финансирование обычно краткосрочно. А продажа доли компании бизнес-ангелу или венчурным капиталистам это долгосрочные взаимоотношения. Polyarc объяснили, как они выстраивали диалог с каждым типом инвесторов.



Moss от Polyarc


Мы всегда заранее знали, с кем мы говорим, рассказывает Армстронг.


Мы подгоняли презентацию к тому, что от нас хотят услышать. Ребята, делающие инвестиции в компанию, хотят узнать о ее глобальной стратегии. Они хотят представлять, что случится с компанией через 2,3,4 года, через 5 лет, и какой вообще план действий.


Для финансирования отдельного проекта эта информация не нужна. При финансировании проекта важен, собственно, сам проект, и обоснованные предположения по его успешности. Это является лучшим стимулом для проектного инвестора.


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


Также необходимо понимать метрики, на которые обращают внимание инвесторы. С тех пор, как индустрия игр начала двигаться в направлении игр сервисов (free-to-play и premium), акционеры фокусируют внимание на них. Для увеличения шансов при поисках инвестора на такие проекты Дела Рокка советует уйти от производственных показателей.


Если вы презентуете достижение производственных целей, то вы совершаете ошибку, объясняет Дела Рокка. Когда я презентую достижения по увеличению коммьюнити игры, это звучит как У нас был ЗБТ пришла 1000 человек. Затем у нас был еще один ЗБТ, но уже с 5000. А еще мы отслеживали сессии, и все, кто играл приходили на следующий день и играли еще по 20 сессий.


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


Согласно утверждениям Дела Рокка, гораздо ценнее сообщить потенциальному акционеру об увеличении пользователей проекта и ретеншена, чем говорить с ним о разнообразии видов оружия в вашей игре.


Это показывает, что вы видите тенденции, знаете важные показатели. Нужно ли делать еще один вид оружия в игре? Это не имеет значения!


Пример графика долевого финансирования


В 2018 году на презентации во время Game Developers Conference, Дела Рокка предоставил график для поиска долевого финансирования на разных стадиях проекта. Все начинается с самофинансирования и love money (если они доступны).



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


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


А после прохождения точки безубыточности новые раунды инвестирования способны подстегнуть рост компании, предоставляя дополнительные средства на обеспечение значительного увеличения аудитории игры.


Дела Рокка также показал визуализацию пути проектного финансирования. При просмотре этого графика необходимо учесть:


  1. издатели, о которых мы поговорим более детально в следующей статье, охотнее финансируют уже существующие команды с прототипом и рассчитанным бюджетом;
  2. курс краудфандинга сместился с 2012 г, когда Double Fine прекратили золотую лихорадку Kickstarter. За последние 8 лет краудфандинг перешел со стадии концепта до довольно поздних стадий цикла производства.


Реальность и отказы


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


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


Я не ожидал, что получу столько звонков от людей, которым до индустрии нет никакого дела, рассказывает Армстронг.


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


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


Мы с такими тоже общались, но чаще всего они говорят, что хотели бы оставаться на связи и узнавать о прогрессе.


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


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


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


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



Disc Jam by High Horse Entertainment


Как вариант, вы можете решить пойти дальше вообще без инвестиций. Независимость достается дорогой ценой. Особенно тем, кто находится в ААА сегменте. High Horse Entertainment, студия из двух людей, сочетающая работу по контракту и собственные разработки. Дуо Типа Раппа и Джея Маттиса работали в Activision, у обоих есть опыт в лайв-сервисах и сетевом программировании. Этот опыт стал решающим для успеха их первой игры Disc Jam.


Сегодня High Horse остается независимой, стойко игнорируя предложения по приобретению доли в компании и по инвестициям.


Когда к тебе приходят, чтобы вложить деньги в компанию, это обычно происходит по одному-двум сценариям. К примеру: Эй, мы компания, у которой не получается делать сетевые проекты правильно. У нас была пара попыток, но их хватило, чтобы понять, как это сложно. Нам нужны профессионалы, которые сделают это и создадут нужные технологии. Это один случай. Бывало и так: Disc Jam крутая игра. У вас, ребята, много пользователей. Вы сделали кросс-платформенный сетевой тайтл. Мы хотим запрыгнуть к вам на этот проект и на другие тоже. Как насчет того, что мы вас купим и уже сообща заработаем?


Были еще версии: Нам нравится то, что вы сделали. Как вам идея завершить этот проект и привнести свою экспертизу в другую команду, которая уже готова или только набирается?


Эти 3 сценария выглядят разными, но суть всегда одна и та же. Причина, по которой мы покинули Activision изначально была в том, что мы хотим делать что-то свое. Мы хотели стать независимыми разработчиками.


Найдите время для поиска средств


Довольно просто забыть, что поиск инвестиций это процесс, проходящий вне традиционных собраний и мероприятий, подобных Game Developer Conference и другим конференциям.


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


Разработка питча, создание играбельного билда, исследование рынка, звонки, отправка электронных писем все это может отнимать много вашего времени. Пока студия еще маленькая и находится в начале своего пути, поиск инвестиций должен стать одной из главных целей.
Я могу сказать, что это занимает существенное количество времени, объясняет Армстронг. Это работа. Количество времени, которое я тратил на поиск инвесторов, было не абсолютным лишь потому, что еще я занимался программированием и дизайном самой игры. В среднем это занимало более одного рабочего дня в неделю. Так как больше дня, то точно больше 20% всего рабочего времени. А должно бы было занимать все 50%.


И даже при этом Армстронг не уверен, что в Polyarc потратили достаточно времени на поиск инвестиций. У студии тогда не было специалиста по данным вопросам.


А если бы и был, пошло ли дело быстрее и эффективнее? Скорее всего. Верхнего порога по времени на поиски нет. Я не видел никого, кто потратил на это слишком много времени.


Майк Фаттер журналист-фрилансер и консультант в игровой индустрии. Автор книги Gamedev Business Handbook (Руководство по ведению бизнеса в сфере разработки видеоигр), а также соавтор подкаста Virtual Economy.


Статья переведена на русский язык и отредактирована командой разработки игры Saturated Outer Space, выпускниками образовательной программы Менеджмент игровых проектов. В настоящее время ребята ищут финансирование для своего проекта. Увидев эту статью на портале www.unrealengine.com, они решили поделиться ей с русскоязычным комьюнити. Если вы захотите отблагодарить их за перевод, то добавляйте игру в свой вишлист в Steam или пишите им в группу в VK.

Подробнее..

Универсальный менеджер приложений (игр)

20.12.2020 22:09:52 | Автор: admin

Любителям Linux-like систем наверняка приходилось устанавливать приложения по найденным руководствам в сети. В итоге описание оказывалось устаревшим/нерабочим, и для получения конечного результата приходилось собирать по крупицам и сводить воедино сведения из различных источников для "похожих" ОС. Иногда вам везло и все заводилось с первого раза.


Либо пытались поиграть в свою любимую игру детства, где для запуска важно установить нужную версию Wine с правильными настройками, или пошаманить с монтированием образов в Dosbox...


Предлагается очередной способ решения подобных проблем.


Для игр существует, конечно, единственный правильный способ установка Windows на отдельный диск. Но в мире насчитывается 1.3 миллиарда PC-геймеров. Из них пользователи MacOS (3%) и Linux (1%) 50 миллионов человек. Поэтому эта статья для них.


Сегодня существует огромное разнообразие средств, упрощающих установку и запуск игр и, скорее всего, вы уже нашли все любимые игры детства на Steam и давно наслаждаетесь ими в своем Ubuntu. Но не все так просто. Например, любителей старых Point-n-Click адвенчур ждет разочарование: очень многих шедевров нет в наличии ни у одного дистрибьютора (distributor) видеоигр.


Дистрибьюторов можно условно разделить на:


Универсальные:


  • Steam сервис цифровой дистрибуции, изначально для игр и апдейтов от Valve, но позже расширен и для других разработчиков;
  • GOG платформа дистрибуции видеоигр и фильмов, подразделение CD Project (которые Ведьмак и Cyberpunk), расположенное в Польше;
  • Humble Store онлайн магазин игр.

Узко-специальные:


  • Origin игры Electronic Arts;
  • Uplay игры Ubisoft;
  • Epic Games Store игры Epic Games;
  • Blizzards Battle.net игры Blizzard.

Дружественнее всех зарекомендовал себя GOG. Именно они предоставляют игры в т.н. DRM-формате. Но, в последнее время, их инициатива с Galaxy клиентом тоже попахивает неудобствами и привязками.


Все основные дистрибьюторы поставляют собственные мульти-платформенные клиенты.


Разнообразие клиентов породило дополнительную прослойку абстракции для конечных пользователей т.н. менеджеров игр (game managers). Вот самые известные из них:


  • PlayOnLinux/BSD/Mac
  • Lutris
  • Homura

Менеджеры игр, в свою очередь, используют т.н. запускатели (runners) для запуска на конечном устройстве пользователя, иногда с дополнительными прослойками (например, Vulkan для Wine).


Основные запускатели:


  • Wine эмулятор Windows
  • Dosbox эмулятор DOS
  • ScummVM виртуальная машина для многих старых квестов
  • Эмуляторы консолей

PlayOnLinux/BSD/Mac


Пациент скорее мертв. Судя по активности на github, четвертая версия давно мертва, а пятая никогда не родится. Хотя, кто его знает...


Достоинства:


  • Приличная база поддерживаемого софта и игр.

Недостатки:


  • Оборачивает исключительно Wine;
  • Кодовая база не обновляется;
  • Вся логика построена на полусотне функций адового bash-скриптинга;
  • Новые скрипты принимаются через веб-форму (ссылку на сайте найти не удалось).

Lutris


На сегодняшний день лидер и стандарт для Linux.


Достоинства:


  • Ansible-like скриптинг;
  • Добавление игр из gog и steam аккаунтов одним кликом.

Недостатки:


  • Вся база с играми вынесена на отдельный полузакрытый сервер;
  • Неудобная форма для добавления новых скриптов (нет версионности и т.п., решение по добавлению выносит непонятный комитет);
  • Поддержка только Linux;
  • Создает отдельный Wine-префикс для каждой новой игры (а они довольно увесисты);
  • Нет клиентского API, отсюда UI довольно уныл;
  • Нет возможности установки из образов (актуально для старых игр).

Homura


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


Разработаем свою собственную систему, удовлетворяющую следующим критериям:


  • Расширяемость;
  • Гибкость;
  • Функциональность;
  • Кроссплатформенность.

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


  • Удобство добавления новых игр\приложений. Любой пользователь может без труда добавить поддержку нового софта через github репозиторий;
  • Отказ от серверной части. База с приложениями должна быть частью самого менеджера и обновляться вместе с ним;
  • Предоставление удобного API\CLI для создания сторонних UI;
  • Отделение изменяемых данных (например, save-ов для игр) от остальных ресурсов приложения;
  • Предоставление базовой возможности инсталляции с медиа-носителя. Важно для редких старых игр, которых не найти через онлайн дистрибьюторов, зато относительно легко найти их медиа-образы;
  • Никакой черной магии в скриптах, каждый шаг должен быть понятен без изучения исходников.

Нечто способное объединить и заменить apt в Ubuntu и pkg в FreeBSD (и при этом еще обладать возможностями Lutris).


Предлагается реализовать всю систему на базе Ansible и его мощной скриптовой поддержкой в виде плей-буков (playbooks). Почему Ansible? Хотя бы потому, что для достижения всех вышеназванных целей вам в любом случае придется разработать свой собственный Ansible-like движок, а это весьма затратно по времени.


Полученное решение (yag) выложено в свободный доступ на github.


Рассмотрим примеры добавления новых игр в базу.


Ace Ventura Pet Detective


Старая, мультяшная Point-n-Click адвенчура. Распространялась на 1CD.


Для добавление поддержки создаем папку в ports/games со следующей структурой:


aceventura:    tasks:        install_image.yml        main.yml        run.yml    info.yml

Структура остается на усмотрение автора, обязательными являются лишь info.yml (описание игры) и main.yml (точка входа). Код можете посмотреть в репозитории, опишу лишь суть.
Установка производиться с помощью команды:


yag install aceventura --source=/path/to/CD1.iso

В install_image.yml передается путь к образу (/path/to/CD1.iso), где производится распаковка и копирование. Источником может быть как исполнимый файл с GOG-а (конкретно этой игры там нет), так и любой другой формат носителя.


Запуск:


yag run aceventura

Игра требует наличия save-ов в основной папке, поэтому запускающий скрипт (run.yml) копирует save-ы из постоянного хранилища и запускает wine с правильными параметрами. При выходе новые save-ы копируются обратно в хранилище. За сейвами во всех играх нужно внимательно следить и не забывать их сохранять (save your saves), создавая символические ссылки там, где это возможно. Также для запуска именно этой игры нужно включить virtual desktop:


- wine:    exec: "{{ app_folder }}/Ace.exe"    virtual_desktop: "800x600"

Обо всех поддерживаемых параметрах модуля wine читайте здесь.


Bad Mojo (redux)


Тоже классика, в которой вам предлагается вжиться в роль милой букашки.


Структура папок аналогична первому примеру:


badmojo_redux:    tasks:        main.yml        run.yml        install_image.yml        install_innosetup.yml    templates:        BADMOJO.j2    vars:        main.yml    info.yml

Здесь уже поддерживается несколько вариантов установки:


yag install badmojo_redux --source=/path/to/CD.iso

  • запустит install_image.yml (распаковка CD образа)

yag install badmojo_redux --source=/path/to/badmojo_gog_setup.exe

  • запустит install_innosetup.yml (распаковка GOG-овского innosetup exe-шника).

Игра требует точного указания путей к ресурсам, поэтому создаем Jinja-совместимый шаблон конфига (BADMOJO.j2).


Запуск:


yag run badmojo_redux

Как видите, внутри скриптов нам доступна вся мощь Ansible, к тому же мы можем спокойно дописывать свои модули.


Другие примеры можно посмотреть здесь


Недостатки


И все-таки Ansible это в первую очередь про развертывание микросервисов на "тысячах нод". Использовать его для локальной установки игр, а тем более заменять им apt/pkg, с одной стороны, забивание гвоздей микроскопом, но с другой, если вся необходимая функциональность имеется почему бы и нет? Более того, недостаток может легко превратиться в достоинство при реализации, например, онлайн сервиса видеоигр, где развертывание приложений на удаленные сервера станет необходимостью (кстати, если кто-то умеет звук\видео в VNC пишите в личку, есть идея для неплохого стартапа).


Следствие главного недостатка задержки при выполнении скриптов. Не критично при инсталляции, но может раздражать при запуске.


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


Возможности


Описанный подход открывает много возможностей. Выше упоминалось о полноценной замене apt/pkg во всех дистрибутивах всех ОС. Согласитесь, было бы здорово, если бы, например, для установки Skype вам не пришлось качать deb-файл в Ubuntu, шаманить с pacman в Archlinux или мучиться с эмулятором linux в FreeBSD, а просто выполнить, например:


yag install skype

и получить одинаковый результат в любой ОС.


При этом все скрипты установки для разных ОС версионированы и поддерживаются в актуальном состоянии сообществом пользователей! А мощь Ansible-playbooks позволяет гибко реализовывать любые механизмы добавления в любой ОС.


Теперь самое важное: если вам понравилась идея, важно чтобы именно вы добавили свою любимую игру\программу в репозиторий, оформив простой PR на github.


Спасибо!

Подробнее..

Опыт разработки первой мобильной игры на Unity или как полностью перевернуть свою жизнь

16.02.2021 16:13:42 | Автор: admin

От кого и для кого

Доброго времени суток! Меня зовут Николай, и я хочу рассказать свою историю и поделиться своим небольшим опытом в разработке своей первой игры. С чего начинал и какие трудности пришлось преодолеть на пути разработки. Статья ориентирована на тех, кто начинает, думает начать или уже разрабатывает свою первую игру. Зачем? Потому что на стадии разработки своей первой игры, сам не однократно читал статьи о подобном опыте, после прочтения которых "наматывал сопли на кулак" и продолжал разработку дальше. От идеи до выпуска в магазин.

Внимание! Статья получилось длинной, так что запаситесь чаем! Если не хочется долго читать, то выжимка из советов в концы статьи.

С чего все начиналось

Шел третий курс универа

По образованию я моряк, однако, после первого рейса, я понял, что перспектива отстуствия на "земле" по 6 месяцев в году меня не радовала, хоть по зарплате в этой отрасли никого не обижают.

Выбор направления

Появилась острая необходимость найти "дело", которое будет приносить удовольствие, не придется отрываться от современного мира на длительный срок и иметь финансовый достаток в перспективе сравнимый с моей по образованию профессией. Конец 4 курса универа и мой выбор пал на IT индустрию, а именно на python разработчика. Уделив 2 недели теории, в частности технической документации языка, я начал развивать логику и выполняя задачки каждый день на протяжении полугода, пока в конце декабря 2018 года не обнаружил геймдев.

А вот и Unity!

Выглядит комично или даже банально, но я повелся на клик-бэйт видео с подобным названием "Как сделать свою первую игру за 15 минут" или "Делаю крутую игру за 5 минут без регистрации и смс". Посмотрев данные материалы, в голове появилась мысль, выделить себе пару дней в своем графике, и утолить свое любопытство, установив данную среду разработки на свой компьютер. Потыкав разные кнопочки, и написав код методом "copy-paste", я пришел в неописуемый восторг! Моя творческая натура внутри меня ликовала. Ведь это было так приятно наблюдать за тем, что ты "сам" написал пару минут назад, сейчас заставляет кубик крутиться, перемещаться или менять цвет. Так уж вышло, что средой разработки установленной на мой компьютер оказалась Unity.

Почему Unity?

Он бесплатный, не такой сложный в освоении, большое сообщество и тонны ресурсов для самообучения, поэтому отлично подходит для начинающих разработчиков. Мобильный рынок заполнен проектами созданные на Unity. Даже такие крупные компании как Blizzard, Riot Games, CD Project RED выпустили всеми известные хиты как Hearthstone, Wild Rift и Gwent, используя эту платформу. Приняв волевое решение, я решил уйти в геймдев на пару с Unity.

Подготовка к разработке

Формирование идеи

Определившись с выбором рабочей платформы для разработки игры, я отправился читать статьи людей, у которых имелся уже хоть какой то опыт в данной сфере, чтобы выбрать нужное мне направление. ПК или смартфоны? 2Д или 3Д? Сингл или мультиплеер?
Прочитав большое количество статей и проанализировав их, все советы сходились к тому, чтобы:

  1. Проект не разрабатывать больше 2-х месяцев, иначе увеличивается вероятность потерять энтузиазм и он окажется на полке "потом доделаю" так не дойдя до выпуска;

  2. Проект должен быть простой, легкий, желательно иметь небольшую изюминку. Иначе нафантазировав себе в голове крутой ААА проект с сетевым режимом и открытом миром, рискуете себе "сломать зубы", потеряв всякую мотивацию к разработке и потеряться где то в пучине депрессии и отчаяния;

Мой выбор

2Д мобильная аркада с сетевым режимом до 6 человек , рейтинговой системой и вознаграждением. Разработка, которой заняла отнюдь не 2 , а все "12 месяцев".

Аргументы "за":

  • Мне показалось заставлять двигаться объекты будет проще, чем те же 3Д;

  • Мобильный рынок огромен и его доля более половины всей игровой индустрии;

  • Писать сюжеты для игр я не умею, да и опыта в этом нет никакого, поэтому я решил сделать упор на веселье. А играть всегда веселее вместе! Поэтому сетевая;

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

Аргументы "против":

  • Игра уже становилась не так уж проста, как советовали более опытные коллеги;

  • Сложность и продолжительность разработки для не опытного "птенца" увеличивалась многократно.

Аргументы "за" были очень привлекательны и я решил рискнуть. Как говорится - "Чем чёрт не шутит" и "Была не была"!

Знакомство с Unity и его изучение

Учится чему то с нуля, это как учится кататься на велосипеде. Главное стараться и упорно трудиться, и тогда рано или поздно точно должно получится.
Начинал с малого, а именно перемещал разные объекты в пространстве, писав самый примитивный код.

Совет: знайте, чтобы вы не делали или не хотели начать делать, это с вероятность больше 90% уже было сделано до вас. Научитесь правильно искать нужную информацию!!!

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

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

Как только я накидал определенный план действий и уже собирался начать делать свой первый будущей "шедевр", встал серьезный вопрос

Где я возьму картинки, музыку и остальные элементы для своей будущей игры? Ведь я совершенно не умею сам это создавать

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

А ты сам все это нарисовал? А музыку ты писал тоже сам?

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

Совет: Не чурайтесь использовать чужие наработки или шаблоны, которые продают или прибегать к работе фрилансеров! Это взаимосвязанная выгода! Конечному пользователю все равно, сами вы рисовали самолетик несколько часов или потратили 10$ на его покупку в магазине, ведь главное результат!

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

Совет: Отслеживайте скидки на продаваемые ассеты в различным магазинах, особенно под новый год! Можно приобрести кучу ассетов по выгодной цене со скидкой до 90% в такое время.

Непосредственная разработка

Первые шаги

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

На этом этапе моя игра имела следующий вид:

Главное начатьГлавное начать

Разумеется, я сразу начал осваивать сетевой режим, расчитывая на это потратить не больше двух недель. Я еще никогда так не ошибался. Вместо предполагаемых 2-х недель, ушло не меньше 2-х месяцев только на освоение софта для сетевой игры. Сам контент для игры простаивал.

Совет: Не думайте, что сетевая игра, будет легче чем написать простенький сюжет. Это совершенно не так.

От простого к сложному

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

Эх, как же сильно была переработана финальная версия интерфейсаЭх, как же сильно была переработана финальная версия интерфейса

Интерфес и меню

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

Совет: Перед тем как создавать свое меню, поиграйте в разные игры подобного или смежного жанра. Выделите для себя полезные фичи интерфейса и меню. Что в них интересного и что вам нравится.

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

  • усталость

  • потеря интереса

  • неувереность в своих силах

  • все кажется адом и этому нет конца и края

Совет: Скажу то, что я прочитал когда сам проходил этот этап. НЕ СДАВАЙСЯ! Как бы не было сложно, ни в кое случае НЕ СДАВАЙСЯ и НИ ШАГУ НАЗАД! Дойдя до самого конца ты познаешь лавину экстаза и самоудовлетворения от того, что ты не бросил все! И разумеется бесценный опыт!!!

Однопользовательский режим

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

Концептуальные различие с финальной версией отстствуютКонцептуальные различие с финальной версией отстствуют

Оптимизация

Фух! Оптимизация это такая штука, о которой ты начинаешь задумываться, когда твой проект на смартфоне выдает 10-15 кадров в секунду с фризами и просадками до 4 кадров секунду. Как только тестирования проекта доходит до сборки его на телефоне, все встает на свои места. При разработке прилождений на мобильные смартфоны, оптимизация имеет очень важную роль. Ведь в них не скрывается такая вычислительная мощность как на ПК.

Я начал оптимизировать свой проект, только спустя 8 месяцев разработки. Из-за чего оптимизировать надо было много и долго:

  • картинки

  • материалы

  • звук

  • шейдеры

  • настройки камеры, рендеринга

  • интерфейс

  • скрипты

Это заняло у меня еще не меньше двух недель.

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

Одна голова хорошо, а несколько лучше

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

Совет: Найдите так называемых "жертв", которых будете использовать в качестве тестировщиков среди ваших родственников, близких друзей и коллег по цеху. Брат, сестера, мама, папа, парень, девушка, друг, подруга, кто угодно. Дайте ему смартфон с игрой, посадите его рядом и просто наблюдайте, что ему не нравится, а что нравится. Главное - просто молчите и смотрите!

Реклама и внутриигровые покупки

Настал черёд встроить в свой проект рекламу и сделать магазин. Кто бы что не говорил, но на одном энтузиазме далеко не уедешь и святым духом сыт не будешь. Посему, это необходимый блок разработки. Тут главное грамотно подойти к этому делу, чтобы игрок не "плевался" и выгода была для обеих сторон!

Софта для рекламных интеграций имеется множетство, в том числе и от самой Unity, так называемая Unity Ads. Однако, мой выбор пал на Google AdMob. Почему не Unity Ads? Почитав обзоры, я узнал, что контент рекламы содержит казино, рулетки и ставки. Тут уже на вкус и цвет, как говорится, но я не хочу чтобы реклама была связана с подобного рода сервисами. Я использовал межстраничную и рекламу с вознаграждением.

Совет: Реклама с вознаграждением, намного лучше, ведь игрок сам нажимает на просмотр рекламы, чтобы получить какие-либо "плюшки" в игре. Разработчик и пользователь в плюсе!

Покупки в игре, я реализовал подобным образом:

Финальная версия игры

"12 месяцев" кропотливой работы , и финальная версия выглядит примерно так:

Меню игрыМеню игрыСетевой ГеймплейСетевой Геймплей

Казалось бы, все сделано и осталось дело за малым, а именно опубликовать игру. Но не все так просто как кажется!

Совет: Тут необходимо открыть еще одно "второе" дыхание , к ранее уже открытым +100500

Публикация игры

Большим плюсом выбора Unity - кроссплатформенность, что позволяет один проект выпустить на всех желаемых платформах (Android, iOS,PC,WebGl и др). К моменту написания статьи игра была опубликована только для Android в Google Play Market, но не за горами ios в Apple Store.

Какие "подводные камни" имеются?

Технически публикация приложения в Google Play Market не составляет никакого труда всё интуитивно понятно и легко. Пройти проверку по возрастному рейтингу, загрузить картинки из игры, логотип и впринципе все готово.

Так в чем же проблема и где те самые "подводные камни"?

Политика конфиденциальности

Для публикации она не является необходимым элементом, однако, если в вашем приложении имеется реклама или внутриигровые покупки, то в кратчайшие сроки стоит обзавестись данной "бумажкой" и указать сссылку на неё в Google Play Console. К счастью, есть моножество ресурсов, которые генерирует данный документ за считанные секунды и сразу предлагают разместить на их сайте, удовлетворяя запросам гугла.
Если проигнорировать предупреждения от гугл, что у вас отсутствует политика конфидециальности, то приложение могут легко снять с публикации.

Совет: Не откладывайте на потом этот пункт, делайте его паралельно с публикацией!

Идентификатор клиента OAuth

Если у вас в игре имеется система достижений, рейтинга от гугл или вы хотя бы сохраняете данные игры в облаке от гугл, то необходимо, чтобы пользователь проходил процесс авторизации используя гугл аккаунт, а значит предоставлял некоторые разрешения на управления его данными. Теперь по порядку. При настройке игровых сервисов в Google Play Console, необходимо создать приложение для авторизации пользователя в Google Cloud Platforms, настроить учетные данные для идентификатора клиента OAuth, и Окно запроса доступа OAuth. Пожалуй это главный "подводный камень".
Сложность состоит не в его первоначальной настройке, чтобы сервисы исправно работали, а в том что приложение было опубликовано и не имело ограничений по количеству пользователей. Если вы намерены создавать крупнобюджетный проект, которые будет привлекать тысячи игроков, то вам придется обязательно пройти этот этап.

Сайт игры

Это не является обязательным пунктом, но лучше сделать сайт, где будут размещены новости вашего проекта, а так же политика конфиденциальности и прочие материалы для ознакомления. Оказывается в 2021 году сделать легкий и простой сайт достаточно просто. С шаблонами для разработки сайтов в Word Press, не долго думая, я останавливаюсь на нем. Для сайта необходим хостинг и собственный домен. Взвесив все "за" и "против", решил потратить пару тысяч рублей на его аренду, сроком на 48 месяцев и не "париться". В сети огромное количество предложений, так что проблем с этим тоже не было. Пару часов уходит на его настройку, и еще пару часов на наполнение его контентом. И вот уже есть свой собственный сайт для игры!

Совет: Чтобы получить заветную галочку во вкладке Окно запроса доступа OAuth в Google Cloud Platforms, иметь сайт игры и свой домен , где так же будет размещена политика конфиденциальности - является обязательным пунктом!

Совет: Так же, если используете рекламу от Google Admob, то сайт тоже необходим. В корневую папку вашего сайта добавляется файл app-ads.txt. Это позволяет рекламодателям понять, какие источники объявлений имеют право продавать рекламный инвентарь. Если не пройти авторизацию, то доход с рекламы будет сильно снижен!

GDPR

Еще одно бюрократическое препятствие осталось, на пути для публикации. Если ваше приложение имеет рекламу, то она может быть персонализированной, а значит ваше приложение собирает данные пользователей, чтобы успешно показывать рекламу. GDPR- (General Data Protection Regulation) -этозакон, принятый Европейским Парламентом, который описывает правила защиты данных для граждан ЕС. Это значит,чтобы показывать персональну рекламу, необходимо перед первым запуском вашей игры, пользователь должен принять соглашение, что ознакомлен с политикой конфиденциальности вашего приложения, а так же прочитать в каких целях будет использоваться его персональные данные и дать согласие/отказаться на их обработку. Разумееется это распространяется на резидентов из стран ЕС.

После выполнения всех выше изложенных пунктов, мое приложение успешно опубликовано в Google Play Market и не знает никаких проблем.

Краткая выжимка советов

  • Изучите рынок, и определитесь с направлением и жанром игры. Главное не стройте в начале "наполеоновские"планы, которые могут и не реализоваться!

  • Распишите план действий и пытайтесь четко следовать ему, попутно внося небольшие правки. Старайтесь укладываться в установленные планом сроки.

  • Обязательно спрашивайте мнение ваших близких и друзей о вашей игре на протяжении всей разработки.

  • Не стесняйтесь приобретать ресурсы для ускорения разработки и повышения качества своего проекта.

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

  • Изучите базовые навыки работы с редактированием изображений и звуков.

  • Соорудите себе в браузере пул полезных закладок с библиотекой различных ресурсов, имея быстрый к ним доступ.

  • Научитесь использовать на базовом уровне Git. Незаменимый помощник при разработке игры, чтобы контролировать внесенные изменения.

  • Последнее и наверно самое важное. Никогда не сдавайтесь , верьте в себя, упорно трудитесь и рано или поздно, но у вас все обязательно получится! Если получилось у меня и миллионов других начинающих разработчиков данного ремесла, то почему не должно получится и у вас!?

Заключение

Премного благодарен вам, что вы дочитали конца эту длинную статью! Надеюсь, что те кто только пробуют себя в роли разработчика, подчерпнут для себя некоторые полезные вещи и получат заряд мотивации, а "бывалые" найдут эту историю как минимум не скучной.

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

Если я где-то ошибся и был не прав - буду рад правкам и пожеланиям в комментариях. Готов ответить на любые вопросы по данной теме.

Чтобы не было недопониманий на счет даты релиза.

Впервые игра была опубликована 2 декабря 2019 года, и это было 10 месяцев разработки. После я был вынужден отдать долг своей родине. Срочную службу в армии я нес до 2 декабря 2020. После демобилизации, я сразу продолжил разработку. И 4 февраля 2021, после "12 месяцев" разработки, я выпустил проект.

Если Вам интересно посмотреть на результат моей работы, то вы можете найти в Google Play Market.

Название игры - Starlake

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru