Архив SS13 · Форум · Вики по состоянию на

Kawaiidesu Guide 2

Материал из Animus Station 13
Версия от 17:16, 23 марта 2014; Epicus (обсуждение | вклад)

Этой страницы нет на живой вики. Удалена 11:50, 2 марта 2016 участником MasterOfDmx с комментарием «содержимое: «{{Удалить}}»».

Эта версия удалена с живой вики и восстановлена по сохранённой Wayback Machine копии страницы (вид, а не исходный текст). Источник: снимок.

Перейти к: навигация, поиск

Это руководство по программированию в BYOND для начинающих от Kawaiidesu.

Оригинал статьи находится на нашем форуме.

Другие руководства по программированию в BYOND на русском языке вы можете найти в раздале All about the code.

Наследование

И снова здравствуйте. Я Kawaiidesu и это второй урок по программированию в бьёнде.

В прошлом мы рассмотрели создание переменных и методов, объектов и использование процедур, в этом же мы глубже копнём методы, изучим основные возможности самого универсального цикла бьёнда и действий ("verb"), наследование, а также подружим два разных объекта.

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

/obj/flashlight
  var/on = 0
  var/charge = 1000

  proc/Switch()
    if(!charge && !on)
      return
    on = !on
 
  proc/Use(var/t = 3)
    charge = max(0, charge - t)
    if(!charge)
      Switch()

Сделаем для фонаря лампочку. Конечно, столь малый код проще сделать прямо в фонаре, но если мы собираемся использовать лампочку где-то ещё или просто хотим отделить одно от другого, то лучше создать новый объект. Лампочка будет обладать параметром "glower", который отвечает за состояние нити накаливания и "glass" за состояние стекла лампы. Конечно, вообще такие сложности ни к чему, но ради примера можно.

/obj/bulb
  //Status
  var/glower = 0
  var/glass = 0

Иногда метод гораздо удобнее, чем проверка каждой переменной, поэтому сделаем такой для проверки:

  proc/Broken()
    if(glower || glass)
      return 1
    return 0
  
  proc/AnotherBroken()
    return glower && glass

Здесь можно увидеть два примера - в первом переменные "складываются", во втором "умножаются". Любой из них будет работать одинаково хорошо, но я возьму первый. Как ранее говорилось, "return" отвечает за то, что возвращает метод - в нашем случае он отвечает, сломана ли лампочка. Сделаем эту лампочку тёплой и ламповой, пускай она греет людям сердца :3

  proc/Emit(var/r)
    if(Broken()) return
    for(var/mob/m in range(r)) m << "You feel some heat."

С первой строкой, я думаю, всё понятно, но во второй "return" на той же строке, что и "if", разве так можно? Да, если после системного оператора(for, if, while и прочее) вам хватает лишь одной строки, то можно поставить код прямо на той же строке, точно также мы можем написать "for(var/mob/m in range®) m << "You feel some heat."" в одну строку. Но не переусердствуйте с этим, обычно так лаконично смотрится лишь простой код.

Следующая строка с for это цикл, довольно сложная строчка, я скажу, поэтому разберём её по кусочкам с конца: range(n) это системный метод бьёнда, который возвращает связный список "list" (далее). "in" это также системный оператор, но немного другой. Он сверяет список справа с условием слева и возвращает уже фильтрованный список. Вообще данная строка читается как "Для (for) всех объектов типа /mob(var/mob/m) в (in) радиусе 3 (range(3))". Внимательные уже заметили "var" и, возможно, поняли, что мы создали переменную в цикле. Но чего мы этим добиваемся? Для этого надо рассмотреть работу "for" - он начинает листать список: берёт первый элемент списка, присваивает его укзанной и созданной специально для этого переменной, указанной в самом "for'е", исполняет код, указанный ниже него, потом повторяет, пока не кончатся объекты или не встретится оператор "break", который как "return" прерывает исполнение, но не метода, а конкретного цикла. Это самый сложный к описанию элемент и если вы его поняли - вы поймёте и дальше.

Переменной m будет присваиваться моб. "<<" - оператор вывода. В контексте бьёнда оператор вывода посылает файл, сообщение или что-то ещё прямо на текстовое поле всех клиентов, подключённых к данному объекту. К примеру, если двое человек управляют мобом, то им обоим пришлётся сообщение, указанное справа.

Теперь наша тёплая, ламповая лампочка светит тёплым, ламповым светом, но ведь её ещё нужно засунуть в фонарик, не так ли? Для этого мы будем использовать ссылку ("reference"/"ref") на объект. Ссылки создаются точно также, как и переменные. Так как в бьёнде используются нетипизированные переменные, то есть не важно, будет ли это строка, объект, число, то всё сокращается до лаконичного:

/obj/flashlight
  var/on = 0
  var/charge = 1000
  var/light ..

Но тут-то и возникает главное отличие ссылки от обычной переменной - ссылка не создаёт объект, она лишь указывает. То есть мы можем создать десять ссылок в разных местах, указать на один объект, и изменение через ссылку объекта отразится на всех ссылках.

Оператор создания нового объекта в бьёнде это "new()". Зачем же скобки? В них мы можем указать нужные для создания переменные, к примеру для всех стандартных классов бьёнда нужно указывать место, где появляется объект.

  New()
    ..()
    light = new /obj/bulb(src)
    l2 = light
    l3 = light

Здесь light, l2 и l3 будут полностью идентичны, они ссылаются на один объект. К тому же здесь мы впервые используем переопределение метода, но об этом позже. Далее, после new вы можете увидеть путь к объекту, я думаю, это очевидно, а в скобках "src". "src" это ссылка на объект, в котором выполняется метод, то есть мы создали новый объект типа /obj/bulb в фонаре, в котором и присутствует данный код. Вызов методов, как многие уже знают, осуществляется через "ProcName(args)", но как нам вызвать его у лампочки? Для этого существует оператор доступа ".", который позволяет нам получить доступ к переменным, ссылкам и методам объекта.

  proc/Use(var/t = 3)
    charge = max(0, charge - t)
    
    light.Emit(3)
    
    if(!charge)
      Switch()

Но тут возникает главная проблема - бьёнд не видит данного метода, так как мы не сообщили ему, на класс какого типа ссылается объект. Исправим это:

/obj/flashlight
  var/on = 0
  var/charge = 1000
  var/obj/bulb/light

Здесь мы видим, что переменная обзавелась путём, типом объекта в середине. Почему именно так? Скорее всего потому что это действительно удобный способ, отличающийся полнотой информации и отсутствием необходимости запоминать множество классов, а так же оставляющий возможность повторяемости названий. Вообщем удобно. Соберём всё вместе: слева, в самом начале, мы указываем, что это переменная("var"), дальше мы сообщаем тип объекта. Если мы этого не сделаем - бьёнд будет обращаться с ней, как с универсальной переменной и не даст нам доступа к методам и переменным. Закрывается же всё названием переменной. Также существует оператор "мягкого" доступа, который не выдаёт ошибку, если метод отсутствует, но реальная необходимость возникает редко, а глупо используют часто, поэтому я лишь сказал про наличие такой возможности.

Наследование классов и переопределение методов, это базис ООП, который позволяет слегка подправивить или полностью изменить объект, добавить новый функционал или изменить старый. Вы уже видели первый пример переопределения с системным методом New(), которая есть у абсолютно всех классов, но я приведу и другой пример. А вот текущей листинг кода.

/obj/flashlight
  var/on = 0
  var/charge = 1000
  var/obj/bulb/light

  New()
    ..()
    light = new /obj/bulb(src)

  proc/Switch()
    if(!charge && !on)
      return
    on = !on

  proc/Use(var/t = 3)
    charge = max(0, charge - t)    
    light.Emit(3)   
    if(!charge)
      Switch()

/obj/bulb
  var/glower = 0
  var/glass = 0

  proc/Broken()
    if(glower || glass)
      return 1
    return 0

  proc/Emit(var/r)
    if(Broken()) return
    for(var/mob/m in range(r)) m << "You feel some heat."

Допустим, нам понадобился фонарик с двумя лампочками, но нам нужен и старый с одной лампочкой. Что нам поможет? Наследование! Совершим наследование от фонаря и добавим новую переменную!

/obj/flashlight/powerful
  var/obj/bulb/anotherLight
  
  New()
    ..()
    anotherLight = new /obj/bulb(src)

Что мы здесь видим? Во-первых, изменился путь/тип, но если быть точнее, то он не изменился, а расширился. Остался старый "/obj/flashlight", но к нему добавился "/powerful". Что это даёт? У данного класса остаются все старые переменные, ссылки (далее просто переменные) и методы, но мы можем добавить новые, не затрагивая старых. Что мы и делаем в следующей строке, объявляя новую лампочку уже известным способом. Ещё можно заметить, что мы снова переопределяем "New()", добавляя в него создание новой лампочки и странный метод "..()". Этот метод отвечает за вызов аналогичного метода, но в интерпретации класса-родителя. Коротко говоря, он вызывает "New()" класса /obj/flashlight. Зачем нам это? Чтобы не переписывать создание первой лампочки, разумеется.

Но что это за сильный фонарь, если он греет так же недалеко? Исправляем!

  Use(var/t = 6)
    charge = max(0, charge - t)    
    light.Emit(8)   
    if(!charge)
      Switch()

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

/mob/flashlighter_monster
  name = "FLASHLIGHTEEEEER!!!!!!"

  var/obj/flashlight/right_flashlight
  var/obj/flashlight/left_flashlight
  
  New()
    ..()
    right_flashlight = new /obj/flashlight(src)
    left_flashlight = new /obj/flashlight/powerful(src)
 
  verb/Flashlight()
    right_flashlight.Use()
    left_flashlight.Use()
/obj/flashlight
  var/on = 0
  var/charge = 1000
  var/obj/bulb/light

  New()
    ..()
    light = new /obj/bulb(src)

  proc/Switch()
    if(!charge && !on)
      return
    on = !on

  proc/Use(var/t = 3)
    charge = max(0, charge - t)    
    light.Emit(3)   
    if(!charge)
      Switch()
/obj/flashlight/powerful
  var/obj/bulb/anotherLight
  
  New()
    ..()
    anotherLight = new /obj/bulb(src)
  Use(var/t = 6)
    charge = max(0, charge - t)    
    light.Emit(8)   
    if(!charge)
      Switch()

Создадим своего первого моба - монстра-флешлайтера, мухахахаха! У него: переопределена системная переменная name, если быть точнее - изменено её значение по умолчанию, а двум фонарям-рукам присвоены фонари - правый - обычный, левый- сильный. Хотя обе переменных были объявлены как "/obj/flashlight", так как метод "Use()" переопределён, то у сильного фонаря будет вызван именно "/obj/flashlight/powerful/Use()", а не "/obj/flashlight/Use()". Если сказать по простому - он будет использовать именно ту версию метода, которая заключена в классе в ссылке, а не в каркасе класса в переменной.

И самое главное - "verb"! Это действие, которое может вызвать у объекта, у моба, сам моб, условия изменяемы. Пока что достаточно лишь знать, что это метод, который может быть вызван пользователем вручную, как, к примеру, вращение стула.

К переменным доступ происходит точно так же, как и к методам, лишь не надо ставить скобки. К примеру

  verb/Flashlight()
    right_flashlight.Use()
    left_flashlight.Use()
    if(prob(1))
      right_flashlight.light.glower = 1

Здесь мы использовали системный метод prob(x), который выдаёт 1 с вероятностью в то значение, которое вы вобьёте, в процентах. То есть если написать prob(100), всегда будет 1, если prob(0), всегда 0, а если prob(50), то 50 на 50. Далее мы обращаемся через ссылку на ссылку и уже к переменной, присваивая ей значение 1. Сломалась лампочка, такие дела.