Классы и структуры¶
TODO
Необходимо обновить ссылки по всей статье.
ZScript является объектно-ориентированным языком, и, соответственно, в нём есть и понятия оттуда:
- Класс: шаблон/штамп/описание/нечто неизменяемое. По классу создаются копии объектов. В классе лежит вся информация о том, что конкретно будет содержаться в объектах (различные переменные) и как оно будет взаимодействовать (функции, которые в ООП называются методами).
- Объект (экземпляр класса): динамическая, "живая" копия класса. Является основным "действующим лицом" в ООП — клонировать их с одного класса можно сколь угодно много (...пока памяти хватает). То есть класс — форма для заливки, описание; объект — взаимодействие, динамика.
- Структура: пользовательский составной тип данных и методов работы с ними.
Напомним, что обращение к какому-либо полю или методу класса производится посредством точки (см. 1.03, "Обращение по указателю").
Опциональность части синтаксиса
Во всех блоках кода ниже [Квадратные скобки] используются для того, чтобы обозначить необязательность написания того, что заключено внутри них.
Объвление структур и классов¶
Структуры и классы в ZScript, несмотря на свою схожесть, различаются сильно. С натяжкой можно сказать, что структуры — это облегчённые классы с порезанными правами, всегда полностью доступные в своей области видимости, не имеющие права на создание объектов-экземпляров с себя. Оба типа — классы, структуры — могут включать переменные, методы, константы и перечислимые типы.
m8f: Ненативные (не встроенные/самописные) структуры нельзя использовать в качестве параметров, также, как и возвращать их из функций. Но зато они легче классов.
Порядок блоков может варьироваться в зависимости от нужды. Фактически же он не имеет значения, обычно задаётся следующим образом:
struct имя_структуры {
[константа1 = значение1;
[...] ]
[перечислимый_тип1;
[...] ]
[тип_переменной1 имя_переменной_структуры1;
[...] ]
[метод_структуры1;
[...] ]
}
[...]
См. 1.04. "Константы и перечисляемые типы", 1.03. "Работа с переменными" и 1.10. "Методы".
Так как класс является описанием именно действующих лиц (объектов) — он должен быть более гибким, чем структура. В нём возможно наследование (см. 1.12. Парадигмы ООП), добавление вложенных структур и, в случае создания объектов мира — акторов — ещё и код взаимодействия с игровой вселенной.
class имя_класса [: от_кого_наследуется] {
[константа1 = значение1;
[...] ]
[перечислимый_тип1;
[...] ]
[default-блок;] // Только в случае наследования от класса Actor или его потомков.
[тип_переменной1 имя_переменной1;
[...] ]
[метод_класса1
[...] ]
[states-блок] // Только в случае наследования от класса Actor или его потомков.
}
[...]
Внутри классов также можно объявлять структуры, однако на практике это практически не используется. Объявить класс внутри класса или структуру внутри структуры уже нельзя.
Пример синтаксиса (скорее всего, будет куда-либо перенесён)¶
version "2.4.0"
#include "constants.zsc" // Положим, здесь лежат второстепенные константы.
#include "zscript/weapons.zsc"
#include "zscript/monsters.zsc"
#include "zscript/npcs.zsc"
enum ENPCTeams {
NPCT_Neutral = 0,
NPCT_Normal = NPCT_Neutral, // ...Почему бы и нет?
NPCT_Red, // == "NPCT_Neutral + 1", то есть "1".
NPCT_Yellow, // "2".
NPCT_Green,
NPCT_Blue,
NPCT_Gray, // "5".
NPCT_LastTeam = NPCT_Gray
// Чтобы каждый раз не вспоминать, какая там команда у нас последняя — делаем себе упрощение (здесь будет "NPCT_Gray", то есть "5").
}
class BaseNPC: Actor {
ENPCTeams team;
double bravefactor;
Default {
Monster;
+FRIENDLY;
Health 100;
}
override void BeginPlay() {
team = Random( NPCT_Neutral, NPCT_LastTeam ); // Пользуемся мнемоническим упрощением...
bravefactor = FRandom( 0.1, 1.0 );
Super.BeginPlay();
} // of override void BeginPlay() {}
}
Указатели на экземпляры классов (на объекты)¶
Указатели служат, как можно понять по названию, для указания процессору, с каким конкретно объектом ему нужно работать. Можно сказать, что количество типов указателей неограничено, так как любой новый написанный класс уже порождает тип указателя.
Указатель на себя¶
У каждого экземпляра класса есть автоматически создаваемый указатель self — буквально указатель на самого себя, на текущий объект, то есть на объект, код которого исполняется в данный момент.
Зачастую используется как аргумент в методе:
// Уменьшить здоровье своей цели, передав в качестве атакующего себя самого.
// Забавно и парадоксально, но подобный вызов используется нередко. Не отовсюду можно "попасть" в цель по-другому.
target.DamageMobj( self, self, 25, 'None' );
// Функция взята из "gzdoom.pk3::actor.txt":
// native virtual int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags = 0, double angle = 0 );
Также можно использовать, если происходит коллизия названия какого-то поля объекта и локальной переменной (у последних приоритет).
class SomeClass {
int a;
double b;
void Init( int a, String b = "" ) {
self.a = a;
self.b = 0.0;
console.printf( b ); // Вот и думай, тут у нас как "double b" воспримется или как "String b".
//(Ответ — второе, как строка. Но сходу это совсем не очевидно).
}
}
Особые правила для указателя self
Существуют специализированная обработка указателя self в классах, наследованных от StateProvider, а также запрет использования этого указателя в action-методах; см. статью 2.04 — "Actors".
Основополагающие типы классов¶
С каждым можно ознакомиться подробнее в соответствущей статье.
Actor
// Указатель на объект типа "Actor". Как раз акторами и являются все
//объекты в игровом мире -- игроки, монстры, оружие, декорации,
//файерболы, дымок от пуль, источники динамического света и так далее.
// Класс "Actor" наследуется от класса "Thinker".
Thinker
// Thinker -- любой объект, имеющий право хоть как-то взаимодействовать
//с миром. Это, собственно, все акторы, эффекты секторов, "сущности"
//ботов, декали, маркеры автокарты и иные объекты, прямо или косвенно
//связанные с игровым миром.
Object
// Самый базовый класс, несмотря на название "объект". Все (вообще
//все) остальные неявно наследуются от него.
ThinkerIterator
ActorIterator
// Вспомогательные объекты, позволяют перебрать всех Thinker'ов в мире,
//удовлетворяющих фильтру наследования.
BlockThingsIterator
BlockLinesIterator
// Вспомогательные объекты, используются для перебора всех линий или акторов,
//попадающих в BLOCKMAP-сетку вокруг определённого актора или определённой
//позиции в мире.
Дерево наследований объектов мира. Через "<...>" обозначены остальные, не основные классы. На наследование самописных это никак не влияет.
Object
Thinker
> Actor // Decorate умеет работать только с акторами...
| Inventory // Базовый класс инвентаря.
| StateProvider
| CustomInventory
| Weapon // Базовый класс оружия.
| <...>
| Powerup // Базовый класс Powerup'ов.
| <...>
| PowerupGiver
| <...>
| <...>
> SectorEffect
| Lighting
| Mover
| MovingFloor
| Floor
| MovingCeiling
| Ceiling
| <...>
ThinkerIterator
ActorIterator
BlockThingsIterator
BlockLinesIterator
<...>
Создание объектов¶
Следует пользоваться двумя общими правилами:
1. Для того, чтобы создать объект класса, не наследованного от Actor (к примеру, Thinker), необходимо вызвать функцию "new()". Будет возвращён указатель на только что созданный объект этого типа.
Thinker th = new( 'Thinker' );
console.printf( "Thinker <" .. th .. "> created." ); // Будет выведен адрес, по которому был создан объект класса Thinker.
th.ChangeStatNum( STAT_STATIC );
2. Для создания в мире объекта типа Actor необходимо вызывать статический метод "Actor.Spawn( class<Actor> cls, vector3 spawnpos )" (какой актор создавать и на каких координатах).
Из этих двух правил есть несколько иключений. Например, для создания ThinkerIterator, ActorIterator, BlockThingsIterator, BlockLinesIterator, SectorTagIterator и других подобных предпочтительнее использовать их встроенные статические методы вроде "Create()", которые внутри себя проводят полную инициализацию и возвращают уже инициализированный объект. Такую же конструцкию можно делать и в самописных классах, если понадобится.