Dusk World – Дневники разработки, часть 5:
Как сделать систему уровней в браузерной MMORPG

Почитывая Toster.ru наткнулся на вопрос, о том, как сделать систему уровней в браузерной MMORPG. Вначале хотел найти какое-нибудь руководство (вопрос-то стандартный, каждый разработчик браузерной игры сталкивается с ним), и сказать мол «держи, учись пользоваться гуглом», но оказалось, что руководства с примерами на эту тему в общем-то и нет.

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

В общем, решение принято – поехали.

Примечание: руководство подразумевает, что вы имеете базовое знание php, sql и ООП. Если даже базовых знаний нет – браться за разработку своей браузерной MMORPG вам еще рано.

 

Результат, который получим

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


Пример реализованной системы уровней | Код на github

 

Руководство: Как сделать систему уровней в браузерной MMORPG

Постановка задачи

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

1. Есть пользователь и его опыт (по сути, один параметр)

2. На основании опыта пользователя необходимо получать:

а) Уровень пользователя
б) Количество опыта, которое ему необходимо набрать до следующего уровня
в) Количество опыта на текущем уровне
г) % величина набранного опыта на текущем уровне – нам эти % потребуются для отображения классической полоски, заполняющейся по мере получения опыта, и визуально показывающая игроку, сколько осталось до уровня.

3. Что будем хранить в БД, что рассчитывать на ходу:

в БД будем хранить:
а) опыт
б) уровень

Оставшиеся параметры будем рассчитывать на ходу.

Хранить уровень в БД или рассчитывать?

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

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

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

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

 

Составляем таблицу уровней

Когда только начинал делать систему уровней, не долго думая набросал табличку в формате:

1 уровень дается сразу
2 уровень требует 500 опыта
3 уровень требует 1000 опыта
4 уровень требует 2000 опыта
5 уровень требует 3000 опыта
и т.д.

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

Чтобы создавать систему уровней не «с потолка», а где мы четко будем понимать, какая у нас прогрессия – необходимо строить функции. Да, те самые «бестолковые» функции вида «y=2x+3», которые мы проходили в школе.

Открываем сайт desmos.com и строим тот график, который ходим, например, с постепенно возрастающей прогрессией:

А затем, на основе функции составляем таблицу уровней.

Я использовал простую функцию: y = (x * 10) ^ 1,4

И вот таким php-кодом, составлял таблицу:

<html>
<head>
    <title>Создаем таблицу уровней и требуемого опыта</title>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body>

<?php

$lvl = 1;
$a = 1.4;
$totalexp = 0;

echo '<table border="1"><tr><td>Уровень</td><td>Всего опыта</td><td>Опыта до<br />следующего уровня</td></tr>';
while ($lvl < 11) {
    $y = round(pow(($lvl * 10), $a));
    echo '<tr><td>'.$lvl.'</td><td>'.$totalexp.'</td><td>'.$y.'</td></tr>';
    $totalexp += $y;
    $lvl++;
}
echo '</table>';
    

Получается такая таблица:

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

Можно (точнее нужно) пойти еще дальше – сделать так, чтобы php скрипт не только отображал таблицу, но и сам, на основе новых значений, заполнял MySQL-таблицу. Чтобы «ребаланс» прокачки менялся нажатием парой кнопкой и все. В текущем примере этого не делается, чтобы не перегружать руководство информацией.

 

Создаем MySQL таблицы

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

1. Таблица пользователей

id – id пользователя
name – имя пользователя
lvl – уровень пользователя
exp – общий опыт пользователя

2. Таблица уровней

lvl – уровень
exp_to_lvl – необходимый опыт для получения следующего уровня
exp_total – общий опыт на текущем уровне

SQL-код, который создаст таблицы и заполнит их информацией:

-- Таблица Users

CREATE TABLE `users` (
	`id` INT AUTO_INCREMENT PRIMARY KEY,
	`name` VARCHAR(255),
	`lvl`INT DEFAULT 1,
	`exp` INT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- Таблица Levels

CREATE TABLE `levels` (
	`lvl` INT DEFAULT NULL,
	`exp_to_lvl` INT DEFAULT NULL,
	`exp_total` INT DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Заполняем таблицу User

INSERT INTO `users` (`id`, `name`, `lvl`, `exp`)
VALUES
(NULL, 'Diablo', '1', '0');

-- Заполняем таблицу Levels

INSERT INTO `levels` (`lvl`, `exp_total`, `exp_to_lvl`)
VALUES
('1', '0', '25'),
('2', '25', '66'),
('3', '91', '117'),
('4', '208', '175'),
('5', '383', '239'),
('6', '622', '309'),
('7', '931', '383'),
('8', '1314', '462'),
('9', '1776', '544'),
('10', '2320', '1000000');
    

На 10-том уровне игрок увидит, что на следующий уровень ему потребуется 1 000 000 опыта. Таким образом я сделал простой «блок прокачки». В вашей реальной игре, конечно же, нужно будет сделать более человечный способ.

 

Переходим к коду

Примечание: при написании этого примера был выбор – или писать его на каком-нибудь Фреймворке, вроде Yii2 или Laravel, или писать «кривой-велосипед». Учитывая, что данное руководство ориентированно на новичков в программировании – новички, могут банально не суметь поставить Yii2 или Laravel, и понять, где там код фреймворка, а где код, относящийся к уровням – решил писать «велосипед» (в котором даже MVC-паттерна нет).

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


Так как финальный код доступен на github, пройдусь только по ключевым аспектам.

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

Уровень пользователя

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

Получать мы его будем таким вот хитрым запросом:

SELECT MAX(t.lvl) FROM (SELECT lvl FROM levels WHERE exp_total - ? <= 0) t

Вместо ? подставляется текущий общий опыт пользователя. И на основе данных из таблицы уровней (levels) сразу возвращается текущий уровень пользователя.

Чтобы руководство не разрослось на 100500 страниц, не буду описывать логику работы этого запроса, скажу лишь, что польза текущего руководства на 50% именно в нем. В качестве домашнего задания можете разобраться, как он работает.

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

Количество опыта до следующего уровня

SELECT `levels`.`exp_to_lvl` FROM levels JOIN users ON users.lvl = levels.lvl users.id = ?

Здесь мы делаем простой запрос, на основе уровня пользователя, который получается на основе его id.

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

Количество опыта на текущем уровне

Для получения этого параметра мы должны из общего опыта пользователя вычесть общий опыт для данного уровня:

SELECT ? - exp_total FROM levels WHERE lvl = ?

Вместо первого ? подставляется общий опыт пользователя
Вместо второго ? подставляется уровень

И на основе данных в таблице уровней получаем количество опыта на текущем уровне.

% величина набранного опыта на текущем уровне

Рассчитывается по следующей формуле: % = («Опыт на тек. уровне»/«Опыта до след. уровня») * 100

В php коде это выглядит так:

$this->userinfo['expbar'] = round(($this->userinfo['exp_at_lvl']/$this->userinfo['exp_to_lvl']) * 100);

Что касается самой логики, то она довольно проста:

1) Если опыт пользователя изменился, то:

2) Делаем проверку на то, изменился ли уровень пользователя, для этого делаем запрос на нового текущего уровня (на основе нового опыта), и сравниваем его с текущим – если они не сходятся – значит:

3) Необходимо перезаписать уровень пользователя в БД

4) Выполнить необходимые действия, в зависимости от того, увеличился уровень или уменьшился. В текущем примере такого функционала нет, но в рабочей MMORPG он обязательно будет – например, игрок получает очко навыка, на каждом уровне (при этом, важно не забыть, рассчитать на сколько именно изменился уровень – вдруг игрок получил сразу 2 или 3 уровня – значит и награду он должен получить *2 либо *3 соответственно.


Выше описаны основные механики. Посмотреть общий код можно на github:

level.sql – SQL запрос для создания таблиц;
model.php – модель работы с БД. Не забудьте изменить данные для подключения к БД;
lvl.php – вид и контроллер. Здесь то, что отвечает за обработку и отображение данных;
index.php – точка входа, просто подгружает lvl.php;
lvl_table.php – скрипт построения таблицы уровней;
/img/ - папка с аватаром пользователя (по-хорошему, информация об аватаре должна храниться в БД, своя для каждого пользователя).

 

Пару слов о безопасности

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

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

Посмотрим на код, который добавляет опыт:

            <form method="post" action="">
                <input type="hidden" name="exp" value="5">
                <button type="submit">Добавить 5 опыта</button>
            </form>
            <form method="post" action="">
                <input type="hidden" name="exp" value="20">
                <button type="submit">Добавить 20 опыта</button>
            </form>
            <form method="post" action="">
                <input type="hidden" name="exp" value="50">
                <button type="submit">Добавить 50 опыта</button>
            </form>
            <form method="post" action="">
                <input type="hidden" name="exp" value="zero">
                <button type="submit">Обнулить опыт</button>
            </form>
        

Как не сложно догадаться, value="" – это и есть тот опыт, который добавляется персонажу. Можно открыть редактор страницы, прямо в браузере (F12 в Google Chrome), изменить значение на 75, нажать на кнопку «добавить опыт» и вы увидите, что вместо старого значения, добавится то, которое вы прописали (но, не больше 99 - в коде сделано ограничение).

Как было бы более защищено? Сделать в формах лишь название типа добавляемого опыта:

  • addsmallexp
  • addmediumexp
  • addbigexp

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

Хотя, и это далеко не все. Есть еще стандартная csrf уязвимость и многое другого. Описыванить их все – не входит в задачи данного руководства.

Но в целом, проверка надежности браузерной MMORPG сводится к следующему:

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

P.S.

Надеюсь, что данное руководство поможет молодым разработчикам браузерных MMORPG. Лично мне, вначале пути, очень не хватало именно примеров реализованного функционалов. Не документации, не умных слов «как надо», а простой банальный пример, который работает.


Вернуться к дневникам разработки Dusk World





Комментарии

Гииме 2017-10-25 10:11:35
Требуемый опыт берется «с потолка» и формула берется с «с потолка» это одно и то же. Так и вижу как ты по «отзывам» игроков ту да сюда эту несчастную формулу гоняешь.

[Ответить]
↑ +2 ↓
Diablo 2017-10-26 17:21:00
1. > это одно и то же
Нет

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

[Ответить]
↑ -1 ↓
Гимме 2017-10-31 16:58:45
Я неговорил что формула не нужна, как раз таки формула должна зависеть от десятков факторов, а не быть взятой с потолка или потому что изгиб полосочки понравился

[Ответить]
↑ +2 ↓
Karlail 2017-10-20 15:44:12
Уровень можно не запрашивать и рассчитывать каждый раз при необходимости, а один раз получив значение посчитать и записать в переменную. Только потом не забыть при извлечении значений из базы его пересчитывать.

[Ответить]
↑ +2 ↓
Diablo 2017-10-21 14:30:03
Конечно можно. И придется делать это каждый раз, при необходимости получить уровень пользователя. А если это страница со списком пользователей, и на каждой их по 30-50, то получается и уровень нужно будет рассчитать 30-50 раз для отображения одной страницы.

Оптимально ли это? Свое мнение я написал в статье.

[Ответить]
↑ 0 ↓

Страницы: [1]

Оставить комментарий


Ваше имя:

Комментарий:



Реклама:

Наша командаПоддержать проектРеклама на сайте

Diablo и Hellfire, Diablo 2 и Lord of Destruction
2008-2016