Russian:Create Missions
From Data Realms Wiki
Основы Начни отсюда.
| Юниты Всё, что можно контролировать.
| Эффекты Взрывы, вспышки и шлейфы.
| |||||
Создание мода Научись, как создавать новые объекты.
| Оружие Оружие и инструменты
| Карты Как создавать карты (сцены)
| |||||
Структура модулей Как создать папку мода.
| Игровые редакторы Редакторы карт и гибов. Средство просмотра акторов .
| Создание миссий Создание миссий
| |||||
Lua Скрипты.
| Создание модов Все страницы по созданию модов.
| Учебники Другая информация о создании модов.
|
Автор - Dantalion13
Выложил, редактировал и оформлял - Ximximik/Программист
Данный учебник научит вас базовым знаниям в создании миссий
Contents |
Часть №1:Создание структуры мода-миссии, вся необходимая подготовка.
Как и говорил ранее, размещаю статью по созданию миссий. Поделил ее на несколько частей. В первой, этой, речь пойдет о создании своей папки с модом, всех необходимых файлов, а также будут описаны приготовления к собственно началу создания миссии.
Подготовка кода модуля
Первым делом, создаем в папке с игрой (скорее всего, она у Вас называется Cortex Command) свою папку.
Я назову свою: NewMission.rte
.rte - часть, обязательная для добавления к названию папки.
Далее, в этой папке создаю текстовый документ (формата .txt), переименовываю его имя и расширение следующим образом: Index.ini
Также в папке с модом создаю файлы: "Activities.ini", "Scenes.ini" и папку под названием "Scenes".
В папке "Scenes" создаю файл "NewMission.lua" (здесь будем писать код миссии)
По поводу структурирования мода могу дать следующую рекомендацию: смотри, как все устроено в оригинальных папках игры и делай так же. Просто если начать оригинальничать, то Кортекс может начать капризничать.
Открываю файл "Index.ini" и пишу там следующее:
DataModule ModuleName = Test //можно называть произвольно IncludeFile = NewMission.rte/Scenes.ini //пути к файлам .ini IncludeFile = NewMission.rte/Activities.ini
Открываю файл "Activities.ini" и прописываю там:
AddActivity = GAScripted PresetName = NewMission //эта строчка и следующая должны совпадать, как мне кажется: одна с названием //файла карты, а другая - со значением PresetName внутри .ini - файла карты. Подробно это не разбирал за //ненадобностью. SceneName = NewMission ScriptFile = NewMission.rte/Scenes/NewMission.lua //путь к файлу с расширением .lua InCampaignStage = 1 LuaClassName = NewMission //на Ваше усмотрение. штука очень нужная, так что запомните или как она //называется или где можно найти название
текст после двух "косых палок" означает комментарии. Их компьютер при чтении файла игнорирует.
Подготовка карты
Следующее, что нужно сделать - это создать карту для миссии.
Описывать процесс не буду. Эта статья может помочь, если вы не умеете создавать карты:Тутор
Не забудьте, что в .ini - файл карты нужно добавить строчки:
LocationOnPlanet = Vector X = 120 Y = 80 //координаты взял наугад
1. Своей карте я дам имя "NewMission". (Смотрим выше и находим, с чем должно совпадать имя карты)
2. Для демонстрации потребуется несколько зон с разными именами.
3. Создайте 5 зон с именами от "A" до "E":
- A - будет зона появления командного юнита игрока
- B - зона появления вражеского командного юнита
- C - зона появления союзных солдат
- D - зона появления вражеских солдат
- E (где-нибудь в воздухе, чтобы вокруг было побольше свободного места) - зона для появления десантных кораблей
4. Сохраните карту.
5. Выйдите из игры.
6. Зайдите в игру, убедитесь, что Вас не выкидывает при загрузке и не выскакивает окно с сообщением об ошибке, что в вашу карту можно играть в режиме скримиша. На всякий случай.
Если есть проблемы
- Изучайте написанное Вами и ищите ошибки. Если ничего не нашли, то:
- Изучайте как устроены другие моды-миссии и сравнивайте с тем, что написали вы. Ищите ошибки. Если и это не помогло:
- Обращайтесь на форум с просьбой помочь. Именно в таком порядке.
Как правило, ошибки в ini или lua бывают от полоротости. Вы же не хотите выставить себя в дурном свете?
Также в отлове ошибок очень помогает консоль (вызывается клавишей ~)
Зачастую там написано, какая именно проблема возникла и где (дается номер строки в файле, в которой произошла ошибка).
Можно, конечно, и самому строчки считать, но если ошибка где-то в строчке эдак двухсотой...
Лучше загрузить на свой комп какой-нибудь текстовый редактор с возможностью нумеровать строки.
К примеру, этот:Notepad ++
Это была подготовительная часть. Дальше уже пойдет собственно создание миссии.
В следующей части я создам несколько переменных и сохраню в них данные, которые нам позже пригодятся, а также размещу в нужных зонах командные юниты с оружием.
А пока настоятельно рекомендую прочитать следующую статью: Lua для начинающих
Хотя и вообще без знания lua можно методом шевеления мозгов делать миссии... Все же будет лучше сначала вникнуть хотя бы в азы этого языка.
Знание операторов присваивания и выбора, операций сравнения, циклов, функций, таблиц - вот что нужно для комфортного программирования миссий.
Часть №2:Размещаем командные юниты и вооружаем их.
Планирование
Стандартная миссия содержит следующие части:
function <имя файла миссии>:StartActivity() <тело функции> end
function <имя файла миссии>:UpdateActivity() <тело функции> end
Кстати, в моем случае <имя файла миссии> - это NewMission
В первой функции описываются различные данные, которые будут в дальнейшем использоваться в программе, различные глобальные переменные и постоянные.
Во второй - размещаются функции или команды, которые нужно выполнить несколько раз или один раз, но не с самого начала.
Прежде чем что-то делать, нужно определиться с тем, что мы желаем получить.
В данном примере я хочу следующее:
- Чтобы в нужных зонах появлялись командные юниты (Brain Robot) с оружием (Auto Pistol) и стояли неподвижно на своих местах.
- В нужных зонах появлялись солдаты (Soldier Light) со стрелковым оружием (Assault Rifle) и гранатами (Grenade), после появления шли уничтожать вражеский мозг.
- После уничтожения мозга компьютерного игрока появлялся в нужном месте корабль (Drop Ship MK1)
- Через 30 секунд после появления корабля, если корабль еще жив и не улетел на орбиту, корабль взрывался
- Условие поражения игрока: командный юнит погиб
- Условие победы: командный юнит внутри корабля улетел на орбиту
Расстановка акторов
Начнем со StartActivity. Запишем там имеющиеся у нас данные. Какие? Имена зон, юнитов, корабля.
function NewMission:StartActivity() -- Сохраняю в переменных имеющиеся зоны: self.ZoneBrainRed = SceneMan.Scene:GetArea("A"); -- зона появления мозга игрока self.ZoneBrainGreen = SceneMan.Scene:GetArea("B"); -- компьютерного противника self.SpawnSoldierR = SceneMan.Scene:GetArea("C"); -- зона спавна солдатов игрока self.SpawnSoldierG = SceneMan.Scene:GetArea("D"); -- -//- противника self.SpawnShip = SceneMan.Scene:GetArea("E"); -- зона появления корабля -- Сохраняю в переменных юниты и оружие: self.BrainRed = CreateAHuman("Brain Robot" , "Base.rte"); -- мозг красных self.BrainGreen = CreateAHuman("Brain Robot" , "Base.rte"); -- зеленых self.Soldier = CreateAHuman("Soldier Light" , "Coalition.rte"); -- солдат self.WeaponBrain = CreateHDFirearm("Auto Pistol" , "Coalition.rte"); -- оружие мозга self.WeaponSoldier = CreateHDFirearm("Assault Rifle" , "Coalition.rte"); -- оружие солдата self.Grenade = CreateTDExplosive("Grenade" , "Coalition.rte"); -- граната self.Ship = CreateACDropShip("Drop Ship MK1","Base.rte");-- корабль end
Давайте теперь разберем более подробно, что же тут написано.
Это: "SceneMan.Scene:GetArea("A");" функция. Ей передается значение "A" (в качестве имени зоны). Она ищет в файле .ini карты зону с таким именем и, если находит, отдает в качестве результата саму эту зону.
В нашем случае зона сохраняется в переменной "self.ZoneBrainRed". Теперь, "self.ZoneBrainRed" - это по своей сути зона. В эту зону можно приказать идти, можно выполнить проверку и так далее.
Зачем вообще нужно сохранять зоны в переменных?
Затем, что вместо длинного и малопонятного "SceneMan.Scene:GetArea("A");" мы имеем переменную "self.ZoneBrainRed", которая, во-первых, короче, а во-вторых, по ее внешнему виду понятно, за что она отвечает, какую информацию хранит. CreateAHuman, CreateHDFirearm, CreateTDExplosive, CreateACDropShip - функции по созданию пехотинцев, оружия, гранат и десантных кораблей соответственно.
В эти функции нужно передать два значения. Первое - имя объекта (лежит в .ini - файле, описывающим объект, в строчке с "PresetName = ").
Второе - имя папки с расширением .rte, в которой лежит .ini - файл с объектом.
Кстати, в миссиях можно использовать не только стандартные объекты, но и из модов.
Допишем эту часть, дав команду на появление командных юнитов с нужным оружием, в нужных местах, с нужным режимом ИИ (чтобы стояли на месте) и чтобы они принадлежали нужной команде.
После строчки "self.Ship = CreateACDropSh..." делаем небольшой пропуск, чтобы не мешалось все в одну кучу, и пишем:
self.BrainRed:AddInventoryItem(self.WeaponBrain); -- добавляем мозгу оружие для мозга self.BrainRed.AIMode = Actor.AIMODE_SENTRY; -- устанавливаем мозгу режим "стоять на месте" self.BrainRed.Pos = Vector(self.ZoneBrainRed:GetRandomPoint().X, self.ZoneBrainRed:GetRandomPoint().Y);-- устанавливаем позицию, где --появится мозг self.BrainRed.Team = Activity.TEAM_1; -- мозг будет за игрока MovableMan:AddActor(self.BrainRed); -- добавляем мозг на карту
Так, хорошо, в результате этого мозг красных появится на карте.
Аналогичное проделываем для командного юнита зеленых:
self.BrainGreen = CreateAHuman("Brain Robot" , "Base.rte"); --далее, в функции я прописал сразу, какое именно оружие выдать мозгу self.BrainGreen:AddInventoryItem(CreateHDFirearm("Auto Pistol" , "Coalition.rte")); -- важное замечание: нельзя дважды выдать на руки двум солдатам одно и тоже оружие.
Если бы я написал сначала "self.BrainRed:AddInventoryItem(self.WeaponBrain)", а затем: "self.BrainGreen:AddInventoryItem(self.WeaponBrain)", то произошла бы ошибка.
Также нельзя дважды спавнить одного и того же солдата, нельзя дважды стрелять одной и той же пулей, и так далее. Помните это.
Если захотите N раз заспавнить солдата, вам придется делать что-то вроде этого:
for i=1,N do local Soldier = CreateAHuman(ля-ля-ля); <прописывание свойств солдата> MovableMan:AddActor(Soldier) end
Вернемся к нашим баранам:
self.BrainGreen.AIMode = Actor.AIMODE_SENTRY; self.BrainGreen.Pos = Vector(self.ZoneBrainGreen:GetRandomPoint().X, self.ZoneBrainGreen:GetRandomPoint().Y);-- в качестве зоны --используется уже другая переменная self.BrainGreen.Team = Activity.TEAM_2; -- команда уже другая MovableMan:AddActor(self.BrainGreen);
Если вы все делали правильно, то после запуска миссии...
Вы увидите здоровенную надпиcь "DEAD" посредине экрана.
Не волнуйтесь, все идет по-плану. Подвигайте мышью и убедитесь, что командные юниты стоят на своих местах и с оружием.
Советы к этой части
Советую скопировать куда-нибудь названия функций или переписать их. И подробно описать, что они делают. Пока эти функции сами собой не запомнятся, их написание частенько придется посматривать.
Думаю, это хорошая идея - попробовать самостоятельно добавить на карту еще парочку солдат с оружием, прописав соответствующий код.
Кстати, могу подсказать простой способ разместить солдата в какой-либо точке, не прописывая в переменной зону для этого солдата.
- В редакторе зон ставим зону.
- Открываем .ini - файл карты и смотрим координаты Х,У зоны, зону из файла убираем. (подсказка: зону в файле можно найти по ее имени)
- Для нужного актора пишем:
self.<наш актор>.Pos = Vector(X, Y);
Где Х и У - какие-то числовые значения.
В следующей части статьи разберем проверку того, жив мозг или нет, напишем условие поражения (смерть мозга).
Часть №3:Условия победы и поражения.
Вступление
В этой части тутора будет разобрана функция, проверяющая, живы ли командные юниты игрока и компьютерного противника, а также объявляющая победу/поражение.
В этот раз к статье прикреплена папка с модом-миссией (Скачать), благодаря чему вы можете получить работающий образец миссии и посмотреть, как эта миссия устроена.
Код я приводить в статье полностью не буду (все равно в прикреплении есть), а лишь поговорю про некоторые моменты и тонкости, встретившиеся мне в ходе создания данной версии тестовой миссии.
Первое, что замечу: в отличие от предыдущей версии .lua-файла миссии, в функции StartActivity добавились следующие строки:
-- Сохраняю в переменных команды self.CPUTeam = Activity.TEAM_2; self.PlayerTeam = Activity.TEAM_1;
также с целью сделать нижеследующий код более наглядным.
CPUTeam/PlayerTeam используется ниже. Когда встретите эти обозначения в коде, обратите внимание, откуда они взялись. Вообще, если вы всерьез займетесь созданием миссий (или модов), то вам наверняка придется доставать из чужих миссий и модов куски кода для получения нужного вам эффекта. Для того, чтобы быстро и безболезненно осуществить интеграцию чужого кода в ваш, всегда тщательно разбирайте чужой код.
Гляньте сюда:
Скопипастить чужой код - дело нескольких секунд, а вот чтобы он заработал как надо...
Если коротко - всегда смотрите, какие данные и откуда берутся в коде.
Цели миссии
Функция "UpdateActivity" выглядит у меня так:
function NewMission:UpdateActivity() self:ClearObjectivePoints(); self:WinDefeat(); self:YSortObjectivePoints(); end
Про функции Clear/YSort Objective я говорил раньше (в прошлой части тутора). "WinDefeat" - название функции, отвечающей за победу/поражение игрока.
Вообще-то, в данной версии миссии функции Clear/YSort ObjectivePoints не нужны. (легко проверить, занеся вызов этих функций под комментарий (поставив перед ними "--" без кавычек) и запустив миссию) Однако, если вы будете использовать в миссии маркеры, то эти функции вам понадобятся. В общем случае рекомендуется все функции в UpdateActivity (или весь код внутри ее) помещать между ClearObjectivePoints и YSortObjectivePoints.
Что насчет функции WinDefeat, то взял ее я из стандартной миссии "Doainar" и довольно сильно урезал. Можете во время миссии посадить своего командного юнита в корабль/ракету/ящик и пронаблюдать, насколько быстро перед вашим взором предстанет рабочий стол.
Поскольку по ходу запланированной миссии мозг должен покинуть планету на корабле, я это доработаю. Пока же функция выглядит достаточно просто, без лишних наворотов, и в ней легче разобраться.
Смысл ее таков:
У нас есть зарезервированная переменная PlayerBrain. Поначалу она пустая (что комп узнает командой self:GetPlayerBrain(player)), поэтому мы находим на карте мозг (MovableMan:GetUnassignedBrain(team)), а затем в эту зарезервированную переменную этот мозг помещаем (на самом деле, всего лишь указатель помещаем на мозг в эту переменную.. но да ладно).
Помещаем следующей командой - self:SetPlayerBrain(newBrain, player);.
Если мозга на карте не нашли, тогда игроку засчитывается поражение.
Если же нашли, уже проверяем, как там у нас поживает мозг синих зеленых.
И если тот мертв, засчитываем игроку победу.
Вот на скорую руку набросанная и кошмарно оформленная схема работы функции:
На этом пока что, пожалуй, все.
В принципе, на основе кода этой тестовой миссии уже можно делать миссии. В духе "атакуй крепость, обороняемую гарнизоном и убей вражеский мозг" или "проберись одним чушком мимо охраны и убей мозг противника" или "выберись из охраняемой тюрьмы и убей ее начальника или, там, важную персону какую-нибудь". Чем я и рекомендую вам заняться.
Часть №4:Респавн, режим "охота на мозг", таймер.
Вступление
Вновь к статье прикреплен архив с тестовым модом и вновь я пробегусь лишь по самому основному, поскольку остальное можно узнать в .lua-файле мода. Скачать архив
Кстати, если к какой-либо функции нет комментария (а он вам нужен для понимания) - поищите выше по тексту аналогичную функцию, там комментарий скорее всего будет.
В этой версии тестовой миссии каждые семь секунд респавнятся солдаты красных и зеленых, и идут убивать мозг команды противника. Также, после смерти командира зеленых не наступает сразу победа, а спавнится в положенном месте корабль, и только лишь через 30 секунд миссия завершается победой.
В этой версии в .lua-файле добавились аж четыре функции.
- War() - периодично спавнит солдат.
- WarInit() - содержит данные для работы первой функции. Лучше сказать, один раз объявляет нужные переменные и один раз
задает нужные начальные значения.
- DropShip() - спавнит корабль и через 30 секунд после этого объявляет игрока победившим.
- DropShipInit() - ... догадались? Нужные переменные и начальные значения для функции DropShip().
Счастливый обладатель .lua-файла миссии к прошлой части тутора может заметить, что я перенес некоторые команды из StartActivity в WarInit и DropShipInit.
Зачем? Затем, что если у вас будет не две-три функции в миссии, а двадцать-тридцать, то вынос данных и констант в отдельные "инициализирующие" функции может вам здорово помочь. Тем, что сгруппирует данные и константы. И StartActivity у вас в помойку не превратится...
Но важнее другое.
Разделение миссии на этапы
Вот, представьте... Вам нужно добраться из одного конца города в другой. Что легче - посмотреть по карте весь путь полностью, полностью его в голове держать и идти по нему, или запоминать путь по кусочкам, по мере необходимости? Сначала посмотрел путь до ул. Архангельской, как дошел до нее - посмотрел путь до Герцена, потом - до центра, до ул. Советской, и так далее... Часто миссию можно (и нужно) разделить на этапы.
К примеру, сначала - высадить свои войска на склоне холма, потом - под огнем противника пробраться внутрь базы, потом - отключить энергию, чтобы ПВО перестали работать, потом массированный десант и в наступление... Что лучше - сразу все в память компьютера вбухать, константы и структуры данных по всем этапам, или по мере необходимости подгружать?
DropShipInit у меня запускается только после смерти мозга зеленых (после эдакого завершения условного первого этапа) и запускается лишь один раз - на это тоже внимание обратите.
Первый этап у меня олицетворяет функция War(), функция WarInit() запускается в функции StartActivity. Другими словами, данные для первого этапа подгружаются с самого начала.
Опишу логику миссии
Функция WinDefeat теперь следит лишь за тем, жив командир красных или нет (если нет - поражение игрока). В функции UpdateActivity появилась следующая конструкция:
if self.AllowDS == false then self:War(); else self:DropShip(); end
Как мы видим, пока AllowDS == false, будет выполняться функция War и будет идти первая фаза, а когда AllowDS поменяет свое значение, уже будет выполняться функция DropShip, причем никакие команды из функции War выполняться уже не будут. AllowDS меняет значение на "true" внутри функции War, но только в том случае, если погибнет мозг зеленых:
-- проверяем, мертв ли командир зеленых if not MovableMan:IsActor(self.CPUBrain) then -- если не_жив, тогда меняем значение переменной, переходя в другую фазу -- и подгружаем данные для следующего этапа: self:DropShipInit(); self.AllowDS = true; end
Так... ну, остальное прописал в файле .lua и достаточно подробно...
Хочу сказать еще несколько вещей.
Иногда посреди фазы должно произойти какое-то событие и должно произойти только один раз. Если нет возможности сделать так, чтобы сразу после происшествия этого события начиналась следующая фаза... всосали?
Тогда можно применить следующую конструкцию:
function <имя скрипта>:<название функции>Init(); <какие-то данные и константы> <наша переменная> = true; end function <имя скрипта>:<название функции>(); <какие-то команды> if <наша переменная> == true then <что-то сделать>; <наша переменная> = false; end <какие-то команды> end
Здесь в псевдокоде представлены две функции, инициализирующая и "рабочая". Благодаря условию if <наша переменная> == true и тому, что после выполнения действий условие это перестает выполняться, нужные действия будут выполнены лишь один раз. В .lua-файле миссии можете данный "прием" увидеть.
Немного для понимания. В начале миссии выполняется один раз "StartActivity". А затем, до тех пор, пока миссия не завершиться, будет выполняться "UpdateActivity" все команды сверху вниз, бесчисленное множество раз, множество раз в секунду. Все функции, которые вызываются внутри "StartActivity" будут выполнены один раз. Все функции, которые могут быть вызваны внутри "UpdateActivity" будут выполнены бесчисленное множество раз.
Советы к этой части
Напоследок процитирую слова классика и посоветую вам следить за тем, чтобы все нужные данные и структуры данных были созданы перед тем, как ваши команды бросятся эти данные и структуры использовать или обрабатывать. А также порекомендую выцарапать из кода условие с таймером и команды для респа, а затем сделать в миссии какую-нибудь интересную штуковину.
Например, чтобы периодично по времени и хаотично по пространству бомбы с неба сыпались. Координаты для бомб: -10 по игреку, а для икса: math.random(<ширина карты в пикселах>); Присвоить переменной бомбу (или создать бомбу) можно следующей командой: local bomb = CreateTDExplosive("Napalm Bomb", "Coalition.rte"); заспавнить - MovableMan:AddMO(bomb);
Считайте это вашим домашним заданием.
Только... Помните - нельзя дважды выстрелить одной пулей. Не понятно? Тогда прочтите комментарии к .lua-файлу или прошлые части тутора.
Важное замечание
Чтобы текст .lua-файла нормально отображался, его следует открывать программой "Notepad++". Если при открытии файла весь текст будет размазан по одной-трем строчкам и вся табуляция будет жестоко убита топором, скачайте вышеуказанную программу. Описание программы и ссылку на скачку можно найти Здесь.