итак (потом всё это прийдётся собирать в "русский перевод АПИ")
1) скрипт состоит из классов, которые в свою очередь содержат методы и глобальные переменные
2) классы могут наследоваться, т.е. все методы класса - родителя наследуются классом наследником (если не добавлена "перегрузка метода")
например
class MyClass001//обьявляем класс-родитель
{
}
class childMyClass001 isclass MyClass001//обьявляем класс-наследник
{
}
Итак, у этих классов есть глобальные переменные и методы
глобыльные переменные (и переменные вообще) бывают 2 видов
1) обычные переменные, такие как
string aaaaa; int n1; bool r; GameObject Mytown;
2) массивы, такие как
int[] massiv1; MapObject[] objectsPack;
и т.д.
Естественно классы, такие как MapObject или GameObject должны быть определены хотя бы в одном из классов, которые
а) содержатся в одном из скриптов, находящемся в папке Scripts директории трейнз
б) эти классы содержится в данном скрипте
в) эти классы находятся в другом скрипте, который "подключили" к этому
(напомню, подключение скрипта из папки локомотива происходит при вставке в директорию куида копии необходимого Вам скрипта и постановке в начало скрипта строки
include "****.gs"
где ****.gs - подключаемый скрипт. Не правда-ли напоминает подключение библиотек СИ ?) (в 2009 можно подключать скрипты не только из директории куида, но и из внутр. папок, но для этого надо с конфигом возиться)
Теперь методы. Их также существует 2 вида
1)функции
2)потоки
Кроме того, методы бывают 2 типов -
1)обычные, "защищённые" (т.е. их запустить из другого скрипта нельзя)
2)публичные (т.е. если в вызывающем (другом) скрипте содержится их описание, он (другой скрипт) может их попытаться вызвать вне зависимости от желания класса, которому принадлежит этот метод)
В принципе, при описании потоки и функции друг от друга почти не отличаются, но выполняют 2 совершенно разные функции
1) функция - является самым обычным "линейным алгоритмом", который подробно изучают школьники. Вызывающая его процедура ждёт, пока он закончится, поэтому если Вы поставите в нём бесконечный цикл, то убедитесь, что трейнз как и МС-ДОС тоже может "виснуть". Применяются, если не охота писать один и тот же кусок кода, либо для того, чтобы проще было изменять "кусками" функции обьекта-родителя, пользуясь методом "перегрузки". Напирмер,
class Myclass
{
float ploschad_pramougolnika(int a; float b)//функция с 2 переменными
{
float c=a*b; //вычисляем площадь
return c; //выводим значение площади
/*хотя можно было не объявлять переменную с, а просто записать
return a*b;
*/
};
и пример функции - вызывающей эту
int getploschad2(float a; int b)//данная функция должна "отбрасывать" дробную часть числа а, и передать его 1 функции именно 1 аргументом(ну я допустим так захотел)
// напомню, что локальные переменные, обьявленные в разных функциях абсолютно не взаимодействуют, не смотря на то, что они имеют одинаковые названия
{
int с = (int)a; // итак, у нас есть преобразованный в челосисленный агрумент а
return ploschad_pramougolnika(c,b); //напомню, преобразование из целого в дробное идёт автоматически
};
};// не забываем, что у нас класс тоже закончился
2)поток. Это почти тоже что и функция, НО при его загрузке вызывающая процедура "забывает" о нём и продолжает работать как ни в чём не бывало. Т.е. мы получили 2 одновременно работающих процесса - вызывавшая процедура и существующий поток (похоже на использование конструкции с функцией fork() из API linux). Распределение времени процессора между ними происходит автоматически (в отличии от КРС).
пример чуть другой
class MyClass2
{
int a1=0;
thread void Checker(float dt1; int newNumber) //итак перед Вами поток. Как видите, схема описания отличается от функции только наличием ключевого слова thread. Я хочу написать поток, который всю игру будет РОВНО раз в промежуток времени dt1 проверял значение переменной a1, и если a1 была равна 0, то заменял её значение на newNumber.Учтите, что поток вообще не может возвращать значение функции(она продолжила выполняться) поэтому он должен быть обязательно void
{
while(/*думаю, здесь как раз самое место поговорить о логическом типе bool в трейнз. Аурановцы постарались сделать его как java и им подобным, т.е. с сохранением Си шного синтаксиса "если число не равно нулю, то это "да", если равно, то "нет""
т.е. если Вы хотите записать ddd1= "нет" (присваивание здесь СИшное, отличается от "паскалевского"), то можете написать ddd1=false; или ddd1=0;
если же наоборот, ddd1= "да", то можете написать сколько угодно вариантов, например ddd1=true; ddd1=10; ddd1=-0.0000001221122; ddd1=22e+1; и так далее. для экономии места обычно пишут ddd1=1;, хотя более "читаемая" запись ddd1=true;
Кстати, об операторе "сравнения". Он тут полностью Сишный, т.е. a==b - или "да", или "нет", но в отличие от СИ логические операторы || и && заменены паскалевскими or и and.
ну а здесь для краткости мы впишем 1, т.к. наш скрипт будет работать всю игру*/ 1)
{
if(a1==0 /*можно было бы записать if(!a1) - это эквивалентно */)
{
a1=newNumber;
}
Sleep(dt1); // а это как раз та функция, которая озволяет просто писать многопоточность. Ведь пока этот поток работает, он требует ресурсы, так же как и функция, но когда он "спит", то эти ресурсы передаются остальным потокам, а также графическому движку игры и прочему. Поэтому если Вы протестируете этот скрипт на ОООЧЕНЬ доисторическом компе, то разница в производительности игры при включении потока при dt1=0.001 будет знаительной по сравнению с dt1=10 (т.е. проверять раз в секунду)
}
}
//ну и процедура, вызывающая этот поток
void init0(void) //слово void говорит, что функция не выводит (в функцию не вводят) никаких переменных
{
Checker(0.01,40);
}
Публичная функция может выглядеть так
class Myclass3
{
public float ploschad(int a; float b) // публичная функция
{
return a*b;
};
public thread void ploschad2(int a; float b) // публичный поток
{
Sleep(1);// ждём 1 сек, а потом выводим
return a*b;
};
}
а класс 4 их использует
class Myclass4
{
Myclass3 ObjClass3;
int da(void)
{
float square = ObjClass3.ploschad(2,3.4444);
}
}
А теперь мы хотм сделать "наследника" класса Myclass3, который должен вычислять плошадь прямоугольника, если b - целое, и если b -дробное, то вычислял площадь треугольника (ну задание такое поставили)
class MyChildclass isclass Myclass3
{
public float ploschad(int a; float b) // публичная функция, которую мы хотим перегрузать
{
if(b==(int)b)
{
inherted(a,b); //вот это и есть "перегрузка" метода. Т.е. вызывается функция объкта-родителя, которой передаются аргументы. Естественно, результат ploschad класса Myclass3 будет возвращаться функции ploschad класса MyСhildclass, которая выдаст его без каких-либо изменениий
}
else return a*b/2; //площадь треугольника
};
}
Кстати, функция public thread float ploschad2 останется без изменений в обьекте-наследнике. Если же мы напишем её новую декларацию, старая (объекта-родителя) использоваться уже не будет
Класс может быть наследником одновременно 2 классов
class MyChildclass2 isclass Myclass3,Myclass4
Если же мы используем метод этого класса в одной из функций(потоков) этого же класса, то правильно будет написать например
int z= me.ploschad(2,1);
но в то же время можно чуть упростить запись, опустив me :
int z=ploschad(2,1);
И последнее - классы можно "преобразовывать". К сожалению, трейнз не всегда может правильно "собрать" это дерево родителей-наследников и не может для наследника всегда вызвать функцию родителя, или наоборот, требуется вызвать именно функцию обьекта-родителя. Тогда нам потребуется оператор преобразования классов. Допустим, нам надо преобразовать объект me134 класса MyChildclass2 в класс Myclass3
MyChildclass2 me134;
Myclass3 newclass9;
newclass9= cast<Myclass3> me134; // теперь мы получили объект me134 преобразованный к классу-родителю Myclass3
предпоследнее. Как известно, массивы, такие как int[] massiv1; необходимо создавать, то есть выделять память для переменных, войдущих в их состав. Для этого используется специальное ключевое слово new . Вообще это некий аналог СИшного alloc'a, но вручную считать, сколько массиву потребутся памяти не надо - движок это сделает за вас ! Инициализировать этот массив можно статически
massiv1=new int[30];
или динамически
void SetMassiveSize(int number)
{
massiv1=new int[number];
}
Если же вам требуется создать единственный элемент, то можно сделать так
newclass9=new Myclass3();
но учтите. Полученный таким образом элемент (не являющиййся стандартным типом как int float sting bool) обязательно инициализировать, т.е. он должен содержать явно написанную функцию public void Init(void) и прежде чем этим объектом пользоваться, надо эту функцию вызвать. Пример примерно такой
class Class01
{
public int a;
public void Init(void)
{
a=0;
}
};
class UsingClass01
{
Class01 myobj1;
public void using(int q)
{
myobj1=new Class01();
myobj1.Init();
myobj1.a=701;
}
}
И ещё 2 полезные фунукции.
1) функция int size() . Определяет число элементов массива. Учитывая, что нумерация массивов в trainz начинается с 0, последний элемент массива (size() -1)
пример
int i;
for(i=0; i<=massiv1.size()-1;i++)
{
massiv1[i]=0;
}
2) функция bool isclass( class) . Определяет, принадлежит ли данный объект классу. Аргументом является класс (не элемент класса !) принадлежность к которому проверяют. Полезна, если объект был про cast ован в объект класса-родителя. Полезна, например, для различения локомотивов и нескриптованных вагонов
Пример составлен для скрипта вагона
#include "Locomotive.gs"
#include "Train.gs"
class myVehicle isclass Vehicle
{
int lastLocoNumber=-1;
void int getlastLoco(void)
{
Train train1=me.GetMyTrain();
Vehicle[] veh_array=train1.GetVehicles(); // как видите, функция возвращает уже готовый инициализированный массив
int i=veh_array.size()-1;
while(i>=0 and lastLocoNumber==-1)
{
if(veh_array.isclass(Locomotive))
{
lastLocoNumber=i;
}
i--;
}
return lastLocoNumber;
}
}