forum.boolean.name

forum.boolean.name (http://forum.boolean.name/index.php)
-   FAQ (http://forum.boolean.name/forumdisplay.php?f=15)
-   -   Создаём свой FPS (first person shooter) (http://forum.boolean.name/showthread.php?t=15)

impersonalis 04.09.2005 06:02

page # 0
Всем привет!
По опыту знаю: большинство хочет сразу после просмотра сэмплов сесть и написать FPS. И не просто какой-нибудь: УБЕЙ_ИХ_ВСЕХ_2010, а что-нибудь революционно-инновационное – с крутейшей графикой, умнейшим ИИ, и возможностью сетевой игры на разных протоколах и в разных игровых режимах, с абсолютной нелинейностью сюжета, причём всё это будет требовать смехотворно мало ресурсов. Если Вы именно такой пользователь THEN goto основы. Так как начинать с подобного рода программ – отбивать себе всякое желание программить на Блитце – попишите пока тетрисы, змейки и проч.

начало статьи (с иллюстрациями, безвозвратно канувшими в Лету, благодаря очередному переезду):
http://blitzetcetera.org/index.php/С...Person_Shooter

impersonalis 04.09.2005 06:03

подготовка
Итак… FPS… конечно же 3D! Запускаем компилятор и смело вбиваем
Код:

Graphics3D 800,600,32
SetBuffer BackBuffer()

чуть ниже организовываем цикл, это будет главный цикл программы MAIN LOOP. Оформляйте его любыми операторами цикла, я, лично, сделаю так:
Код:

While Not KeyHit(1)=1
Wend
end

тут всё должно быть ясно как день:
цикл крутится пока Не ПрерываниеОтКнопки (Escape)
команда END в данном случае – чистый формализм: после инструкции WEND у нас в коде ничего нет, таким образом, после выхода из цикла программа и так завершится. Но всё же я настоятельно рекомендую не пренебрегать подобными формальностями!
Уже можно давить F5 – и любоваться :) … космосом… без звёзд…
Теперь займёмся камерой… даже - лучше сразу игроком ;)
Опять же: я настоятельно рекомендую заключать подобные знаковые участки кода в тела функций. Поэтому где-нибудь повыше напишем:
Код:

Function create_user()
End Function

Если Вы затрудняетесь определить: «где же это самое то место» - в принципе Блитцу всё равно, но я настоятельно рекомендую объявлять все функции перед главным циклом (а так как это не С++, то и писать тела функций там же), а поскольку в функции будут использоваться команды работы с 3d-объектами объявить их надо после строчек
Код:

Graphics3D 800,600,32
SetBuffer BackBuffer()

Надеюсь, с местом определились.
Теперь передаваемые в функцию аргументы… Думаю, удобно будет задавать ими место появления игрока. С другой стороны, практически всегда у нас он появляется в 0,h#,0 – в центре карты на небольшом расстоянии от земли, поэтому пишем следующее:
Код:

Function create_user(x#=0,y#=1,z#=0)
End Function

Теперь подумаем о физической модели игрока… В масштабах этого манула, позволительно допустить: игрока никто не видит, и камера как в FPS. Значит не стоит отвлекаться на фактический внешний вид игрока. У нас должна быть точка опоры и над ней (на некотором расстоянии) камера. А это, имхо, самый простой и надёжно работающий способ решения:
Код:

user=CreateSphere()
camera=CreateCamera(user)
PositionEntity camera,0,1,-0.5

PositionEntity user,x#,y#,z#

набираем мы в функции. Сам пользователь – это будет сфера (та самая точка) к которой прикреплена камера : в координатах 0,1,-0.5 относительно сферы. Камера прикрепляется сразу после создания, т.к. ей задан родитель (парент) в качестве аргумента. Затем происходит её позиционирование, и затем позиционирование самого игрока.
Запускаем… И снова темнота :sad
А как по-вашему: у нас есть описание действия «создать игрока» и пустой цикл.
Для начал надо вызвать функцию создания перед главным циклом:
Код:

create_user()
Затем: ну да – в памяти мы создали и сферку и камеру – а дальше то что? Как минимум надо это всё обработать как геометрические объекты и отрисовать в BackBuffer . Добавим в главном цикле команду:
Код:

RenderWorld()
Ещё одна неувязочка: на экране находится при двойной буферизации FrontBuffer, а менять содержимое буферов мы будем командой
Код:

Flip
После такой перекройки программа должна выглядеть примерно так:
Код:

Graphics3D 800,600,32
SetBuffer BackBuffer()

Function create_user(x#=0,y#=1,z#=0)
 user=CreateSphere()
camera=CreateCamera(user)
PositionEntity camera,0,1,-0.5

PositionEntity user,x#,y#,z#

End Function

create_user()
While Not KeyHit(1)=1
RenderWorld()
Flip
Wend
End

Снова запускаем – и опять темнота… :surpr Но на самом деле всё рисуется – сфера которую вы не видите, потому что «не можете наклонить голову» и… всё – больше в мире пока ничего нет.
Нам нужна земная твердь. И обязательно текстурка для неё. Ведь компьютер без дополнительных инструкций строит идеальные видео образы – абсолютно ровную поверхность земли (на самом деле мелкие неровности пока тоже ничего бы не дали – нет освещения – которое и позволяет видеть объёмную картину мира , без света всё будет казаться «плосокватым») так что вы просто увидите светлый прямоугольник занимающий часть экрана. Для примера – стандартная Блитц-текстура:





Опять же прибегая к упрощениям – земля будет в виде бесконечной ровной поверхности – создадим её плэйном:
Код:

terrain=CreatePlane()
затем загрузим текстурку
Код:

tertex=LoadTexture("terrain1.jpg")
и наложим её на поверхность
Код:

EntityTexture terrain,tertex
ну и для профилактики очистим память из под текстуры – она нам больше не понадобиться:
Код:

FreeTexture tertex
Ах да всё это должно быть набрано после инициализации графики но перед циклом.
И ещё немного подумав, объединим вызов ф-ции создания игрока и создание мира в одну функцию:
Код:

Graphics3D 800,600,32
SetBuffer BackBuffer()


Function create_user(x#=0,y#=1,z#=0)
 user=CreateSphere()
camera=CreateCamera(user)
PositionEntity camera,0,1,-0.5

PositionEntity user,x#,y#,z#

End Function

Function create_world()
terrain=CreatePlane()
tertex=LoadTexture("terrain1.jpg")
EntityTexture terrain,tertex
FreeTexture tertex
create_user()
End Function

create_world()

While Not KeyHit(1)=1
RenderWorld()
Flip
Wend
End

Та-дам! Запускаем, наслаждаемся :D

ПРОДОЛЖЕНИЕ СЛЕДУЕТ :super:

impersonalis 04.09.2005 06:03

кодим-кодим
Для практики: закомментируем команду текстурирования террайна и посмотрим на выполнение:
Код:

Graphics3D 800,600,32
SetBuffer BackBuffer()


Function create_user(x#=0,y#=1,z#=0)
 user=CreateSphere()
camera=CreateCamera(user)
PositionEntity camera,0,1,-0.5

PositionEntity user,x#,y#,z#

End Function

Function create_world()
terrain=CreatePlane()
tertex=LoadTexture("terrain1.jpg")
;EntityTexture terrain,tertex
FreeTexture tertex
create_user()
End Function

create_world()

While Not KeyHit(1)=1
RenderWorld()
Flip
Wend
End

Как я и обещал, видим светлый прямоугольник отхапавший часть экрана. :grins Раскомментируем строчку обратно.
Кстати о комментариях: не стоит ими пренебрегать но и объяснять всё подряд не стоит. :o
Код:

;инициализация графики
Graphics3D 800,600,32
SetBuffer BackBuffer()

;создание игрока
Function create_user(x#=0,y#=1,z#=0)
 user=CreateSphere()
camera=CreateCamera(user)
PositionEntity camera,0,1,-0.5

PositionEntity user,x#,y#,z#

End Function

;создание игрового мира
Function create_world()
terrain=CreatePlane()
tertex=LoadTexture("terrain1.jpg")
EntityTexture terrain,tertex
FreeTexture tertex
create_user()
End Function
;функции закончились :)
;--------------------------------
create_world()

;MAIN LOOP
While Not KeyHit(1)=1
RenderWorld()
Flip
Wend
;----------------------------
End

Надеюсь, вы уже обучили Блитц понимать русский язык? ;) Только по секрету:
http://community.boolean.name/index.php?act=ST&f=4&t=25
Не стоит так же пренебрегать грамотным форматированием кода:
Код:

;инициализация графики
Graphics3D 800,600,32
SetBuffer BackBuffer()

;создание игрока
Function create_user(x#=0,y#=1,z#=0)
        user=CreateSphere()
        camera=CreateCamera(user)
        PositionEntity camera,0,1,-0.5
       
        PositionEntity user,x#,y#,z#
       
End Function

;создание игрового мира
Function create_world()
        terrain=CreatePlane()
        tertex=LoadTexture("terrain1.jpg")
        EntityTexture terrain,tertex
        FreeTexture tertex
        create_user()
End Function
;функции закончились :)
;--------------------------------
create_world()

;MAIN LOOP
While Not KeyHit(1)=1
        RenderWorld()
        Flip
Wend
;----------------------------
End

Вот…

impersonalis 04.09.2005 06:06

Озираемся и бегаем
Так-с-с : на поверхность посмотрели – пора двигаться дальше. Будем учить игрока двигаться:
Код:

;обновление игрока
Function update_user()
End Function

Так получается, что к глазам игрока (камере) и его ногам (сфере) будут обращаться вне функции создания игрока. А значит надо сделать хендлы камеры и сферы глобальными. Для этого подготовим для их хранения глобальные переменные:
Код:

Global user
Global camera

Теперь это уже не локальные переменные функции create_user, а глобальные всей программы.
Игрока мы будем обновлять на каждой итерации главного цикла, поэтому сразу допишем вызов соответствующей функции:
Код:

;инициализация графики
Graphics3D 800,600,32
SetBuffer BackBuffer()

Global user
Global camera

;создание игрока
Function create_user(x#=0,y#=1,z#=0)
        user=CreateSphere()
        camera=CreateCamera(user)
        PositionEntity camera,0,1,-0.5
       
        PositionEntity user,x#,y#,z#
       
End Function

;обновление игрока
Function update_user()
End Function

;создание игрового мира
Function create_world()
        terrain=CreatePlane()
        tertex=LoadTexture("terrain1.jpg")
        EntityTexture terrain,tertex
        FreeTexture tertex
        create_user()
End Function
;функции закончились :)
;--------------------------------
create_world()

;MAIN LOOP
While Not KeyHit(1)=1
        update_user()
        RenderWorld()
        Flip
Wend
;----------------------------
End

Так… перейдём к программированию движений: для удобства объявим в нашей функции переменную, отвечающую за смещения по осям (никто не мешает вам сделать разную скорость для движения вперёд, назад и шагов влево-вправо)
Код:

V#=0.5
и кодируем движения нашей сферки (напомню: на ней укреплена камера)
Код:

        If KeyDown(203)=1 *Then MoveEntity user,-V#,0,0
        If KeyDown(205)=1 *Then MoveEntity user,V#,0,0
        If KeyDown(200)=1 *Then MoveEntity user,0,0,+V#
        If KeyDown(208)=1 *Then MoveEntity user,0,0,-V#

Запустим… результат, конечно зависит от производительности вашей машины, но, думаю, он не будет сильно разнится: при движении земля жутко мельтишит под ногами, так что невозможно даже оценить – в какую сторону мы сейчас двигаемся. Для устранения этого надо или понизить коэффициент скорости, или (что мы и сделаем ) растянуть текстурку:
Код:

        tertex=LoadTexture("terrain1.jpg")
        ScaleTexture tertex,10,10;вот тута новое
        EntityTexture terrain,tertex

мир по-прежнему имеет весьма сомнительный вид, но уже лучше, чем было :grins
Теперь займёмся обзорами местности.
Будем вращать камеру вверх-вниз в зависимости от смещения мыши по Y-оси и влево-вправо от смещения по X-оси.
Угу: как же ну смотрю я вправо… ещё вправо… опа: курсор упёрся в правый край экрана и всё :sungl . Значит после оценки изменений мы должны вернуть мышь на исходную – самое логичное, в центр экрана. Получить текущий размер экрана в пикселях по X и по Y можно функциями
Код:

GraphicsWidth()
GraphicsHeight()

соответственно. Т.е. задвинуть мышь в центр экрана:
Код:

MoveMouse GraphicsWidth()*0.5,GraphicsHeight()*0.5
А снять перемещение мыши – измерив её скорость по осям:
Код:

MouseYSpeed()
MouseXSpeed()

Но вот ещё загвоздка: если мы будем крутить только камеру, то получается: мы развернули камеру влево и нажали кнопку ВПЕРЁД, но наша опора то как стояла, так и стоит неразвёрнутой – в результате смотрим вбок и прём вперёд, а надо бы идти, туда куда смотрим. С другой стороны вращать только основу мы не можем по схожей причине: Уставились мы в небо и нажали ВПЕРЁД и наша основа полетела тоже в небо (даже если мы включим гравитацию, эффект останется – модуль скорости будет равен проекции вектора скорости на поверхность т.е. если мы задерём голову вверх – cos(90)=0 и мы не сможем двигаться).
Код:

TurnEntity camera,MouseYSpeed(),0,0
TurnEntity user,0,-MouseXSpeed(),0

т.е. после доработок:
Код:

Function update_user()

        V#=0.5
       
        TurnEntity camera,MouseYSpeed(),0,0
        TurnEntity user,0,-MouseXSpeed(),0
        If KeyDown(203)=1 *Then MoveEntity user,-V#,0,0
        If KeyDown(205)=1 *Then MoveEntity user,V#,0,0
        If KeyDown(200)=1 *Then MoveEntity user,0,0,+V#
        If KeyDown(208)=1 *Then MoveEntity user,0,0,-V#
        MoveMouse GraphicsWidth()*0.5,GraphicsHeight()*0.5


End Function

Ндя… :o неприятный осадок всё же остался: как это можно так задрать голову, что увидеть происходящее за спиной и ниже и сделать 360 градусный проворт головы?!
Модифим – теперь камера не будет превышать указанный нами угол вверх и вниз:
Код:

Function update_user()

        V#=0.5
        u#=70;предельный угол
        TurnEntity camera,MouseYSpeed(),0,0
        TurnEntity user,0,-MouseXSpeed(),0
        If KeyDown(203)=1 *Then MoveEntity user,-V#,0,0
        If KeyDown(205)=1 *Then MoveEntity user,V#,0,0
        If KeyDown(200)=1 *Then MoveEntity user,0,0,+V#
        If KeyDown(208)=1 *Then MoveEntity user,0,0,-V#
        MoveMouse GraphicsWidth()*0.5,GraphicsHeight()*0.5
        If Abs(EntityPitch#(camera))>u# RotateEntity camera,u#*Sgn(EntityPitch#(camera)),0,0

End Function

ABS-модуль
EntityPitch# ( entity[,global] ) – угол вращения в плоскости Y0Z, т.е. вокруг оси X
SGN – функция-знак
Если что не понятно - ставим курсор на функцию и кликаем 2 раза F1. ;)
Код:

;инициализация графики
Graphics3D 800,600,32
SetBuffer BackBuffer()

Global user
Global camera

;создание игрока
Function create_user(x#=0,y#=1,z#=0)
        user=CreateSphere()
        camera=CreateCamera(user)
        PositionEntity camera,0,1,-0.5
       
        PositionEntity user,x#,y#,z#
       
End Function

;обновление игрока
Function update_user()

        V#=0.5
        u#=70;предельный угол
        TurnEntity camera,MouseYSpeed(),0,0
        TurnEntity user,0,-MouseXSpeed(),0
        If KeyDown(203)=1 *Then MoveEntity user,-V#,0,0
        If KeyDown(205)=1 *Then MoveEntity user,V#,0,0
        If KeyDown(200)=1 *Then MoveEntity user,0,0,+V#
        If KeyDown(208)=1 *Then MoveEntity user,0,0,-V#
        MoveMouse GraphicsWidth()*0.5,GraphicsHeight()*0.5
        If Abs(EntityPitch#(camera))>u# RotateEntity camera,u#*Sgn(EntityPitch#(camera)),0,0

End Function

;создание игрового мира
Function create_world()
        terrain=CreatePlane()
        tertex=LoadTexture("terrain1.jpg")
        ScaleTexture tertex,10,10
        EntityTexture terrain,tertex
        FreeTexture tertex
        create_user()
End Function
;функции закончились :)
;--------------------------------
create_world()

;MAIN LOOP
While Not KeyHit(1)=1
        update_user()
        RenderWorld()
        Flip
Wend
;----------------------------
End

:glag:

impersonalis 04.09.2005 06:08

"честная" физика :)
Так-с приступим к добавлению гравитации. Объявим где-нибудь в глобале переменную ускорения

свободного падения
Код:

Const G#=1
Обратите внимание на идентификатор const (константа) и тип переменной (#-с плавающей
точкой).
Теперь надо реализовать контроль за отрывом от поверхности...
Это можно сделать 2 способами через просчёт столкновений (Collisions) и через "пики"

(Pick). С одной стороны, так как Collisions обрабатывается дольше, сложнее в коде... и

самое главное так или иначе автоматически производит response - один из 3-х методов

обработки столкновения:
1: stop
2: slide1 - full sliding collision
3: slide2 - prevent entities from sliding down slopes
С другой стороны обрабатывать это будет проще... впал в раздумья... короче делаем через

collision.
Добавим в глобал константы типов
Код:

Const USERT=1;for user
Const TERRT=2;for terrain

Переходим в Create_user() и делаем следующее.
Код:

EntityType user,USERT
Переходим в Create_world() и делаем следующее.
Код:

EntityType terrain,TERRT
и после вызова create_user()
Код:

Collisions USERT,TERRT,2,3
в главном цикле допишем
Код:

UpdateWorld()
и наконец в update_user() нацарапем
Код:

;физика
        TranslateEntity user,0,-G#,0

/сразу предупреждаю физиков: это не окончательный вараинт кода/
И мы смело двигаем пользователя вниз ф-цией TranslateEntity.
TranslateEntity отличается от MoveEntity безразличем к пространственной ориентации самой

модели. Пример: если объект перевернуть "на голову", то команда move вниз приведёт к

движению этого объекта вверх, Translate же будет двигать объект вниз.
Перейдём в create_user() и подправим кое-что:
Код:

Function create_user(x#=0,y#=10,z#=0)
Код:

PositionEntity camera,0,2,-0.5
Можно запускать! В начале текста я немного занизил высоту камеры относительно "ног", что

сейчас и исправил. Теперь, между прочим, стала заметна сфера. Вернёмся к ф-ции создания:
Код:

        user=CreateSphere()
        EntityAlpha user,0

Этим мы задали нулевую "альфу" для сферы - т.е. сделали её полностью прозрачной.

Термины "полностью прозрачный" и "невидимый" здесь не одно и то же. Если первый сводится к
изменению альфы (и объект просто не видим для камер), то второй реализуется командой
HideEntity (и объект убирается из процедуры рендера движка). Но об этом как-нибудь в другой раз.

Как очевидно, код:
Код:

        TranslateEntity user,0,-G#,0
не совсем то, что нам нужно. Хотя вблизи земли разницы никакой нет (допустимые

приближения). Вообще, здесь гравитация (вернее её подобие) нужна не столько для симуляции реалистичной физики, сколько для "прижимания" игрока к земле.
Код:

;инициализация графики
Graphics3D 800,600,32
SetBuffer BackBuffer()

Global user
Global camera

Const G#=1

Const USERT=1;for user
Const TERRT=2;for terrain

;создание игрока
Function create_user(x#=0,y#=10,z#=0)
        user=CreateSphere()
        EntityAlpha user,0
        camera=CreateCamera(user)
        PositionEntity camera,0,2,-0.5
       
        PositionEntity user,x#,y#,z#
        EntityType user,USERT

End Function

;обновление игрока
Function update_user()

        V#=0.5
        u#=70;предельный угол
        TurnEntity camera,MouseYSpeed(),0,0
        TurnEntity user,0,-MouseXSpeed(),0
        If KeyDown(203)=1 Then MoveEntity user,-V#,0,0
        If KeyDown(205)=1 Then MoveEntity user,V#,0,0
        If KeyDown(200)=1 Then MoveEntity user,0,0,+V#
        If KeyDown(208)=1 Then MoveEntity user,0,0,-V#
        MoveMouse GraphicsWidth()*0.5,GraphicsHeight()*0.5
        If Abs(EntityPitch#(camera))>u# RotateEntity camera,u#*Sgn(EntityPitch#(camera)),0,0
;физика
        TranslateEntity user,0,-G#,0
End Function

;создание игрового мира
Function create_world()
        terrain=CreatePlane()
        tertex=LoadTexture("terrain1.jpg")
        ScaleTexture tertex,10,10
        EntityTexture terrain,tertex
        FreeTexture tertex
        EntityType terrain,TERRT
        create_user()
        Collisions USERT,TERRT,2,3
End Function
;функции закончились
;--------------------------------
create_world()

;MAIN LOOP
While Not KeyHit(1)=1
        update_user()
        UpdateWorld()
        RenderWorld()
        Flip
Wend
;----------------------------
End


impersonalis 04.09.2005 06:09

B) Конечно, ничто кроме фантазии программиста не ограничивает возможности языка - так что, никто не мешает Вам, немного поразмыслив, реализовать настоящую модель гравитации.
B) Обратите внимание: я стараюсь заменять конструкции типа x/2 на x*0.5 - это связано со спецификой обработки этих мат.операций процессором (спросите у SubZer0)

impersonalis 04.09.2005 06:16

Прицел
Тихо - не ржите: :rolleyes:



Тыкс загрузим картинку прицела в глобале
Код:

Global pricel=LoadImage("pricel.bmp")
назначаем начало координат картинки в её центре
Код:

MidHandle pricel
А это
Код:

DrawImage pricel,MouseX(),MouseY()
нужно вписть в главном цикле... вот не скажу где
Если вы использовали мой прицел, то увидите, что отображается он несовсем как хотелось бы.
Установим Mask-цвет - белый:
Код:

Global pricel=LoadImage("pricel.bmp")
MidHandle pricel
MaskImage pricel,255,255,255

:)

impersonalis 04.09.2005 06:21

Так: немного подправим код для удобства:
внесём прицел в функцию создания перса, м немного морфируем физ. модель игрока. Теперь уже понятно почему и когда мы крутим камеру и основание. И теперь для удобства просчёта коллизий камеру поместим в центр прозрачной сферы.
И ещё
Код:

CameraRange camera,0.1,100
установит минимальный и максимальный радиусы рендеринга
Код:

;инициализация графики
Graphics3D 800,600,32
SetBuffer BackBuffer()

Global user
Global camera

Const G#=1

Const USERT=1;for user
Const TERRT=2;for terrain

Global pricel=LoadImage("pricel.bmp")


;создание игрока
Function create_user(x#=0,y#=10,z#=0)

        MidHandle pricel
        MaskImage pricel,255,255,255
       
        user=CreateSphere()
        k#=3
        ScaleEntity user,k#,k#,k#
        EntityRadius user,k#
        camera=CreateCamera(user)
        CameraRange camera,0.1,100
        PositionEntity user,x#,y#,z#
        EntityType user,USERT

End Function

;обновление игрока
Function update_user()

        V#=0.5
        u#=70;предельный угол
        TurnEntity camera,MouseYSpeed(),0,0
        TurnEntity user,0,-MouseXSpeed(),0
        If KeyDown(203)=1 Then MoveEntity user,-V#,0,0
        If KeyDown(205)=1 Then MoveEntity user,V#,0,0
        If KeyDown(200)=1 Then MoveEntity user,0,0,+V#
        If KeyDown(208)=1 Then MoveEntity user,0,0,-V#
        MoveMouse GraphicsWidth()*0.5,GraphicsHeight()*0.5
        If Abs(EntityPitch#(camera))>u# RotateEntity camera,u#*Sgn(EntityPitch#(camera)),0,0
;физика
        TranslateEntity user,0,-G#,0
End Function

;создание игрового мира
Function create_world()
        terrain=CreatePlane()
        tertex=LoadTexture("terrain1.jpg")
        ScaleTexture tertex,10,10
        EntityTexture terrain,tertex
        FreeTexture tertex
        EntityType terrain,TERRT
        create_user()
        Collisions USERT,TERRT,2,3
End Function
;функции закончились
;--------------------------------
create_world()

;MAIN LOOP
While Not KeyHit(1)=1
        update_user()
        UpdateWorld()
        RenderWorld()
        DrawImage pricel,MouseX(),MouseY()
        Flip
Wend
;----------------------------
End


impersonalis 04.09.2005 06:26

Выстрелы

Код:

Global shot_sprite=LoadSprite("sprite.bmp")
это будет спрайт выстрела.
В создании мира добавим
Код:

HideEntity shot_sprite
скрыв таким образом загруженный спрайт.
Создадим функцию порождения выстрела
Код:

Function create_Shot(x#,y#,z#,pitch#,yaw#,roll#)
 
End Function

6 аргументов описывают вектор выстрела: з координаты и углы вращения по осям
Создадим тип выстрела:
Код:

Type shot
        Field entity
End Type

Далее...
Код:

Function create_Shot(x#,y#,z#,pitch#,yaw#,roll#)
        s.shot=New shot
        s\entity=CopyEntity(shot_sprite)
        PositionEntity s\entity,x#,y#,z#,1
        RotateEntity s\entity,pitch#,yaw#,roll#,1
        Return True
End Function

В первой строке при помощи опереатора NEW создаём новый элемент типа shot.
Теперь через S мы можем обратиться к любому полю данного элемента типа shot.
s\entity - обращаемся к полю entity вновь созданного элемента. Сохраняем туда handle копии спрайта. Далее позиционируем вновь созданный (скопированный) спрайт и разворачиваем его. Последний аргумент 1 (true) означает, что действия будут выполнятся относительно мировых координат и осей.
Код:

Function update_shot()
        vs#=1
        For a.shot=Each shot
 MoveEntity a\entity,0,0,vs#
        Next
End Function

For a.shot=Each shot - на первый взгляд, заковыпистая конструкция. Такой цикл будет помещать по очереди в переменную а хендлы всех элементов указанного типа (в данном случае shot). Таким образом данный цикл продвинет все спрайты выстрелов на vs# вдоль оси Z.
Добавляем update_shot() в главный цикл.
Добавим строку
Код:

If MouseHit(1) create_shot(EntityX(user),EntityY(user),EntityZ(user),EntityPitch(camera),EntityYaw(user),0)
В ф-цию обновления игрока.
Уже можно посмотреть в действии:


impersonalis 04.09.2005 06:28

Собственно, текстура спрайта (спрайт):


impersonalis 04.09.2005 06:29

вот так будут улетать вдаль спрайты. Слово далеко относится к неопределённой логике. Ограничим расстояние полёта.
Код:

        Field dist#
добавим ещё одно поле в тип. И обработаем его:
Код:

Function update_shot()
        vs#=1
        max_dist#=1000
        For a.shot=Each shot
 MoveEntity a\entity,0,0,vs#
 a\dist#=a\dist#+vs#
 If a\dist#>max_dist#
        FreeEntity a\entity
        Delete a
 EndIf
        Next
End Function

будем накапливать всоответствующем поле пройденное "снарядом" расстояние, и если оно больше максимального, то: очистим память, занимаемую спрайтом, затем удалим этот элемент типа. В том, что всё работает как надо Вы можите убедиться поэкспериментировав со значением max_dist# и сделав кучу выстрелов (после исчезновения выстрелов фпс возвращается в стабильное состояние).
Код:

Const SHOTT=3;for shot
в глобал.
Код:

Collisions SHOTT,TERRT,2,1
в создание мира
Код:

EntityType s\entity,SHOTT
в создании выстрела.
Если запустить программу теперь, то можно наблюдать забавный эффект - поверхность стала уловителем выстрелов:



Обновим условие в цикле обработки выстрелов
Код:

If a\dist#>max_dist#
        FreeEntity a\entity
        Delete a
 ElseIf EntityCollided(a\entity,TERRT) < > 0
        FreeEntity a\entity
        Delete a
 EndIf

Теперь при столкновении текущего спрайта с объектом, у которого коллизионный тип (а не "обычный" тип - как shot) TERRT, происходит удаление выстрела.

Код:

;инициализация графики
Graphics3D 800,600,32
SetBuffer BackBuffer()

Global user
Global camera

Const G#=1

Const USERT=1;for user
Const TERRT=2;for terrain
Const SHOTT=3;for shot

Global pricel=LoadImage("pricel.bmp")
Global shot_sprite=LoadSprite("sprite.bmp")

Type shot
        Field entity
        Field dist#
End Type

;создание игрока
Function create_user(x#=0,y#=10,z#=0)

        MidHandle pricel
        MaskImage pricel,255,255,255
       
        user=CreateSphere()
        k#=3
        ScaleEntity user,k#,k#,k#
        EntityRadius user,k#
        camera=CreateCamera(user)
        CameraRange camera,0.1,100
        PositionEntity user,x#,y#,z#
        EntityType user,USERT

End Function

;обновление игрока
Function update_user()

        V#=0.5
        u#=70;предельный угол
        TurnEntity camera,MouseYSpeed(),0,0
        TurnEntity user,0,-MouseXSpeed(),0
        If KeyDown(203)=1 Then MoveEntity user,-V#,0,0
        If KeyDown(205)=1 Then MoveEntity user,V#,0,0
        If KeyDown(200)=1 Then MoveEntity user,0,0,+V#
        If KeyDown(208)=1 Then MoveEntity user,0,0,-V#
        MoveMouse GraphicsWidth()*0.5,GraphicsHeight()*0.5
        If Abs(EntityPitch#(camera))>u# RotateEntity camera,u#*Sgn(EntityPitch#(camera)),0,0
       
        If MouseHit(1) create_shot(EntityX(user),EntityY(user),EntityZ(user),EntityPitch(camera),EntityYaw(user),0)
;физика
        TranslateEntity user,0,-G#,0
End Function

Function create_Shot(x#,y#,z#,pitch#,yaw#,roll#)
        s.shot=New shot
        s\entity=CopyEntity(shot_sprite)
        EntityType s\entity,SHOTT
        PositionEntity s\entity,x#,y#,z#,1
        RotateEntity s\entity,pitch#,yaw#,roll#,1
        Return True
End Function

Function update_shot()
        vs#=1
        max_dist#=1000
        For a.shot=Each shot
 MoveEntity a\entity,0,0,vs#
 a\dist#=a\dist#+vs#
 If a\dist#>max_dist#
        FreeEntity a\entity
        Delete a
 ElseIf EntityCollided(a\entity,TERRT) < > 0
        FreeEntity a\entity
        Delete a
 EndIf
        Next
End Function

;создание игрового мира
Function create_world()
        HideEntity shot_sprite
        terrain=CreatePlane()
        tertex=LoadTexture("terrain1.jpg")
        ScaleTexture tertex,10,10
        EntityTexture terrain,tertex
        FreeTexture tertex
        EntityType terrain,TERRT
        create_user()
        Collisions USERT,TERRT,2,3
        Collisions SHOTT,TERRT,2,1
End Function
;функции закончились
;--------------------------------
create_world()

;MAIN LOOP
While Not KeyHit(1)=1
        update_user()
        update_shot()
        UpdateWorld()
        RenderWorld()
        DrawImage pricel,MouseX(),MouseY()
        Flip
Wend
;----------------------------
End


impersonalis 04.09.2005 06:30

Кодим-кодим
B) На ряду с описанными в хелпе функциями, для работы с типами, есть и недокументированные команды Object и Handle, которые облегчают программирование доступа к конкретному элементу типа. Об этом в другой раз. ;)

impersonalis 04.09.2005 06:32

коварные боты

Итак, оружие есть - надо и поохотиться )
Создадим тип для ботов
Код:

Type bot
        Field entity
End Type

Далее как обычно:
Код:

Function create_bot(x#,y#,z#)
        b.bot=New bot
        b\entity=CreateSphere()
        PositionEntity b\entity,x#,y#,z#
        Return True
End Function

Для простоты визуализация бота будет сведена отображению на экране сферы.
Код:

Function update_bot()
        v#=0.3
        For a.bot=Each bot
 PointEntity a\entity,user
 MoveEntity a\entity,0,0,v#
        Next
End Function

Тут всё ясно как день. За исключением, быть может, команды PointEntity entity,target[,roll#] . Так как основы создания искусственного
интеллекта на базе нейроподобных сетей здесь рассматривать не имеет смысла. А ур-ий, по которым бот, так или иначе, будет за вами двигаться,
можно придумать достаточно много, то ограничимся результатом работы самой простой (в исполнении Блитца) команды. После её выполнения объект
entity будет развёрнут "лицом" (т.е. в сторону положительного направления Z-оси) к объекту target. Это допустимо, так как в нашем случае
действие происходит на плоскости.
Не забываем про главный цикл программы ;)
Чуть не забыл (куда это добавлять - должно быть уже очевидно, если нет - в конце я прилагаю исходный код всей программы):
Код:

Const BOTT=4;for bot
Код:

EntityType b\entity,BOTT
Код:

        Collisions BOTT,TERRT,2,3
        Collisions BOTT,USERT,2,3
        Collisions BOTT,BOTT,2,3
       
        Collisions USERT,BOTT,2,3
       
        Collisions SHOTT,BOTT,2,1

а в функции создания мира
Код:

create_bot(10,2,50)
Можно запустить и посмотреть. Как видно, на некотором расстоянии от игрока, бот остановится и начнёт совершать непонятные движения - это он
наткнулся на прозрачную сферу, которой окружён игрок.
Перепишем ф-цию обновления выстрелов:
Код:

Function update_shot()
        vs#=1
        max_dist#=1000
        For a.shot=Each shot
 MoveEntity a\entity,0,0,vs#
 a\dist#=a\dist#+vs#
 bot_h=EntityCollided(a\entity,BOTT)
 If a\dist#>max_dist#
        FreeEntity a\entity
        Delete a
 ElseIf EntityCollided(a\entity,TERRT) < > 0
        FreeEntity a\entity
        Delete a
 ElseIf bot_h < > 0
        FreeEntity a\entity
        Delete a
        For q.bot=Each bot
  If q\entity=bot_h
          FreeEntity bot_h
          Delete q
          Exit
  EndIf
        Next
 EndIf
        Next
End Function

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

        FreeEntity bot_h
        Delete q
        create_bot(EntityX(user)+Rnd(-100,100),2,EntityZ(user)+Rnd(-100,100))
        Exit

Теперь за каждого убитого бота около игрока (в радиусе 100) будет "респиться" новый бот.
Причём сколько было ботов при загрузке - такое их кол-во и будет поддерживаться в игре.
Перепишем функцию создания мира:
Код:

        c_bot=Input("input Amount")
        For i=1 To c_bot
 create_bot(Rnd(-100,100),2,Rnd(-100,100))
        Next

При загрузке будет запрос на ввод кол-ва ботов.
Попробуйте указать 20 и ничего не делайте - вскоре боты заблокируют вас полностью.
Понаблюдайте за обработкой коллизий.

impersonalis 04.09.2005 06:33

B) Даже если Вы работаете с типами впервые, рассмотренного здесь вполне достаточно, чтобы реализовать "жизни" для ботов и зарезервировать поля для хранения промежуточных результатов работы ИИ.
:rolleyes:

impersonalis 04.09.2005 06:39

Для придания объёма (это надо было ещё вначале сделать) я добавил в create_world() создание и ротацию света:
Код:

        light=CreateLight()
        RotateEntity light,90,0,0

:ok:

Код:

;инициализация графики
Graphics3D 800,600,32
SetBuffer BackBuffer()

Global user
Global camera

Const G#=1

Const USERT=1;for user
Const TERRT=2;for terrain
Const SHOTT=3;for shot
Const BOTT=4;for bot

Global pricel=LoadImage("pricel.bmp")
Global shot_sprite=LoadSprite("sprite.bmp")

Type shot
        Field entity
        Field dist#
End Type

Type bot
        Field entity
End Type

;создание игрока
Function create_user(x#=0,y#=10,z#=0)

        MidHandle pricel
        MaskImage pricel,255,255,255
       
        user=CreateSphere()
        k#=3
        ScaleEntity user,k#,k#,k#
        EntityRadius user,k#
        camera=CreateCamera(user)
        CameraRange camera,0.1,100
        PositionEntity user,x#,y#,z#
        EntityType user,USERT

End Function

;обновление игрока
Function update_user()

        V#=0.5
        u#=70;предельный угол
        TurnEntity camera,MouseYSpeed(),0,0
        TurnEntity user,0,-MouseXSpeed(),0
        If KeyDown(203)=1 Then MoveEntity user,-V#,0,0
        If KeyDown(205)=1 Then MoveEntity user,V#,0,0
        If KeyDown(200)=1 Then MoveEntity user,0,0,+V#
        If KeyDown(208)=1 Then MoveEntity user,0,0,-V#
        MoveMouse GraphicsWidth()*0.5,GraphicsHeight()*0.5
        If Abs(EntityPitch#(camera))>u# RotateEntity camera,u#*Sgn(EntityPitch#(camera)),0,0
       
        If MouseHit(1) create_shot(EntityX(user),EntityY(user),EntityZ(user),EntityPitch(camera),EntityYaw(user),0)
;физика
        TranslateEntity user,0,-G#,0
End Function

Function create_Shot(x#,y#,z#,pitch#,yaw#,roll#)
        s.shot=New shot
        s\entity=CopyEntity(shot_sprite)
        EntityType s\entity,SHOTT
        PositionEntity s\entity,x#,y#,z#,1
        RotateEntity s\entity,pitch#,yaw#,roll#,1
        Return True
End Function

Function update_shot()
        vs#=1
        max_dist#=1000
        For a.shot=Each shot
 MoveEntity a\entity,0,0,vs#
 a\dist#=a\dist#+vs#
 bot_h=EntityCollided(a\entity,BOTT)
 If a\dist#>max_dist#
        FreeEntity a\entity
        Delete a
 ElseIf EntityCollided(a\entity,TERRT) < > 0
        FreeEntity a\entity
        Delete a
 ElseIf bot_h < > 0
        FreeEntity a\entity
        Delete a
        For q.bot=Each bot
  If q\entity=bot_h
          FreeEntity bot_h
          Delete q
          create_bot(EntityX(user)+Rnd(-100,100),2,EntityZ(user)+Rnd(-100,100))
          Exit
  EndIf
        Next
 EndIf
        Next
End Function

Function create_bot(x#,y#,z#)
        b.bot=New bot
        b\entity=CreateSphere()
        EntityType b\entity,BOTT
        PositionEntity b\entity,x#,y#,z#
        Return True
End Function

Function update_bot()
        v#=0.3
        For a.bot=Each bot
 PointEntity a\entity,user
 MoveEntity a\entity,0,0,v#
        Next
End Function

;создание игрового мира
Function create_world()
        light=CreateLight()
        RotateEntity light,90,0,0
        HideEntity shot_sprite
        terrain=CreatePlane()
        tertex=LoadTexture("terrain1.jpg")
        ScaleTexture tertex,10,10
        EntityTexture terrain,tertex
        FreeTexture tertex
        EntityType terrain,TERRT
        create_user()
       
        c_bot=Input("input Amount")
        For i=1 To c_bot
 create_bot(Rnd(-100,100),2,Rnd(-100,100))
        Next
       
        Collisions USERT,TERRT,2,3
        Collisions SHOTT,TERRT,2,1
       
        Collisions BOTT,TERRT,2,3
        Collisions BOTT,USERT,2,3
        Collisions BOTT,BOTT,2,3
       
        Collisions USERT,BOTT,2,3
       
        Collisions SHOTT,BOTT,2,1
End Function
;функции закончились
;--------------------------------
create_world()

;MAIN LOOP
While Not KeyHit(1)=1
        update_user()
        update_shot()
        update_bot()
        UpdateWorld()
        RenderWorld()
        DrawImage pricel,MouseX(),MouseY()
        Flip
Wend
;----------------------------
End

:glag:

impersonalis 04.09.2005 06:40

Баг-фикс
Я всё-таки почти человек, поэтому ничего странного, что забыл дописать ещё вот что:
Код:

Function update_bot()
        v#=0.3
        For a.bot=Each bot
 PointEntity a\entity,user
 MoveEntity a\entity,0,0,v#
        ;физика
 TranslateEntity user,0,-G#,0
        Next
End Function

теперь ботов будет притягивать вниз :)

impersonalis 04.09.2005 06:40

Чит-мод :)

Попробуйте включить в функцию обработки игрока строку
Код:

If MouseDown(2) create_shot(EntityX(user),EntityY(user),EntityZ(user),EntityPitch(camera),EntityYaw(user),0)
И стрелять, используя удержание правой кнопки мыши :)

impersonalis 04.09.2005 06:42

Релиз

В аттаче полностью рабочая версия описанной здесь программы. Со всеми ресурсами :grins


http://boolean.name/archive/code/Example%2...0FPS%20v1.0.rar

impersonalis 04.09.2005 06:44

Уход от полного перебора

Как и обещал, расскажу, как улучшить цикл обработки выстрелов.
В Блитце есть недокументированные команды Handle и Object. Благодаря команде Handle можно получить
"указатель" на объект типа (использование термина "указатель" весьма спорно для Блитца). Например:
Код:

P.Player=New Player
pHANDLE=Handle(P)

а затем, используя сохранённое значение (здесь в переменной pHANDLE) получить доступ к элементу типа:
Код:

P.PLAYER=Object.PLAYER(pHANDLE)
*******
Модифицируем функцию create_bot
Код:

Function create_bot(x#,y#,z#)
        b.bot=New bot
        b\entity=CreateSphere()
        EntityType b\entity,BOTT
        PositionEntity b\entity,x#,y#,z#
        NameEntity b\entity,Handle(b)
        Return True
End Function

Командой NameEntity присвоим объекту B\entity имя - в данном случае, совпадающее с хендлом элемента типа,
содержащего данный объект.
Теперь модифим кусок функции update_shot()
Код:

Function update_shot()
        vs#=1
        max_dist#=1000
        For a.shot=Each shot
 MoveEntity a\entity,0,0,vs#
 a\dist#=a\dist#+vs#
 bot_h=EntityCollided(a\entity,BOTT)
 If a\dist#>max_dist#
        FreeEntity a\entity
        Delete a
 ElseIf EntityCollided(a\entity,TERRT)<>0
        FreeEntity a\entity
        Delete a
 ElseIf bot_h<>0
        FreeEntity a\entity
        Delete a
        bhandle=EntityName(bot_h)
        bc.bot=Object.bot(bhandle)
        Delete bc
        FreeEntity bot_h
        create_bot(EntityX(user)+Rnd(-100,100),2,EntityZ(user)+Rnd(-100,100))
 EndIf
        Next
End Function

Здесь мы получаем указатель на элемент типа из имени бота (имени объекта, который его визуализирует - сферы)
Код:

bhandle=EntityName(bot_h)
далее удалям запись об этом боте (удаляем соответствующий элемент типа)
Код:

        bc.bot=Object.bot(bhandle)
        Delete bc

И, наконец, удаляем сам объект (используя его хендл)
Код:

FreeEntity bot_h
Таким образом, мы избавились от цикла и сохранили прежнюю функциональность, повысив при этом
производительность.

impersonalis 04.09.2005 06:46

Уход от привязки к FPS
Frames Per Second
В текущей версии программы события (а именно: обновление ботов, игрока, выстрелов) будут происходить с частотой вызова соответствующих

ф-ций. Т.е. (как видно из записи главного цикла) с частотой прорисовки - ФПС. А значит напрямую будут зависеть от производительности

системы.
Избавиться можно 2мя способами:
:?? 1) Производить обновление N раз в секунду - что приведёт к принудительному занижению высоких ФПС и скачкообразной анимации.
:?? 2) Рассчитывать переменные скорости, ускорения и проч. в соответствии с дельтой времени. Что сейчас и сделаем.

Так как в следующих версиях алгоритма или уже в этой ( при большом кол-ве ботов) время начала обработки первого и последнего ботов

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

переносимой, а так же плодят логические ошибки (из-за ошибок при вводе имени) - для каждого бота сделаем собственное время последнего

обновления.
Код:

Type bot
        Field entity
        Field time
End Type

Код:

Function update_bot()
        v#=0.03
        For a.bot=Each bot
        ;=
 If a\time=0 a\time=MilliSecs()
 new_time=MilliSecs()
 delta_t=new_time-a\time
 a\time=new_time
        ;=
 PointEntity a\entity,user
 MoveEntity a\entity,0,0,v#*delta_t
        ;физика
 TranslateEntity user,0,-G#*delta_t,0
        Next
End Function

Ёлки-палки :o :oops - только сейчас заметил, что боты притягиваются с коэффицентом 1 а не G
Переменная v# должна получиться универсальным значением для всех ПК (однако Вы можете откалибровать её по-точнее).
Проделаем операцию ухода... для выстрелов:
Код:

Type shot
        Field entity
        Field dist#
        Field time
End Type

Код:

Function update_shot()
        v#=0.1
        max_dist#=1000
        For a.shot=Each shot
        ;=
 If a\time=0 a\time=MilliSecs()
 new_time=MilliSecs()
 delta_t=new_time-a\time
 a\time=new_time
 vs#=v*delta_t
        ;=
 MoveEntity a\entity,0,0,vs#
 a\dist#=a\dist#+vs#
 bot_h=EntityCollided(a\entity,BOTT)
 If a\dist#>max_dist#
        FreeEntity a\entity
        Delete a
 ElseIf EntityCollided(a\entity,TERRT)0
        FreeEntity a\entity
        Delete a
 ElseIf bot_h0
        FreeEntity a\entity
        Delete a
        bhandle=EntityName(bot_h)
        bc.bot=Object.bot(bhandle)
        Delete bc
        FreeEntity bot_h
        create_bot(EntityX(user)+Rnd(-100,100),2,EntityZ(user)+Rnd(-100,100))
 EndIf
        Next
End Function

Обратите внимание здесь рассчитанная по дельте скорость используется не идиножды - сохраняем в отдельную переменную.
Теперь сам игрок.
Поскольку мультиплеером в игре и не пахнет - игрок реализован не типизированным объектом и он один. Заведём для его обновления глобальную

переменную:
Код:

Global user_time
Код:

Function update_user()
;=
        If user_time=0 user_time=MilliSecs()
        new_time=MilliSecs()
        delta_t=new_time-user_time
        user_time=new_time
;=
        V#=0.02*delta_t
        u#=70;предельный угол
        TurnEntity camera,MouseYSpeed(),0,0
        TurnEntity user,0,-MouseXSpeed(),0
        If KeyDown(203)=1 Then MoveEntity user,-V#,0,0
        If KeyDown(205)=1 Then MoveEntity user,V#,0,0
        If KeyDown(200)=1 Then MoveEntity user,0,0,+V#
        If KeyDown(208)=1 Then MoveEntity user,0,0,-V#
        MoveMouse GraphicsWidth()*0.5,GraphicsHeight()*0.5
        If Abs(EntityPitch#(camera))>u# RotateEntity camera,u#*Sgn(EntityPitch#(camera)),0,0
       
        If MouseHit(1) create_shot(EntityX(user),EntityY(user),EntityZ(user),EntityPitch(camera),EntityYaw(user),0)
        If MouseDown(2) create_shot(EntityX(user),EntityY(user),EntityZ(user),EntityPitch(camera),EntityYaw(user),0)
;физика
        TranslateEntity user,0,-G#*delta_t,0
End Function

Осталось откалибровать коэффициент гравитации.
Код:

Const G#=0.1
Пока поставил такое значение.

impersonalis 04.09.2005 06:47

Небесная сфера
Вопреки названию, будем создавать не сферу, а SkyBox -

коробку, так как для неё проще изготовить текстуру и

смотрится, имхо, она красивее.
Возьмём стандартную функцию создания из примеров блитца

(\Samples\Blitz 3D Samples\AGore\GrassDemo):
Код:

Function MakeSkyBox( file$ )

        m=CreateMesh()
;front face
        b=LoadBrush( file$+"_FR.bmp",49 )
        s=CreateSurface( m,b )
        AddVertex s,-1,+1,-1,0,0:AddVertex s,+1,+1,-1,1,0
        AddVertex s,+1,-1,-1,1,1:AddVertex s,-1,-1,-1,0,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3
        FreeBrush b
;right face
        b=LoadBrush( file$+"_LF.bmp",49 )
        s=CreateSurface( m,b )
        AddVertex s,+1,+1,-1,0,0:AddVertex s,+1,+1,+1,1,0
        AddVertex s,+1,-1,+1,1,1:AddVertex s,+1,-1,-1,0,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3
        FreeBrush b
;back face
        b=LoadBrush( file$+"_BK.bmp",49 )
        s=CreateSurface( m,b )
        AddVertex s,+1,+1,+1,0,0:AddVertex s,-1,+1,+1,1,0
        AddVertex s,-1,-1,+1,1,1:AddVertex s,+1,-1,+1,0,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3
        FreeBrush b
;left face
        b=LoadBrush( file$+"_RT.bmp",49 )
        s=CreateSurface( m,b )
        AddVertex s,-1,+1,+1,0,0:AddVertex s,-1,+1,-1,1,0
        AddVertex s,-1,-1,-1,1,1:AddVertex s,-1,-1,+1,0,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3
        FreeBrush b
;top face
        b=LoadBrush( file$+"_UP.bmp",49 )
        s=CreateSurface( m,b )
        AddVertex s,-1,+1,+1,0,1:AddVertex s,+1,+1,+1,0,0
        AddVertex s,+1,+1,-1,1,0:AddVertex s,-1,+1,-1,1,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3
        FreeBrush b

        ScaleMesh m,100,100,100
        FlipMesh m
        EntityFX m,9
        EntityOrder m,10
        Return m
       
End Function

и скопируем её в наш код. Вот, кстати, довод за

использование ф-ций: в любой момент вы можете использовать

свои наработки в других проектах.

Переместимся в функцию create_world()
Код:

        create_user()
       
        sky=MakeSkyBox("sky")
       
        c_bot=Input("input Amount")

и скопируем текстуры неба из папки с примером в папку нашеё

программы. Уже сейчас можно посмотреть на результат, правда,

сомнительный.
Из-за недостаточных размеров skybox`а небо ощутимо

квадратное:



Отскалим небо
Код:

ScaleEntity sky,150,150,150
После этого незамысловатого изменения небо исчезнет совсем =(
Вероятно оно за максимальным радиусом ренедера.
глянем в create_user - именно здесь мы обрезали расстония

рендера, правим:
Код:

CameraRange camera,0.1,10000
Проверяем - всё работает =)

impersonalis 04.09.2005 06:48

Релиз

В аттаче полностью рабочая версия (уже 2ая)описанной здесь программы. Со всеми ресурсами :grins

http://boolean.name/archive/code/Example%2...0FPS%20v2.0.rar

impersonalis 04.09.2005 06:49

Живучие боты
Бот, умирающий от одного залпа, - это, кончено весело, но не интересно. Добавим ботам шкалу жизни.
Итак, переходим в объявление типа и правим:
Код:

Type bot
        Field entity
        Field time
        Field Survivability
End Type

В создании бота припишем:
Код:

b\Survivability=100
Теперь надо растянуть смерть бота, а значит переходим в ф-цию update_shot()
Код:

ElseIf bot_h<>0
        FreeEntity a\entity
        Delete a
        bhandle=EntityName(bot_h)
        bc.bot=Object.bot(bhandle)
        bc\Survivability=bc\Survivability-16
        If bc\Survivability<=0
  Delete bc
  FreeEntity bot_h
  create_bot(EntityX(user)+Rnd(-100,100),2,EntityZ(user)+Rnd(-100,100))
        EndIf
 EndIf

Если есть столкновение пули с ботом, то удаляем пулю и её информационный элемент, получаем хендл элемента типа для бота, получаем доступ к элементу типа, вычитаем из значения живучести бота число. Если живучесть бота меньше или равна 0, то удаляем информацию о боте, удаляем тело бота, создаём нового бота.
Теперь равновесие сместилось в пользу ботов - за это понизим им скорость передвижения:
Код:

Function update_bot()
        v#=0.01


impersonalis 04.09.2005 06:50

Жизнь в коробке
Рано или поздно, надо чётко обазначить границы игрового мира. После добавления в игру SkyBox, это становится необходимо сделать.
Модифицируем ф-цию создания неба и сохраним её под новым именем (прототип тоже надо оставить в коде):
Код:

Function MakeBox()

        m=CreateMesh()
;front face
        s=CreateSurface( m )
        AddVertex s,-1,+1,-1,0,0:AddVertex s,+1,+1,-1,1,0
        AddVertex s,+1,-1,-1,1,1:AddVertex s,-1,-1,-1,0,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3
;right face
        s=CreateSurface( m )
        AddVertex s,+1,+1,-1,0,0:AddVertex s,+1,+1,+1,1,0
        AddVertex s,+1,-1,+1,1,1:AddVertex s,+1,-1,-1,0,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3
;back face
        s=CreateSurface( m )
        AddVertex s,+1,+1,+1,0,0:AddVertex s,-1,+1,+1,1,0
        AddVertex s,-1,-1,+1,1,1:AddVertex s,+1,-1,+1,0,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3
;left face
        s=CreateSurface( m )
        AddVertex s,-1,+1,+1,0,0:AddVertex s,-1,+1,-1,1,0
        AddVertex s,-1,-1,-1,1,1:AddVertex s,-1,-1,+1,0,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3
;top face
        s=CreateSurface( m)
        AddVertex s,-1,+1,+1,0,1:AddVertex s,+1,+1,+1,0,0
        AddVertex s,+1,+1,-1,1,0:AddVertex s,-1,+1,-1,1,1
        AddTriangle s,0,1,2:AddTriangle s,0,2,3

        ScaleMesh m,100,100,100
        FlipMesh m
        EntityFX m,9
        EntityOrder m,10
        Return m
       
End Function

Теперь функция возвращает хендл простой неоттекстурированной корбки. Зайдём в create_world():
Код:

border=MakeBox()
;ScaleEntity border,140,140,140
EntityAlpha border,.5

Запустим прогамму. Если некоторое время идти в одном направлении, можно увидеть коробку, ограничивающую уровень.

Теперь поставим этой коробке в соответствие тип для колизии
Код:

Const BORDERT=5;for box
Код:

EntityAlpha border,.5
EntityType border,BORDERT

Код:

Collisions USERT,BORDERT,2,1
Боты могут проходить сквозь эту стену - на случай если игрок будет в крайнем положении, в следствие чего бот создастся за стеной. Поэтому коллизию с типом ботов не ставим. Спрайтам выстрелов это тоже не нужно.
Теперь, если идти в одну сторону, в какой-то момент вы просто упрётесь в стену.
Ставим стене нулевую альфу и растягиваем её.
Код:

border=MakeBox()
ScaleEntity border,140,140,140
EntityAlpha border,0
EntityType border,BORDERT


impersonalis 04.09.2005 06:51

кодим-кодим

Что-то управление больно казуальным вышло. :unsure:
Перенастроим движение на W,S,A,D.
Функция update_user()

Код:

        If KeyDown(30)=1 Then MoveEntity user,-V#,0,0
        If KeyDown(32)=1 Then MoveEntity user,V#,0,0
        If KeyDown(17)=1 Then MoveEntity user,0,0,+V#
        If KeyDown(31)=1 Then MoveEntity user,0,0,-V#

:glag:

impersonalis 04.09.2005 06:52

Прыжки и реалистичность физики

давно пора было это сделать

Для нормальной реализации кинематики нам понадобится:
1)контроль за отрывом от земли и моментом соприкосновения с ней.
Код:

EntityPickMode terrain,2
теперь к земле можно применить операцию pick.
Код:

Global jump_bool
переменная под логику отрыва от поверхности.
Код:

;физика
jump_bool=True
pick_ent=LinePick(EntityX(user,1),EntityY(user,1),EntityZ(user,1),0,-3,0)
If pick_ent jump_bool=False

допишем в функции обновления игрока. И добавим
Код:

Text 10,10,"jump_bool="+jump_bool
Flip

в главном цикле.
Запустим.
И увидим, что программа работает неверно.
jump_bool=1, т.е. условие If pick_ent не срабатывает, т.е. LinePick возвращает 0, т.е. на расстоянии 3 ед. под игроком ничего "не цепляется" командой Pick =(
Вообще в этой точке будет как раз соприкосновение сферы игрока и земли, так что надо попробовать увеличить расстояние - методом калибровки подбираем значение=3.6
Код:

pick_ent=LinePick(EntityX(user,1),EntityY(user,1),EntityZ(user,1),0,-3.6,0)
Ориентировочное значение три я взял, исходя из ф-ции создания игрока:
Код:

user=CreateSphere()
k#=3
ScaleEntity user,k#,k#,k#

и если был бы внимательнее, посмотрел чуть ниже
Код:

EntityRadius user,k#
Радиус коллизи тоже равен 3, что как раз и объясняет возникшее затруднение. Наше расстояние должно быть немногим больше 3, как оказалось = 3.6 .
Теперь значение jump_bool характеризует состояние игрока - есть или нет сцепления.
2)время невзаимодействия с землёй.
перепишем обновление игрока ещё раз
Код:

;физика
       
        pick_ent=LinePick(EntityX(user,1),EntityY(user,1),EntityZ(user,1),0,-3.6,0)
        If pick_ent
 jump_bool=False
        Else
 jump_bool=True
        EndIf

время взаимодействия как-таковое нам нужно между обновлениями,а оно у нас уже есть.
3)скорости движения для их динамического изменения.
Код:

Global user_vy#
снова переписываем
Код:

        pick_ent=LinePick(EntityX(user,1),EntityY(user,1),EntityZ(user,1),0,-3.6,0)
        If pick_ent
 jump_bool=False
 user_vy#=0
        Else
 jump_bool=True
 user_vy#=user_vy#-G#*delta_t;v1=v2-a*t
        EndIf
        TranslateEntity user,0,user_vy#*delta_t,0

4)инициализация события "прыжок".
Код:

TranslateEntity user,0,user_vy#*delta_t,0
;;;
        If KeyHit(57) And jump_bool=False Then user_vy#=50

однако - не работает =(

после некоторых раздумий подправим физику:
Код:

        pick_ent=LinePick(EntityX(user,1),EntityY(user,1),EntityZ(user,1),0,-3.6,0)
       
        TranslateEntity user,0,user_vy#*delta_t,0,True
       
        If pick_ent
 jump_bool=False
 user_vy=0
 TranslateEntity user,0,-1,0,True
        Else
 jump_bool=True
 user_vy#=user_vy#-G#*delta_t;v1=v2-a*t
        EndIf

;;;
        If KeyHit(57) And jump_bool=False Then user_vy#=1

И гравитацию на 0.001
Однако теперь объект не смещается вниз, пока его не сдвинут вперёд-назад или влево-вправо.
Я думаю, логика игры не пострадает, если предположить, что человек, прыгая вверх чуть-чуть смещается вперёд.
Код:

        TranslateEntity user,0,user_vy#*delta_t,user_vy#*0.01,True
Всё!

impersonalis 04.09.2005 06:53

Релиз

В аттаче обновление кода

http://boolean.name/archive/code/ES_FPS_3_codeUp.rar

Необходимые ресурсы, можно взять из более ранней версии программы
http://boolean.name/archive/code/Example%2...0FPS%20v2.0.rar

:glag:

impersonalis 08.10.2005 13:54

Можно прочитать вышеизложенное и здесь:
http://blitzetc.boolean.name/fps.htm

Brodyaga 08.10.2005 15:15

:o
Что-то нето..
Во время игры, точнее стрельбы второй клавишей Рантайм еррор сообщает что не существует Ентити bot_h

alcosholik 08.10.2005 15:39

Скачал. Все работает.

Только прыжок нужно более реалистичным сделать: чтобы и при движении вверх ускорение было, а не просто мгновенное перемещение по оси Y.

impersonalis 08.10.2005 17:01

да? и откуда же ему браться. Тело приобретает ускорение при толчке от земли, а дальше, это ускорение падает до 0 (точка повисает над землёй) и падает ниже 0 (тело с ускорением движется вниз).

alcosholik 08.10.2005 17:35

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

Например: в файле ES_FPS_3.bb в функции update_user() заменить этот код:
Код:

        If pick_ent
 jump_bool=False
 user_vy=0
 TranslateEntity user,0,-1,0,True
        Else
 jump_bool=True
 user_vy#=user_vy#-G#*delta_t;v1=v2-a*t
        EndIf

;;;
        If KeyHit(57) And jump_bool=False Then user_vy#=1

на этот:
Код:

        If if_jumping=0
 If pick_ent
        jump_bool=False
        user_vy=0
        TranslateEntity user,0,-1,0,True
 Else
  jump_bool=True
  user_vy#=user_vy#-G#*delta_t;v1=v2-a*t
 EndIf
        Else
 jump_amount#=jump_amount#-G#*delta_t
 If jump_amount#<0 Then if_jumping=0
        End If

;;;
        If KeyHit(57) And jump_bool=False Then jump_amount#=.06: user_vy#=user_vy#+jump_amount#: if_jumping=1

при этом в начале объявив две переменные
Код:

Global if_jumping
Global jump_amount#

А значение G# я сделал равным 0.0005

impersonalis 08.10.2005 17:44

Мдя - понизь просто ускорение, движение в компьютерной эмуляции так или иначе состоит из рывков моментального перемещения. У меня игрок перемещается на х потом на ещё часть х и ещё - т.е. плавно до точки зависания. А скачкообразность первого перемещения на х зависит только от кол-ва ФПС и калибровки коэффициентов, при чём так или иначе приращения будут дискретны, а значит характер движения на dt не равномерным.

alcosholik 08.10.2005 17:49

Цитата:

У меня игрок перемещается на х потом на ещё часть х и ещё - т.е. плавно до точки зависания
Цитата:

If KeyHit(57) And jump_bool=False Then user_vy#=1
Не могу увидеть здесь плавного перемещения до точки зависания.

impersonalis 08.10.2005 17:51

А я вообще не вижу здесь перемещения :D

alcosholik 08.10.2005 18:10

Цитата:

У меня игрок перемещается на х потом на ещё часть х и ещё - т.е. плавно до точки зависания.
Цитата:

А я вообще не вижу здесь перемещения :D
:dontknow:

jimon 08.10.2005 18:35

нестал разбиратся в коде... но пишу что translateentity zzz,0,1,0 переместит обект не резко а плавно ! ;)

impersonalis 08.10.2005 19:08

Блин! по твоему, alcoSHoLiK, строка
If KeyHit(57) And jump_bool=False Then user_vy#=1
каким то таинственным образом двигает объект? Где здесь move,translate,position entity?!

alcosholik 08.10.2005 22:13

Я говорил что эта строка двигает объект? Нет.
Я говорил "мгновенно перемещает". Да, я украинец. Возможно, я не знаю русский. Тогда приведу синонимы: "мгновенно перемещает"="телепортирует"="переставляет из одной точки в другую". Т.е. само перемещение происходит фактически за 0 интервал времени.

Я утверждал, что строчка
If KeyHit(57) And jump_bool=False Then user_vy#=1
не передвигает объект по оси Y, а мгновенно перемещает(телепортирует) его в точку 1 по Y.
Но ведь в реальном мире при прыжке человек поднимается вверх, потом опускается. Он не телепортируется в точку над землей, он поднимается по оси Y до нее.
В оригинальном коде такого нет. Я привел пример, как это сделать, немного изменив оригинальный код.
Это все, о чем говорил. Никаких претензий к автору я не имею, просто показал, как можно сделать прыжок более реалистичным.

jimon 08.10.2005 23:56

мдя... все опять кричат что я читать не умею

сами вы читать неумеете

Цитата:

нестал разбиратся в коде... но пишу что translateentity zzz,0,1,0 переместит обект не резко а плавно !
теперь прочитайте ету строку еще пару раз пока не дойдет ...

alcosholik 09.10.2005 01:46

ДЖИМОН!
ТЫ БЫ В КОДЕ СНАЧАЛА РАЗОБРАЛСЯ, А ПОТОМ КОММЕНТАРИИ ОСТАВЛЯЛ!
Код:

TranslateEntity user,0,user_vy#*delta_t,user_vy#*0.01,True
Это строчка из оригинального кода. Характер перемещения игрока по оси Y зависит от переменной user_vy#. В рассматриваемом мной блоке кода я манипулировал значением переменной, а со следующим проходом цикла координата Y игрока изменится в зависимости от значения user_vy#.

УЧИСЬ ВНИКАТЬ В СУТЬ СПОРА!

impersonalis 09.10.2005 12:51

alcoSHoLiK, я лишь говорю, что перемещение на 1 можно заменить на 0.1 или 0.00001, добившись оного варьированием коэффициентов. И будет тебе плавное смещение. ТО есть , имхо, алгоритм тут не причём. А понятие "плавного движения"- здесь суть абстракция, и используется только на приближения - допустим, в космосиме с размерами порядка 500 скачок на 1 можно считать плавным и т.п.

Короче высказались все, порядочно и пофлудили.

impersonalis 03.02.2006 15:36

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

Const G#=0.001

Const shot_delay=1000; - вот это

Const USERT=1;for user

Нетрудно догадаться - это пауза между выстрелами в мс.
Код:

Global user_vy#
Global last_shot_time

пишем чуть ниже в глобалах.
Переходим в update_user().
Переписываем условие выстрела:
Код:

        If MouseHit(1) And MilliSecs()-last_shot_time>shot_delay
 *create_shot(EntityX(user),EntityY(user),EntityZ(user),EntityPitch(camera),EntityYaw(user),0)
 *last_shot_time=MilliSecs()
        EndIf

Ну и для большей наглядности визуализируем процесс перезарадки в MAIN LOOP:
Код:

;=
        If MilliSecs()-last_shot_time<=shot_delay
 Text 10,10,"RELOAD..."
        EndIf
;=


impersonalis 09.02.2006 19:09

Автомат в руки
Вот мне тут сообщают в ICQ, что дескать невозможно главгеру оружие вручить из-за моей модели представления... Странно...
Попробуем.
Смотрим в контакт-лист: ни одного моделлера в сети =/ кхым... Роль оружия будет выполнять цилиндр.
Код:

Global user
Global camera
Global GUN

ну скука прямо..
Код:

Function create_user(x#=0,y#=10,z#=0)

        MidHandle pricel
        MaskImage pricel,255,255,255
       
        user=CreateSphere()
        k#=3
        ScaleEntity user,k#,k#,k#
        EntityRadius user,k#
        camera=CreateCamera(user)
        CameraRange camera,0.1,10000
        PositionEntity user,x#,y#,z#
        EntityType user,USERT
       
        GUN=CreateCylinder()
        TurnEntity GUN,90,0,0
        ScaleEntity GUN,.5,1,.5
        EntityParent GUN,camera
        PositionEntity GUN,0,-.4,0.4
End Function

Запускаем и любуемсЯ =)
Но недолго: если стрелять очередью, видно, что огонь ведётся не из ствола, а так сказать,
Цитата:

die Sonne scheint mir aus den Augen © RAMMSTEIN

Нам надо как-то сассоциировать место начала движения спрайта выстрела и начало ствола.
Если бы у меня была нормальная модель, то дельный моделлер просто разместил бы пивот на конце ствола. Сейчас мы сделаем то же самое, но ручками ^_^
Код:

Function create_user(x#=0,y#=10,z#=0)

        MidHandle pricel
        MaskImage pricel,255,255,255
       
        user=CreateSphere()
        k#=3
        ScaleEntity user,k#,k#,k#
        EntityRadius user,k#
        camera=CreateCamera(user)
        CameraRange camera,0.1,10000
        PositionEntity user,x#,y#,z#
        EntityType user,USERT
       
        GUN=CreateCylinder()
        TurnEntity GUN,90,0,0
        ScaleEntity GUN,.5,1,.5
        EntityParent GUN,camera
        PositionEntity GUN,0,-.4,0.4
       
        end_=CreatePivot()
        NameEntity end_,"END"
        EntityParent end_,GUN
        PositionEntity end_,0,1.3,0
End Function

Так... Ну а теперь, перепишем событие - выстрел:
Код:

        Local user_gun
        If MouseHit(1) And MilliSecs()-last_shot_time>shot_delay
 *user_gun=FindChild(GUN,"END")
 *create_shot(EntityX(user_gun,1),EntityY(user_gun,1),EntityZ(user_gun,1),EntityPitch(camera),EntityYaw(user),0)
 *last_shot_time=MilliSecs()
        EndIf
        If MouseDown(2)
 *user_gun=FindChild(GUN,"END")
 *create_shot(EntityX(user_gun,1),EntityY(user_gun,1),EntityZ(user_gun,1),EntityPitch(camera),EntityYaw(user),0)
 *
        EndIf

Это мы в функции обновления игрока пишем.

Завершая тему личной обороны игрока, хотелось бы добавить вот что:
Код:

Graphics3D 800,600,32
SetBuffer BackBuffer()

SetFont LoadFont("Arial",21)
Color 255,0,0

Global user
Global camera
Global GUN

Следует, так же учесть - что точность выстрела после привязки выстрела к стволу немного снижается. Выход из проблемы (если это критично в вашей игре) не один, и зависит от контекста.

impersonalis 20.02.2006 21:20

Вложений: 2
Куда глаза глядят
Следующее что хотелось рассмотреть в цикле заметочек.
Указание направления дивжения бота при помощи спрайта. Как таковой пример особой ценности не

имеет, однако может понадобиться в дальнейшем и может быть модфицирован под нужды "девелопера".
Открываем графический редактор (к примеру MS Paint) и творим: нам понадобиться круг, с одним

проведённым радиусом.
Теперь вопрос: подгружать спрайт по мере необходимости или использовать образец? Для такой простой

игры это не критично, однако, стоит приучиться использовать копии объектов. (в идеале должен быть

и образец для каждого типа "выстрела"). Приступим:
Код:

Global pricel=LoadImage("pricel.bmp")
Global shot_sprite=LoadSprite("sprite.bmp")
Global orient_sprite=LoadSprite("orient.bmp")

Код:

Function create_world()
Function create_world()
        light=CreateLight()
        RotateEntity light,90,0,0
       
        HideEntity shot_sprite
        HideEntity orient_sprite
        SpriteViewMode orient_sprite,2

Создание бота немного подправим:
Код:

        NameEntity b\entity,Handle(b)
       
        Orient=CopyEntity(orient_sprite)
        TurnEntity Orient,90,0,0
        EntityParent Orient,b\entity
        PositionEntity Orient,0,-1,0
       
       
        Return True

Так... теперь вернёмся в место сокрытия образца и откалибруем размер спрайта:
Код:

SpriteViewMode orient_sprite,2
ScaleSprite orient_sprite,5,5

Собственно... пока всё.

impersonalis 20.02.2006 21:45

Вложений: 2
Квази-интеллект
Немного усложним поведение ботов - чтобы игра не была такой "пресной".
Сделаем из бота агр-моба (см LA2).
Сценарий следующий:
Цитата:

Бот шатается по уровню туда-сюда, пока мимо него не промчится игрок. Как только происходит это событие, бот переключается на игрока (со всеми последствиями). В случае если игрок отрывается на безопасное расстояние от преследователя - бот переходи снова к мироному шатанию по уровню.

Нам понадобиться ещё одно поле в типе бота - хендл пивота-вейпоинта.
Код:

Type bot
        Field entity
        Field time
        Field Survivability
        Field point
End Type

Правим функцию создания бота:
Код:

b\point=CreatePivot()
PositionEntity b\point,x,y,z
Return True

Ну и теперь програмим квази-интеллект:
Код:

Function update_bot()
        Rdist#=15
        v#=0.01
        For a.bot=Each bot
        ;=
 If a\time=0 a\time=MilliSecs()
 new_time=MilliSecs()
 delta_t=new_time-a\time
 a\time=new_time
        ;=
 If EntityDistance(user,a\entity)<Rdist#
        PointEntity a\entity,user
 Else
        If EntityDistance(a\entity,a\point)<2
  PositionEntity a\point,EntityX(a\point)+Rnd(-40,40),EntityY(a\point),EntityZ(a\point)+Rnd(-40,40)
        EndIf
        PointEntity a\entity,a\point
 EndIf
        ;===========
 MoveEntity a\entity,0,0,v#*delta_t
 TranslateEntity user,0,-G#*delta_t,0
        Next
End Function

Rdist# отвечает за расстояние, на котором бот переключается на игрока.
Рассмотрим подробнее:
Код:

If EntityDistance(user,a\entity)<Rdist#
Если расстояние до игрока меньше критического - время действовать.
Код:

PointEntity a\entity,user
"наводка" на игрока.
Если же игрока рядом нет, то
Код:

If EntityDistance(a\entity,a\point)<2
Бот подошёл к контрольной тчке достаточно близко - пора её сменить
Код:

PositionEntity a\point,EntityX(a\point)+Rnd(-40,40),EntityY(a\point),EntityZ(a\point)+Rnd(-40,40)
в любом случае - наводим бота на точку:
Код:

PointEntity a\entity,a\point
А дальше - дело техники :)

impersonalis 21.02.2006 01:03

Квази-интеллект2
Слегка погонял ботов и понял, что чего-то не хватает.
Подправим алгоритм поведения:
Цитата:

Если бот получил урон от выстрела он начинает двигаться в строну выстрела. Причём игрок, если выстрел

был совершён с дальнего расстояния, может успеть смениь локацию.

Код:

Function update_shot()
        v#=0.1
        max_dist#=1000
        For a.shot=Each shot
        ;=
 If a\time=0 a\time=MilliSecs()
 new_time=MilliSecs()
 delta_t=new_time-a\time
 a\time=new_time
 vs#=v*delta_t
        ;=
 MoveEntity a\entity,0,0,vs#
 a\dist#=a\dist#+vs#
 bot_h=EntityCollided(a\entity,BOTT)
 If a\dist#>max_dist#
        FreeEntity a\entity
        Delete a
 ElseIf EntityCollided(a\entity,TERRT)<>0
        FreeEntity a\entity
        Delete a
 ElseIf bot_h<>0
        FreeEntity a\entity
        Delete a
        bhandle=EntityName(bot_h)
        bc.bot=Object.bot(bhandle)
        bc\Survivability=bc\Survivability-16
 ;=
        PositionEntity bc\point,EntityX(user,1),EntityY(bc\point,1),EntityZ(user,1)
 ;=
        If bc\Survivability<=0
  Delete bc
  FreeEntity bot_h
  create_bot(EntityX(user)+Rnd(-100,100),2,EntityZ(user)+Rnd(-100,100))
        EndIf
 EndIf
        Next
End Function

Как видите - дописали ВСЕГО одну строчку,
Цитата:

PositionEntity bc\point,EntityX(user,1),EntityY(bc\point,1),Entit yZ(user,1)

но какой эффект =)
И ещё кое-что: пусть бот становится красным, если он "навёлся" на игрока.
Код:

Function update_bot()
        Rdist#=15
        v#=0.01
        For a.bot=Each bot
        ;=
 If a\time=0 a\time=MilliSecs()
 new_time=MilliSecs()
 delta_t=new_time-a\time
 a\time=new_time
        ;=
 If EntityDistance(user,a\entity)<Rdist#
        PointEntity a\entity,user
        EntityColor a\entity,255,0,0
 Else
        EntityColor a\entity,255,255,255
        If EntityDistance(a\entity,a\point)<2
  PositionEntity

a\point,EntityX(a\point)+Rnd(-40,40),EntityY(a\point),EntityZ(a\point)+Rnd(-40,40)
        EndIf
        PointEntity a\entity,a\point
 EndIf
        ;===========
 MoveEntity a\entity,0,0,v#*delta_t
 TranslateEntity user,0,-G#*delta_t,0
        Next
End Function


Guest 13.03.2006 17:42

Очень крутая статья все просто и ясно, лучше нигде не втречал, спасибо!

impersonalis 13.03.2006 18:34

Всегда пожалуйста. Будут вопросы - обращайтесь =)

impersonalis 20.07.2006 00:40

Re: Создаём свой FPS (first person shooter)
 
MANIAK_dobrii первёл данную работу на английский язык:
english-version

impersonalis 16.08.2006 15:15

Re: Создаём свой FPS (first person shooter)
 
Благодаря GoodWin (http://www.boolean.name/showthread.php?p=15571) нарыл баг в коде, который сказывается при использовании уровня с ра3личными высотами. Короче говоря, код создания выстрела должен быть такой:
Код:

Function create_Shot(x#,y#,z#,pitch#,yaw#,roll#)
        s.shot=New shot
        s\entity=CopyEntity(shot_sprite)
        PositionEntity s\entity,x#,y#,z#,1
        EntityType s\entity,SHOTT
        RotateEntity s\entity,pitch#,yaw#,roll#,1
        Return True
End Function

Для тех кто не понял:
команда позиционирования объекта
Код:

PositionEntity s\entity,x#,y#,z#,1
должна идти до того, как мы поставим ему тип коллизии:
Код:

EntityType s\entity,SHOTT
Иначе объект просто нельзя будет переместить в указанную точку.
Глюк обидный и вызван только моим недосмотром =(

TARAPUPENKO 17.08.2006 02:24

Re: Создаём свой FPS (first person shooter)
 
А как сделать здоровье персонажу и отнятие здоровья при прикосновении бота к игроку?

impersonalis 17.08.2006 03:11

Re: Создаём свой FPS (first person shooter)
 
Завтра (имеется в виду - т.н. "логическое завтра") отвечу - сегодня спать хочется.

GoodWin 17.08.2006 15:23

Re: Создаём свой FPS (first person shooter)
 
Цитата:

А как сделать здоровье персонажу и отнятие здоровья при прикосновении бота к игроку?
Вот попробуйте....

1) это моя тема
2) выложить просто переправленный аттач моджет каждый - для этого есть рубрики по 2д и 3д. ФАК подразумевает объяснения, тем более что
3)в коде много изменений без которых можно обойтись

imper

ладно, а так пойдет:
назначим еще одну переменную отвечающую за кол-во жизни у игрока
Global user, live
добавим в функцию создания игрока следующую строку
live=100
user=CreateSphere()
k#=3
ScaleEntity user,k#,k#,k#
теперь необходимо обработать прикосновение бота к игроку
изменим функцию
update_bot()
добавим следующие строки кода:
a\time=new_time
;=
If EntityCollided (a\entity,USERT); проверякм есть ли колизия между ботои и игроком
live=live-1; если есть то уменьшаем кол-во жизни
If live<=0; если кол-во жизни игрока 0 или меньше
FreeEntity user_h; удаляем игрока и снова его
create_user(0,100,0);создаем
EndIf
ElseIf

EntityDistance(user,a\entity)<Rdist#
PointEntity a\entity,user
Else
If EntityDistance(a\entity,a\point)<2
PositionEntity a\point,EntityX(a\point)+Rnd(-40,40),EntityY(a\point),EntityZ(a\point)+Rnd(-40,40)
EndIf
PointEntity a\entity,a\point
EndIf

красным новые строки которые необходимо добавить...

и конечно-же... идея реализации полность принадлежит Имперсоналису, за что ему огромное спасибо...
P.S. извини, что отнял у тебя возможность помочь людям

impersonalis 17.08.2006 20:33

Re: Создаём свой FPS (first person shooter)
 
Смертный герой
Сегодня прикрутим показатель жизни игроку.
Как всегда код будет максимально простым - чтобы показать прицип работы. Конечная реализация зависит от вас.
В глобал:
[highlight=blitzbasic]
Global user_H
[/highlight]
в функцию create_user(x#=0,y#=10,z#=0)
[highlight=blitzbasic]
user_H=100
[/highlight]
в MAIN LOOP:
[highlight=blitzbasic]
Text 10,40,"H="+user_H
[/highlight]
Теперь опишем систему нанесения урона пользователю.
В функции update_bot() пишем:
[highlight=blitzbasic]
Function update_bot()
Rdist#=15
v#=0.01
For a.bot=Each bot
;=
If a\time=0 a\time=MilliSecs()
new_time=MilliSecs()
delta_t=new_time-a\time
a\time=new_time
;=
If EntityDistance(user,a\entity)<Rdist#
PointEntity a\entity,user
EntityColor a\entity,255,0,0
Else
EntityColor a\entity,255,255,255
If EntityDistance(a\entity,a\point)<2
PositionEntity a\point,EntityX(a\point)+Rnd(-40,40),EntityY(a\point),EntityZ(a\point)+Rnd(-40,40)
EndIf
PointEntity a\entity,a\point
EndIf
;===========новое
player=EntityCollided(a\entity,USERT)
If player<>0
user_H=user_H-1
EndIf
;======конец нового
MoveEntity a\entity,0,0,v#*delta_t
TranslateEntity user,0,-G#*delta_t,0
Next
End Function
[/highlight]
Если бот столкнулся с объектом, у которого стоит тип коллизии USERT, то убавить игроку жизнь на единицу.
Запустите и убедитесь в недостаточности этой конструкции. Жизнь уменьшается при каждом вызове функции, т.е. если у вас 60 ФПС, то за секунду прикосновения к вам бота у вас онимется 60 единиц здоровья.
Приступим.
[highlight=blitzbasic]
Type bot
Field entity
Field time
Field Survivability
Field point
Field last_damage_time
End Type
[/highlight]
Здесь будем хранить время последнего "удара".
Подправим функцию обеновления бота:
[highlight=blitzbasic]
;===========
player=EntityCollided(a\entity,USERT)
If player<>0
user_H=user_H-1
Else
a\ last_damage_time=0
EndIf
;======
[/highlight]
и далее в блоке If player<>0
[highlight=blitzbasic]
If a\last_damage_time=-1
a\last_damage_time=MilliSecs()
EndIf
[/highlight]
Если мы фиксируем акт соприкосновения первый раз (так сказать "локально" - т.е. в прошлом обновлении коллизии не было, а перед этим соприкосновений могло быть лююбое кол-во), то сохраняем в поле время, прошедшее с начала соприкосновения (а далее - время, с последнего обновления события, влекующего сосприкосновение - т.е. время прошлого "удара"). Вообще говоря, факт коллизии будет зафиксирован при вызове UpdateWorld(), т.е. время, прошедешее с акта коллизии до обработки этого факта в функции обновления бота весьма мало. Но всё же, не "пренебрежимо мало", особенно если частота UpdateWorld и функций обновления бота сильно разняться.
Но для данного примера это не критично (в описанном выше случае надо или логику вызвать вместе с UpdateWorld или искать иные пути - зависит от конкретики реализации стабилизации и логики мира)
[highlight=blitzbasic]
If MilliSecs()-a\last_damage_time>1000
user_H=user_H-1
a\last_damage_time=MilliSecs()
EndIf
[/highlight]
Ну и как завершающий штрих - сделаем силу урона произвольной (из диапазона):
[highlight=blitzbasic]
Function update_bot()
Rdist#=15
v#=0.01
For a.bot=Each bot
;=
If a\time=0 a\time=MilliSecs()
new_time=MilliSecs()
delta_t=new_time-a\time
a\time=new_time
;=
If EntityDistance(user,a\entity)<Rdist#
PointEntity a\entity,user
EntityColor a\entity,255,0,0
Else
EntityColor a\entity,255,255,255
If EntityDistance(a\entity,a\point)<2
PositionEntity a\point,EntityX(a\point)+Rnd(-40,40),EntityY(a\point),EntityZ(a\point)+Rnd(-40,40)
EndIf
PointEntity a\entity,a\point
EndIf
;===========
player=EntityCollided(a\entity,USERT)
If player<>0

If a\last_damage_time=-1
a\last_damage_time=MilliSecs()
EndIf
If MilliSecs()-a\last_damage_time>1000
user_H=user_H-Rand(1,7)
a\last_damage_time=MilliSecs()
EndIf
Else
a\ last_damage_time=-1
EndIf
;======
MoveEntity a\entity,0,0,v#*delta_t
TranslateEntity user,0,-G#*delta_t,0
Next
End Function


[/highlight]

impersonalis 18.08.2006 03:08

Re: Создаём свой FPS (first person shooter)
 
Вложений: 2
Вот - код и все ресурсы к нему. В коде GoodWin (спс. за старание) я не нашёл временной паузы между нанесением поврежденй игроку - весьма обидное упущение. В моей заметке это есть :@
ATTENTION! Если вы скачивали сегодня уже этот архив, то перекачайте - в коде была допущена некритичная (но всё же!) неточность.

GoodWin 18.08.2006 10:29

Re: Создаём свой FPS (first person shooter)
 
Думаю следующий урок будет "научим Бота стрелять!"
всем это будет интересно и мне тоже....
ждемс....

Цитата:

В коде GoodWin (спс. за старание) я не нашёл временной паузы между нанесением поврежденй игроку - весьма обидное упущение
в вопросе
Цитата:

А как сделать здоровье персонажу и отнятие здоровья при прикосновении бота к игроку?
о необходимости временной паузы ни слова не было... хотя ты все равно прав...признаю...

impersonalis 19.08.2006 01:48

Re: Создаём свой FPS (first person shooter)
 
Боевая система
Сделаем несколько типов оружия. Модифицируем систему.
[highlight=blitzbasic]
Type shot
Field entity
Field dist#
Field time
Field class_id%;здесь будет хранится инфа о классе снаряда(патрона)
End Type
[/highlight]
Визуализация снаряда зависит от его класса
[highlight=blitzbasic]
Function create_Shot(x#,y#,z#,pitch#,yaw#,roll#,class%=1)
s.shot=New shot
s\class_id=class
Local shot_sprite_handle=shot_spriteS(s\class_id)
s\entity=CopyEntity(shot_sprite_handle)
PositionEntity s\entity,x#,y#,z#,1
EntityType s\entity,SHOTT
RotateEntity s\entity,pitch#,yaw#,roll#,1
Return True
End Function
[/highlight]
shot_spriteS - это будет наш массив с образцами спрайтов
[highlight=blitzbasic]
Global pricel=LoadImage("pricel.bmp")

Dim shot_spriteS(2);инициализация
For i=0 To 1;загрузка
shot_spriteS(i)=LoadSprite("sprite"+i+".bmp")
Next

Global orient_sprite=LoadSprite("orient.bmp")
[/highlight]
А вот это - нафик
[highlight=blitzbasic]
Function create_world()
light=CreateLight()
RotateEntity light,90,0,0

HideEntity shot_sprite; уже не катит :)
[/highlight]
Скрывать будем сразу после загрузки:
[highlight=blitzbasic]
For i=0 To 1
shot_spriteS(i)=LoadSprite("sprite"+i+".bmp")
HideEntity shot_spriteS(i)
Next
[/highlight]
Так теперь переименуйте файл со спрайтом выстрела в sprite0 и найдите ещё один (отличный от имеющегося) спрайт - назовите его sprite1.
Подправим функцию обновления игрока:
[highlight=blitzbasic]
If MouseHit(2) And MilliSecs()-last_shot_time>shot_delay
user_gun=FindChild(GUN,"END")
create_shot(EntityX(user_gun,1),EntityY(user_gun,1 ),EntityZ(user_gun,1),EntityPitch(camera),EntityYa w(user),0)
last_shot_time=MilliSecs()
EndIf
If MouseHit(1)
user_gun=FindChild(GUN,"END")
create_shot(EntityX(user_gun,1),EntityY(user_gun,1 ),EntityZ(user_gun,1),EntityPitch(camera),EntityYa w(user),0,1)

EndIf
[/highlight]
В зависимости от того что нажато (ЛКМ или ПКМ), производится выстрел из 1ого или 2ого оружия.
Уже можно псмотреть результаты работы.
Далее.
И вот тут начинаешь задумываться: у различного оружия - разлиные характеристики (глубокая мысль).
Надо как-то оценивать дальность стрельбы, урон, особенности траектории и прочее... Эх - создаём ещё один информационный тип:
[highlight=blitzbasic]
Type SHOT_TYPE
Field sprite
Field damage
Field max_dist
Field v#
End Type
[/highlight]
Самое мерзкое (так делать не надо!) описываем в коде характеристики оружия.
В реальных играх это должно хранится в конфигурационных файлах и загружаться специальной функцией.
[highlight=blitzbasic]
Dim shot_S.SHOT_TYPE(2)

For i=0 To 1
shot_S(i)=New SHOT_TYPE
Next

shot_S(0)\sprite=LoadSprite("sprite0.bmp")
HideEntity shot_S(0)\sprite
shot_S(0)\damage=95
shot_S(0)\max_dist=100
shot_S(0)\v=0.3

shot_S(1)\sprite=LoadSprite("sprite1.bmp")
HideEntity shot_S(1)\sprite
shot_S(1)\damage=10
shot_S(1)\max_dist=1000
shot_S(1)\v=0.1
[/highlight]
Характеристики зависят от конкретики игры ( можно хоть мат.дисперсию величины повреждения записать).
Не забудьте удалить старый код - с массивом под спрайты.
[highlight=blitzbasic]
Function create_Shot(x#,y#,z#,pitch#,yaw#,roll#,class%=1)
s.shot=New shot
s\class_id=class
s\entity=CopyEntity(shot_S(s\class_id)\sprite)
PositionEntity s\entity,x#,y#,z#,1
EntityType s\entity,SHOTT
RotateEntity s\entity,pitch#,yaw#,roll#,1
Return True
End Function
[/highlight]
ну и правим обновление снарядов:
[highlight=blitzbasic]
Function update_shot()

For a.shot=Each shot
;=
If a\time=0 a\time=MilliSecs()
new_time=MilliSecs()
delta_t=new_time-a\time
a\time=new_time
vs#=shot_S(a\class_id)\v*delta_t
;=
MoveEntity a\entity,0,0,vs#
a\dist#=a\dist#+vs#
Local copy_a_class_id=a\class_id

bot_h=EntityCollided(a\entity,BOTT)
If a\dist#>shot_S(a\class_id)\max_dist
FreeEntity a\entity
Delete a
ElseIf EntityCollided(a\entity,TERRT)<>0
FreeEntity a\entity
Delete a
ElseIf bot_h<>0
FreeEntity a\entity
Delete a
bhandle=EntityName(bot_h)
bc.bot=Object.bot(bhandle)
bc\Survivability=bc\Survivability-shot_S(copy_a_class_id)\damage
;=
PositionEntity bc\point,EntityX(user,1),EntityY(bc\point,1),Entit yZ(user,1)
;=
If bc\Survivability<=0
Delete bc
FreeEntity bot_h
create_bot(EntityX(user)+Rnd(-100,100),2,EntityZ(user)+Rnd(-100,100))
EndIf
EndIf
Next
End Function
[/highlight]
Запускаем - играем.
ГГ... Теперь даже тактика есть: заманить противника на себя из слабого быстроперезаряжаемого оружия а потом, вблизи, замочить из СуперПушки с одного залпа =)

impersonalis 19.08.2006 01:54

Re: Создаём свой FPS (first person shooter)
 
Вложений: 4
Я так смотрю - архив качают :-)
А значит мои коды не лишены смысла - это приятно.

В архиве - модифицированный код со всеми ресурсами B)

Тарас Шевченко 19.08.2006 14:57

Re: Создаём свой FPS (first person shooter)
 
Для полноценности надо сделать HUD.

Ins!de 19.08.2006 15:32

Re: Создаём свой FPS (first person shooter)
 
Ух ты, спасибо impersonalis! Полезная статья)

impersonalis 20.08.2006 23:14

Re: Создаём свой FPS (first person shooter)
 
Инвентарь
Один из самых популярных вопросов.
Инвентарь (как и многое другое) - как хочешь так и кодируй. Я предложу один из вариантов.
Пишем тип:
[highlight=blitzbasic]
Type inventar
Field s[10]
Field current
Field last_shot_time
End Type
[/highlight]
создайм "один инвентарь" для игрока:
[highlight=blitzbasic]
Global user_inv.INVENTAR=New INVENTAR
[/highlight]
в Function create_user(x#=0,y#=10,z#=0) допишем:
[highlight=blitzbasic]
For i=0 To 10
user_inv\s[i]=0
Next
user_inv\s[0]=1
user_inv\current=0
[/highlight]
Global last_shot_time -удаляем из кода.
В Function update_user() добавляем универсальности:
[highlight=blitzbasic]
If MouseHit(1) And MilliSecs()-user_inv\last_shot_time>shot_delay
user_gun=FindChild(GUN,"END")
create_shot(EntityX(user_gun,1),EntityY(user_gun,1 ),EntityZ(user_gun,1),EntityPitch(camera),EntityYa w(user),0,0)
user_inv\last_shot_time=MilliSecs()
EndIf
[/highlight]
Обработчик ПКМ стираем.
Неплохо бы сделать для каждого образца оружия своё время перезарядки (ожидания между выстрелами):
[highlight=blitzbasic]
Type SHOT_TYPE
Field sprite
Field damage
Field max_dist
Field v#
Field reload_time
End Type
[/highlight]
[highlight=blitzbasic]
shot_S(0)\sprite=LoadSprite("sprite0.bmp")
HideEntity shot_S(0)\sprite
shot_S(0)\damage=95
shot_S(0)\max_dist=100
shot_S(0)\v=0.3
shot_S(0)\reload_time=1500

shot_S(1)\sprite=LoadSprite("sprite1.bmp")
HideEntity shot_S(1)\sprite
shot_S(1)\damage=10
shot_S(1)\max_dist=1000
shot_S(1)\v=0.1
shot_S(1)\reload_time=0
[/highlight]
Const shot_delay=1000 - нам больше не понадобится.
[highlight=blitzbasic]
If MouseHit(1) And MilliSecs()-user_inv\last_shot_time>shot_S(user_inv\current)\r eload_time
[/highlight]
Если юзер тыкнул ЛКМ и время с последнего выстрела прошло достаточно для перезарядки текущего оружия, то
[highlight=blitzbasic]
user_gun=FindChild(GUN,"END")
create_shot(EntityX(user_gun,1),EntityY(user_gun,1 ),EntityZ(user_gun,1),EntityPitch(camera),EntityYa w(user),0,user_inv\current)
user_inv\last_shot_time=MilliSecs()
[/highlight]
[highlight=blitzbasic]
If MilliSecs()-user_inv\last_shot_time<shot_S(user_inv\current)\r eload_time
Text 10,10,"RELOAD..."
EndIf
[/highlight]
Теперь - навигация по инвентарю:
[highlight=blitzbasic]
Function change_item(i.inventar,move%)
p=i\current
While True
p=p+move
If p<0 p=9
If p>9 p=0
If i\s[p]<>0 Exit
Wend
i\current=p
End Function
[/highlight]
Вот такая вот фунЕция:
i.inventar - инвентарь с которым мы будем работать
move% -примает значения 1 и -1 -направление движения по инвентарю (след./пред.)
В update_user() допишем:
[highlight=blitzbasic]
change_item(user_inv,MouseZSpeed())
[/highlight]
А в Function create_user(x#=0,y#=10,z#=0):
[highlight=blitzbasic]
user_inv\s[0]=1
user_inv\s[1]=1;добавим
[/highlight]
Можно полюбоватся (или поужасаться) уже: запускаем, колёскиом выбираем оружие из инвентаря, ЛКМ-ом стреляем.

impersonalis 20.08.2006 23:19

Re: Создаём свой FPS (first person shooter)
 
Вложений: 2
B)

GoodWin 21.08.2006 13:49

Re: Создаём свой FPS (first person shooter)
 

Цитата:

Field s&#91

а это, что за переменная такая... имя такое...или что-то особое?


Часовой пояс GMT +4, время: 14:12.

vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot