Войти на сайт

Авторизация, ждите ...
×

ТЕМА: Зрение противника

Зрение противника 6 года 4 мес. назад #89842

  • DeadElf79
  • DeadElf79 аватар
  • Вне сайта
  • Звездный Страж
  • Сообщений: 3147
  • Спасибо получено: 2650
  • Писатель 3 местоПроект месяца 1 местоУчительПрограммист RubyПроект месяца 2 место3 место1 место в ГотвВетеранОрганизатор конкурсов
Данная тема создана под впечатлением от разбора зрения противников на основе ивентов, начиная с поста от Юриоля.

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

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

Оглавление:
  1. Как поставить поле зрения, часть 1. Вводная для самых маленьких..
  2. Как поставить поле зрения, часть 2. Свойства и методы для самых маленьких.
  3. Как поставить поле зрения, часть 3. Установка области видимости.
  4. Поведение ивента. Опишем реакцию ивента на появление рядом игрока.
  5. Визуальная отладка. Отобразим поле зрения.
  6. Конус зрения
  7. Прятки за непроходимыми тайлами
  8. Прятки за крайними стенками

Посты будут появляться по мере написания, ждите ^_^
Последнее редактирование: 6 года 4 мес. назад от DeadElf79.
Администратор запретил публиковать записи гостям.
За этот пост поблагодарили: Демий, Jas6666, yuryol

Зрение противника 6 года 4 мес. назад #89843

  • DeadElf79
  • DeadElf79 аватар
  • Вне сайта
  • Звездный Страж
  • Сообщений: 3147
  • Спасибо получено: 2650
  • Писатель 3 местоПроект месяца 1 местоУчительПрограммист RubyПроект месяца 2 место3 место1 место в ГотвВетеранОрганизатор конкурсов
Как поставить поле зрения, часть 1
Вводная для самых маленьких

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

Заготовка для зрения ивента
Для зрения ивента нам понадобится внести изменения в класс Game_Event.

Создадим новый скрипт со следующим содержимым.
class Game_Event < Game_Character
    # тут будет наш код
end

Разберем по порядку.
Конструкция class...end описывает определенный класс объектов.

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

С помощью конструкции class...end мы описываем сам этот класс со всеми параметрами по умолчанию.
У этой конструкции есть один параметр: имя класса. Задается оно просто:
class ClassName
# ...
end

Здесь ClassName - это имя класса.
Класс может наследовать параметры другого класса вот таким вот способом
class ClassName < ParentClassName
 
end

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

К примеру, у вас есть пёс по кличке Шарик. Сам по себе Шарик - уникальная особь вида Собака. У этого вида (класса) по умолчанию четыре ноги, есть хвост, есть шерсть и много других параметров. Вспомним о том, что вы - экзмепляр класса Человек, у которого тоже есть параметры. На минутку посмотрим на себя, на своего пса, снова на себя и вдруг неожиданно для себя отметим, что у нас есть нечто общее. Вы и ваш пес - представители класса Млекопитающие. В этом классе собраны параметры, которые есть и у нас, и у собак, и у других видов. Основной параметр - вскармливание детенышей молоком.

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


картинка взята с сайта mimege.ru.

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

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

То есть для Game_Event не нужно второй раз писать константы из класса-родителя по имени Game_Caharacter. Иначе бы нас встречал вот примерно такой список сразу в двух скриптах:
ROUTE_END               = 0             # End of Move Route
  ROUTE_MOVE_DOWN         = 1             # Move Down
  ROUTE_MOVE_LEFT         = 2             # Move Left
  ROUTE_MOVE_RIGHT        = 3             # Move Right

Сам класс Game_Character наследует (иначе говоря, является потомком) класса Game_CharacterBase, в котором тоже описаны параметры по умолчанию для всех экземпляров этого класса, а также различные функции.

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

Когда мы пишем новый скрипт и добавляем его под всеми стандартными скриптами и при этом над Main, нам нет необходимости копировать весь код того класса, который мы собираемся изменить или дополнить.

Поэтому если мы пишем такой код:
class Game_Event < Game_Character
    # тут будет наш код
end
то мы говорим руби, что мы будем что-то дополнять или изменить. И вместо строки комментария (все, что начинается с # и до конца строки) мы будем вставлять наши изменения.

Так как посты ограничены по символам, то этот урок я разобью на несколько частей. В следующей мы уже напишем первый скрипт из необходимых и разберем его по кусочкам.
Последнее редактирование: 6 года 4 мес. назад от DeadElf79.
Администратор запретил публиковать записи гостям.
За этот пост поблагодарили: Демий, Jas6666, yuryol, DesKarD

Зрение противника 6 года 4 мес. назад #89855

  • Paranoid
  • Paranoid аватар
  • Вне сайта
  • Светлый дракон
  • Сообщений: 688
  • Спасибо получено: 350
Огосподитыбожемой, я ожидал увидеть готовый скрипт для зрения врагов. Теперь придется ждать(
Администратор запретил публиковать записи гостям.
За этот пост поблагодарили: JackCL

Зрение противника 6 года 4 мес. назад #89871

  • DeadElf79
  • DeadElf79 аватар
  • Вне сайта
  • Звездный Страж
  • Сообщений: 3147
  • Спасибо получено: 2650
  • Писатель 3 местоПроект месяца 1 местоУчительПрограммист RubyПроект месяца 2 место3 место1 место в ГотвВетеранОрганизатор конкурсов
Параноид, все хотят готовый скрипт, но никто не хочет подумать, как это работает. Сиди и жди, только потом не жалуйся, если не поймешь, как он устроен. ^_^
Администратор запретил публиковать записи гостям.
За этот пост поблагодарили: yuryol

Зрение противника 6 года 4 мес. назад #89909

  • DeadElf79
  • DeadElf79 аватар
  • Вне сайта
  • Звездный Страж
  • Сообщений: 3147
  • Спасибо получено: 2650
  • Писатель 3 местоПроект месяца 1 местоУчительПрограммист RubyПроект месяца 2 место3 место1 место в ГотвВетеранОрганизатор конкурсов
Как поставить поле зрения, часть 2
Свойства и методы для самых маленьких

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

Постановка задачи
Итак, что мы сделаем на этом уроке?
  1. Добавим ивентам параметр "область видимости"

Решение
Для начала добавим параметр в класс. Для этого нам понадобится перейти в метод под названием initialize, который вызывается, когда мы создаем объект класса. То есть, если в скриптах вы встретите Some_Class.new, то вы знаете, что не только создаете новый объект класса Some_Class, но и вызываете в нем метод под названием initialize.

Для начала перейдем в оригинальный скрипт Game_Event и скопируем оттуда себе весь этот метод.
class Game_Event < Game_Character
  def initialize(map_id, event)
    super()
    @map_id = map_id
    @event = event
    @id = @event.id
    moveto(@event.x, @event.y)
    refresh
  end
end

С помощью конструкции def...end мы создаем новый метод. Это примерно как конструкция создания класса, которую мы рассмотрели ранее. Только вот методы, в отличие от классов, объектами не являются и не могут наследовать свойства сами по себе. Но об этом мы поговорим немного позднее.

У конструкции def...end есть один основной параметр - имя метода, - и ноль или более дополнительных параметров, которые передаются этому методу.

Вот например, метод с одним только основным параметром:
def some_def
# здесь код метода
end

А вот другой метод, у которого, кроме имени, есть параметры, которые ему передаются для обработки:
def some_other_def(x,y,z)
# здесь код метода, который что-то делает с x,y и z
end

Теперь разберем наш код Game_Event построчно.
def initialize(map_id, event)
С этой строки мы начинаем описание метода. Параметры его таковы:
  • map_id - номер карты (вы можете посмотреть его в редакторе карт в правом нижнем углу)
  • event - объект класса RPG::Event. Вот тут остановимся подробнее.

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

    Модуль, описывающий стандартные виды данных, называется RPG и уже "вшит" в базовую сборку RPG Maker. Подробнее о данных вы можете почитать непосредственно в справке.

    Идем далее. Чтобы вы не потерялись и не возвращались каждый раз к изначальному коду, я буду просто добавлять одну строку и отмечать ее стрелочкой, чтобы показать, что именно ее мы и рассматриваем.
    def initialize(map_id, event)
    super() # <-

    Вот тут мы вспомним прошлый урок. Помните, я говорил, что классы-потомки могут наследовать свойства и методы класса родителя? С помощью вызова метода super мы указываем классу, что он должен взять метод с тем же названием из класса-родителя и выполнить его полностью. По умолчанию все методы, которые есть у класса-потомка, описаны так:
    class ClassName < ParentClassName
        def some_parent_method
            super()
        end
    end

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

    Именно это и делает Game_Event! :laugh: После вызова метода класса-родителя у него идет добавление новых свойств и вызов двух методов. Чтобы сократить текст урока, рассмотрим сразу три свойства подряд.
    def initialize(map_id, event)
    super()
    @map_id = map_id # <-
    @event = event # <-
    @id = @event.id # <-

  • @map_id - номер карты, он сохраняется в ивенте
  • @event - объект класса RPG::Event, из которого ивент будет брать данные
  • @id - номер ивента, который мы взяли из объекта класса RPG::Event

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

    Идем далее.
    class Game_Event < Game_Character
      def initialize(map_id, event)
        super()
        @map_id = map_id
        @event = event
        @id = @event.id
        moveto(@event.x, @event.y) # <-
        refresh # <-
      end
    end

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

    Строки c end я рассматривать не буду, они лишь завершают описание метода и класса.

    А теперь, когда мы уже все рассмотрели, добавим, наконец, наш параметр.
    class Game_Event < Game_Character
      def initialize(map_id, event)
        super()
        @map_id = map_id
        @event = event
        @id = @event.id
        moveto(@event.x, @event.y)
        refresh
     
        @view_area = nil # <-
      end
    end

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

    P.S. Скриптеры, не ругайте за отсутствие описания такой полезной штуки, как alias. Не вмещалось оно, так что рассмотрим его впоследствии ^_^
  • Последнее редактирование: 6 года 4 мес. назад от DeadElf79.
    Администратор запретил публиковать записи гостям.
    За этот пост поблагодарили: Демий, DesKarD

    Зрение противника 6 года 4 мес. назад #89910

    • DeadElf79
    • DeadElf79 аватар
    • Вне сайта
    • Звездный Страж
    • Сообщений: 3147
    • Спасибо получено: 2650
    • Писатель 3 местоПроект месяца 1 местоУчительПрограммист RubyПроект месяца 2 место3 место1 место в ГотвВетеранОрганизатор конкурсов
    Как поставить поле зрения, часть 3
    Установка области видимости

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

    Постановка задачи
    Итак, что мы сделаем на этом уроке?
    1. Определим область видимости по умолчанию
    2. Будем изменять область видимости в зависимости от того, куда смотрит ивент

    Решение
    Для начала область видимости будет как на первом скриншоте у Юриоля.
    ВНИМАНИЕ: Спойлер! [ Нажмите, чтобы развернуть ]


    Чтобы ее создать, нам нужно взять прямоугольник. Предположим, что по умолчанию ивент смотрит вверх. Тогда левых верхний угол будет упираться в левый верхний угол карты, ширина прямоугольника будет такой же, как и ширина карты, а высота будет вычисляться по координате y.
    class Game_Event < Game_Character
      def initialize(map_id, event)
        super()
        @map_id = map_id
        @event = event
        @id = @event.id
        moveto(@event.x, @event.y)
        refresh
     
        @view_area = Rect.new(0,0,$game_map.width,event.y) # <-
      end
    end

    Я отметил стрелочкой ту строку, которую изменил. Расскажу немного подробнее о ней. Мы создаем новый объект класса Rect (прямоугольник), который входит в состав базовой библиотеки RGSS. У этого класса есть свойства координат в пространстве, ширины и высоты. Сам по себе прямоугольник в игре не виден, потому что это не визуальный объект. Поэтому не удивляйтесь, что при запуске проекта с этим кодом вы не увидите изменений.

    И вот, смотрите-ка, с первой целью данного урока мы разобрались! Достаточно быстро, не так ли? Идем далее.

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

    Дело в том, что почти у всех внутриигровых классов в RGSS есть метод update, который вызывается для того, чтобы "обновить" объекты этих классов. Как правило этот метод вызывается каждый кадр, если объект активен. То есть если у нас есть ивент, то каждый кадр игра вызывает update, который проверяет условия выполнения ивента, необходимость движения и прочее.

    Вот оригинал кода этого метода, который выполняет все вышеописанное.
    def update
        super
        check_event_trigger_auto
        return unless @interpreter
        @interpreter.setup(@list, @event.id) unless @interpreter.running?
        @interpreter.update
    end

    А теперь сделаем небольшое отступление, чтобы установить некоторое правило для всего последующего кода.
    Есть множество разработчиков на RPG Maker. Среди них есть множество людей, которые пишут скрипты. Каждый новый скрипт для совместимости с другими должен не изменять напрочь, а дополнять работу других. Именно для этого существует механизм вызова сохраненной копии метода. Сейчас я объясню его работу.

    Для начала мы возьмем какой-нибудь метод, например, такой:
    def some_def
    # какой-то код там внутри
    end

    Сохраним копию этого метода со всем кодом, который внутри него находится, под новым именем.
    alias copy_of_some_def some_def
    def some_def
    # какой-то код там внутри
    end

    Здесь copy_of_some_def - это новый метод, который делает все то же самое, что и some_def и может быть даже вызван откуда-нибудь. Что мы и сделаем.
    alias copy_of_some_def some_def
    def some_def
        copy_of_some_def
        # а здесь будет новый код, который не заменяет, но дополняет предыдущий
    end

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

    Помните также, что все параметры, которые требует оригинал, будет требовать и копия. Для того, чтобы это продемонстрировать, перепишем наш скрипт, а именно - код метода initialize.
    class Game_Event < Game_Character
      alias copy_of_initialize initialize
      def initialize(map_id, event)
        copy_of_initialize(map_id, event)
     
        @view_area = Rect.new(0,0,$game_map.width,event.y)
      end
    end

    Смотрите, насколько короче стал наш код! И, главное, мы не заменяем стандартную обработку, так что теперь он совместим со скриптами от других разработчиков!

    Кстати, еще одно небольшое дополнение (спасибо, Амфилохий): если мы пишем новый скрипт для изменения старого, то указывать родительский класс после названия класса-потомка не обязательно. Таким образом мы еще немного упростим код:
    class Game_Event
      alias copy_of_initialize initialize
      def initialize(map_id, event)
        copy_of_initialize(map_id, event)
     
        @view_area = Rect.new(0,0,$game_map.width,event.y)
      end
    end

    И теперь, обладая такими крутыми и навороченными знаниями, добавим update.
    class Game_Event
      alias copy_of_initialize initialize
      def initialize(map_id, event)
        copy_of_initialize(map_id, event)
     
        @view_area = Rect.new(0,0,$game_map.width,event.y)
      end
     
      alias copy_of_update update
      def update
        copy_of_update
      end
    end

    Далее нам понадобится изменять область видимости по таким условиям:
  • если ивент смотрит вверх, то левый угол расположен в левом верхнем углу карты, ширина равна ширине карты, высота равна координате y.
  • если ивент смотрит вниз, то левый угол по x расположен вначале карты и по y равен координате ивента, ширина равна ширине карты, высота равна разнице между высотой карты и координатой ивента.
  • если ивент смотрит влево, то левый угол расположен в левом верхнем углу карты, ширина равна координате x ивента, высота равна высоте карты.
  • если ивент смотрит вправо, то левый угол расположен в координатах: x - координата x ивента, y - начало карты, ширина равна разнице между шириной карты и координатой x ивента, высота равна высоте карты.

    Для описания такой группы условий конструкция if...else...end не подойдет. Нам нужна конструкция для проверки множества не сочетаемых условий, которые физически неспособны происходить одновременно. И это конструкция case...when...end.

    Вот код готового метода:
    alias copy_of_update update
      def update
        copy_of_update
     
        # В качестве условия - направление взгляда ивента
        case @direction
        when 8 # Смотрит вверх
          @view_area = Rect.new(0,0,$game_map.width,@event.y)
        when 2 # Смотрит вниз
          @view_area = Rect.new(0,@event.y,$game_map.width,$game_map.height[email protected]event.y)
        when 4 # Смотрит влево
          @view_area = Rect.new(0,0,@event.x,$game_map.height)
        when 6 # Смотрит вправо
          @view_area = Rect.new(@event.x,0,$game_map.width[email protected]event.x,$game_map.height)
        end
      end

    На этом все. В следующем уроке мы опишем реакцию ивента на появление игрока в области видимости.
    Вот полный код на данный момент:
    class Game_Event
      # Создаем копию оригинального метода для совместимости
      alias copy_of_initialize initialize
      def initialize(map_id, event)
        # Вызываем сохраненную копию, передаем те же параметры
        copy_of_initialize(map_id, event)
        # Дописываем новое свойство и задаем область видимости по умолчанию
        @view_area = Rect.new(0,0,$game_map.width,event.y)
      end
     
      # Создаем копию оригинального метода для совместимости
      alias copy_of_update update
      def update
        # Вызываем сохраненную копию, параметров у нее нет
        copy_of_update
     
        # В качестве условия - направление взгляда ивента
        case @direction
        when 8 # Смотрит вверх
          @view_area = Rect.new(0,0,$game_map.width,@event.y)
        when 2 # Смотрит вниз
          @view_area = Rect.new(0,@event.y,$game_map.width,$game_map.height[email protected]event.y)
        when 4 # Смотрит влево
          @view_area = Rect.new(0,0,@event.x,$game_map.height)
        when 6 # Смотрит вправо
          @view_area = Rect.new(@event.x,0,$game_map.width[email protected]event.x,$game_map.height)
        end
      end
    end

    До скорых встреч!
  • Последнее редактирование: 6 года 4 мес. назад от DeadElf79.
    Администратор запретил публиковать записи гостям.
    За этот пост поблагодарили: I_LORD, Демий, Jas6666, DesKarD
    Время создания страницы: 1.142 секунд