StarCraft 2: Основы Galaxy (II вариант)

» Раздел: 1. Основы

Приветствую тебя, читатель! Сегодня я расскажу тебе об основах Galaxy - скриптового языка игры Starcraft II.
Предполагается, что ты уже знаком с основами программирования в редакторе триггеров, а если нет - вперед изучать остальные статьи!
Итак, пойдем по порядку:

Содержание:

  • Функции
  • Переменные
  • Массивы
  • Циклы
  • Условия
  • Практикум

Функции:

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

Синтаксис функции определяется следующим образом:

<ТИП ВОЗВРАЩАЕМОЕГО ЗНАЧЕНИЯ> <ИМЯ ФУНКЦИИ>(<АРГУМЕНТЫ ЧЕРЕЗ ЗАПЯТУЮ>){
...
<ТЕЛО ФУНКЦИИ>
...
}

Например:

void CreatePylon(){
UnitCreate(1, "Pylon", 0, 0, Point(0.0, 0.0), 0);
}

Вызывается она так:

CreatePylon();
Пожалуй, объясню что означает весь этот бред выше. Во-первых, все функции делятся на кастомные и нативные. Нативные функции - это те функции, которые непосредственно влияют на игровой процесс, совершая действия. Кастомные функции состоят из нативных чуть менее чем полностью и созданы они лишь для удобства, чтобы не повторять множество раз одни и те же действия. Тогда очевидно, что функция UnitCreate является нативной, а CreatePylon - кастомной.
Давайте же рассмотрим, что делает эта наша суперфункция! Когда мы вызываем ее, в левом нижнем углу карты создается пилон, принад лежащий нейтральному игроку. Итак, первый аргумент отвечает за количество создаваемых юнитов. Второй - за тип самого юнита, пишется в кавычках. Третий - за то, создастся ли юнит сразу же или будет произведена его постройка. Четвертый - за номер игрока, которому будет принадлежать юнит. Пятый - за точку создания юнита, как видите тут я использовал нативную функцию Point создающую точку в левом нижнем углу карты. Шестой - за угол поворота юнита.
Итак, мы разобрались в устройстве нашей функции. Теперь давайте дополним ее, а заодно и разберемся с принимаемыми и возвращаемыми значениями.
А что если сделать так, чтобы можно было задавать игрока-владельца созданного пилона? Давайте попробуем!
void CreatePylon(int PlayerOwner){
UnitCreate(1, "Pylon", 0, PlayerOwner, Point(0.0, 0.0), 0);
}
Теперь мы можем одной функцией создать несколько пилонов для разных игроков.

Вызываться она будет так:

CreatePylon(1);

Или

CreatePylon(2);

А давайте еще добавим выбор той точки, в которой будет создаваться наш пилон:
void CreatePylon(int PlayerOwner, point CreationPoint){
UnitCreate(1, "Pylon", 0, PlayerOwner, CreationPoint, 0);
}

А она вызываться будет уже так:

CreatePylon(1, Point(20, 20));

Или

CreatePylon(2, Point(15, 40));

Вот и пришла пора рассказать о возвращаемых значениях функции. Возвращаемое значение - это некий результат функции, который возможно передать в другую функцию или записать в переменную. Рассмотрим, например, функцию Point. Она создает некую точку а потом возвращает ее чтобы мы могли в ней, например, создать юнита. Давайте превратим нашу функцию в возвращающую значение.
unit CreatePylon(int PlayerOwner, point CreationPoint){
UnitCreate(1, "Pylon", 0, PlayerOwner, CreationPoint, 0);
return UnitLastCreated();
}

Использовать это можно так:

UnitKill(CreatePylon(1, Point(20, 20)));

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

Переменные

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

Разберем синтаксис объявления переменных:

<ТИП ПЕРЕМЕННОЙ> <ИМЯ ПЕРЕМЕННОЙ> = <ЗНАЧЕНИЕ ПЕРЕМЕННОЙ>;
Заметьте, что изначального значения может и не быть, оно может задаваться в ходе выполнения функций.

Вот и пример:

unit u = CreatePylon(1, Point(0, 0))

Или

int i = 5 * 20 - 100

Существует два основных типа переменных: глобальные и локальные. Глобальные переменные объявляются вне функций и постоянно хранят ту информацию, которую в них заложит пользователь. Локальные переменные объявляются внутри функции, вверху кода, под первой фигурной скобкой. Локальные переменные уникальны для каждого вызова функции.
Это пока все, что тебе нужно знать о переменных, читатель. Приступим к массивам.

Массивы

Итак, уважаемый читатель, представь, что у тебя на куртке несколько одинаковых карманов, в которых лежат разные вещи одинакового размера.
Это и будет называться массивом. То есть [b]массив[/b] - [u]множество пронумерованных переменных с одинаковым именем[/u].

Объявляются массивы так:

<ТИП ПЕРЕМЕННОЙ>[<КОЛИЧЕСТВО ЯЧЕЕК В МАССИВЕ> - 1] <ИМЯ ПЕРЕМЕННОЙ>
Заметьте, что задать начальное значение нельзя.

Вот простенький пример использования массивов:

int[10] i;
int j = 0;
j = RandomInt(0, 10);
i[j] = j + 5;
Опять же, эти действия не делают ничего конкретного, но это показывает что в индексее ячейки массива можно использовать любые числа, переменные, функции или их сочетания. Массивы - идеальное решение для хранения большого количества информации. Реальное практическое применение массивов вы увидите в части 7. А сейчас перейдем к циклам.

Циклы

Предположим, что нам нужно создать 10 пилонов функцией CreatePylon так, чтобы каждому пилону соответствовал свой игрок. Как бы вы сделали это?
CreatePylon(1, Point(0, 0));
CreatePylon(2, Point(0, 0));
CreatePylon(3, Point(0, 0));
CreatePylon(4, Point(0, 0));
CreatePylon(5, Point(0, 0));
... и т.д.
Так? А если вам нужно создать сотню, тысячу пилонов? Выходом из этого положения являются циклы. Цикл - блок кода, повторяющий заложенные в него действия до прекращения выполнения определенного условия.

Синтаксис цикла выглядит так:

while (<УСЛОВИЕ>){
...
<ДЕЙСТВИЯ ЦКИЛА>
...
}

Вот как нужно решать вышеописанную задачку:

int i = 0;
while (i < 10){
CreatePylon(i, Point(0, 0));
i = i + 1;
}

Удобно, не правда ли? Приступим к условиям.

Условия

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

Синтаксис условий выглядит примерно так:

if (<УСЛОВИЕ>) {
...
<ТЕЛО УСЛОВИЯ>
...
}
Хочу заметить, что внутри условий могут быть использованы такие операторы как:
== - равенство.
!= - неравенство.
> < - больше, меньше.
>= <= - больше или равно, меньше или равно.
Для соединения нескольких условий в одной строке могут быть использованы скобки и нижеследующие операторы:
|| - или.
&& - и.
Мы можем сравнить значения с true и false - истинностью и ложью.

Вот пожалуй и все, перейдем к последней части нашей статьи: практикуму.

Практикум

Вот мы и подошли с тобой, читатель, к заключительной части статьи: практикуму. Здесь мы с тобой напишем с нуля и разложим по полочкам код, создающий заданное тобой количество пилонов на заданном расстоянии от центра карты и постепенно перемещающий их по кругу.
Вроде бы просто, не так ли? Итак, приступим:

Для начала создадим в редакторе триггеров функцию и назовем ее, например, Custom code. Это будет наш аналог Кастом кода из Warcraft III. Создадим действие "Общее - Пользовательский скрипт". Щелкните на него и в открывшемся поле для ввода пишите:
return true;
}
			
void End(){
Это некоторое ухищрение(thx to ScorpioT1000) для того, чтобы создать пустое поле для кода. Все действия далее производятся между
return true;
}
			
void End(){
Давайте напишем скелет функции, которая будет запускать все эти наши будущие чудеса.
void InitCircle(int count, fixed dist){
    
}
count - количество создаваемых пилонов, dist - действительная переменная диаметра круга пилонов.

Внутри функции нам понадобится цикл, добавим его.
void InitCircle(int count, fixed dist){
	int i = 0;
	while (i < count) {
			
	i = i + 1;
        }
}
Мы хотим чтобы пилоны создавались ровно по кругу, не так ли? Зададим рассчет угла, добавив еще одну переменную.
void InitCircle(int count, fixed dist){
	int i = 0;
	fixed ang;
	while (i < count) {
		ang = (360.0 / IntToFixed(count)) * IntToFixed(i);
	        i = i + 1;
	}
}
Теперь добавим еще одну переменную для задания точки создания.
void InitCircle(int count, fixed dist){
	int i = 0;
	fixed ang;
	point p;
	while (i < count) {
		ang = (360.0 / IntToFixed(count)) * IntToFixed(i);
		p = PointWithOffsetPolar(RegionGetCenter(RegionPlayableMap()), dist, ang);

	i = i + 1;
	}
}
Точку мы задали полярной координатой от центра карты. Пора бы добавить создание самого юнита.
void InitCircle(int count, fixed dist){
	int i = 0;
	fixed ang;
	point p;
	while (i < count) {
		ang = (360.0 / IntToFixed(count)) * IntToFixed(i);
		p = PointWithOffsetPolar(RegionGetCenter(RegionPlayableMap()), dist, ang);
		UnitCreate(1, "Pylon", 0, 0, p, 0);
	i = i + 1;
	}
}
Сейчас, это уже вполне рабочая функция, создающая пилоны по кругу, но мы ведь хотим чтобы они еще и двигались, не так ли? Добавим несколько глобальных переменных для передачи данных из одной функции в другую, а также занесение последнего созданного пилона в массив.
unit[100] Pylon;
int PylonCount;
fixed PylonDist;
trigger PylonMove

void InitCircle(int count, fixed dist){
	int i = 0;
	fixed ang;
	point p;
	while (i < count) {
		ang = (360.0 / IntToFixed(count)) * IntToFixed(i);
		p = PointWithOffsetPolar(RegionGetCenter(RegionPlayableMap()), dist, ang);
		UnitCreate(1, "Pylon", 0, 0, p, 0);
		Pylon[i] = UnitLastCreated();
	i = i + 1;
	}
}
Поясню: триггер PylonMove нам будет нужен для периодического перемещения. Создадим каркас двигательной функции и присвоение остальных глобальных переменных.
unit[100] Pylon;
int PylonCount;
fixed PylonDist;
trigger PylonMove

bool PylonMoveFunc(bool testConds, bool runActions){
if (!runActions) {
	return true;
}
		
return true;
}

void InitCircle(int count, fixed dist){
	int i = 0;
	fixed ang;
	point p;
	while (i < count) {
		ang = (360.0 / IntToFixed(count)) * IntToFixed(i);
		p = PointWithOffsetPolar(RegionGetCenter(RegionPlayableMap()), dist, ang);
		UnitCreate(1, "Pylon", 0, 0, p, 0);
		Pylon[i] = UnitLastCreated();
	i = i + 1;
	}
	PylonCount = count;
	PylonDist = dist;
}
Поясню: любая функция, привязанная к триггеру выглядит именно таким образом как и PylonMoveFunc. Действия пишутся непосредственно после первого условия. Итак, повесим функцию на триггер и зарегистрируем на триггер периодическое событие.
unit[100] Pylon;
int PylonCount;
fixed PylonDist;
trigger PylonMove = TriggerCreate("PylonMoveFunc");

bool PylonMoveFunc(bool testConds, bool runActions){
	if (!runActions) {
		return true;
	}
		
	return true;
}

void InitCircle(int count, fixed dist){
	int i = 0;
	fixed ang;
	point p;
	while (i < count) {
		ang = (360.0 / IntToFixed(count)) * IntToFixed(i);
		p = PointWithOffsetPolar(RegionGetCenter(RegionPlayableMap()), dist, ang);
		UnitCreate(1, "Pylon", 0, 0, p, 0);
		Pylon[i] = UnitLastCreated();
	i = i + 1;
	}
	PylonCount = count;
	PylonDist = dist;
	TriggerAddEventTimePeriodic (PylonMove, 0.04, c_timeGame);
}
Поясню: c_timeGame - константа игрового времени. Далее нам осталось лишь дописать тело двигающей функции.
unit[100] Pylon;
int PylonCount;
fixed PylonDist;
trigger PylonMove = TriggerCreate("PylonMoveFunc");

bool PylonMoveFunc(bool testConds, bool runActions){
	int i = 0;
	fixed ang;
	point p;
	if (!runActions) {
		return true;
	}
	while (i < PylonCount) {
		ang = AngleBetweenPoints(RegionGetCenter(RegionPlayableMap()), UnitGetPosition(Pylon[i])) + 2.0;
		p = PointWithOffsetPolar(RegionGetCenter(RegionPlayableMap()), PylonDist, ang);
		UnitSetPosition(Pylon[i], p, true);
	i = i + 1;
	}
	return true;
}

void InitCircle(int count, fixed dist){
	int i = 0;
	fixed ang;
	point p;
	while (i < count) {
		ang = (360.0 / IntToFixed(count)) * IntToFixed(i);
		p = PointWithOffsetPolar(RegionGetCenter(RegionPlayableMap()), dist, ang);
		UnitCreate(1, "Pylon", 0, 0, p, 0);
		Pylon[i] = UnitLastCreated();
	i = i + 1;
	}
	PylonCount = count;
	PylonDist = dist;
	TriggerAddEventTimePeriodic (PylonMove, 0.04, c_timeGame);
}
Готово! Итого мы получаем:
return true;
}
			
unit[100] Pylon;
int PylonCount;
fixed PylonDist;
trigger PylonMove = TriggerCreate("PylonMoveFunc");

bool PylonMoveFunc(bool testConds, bool runActions){
	int i = 0;
	fixed ang;
	point p;
	if (!runActions) {
		return true;
	}
	while (i < PylonCount) {
		ang = AngleBetweenPoints(RegionGetCenter(RegionPlayableMap()), UnitGetPosition(Pylon[i])) + 2.0;
		p = PointWithOffsetPolar(RegionGetCenter(RegionPlayableMap()), PylonDist, ang);
		UnitSetPosition(Pylon[i], p, true);
	i = i + 1;
	}
	return true;
}

void InitCircle(int count, fixed dist){
	int i = 0;
	fixed ang;
	point p;
	while (i < count) {
		ang = (360.0 / IntToFixed(count)) * IntToFixed(i);
		p = PointWithOffsetPolar(RegionGetCenter(RegionPlayableMap()), dist, ang);
		UnitCreate(1, "Pylon", 0, 0, p, 0);
		Pylon[i] = UnitLastCreated();
		i = i + 1;
	}
	PylonCount = count;
	PylonDist = dist;
	TriggerAddEventTimePeriodic (PylonMove, 0.04, c_timeGame);
}
		
void End(){
Поздравляю тебя, читатель, и благодарю за прочтение моей статьи! Карту с вышеописанным кодом ты можешь найти чуть ниже.

Просмотров: 5 449

13 комментариев удалено
ScorpioT1000 #14 - 9 лет назад 2
определения неверны, описания высосаны из пальца, автор понятия не имеет о программировании
эх, придется свою статью писать =(
Doc #15 - 9 лет назад 2
ScorpioT1000, да. писал своими словами, так же как и делал сергей, по статьям которого большинство из нас учило жасс. Так гораздо понятнее юзерам, так что все ок.
ScorpioT1000 #16 - 9 лет назад 0
статья сергея написано, мягко говоря, убого, но там был хз какой год и никто толком не шарил в программировании (в т.ч. сергей)
SageOne #17 - 9 лет назад 0
у сергея статья как раз напоминала учебник по программированию, а то, что определения даны самостоятельно - как раз верное решение, вроде все понятно, и нету вашей роботической нудятины...
4 комментария удалено
Лекс #22 - 9 лет назад 0
Условие - блок кода, действия в котором выполняются только при соблюдении неких условий (уж простите за тавтологию), в противном случае действия игнорируются.
игнорируются? else же? =/
Oxygen D #23 - 8 лет назад 0
Тот же жасс только с в профиль. А я уж боялся переучиваться прийдется, и еще раз спасибо близардам.
А есть что то типа jnpg для sc?
Doc #24 - 8 лет назад 0
Нету.
Амбидекстрия #25 - 8 лет назад 0
Бтв, ненужно писать статью как сергей. Тогда о джаззе и галакси ничего не знали, и поэтому он писал таким "тоном" "обращение к младенцу" а тут просто изменение статьи сергея из джазза в галакси