Вместо предисловия:
По образованию я экономист, компьютер у меня появился в 18 лет, а кодить я начал летом 2016-ого года. А потому, хоть и работаю в данный момент программистом (на полумертвом языке Делфи), много чего сам не понимаю и постоянно прошу помощи то у одного, то у другого. С одной стороны, вполне могу научить чему-то неправильно. С другой стороны - объяснять я буду довольно простым языком, которые поймут даже совсем новички. Просто потому что говорить правильным "программистским"" языком еще не научился.
С чего начать?
(для тех, кто совсем ничего не знает)
В RPG maker MV язык javaScript. А потому начать надо именно с его основ. Сейчас информации в интернете столько, что найти можно всё. А так как в интернете есть ВСЁ, то найти что-то действительно хорошее всё сложнее
1) Я бы посоветовал начать с сайта
"CodeAcademy"
Объясняется всё довольно понятно, а главное, что после каждого урока с теорией следует практическая часть, где вы сможете закрепить знания написанием кода прямо на сайте. После прохождения всего курса вы вполне можете начинать писать плагины, а значит перейти к моим урокам ниже.
2) То же самое, только на мобильнике -
"sololearn"
Больше, чем просто самоучитель. Здесь можно "баттлиться" знаниями с другими пользователями, смотреть размещенный чужой код и прокачиваться прямо как в РПГ
3) Отличный сайт, который многие используют и тогда, когда уже работают программистами
learn.javascript.ru
. Своего рода самоучитель-справочник, на который вы еще не раз наткнетесь, когда будете гуглить то, что уже позабыли.
В конце каждого урока практические задания, некоторые довольно сложные. Очень полезны комментарии пользователей, лучше их почитать хотя бы первые 20.
4) Если вы хотите закрепить знания JS или пойти вглубь, то отлично подойдет курс из этой
раздачи
, а точнее "Бонус 1. Премиум курс по JavaScript (24 урока, 19,5 часов)"
Курс будет полезен, даже если вы просто будете включать его например в метро. Но конечно лучше практики ничего нет
5) Более заумный сайт, который еще ближе к справке-документации. Обычно бывает полезным в более сложных вопросах, начинать с него я бы не советовал
developer.mozilla.org
Редактор кода
Программировать можно хоть в стандартном блокноте от "виндоус", но я бы посоветовал выбрать что-нибудь посерьезнее. Но не слишком. Идеальным вариантом является
"Sublime Text"
. Он довольно быстрый, приятно выглядит, есть удобные горячие клавиши, которые заметно убыстряют написание кода. Потому объяснять буду именно на нем.
Пишем свой первый скрипт
Самое сложное в создании плагинов - начать. Легко повторить за учителем код в своем редакторе. Гораздо сложнее понять, почему он начал именно с этого и зачем делает то, без чего на твой взгляд можно обойтись. А главное - как вообще учитель понял, что надо изменить именно эту функцию, а не какую-то другую?
За кучей текста про оформление плагина, указания автора и создании параметров легко можно потерять само программирование. Зачем нам учиться оформлять плагин, который мы еще не написали?
Поэтому будем идти от важного к красивому.
1) Сначала будем осуществлять свои задумки, и только потом делать код лучше, а сам плагин презентабельней.
2) Сначала будем писать логику в мейкере, а потом уже переводить его в плагин. Так мы сможем понять, чем же код удобнее стандартных мейкерских средств. И так мы сможем разложить мейкер по кусочкам.
3) Я не буду говорить "берем и меняем эту стандартную функцию мейкера", а покажу, как нам понять, что менять надо именно ее.
4) Напишем тестовые сценарии для тестирования нашего скрипта
Вызов общего события по нажатию кнопки
ЦЕЛЬ:
давайте представим, что нам надо вызвать общее событите при нажатии на определенную кнопку. ПРИМЕРЫ:
1) увеличить переменную на 1
2) показать переменную в сообщении
3) открытие картинки при нажатии на кнопку
4) использовать предмет для увеличения уровня здоровья героя
5) спрятать всё оружие при заходе в город
6) показать сообщение, в котором будем указано текущее время в игре, которое равно реальному времени игрока
МЕЙКЕР:
Для начала попробуем осуществить первые два пункта. На первый взгляд нет ничего проще сделать то же самое на ивентах:
1) создаем параллельное событие
2) пишем ветвление условий - "если кнопка "нас страницу вниз" нажата" (это кнопка W)
3) внутри него - "вызвать общее событие 1"
4) в самом общем событии пишем нужное нам действие, т.е. увеличение переменной
5) пишем ветвление условий - "если кнопка "нас страницу вверх" нажата" (это кнопка Q)
6) внутри него - "вызвать общее событие 2"
7) в самом общем событии пишем нужное нам действие, т.е. показ переменной в сообщении
Проверка нажатия клавиш
ТЕСТ:
Сценарий 1:
1) Запускаем игру
2) нажимаем кнопку "W" для увеличении переменной
3) нажимаем кнопку "Q" для просмотра значения переменной
Ожидаемый результат (то, что мы хотим увидеть. Далее - ОР): так как мы нажали один раз, то и переменная будет равной единице
Фактический результат (то, что произошло при тесте. Далее - ФР): переменная больше единицы. В моем случае - пять. Значение переменной зависит от того, насколько долго мы держим клавишу.
ЗАДАЧА:
Как видим, в стандартной проверке нажатия клавиш учитывается нажатие на клавишу, а не ее отпускание. Мы конечно можем добавить паузу, как учит тот же небезызвестный Сириус, однако подобная реализация далека от идеала.
Давайте попробуем исправить это программно и перевести события в код
ПОИСК
1) Заходим в один очень полезный файлик, в котором содержатся команды для мейкера
docs.google.com/spreadsheets/d/1-Oa0cRGp...etnvh7OHs/edit#gid=0
и ищем нужное нам
2) Если не знаем ЧТО исктаь остается гуглить
ГУГЛЕЖ
1) Для того, чтоб понять как нам отследить нажатие клавиши в JS лезем в гугл и вбиваем что-нибудь типа "нажатие клавиши js"
Переходим на уже знакомый нам сайт и видим
learn.javascript.ru/keyboard-events
Понимаем, что клавиши отслеживаются командами keyup, keydown, keypress.
Лезем в гугл-переводчик, понимаем, что key - это клавиша.
2) Заходим в один очень полезный файлик, в котором содержатся команды для мейкера
docs.google.com/spreadsheets/d/1-Oa0cRGp...etnvh7OHs/edit#gid=0
нажимаем CTRL+F для поиска и пытаемся найти сначала вышеназванный key.
3) на 186ой строке видим знакомые press, key
4) таким образом узнаем, что нажатие клавиш можно отследить через одну из команд, в скобках написав название клавиши
Code:
(Input.isTriggered('ok'))
(Input.isRepeated('ok'))
(Input.isPressed('ok'))
(Input.isLongPressed('ok'))
5) открываем ветвление условий и вместо "кнопка нажата" вставляем первую команду в поле "скрипт", надеясь, что всё произойдет удачно и пока не заморачиваясь над тем какая кнопка указана параметром. Учитывая,что в ветвлении кнопка "энтера" названиа ok, предполагаем что нам надо жать энтер
6) На этот раз нам повезло. Именно isTriggered позволяет нам фиксировать нажатие кнопки как однократное
СПРАВКА
1) Еще один способ, позволяющий найти нужное. Находясь в редакторе нажимаем F1 (это вызовет открытие справки мейкера).
2) Затем мотаем в самый низ в раздел JS Library и ищем КАК же может называться инициализация нажатия кнопок. В данном случае это Input.
2) Находим строчку isLongPressed, isTriggered и прочие и просто пробуем. Способ требует более глубоких знаний, так как информации много, а конкретных примеров в справке практически нет.
Подключение всех клавиш клавиатуры
Когда я впервые открыл МВ был очень разочарован, что клавиш стало еще меньше, чем в МВ. Впоследствии оказалось, что добавить их намного легче, чем кажется
ЗАДАЧА:
Итак, мы знаем, что команда (Input.isPressed('ok')) считывает нажатие клавиши. Теперь нам следует найти в коде движка ГДЕ же определяется название клавиш. Почему "ok" - это "ENTER"?
ПОИСК
1) Тем, кто хоть немного нзает JS ясно, что isPressed('ok') - это метод объекта Input. А потому залазим в папку демки "js" и открываем по очереди каждый из файлов с разрешением ".js"
2) Нажимаем в редакторе кода клавиши CTRL+F, т.е. поиск, и вставляем туда "Input". Для быстрого перехода от одного найденного слова к другому нажимаем клавишу "F3"
3) После недолгих поисков видим довольно интересный кусок кода в rpg_core.js, а именно Input.keyMapper
Как мы видим, в этом объекте (или ассоциативном массиве - кому как удобней) содержатся элементы в виде цифр и через двоеточие их значения типа "ok", "control". Давайте проверим, и вставим в ветвление условий что-нибудь из этого объекта, например 'control': Input.isTriggered('control')
4)Всё получилось, при нажатии клавиши "control", сообщение появляется. Более того, если мы и в ветвлении, и в объекте Input.keyMapper мы заменим слово control на любое другое, то всё опять же будет работать! Логика подсказывает, что цифры - это коды клавиш, а значение может быть таким, каким мы пожелаем.
Добавляем другие клавиши клавиатуры
1) Давайте попробует найти коды остальных клавиш. Ввводим в гугле "клаивши js" и переходим по первой ссылке
learn.javascript.ru/keyboard-events
2) Сайт оказывается довольно полезным. Ставим курсор туда, куда нас просят, а ниже мы видим код клавиши. В данном случае код клавиши 'b' то ли 98, то ли 66, а потому пробуем и то, и то
3) В редакторе ставим курсор на одну из строк внутри массива Input.keyMapper, жмем кнопку CTRL+SHIFT+D и строчка дублируется. Теперь вместо pageup пишем 'b', а вместо цифры подставляем по очереди 66 и 98
Как видим, 98 не рабоатет, а вот 66 в самый раз! Теперь вписываем код в событие и радуемся
4) Конечно, можно самому для каждой клавиши найти код. Но зачем изобретать велосипед? Вводим в гугле "все коды клаивши js", переходим по ссылке
umi-cms.spb.su/ref/javascript/251/
и вуаля! Составляем громадный массив со всеми клавишами клавиатуры и вставляем его rpg_core.js . Перед массивом пишем комментарий, содержащий слово "тест", чтобы потом можно было быстро найти место, где ты что-то поправил
Вызов общего события скриптом
ПОИСК
1) В нашем полезном файлике
docs.google.com/spreadsheets/d/1-Oa0cRGp...etnvh7OHs/edit#gid=0
все команды рассортированы по командам в событии, поэтому мы довольно легко можем найти код, вызывающий общее событие $gameTemp.reserveCommonEvent(n);
2) Если же не выходит - вводим в переводчик слово "общее", выясняем, что на английском оно звучит как "Common", и после нажатия CTRL+F на вышеупомянутой страничке легко находим Common Event с вышеупомянутой командой
КОДИМ
1) Логика подсказывает, что в $gameTemp.reserveCommonEvent(n) переменная n - это номер общего события. А значит, если мы введем $gameTemp.reserveCommonEvent(2), то вызовем второе общее событие.
2) Вставляем данный код в "скрипт"
, сохраняем и проверяем. Всё работает! Делаем то же самое и для вызова первого события
Замена ветвления событий кодом
ПОИСК
1) Снова довольно легко находим в файлике
docs.google.com/spreadsheets/d/1-Oa0cRGp...etnvh7OHs/edit#gid=0
строчку соответствия ветвления события - "Conditional Branch". Код в нем знакомый нам "if (code) { stuff } else { stuff }", т.е. обычные условия JS, где
- if - если
- code - условие. В данном случае это Input.isTriggered('b') и Input.isTriggered('control')
- stuff - ЧТО будет при выполнении условия. В нашем случае $gameTemp.reserveCommonEvent(2) и $gameTemp.reserveCommonEvent(1)
- else - "иначе". Пока нам не понадобится
2) Пишем то, что у нас получилось
Code:
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
3) Вводим получившийся код в "скрипт события"
, а остальное удаляем. Проверяем и поздравляем себя!
Параллельность кода
Это всё конечно здорово, но на каждой карте постоянно вставлять это параллельное событие не есть хорошо. Поэтому давайте получившийся у нас код превратим в отдельный плагин
КОДИМ
1) В папке js/plugine создаем файл например "input.js", где input - любое название, которое вы захотите
2) всталяем туда наш код
Code:
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
3) Запускаем игру и видим, что у нас ничего не работает. Почему? В событии этот код запускался параллельным событием. Как же нам сделать то же самое кодом?
ПОИСК
1) Часто в программировании практически во всех программах названия методов или функций одинаковые. К примеру:
- "remove" или "delete" - удаления объектов
- "add" - добавление
- "is" - проверка есть ли у объекта определнное свойство, например isOpacity
- "get" - считывание значения свойства объекта, например getHpActor вполне подошло бы для названия функции, возвращающей кол-во HP героя
- "set" - запись значения свойства, например setHpActor подойдет для изменения здоровья героя
Также и то, что в мейкере называют "параллельное событие" в программирование чаще всгео называют update. А поэтому давайте поищем что-то похожее и в коде мейкера
2) Разу уж нажатие клавиши находится в объекте Input в файле rpg_core.js, то адвайте поищем именно там. И практически сходу мы находим
3) Давайте скопируем всю эту функцию в наш плагин, и перед самым закрытием функции вставим наш код
Code:
Input.update = function() {
this._pollGamepads();
if (this._currentState[this._latestButton]) {
this._pressedTime++;
} else {
this._latestButton = null;
}
for (var name in this._currentState) {
if (this._currentState[name] && !this._previousState[name]) {
this._latestButton = name;
this._pressedTime = 0;
this._date = Date.now();
}
this._previousState[name] = this._currentState[name];
}
this._updateDirection();
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
};
4) После запуска видим что всё рбаотает. Теперь сделаем код красивее - обернем наш код в отдельную функцию. Вообще, в идела каждой действие должно быть в отдельной функции. Так мы сможем легко найти ее и поправить, если надо будет например добавить еще несоклько клавиш.
Назовем функцию triggeredKeys и вызовем ее в конце Input.update. this в данном случае равно Input
Code:
Input.update = function() {
this._pollGamepads();
if (this._currentState[this._latestButton]) {
this._pressedTime++;
} else {
this._latestButton = null;
}
for (var name in this._currentState) {
if (this._currentState[name] && !this._previousState[name]) {
this._latestButton = name;
this._pressedTime = 0;
this._date = Date.now();
}
this._previousState[name] = this._currentState[name];
}
this._updateDirection();
this.triggeredKeys();
};
Input.triggeredKeys = function() {
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
}
5) Если посмотреть на код, то становится понятно, что у нас дважды написана одна и та же функция "update".
Code:
Input.update = function() {
//Input.update из rpg_core
this._pollGamepads();
if (this._currentState[this._latestButton]) {
this._pressedTime++;
} else {
this._latestButton = null;
}
for (var name in this._currentState) {
if (this._currentState[name] && !this._previousState[name]) {
this._latestButton = name;
this._pressedTime = 0;
this._date = Date.now();
}
this._previousState[name] = this._currentState[name];
}
this._updateDirection();
//заканчивается функция апдейта
this.triggeredKeys();
};
6) Зачем нам переписывать ее в свой плагин если мы можем скопировать ее в какую-нибудь переменную, а потом вызвать? Давайте так и сделаем. после тестов видим, что всё отлично работает!
Code:
const updateCore = Input.update; //здесь мы сохраняем Input.update из rpg_core в константу updateCore
Input.update = function() {
updateCore.call(this); //вызываем функцию updateCore
this.triggeredKeys(); //вызываем нашу функцию triggeredKeys.
};
Input.triggeredKeys = function() {
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
}
Защищаем наши функции
Часто в плагинах можно встретить конструкцию (function(){}) () . Как говорят, это "самовызывающаяся функция для защиты переменных".
Этот шаблон часто используется при попытке избежать загрязнения глобального пространства имен, поскольку все переменные, используемые внутри IIFE (как и в любой другой нормальной функции), не видны за пределами его объема.
Конечно, можно его вставить и забыть, не разобравшись, но давайте все-таки разберемся.
КОДИМ
1) Давайте попробуем защитить наши переменные. Что надо сделать, чтоб переменные в нашей функции перестали быть глобальными? Конечно же,обернуть код в функцию, допустим назовем ее "a"
Code:
const a = function () {
const updateCore = Input.update;
Input.update = function() {
updateCore.call(this);
Input.triggeredKeys();
};
Input.triggeredKeys = function() {
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
}
};
2) Теперь наш плагин сломан. Наш "апдейт" не обновляется, ведь теперь он в функции. А потому вызываем нашу функцию в конце плагина
Code:
const a = function () {
const updateCore = Input.update;
Input.update = function() {
updateCore.call(this);
Input.triggeredKeys();
};
Input.triggeredKeys = function() {
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
}
};
a(); //вызов функции
3) Можем написать более кратко - сразу после функции написать круглые скобки для мгновенного вызова
Code:
const a = function () {
const updateCore = Input.update;
Input.update = function() {
updateCore.call(this);
Input.triggeredKeys();
};
Input.triggeredKeys = function() {
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
}
}(); //вызов функции
4) Теперь ясно, что переменная "а" нам не нужна. Зачем она, если затем ее совсем не используем. Избалвяемся от нее и снова у нас ничего не рбаотает
Code:
function () {
const updateCore = Input.update;
Input.update = function() {
updateCore.call(this);
Input.triggeredKeys();
};
Input.triggeredKeys = function() {
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
}
}(); //вызов функции
5) Так как JS после слова function хочет видеть ее название, а его нет, то и наш код ошибочный. Что делать? Как заставить JS распознать наш функцию как анонимную, т.е. без названия? Для этого вспомним ГДЕ мы используем анонимные функции. Например, при передаче функции как аргумент в другую функцию присваивать функцию перемнной не требуется
func(function () {}, 2)
Поэтому давайте попробует обитрить и заключить нашу функцию в скобки
Code:
(function () {
const updateCore = Input.update;
Input.update = function() {
updateCore.call(this);
Input.triggeredKeys();
};
Input.triggeredKeys = function() {
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
}
})();
И о чудо, JS увидело скобку, поняла, что сейчас будет следовать выражение, а не функция, а раз это выражение, то и анонимную функцию использовать в ней можно
6) Кроме скобок подойдет любой символ, который бы показывал JS что перед нами выражение. Например унрарные плюсы или минусы
Code:
-function () {
const updateCore = Input.update;
Input.update = function() {
updateCore.call(this);
Input.triggeredKeys();
};
Input.triggeredKeys = function() {
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
$gameTemp.reserveCommonEvent(2)
}
}
}();
7) Кроме того, как и любой функции мы можем передать ей аргумент
Code:
+function (numb) {
const updateCore = Input.update;
Input.update = function() {
updateCore.call(this);
Input.triggeredKeys();
};
Input.triggeredKeys = function() {
if (Input.isTriggered('b')) {
$gameTemp.reserveCommonEvent(1)
}
if (Input.isTriggered('control')) {
console.log(numb);
$gameTemp.reserveCommonEvent(2)
}
}
}(2);
ИТОГО:
Итого мы получили маленький плагинчик. Но цель урока была не конкретно в написании плагина, а в умении находить чать кода, которая нужна именно для нашего случая.
ДОМАШНЕЕ ЗАДАНИЕ
Надеюсь, что после данной статейки любой сможет написать плагин не только для вызова общих событий нажатия на клавишу, но и конкретные действия при нажатиях без использования общих событий.
1) увеличить переменную на 1
2) показать переменную в сообщении
3) открытие картинки при нажатии на кнопку
4) использовать предмет для увеличения уровня здоровья героя
5) спрятать всё оружие при заходе в город
6) показать сообщение, в котором будем указано текущее время в игре, которое равно реальному времени игрока
Ответы в любом случае будут, а вот буду ли дальше писать зависит от отзывов, все-таки времени уходит много, а его ужас как не хватает