TRam_
21.10.2009, 00:47
в формате doc
http://dump.ru/file/3619559
Простейшие скрипты
Что такое скрипты? Это маленькие подпрограммы, на которых можно создавать различные явления игровового мира, в народе называемые "фитчами"(feature - свойство). В данном уроке постараюсь объяснить, как написать простой (и не очень глючный) скрипт для какого-нибудь вагона. Сразу скажу, что я смог его только откомпилировать, но в самой игре не смотрел (видюха не поддерживает openGL). Кое-что из объектно-ореентированного программирования я уже писал на railunion'e, остальное лучше почитать в любой книге по объектно-ориентированному языку программирования (лучше семейства Си - C++, C#, Java). Будем считать, что кое-что вы из этого знаете. Итак, допустим, нам требуется сделать вагон, который будет менять разъединённые шланги тормозной магистрали на соединённые, если он хвостовой - дополнительно устанавливать диск хвоста поезда, и при проследовании моста будет издавать дополнительный звук, в снежный день будет пылить снегом.
Начнём с того, что внимательно поищем зацепки, с помощью которых мы сможем ухватиться для обнаружения и обработки того или иного события. Пока предположу, что у вас trainz не ниже Классика (в 2006 одной из вещей нет).
Мы скачали с files.auran.com архив TRS2006-API.zip , и распаковали содержащийся в нём index.chm . Хотя по применимости к 2009 он несколько устарел, всё новое можете посмотреть на сайте trainzdev.com либо почитав базовые скрипты в вашей директории trainz (папка scripts).
Открываем в index.chm раздел Class API Reference, в нём подраздел с таким же названием. Видим длинный список всех базовых классов, которые описаны в скриптах любой trainz 2006 . Нам потребуется класс Vehicle (Вагон), все его родительские классы, а также некоторы другие, которые я укажу позже. Советую перед этим поставить любой электронный cловарь (либо переводчик), так как описание на английском.
Начнём с самой первой функции, которая стоит в оглавлении. В данном случае её можно назвать самой главной. Это
public void Init (void)
(расшифрую - публичная(её может вызвать кто-угодно), не возвращающая ничего, и не нимеющая аргументов функция "иницализация" )
по ссылке находим её описание - Called by Trainz to initialize this Vehicle. (вызывается движком трейнз для инициализации объекта Вагон).
Т.Е. если в эту функцию чего-нибудь добавить, то при инициализации вагона она будет вызвана, а наше добавление выполнится. Инициализация вагона (да и вообще почти всех объектов класса MeshObject и его подклассов, в т.ч. Vehicle) происходит всего 1 раз - при постановке их на карту либо при открытии карты(за исключением кабины). При входе в быстрый машинист переинициализации НЕ ПРОИСХОДИТ, поэтому этот переход надо писать отдельно.
Но там написано замечание - If overriding this method to perform your own custom vehicle initialization, always call back to this parent method via the inherited keyword.
Перевожу - "если вы делаете свой собственный скрипт, вы обязаны вызвать родительский метод с помощью тега inherited". Это значит, что лучше написать своё описание в своём собственном скрипте так
public void Init (void)
{
inherited(void);
//наши добавления
}
Значит это следующее - когда движок вызовет Init вагона с нашим собственным классом, то он в начале выполнит все инструкции класса-родителя (в данном случае vehicle), а затем выполнит все наши инструкции.
С тем, кто будет наш скрипт "запускать", разобрались. Смотрим дальше.
следующая функция public native Train GetMyTrain (void) .
Но в описании её написали как public native Train Vehicle.GetMyTrain(void) . Зачем же нужна эта часть "Vehicle." ?
А вот зачем. Мы вызываем функцию некого объекта. А объект, чья функкция вызывается, тоже надо указать, и это указание позволяет нам управлять как функциями нашего объекта, так и функциями других объектов(если они публичные). Вызов функции нашего объекта вообще-то записывается me.GetMyTrain(void); , но для простоты это же можно записать как GetMyTrain(void); Различия между этими 2 записями никакой нет, но лично я предпочитаю 1 - сразу понятно, над каким объектом проводится функция (т.е. над нами же)
Она нам тоже понадобится. Как мы видим, она возвращает объект класса Поезд, которому принадлежит этот вагон ( Gets the Train this vehicle belongs to.). Но о этом попозже.
Далее идёт public native bool GetDirectionRelativeToTrain (void) . Очнь полезная функция. Определяет, где перед вагона и где его зад относительно поезда. Понадобится нам, когда будем определять, какая шланга у нас соединена, а какая - нет.
(заодно и поставим диск конца поезда)
Полезна и public native bool IsOnBridge (void) - определяет, находится ли наш вагон на мосту.
Прокручиваем АПИ далее, и видим табличку с заголовом Vehicle object messages . Перевод описания системы сообщений есть на трейнсиме (ссылку можете найти на railunion'e), и в данном случае нам понадобится 2 и 3
"Vehicle","Coupled" посылаемое от сцепляемого вагона всем остальным объектам.
"Vehicle" "Decoupled" посылаемое от одного из 2 расцепляемых вагонов всем остальным объектам. В 2006 она плохо работала, в последующих её исправили (об этом я и сказал в самом начале), но я сделаю "заплатку" для работы в 2006.
А теперь смотрим родительские классы. Из класса Trackside нам пока ничего не понадобится, из MapObject - тоже.
А вот из MeshObject кое-что нам понадобится. А именно
public native MeshObject MeshObject.SetFXAttachment(string effectName, Asset asset)
Как вы поняли, именно этой функцией мы будем цеплять мешь. А так как объект меши нам не нужен(честно, об этом объекте я узнал когда писал эту статью :) ), то можем его никому не возвращать. То есть записи
MeshObject MO= (cast<MeshObject>me).SetFXAttachment("front_couple",myScepkaAsset); //так тут мы явно преобразовали наш объект из нашего класса в класс MeshObject
MeshObject MO= me.SetFXAttachment("front_couple",myScepkaAsset);
MeshObject MO= SetFXAttachment("front_couple",myScepkaAsset);
me.SetFXAttachment("front_couple",myScepkaAsset);
SetFXAttachment("front_couple",myScepkaAsset);
(cast<MeshObject>me).SetFXAttachment("front_couple",myScepkaAsset);
для данного случая абсолютно идентичны.
Ну а чтобы отцепить мешь, можно провести
SetFXAttachment("front_couple",null);
где под null понимается указатель на несуществующий объект (тоесть мы именно такой несуществующий объект цепляем вместо старого, в результате на месте сцепки у нас ничего нет). Кстати, 2004 не умела такое делать - там надо было цеплять только существующую мешь (там для имитации полной отцепки новая мешь должна была быть невидимой)
Как вы догадались, string effectName - это имя эффекта, прописанное в конфиге (в примере - довольно распространённое имя "front_couple", используемое в оснащённых скриптами AlexEF'a локах).
Далее нам потребуется класс Asset, который представляет ссылку на "дополнение"(мы его называем допом, куидом(что кстати неверно)), которое установлено в CMP (а не объект, стоящий на карте).
Но в начале мы хотя бы должны знать, какие именно "дополнения" нам потребуются. Для этого лучше всего использовать kuid-table нашего же вагона. Так как эта table ("таблица") находится вне скрипта, то её содержимое можно менять, а значит скрипт можно будет ставить вагонам с разными сцепками.
Чтобы её получить, нам надо посмотреть ещё более общий родитель класса Vehicle - класс TrainzGameObject .
В нём всего 2 функции, одна из которых нам нужна - это public Asset TrainzGameObject.GetAsset(void) С помощью этой функции мы получим "дополнение" нашего же вагона. Например так
Asset MyAsset = (cast<TrainzGameObject>me).GetAsset(void);
а можно намного короче
Asset MyAsset = GetAsset();
значат эти 2 строки одно и то же, но, думаю, начинающим 1 вариант более понятен.
А как же получать это самое kuid-table получить-то из "дополнения" нашего вагона?
А функцией public native Asset Asset.FindAsset(string kuidTableAssetName) .
А string kuidTableAssetName - строка с именем из kuid-table . Пуолучение ассета, написанного в куид-тейбле как
SA3-uncoupled <kuid:19878:142>
можно сделать так
Asset MyAsset = (cast<TrainzGameObject>me).GetAsset(void);
Asset SA3_coupled = MyAsset.FindAsset("SA3-uncoupled");
или
Asset SA3_uncouped = GetAsset().FindAsset("SA3-uncoupled");
Только учитываем, что все пересенные и ссылки, которыми могут пользоваться другие функции нашего скрипта, должны быть написаны вне функций (глобальные переменные).
Осталось посмотреть ещё 4 класса. Первым будет класс Train , из которого нам понадобится 2 функции -
public native Vehicle[] Train.GetVehicles (void) - получаем массив ссылок на все вагоны нашего поезда в порядке следования от головы, и с нумерацией, начинающейся с 0.
Чтобы наш вагон нашёл этот массив вагонов нужно сделать
Train MyTrain= (cast<Vehicle>me).GetMyTrain(void);
Vehicle[] VehicleArray= MyTrain.GetVehicles (void);
а можно кратко
Vehicle[] VehicleArray= GetMyTrain().GetVehicles();
Из этого класса нам потребуется также функция определения скорости (чтоб поезд не снежил во время стоянки) -
public native float Train.GetVelocity (void)
но проблема в том, что получаемая скорость имеет знак, а нам надо получать её абсолютную величину (модуль).
http://dump.ru/file/3619559
Простейшие скрипты
Что такое скрипты? Это маленькие подпрограммы, на которых можно создавать различные явления игровового мира, в народе называемые "фитчами"(feature - свойство). В данном уроке постараюсь объяснить, как написать простой (и не очень глючный) скрипт для какого-нибудь вагона. Сразу скажу, что я смог его только откомпилировать, но в самой игре не смотрел (видюха не поддерживает openGL). Кое-что из объектно-ореентированного программирования я уже писал на railunion'e, остальное лучше почитать в любой книге по объектно-ориентированному языку программирования (лучше семейства Си - C++, C#, Java). Будем считать, что кое-что вы из этого знаете. Итак, допустим, нам требуется сделать вагон, который будет менять разъединённые шланги тормозной магистрали на соединённые, если он хвостовой - дополнительно устанавливать диск хвоста поезда, и при проследовании моста будет издавать дополнительный звук, в снежный день будет пылить снегом.
Начнём с того, что внимательно поищем зацепки, с помощью которых мы сможем ухватиться для обнаружения и обработки того или иного события. Пока предположу, что у вас trainz не ниже Классика (в 2006 одной из вещей нет).
Мы скачали с files.auran.com архив TRS2006-API.zip , и распаковали содержащийся в нём index.chm . Хотя по применимости к 2009 он несколько устарел, всё новое можете посмотреть на сайте trainzdev.com либо почитав базовые скрипты в вашей директории trainz (папка scripts).
Открываем в index.chm раздел Class API Reference, в нём подраздел с таким же названием. Видим длинный список всех базовых классов, которые описаны в скриптах любой trainz 2006 . Нам потребуется класс Vehicle (Вагон), все его родительские классы, а также некоторы другие, которые я укажу позже. Советую перед этим поставить любой электронный cловарь (либо переводчик), так как описание на английском.
Начнём с самой первой функции, которая стоит в оглавлении. В данном случае её можно назвать самой главной. Это
public void Init (void)
(расшифрую - публичная(её может вызвать кто-угодно), не возвращающая ничего, и не нимеющая аргументов функция "иницализация" )
по ссылке находим её описание - Called by Trainz to initialize this Vehicle. (вызывается движком трейнз для инициализации объекта Вагон).
Т.Е. если в эту функцию чего-нибудь добавить, то при инициализации вагона она будет вызвана, а наше добавление выполнится. Инициализация вагона (да и вообще почти всех объектов класса MeshObject и его подклассов, в т.ч. Vehicle) происходит всего 1 раз - при постановке их на карту либо при открытии карты(за исключением кабины). При входе в быстрый машинист переинициализации НЕ ПРОИСХОДИТ, поэтому этот переход надо писать отдельно.
Но там написано замечание - If overriding this method to perform your own custom vehicle initialization, always call back to this parent method via the inherited keyword.
Перевожу - "если вы делаете свой собственный скрипт, вы обязаны вызвать родительский метод с помощью тега inherited". Это значит, что лучше написать своё описание в своём собственном скрипте так
public void Init (void)
{
inherited(void);
//наши добавления
}
Значит это следующее - когда движок вызовет Init вагона с нашим собственным классом, то он в начале выполнит все инструкции класса-родителя (в данном случае vehicle), а затем выполнит все наши инструкции.
С тем, кто будет наш скрипт "запускать", разобрались. Смотрим дальше.
следующая функция public native Train GetMyTrain (void) .
Но в описании её написали как public native Train Vehicle.GetMyTrain(void) . Зачем же нужна эта часть "Vehicle." ?
А вот зачем. Мы вызываем функцию некого объекта. А объект, чья функкция вызывается, тоже надо указать, и это указание позволяет нам управлять как функциями нашего объекта, так и функциями других объектов(если они публичные). Вызов функции нашего объекта вообще-то записывается me.GetMyTrain(void); , но для простоты это же можно записать как GetMyTrain(void); Различия между этими 2 записями никакой нет, но лично я предпочитаю 1 - сразу понятно, над каким объектом проводится функция (т.е. над нами же)
Она нам тоже понадобится. Как мы видим, она возвращает объект класса Поезд, которому принадлежит этот вагон ( Gets the Train this vehicle belongs to.). Но о этом попозже.
Далее идёт public native bool GetDirectionRelativeToTrain (void) . Очнь полезная функция. Определяет, где перед вагона и где его зад относительно поезда. Понадобится нам, когда будем определять, какая шланга у нас соединена, а какая - нет.
(заодно и поставим диск конца поезда)
Полезна и public native bool IsOnBridge (void) - определяет, находится ли наш вагон на мосту.
Прокручиваем АПИ далее, и видим табличку с заголовом Vehicle object messages . Перевод описания системы сообщений есть на трейнсиме (ссылку можете найти на railunion'e), и в данном случае нам понадобится 2 и 3
"Vehicle","Coupled" посылаемое от сцепляемого вагона всем остальным объектам.
"Vehicle" "Decoupled" посылаемое от одного из 2 расцепляемых вагонов всем остальным объектам. В 2006 она плохо работала, в последующих её исправили (об этом я и сказал в самом начале), но я сделаю "заплатку" для работы в 2006.
А теперь смотрим родительские классы. Из класса Trackside нам пока ничего не понадобится, из MapObject - тоже.
А вот из MeshObject кое-что нам понадобится. А именно
public native MeshObject MeshObject.SetFXAttachment(string effectName, Asset asset)
Как вы поняли, именно этой функцией мы будем цеплять мешь. А так как объект меши нам не нужен(честно, об этом объекте я узнал когда писал эту статью :) ), то можем его никому не возвращать. То есть записи
MeshObject MO= (cast<MeshObject>me).SetFXAttachment("front_couple",myScepkaAsset); //так тут мы явно преобразовали наш объект из нашего класса в класс MeshObject
MeshObject MO= me.SetFXAttachment("front_couple",myScepkaAsset);
MeshObject MO= SetFXAttachment("front_couple",myScepkaAsset);
me.SetFXAttachment("front_couple",myScepkaAsset);
SetFXAttachment("front_couple",myScepkaAsset);
(cast<MeshObject>me).SetFXAttachment("front_couple",myScepkaAsset);
для данного случая абсолютно идентичны.
Ну а чтобы отцепить мешь, можно провести
SetFXAttachment("front_couple",null);
где под null понимается указатель на несуществующий объект (тоесть мы именно такой несуществующий объект цепляем вместо старого, в результате на месте сцепки у нас ничего нет). Кстати, 2004 не умела такое делать - там надо было цеплять только существующую мешь (там для имитации полной отцепки новая мешь должна была быть невидимой)
Как вы догадались, string effectName - это имя эффекта, прописанное в конфиге (в примере - довольно распространённое имя "front_couple", используемое в оснащённых скриптами AlexEF'a локах).
Далее нам потребуется класс Asset, который представляет ссылку на "дополнение"(мы его называем допом, куидом(что кстати неверно)), которое установлено в CMP (а не объект, стоящий на карте).
Но в начале мы хотя бы должны знать, какие именно "дополнения" нам потребуются. Для этого лучше всего использовать kuid-table нашего же вагона. Так как эта table ("таблица") находится вне скрипта, то её содержимое можно менять, а значит скрипт можно будет ставить вагонам с разными сцепками.
Чтобы её получить, нам надо посмотреть ещё более общий родитель класса Vehicle - класс TrainzGameObject .
В нём всего 2 функции, одна из которых нам нужна - это public Asset TrainzGameObject.GetAsset(void) С помощью этой функции мы получим "дополнение" нашего же вагона. Например так
Asset MyAsset = (cast<TrainzGameObject>me).GetAsset(void);
а можно намного короче
Asset MyAsset = GetAsset();
значат эти 2 строки одно и то же, но, думаю, начинающим 1 вариант более понятен.
А как же получать это самое kuid-table получить-то из "дополнения" нашего вагона?
А функцией public native Asset Asset.FindAsset(string kuidTableAssetName) .
А string kuidTableAssetName - строка с именем из kuid-table . Пуолучение ассета, написанного в куид-тейбле как
SA3-uncoupled <kuid:19878:142>
можно сделать так
Asset MyAsset = (cast<TrainzGameObject>me).GetAsset(void);
Asset SA3_coupled = MyAsset.FindAsset("SA3-uncoupled");
или
Asset SA3_uncouped = GetAsset().FindAsset("SA3-uncoupled");
Только учитываем, что все пересенные и ссылки, которыми могут пользоваться другие функции нашего скрипта, должны быть написаны вне функций (глобальные переменные).
Осталось посмотреть ещё 4 класса. Первым будет класс Train , из которого нам понадобится 2 функции -
public native Vehicle[] Train.GetVehicles (void) - получаем массив ссылок на все вагоны нашего поезда в порядке следования от головы, и с нумерацией, начинающейся с 0.
Чтобы наш вагон нашёл этот массив вагонов нужно сделать
Train MyTrain= (cast<Vehicle>me).GetMyTrain(void);
Vehicle[] VehicleArray= MyTrain.GetVehicles (void);
а можно кратко
Vehicle[] VehicleArray= GetMyTrain().GetVehicles();
Из этого класса нам потребуется также функция определения скорости (чтоб поезд не снежил во время стоянки) -
public native float Train.GetVelocity (void)
но проблема в том, что получаемая скорость имеет знак, а нам надо получать её абсолютную величину (модуль).