|
DWD-009 |
|
Меню Сайта |
 |
|
 |
|
 |
__________________
|
 |
|
 |
|
 |
|
 |
|
 |
|
 |
____________________
|
 |
|
 |
|
 |
|
 |
_______________
|
 |
|
 |
|
 |
|
 |
|
 |
_______________________
|
 |
|
 |
|
 |
|
 |
|
 |
|
 |
|
 |
|
 |
|
 |
|
 |
|
 |
|
 |
|
 |
___________________
|
 |
|
 |
_________________
|
 |
|
 |
|
 |
|
 |
|
 |
|
|
 |
ToP Sites
http://mo3del.ru/
http://mir3d.3dn.ru/
http://sw-in.narod.ru/
|
Создание основ Pолевой Игры---
Создание основ ролевой игры
12.1. Детали игрового мира
Ролевая игра предусматривает выполнение каких-либо заданий и развития нашего персонажа (или персонажей). Мы создадим основы игры такого жанра и попробуем реализовать выполнение задание (квестов) и общение с NPC (non player character), но до развития персонажа дело не дойдет, так как мы сделаем небольшую стартовую локацию (местность), где вся сюжетная линия будут связана с неполным десятком домов и проживающих там жителей.
Давайте придумаем небольшой игровой мир - предположим это будет изолированный остров с несколькими домами и создадим примитивный сюжет, позволяющий обыграть задуманные нами основы ролевых игр.
Итак, начнем
а) Сюжет
Наш персонаж, по имени Батыгин, будучи помощником главного королевского следователя земли Альтрикс направляется на остров Силлурия для расследования загадочного дела гибели нескольких островитян. По прибытии на остров он в первую очередь должен поговорить с мэром острова Натаниэлом Флетчером для получения дальнейших инструкций. Инструкции эти заключаются в том, чтобы найти нотариальную книгу, чтоб определить дату рождения одного подозреваемого, который по утверждению старожилов живет на острове уже не одно столетие и не имеет склонности к старению (что, естественно очень подозрительно!). На острове также обитают еще пару человек, с которыми нам придется общаться. Безусловно общение, которое должно нам дать позитивный результат в наших поисках проходит в определенной последовательности. Предположим, что на острове живет старейшая жительница, которая помнит разыскиваемого персонажа (назовем его - Дориан Фишборн) и может помочь нам найти книгу. Но вот беда - старушка мучается склерозом и ей нужен лечебный корешок, который может дать только лекарь острова (к примеру, имя его будет - Джонатан Уоррен). Он даст нам его просто так из уважения к старушке (звать ее будет Фелисия Хауз). Получив корень, мы поспешим к ней и она приведя в некоторый порядок свои воспоминания сообщит, что видела нотариальную книгу на заброшенном кладбище, ключ от которого с любезностью нам предоставит. Посетив кладбище и найдя книгу, нам не остается ничего как триумфально вернуться к мэру и сообщить о наших достижениях. Мэр, прочитав интересующего его факты, понимает, что дело - дрянь, подозреваемый действительно не умирает уже пару веков и посылает нас на материк архимагу Археусу для дальнейших инструкций. Однако капитан кораблика (Сергей Казаков), который стоит в местной бухте так занят починкой вверенного ему имущества, что слова мэра для него пустой звук и мы вынуждены заручиться поддержкой, сначала рыбака (Аарон Фиш), а потом мирно гуляющего по острову фрахтового брокера (Сергей Левандовский) для того чтоб капитан все таки сдвинул с места свою посудину и отвез нас на материк.
Вот такова сюжетная линия, а каким же будет остров:
б) Карта острова
Остров - это просто небезизвестный вам объект Blitz3D - Terrain, мы покажем Вам как его можно легко сделать в редакторе ландшафтов GeoScape3D, хотя вы можете воспользоваться любым другим. На острове будет десяток домов, либо пустующих, либо заселенных квестовыми персонажами. Дома будем создавать в CartographyShop и затем размещать их на
объекте Terrain. Дом мэра (Натаниэла Флетчера) можно сделать чуть побольше, чтоб отличался, а остальные можно для разнообразия создать 3-4 вида, чтоб не путать игрока схожестью. Кладбище острова - это всего лишь 4 стены и дверь, которая откроется только тогда, когда мы будем иметь в инвентаре ключ, предоставленный Фелисией Хауз. Все NPC живущие в домах - это просто картинки внутренних покоев построек с изображением жильца и только один персонаж у нас будет выполнен в виде модели - дышащий на другой оконечности острова свежим воздухом - фрахтовый брокер. Кроме домов, можно насадить пару деревьев, ну и конечно - отобразить морскую гладь с корабликом, стоящим в бухте. После выполнения последнего квеста нужно отобразить как кораблик выходит из бухты, увозя нас на материк для новых приключений.
в) предметы и инвентарь
Ко всему прочему, наш персонаж будет иметь инвентарь на несколько слотов, для получения и хранения квестовых предметов. Какие это предметы? А их всего три. Первый - лечебный корешок для Фелисии Хауз, второй - ключ, который она нам даст от кладбищенских ворот и третий - нотариальная книга, которую мы должны разыскать и принести мэру острова.
Предметы выглядят следующим образом:

и должны в игре иметь представление в виде спрайта (для отображения в инвентаре) и в виде модели (для отображения на ландшафте)
Все картинки внутренней обстановки комнат взяты из игры Might and Magic 7, как прообраза создаваемого демонстрационного примера.
12.2. Создание ландшафта острова
Как мы уже упоминали, для создания ландшафта использовался редактор GeoScape3D, как наиболее удобный с нашей точки зрения. Он позволяет визуально формировать terrain, обтягивать его текстурами и рисовать на нем собственные объекты - дороги, поля и т.д. и сохранять карту высот и карту текстур для дальнейшей загрузки в нашу игру. Редактор можно скачать с сайта http://www.geoscape3d.net.
Внешний вид редактора GeoScape3D выглядит так:

С помощью инструментов Build, Dig, Level и т.д. можно очень быстро создать задуманный ландшафт.

Об инструментах подробнее:
Build - если выбрать этот инструмент и перемещать курсор мыши по рабочему полю с прижатой левой кнопкой мыши, то мы будем наблюдать за ростом гор из водной поверхности. Этот инструмент служит для быстрого грубого создания ландшафта.
Dig - наоборот создает впадины и служит для проектирования котлованов среди возведенных ранее высот.
Level - очень хороший инструмент для создания плоских поверхностей. К примеру под поселок можно выровнять ранее возведенную командой Build холмистую местность.
Smooth - инструмент для окончательного сглаживания созданного ландшафта.
Sharpen - наоборот выделит заостренные грани скалистых отрогов.
Noise - этот инструмент спроектирует случайную пересеченную местность с впадинами и ухабами.
Lines - немного похож на Level, также выравнивает местность, но позволяет точно учитывает длину выравнивания.
Road - с помощью этой команды можно прокладывать дорожки в ранее созданных горных массивах.
Paint - этот инструмент дает возможность рисовать прямо на текстуре ландшафта, с его помощью мы можем легко выделить прибрежные зоны - выкрасив их в песочных цвет, добавить сочной травы на ровных участках и каменные породы на отрогах гор и все это легкие движением кисти.
Erosion - автоматический инструмент, задав параметры которого, можно задать неровности грунта
Auto Terrain - создает ландшафт в автоматическом режиме.
Create Texture - позволяет текстурировать созданный предыдущими инструментами ландшафт при помощи уже готовых рушений для стандартных типов местности или загружать пользовательские текстуры.
Для создания нашего уровня мы пользовались инструментами Build, Level (для площадки под поселок), Smooth (для сглаживания неровностей, от действия предыдущего инструмента), Paint (после нанесения стандартной текстуры, мы раскрасили площадку под дома, начертили дорожку и чуть больше обозначили песок возле моря). На все эти действия ушло не более пяти минут. Получилось примерно следующее:

В игре это будет выглядеть более привлекательно, и для этого нам осталось только экспортировать текстуру и карту высот. Выбираем : File->Export->Export Texture и File->Export->Export Terrain as Bitmap. Этого почти достаточно, чтобы использовать созданный ландшафт в нашей игре.
Чтоб загруженный ландшафт не выглядел вблизи слишком размытым, обычно используют специальную текстуру для детализации. Текстура может быть такой:

Используя ее, текстуру уровня и карту высот мы создать фрагмент когда по загрузке нашего ландшафта:
; Load terrain
Global terrain=LoadTerrain( "terra.bmp" )
NameEntity terrain, "FLOOR"
EntityPickMode terrain, 2
; Set terrain detail, enable vertex morphing
TerrainDetail terrain,4000,True
; Scale terrain
ScaleEntity terrain,6,80,6
; Texture terrain
grass_tex=LoadTexture( "terra_tex.bmp" )
detail=LoadTexture( "tex4.png" )
EntityTexture terrain,detail,0,1
EntityTexture terrain,grass_tex,0,2
ScaleTexture grass_tex, 256,256
ScaleTexture detail, 2,2 |
Файл terra.bmp - это наша карта высот. Нашему ландшафту мы сразу присваиваем имя объекта "FLOOR", которое будет использоваться для коллизий с нашим героем. После установки детализации ландшафта и его масштабирования (чтоб горы были повыше, мы отмасштабировали их по оси У, аж на 80), можно приступать к наложению текстур. Файл terra_tex.bmp - это наша основная текстура, а tex4.png - карта детализации. Посмотрите и сравните, как выглядит один и тот же фрагмент ландшафта в игре с и без карты детализации:
Думается не стоит подчеркивать, что реалистичнее. Единственное, что - можно "поиграться" с масштабом карты детализации для получения наиболее приемлемых результатов.
Так как остров по всем правилам должен омываться водами моря, то нам потребуется вспомнить пример из урока №8, где мы делали нечто подобное.
Фрагмент кода загрузки mesh-а для водного эффекта приведен ниже:
; Water
Global watermesh=LoadMesh("20x20mesh.3ds")
RotateMesh watermesh,90,0,0
RotateEntity watermesh,-90,0,0
ScaleEntity watermesh,10,10,10
PositionEntity watermesh, 0,2,0
Global watertexture=LoadAnimTexture("wateranim.jpg",256,124,124,0,25)
EntityTexture watermesh,watertexture,0,1
ScaleTexture watertexture,.01,.01
EntityShininess watermesh,0.2
EntityAlpha watermesh, 0.9
EntityFX watermesh, 16
Global waterflow# = 10
Global wavesize# = 0.05
Global surface=GetSurface(watermesh,1)
Global VertexCount=CountVertices(surface)
Type Vertices
Field x#
Field y#
Field z#
End Type
Dim Vertex.Vertices(VertexCount)
For A=0 To VertexCount-1
Vertex(a) = New Vertices
Vertex(a)x#=VertexX#(surface,a)
Vertex(a)y#=VertexY#(surface,a)
Vertex(a)z#=VertexZ#(surface,a)
Next |
Все это должно быть вам знакомым по уроку № 8. Напомним лишь, что эффект воды основан на синусоидальном законе перемещения вертексов объекта типа mesh на который натянута анимированная текстура воды. В главном цикле описан алгоритм смены кадров анимированной текстуры и вызов функции движения вертексов.
wd=wd+1
If wd=4
EntityTexture watermesh,watertexture,frame,1
frame=frame+1
If frame=22 Then frame=0
wd=0
EndIf
UpdateWater() |
Сама функция перемещения вертексов приведена ниже:
Function UpdateWater()
For a=0 To VertexCount-1
Freq#=MilliSecs()/waterflow
Vertex(a)z#=Sin(freq+Vertex(a)x#*300+Vertex(a)y#*400)*wavesize
VertexCoords surface,a,Vertex(a)x#,Vertex(a)y#,Vertex(a)z#
Next
UpdateNormals watermesh
End Function |
Кроме острова и омывающего его берега моря у нас должен быть поселок. Давайте рассмотрим его создание в следующем пункте нашего урока.
12.3. Проектирование домов для NPC и других объектов
Теперь нам снова предстоит вспомнить знакомый нам редактор CartographyShop, так как именно в нем мы собираемся создавать дома для персонажей, с которыми придется контактировать нашему герою. Если вы играли в игру Might and Magic 6-ой, 7-ой или 8-ой версии, то прекрасно помните, как выглядят дома тамошних жителей. В самом простом случае, дом представляет собой - каркас из нескольких кубов и крышей (тот же куб, но с немного модифицированными поверхностями). Еще понадобится дверь, как отдельный плоский объект box, так как именно щелкая по двери мы должны отобразить содержимое комнаты. Создать несколько кубов в CartographyShop, думается не представляет труда.
Текстуры для домов находятся здесь и их нужно переписать в папку TEXTURESMM с редактором и также в ту папку, где будет создаваться Ваша игра.
В течении пары минут можно создать нечто подобное:

Красным цветом, выделен объект двери. Чуть позже, прямо в редакторе мы присвоим двери класс и еще несколько параметров, которые будут считываться в игре и влиять на ход игровой логики. Скачать домик можно тут (файл с домиком). Так как мы ранее заготовили под поселок ровный участок на нашем ландшафте, то и дома можно выравнивать по высоте. Мы можем создать отдельный файл для каждого дома, но можем и создать все дома в одном файле .CSM. Мы поступили вторым способом и спроектировали еще пару домиков. Всего их 5 видов, домик мэра чуть выделяется своими размерами и наличием второго этажа. В каждом из домов обязательно должна быть создана дверь по которой будем щелкать мышью в игре. Домики установили вдоль пересекающихся улиц в соответствии с задуманным планом. Вышло примерно следующее:

Файл с картой уровня находится здесь (карта уровня) Для того чтоб корректно отображались текстуры их нужно выравнивать после помещения на поверхность стен, крыш или дверей домов. Как вы помните это делается в режиме "select surface" в редакторе.
И затем положение текстур визуально подгоняется с помощью регуляторов Offset, Scale и Rotation
Не все дома в поселке будут нести какую-то смысловую нагрузку. Некоторые будут просто пустыми и при щелчке на дверь такого дома должна выдаваться картинка с надписью, что жители покинули это строение. Кроме домов на карте еще будут три объекта, с которыми осуществляется взаимодействие - это корабль, кладбищенская дверь и фрахтовый брокер, который находится где-то на острове. Что касается двери заброшенного кладбища, то она отличается от дверей домов тем, что должна открываться при щелчке на ней (если у нас есть ключ от нее, конечно) и пропустить нас внутрь загородки. Корабль вообще не отличается от обычных дверей домов, он также отрабатывает как и они ролевой сюжет, показывая нам картинку с капитаном и диалоговое окно. Фрахтовый брокер - это модель человека и при взаимодействии с ним, открывается только диалоговое окно.
Модель корабля мы уже использовали ранее в упрощенном морском бое:

Что касается фрахтового брокера, то мы ее взяли с сайта www.planetquake.com/polycount и выглядит она так:

Примечание: Никаких движущихся персонажей и монстров для сражений с ними мы делать не будем. Так как все это будет реализовано в последнем уроке по созданию простейшего шутера от первого лица.
12.4. Структура квестового файла, квестовые флаги
Вся игровая логика должна по максимуму быть вынесена за пределы нашей программы. То есть квесты должны содержаться в специальных квестовых файлах и выполняться по мере наступления определенных событий. Какие события имеются в виду? А вот какие. Например наш персонаж щелкнул на двери домика лекаря: программа должна знать, что именно лекарь обитает в этом домике и что он должен нам сказать в текущий момент. Допусти мы только приплыли на остров и первый домик, который мы посетили - дом лекаря. Лекарь еще не знает нас и потому должен только поприветствовать. Когда же мы получаем задание от Фелисии Хауз принести лечебный корешок, лекарь уже должен реагировать другим образом и дать нам это растение.
Самый простой способ реализовать эту несложную логику - это создание квестовых файлов для каждого персонажа. В самой же игре должен быть массив квестовых флагов для каждого персонажа, чтоб отслеживать какие события уже прошли, а какие еще не наступили. Так лекарь при следующем посещении скажет нам, что уже давал нам корешок и мы его больше не тревожили. Сам квестовый файл должен иметь управляющие директивы для смены квестовых флагов текущего NPC с которым мы взаимодействуем или для других персонажей. Например мэр советует нам поговорить с Фелисией Хауз для того, чтоб узнать где может находиться нотариальная книга и тем устанавливает квестовый флаг другого персонажа. Кроме того персонаж может дать нам предмет - это тоже должно описываться в квестовом файле. Даже звуковые фрагменты какого-либо NPC будут описываться тут, не говоря уже о тексте произносимых ими фраз.
Давайте рассмотрим структуру квестового файла на примере такового для старейшей жительницы острова Фелисии Хауз:
0:0:vuh1.wav: :Эй...проваливай, ходят тут всякие!:::
1:0:vuh2.wav: :Хм...кого это черти принесли? Где мои очки?..ага вот они!:::
1:0:vuh3.wav: :А, молодой воин! Ну и что тебе понадобилось от старухи Фелисии?:::
1:0:vuh4.wav: :Чего? Старая нотариальная книга?..Хм..она...э-э-э-э...ну..как это?:::
1:0:vuh5.wav: :Нифига не помню..В моем почтенном возрасте бывают и не такие приколы, Вот доживешь до моего...Эх...Слушай принеси мне лекарственный корешок от лекаря...и моя память немного освежится!:::
1:3: : :Корешок очень поможет мне...давай принеси его скорей!:2:1:
1:1: : : :::
2:0:vuh6.wav:fpotion:Неужели я прошу так много? Где мой корень?:::
3:0:quest.wav: :Спасибо, тебе! Давай его сюда. Хрум-Хрум..эх..минутку, о чем это мы?:::
3:0:vuh7.wav: :Ага книга! Старая нотариальная книга! Последний раз я ее видела на кладбище.:::
3:0: : :Вот тебе ключ от ворот. Не забудь его вернуть! Все..оставь меня одну...:::
3:2: :gravekey: : :::
3:1: : : :::
4:0:vuh8.wav: :Ты еще не нашел применения ключу? Вобщем когда сходишь на кладбище - отдашь. Не забудь!:::
5:0: :gravekey::::
6:0:quest.wav: :Ну и отлично! Честно, я думала, что ты сопрешь мой любимый ключик! Хе-Хе...:::
6:3:vuh9.wav: :Ну иди с миром..э-э-э-э. По правде говоря, я уже забыла зачем ты приходил...:4:2:
6:1: : : :::
7:0:vuh10.wav: :Оставь меня в покое, неужели нет других мест для посещения?::: |
Сам квестовый файл находится тут (квестовый фал Фелисии Хауз) Структура каждой строки достаточно проста: любая строка (не забудьте, что переносов строк нет) состоит из семи полей, которые разделены знаком ":" (двоеточие).
1 поле - квестовый флаг
2 поле - управляющая команда, которая бывает следующих видов:
0 - ничего не происходит
1 - изменение квестового флага текущего персонажа
2 - дать предмет игроку
3 - изменение квестового флага другого персонажа (в этом случае анализируется 6 и 7
поле, в которых содержится номер этого персонажа и значение квестового флага на
который нужно его поменять)
4 - закончить игру
3 поле - звуковой файл, который должен проиграться в момент отображение фразы (если файл
не содержится на диске, ничего не происходит)
4 поле - название предмета. который должен нам дать или получить персонаж (если указан
номер 2 во втором поле, значит персонаж дает нам предмет, если - 0 значит ожидает от
нас этот предмет)
5 поле - Текстовая фраза персонажа с которой он обращается к нам (может отсутствовать, если
в строке просто содержаться команды для изменения флагов и т.д. )
6 поле - содержит номер персонажа, квестовый флаг которого нужно изменить.
7 поле - содержит значение на которое должен быть изменен квестовый флаг персонажа, номер
которого содержится в поле под номером 6.
Разумеется в солидной ролевой игре применяются куда более сложные способы управления сюжетом, но мы в учебных целях остановимся на этих.
Квестовые флаги хранятся в массиве квестовых флагов (aFlags), где индекс массива соответствует номеру NPC с которыми мы будет общаться или просто взаимодействовать
Номера персонажей (объектов взаимодействия) для нашего примера выглядят следующим образом:
0 - мэр острова Натаниэл Флетчер
1 - старейшая жительница острова Фелисия Хауз
2 - лекарь Джонатан Уоррен
3 - рыбак Аарон Фиш
4 - местный житель Фредди Ниллис
5 - пустой дом
6 - корабль с капитаном Казаковым
7 - фрахтовый брокер Левандовский
Давайте подробнее остановимся на квестовом файле Фелисии Хауз и рассмотрим все его строчки. После загрузки игры все квестовые флаги персонажей установлены в 0. То есть, если мы придем в домик Фелисии с самого начала, то она будет выдавать нам фразу:
"Эй...проваливай, ходят тут всякие!" - так как в первом поле этой фразы стоит цифра 0.
Естественно эта фраза выдается после того как мы приблизимся к домику Фелисии Хауз и нажмем на ее дверь. В редакторе CartographyShop параметры двери ( выделите дверь и нажмите "Р", выберите Entity Properties) будут следующими:

Класс объекта (class)- door, квестовый файл (quest file) - felicia.qst, картинка, которая отобразится на экране, после щелчка на двери (image) - felicia.jpg, квестовый флаг, связанный с этой дверью - не что иное как индекс в массиве aFlags.
Фраза "Эй...проваливай, ходят тут всякие!" - будет произноситься ей до тех пор пока мы не поговорим с мэром и он не посоветует обратиться к Фелисии Хауз.
В квестовом файле мэра есть строчка:
0:3: : :Жду твоих донесений с нетерпением. А теперь в путь, Батыгин и да пребудет с тобой Свет! :1:1: |
Второе поле этой строки содержит директиву (3) для смены квестового флага для персонажа (номер персонажа содержится в поле 6, это цифра 1 (соответствует Фелисии Хауз)) и изменяется этот флаг на 1. То есть после посещения мэра, Фелисия уже будет выдавать фразы начинающиеся с цифры (квестового флага) - 1.
После того как она выдаст нам информацию о своей дырявой памяти и необходимости принести ей корешок лекаря, идет строка:
1:3: : :Корешок очень поможет мне...давай принеси его скорей!:2:1: |
Второе поле этой строки содержит директиву (3) для смены квестового флага для персонажа (номер персонажа содержится в поле 6, это цифра 2 (соответствует лекарю Джонатану Уоррену )) и изменяется этот флаг на 1. Это говорит о том, что теперь уже активирован лекарь и будет выдавать нам корешок.
Строчка:
Указывает что нужно поменять квестовый флаг текущего NPC (то есть Фелисии) и теперь она будет выдавать фразу "Неужели я прошу так много? Где мой корень?" Фраза эта содержится под номером 2 квестового флага
2:0:vuh6.wav:fpotion:Неужели я прошу так много? Где мой корень?::: |
и интересна тем, что в поле 4 находится имя предмета, который мы должны отдать Фелисии, а именно - fpotion. Это и есть корешок, который даст нам лекарь. Если корешок уже получен от лекаря, то программа автоматически инкрементирует квестовый флаг Фелисии и он стает равным 3, а корешок будет удален из нашего инвентаря. Под номером этого флага (3) идут несколько строк, повествующих о том, с каким аппетитом Фелисия поедает корешок и вдруг вспоминает, что книга находится на заброшенном кладбище.
Последней под 3 номером квестового флага идет строка:
Это ни что иное как выдача нам ключа от ворот кладбища (помните, что второе поле значение 2 - выдача предмета игроку, а имя предмета gravekey). После этого инкрементируем квестовый флаг - теперь он будет равен 4.
Теперь, до тех пор пока мы не сходим и не откроем дверь кладбища, старушка будет упорно повторять фразу:
4:0:vuh8.wav: :Ты еще не нашел применения ключу? Вобщем когда сходишь на кладбище - отдашь. Не забудь!::: |
После активации ключом двери кладбища Фелисия будет ждать, когда мы вернем ей ключик, так как процесс открытия двери инкрементирует ее квестовый флаг на единицу и стает он равным 5. С данного момента она станет ждать, когда же мы вернем ей ключик обратно.
Надо заметить, это не обязательный квест и можем не возвращать ключ. Единственно на что это влияет , это на разговорчивость одного жителя по имени Фредди Ниллис, который не сообщит нам полезный совет, так как не доверяет нам. Вы можете отследить это по его квестовому файлу. Именно отдача ключа Фелисии, заставляет его поверить нам и выдать пару ценных советов.
6:0:quest.wav: :Ну и отлично! Честно, я думала, что ты сопрешь мой любимый ключик! Хе-Хе...::: |
И изменение квестового флага Фредди Нилиса:
6:3:vuh9.wav: :Ну иди с миром..э-э-э-э. По правде говоря, я уже забыла зачем ты приходил...:4:2: |
Шестое поле содержит цифру 4, а 4 квестовый флаг соответствует местному жителю Фредди Ниллису.
И последнее - инкрементируется флаг, после чего Фелисия переходит в свой завершающий режим с выдачей нам фразы, чтоб мы оставили ее в покое.
6:1: : : :::
7:0:vuh10.wav: :Оставь меня в покое, неужели нет других мест для посещения?::: |
Итак персонаж отыграл отведенную ей роль. Думается было не сложно отследить логику выполнения квестовых строк.
Чуть позже мы узнаем, как эта логика, содержащаяся в квестовых файлах будет обрабатываться программой.
12.5. Загрузка предметов, инвентарь
Как мы уже с вами определились, у нас будет три предмета: лечебный корешок растения, ключ от кладбища и нотариальная книга, посему ничего не остается как изготовить их в редакторе MilkShape3D. Через минут 15 мучений у нас получилось следующее:

Естественно все предметы разделены по файлам и для каждого заданы свои текстуры. Модели предметов находятся тут: (книга, корешок, ключ)
Для хранения предметов необходимо сделать инвентарь, куда предметы будут переноситься мышью. Например, если предмет лежит на земле, он отображается как модель. При щелчке на нем мышью - модель прячется и предмет отображается в виде спрайта, так как именно спрайт будет затем помещен в инвентарь. Для упрощения программного кода, инвентарь будем отображать в виде полоски нескольких ячеек следующим образом:

Если вы играли в игру Lands of Lore1, то должны вспомнить этот рисунок. У нас будет 9 ячеек, что вполне достаточно для хранения 3 предметов. Если бы предметов было больше, можно было бы сделать инвентарь с возможностью скроллинга ячеек, но мы остановимся на простом варианте без скроллинга. Загрузка и вывод инвентаря на игровой экран осуществляется при помощи ниже приведенных строк кода:
Global gfxStatus = LoadImage("status.bmp") |
Безусловно отображение инвентаря происходит в главном цикле:
Реализация этой функции приведена в следующей фрагменте кода:
Function DrawInventory()
DrawImage(gfxStatus, 0, 418 )
For i=0 To 8
If Inventory(i)<>0
DrawImage(Inventory(i), 215+i*42+5, 429 )
EndIf
Next
End Function |
Как вы знаете, функция Blitz3D - DrawImage() выводит изображение на экран. Так как у нас используется разрешение 640 х 480, то координаты инвентаря будет в тех пределах, которые переданы в качестве аргументов этой функции. Безусловно в профессионально сделанной игре нужно предусматривать смену разрешений и соответственно масштабирование инвентаря (либо изготовление картинок инвентарей под все возможные разрешения), но мы для простоты будет использовать только 1 вариант инвентаря для разрешения 640 х 480 пикселей. Массив Inventory хранит идентификаторы предметов, т.е если ячейка не пуста, мы должны нарисовать поверх этой ячейки лежащий в ней предмет в виде спрайта. Выглядеть в игре это будет примерно так:

Как видно из рисунка, в инвентаре находится ключ и книга.
Для информации о предметах в игре, создан специальный пользовательский тип и сопутствующие переменные и массивы:
; Items
Type itemsinfo
Field oItem%
Field ItemModel%
Field ItemID$
Field ItemName$
Field ItemDescription$
End Type
Global ALL_ITEMS = 30;
Dim Items.itemsinfo( ALL_ITEMS )
Global cFound$, NumItems = 0
Global cInvPos$
Global lItemInHand = False, CurrentItem%, CurrentModel% |
Поле oItem - будет хранит спрайтовое изображение предмета, а поле ItemModel - модель этого предмета. Переменная lItemInHand - будет использоваться для того, чтоб знать, есть ли у нас что-то в данный момент в руке (под курсором) или нет. Поле ItemName - это как раз имя предмета (gravekey, book, fpotion) по которым определяется выполнение квестов (отдать предмет/ждать предмет) связанных с предметами.
Предметы будем хранить в файле и загружать оттуда следующей функцией:
Function LoadItems(LevelName$)
NumItems = 0
ClearInventory()
filein = ReadFile(LevelName)
While Not Eof(filein)
Read1$ = ReadLine$( filein )
If Mid(Read1$,1,1) <> ";" And (Read1$ <> "")
Var$=GetStrParam$(Read1$,1)
; Any item - KEY, PLANT, BOOK etc.
If Var$="ITEM"
Items(NumItems) = New itemsinfo
CX% = Int(GetStrParam$(Read1$,3))
CZ% = Int(GetStrParam$(Read1$,4))
CY# = TerrainY(terrain, CX,50,CZ)
Items(NumItems)ItemModel = LoadMD2( GetStrParam$(Read1$,5)) ;model name
EntityTexture Items(NumItems)ItemModel, LoadTexture( GetStrParam$(Read1$,6) )
ScaleEntity Items(NumItems)ItemModel, 0.2, 0.2, 0.2
PositionEntity Items(NumItems)ItemModel,CX,CY,CZ
RotateEntity Items(NumItems)ItemModel,0,180,0
EntityRadius Items(NumItems)ItemModel, 1
EntityPickMode Items(NumItems)ItemModel,1
NameEntity Items(NumItems)ItemModel, GetStrParam$(Read1$,1)
Items(NumItems)oItem = LoadImage(GetStrParam$(Read1$,7))
Items(NumItems)ItemID = GetStrParam$(Read1$,2)
Items(NumItems)ItemName = GetStrParam$(Read1$,8)
Items(NumItems)ItemDescription = GetStrParam$(Read1$,9)
NumItems = NumItems + 1
EndIf
EndIf
Wend
CloseFile( filein )
End Function |
Этот фрагмент кода построчно читает файл, а функция GetStrParam(), возвращает элемент строки по его порядковому номеру. Все элементы строки отделены друг от друга двоеточием. Реализация функции GetStrParam() находится в файле readmap.bb. А пример строчки, где содержится информация, например о книге, выглядит так:
ITEM:book:684:539:book.md2:bookskin.bmp:book.bmp:book:Книга: |
Здесь хранятся также координаты объекта (предмета), его имя, название модели и его текстуры и название спрайта. Все эти параметры после чтения строки присваиваются соответствующим полям типа itemsinfo для дальнейшего использования. К примеру, строка:
Items(NumItems)oItem = LoadImage(GetStrParam$(Read1$,7)) |
загружает спрайт в поле oItem.
По сюжету, предмет - корешок и предмет - ключ, дают нам персонажи проживающие в домах. Посему они сразу появляются в незанятой ячейки нашего инвентаря после заданной фразы в квестовом файле. Однако у нас есть еще случай когда мы подбираем книгу, лежащую на кладбище и кладем ее в инвентарь.
Последовательность действия такая:
1. Приближаемся к предмету и кликаем на него левой кнопкой мыши. При этом прячем модель и показываем спрайтовой изображение предмета под курсором мыши.
2. Кликаем на ячейке инвентаря. Если она пуста, предмет должен остаться в ячейке, если эта ячейка занята другим предметом, то он должен оказаться "в руках", вместо того предмета, который мы держали до щелчка по ячейке.
Итак, модель предмета лежит на земле. Фрагмент кода щелчка курсором мыши по предмету:
If MouseHit(1)=True
If (Not CheckInventory(MouseX(), MouseY()))
ent = CameraPick(cam,MouseX(),MouseY())
If ent
x1=PickedX()
y1=PickedY()
z1=PickedZ()
name$ = EntityName(ent)
class$ = KeyValue$(ent,"class")
; if ANY ITEM_ found: KEY, PLANT, BOOK etc.
If name = "ITEM"
If MinXZDist(player,x1,z1) < 30
If lItemInHand
tmpPic = CurrentItem
tmpModel = CurrentModel
CurrentItem = FindItem( ent )
CurrentModel = ent
ShowEntity tmpModel
CY# = TerrainY(terrain, x1,y1,z1)
PositionEntity tmpModel, x1,CY+1, z1
HideEntity ent
Else
lItemInHand = True
CurrentItem = FindItem( ent )
CurrentModel = ent
If CurrentItem
HideEntity ent
EndIf
EndIf
EndIf
cObjectName = FindItemDescription(ent,1)
EndIf
If name = "FLOOR"
(*)................. (см ниже)
endif
;Door Class
If class$ = "door"
(**).................(см ниже)
endif
If class$ = "actdoor"
(***).................(см ниже)
endif
Endif
|
Вначале проверяется, нажата ли левая кнопка мыши и если да, то вызывается функция Blitz3D CameraPick(), которая должна вернуть нам идентификатор объекта, по котором щелкнули мышью. Если объект существует (то есть мы не щелкнули мышью по небу) и мы находимся поблизости от него, то проверяется следующее условие: есть ли у нас "в руках" какой-то предмет, если да - то мы его должны положить в то место, где был предыдущий, а тот, в свою очередь берем "в руки" Вертикальная координата У места, где мы щелкали мышью определяется при помощи функции TerrainY(), которая всегда возвратит нам текущую высоту ландшафта.
Итак предмет у нас в руке, и мы хотим положить его в инвентарь. Для этого напишем функцию с названием CheckInventory() которой будем передавать координаты мыши (экранные координаты, не абсолютные координаты мира). Функция возвратит нам True если щелчок был сделан по инвентарю. Сама функция приведена ниже:
Function CheckInventory(X, Y)
lVal = False
If (X>220) And (X<592) And (Y>432) And (Y<468)
cInvPos = Int((X-215)/42)
If lItemInHand
If Inventory(cInvPos) <> 0
tmpPic = CurrentItem
CurrentItem = Inventory(cInvPos)
CurrentModel = FindModel(CurrentItem)
Inventory(cInvPos) = tmpPic
lItemInHand = True
cObjectName = FindItemDescription(CurrentItem,2)
Else
Inventory(cInvPos) = CurrentItem
lItemInHand = False
EndIf
Else
If Inventory(cInvPos) <> 0
CurrentItem = Inventory(cInvPos)
CurrentModel = FindModel(CurrentItem)
Inventory(cInvPos) = 0
lItemInHand = True
cObjectName = FindItemDescription(CurrentItem,2)
EndIf
EndIf
lVal = True
EndIf
Return lVal
End Function |
Как вы видите, для простоты, у нас жестко заданы координаты размещения инвентаря на экране. Еще раз напомню, что в профессиональной игре это недопустимо, так как для игры может быть выбрано разное разрешение по желанию пользователя. Глобальные переменные CurrentItem и CurrentModel содержат фидентификаторы спрайта и модели предмета, который у нас находится "в руках". Если предмет "в руках", переменная lItemInHand устанавливается в True. Если мы щелкнули на спрайте, который лежит в инвентаре, то дальнейший щелчок по земле должен привести к появлению модели этого предмета в месте щелчка. Определить, какая модель соответствует заданному спрайту поможет наша функция FindModel()
Function FindModel(ItemPic)
retM = 0
For i=0 To NumItems-1
If Items(i)oItem = ItemPic
retM = Items(i)ItemModel ; Return Model
CurrentModel = ent
Exit
EndIf
Next
Return retM
End Function |
Как вы заметили, функция простым перебором проверяет соответствие идентификатора спрайта (преданного ей в качестве аргумента) текущего предмета "в руках" содержащимся в массиве предметам и при совпадении, возвращает идентификатор соответствующей спрайту модели.
12.6. Выполнение квестового файла, диалоги
Итак, мы прибыли на остров и пошли к первому попавшемуся строению для того, чтоб пообщаться с местными жителями. Давайте рассмотри механизм обработки квестового файла в нашей программе. Как вы уже знаете, сам диалог инициируется нажатием курсора мыши на двери строения, где живет кто-то из местных поселенцев:
;Door Class
If class$ = "door"
distance = MinXZDist(player, x1,z1)
If MinXZDist(player, x1,z1) < 20
; Who Lives There?
name$ = KeyValue$(ent,"name")
If name = "livenpc"
RotateEntity Npc(NumNpc-1)NpcModel, 0, EntityYaw(player), 0
EndIf
lDialogMode = True
NPCDialog(ent)
EndIf
EndIf |
Этот фрагмент кода принадлежит фрагменту (*). Здесь осуществляется проверка имени объекта и если это живой NPC (он у нас только один - фрахтовый брокер Левандовский), то мы разворачиваем его модель к камере для последующего диалога. Устанавливается переменная диалогового режима lDialogMode, и вызывается функция диалога NPCDialog(), которой передается идентификатор объекта диалога (дверь или модель живого NPC). Именно эта функция является "сердцем" общения и контроля выполнения сюжета нашей совместно проектируемой ролевой игры. Приведем текст этой функции ниже и затем прокомментируем:
Function NPCDialog(ent)
If lDialogMode
ImageFile$ = FindNPCImageFile(ent)
nameNPC$ = KeyValue$(ent,"name")
img = LoadImage("PICS"+ImageFile$)
qfile = ReadFile( FindNPCQuestFile(ent) )
iQuestFlag% = FindNPCQuestFlag(ent)
S$ = ReadLine$( qfile )
If nameNPC <> "livenpc"
enterSound = LoadSound("sounds/enter.wav")
PlaySound(enterSound)
EndIf
;go to currentquest
While aFlags(iQuestFlag) <> Int(GetStrParam$(S$,1)) And (Not Eof(qfile) )
S$ = ReadLine$( qfile )
Wend
quest_name$ = GetStrParam$(S$,4)
;Search Item in Inventory
For i=0 To 8
item_name$ = FindItemName( Inventory(i) )
If quest_name = item_name
Inventory(i) = 0
S$ = ReadLine$( qfile )
aFlags(iQuestFlag) = aFlags(iQuestFlag) + 1
CurrentItem = 0
lItemInHand = False
EndIf
Next
;Repeat While quest strings
While (aFlags(iQuestFlag) = Int(GetStrParam$(S$,1)))
lBtPress% = False
cDialogText = GetStrParam$(S$,5)
l_First_Time = True
While lBtPress = False
If MouseHit(1)=True
X% = MouseX()
Y% = MouseY()
If (X>548) And (X<627) And (Y>381) And (Y<402)
lBtPress = True
EndIf
EndIf
UpdateWorld
RenderWorld
; Not Show Picture if Live NPC
If nameNPC <> "livenpc"
Cls
DrawImage img, 0, 0 ; Draw house inside
EndIf
DrawInventory()
CountStrings(cDialogText)
DrawImage gfxDialogDST, 0, 418-imY
SetFont Font1
WriteInfo()
Flip
If l_First_Time
If Int(GetStrParam$(S$,3)) <> "" ; Sound
If i_Sou
StopChannel chnWave
EndIf
i_sou = LoadSound("sounds/"+GetStrParam$(S$,3))
PlaySound(i_sou)
EndIf
l_First_Time = False
EndIf
Wend
If Eof(qfile)
Exit
EndIf
S$ = ReadLine$( qfile )
If Int(GetStrParam$(S$,2)) = 1 ;Increment Quest flag
aFlags(iQuestFlag) = aFlags(iQuestFlag) + 1
Exit
EndIf
If Int(GetStrParam$(S$,2)) = 2 ; Give item to hero
aFlags(iQuestFlag) = aFlags(iQuestFlag) + 1
GivenItem$ = GetStrParam$(S$,4)
ItemPic = FindItemPic( GivenItem )
; Put Item to empty cell of Inventory
For i=0 To 8
If Inventory(i) = 0
Inventory(i) = ItemPic
EndIf
Exit
Next
EndIf
If Int(GetStrParam$(S$,2)) = 3 ; Change QFlag of other NPC
OtherFlag% = GetStrParam$(S$,6)
OtherFlagValue% = GetStrParam$(S$,7)
aFlags(OtherFlag) = OtherFlagValue
EndIf
If Int(GetStrParam$(S$,2)) = 4 ; End The Game
ShowEndSequence()
Exit
EndIf
Wend
CloseFile(qfile)
FreeImage( img )
EndIf
If nameNPC <> "livenpc"
enterSound = LoadSound("sounds/close.wav")
PlaySound(enterSound)
EndIf
End Function |
Эта, кажущаяся на первый взгляд громоздкая функция, на самом деле весьма проста. Вся информация о жителях находится в массиве типа npcinfo - (сам тип с сопутствующими переменными и массивом приведен ниже)
;NPC
Type npcinfo
Field oNPC%
Field NPCModel%
Field NPCID$
Field status%
Field NPCName$
Field QuestFile$
Field QuestFlag%
Field Image$
End Type
Global ALL_NPC = 30; Number of NPC
Dim Npc.npcinfo( ALL_NPC )
Global NumNpc = 0 |
Его инициализация происходит при загрузке уровня. Так как большинство NPC (то есть жителей, с которыми общается наш персонаж) - на самом деле - объекты типа дверь домов, то именно информация хранящаяся в объекте двери (мы вводили ее в редакторе Cartography Shop) попадает в массив Npc описывающий всех персонажей нашего демонстрационного примера. Ниже приведен фрагмент, заполняющий массив Npc во время чтения элементов карты уровня.
; === check for NPC ====
; === doortype NPC
If KeyValue(mesh,"class") = "door"
Npc(NumNpc) = New npcinfo
Npc(NumNpc)NpcName = KeyValue(mesh,"name")
Npc(NumNpc)NpcModel = mesh
EntityType Npc(NumNpc)NpcModel, TypeDoor
EntityPickMode Npc(NumNpc)NpcModel,2
NameEntity Npc(NumNpc)NpcModel, properties
Npc(NumNpc)QuestFile = KeyValue(mesh,"questfile") ; QuestFile
Npc(NumNpc)QuestFlag = KeyValue(mesh,"questflag") ; QuestFlag
Npc(NumNpc)Image = KeyValue(mesh,"image") ; House Inside Image
NumNpc = NumNpc + 1
EndIf
If KeyValue(mesh,"class") = "door"
Doors(NumDoors) = New doorsinfo
Doors(NumDoors)DoorName = KeyValue(mesh,"name")
Doors(NumDoors)oDoor = mesh
Doors(NumDoors)doortype = VDOOR
Doors(NumDoors)status = CLOSED
EntityPickMode Doors(NumDoors)oDoor,2
NameEntity Doors(NumDoors)oDoor, properties
NumDoors = NumDoors + 1
EndIf |
Как вы помните, вся информация, о квестовых файлах, индексах в массиве квестовых флагов введена на этапе проектирования уровня и здесь только считывается в массив. Как вы заметили, на уровне существует два класса дверей - "door" и "actdoor". К первым принадлежат двери в жилища, а ко второму типу принадлежит дверь, открывающая кладбище. Если что-то неясно, загрузите уровень в редактор Cartography Shop и еще раз просмотрите все параметры дверей нашей карты (выделение двери мышью и нажатие клавиши "P").
Давайте вернемся к нашей громоздкой функции NPCDialog() и рассмотрим как она работает. Вначале при помощи функций FindNPCImageFile(), FindNPCQuestFile(), FindNPCQuestFlag() мы получаем информацию о картинке, которая должна отображаться при щелчке на дверь жилища, квестовом файле, связанным с этим жилищем и индексе в массиве квестовых флагов, соответствующем этому жилищу. Эти функции получают в качестве аргумента идентификатор двери, по которой щелкнули мышью и простым перебором ищет совпадение в массиве Npc и найдя, возвращает искомый параметр. При входе в дом проигрывается звуковой файл открывающейся двери. Затем мы выходим на значение текущего квестового флага, что значит, что мы должны пропустить те строки, которые уже отыграли. Далее, если в четвертом параметре квестовой строки есть упоминание, о предмете, который мы должны принести, то проверяется инвентарь на наличие этого предмета. Если предмет найден, квест выполнятся и предмет удаляется из инвентаря, при этом увеличивается квестовый флаг данного NPC на единицу. Далее начинается цикл перебора строк квестового файла. Текст фразы (из отдельно взятой строки), которую хочет донести до нашего сведения обитатель жилища считывается в переменную cDialogText и дальше мы должны вывести ее на экран. Так как строки эти бывают разной длины (в зависимости от разговорчивости того или иного жителя ), то обработку их отображения возьмет на себя отдельно написанная функция, которую мы назовем в программе - CountStrings(). Цель этой функции - корректно вывести на экран отдельную диалоговую строку. Результат работы этой функции выглядит так:

Чем больше строк в фразе, тем больше диалоговое окно. Чтоб вывести на экран следующую строку квестового файла, нужно нажать на кнопку "Далее" указателем мыши (щелчок по этой кнопку определяется по координатам мыши во время щелчка и если он находится в определенных пределах, то значит мы щелкнули на кнопке "Далее")
В третьем параметре квестовой строки содержится имя звукового файла , который мы должны воспроизвести. Если такой файл не существует - ничего не воспроизводится:
If Int(GetStrParam$(S$,3)) <> "" ; Sound
If i_Sou
StopChannel chnWave
EndIf
i_Sou = LoadSound("sounds/"+GetStrParam$(S$,3))
PlaySound(i_sou)
EndIf |
После всего этого идут наиболее важные проверки второго параметра квестовой строки. Как вы помните эти параметры значат следующее
0 - ничего не происходит
1 - изменение квестового флага текущего персонажа
2 - дать предмет игроку
3 - изменение квестового флага другого персонажа (в этом случае анализируется 6 и 7
поле, в которых содержится номер этого персонажа и значение квестового флага на
который нужно его поменять)
4 - закончить игру
Все проверки сводятся к анализу параметров квестовых строк и изменений квестовых флагов как текущего NPC, так и других связанных дальнейшим развитием сюжета.
И, наконец, при выходе из жилища - воспроизводим звук закрывающейся двери.
Если у Вас возникли какие-то затруднения - внимательно изучите квестовые файлы всех действующих персонажей, еще раз проиграйте демо и освежите сюжетную линию, просмотрите параметры интерактивных объектов (дверей) в файле уровня.
Два персонажа, с которыми нам приходится общаться и они не спроектированы в редакторе Cartography Shop - это корабль (капитан внутри корабля) и модель фрахтового брокера. Оба эти объекта описаны прямо в программе, что сделано в учебных целях. Гораздо более правильным было бы вынести их также в файл, где описываются персонажи или нечто подобное, чтоб не делать исправления в программе, а менять, если возникнет необходимость внешние файлы. Далее следует фрагмент инициализации модели корабля и модели фрахтового брокера и программное задание для них квестовых файлов, картинок и квестовых флагов:
; Ship
Global shiptex = LoadTexture("Modelsship.jpg")
Npc(NumNpc) = New npcinfo
Npc(NumNpc)NpcName = "door"
Npc(NumNpc)NpcModel = LoadMD2("Modelsship1t.md2")
PositionEntity Npc(NumNpc)NpcModel, 850, 2, 859
TurnEntity Npc(NumNpc)NpcModel, 0, 180, 0
ScaleEntity Npc(NumNpc)NpcModel, 1.5, 1.5, 1.5
AnimateMD2 Npc(NumNpc)NpcModel, 1, 0.05, 1, 1
EntityTexture Npc(NumNpc)NpcModel, shiptex
EntityType Npc(NumNpc)NpcModel, TypeShip
EntityPickMode Npc(NumNpc)NpcModel,1
EntityRadius Npc(NumNpc)NpcModel, 30
NameEntity Npc(NumNpc)NpcModel, "class=door"
Npc(NumNpc)QuestFile = "captain.qst"
Npc(NumNpc)QuestFlag = 6
Npc(NumNpc)Image = "captain.jpg"
NumNpc = NumNpc + 1
; Peasant
Npc(NumNpc) = New npcinfo
Npc(NumNpc)NpcName = "livenpc"
Npc(NumNpc)NpcModel = LoadAnimMesh("Modelspeasant.b3d")
PositionEntity Npc(NumNpc)NpcModel, 557, 8, 346
ScaleEntity Npc(NumNpc)NpcModel, 0.1, 0.1, 0.1
EntityType Npc(NumNpc)NpcModel, TypeNPC
EntityPickMode Npc(NumNpc)NpcModel,2
EntityRadius Npc(NumNpc)NpcModel, 20
NameEntity Npc(NumNpc)NpcModel, "class=door"+Chr(10)+"name=livenpc"
Npc(NumNpc)QuestFile = "levanda.qst"
Npc(NumNpc)QuestFlag = 7
Npc(NumNpc)Image = "toad.bmp"
Animate Npc(NumNpc)NpcModel, 1, 0.05
TurnEntity Npc(NumNpc)NpcModel, 0, 120, 0
NumNpc = NumNpc + 1 |
Примечание: Еще раз заметим - все объекты игрового мира должны быть описаны вне пределов программы и приведены здесь для примера.
12.7. Финальная сцена
После того, как вы поговорили с фрахтовым брокером Левандовским, который стоит и дышит воздухом на берегу моря за ближайшим холмиком, становится возможным уплыть с острова на материк. Это и есть завершающее задание нашего демо-примера. Для того, чтоб отправится восвояси, вы подходите к кораблю и прослушав речь капитана о том, что он готов к отплытию, смотрите, как корабль отплывает из бухты.
Если вы посмотрите на завершающую строчку квестового файла капитана корабля:
То заметите, что второй параметр этой строки равен 4, что по нашей договоренности символизирует об окончании игры. В функции NPCDialog() есть так фрагмент кода:
If Int(GetStrParam$(S$,2)) = 4 ; End The Game
ShowEndSequence()
Exit
EndIf |
Это означает, что если второй параметр квестовой строки равен 2, мы должны вызвать функцию
ShowEndSequence(), реализацию которой смотрим ниже:
; !!!!!!!!!!!!!! Finale !!!!!!!!!!!!!!
Function ShowEndSequence()
sndWave1=LoadSound("Soundsvictory.mp3")
EntityType Npc(NumNpc-2)NpcModel, TypeNPC
StopChannel chnWave
PlaySound(sndWave1)
PositionEntity player, 815,20,1110
RotateEntity player, 0,-180,0
AnimateMD2 Npc(NumNpc-2)NpcModel, 1, 0.1
While KeyHit(1) <> True
MoveEntity Npc(NumNpc-2)NpcModel, 0,0,-0.3
UpdateWorld
RenderWorld
wd=wd+1
If wd=4
EntityTexture watermesh,watertexture,frame,1
frame=frame+1
If frame=22 Then frame=0
wd=0
EndIf
UpdateWater()
PointEntity cam, Npc(NumNpc-2)NpcModel
Flip
Wend
End
End Function |
Эта функция проигрывает финальный музыкальный трек, загруженный из файла victory.mp3, устанавливает камеру (вместе с игроком) на некотором удалении от острова и позиционирует ее на корабль. Корабль медленно двигаем к выходу из гавани и далее в открытое море.
Некоторые разработчики делают заставки в специализированных пакетах - 3D Studio MAX, Maya и прочих, а другие создают сюжетные ролики на движке самой игры. Иногда это даже более эффективно!
Некоторые скриншоты разработанной с Вами ролевой игры приведены ниже:
Все файлы демо-примера с исходниками находятся здесь.
Что будет далее:
Темой следующего урока будет разработка трехмерного шутера от первого лица с некоторыми зачатками искусственного интеллекта противника.
уже 16009 посетителей!
|
|