Приветствую Вас Гость | Зарегистрироваться | Вход | RSS

Поиск по сайту:
Поиск в интернете:
Главная » Статьи » Учебники по Blitz3d

Учебник для начинающих по Blitz3d (Часть 2_1)
Полный учебник по Blitz 3D. Часть 2.
Продолжение учебника по Blitz 3D. Теперь будет намного интереснее, конечно при условии, что Вы тщательно изучали первую часть учебника. В этой части мы займемся созданием простенькой 3D игры.

Основы движка

Итак, мы уже достаточно вооружены, чтобы сделать простенькую игру, так что хватит абстрактных примеров, а начнём уже делать игры, а то, что мы ещё не изучили, узнаем в самом процессе, по мере надобности. Что это будет за игра? Сделаем такой вариант игры, которую делали в основах по BlitzBasic`у, только трёхмерный. Там у нас будет шарик, который будет ездить по плоскости, собирать, допустим, жёлтые кубики, и умирать от столкновения с красными, короче, сюжет стандартный. Начнём с того, что создадим шарик игрока и его управление (ну и камеру со светом):

SeedRnd MilliSecs()
Graphics3D 640,480,16,1
SetBuffer BackBuffer()
Global Player=CreateSphere()
cam=CreateCamera()
PositionEntity cam,0,60,0
TurnEntity cam,90,0,0
lit=CreateLight()
TurnEntity lit,70,70,0
Repeat
If KeyDown(200) MoveEntity Player,0,0,.2
If KeyDown(208) MoveEntity Player,0,0,-.2
If KeyDown(203) TurnEntity Player,0,3,0
If KeyDown(205) TurnEntity Player,0,-3,0
UpdateWorld
RenderWorld
Flip
Until KeyHit(1)
End


Итак, всё просто – есть шарик, который ездит где-то внизу и поворачивается, вот только одно меня здесь смущает – не понятно, куда этот шарик повёрнут – он везде круглый, не видно, где перед, где, гм, зад. У меня вот такое решение этого вопроса – что если сделать у него такой мини хвост – как у капли или метеора? Т.е. здесь просто бы подошёл конус. Можно, конечно, сделать ещё один объект – как раз конус и просто сделать привязку к шару, но привязки полезны, когда нам нужно обязательно 2 различных объекта, а не один, или, например, если привязка через некоторое время должна пропасть, здесь же нам желателен объект, представляющий из себя цельный 3D-объект. Можно сделать модель в 3D Studio MAX, конечно, а потом её сюда загрузить, вот только зачем забивать лишнее дисковое пространство на модели, которые мы сами можем сделать в самом Blitz3D! Как? Очень просто – присоединив к 3D-объекту шара объект конуса! Вот как мы поступим (после создания шара):

Plac=CreateCone(8)
RotateMesh Plac,-90,0,0
ScaleMesh Plac,1,1,1.2
PositionMesh Plac,0,0,-1.5


Здесь мы создаём конус по имени Plac, вертим его там и по всякому подгоняем. Так как после создания шар будет стоять в точке 0,0,0 – конус будет ровно подогнан к нему – можете запустить и посмотреть. Вот только если Вы будете этот шар двигать, конус останется на месте (естественно). Итак, как я уже говорил, можно этот конус привязать (особой разницы не будет), но всё таки основываясь на программерскую этику, сделаем из них один объект (у нас есть возможность, да и зачем нам 2 объекта вместо одного). Короче, добавляем:

AddMesh Plac,Player

Эта команда добавляет один 3D-объект к другому, то есть в данном случае мы добавляем 3D-объект конуса к 3D-объекту шара. Но! При этом сам объект конуса не исчезает (то есть мы этот конус как бы добавляем копированием). Короче, теперь у нас два объекта – шар присоединённый к конусу, и сам конус, который нам сейчас не нужен. Поэтому (опять новая команда) мы его уберём:

FreeEntity Plac

Эта команда убирает заданный объект (в данном случае конус) и (самое главное) очищает от него память. Когда будете делать большие игры, где будет много уровней, при выходе из одного из них, не забывайте очищать ненужные объекты, иначе они будут копиться и занимать память, которая не резиновая, в результате всё будет тормозить после определённого времени игрового процесса. Дальше, не будет же этот шарик просто так ездить по полю. Надо создать ему определенные препятствия. Создадим-ка мы кубики, и разбросаем их по нашему будущему полю:

Dim Walls(29)
For i=0 To 29
Walls(i)=CreateCube()
PositionEntity Walls(i),Rnd(-50,50),0,Rnd(-50,50)
Next


Здесь мы, как Вы видите, создаём массив на 30 элементов, и затем расставляем в них кубики. А сами кубики раскидываем в случайном порядке по полю. Так, кубики есть, столкновений нет, вывод: надо вставить сюда наш инициализатор столкновений, который мы так старательно изучали в прошлой главе. Поехали. Сначала мы должны определить две константы для наших объектов – для шарика и для кубиков:

Const TypePlayer=1,TypeWalls=2


Вставьте эту строчку после инициализации графики – т.е. в начало. Так типы есть, осталось только указать что шар и кубики к ним принадлежат:

EntityType Player, TypePlayer

Эту строчку поставьте после создания самого объекта шарика - т.е. после присоединения к нему конуса.

EntityType Walls(i), TypeWalls


А эту строчку вставьте в конец цикла создания кубиков (после команды PositionEntity Walls(i)…). Так, типы задали, конечно, но сталкиваться они всё равно не будут – пока мы прямо не укажем, что они, мол, сталкиваться должны.

Collisions TypePlayer,TypeWalls,2,2

Здесь мы задали, что TypePlayer – который у нас является шаром должен сталкиваться с TypeWalls – т.е. с кубиками, соприкасаясь методом сфера к полигону, полностью скользя по нему. Можно последнюю цифру заменить на 1 – тогда шарик будет как бы «прилипать» к кубикам. Эту строчку поставьте после создания всех кубиков. Хочу сразу заметить (из своего опыта), что команды Collisions нужно ставить одной из последних – т.е. сначала нужно инициализировать все предметы, затем их все расставить по местам, и только потом обозначать столкновения между ними, можно, конечно сразу задать константы, и все столкновения между этими константами, но потом, когда Вы будете создавать объекты, помните, что создаются-то они все в одной точке – 0, 0, 0 и получается, что находятся друг в друге, и затем, когда мы будем их расставлять, могут появиться баги – т.е. мы скажем объекту, типа, «поставься в точку 0, 10, 100», а он окажется в точке 0,8,70 (или типа того). Либо в таком случае нужно делать так: создавать объекты, и тут же их переставлять в новые места, но, по-моему первая идея была лучше!

Так, теперь можно со спокойной душой всё это дело запустить, да нет, не в производство - рановато пока. Уже что-то вырисовывается!

По традиции – полный код всего этого безобразия:

SeedRnd MilliSecs()
Graphics3D 640,480,16,1
SetBuffer BackBuffer()
Const TypePlayer=1,TypeWalls=2
Global Player=CreateSphere()
Plac=CreateCone(8)
RotateMesh Plac,-90,0,0
ScaleMesh Plac,1,1,1.2
PositionMesh Plac,0,0,-1.5
AddMesh Plac,Player
EntityType Player, TypePlayer
FreeEntity Plac
Dim Walls(29)
For i=0 To 29
Walls(i)=CreateCube()
PositionEntity Walls(i),Rnd(-40,40),0,Rnd(-40,40)
EntityType Walls(i), TypeWalls
Next
cam=CreateCamera()
PositionEntity cam,0,60,0
TurnEntity cam,90,0,0
lit=CreateLight()
TurnEntity lit,70,70,0
Collisions TypePlayer,TypeWalls,2,2
Repeat
If KeyDown(200) MoveEntity Player,0,0,.2
If KeyDown(208) MoveEntity Player,0,0,-.2
If KeyDown(203) TurnEntity Player,0,3,0
If KeyDown(205) TurnEntity Player,0,-3,0
UpdateWorld
RenderWorld
Flip
Until KeyHit(1)
End


Доработка движка

Я не спорю, ездить шариком по плоскости, лавируя между кубиков, в созданной нами программе конечно очень захватывающе, но боюсь другие люди (не Вы – да, да существуют ещё и другие!) скажут что это, конечно, круто, но Quake им нравится больше, правильно, они ничего не понимают в искусстве! Вот только таких людей подавляющее большинство, да и сами игры обычно создаются для других людей (за редким исключением), а этим варварам нужно что? Ну не нравится им просто спокойно ездить по полю. Хорошо, мы им сейчас такое устроим! Помните, в змейке использовали такие квадратики, которые надо было собирать, используем этот примитивный метод. Для начала добавим для него отдельную константу – вот так

Const TypePlayer=1,TypeWalls=2,TypeTarget=3

Дальше создадим сам объект, на этот раз у нас будет цилиндр, и укажем его тип:

Target=CreateCylinder()
EntityType Target,TypeTarget


Ну, и добавим обработку столкновений:

Collisions TypePlayer,TypeTarget,2,2


Ок, вроде бы основу инициализировали. Дальше сделаем так, чтобы цилиндр перемещался в другое место при столкновении с шаром. Как это сделать? Естественно – условием внутри главного цикла (Вы хоть помните такой?):

If EntityCollided (Player,TypeTarget) PositionEntity Target,Rnd(-40,40),0,Rnd(-40,40)


Новая команда? Точно! Вы наверное уже поняли что это? Да – это и есть проверка на соприкосновение: Пишется так EntityCollided (Объект, тип с которым он соприкасается) – итак, эта команда возвращает нам True (Истина), если заданный в скобках объект соприкасается с заданным типом. Т.е. в данном случае – шар соприкасается с типом цилиндра (TypeTarget). Перед использованием этой команды нужно обязательно указать этот тип столкновения командой Collisions, иначе она работать не будет!

Так, теперь уже ездить можно не бесцельно, а гоняясь за цилиндром. Но, опять же – слишком миролюбиво, нам нужен Game Over.

Как его реализовать? Очень просто – когда шар будет сталкиваться с кубиками – тогда и будет конец игры. Следующее условие:

If EntityCollided (Player,TypeWalls) End

Здесь мы пишем, что когда шар сталкивается с типом кубиков, игра заканчивается, она просто выходит, грубо, конечно, но мы всё это потом исправим, я Вам обещаю! Правда исправим!!! Да я Вас вообще когда-нибудь обманывал? ...... Когда??? Пойдём дальше, игрок начинает подумывать о том, что ему хочется выйти тогда, когда игра даёт ему шанс расслабиться – то есть проиграв или просто затормозив где-нибудь, у игрока начинает происходить процесс анализа происходящего. К чему это я? А, да – внимание игрока должно быть сосредоточено на игре – т.е. шарик должен всё время быть в движении, что бы у центрального процессора человека под названием «мозг» даже не появлялось мысли о кнопке Esc, но и двигаться быстро он тоже не должен – если человек будет проигрывать сразу после нажатия кнопки Старт – тоже будет не очень хорошо.

Теперь нам нужна постоянная скорость, и, естественно, стрелки управления – вперёд-назад теперь нам будут не нужны (если игрок будет передвигаться с постоянной скоростью), поэтому строчки:

If KeyDown(200) MoveEntity Player,0,0,.2
If KeyDown(208) MoveEntity Player,0,0,-.2


Нужно заменить на:

MoveEntity Player,0,0,.2


Так, уже что-то, во что можно поиграть, во всяком случае уже интереснее, но поиграв какое-то время (минут десять) можно натренироваться так, что пальцы уже сами будут нажимать на стрелки, и обходить препятствия используя уже один спинной мозг, и опять же центральный процессор головного мозга будет посылать сигналы о том, что, типа, не пора бы выйти? Поэтому требуется усложнять игру. Как это сделать? О, в голову приходят всякие извращённые мысли о том, что можно сделать с игроком, но, думаю, лучше всего просто так увеличивать скорость после каждого собранного цилиндра, заодно ещё будем записывать очки игрока! Для этого нам потребуется указать новые переменные (эту строчку нужно вставить где-нибуть в начале, допустим, после констант):

Global Speed#=.1,Score=0

Здесь у нас Speed# (не забудьте, что когда Вы собираетесь использовать дробные числа, Вы должны использовать # после названия переменных, а то у Вас ничего не получится) – скорость, и Score – очки. Теперь, скорость должна увеличиваться после столкновения с цилиндром, поэтому простое условие:

If EntityCollided (Player,TypeTarget) PositionEntity Target,Rnd(-40,40),0,Rnd(-40.40)
If EntityCollided (Player,TypeTarget)
PositionEntity Target,Rnd(-40,40),0,Rnd(-40,40)
Score=Score+1
Speed=Speed+.01
EndIf


Т.е. здесь мы при условии, что шар сталкивается с типом цилиндра, выполняются три действия – цилиндр перемещается в новое место, переменная Score увеличивается на 1, и переменная Speed увеличивается на 0.1, вот только скорость-то от этого не меняется, поэтому строчку перемещения самого игрока в цикле:

MoveEntity Player,0,0,.2


Заменим на

MoveEntity Player,0,0,Speed#


Что-то изменилось? Видите – теперь игрок передвигается не просто со скоростью 0.2, а со значением переменной Speed#, которое изменяется. Ну, вот так намного интереснее – теперь это уже может, хоть и с натягом, но называться игрой! В это даже самому можно поиграть, чем я сейчас и займусь, после того, как покажу, как писать на экране количество очков игрока. Итак новая команда:

Text 320,10,"Score : "+Score,True,True

Синтакс: Text x, y, текст, центрирование по х, центрирование по у – эта команда схожа с командой Print, только предназначена для графического режима. Эта команда 2-х мерная, т.е. рисуется поверх экрана, запомните, 2-х мерные команды типа рисования линий, картинок, текста нужно ставить между командами RenderWorld и Flip, иначе Вы их не увидите. Ну всё кажется самая глючная из Ваших игр готова, да, глючная, а всё же игра!!! Но все глюки мы постараемся убрать в следующей части. Вот весь код:

EntityType Player, TypePlayer
FreeEntity Plac
Dim Walls(29)
For i=0 To 29
Walls(i)=CreateCube()
PositionEntity Walls(i),Rnd(-40,40),0,Rnd(-40,40)
EntityType Walls(i), TypeWalls
Next
Target=CreateCylinder()
PositionEntity Target,Rnd(-40,40),0,Rnd(-40,40)
EntityType Target,TypeTarget
cam=CreateCamera()
PositionEntity cam,0,60,0
TurnEntity cam,90,0,0
lit=CreateLight()
TurnEntity lit,70,70,0
Collisions TypePlayer,TypeWalls,2,2
Collisions TypePlayer,TypeTarget,2,2
Repeat
MoveEntity Player,0,0,Speed#
If KeyDown(203) TurnEntity Player,0,3,0
If KeyDown(205) TurnEntity Player,0,-3,0
If EntityCollided (Player,TypeTarget)
PositionEntity Target,Rnd(-40,40),0,Rnd(-40,40)
Score=Score+1
Speed=Speed+.01
EndIf
If EntityCollided (Player,TypeWalls) End
UpdateWorld
RenderWorld
Text 320,10,"Score : "+Score,True,True
Flip
Until KeyHit(1)
End


Устранение багов

Надоело играть, слишком сырая получилась, хотелось бы её переделать. Вот и давайте её доработаем. Первым, что бросается в глаза – во-первых, (во всяком случае на более медленных машинах это заметно) это то, что когда мы прикасаемся к цилиндру, он меняет своё положение, причём 2 раза, во-вторых очки тоже прибавляются на 2, хотя мы белым по синему написали Score=Score+1” то бишь на 1, а не на 2, ну, и кроме того в общем скорость тоже увеличивается в 2 раза быстрее чем надо. Когда я это заметил (а заметил я это сразу), моё подсознание сразу выдало мне способ решения этого бага (интуиция - хорошая вещь), затем через некоторое время сознание дало подробное объяснение, почему это происходит, ну, а память, посчитала эту информацию ненужной и не запомнила. Короче не помню я почему, но решается это всего одной единственной строкой:

UpdateWorld

Что Вы уже эту команду знаете? И она уже у нас стоит? Правильно, а Вы добавьте ещё одну. Да не рядом с предыдущей. Добавьте её в условие соприкосновения шара и типа цилиндра после установки цилиндра на новое место. Кому здесь не понятно? Объясняю подробнее, кто не хочет осмысливать мою предыдущую фразу: поставьте эту команду после строчки

PositionEntity Target,Rnd(-40,40),0,Rnd(-40,40)


Как где она? В программе! Почему так происходит? Вспоминать не охото, попробуйте догадаться сами, помню только, что это связано с обработкой столкновений и, кажется после первого UpdateWorld как бы столкновение остаётся, хотя цилиндр перемещается, поэтому надо ставить ещё один, тьфу ты блин, не помню, в общем и всё тут. (программист называется).

Второй баг. Вы наверное уже заметили, что иногда, когда Вы запускаете игру, она сразу выходит? Вот здесь всё наоборот: объяснение простое, а попариться придётся дольше! Так вот, если Вы ещё не поняли, в таких случаях получается так, что кубик создаётся слишком близко к шарику, так что шарик уже «влезает» в него при своём создании, дольше срабатывает условие, и игра выходит, так что нам нужно будет сделать так, чтобы кубики не создавались слишком близко к центру. Сделать это можно в начале. Алгоритм таков: когда кубики случайным образом расставляются по местам, нужно проверять их положение, и, если они будут находиться на расстоянии меньше чем 3 от центра, задавать их положение заново, т.е. циклом с условием. Так вот просто строчку в цикле создания кубиков:

PositionEntity Walls(i),Rnd(-40,40),0,Rnd(-40,40)

Нужно заменить на:

Repeat
PositionEntity Walls(i),Rnd(-40,40),0,Rnd(-40,40)
Until Abs(EntityX(Walls(i)))>4 Or Abs(EntityZ(Walls(i)))>4


Так, новые слова: Abs(число) – это не команда, а функция. Она возвращает нам модуль числа стоящего в скобках (т.е. если в скобках было –4, то она возвращает 4, если 5 – то число так и остаётся положительным и равным 5). Эти строчки расшифровываются так. Сначала в случайное место ставится кубик, затем идёт проверка, если координата X или координата Z меньше 4 по модулю (т.е. в диапазоне от –4 до 4), то цикл повторяется (так пока кубик не встанет в нужное место), короче, получается такой квадрат размером 4*4 вокруг центра, в который кубики уже никогда не попадут.

Третий баг. Особенно некрасиво смотрится, когда цилиндр оказывается внутри какого-либо кубика, и его невозможно достать сейчас мы с ней справимся. Опять же исправлять это надо в тот момент, когда цилиндр куда-то ставится случайным образом. Алгоритм будет примерно такой же как в прошлый раз – т.е. в цикле с условием, только на этот раз предметом проверки будет пересечение цилиндра с кубиками. Так, строчку в главном цикле:

PositionEntity Target,Rnd(-40,40),0,Rnd(-40,40)

Нам нужно заменить,

Repeat
inter=False
PositionEntity Target,Rnd(-40,40),0,Rnd(-40,40)
For i=0 To 29
If MeshesIntersect(Target, Walls(i)) inter=True
Next
Until inter=False


Объясняю как это происходит. Для начала новая команда – MeshesIntersect(объект1,объект2) возвращает True (Истина), если объект1 пересекается с объектом2, так как здесь применяется метод проверки полигон к полигону, она довольно медленная(зато точная), но для нашей игры как раз. Дальше – как идёт цикл. Сначала мы задаём переменной inter значение False, затем ставим в случайное место цилиндр, и затем идёт цикл из 29 проверок, и если цилиндр пересекается хотя бы с одним из кубиков, значение переменной inter становится равным True (Истина). Ну и дальше проверяем – если inter так и осталась False (Ложь) идём дальше, если нет – возвращаемся и проводим все операции заново. Ну, вот вроде все самые бросающиеся в глаза баги убрали – теперь это довольно играбельный движок.

А вот всё что у нас есть на данный момент:

SeedRnd MilliSecs()
Graphics3D 640,480,16,1
SetBuffer BackBuffer()
Const TypePlayer=1,TypeWalls=2,TypeTarget=3
Global Speed#=.1,Score=0
Global Player=CreateSphere()
Plac=CreateCone(8)
RotateMesh Plac,-90,0,0
ScaleMesh Plac,1,1,1.2
PositionMesh Plac,0,0,-1.5
AddMesh Plac,Player
EntityType Player, TypePlayer
FreeEntity Plac
Dim Walls(29)
For i=0 To 29
Walls(i)=CreateCube()
Repeat
PositionEntity Walls(i),Rnd(-40,40),0,Rnd(-40,40)
Until Abs(EntityX(Walls(i)))>10 Or Abs(EntityZ(Walls(i)))>10
EntityType Walls(i), TypeWalls
Next
Target=CreateCylinder()
PositionEntity Target,Rnd(-40,40),0,Rnd(-40,40)
EntityType Target,TypeTarget
cam=CreateCamera()
PositionEntity cam,0,60,0
TurnEntity cam,90,0,0
lit=CreateLight()
TurnEntity lit,70,70,0
Collisions TypePlayer,TypeWalls,2,2
Collisions TypePlayer,TypeTarget,2,2
Repeat
MoveEntity Player,0,0,Speed#
If KeyDown(203) TurnEntity Player,0,3,0
If KeyDown(205) TurnEntity Player,0,-3,0
If EntityCollided (Player,TypeTarget)
Repeat
inter=False
PositionEntity Target,Rnd(-40,40),0,Rnd(-40,40)
For i=0 To 29
If MeshesIntersect(Target, Walls(i)) inter=True
Next
Until inter=False
UpdateWorld
Score=Score+1
Speed=Speed+.01
EndIf
If EntityCollided (Player,TypeWalls) End
UpdateWorld
RenderWorld
Color 255,215,0
Text 320,10,"Score : "+Score,True,True
Flip
Until KeyHit(1)
End
Категория: Учебники по Blitz3d | Добавил: blitz3d-portal (08.Декабрь.04) E W
Просмотров: 2231 | Рейтинг: 5.0/1
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]