Всем привет, я сразу напишу вывод, может кто нибудь не захочет читать дальше
Вывод: К игре можно подключить гемы, но я пока что не знаю как подключить
любой гем. Во первых есть проблема с тем, что руби в игре немного старое, и немного урезанное, некоторые классы стандартной библиотеки отсутствуют (Etc к примеру), а многие гемы их используют. Во вторых я не знаю пока как работать с гемами с extension (но я точно знаю что можно).
И так, сразу напишу что такое
гемы - это крассивое название для сторонних библиотек, оформленных стандартным образом. Это ненмого похоже на сторонние скрипты, которые вы копируете в игру, но чаще всего файлов там гораздо больше чем один. Давайте рассмотрим гем
cancan, который мы будем сегодня вставлять в проект. Пока что все что нас интересует - это папка lib, в ней лежит весь код, который нам нужен. Обычно используют Rubygems (часть руби, которая работает с гемами, и которая кстати вырезана из руби игры) для установки гема. Все что rubygems сделают для cancan - это скачают гем, сохранят его, и что самое важное - добавят путь к папке lib гема в $LOAD_PATH. В нормальном проекте вы теперь можете просто написать
и использовать библиотеку.
Так же расскожу о extensions. Стандартная реализация руби написана на Си (да да, один язык написан на другом языке, можете на досуге подумать о проблеме яйца и курицы). Руби - скриптовый язык, что означает что его код не нужно компилировать (переводить в бинарный машинный код) перед исполнением. Так же плюс скриптовых языков - на них обычно писать гораздо легче, чем на компилируемых, они крассивее (руби вообще самый крассивый язык
), программы на них гораздо легче поддерживать и дебажить (искать ошибки). А минус фактически один - они гораздо медленнее, чем компилируемые языки. Но в руби есть одна лазейка - вы можете узкую часть программы написать на Си, а поверх этой части написать обертку на руби, чтобы остальной код мог с ним работать. Это называется расширение (extension), и да, расширение нужно компилировать. Многие гемы реализуют extension. Rubygems берет на себя задачу компиляции, и конечный юзер обычно ничего не замечает. Я пока что не знаю как работать из игры с такими гемами, но точно знаю что возможно. А это такие вкусные гемы как RMagic (удобное рисование граффики на лету) pry (офигенный дебагер) и многие другие.
Довольно лирики, приступим к методике.
Для начала создадим новый проект, открываем редактор скриптов и вставляем в новый скрипт следуещее:
puts Dir.pwd #puts печатает строку, Dir.pwd возращает путь к текущей папке.
Сохраняем, запускаем игру (не забываем кликнуть по пункту меню "Показывать консоль") - в консоли вы увидите путь к папке текущего исполняемого файла. Так вот файла этого самого там нет, игра генерирует его динамически, но путь - есть, это корневая папка проекта. Запоминаем эту полезную информацию.
Продолжаем эксперимент - берем редактор кода (не из игры, сторонний, рекомендую subline), создаем файл hello.rb со следующим кодом:
puts "Hello World" #этот код просто напечатает строку в ковычках
и сохраняем его в папке проекта. Теперь открываем редактор скриптов игры и заменяем код, который мы написали на следующее:
require File.expand_path('hello.rb', Dir.pwd)
#requrie подключает файл
#File.expand_path возвращает абсолютный путь к файлу (первый аргумент),
#используя абсолютный путь из второго аргумента
Запускаем игру и видим в консоли заветное 'Hello World'. Это означает что мы можем подгружать сторонние скрипты в игру. Теперь ясно, что мы должны сделать - cохранить гем где нибудь в проекте, добавить в $LOAD_PATH путь к папке lib этого гема и сделать require.
Однако беда в том, что многие гемы имеют зависимости от других гемов, т.е. чтобы такой зависимый гем работал, мы должны еще и подгрузить в проект код другого гема. Добавте к этому версионность гемов, и можно сойти сума
. Будем все таки делать так, чтобы остаться в здравом рассудке. Используем
bundler. Для начала установим руби, которые нужны для бандлера. Идем
сюда и качаем инсталятор. Советую 1.9.3 - будете ближе к версии руби из игры, если захотите потом поиграться с руби, но это не принципиально, главное чтобы не 1.8.7. Устанавливаем руби, нажимаем на клавиатуре windows + r (вы же на винде все это делаете, да?), в появившемся окошке вводим cmd. Знакомьтесь - консоль. Набираем
Если все прошло удачно - вы увидите версию только что установленного руби. Руби от версии 1.9 идут вместе с rubgems в комплекте. Набираем
Привет старый друг!
Теперь самое сложное в нашей затее - перейти в этой гребанной недоконсоли в папку с проектом. Для начала набираем (заменяем Е на диск где у вас лежит проект)
дальше переходим к самому проекту (тут мой путь, замените на свой)
CD rpg_maker/projects/gems_testing
Не закрываем консоль, она нам еще пригодится.
Теперь открываем subline и создаем файл Gemfile (именно так, без расширения вообще)
source 'https://rubygems.org'
gem 'cancan', '1.5.1'
Тут вроде все прозрачно - первая строка - где брать гемы. Дальше функция gem с двумя параметрами - имя гема и версия. Немного о версии гема - у нас руби в игре версии 1.9.2 (RUBY_VERSION подсказал), они вышли в конце 2010 года, гемы стоит искать начала 2011 года. Более ранние или более поздние могут просто не заработать. Идем на
rubygems, вверху справа есть поле поиска, вбиваем туда cancan. Переходим по exact match. Кликаем по show all versions, и ищем версию гему близкую нам по дате. Переходим на его страницу -
cancan 1.5.1. Нас интересует 'runtime dependencies' которых тут о чудо - нет! Это значит что мы будем устанавливать только один гем. На development dependencies можно забить.
А что делать если они все таки есть? Откройте для ознакомления
pry 0.9.7.4 Тут мы видем 5 зависимостей. Символ '~>' означает "версия близкая к",
символ '>=' - "версия больше или равная". Так вот ~> правильный способ, а >= неправильный, гореть разработчикам pry за это в аду. Bundler когда видит в зависимостях ~> ставит версию близкую к указанной, когда же видит >= ставит саму последнюю доступную, которая может быть не совместима с нашими руби 1.9.2. Т.е. мы должны прямо попросить Bundler поставить нужную версию. В этом случае с pry Gemfile выглядел бы так:
source 'https://rubygems.org'
gem 'pry', '~> 0.9.7.4'
#Обратите внимание, вы можете использовать символы ~> и >= в Gemfile, это даст Бандлеру некоторую свободу выбора
gem 'ruby_parser', '~> 2.3.1'
Если бы разработчики не поленились и точно указали версию ruby_parser - нам не нужно было бы заострять на нем внимание в Gemfile.
Ок, мы разобрались с нашим Gemfile для канкан. Теперь разворачиваем консоль (вы же ее не закрыли, да?) и запускаем
Наш старый друг Бандлер установит все гемы и их зависимости из Gemfile куда то в систему. Мы бы могли попросить его установить все сразу в проект, но лучше пускай будут и в системе тоже, может потом поиграетесь с cancan. В консоли вводим
И бандлер покажет нам куда же он скачал гем. Переходим туда, копируем папку с гемом (она будет называться как гем + версия), переходим в папку проекта, создаем папку gems, скидываем туда папку с гемом.
На данном этапе у нас есть проект, файл Gemfile, папочка gems, папочка cancan-1.5.1 в ней. Теперь нам нужно подгрузить cancan в проект.
Открываем редактор скриптов в игре, создаем скрипт в самом верху (гемы нужно загружать до их использования) называем его как нибудь Gems_Loader и вводим туда
Dir.entries(File.expand_path 'gems', Dir.pwd).each do |entry| #Берем все что есть в папке gems и перебираем
unless %w(. ..).include? entry # пока наш entry не бдует равен . или .. (это ссылки на текущий каталог и на родительский)
lib_dir = File.expand_path File.join('gems', entry, 'lib'), Dir.pwd #генерим путь к папке lib каждого гема
$LOAD_PATH.push lib_dir if Dir.exist? lib_dir # и включаем этот путь в $LOAD_PATH, если такая папка есть
end
end
require 'cancan' #привет cancan!
Запускаем игру и... может вы не заметили, но мы подключили сторонний гем! Если бы что-то пошло не так, мы бы уже увидели бы ошибку!
Ну а для тех кто мне не верит, напишу немножко кода, который докажет что мы можем использовать cancan в игре. Для начала три слова о том что такое cancan - это гем для RoR, который позволяет гибко управлять правами юзера (так нам автор говорит). Но на самом деле он позволяет гибко описывать условия, для if, while и т.п. Короткий пример от балды:
if enemy.alive? && actor.alive? && !enemy.fire_creature? && !enemy.imune_to_spells? #иногда так лучше не писать
actor.cast Spells.firbolt, on: enemy
end
#VS
if actor.can? :hit, enemy, with: Spells.firebolt
actor.cast Spells.firbolt, on: enemy
end
Теперь к делу - создаем скрипт в редакторе скриптов в игре, называем его Ability
class Ability
include CanCan::Ability # Я уже не буду описывать что такое include, и так затянул статью
def initialize(object)
can :manage, :all # в общем это не очень полезное правило, оно разрешает всем делать все
end
end
Создаем еще один скрипт, называем его Dog
class Dog
def ability #Создаем объект абилити, сохраняем его в @ability, если интересно что такое ||= - спрашивайте
@ability ||= Ability.new self
end
%w(can? cannot?).each do |method_name| #Тут метопрограммирование для ускорения процесса, тоже спрашивайте если не ясно
define_method method_name do |*args|
ability.public_send method_name, *args
end
end
def bar(target) #каждая сабака должна уметь лаять, да?
target.puts "BAR!"
end
end
dog = Dog.new #объект сабаки
if dog.can? :bar, $stdout #проверяем может ли сабака гакать на STDOUT
dog.bar $stdout #гав!
end
Запускаем игру - видим "BAR!" в консоли игры.
Вот собственно и все что я сегодня хотел сказать, потратил пол выходного, надеюсь вам будет полезно, спасибо за внимание!