Показано с 1 по 10 из 29

Тема: Создание скриптов на RGSS для людей со средними знаниями и экспертов

Древовидный режим

Предыдущее сообщение Предыдущее сообщение   Следующее сообщение Следующее сообщение
  1. #6

    По умолчанию

    5. Борьба с лагами

    Эта глава научит вас, как победить главного босса в игре со скриптами: лаг.

    5.1. Сложность алгоритма

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

    Код:
    p 1
    if a
      s = Sprite.new
    end
    c = d + 11


    Как вы думаете, какова здесь сложность? Она равна O(1). Теперь этот:

    Код:
    for i in 0…n
      f += determine_special_string(n)
    end


    Сложность алгоритма O(n). А в следующем примере?

    Код:
    for i in 0…n
      for j in 0…n
        for k in 0…n
          make_some_lag
        end
      end
    end


    Здесь сложность алгоритма O(n3). Проще говоря, сложность алгоритма говорит нам, как количество выполняющихся повторений зависит от количества обрабатываемых данных. В последнем примере n равное 2 вызовет 8 итераций в общей сложности, в то время как n равное 3 вызовет уже 27 итераций, а n равное 4 вызовет в алгоритме 64 итераций. Теперь картина складывается воедино? Вы видите как быстро возрастает количество обработки? Ведь n равное 10 вызовет целую 1000 выполнений метода make_some_lag. Наиболее распространенная сложность (n2). В своё время было разработано несколько алгоритмов для уменьшения сложности кода. Один из моих любимых это применение сложности O(n×log2(n)). Функция log (логарифм) является обратной к экспоненте функцией. Например, следующие уравнения значат одно и то же:

    log232 = 5
    32 = 25
    Значения, выделенные одинаковым цветом одинаковые в обоих случаях. На словах это можно передать как: “Если основание равно 2, то экспонента, необходимая для получения числа 32 будет равна 5.” (смотрите ссылки о логарифмической алгебре в [IURL="http://rpgmaker.su/showthread.php/518-Создание-скриптов-на-RGSS-для-людей-со-средними-знаниями-и-экспертов?p=9622&viewfull=1#post9622"]главе 8[/IURL]). В целом, эта сложность популярна, ведь она работает как демпфер. Вот ещё примеры: log2(2) = 1, log2(32) = 5, log2(1024) = 10, log2(1048576) = 20. При n равным 1000 понадобится только 10 итераций. Даже если n равна 1000000, будет всего 20 итераций. Умножение n в рамках этой сложности серьёзно повлияет на количество итераций, это так, однако же, сложность O(n×log2(n)) гораздо ближе к O(n) чем к O(n2).

    Применение сложности алгоритмов позволяет провести анализ лучших-худших-средних случаев поведения. Объяснение, что же это означает также довольно простое. Представьте, что вы производите поиск одного числа в массиве. В лучшем случае это число будет самым первым в массиве. В худшем случае оно будет в самом конце или же вообще отсутствовать, то есть вам придется пробежаться по всем элементам массива лишь для того, чтобы узнать что в нём нет того что вам нужно. В среднем случае (лучшем + худшем) / 2 или другими словам: В среднем случае оно будет посередине. Вот в таких вещах и требуется установить сложность алгоритма. Каждый из этих факторов или же меньшие компоненты в сложности удаляются, уменьшая влияние в каждой итерации.

    Допустим, сложность среднего случая равна 5/4*n4+2*n2+43. Сложность алгоритма просто равна =(n4) так как все факторы и меньшие компоненты сложности удаляются. Ищите ссылки на дополнительную информацию о логарифмической алгебре в [IURL="http://rpgmaker.su/showthread.php/518-Создание-скриптов-на-RGSS-для-людей-со-средними-знаниями-и-экспертов?p=9622&viewfull=1#post9622"]главе 8[/IURL].

    5.2. Что такое лаги и их разновидность (в рамках RGSS)

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

    Конкретно в RGSS спрайты и окна вызывают самые большие лаги. Наибольшее количество лагов будет в интерфейсе с множеством окон или на карте с множеством событий, так как к каждому событию привязан спрайт. А каждый спрайт вызывает лаги, независимо от того установлена ли у него графика или нет, потому как сам процесс проверки и обновления спрайта требует времени на обработку процессором (CPU). Обновление события само по себе происходит довольно быстро, так как классу Interpreter нужно только лишь получить и запустить команды события. На карте, где обновляются 100 событий, но без спрайтов будут лишь незаметные лаги. На карте, где 100 событий и спрайты без графики, которые не обновляются, тоже вызовут небольшие, но уже заметные лаги. Это показывает, что только сам факт наличия спрайтов вызывает лаги. На карте, где 100 событий и 100 видимых спрайтов будет невозможно находиться. По этой причине большинство систем анти-лагов — это системы анти-лагов событий (Event-Anti-Lag Systems), которые способны предотвращать бесконтрольное обновление событий и их спрайтов за пределами экрана, потому что игрок всё равно их не видит. Правда создателей таких систем не волнует, что происходит с событием, они только определяют, должен ли спрайт быть обновлен или нет. В моей системе Blizz-ABSEAL внедрена более мощная техника для предотвращения лагов. Вместо того, чтобы просто отключать обновление спрайтов, ненужные спрайты уничтожаются и полностью удаляются из памяти, если событие выходит из зоны, в которой требуется его обновление. Само собой обновление при этом отключается.

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

    5.3. Уменьшение времени обработки

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

    Код:
    bitmap.draw_text(0, 0, 608, 32,
        ‘This string is being processed faster ’ + “than this one.\n”)


    Используйте двойные кавычки, только если вы задействовали символ подстановки (например “\n”) или интерполяцию/внедрение переменных (например, “#{some_numeric_calculation_or_value}”). Также не используйте внедрение переменных, если у вас есть только одно значение. Вы можете использовать функцию value.to_s, которая сконвертирует число в строку.

    Код:
    bitmap.draw_text(0, 0, 128, 32, “#{actor.hp*100/actor.maxhp}%”)

    Код:
    bitmap.draw_text(0, 0, 128, 32, $game_party.gold.to_s)


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

    Код:
    bitmap.draw_text(0, 0, 128, 32, “#{$game_party.gold}”)


    Или ещё хуже:

    Код:
    bitmap.draw_text(0, 0, 128, 32, “#{$game_party.gold}”.to_s)


    Другой пример того, как уменьшить время обработки, это, очевидно, писать меньше кода. И под меньшим количеством кода я подразумеваю не короткие скрипты, а избавление от бесполезных и ненужных временных переменных (смотрите 5.4. для подробностей). Ещё одно полезное решение — это ветвление. В этом случае даже если у вас огромное количество строк кода, он будет достаточно быстрым, так как будет обрабатываться только когда это необходимо. Вы также можете уменьшить время обработки, используя блоки памяти, а не распространение адресации памяти (смотрите 3.3. для подробностей).

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

    Код:
    for actor in $game_party.actors
      actor.hp -= calculate_special_skill_damage(skill)
    end


    Следует использовать код, подобный этому:

    Код:
    result = calculate_special_skill_damage(skill)
    for actor in $game_party.actors
      actor.hp -= result
    end


    Однако, имейте в виду, что это действенно только в случае сложных вычислений. Сохранение и загрузка памяти между CPU и RAM в 10 или 15 раз медленнее простых арифметических операций, о чём я уже говорил. В первом примере метод запускается каждый раз для каждого героя. Во втором примере метод запускается только один раз и его результат сохраняется и может быть использован позднее. Может это и не сильно уменьшит время обработки скрипта, но представьте себе ситуацию, где вам нужно будет запустить метод для обработки каждого навыка в базе данных, коих, к примеру, 200 штук и в течение этого метода нужно будет выполнить другую вещь, например, для 300 анимаций. В итоге получится 200×300 = 60000 выполнений части кода для анимации. Вы хотите, чтобы ваш скрипт запускался 60000 лишних раз только в одном кусочке кода? Я так не думаю. Заметьте, что это имеет смысл только в том случае, если результат одинаковый для каждого актера, как в данном примере. А вот пример, где это не поможет:

    Код:
    for actor in $game_party.actors
      actor.hp -= calculate_special_skill_damage(actor.id)
    end


    Код:
    result = calculate_special_skill_damage(actor.id)
    for actor in $game_party.actors
      actor.hp -= result
    end


    Второй пример кода вызовет ошибку (undefined method ‘id’ for nil:NilClass) или ещё хуже: сработает. В этом случае результат будет зависеть от того, к какому герою применяется метод, так что вы не можете рассчитать всё заранее и сохранить результат. У вас нет другого выбора, кроме как производить вычисление каждый раз для каждого героя, так как он зависит от героя.

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

    Код:
    number = 5
    “#{number}”.to_s


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

    Код:
    nums = [0, 1, 2, 3]
    “#{nums[0]} #{nums[1]} #{nums[2]} #{nums[3]}”


    Код:
    nums = [0, 1, 2, 3]
    nums[0].to_s + nums[1].to_s + nums[2].to_s + nums[3].to_s


    Что вы думаете? Какой из них будет работать быстрее? Если вы думаете, что второй, то вы абсолютно правы. Однако разница незначительна и составляет всего-навсего 2%. Почему так мало? Всё очень просто. Возможно, операции to_s и требуется меньше времени на выполнение, чем внедрению переменных, но конкатенация строк (или другими словами присоединение одной строки к концу другой оператором +) приведет к потере времени процессора. Таким образом, если вам нужно сконвертировать число в строку, используйте to_s, но если вам нужно сконвертировать в строку и соединить с другими строками, то, по сути, нет разницы какой метод. Главное не используйте оба одновременно.

    5.4. Не бойтесь использовать больше кода

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

    Первый появляется с большим количеством лишних строк, в то время как вы могли бы использовать другую идею или метод для обработки данных. Возможно, вы даже могли бы использовать меньшее количество памяти для обработки данных. Это не плохо, если у вас есть такие строки, но стараюсь свести их до минимума. Большинство из них появляются в результате недостаточных знаний о том, как работаю компьютерные системы, а также о том, как устроен интерпретатор RGSS. Со временем вы обязательно наберётесь больше опыта и, взглянув на свои старые наработки, сразу же заметите такие строки и подумаете или даже воскликните “Эй! А зачем мне нужно ЭТО?! Ведь я могу сделать то же самое намного проще, сохранив время для обработки и при этом оставить только 4 команды из 7…” или “Эй… Тут столько ненужных переменных…” или…

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

    Код:
    class Bitmap
      def gradient_bar_2(x, y, w, h, fill_rate, c1, c2)
        # fill background with black color
        fill_rect(x, y, w, h, Color.new(0, 0, 0))
        # iterate through x coordinates, but not for all, only for the
        # fill_rate
        for i in 0...(w * fill_rate).to_i
          # iterate through y coordinates
          for j in 0...h
            # how the calculation works:
            # 1. base color is the color of c1
            # 2. get the difference between c2 and c1
            # 3. depending on which iteration it is, a color between
            # c1 and c2 will be chosen
            # 4. what “* i / w” is for: it will multiply the difference
            # with the “percentage” of the current iteration and modify
            # the difference
            r = c1.red + (c2.red - c1.red) * i / w
            g = c1.green + (c2.green - c1.green) * i / w
            b = c1.blue + (c2.blue - c1.blue) * i / w
            # if the second style is used
            if $style == 1
              # This will modify the colors depending on which “row” is
              # currently being drawn. “style 1” will make the upper
              # lines darker than lower lines. The lowest lines are in
              # the normal color. The result is a multi gradient.
              r = r * j / h
              g = g * j / h
              b = b * j / h
            end
            set_pixel(x+i, y+j, Color.new(r, g, b))
          end
        end
      end
    end


    Прорисовка пиксель за пикселем (а это то, что делает метод set_pixel) занимает довольно продолжительное время и может вызвать лаги. set_pixel почти на 40% быстрее, чем fill_rect и это значит, что set_pixel следует использовать только для прорисовки одного пикселя, а fill_rect если нужно прорисовать два и более пикселя. В следующем решении прорисовка стиля 1 будет всё так же занимать какое-то время, однако стиль 0 будет прорисовываться ГОРАЗДО быстрее.

    Код:
    class Bitmap
      def gradient_bar_2(x, y, w, h, fill_rate, c1, c2)
        fill_rect(x, y, w, h, Color.new(0, 0, 0))
        # check for style 1 already at the beginning
        if $style == 1
          for i in 0...(w * fill_rate).to_i
            for j in 0...h
              r = (c1.red + (c2.red - c1.red) * i / w) * j / h
              g = (c1.green + (c2.green - c1.green) * i / w) * j / h
              b = (c1.blue + (c2.blue - c1.blue) * i / w) * j / h
              set_pixel(x+i, y+j, Color.new(r, g, b))
            end
          end
        # this piece will get executed if “style 0” is being used.
        else
          for i in 0...(w * fill_rate).to_i
            r = c1.red + (c2.red - c1.red) * i / w
            g = c1.green + (c2.green - c1.green) * i / w
            b = c1.blue + (c2.blue - c1.blue) * i / w
            fill_rect(x+i, y, 1, height, Color.new(r, g, b))
          end
        end
      end
    end


    Если последовательно сравнить эти два кода (без строк с комментариями), то вы увидите, что второй больше первого на 4 строки, однако работает быстрее при использовании стиля 0. Пока что это голословное заявление, и вы можете не поверить, что второй код работает ГОРАЗДО быстрее со стилем 0. Чтобы убедиться своими глазами, просто скопируйте первый код и попробуйте испытать каждый стиль. Затем скопируйте второй код и попробуйте снова. Вы сразу заметите невероятные лаги со стилем 1 в обоих случаях, но со стилем 0 второй код будет выглядеть невероятно быстрым. Если вы, дочитав досюда, действительно решили опробовать сделать это, то создайте новый проект в RMXP и используйте следующий код. Просто добавьте его выше категории Main, как и один из кодов для градиента выше.

    Код:
    $style, $c1, $c2 = 1, Color.new(255, 0, 0),  Color.new(255, 255, 0)
    $sprite = Sprite.new
    $sprite.bitmap = Bitmap.new(128, 16)
    $sprite.z = 1000
    class Scene_Map
      alias _5_4_test update
      def update
        $sprite.bitmap.gradient_bar_2(0, 0, 128, 16, 0.8, $c1, $c2)
        _5_4_test
      end
    end


    Вы можете подумать, что улучшение в коде не очевидно, но представьте себе, если потребуется управлять 6 стилями, для 5 из которых код работает быстрее.
    Если вы заботливый скриптер и выпускаете обновления для ваших старых скриптов, то в своём старом коде вы обязательно заметите возможность усовершенствовать его тем или иным способом. Вы просто почувствуете необходимость сделать их лучше, чтобы они отражали ваше текущее состояние знаний. (могу привести пример из своей жизни, в сети вы больше не найдёте мой ранний скрипт под названием Credits. Новый скрипт Picture Movie Scene делает тоже, что делал Credits, но у него гораздо больше возможностей.)


    5.4.1. – злобный лаг, вызванный градиентом (слева стиль 0, справа стиль 1)

    5.5. RAM или CPU?

    Лаги являются проблемой во всей области программирования, не только в разработке игр. Само слово «лаг» — это только мирской термин, который говорит о том, что процессор (CPU) недостаточно мощный для обработки данных с такой скоростью, чтобы это прошло незамеченным. Изучайте чем живёт область программирования, изучайте как работают алгоритмы, изучайте как работает скриптовый язык и изучайте принципы работы процессоров и тогда вы шаг за шагом приблизитесь к избавлению от лагов. Заметьте, что эта глава не только объясняет, откуда берутся лаги, но и помогает вам лучше понять почему и что можно с этим сделать. Собственно говоря, всё это пособие рассказывает о том, как избежать лагов.



    [IURL="http://rpgmaker.su/showthread.php/518-Создание-скриптов-на-RGSS-для-людей-со-средними-знаниями-и-экспертов?p=9614&viewfull=1#post9614"]Вернуться к содержанию...[/IURL]
    Последний раз редактировалось Arnon; 30.10.2012 в 10:57.

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Метки этой темы

Социальные закладки

Социальные закладки

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •  
Создание скриптов на RGSS для людей со средними знаниями и экспертов