Russian:Create Missions

From Data Realms Wiki

Revision as of 03:33, 10 June 2010 by Ximximik (Talk)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
Russian:Basics of modding Основы
Начни отсюда.


Russian:Actor Юниты
Всё, что можно контролировать.


Russian:Effects Эффекты
Взрывы, вспышки и шлейфы.


Russian:Modding 107 Создание мода
Научись, как создавать новые объекты.


Russian:Devices Оружие
Оружие и инструменты


Russian:Scenes Карты
Как создавать карты (сцены)


Russian:Module Structure Структура модулей
Как создать папку мода.


Russian:Editors Игровые редакторы
Редакторы карт и гибов. Средство просмотра акторов .


Russian:Create Missions Создание миссий
Создание миссий


Russian:Lua Lua
Скрипты.


:Category:Russian:Modding Создание модов
Все страницы по созданию модов.


:Category:Russian:Tutorials Учебники
Другая информация о создании модов.


Автор - 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. Зайдите в игру, убедитесь, что Вас не выкидывает при загрузке и не выскакивает окно с сообщением об ошибке, что в вашу карту можно играть в режиме скримиша. На всякий случай.


Если есть проблемы

  1. Изучайте написанное Вами и ищите ошибки. Если ничего не нашли, то:
  2. Изучайте как устроены другие моды-миссии и сравнивайте с тем, что написали вы. Ищите ошибки. Если и это не помогло:
  3. Обращайтесь на форум с просьбой помочь. Именно в таком порядке.

Как правило, ошибки в ini или lua бывают от полоротости. Вы же не хотите выставить себя в дурном свете?

Также в отлове ошибок очень помогает консоль (вызывается клавишей ~)

Зачастую там написано, какая именно проблема возникла и где (дается номер строки в файле, в которой произошла ошибка).

Можно, конечно, и самому строчки считать, но если ошибка где-то в строчке эдак двухсотой...

Лучше загрузить на свой комп какой-нибудь текстовый редактор с возможностью нумеровать строки.

К примеру, этот:Notepad ++

Это была подготовительная часть. Дальше уже пойдет собственно создание миссии.

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

А пока настоятельно рекомендую прочитать следующую статью: Lua для начинающих

Хотя и вообще без знания lua можно методом шевеления мозгов делать миссии... Все же будет лучше сначала вникнуть хотя бы в азы этого языка.

Знание операторов присваивания и выбора, операций сравнения, циклов, функций, таблиц - вот что нужно для комфортного программирования миссий.

Часть №2:Размещаем командные юниты и вооружаем их.

Планирование

Стандартная миссия содержит следующие части:

function <имя файла миссии>:StartActivity() 
<тело функции> 
end 
function <имя файла миссии>:UpdateActivity() 
<тело функции> 
end

Кстати, в моем случае <имя файла миссии> - это NewMission

В первой функции описываются различные данные, которые будут в дальнейшем использоваться в программе, различные глобальные переменные и постоянные.

Во второй - размещаются функции или команды, которые нужно выполнить несколько раз или один раз, но не с самого начала.

Прежде чем что-то делать, нужно определиться с тем, что мы желаем получить.

В данном примере я хочу следующее:

  1. Чтобы в нужных зонах появлялись командные юниты (Brain Robot) с оружием (Auto Pistol) и стояли неподвижно на своих местах.
  2. В нужных зонах появлялись солдаты (Soldier Light) со стрелковым оружием (Assault Rifle) и гранатами (Grenade), после появления шли уничтожать вражеский мозг.
  3. После уничтожения мозга компьютерного игрока появлялся в нужном месте корабль (Drop Ship MK1)
  4. Через 30 секунд после появления корабля, если корабль еще жив и не улетел на орбиту, корабль взрывался
  5. Условие поражения игрока: командный юнит погиб
  6. Условие победы: командный юнит внутри корабля улетел на орбиту

Расстановка акторов

Начнем со 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" посредине экрана.

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

Советы к этой части

Советую скопировать куда-нибудь названия функций или переписать их. И подробно описать, что они делают. Пока эти функции сами собой не запомнятся, их написание частенько придется посматривать.

Думаю, это хорошая идея - попробовать самостоятельно добавить на карту еще парочку солдат с оружием, прописав соответствующий код.

Кстати, могу подсказать простой способ разместить солдата в какой-либо точке, не прописывая в переменной зону для этого солдата.

  1. В редакторе зон ставим зону.
  2. Открываем .ini - файл карты и смотрим координаты Х,У зоны, зону из файла убираем. (подсказка: зону в файле можно найти по ее имени)
  3. Для нужного актора пишем:
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-файле добавились аж четыре функции.

  1. War() - периодично спавнит солдат.
  2. WarInit() - содержит данные для работы первой функции. Лучше сказать, один раз объявляет нужные переменные и один раз

задает нужные начальные значения.

  1. DropShip() - спавнит корабль и через 30 секунд после этого объявляет игрока победившим.
  2. 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++". Если при открытии файла весь текст будет размазан по одной-трем строчкам и вся табуляция будет жестоко убита топором, скачайте вышеуказанную программу. Описание программы и ссылку на скачку можно найти Здесь.

Personal tools