четверг, 31 марта 2016 г.

Великая анимационная революция.

итак, великая анимационная революция таки свершилась. Заодно она потянула за собой и устранение старых проблем и появление новых. Собственно, из новых проблем наиболее сложной является лишь строгое следование тобой же разработанным правилам. А еще - пересоздание анимаций...
На данный момент все движения и трансформации, за исключением вращения колес шасси, пламени форсажа и свечения турбины задаются изменеием всевозможных проперти. Несомненным достоинством этого метода является возможность избежать резких рывков анимации и прыжков скоростей. Особенно это касается анимаций аэродинамических поверхностей, "рывки" при кренах, тангаже и рыске были настоящим проклятием - было отчетливо видно, как резко "дергается" стабилизатор или элерон при смене курса. Также не вызывало особого восторга стремительное, почти мгновенное возрастание скорости при смене стреловидности или выпуске тормоза. Теперь проперти, управляющие кадрами анимации, интегрированы в модель полета. Это можно рассмотреть на примере выпуска-уборки щитков воздушного тормоза.
Как выглядит работа анимации тормоза:

 if own['levelsDetails'] == 0:
                #Собственно, анимация щитков тормоза - кадр анимации задается значением проперти AIRBRAKE
                for obj in own['localDict']['deviceAirbrakes']:
                    obj.playAction(obj.name,own['AIRBRAKE'],own['AIRBRAKE'],0,0,0,bge.logic.KX_ACTION_MODE_PLAY,0.0,0,1.0)
            #Выпуск тормоза           
            if own['localDict']['AIRBRAKE'] < own['AIRBRAKE'] and own['AIRBRAKE'] < 100:
                own['AIRBRAKE'] += 1
                if own['AIRBRAKE'] == 100:  
                    own['localDict']['AIRBRAKE'] = own['AIRBRAKE']
            #Уборка тормоза
            if own['localDict']['AIRBRAKE'] > own['AIRBRAKE'] and own['AIRBRAKE'] > 1:
                own['AIRBRAKE'] -= 1
                if own['AIRBRAKE'] == 1:                   
                     own['localDict']['AIRBRAKE'] = own['AIRBRAKE']

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

 ###################################################
        #Выпуск-уборка воздушного тормоза
        if keyboard.events[bge.events.AKEY] == JUST_ACTIVATED:
            if self['AIRBRAKE'] == 1:
                self['AIRBRAKE'] += 1
            elif self['AIRBRAKE'] == 100:
                self['AIRBRAKE'] -= 1
            #Если оверлейная сцена с кокпитом уже работает
            for sceneGame in bge.logic.getSceneList():
                if sceneGame.name == 'Cockpit':
                    cockpit = sceneGame.objects['Cockpit']
                    cockpit['airbrake'] = self['AIRBRAKE']

то есть достаточно просто "подтолкнуть" проперти вперед или назад один раз. и пока проперти не получит самого большого или самого маленького значения, будет продолжаться его нарастание или убывание. Для тормоза эти значения равны 1 и 100. Аналогично происходит выпуск-уборка шасси, закрылков, смена стреловидности, открытие фонаря и так далее. Но и это не все. величина проперти AIRBRAKE еще и влияет на скорость полета. Упрощенно - скорость задается какой-то формулой, в составе которой имеется и AIRBRAKES, только вот значение его используется не прямо, а вот так:
151/(150 + AIRBRAKES)
Поясняю - минимальное значение проперти равно 1 - тормоз убран. И мы имеем в итоге 1. Скорость не падает. Но стоит только выпустить тормоз, как в конце анимации мы получаем 151/(150 + 100). Что-то коло 0.6. Тормоз действует, "съедая" сорок процентов скорости. Но при этом эти проценты съедаются постепенно - с каждым тиком - с каждым кадром анимации.

Добившись четкой работы анимации всех деталей, я занялся дальнейшей доводкой кокпитов и пополнением  авиапарка. Пока он состоит лишь из модификаций МиГ-23, да и то - одной национальности - ВВС Ливии полковника Каддафи.  изначально я собирался делать версию про египетско-ливийскую войну 1977, да ещ и не сильно отходя от, как мне казалось тогда, незыблемых канонов. оказалось, каноны не такие уж незыблемые...
Пришлось немного повоевать с моделью движения самолета. Если с разворотами все оказалось более-менее понятно - с применением силы Torque, да и с новым методом учета стрелодидности - картинка получалсь архизамечательная по сравнению с первой версией, то вот с линейным движением вышел облом. Как оказалось, сила, толкающая объект вперед, не компенсирует силу тяжести, увы. Так что пришлось опять прибегнуть к localLinearVelocity. Хотя она рассчитывается по-другому, и, как я говорил, ее изменение стало более плавным и реалистичным... Но это дело можно совершенствовать по мере дальнейшего продвижения вперед, так что, кто его знает, что в итоге получится.

Всплыла еще одна не слишком приятная проблема. Ландшафт. С ним я воевал долгог и упорно, и, казалось, победил. но не совсем. При возросших скоростях движения вдруг выяснилось, что его подгрузка не поспевает за камерой и, что хуже всего, просаживает ФПС и сильно при увеличении частоты работы. Однако варианты все же есть и можно попробовать их несколько.
Первый - увеличить размеры блока, что приведет к уменьшению их числа,а, значит, можно даже уменьшить количество обновлений террайна в игре.
Второй вариант - отшлифовать код генерации блоков. что-то мне кажется, что я недостаточно уделял ему внимания. Это, пожалуй, обязательно.
Третий - многообещающий и в то же время рискованный. БГЕ может меня неправильно понять и сильно обидеться. Вплоть до зависания и вылета. Но попробовать надо. Суть в том, что грузится ВЕСЬ ландшафт - все блоки сразу на игровую сцену. И на ней происходит замена мешей блоков на плейны. Маленькие. Но физика-то меша должна сохраниться, как мне объясняли. Грузим ландшафт второй раз, но МЕШЕМ,  из дугого файла. Дальше получается довольно приличное количество плейнов в виде "ячеек", которые будут подменяться мешаи по мере приближения к ним камеры. Проблема в том, что у меня ландшафт имеет 640 килополиков и комп начинает подтормаживать. Не в БГЕ, а в редактировании. Но не знаю, выдержит ли движок такую лютую нагрузку, пусть и на короткое время... Во всяком случае = первый вариант при этом можно применить. Или сделать блоки меша очень сильно низкополигональными. Да, пожалуй, стоит и это попробовать...

Ну, и завершаю пост красивыми (как без них) картинками.
МиГ-23МФ ВВС Ливии. Тест на замену мешей.
МиГ-23БН ВВС Ливии. Тест на анимацию механических частей.

МиГ-23МЛАЭ ВВС Ливии - тест на анимацию шасси. 


четверг, 24 марта 2016 г.

От серматов к камуфляжу...

Что такое сермат я узнал относительно недавно. как оказалось, это всего лишь сокращение от слов "серый материал". Имеется в виду модель без текстур стандартного серого цвета. Именно с с созерцания такой модели пришлось начать работу по отработке уровней детализации.
Собственно, уровней детализации у меня сейчас  четыре. Малый уровень детализации - за пределами визуальной видимости (похоже, придется вводить еще одно проперти для юнитов - дистанцию визуальной видимости, потому как Б-52 и МиГ-21, к примеру, несопоставимы по размерам и первый виден гораздо дальше), на этом уровне, как таковая, отсутствует, есть только плейны-меши оружия и пилонов, ну и разумеется, сам параллелепипед юнита (движок). Низкий уровень детализации - присутствует схематично-грубый меш самолета, очень низкополигональный, без текстур(на больших расстояниях видны лишь черные и серые силуэты), затем средний - присутствует выскополигональная неанимрованная модель в виде единого меша. И только на дистанции меньше 200 метров появляется полностью анимированная и детализированная модель - это - высокий уровень детализации. Все это хитроумно расписано в коде... Было. Потому что все манипуляции с моделями идут через операцию replaceMesh.и вот тут опять начались проблемы.
Первым к использованию в игре был предназначен МиГ-23МФ. во-первых, для него была давно готова текстура, во-вторых, Миг-23 имеет крылог изменяемой стреловидности и есть возможность отработать вопрос с изменением свойств самолета сразу, чтобы потом к этому вопросу не возвращаться, в-третьих, когда я создавал эту модель, произошел какой-то сбой при дублировании мешей и они упорно не желали нормально подменяться. Для решения этой проблемы я просто экспортировал все меши в формат obj, а потом импортировал обратно в Блендер. Как выяснилось, надо было трясти не только меши крыльев, фюзеляжей, створок шасси, но и меши остекления и кокпита, которые были в отдельном файле. я же сдуру спокойно добавил и стекло и кокпит в создаваемый файл МиГ-23МФ и объединил их с мешами фюзеляжа и фонаря. Результат оказался "на лице": все прекрасно заменялось и работало, кроме фюзеляжа. Для проверки я полез в ранее созданный и временно отложенный за отсутствием текстур файл МиГ-23 БН. Там ни стекла, ни кокпитов не было, поэтому произошла замена мешей, как положено. Только результатом стала такая картинка:

Сермат, однако. И почему-то дырка в фюзеляже на месте заплатки створок шасси. Ладно, пересоздал файл МиГ-23МФ и снова получил ожидаемый сермат. Текстуры сразу накладывать не стал, решив проверить работу замены мешей. вторая картинка. И снова сермат:



И опять дыра в фюзеляже. Где заплатка?! Снова полез проверять принтом наличие искомой детали и ее видимость. Есть такая и видимость - Тrue. Не понял... А если это опять та застарелая ошибка? Вроде "заплатку2 я делал отдельно и не конвертил. Снова экспорт в obj и импорт обратно. Снова переименование объектов и добавление их в файлы (что характерно, ни БН, ни МФ мне до этого видимый меш заплатки не показали). И снова пуск игры. Так и есть. Видимо, тот дефектный меш снова о себе дал знать. Перед этим к модели я присоединил "леченые" стекла и "ванну" кокпита. И в итоге получил требуемую картинку. На сей раз в камуфляже.

Пока нет в кабине летчика, недоведена полетная модель и отсутствует анимация. Это еще предстоит сделать. Как и освоить агрегатирование, пример которогог denis8424 выложил в своем блоге. http://denis8424.blogspot.ru/2016/03/blog-post_22.html

четверг, 17 марта 2016 г.

Классы и физика. Кокпит и аэродинамика...

Помимо такой увлекательной вещи, как кодинг, существует масса рутинных вещей. Например, создание номеров. номеров надо много, хороших и разных. В начале недели этим пришлось заняться весьма тщательно. Помимо создания своих собственных номеров для ливийских самолетов (четырехзначные "арабские" - не путать с настоящими арабскими цифрами, без кавычек!), на основе известных мне фото и декалей для моделей (пластиковых, не трехмерных, и самых, что ни есть настоящих, только маленьких!), поискал и нашел в своих закромах арабские номера, с настоящими, без кавычек, арабскими цифрами. Все они были маленькие и из них пришлось "клеить" большую текстуру. С обычными цифрами дело было проще - создаем в ГИМПе текстовый слой и набиваем до одурения - 6901, 6902 и так далее с вариациями для первых двух цифр.
Далее я "припаял" стекла к кабине и фонарю, в смысле, объединил стекла с их "родителями". По большому счету, чем меньше объектов, тем лучше, а прописывать в большом коде по замене мешей еще и стекла... Да ну на фиг... Та же участь постигла малодетализированный кокпит модели на внешнем игровом слое. Я его объединил с фюзеляжем. Все равно в кабине с внешней камеры ничего не видно. Правда, пути отступления я себе сохранил, создав группы вершин для стекол и этого самого кокпита. Чтобы в случае чего, быстро "отрезать".
Затем я поперся постигать Силу... Не джедайскую. Force в БГЕ. Занимает она меня тем, что с ней можно творить что-то близкое к аэродинамике, ну и поведение самолетов как бы естественней. Тем более, что на данный момент Torque задействована в управлении разворотами. Пока я не стал еще переделывать скрипт движения, потому что опять  взбрело в голову поискать, нет ли чего по аэродинамике в BGE. Оказалось, было дело - в 2009 году делалось что-то подобное и ветка на blenderartist сохранилась. С файлами. Устаревшими напрочь. Но несколько таких файликов я скачал и самый "свежий" переделал под сегодняшний Блендер. Ну как переделал? Поубирал GameLogic и вписал bge.logic, с помощью Яндекса перевел комментарии (несколько коряво, но уж лучше того же Гугла-переводчика). Результат столь же ошеломителен, сколь и непонятен... Объект то ли летает задом наперед, то ли что-то было заменено в Питоне с тех пор, в общем, летает "пуля", только очень быстро. Половина скорости света точно есть. Что с этим дальше делать - ясно. Разбираться, разбираться и еще раз разбираться (перевранное (с)). Ну ладно, аэродинамика вещь сложная, но постижимая.времени б только побольше, а то и другое делать надо.
До всех описанных выше событий я занимался кокпитом. Консоль матерится на пару текстур, есть подозрение, что dds не любит квадратные текстуры, не кратных 2 в энной степени (таких у меня 2 точно есть- надо их проверить). Однако сам принцип работы кокпита, о котором я писал ранее, на удивление, пока работает и хорошо. Все стрелки, за исключением указателей ориентации, работают,  их прилично и пульсация для них установлена 1. Если в старой версии кокпитов работа стрелочек кушала много (я проверял, отключая зеркала и метку цели), то в новой версии кокпит пока жрет 1 процент логики, в дополнение к паре процентов движка на игровом слое.
Разумеется, какая-то часть ресурсов еще будет съедена теми же зеркалами, меткой цели, СПО и прочей индикацией. однако... В старой версии, по моим подсчетам, кокпит жрал около 20 процентов, а иногда и до 30. Ненормально много, как выясняется. Причина - запись в глобальный словарь ну очень большого количества данных, да еще и с вычислением попутно, прямо в списке, да еще и с вызовом функций типа int. В общем, теперь в списке находятся 11 данных вместо более полусотни - и это только те, которые меняются постоянно. Остальные будут передаваться напрямую на сцену кокпита. И как управлять объектами оверлее с игровой сцены, не дописывая отдельные скрипты для оверлейных сцен я понял относительно недавно и сегодня успешно реализовал. Пока для переключения камер и зума камеры летчика.

Приведу кусочек кода

#Переключения камеры, если камера в кабине, заодно выставляется ее фокус
                if key == bge.events.F1KEY or key == bge.events.F2KEY or key == bge.events.F3KEY or key == bge.events.F4KEY or key == bge.events.F5KEY or key == bge.events.F6KEY or key == bge.events.F7KEY or key == bge.events.F8KEY or key == bge.events.F9KEY or key == bge.events.F10KEY or key == bge.events.F11KEY or key == bge.events.F12KEY:
                   
                    scene.active_camera = cameraPilot
                    #Если оверлейная сцена с кокпитом уже работает, переключаем камеру и там
                    for sceneGame in bge.logic.getSceneList():
                        if sceneGame.name == 'Cockpit':
                            cameraPilotOver = sceneGame.objects['CameraPilotOver']
                            sceneGame.active_camera = cameraPilotOver
                           
                    if key == bge.events.F1KEY:
                        cameraPilot.lens = 50
                        #Если оверлейная сцена с кокпитом уже работает, переключаем камеру и там
                        cameraPilotOver.lens = 50
                           
                    if key == bge.events.F2KEY:
                        cameraPilot.lens = 35
                        #Если оверлейная сцена с кокпитом уже работает, переключаем камеру и там
                        cameraPilotOver.lens = 35

Думаю, понятно. Но с одной оговоркой - на мой взгляд такие фокусы с оверлеем пройдут при нечастом вызове циклов. Я вообще стараюсь циклы вызывать пореже, где это возможно. И по возможности их не использовать вообще. Попутно сделал так, чтобы камера на оверлее при подключении сцены занимала свое место  соответственно нужному расположению в кабине и припаренчивалась к управляющим плейнам, обеспечивающим ее повороты (имитация поворотов головы летчика). Теперь пора подумать о передаче изменяемых данных непосредственно на кокпит, который будет по поступлении команд "мигать" индикацией шасси, отказов систем, и прочая и прочая... И начать писать код для смены уровней детализации и замены мешей.
Пока что скрин кабины выглядит так (я попутно тестировал еще и работу камеры, поэтому взгляд "не прямо").
В общем пока так. Впереди еще подгонка данных по движению, ЛОДы и прочая. А затем - отработка взаимодействия систем вооружения и БРЭО. Кстати о БРЭО. На мой взгляд, использовать скрипт радара все время нерационально. Скорее всего, он будет работать импульсами - раз в столько-то секунд с десятыми долями. Сильно измениться положение оппонентов за это время не успеет, а в ближнем бою у меня и так была отработана методика стрельбы из пушки и пусков ракет малой дальности с ТГСН. Заодно дополнительно можно сделать разные характеристики для разных машин. По частоте работы радара, прежде всего. Про угол обзора и дальность я уж не говорю. Цели должны попадать "под раздачу" при двух условиях - первое - дистанция меньше, чем разрешенная дальность пуска (зависит от высоты, положения цели, ее ЭПР и еще много чего) и второе  - нос самолета должен быть направлен если не строго на цель, то близко к этому - все это у меня в скриптах было, теперь это надо "очистить от шелухи" и привести в удобочитаемый вид с комментариями. И научиться запихивать в класс несколько функций сразу. И заодно сделать самолеты "ограниченно зрячими". В первой версии боты всегда видели игрока, а вот игрок их мог и не увидеть. Теперь же боты будут следовать либо по маршруту, либо к обнаруженной их радаром цели. У игрока появляется шанс "подловить" ничего не подозревающего оппонента, к примеру подойдя сбоку или сзади. Разумеется, на расстоянии этак км в 8 бот будет знать, что творится и позади него (типа имитация взгляда летчика в зеркало заднего вида). Но вот безнаказанно расстрелять группу противника, зайдя ей в хвост, с большего расстояния - это станет вполне реально. Все-таки работу систем прицеливания и обнаружения надо подтягивать поближе к реальности.

суббота, 12 марта 2016 г.

О полезности захламления винчестера или Закрома жестких дисков.

Большая часть данных на любом компе почти всегда лежит мертвым грузом, отнимая место на жестком диске. причиной тому быть может элементарная лень "почистить" или ... жалко удалять, особенно когда-то созданное тобой. у меня случай тяжелый - и то и то... Со временем подзабываешь, для чего нужен тот или иной файл и уже опасаешься удалять. А ну как пригодится?
И пригождается. Раз в квартал, а то и год. Во всяком случае, где-то год, а может и больше, тому назад начал я делать супер-пупер мега-текстуру для символики ИЛС летательных аппаратов. На выходе должно было получиться 64 элемента со всевозможными кругами, крестиками, дугами, ромбами и прочим. Однако мысль тогда совершила резкий поворот и дело я это прикрыл...
Как оказалось, текстура благополучно дожила до нынешних дней и я вновь вернулся к данной теме. Учиняемый мною погром в создании-пересоздании новой версии настоятельно потребовал завершения работы. В итоге получилось 62 элемента (потому что две ячейки оказались по размерам вдвое больше стандартных - 256х256 пикселей. И заняла их символика радиолокационных прицелов РП-21 и РП-22.. Как она работает, я пока не знаю, но сделал, "шоб було". Остальные ячейки заполнили эти самые геометрические фигуры и текстура светящихся лампочек - 6 штук. Далее я попробовал эту текстуру сохранить в формате dds, но, хотя и сохранилась прозрачность, градиентные участки исчезли, так что мегатекстуру сохранил в tarGA. Как и текстуру стекол кокпита. А перед этим была нудная процедура распаковки и очистки старого бленда кабины МиГ-23 от материалов, текстур и логики, потому как новый кокпит будет похож на старый только внешне. Текстуры приборнной доски, фоновых объектов и всевозможных панелей и деталей были опять же сконвертированы , dds - нашефсе, даешь стандарт. По возможности...
Далее три дня подряд - медленно и методично - нанесение текстур, доделка "мегатекстуры", и зачитска бленда от ненужных объектов, точнее, ставших ненужными. Потому что вместо символики ИЛС из мешей с множеством вершин появилась та же символика, но из плейнов с ячейками "мегатекстуры". Лампочки стали несколько более правдоподобными, индикация приобрела более яркие краски, внешний вид ИЛС немного изменился. А вот приборы остались теми же. Был наведен порядок с цифрами - для показателя тангажа и курвиметра - опять dds текстура. Теперь необходимо опять провести "инспекцию" по именам всех объектов, убрать всевозможные 001 или в том же духе и опять-таки провести "приведение в соотетствие" имен объектов и мешей. Да, занудство и педантство, но ктог его знает, что потом потребуется...Может быть и здесь произойдет замена мешей скриптом... А еще остается заморочка с текстовой символикой - старый глюк, при котором буквы за объектом с прозрачной текстурой не видны. Ладно, как-нибудь разберусть. Не хочется городить вторую оверлейную сцену с текстовой информацией ИЛС...
После чего можно доставать из старой версии скрипт кокпита и начинать его дорабатывать под новые требования. Здесь тоже будут изменения. Поскольку в глобальном словаре жизненно необходимо иметь фактически только характеристики движения самолета, остальные же данные можно передавать либо отдельным блоком, либо напрямую на кокпит - это касается тормозов, шасси, стреловидности и прочего. В первой же версии у меня было около 60 параметров в глобальном словаре только для самолета игрока. Слишком жирно. Тем более, что многие повторялись, пусть и "завуалированно". Так что нефиг.
Так сейчас выглядит кабина МиГ-23МФ. При небольшой переделке из нее легко получить кабины  МиГ-23МЛА и МиГ-23МЛД и им подобных. Символика ИЛС у 23-х в истребительном варианте одинаковая, за исключением МиГ-23МС, на котором ИЛС как такового не было. Кабины МиГ-23БН и МиГ-27 всех модификаций также подвергнутся "модернизации", но позже, надо отработать работу скрипта кокпита и поведение МиГ-23МФ в полете. Из самолетов с обычным крылом, первым во второй версии, похоже, будет Су-25. Просто для него кокпит уже тоже был сделан и работал. Его также  необходимо привести к стандарту.
А так сейчас выглядит модель ливийского МиГ-23МФ. Текстуры были опять же извлечены "из закромов". Как видите, привычка ничего не выбрасывать иногда очень даже полезна.

среда, 9 марта 2016 г.

Физика и классы.

Как-то быстро начинаешь привыкать к тому, что еще недавно было для самого себя непонятным и почти магическим. Даже если еще не до конца в этой магии разобрался.
Как справедливо заметили на ГеймДеве, у моих машин в первой версии поведение было излишне резким, дерганым. собственно, это я знал и сам, но без капитальной переделки кода первой версии исправить это было невозможно. А учитывая то, что я просто надстраивал код, дописывая функции и модули довольно-таки хаотично, переделка влекла за собой залезание в лютые дебри. Возможно, из этого что-нибудь и вышло, но сожрало бы уйму времени. Гораздо проще написать код заново, жестко "раскладывая все по полочкам", и используя все лучшее, что сумел наработать ранее.
Физика вращения (самое больное место) теперь задается через Torque, силу, вращающую юнит, заодно стал интенсивно использоваться дампинг для Rotation, что придало вращению юнита плавность и инерционность. Была мысль использовать изменение веса юнита через mass, однако после неудачной попытки я это дело пока отложил, решив воздействовать на поведение самолета через коэффициенты, учитывающие изменения массы топлива, наличия подвесок, и их лобового сопротивления. помимо прочего, сделал еще и плавное убывание-нарастание скорости при выпуске шасси, тормозов и так далее. Раньше скорость менялась практически мгновенно. Все это было достигнуто классами. Собственно, модуль движения юнита состоит из двух функций, которые вызывают и используют классы. Пока классов два - класс управления через клавиатуру и кдасс собственно движения.Выглядит это дело так:

import bge

#Функция, описывающая способ управления ЛА - вызов класса управления - игрок, бот или онлайн
def control():
    cont = bge.logic.getCurrentController()
    own = cont.owner
    scene = bge.logic.getCurrentScene()
    own = cont.owner
    sensor = cont.sensors[0]
      
    #Создание экземпляра класса и его использование
    if sensor.positive:
        if own['bot'] == 0:
            import ClassGamer
            engine = ClassGamer.control(own)
           
#Функция, описывающая полет ЛА - вращение, ускорение, торможение и прочее - физика
def flight():
    cont = bge.logic.getCurrentController()
    own = cont.owner
    scene = bge.logic.getCurrentScene()
    own = cont.owner
    sensor = cont.sensors[0]
   
    #Создание экземпляра класса и его использование
    if sensor.positive:
        if own['crash'] > 0.2:
            import ClassEngineAir
            engine = ClassEngineAir.control(own)

Сами классы  управления и движения полностью здесь я приводить не буду, поскольку это вещи довольно простые - в прошлых постах их создание было описано. "Шапка" и "окончание2 текста класса одинаковы, различие лишь в "начинке". Для описания движения используется стандартный метод расчета "потребной" скорости движения и вращения. Даже не очень сильно отличается от первой версии проекта. Различия лишь в уборке ненужных проперти, и усовершенствовании "работы" остающихся. к примеру, выкинуты проперти направления движения (значения -1, 0 и +1), выкинуты такие проперти, как sdvigkrylo, krylo, krenNapr и тому подобные. Свойство аэродинамической схемы вообще, индицирующее наличие-отсутствие крыла изменяемой стреловидности, объединилось с wing(-1 - КИС отсутствует, от нуля и больше - уже положение крыла). Предполагаю, что при изменении положения крыла ЛТХ будут меняться единоразово, вызовом данных из текстового файла. В итоге будет выброшена куча проперти, оставшихся будет, конечно, много, но до 60 штук, как было в первой версии, надеюсь, не дойдет. Кроме того, отдельными функциями будет вызываться радар, и, скорее всего, шасси. У меня как-то так сложилось, что шасси представляют собой отдельный блок анимированных деталей, который используется чрезвычайно редко. Наверное, стоит его выделить в отдельную функцию. Радар же по идее, должен во второй версии работать импульсами, включаясь через равные промежутки времени, которые для разных самолетов можно задать разными. Раньше радар работал непрерывно, хотя вроде бы и немного кушал, но если есть возможность разгрузить движок от  лишней работы, да еще и весьма существенно, почему нет? Также отдельной функцией будет, по-видимому, анимирование юнита. Вызываться оно будет при небольшом расстоянии от активной камеры, все остальное время это будут либо ЛОДы, либо единый неанимированный меш.
По поводу пейзажа и небосвода. Три года назад Мартинс Упитис выкладывал примеры своих наработок шейдеров неба, земли, воды и солнца. По каким-то причинам он последнюю версию не довел, а кушает она прилично - около 20 процентов. Поэтому от идеи использовать все и сразу я пока отказался, взяв за основу менее навороченную, но гораздо более быструу версию Упитиса, солнце,  как обычно, немного внеся "своего колорита". Солнце у меня привязано к сфере-скайбоксу,  и может совершать движение по небосводу. Во всяком случае, так задумывается. Само солнце - это лампа типа Sun, к которой я рассчитываю привязать спрайт солнца, но необходимо рассчитать изменение его размеров, так чтобы для наблюдателя они оставались постоянными. Имитации эффектов линзы, которую Упитис очень хорошо продемонстрировал в своем творее, я пока делать не буду. Надо подробно разобрать код шейдера, понять, как это все работает и постараться его реализовать. Кстати, сам Упитис ссылался на работы еще одного человека, который занимался написанием шейдеров, и вообще, прошерстив Интернет, я нашел довольно много литературы по написанию шейдеров, но пока стоят более насущные задачи - доводка физики юнита, создание оверлея и кокпита, отработка уровней детализации. Работы довольно много, идет она не слишком быстро, по причине того, что я пытаюсь зафиксировать в комментариях к коду все свои действия и максимально тщательно писать скрипты, чтобы потом не искать судорожно, вспоминая, в каком скрипте и где я разместил то или иное действие.
Во всяком случае, что-то стало получаться...

вторник, 1 марта 2016 г.

О ландшафте серьезно. Подведение итогов.

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

1. Берем плейн, обыкновенный Plane. Подразделяем его (W - Subdivide) на квадраты. Отмечу, что заранее надо примерно представить себе размеры террайна по Х и Y и сам террайн должен быть квадратным в этом плане.  Также заранее надо рассчитать, сколько блоков-квадратов необходимо получить по сторонам террайна - то есть знать величину блока террайна, сам блок должен быть тоже квадратным.
2. Получив требуемый уровень подразделения, ставим размеры заготовки будущего террайна в стандартные рамки. Изначально в Блендере добавляемые плейны имеют размер 2х2х0 метров и Scale 1х1х1. Поэтому полученная заготовка должна быть именно таких размеров - узнано на собственном горьком опыте, если имеются другие размеры - будут искажения при дальнейшей работе. Когда все размеры подогнаны, нажимаем U - Unwrap и проверяем раскладку вершин на УВ-развертке - она должна быть равномерной  - просто квадрат в "мелкую клеточку", не более.
3. Выделяем все вершины заготовки и жмем Ctrl-E - Edge Split.  Происходит разделение выделенных ребер заготовки, но она сама еще представляет из себя единое целое (это очень важно- в этом шаге закладывается основа для дальнейшего "дробления" террайна на блоки).
4. Далее опять выделяем все вершины заготовки и снова применяем W - subdivide. Здесь уже вам решать, насколько будет сложен и детализирован террайн. У меня комп выдерживал 640 тысяч полигонов, но для страховки после каждого шага я сохранял файл, потому что вылеты Блендера  редкостью уже не были.
5. Берем карту высот, заранее подготовленную в ГИМПЕ - какого формата и размера - неважно, главное, чтобы комп выдержал следующую операцию. В Блендере есть такой отличный инструмент, как Displace в модификаторах, который позволяет создать модель на основе карты высот - очень быстро и вполне качественно. Настройки модификатора - на ваше усмотрение - в зависимости от требуемого результата. После нажатия Аpply получаем мини-террайн размерами 2 на 2 метра и очень сильно детализированный. Сохраняем файл.
6. Над террайном подвешиваем камеру (повыше, этак км на 500-700 (да именно так высоко, не забудьте ввести поправки в дальность видения для камеры). Переключаемся в вид из этой камеры. Увеличиваем ландашафт до требуемых размеров по всем осям. У меня размеры террайна - 1000 на 1000 км, поэтому и высота камеры такая, если у вас размеры меньше, камеру можно высоко не поднимать, но ваш ландшафт должен быть видимым целиком!). Далее добавляем материалы и текстуры к ним к нашему террайну.
7. Постоянно после каждого шага сохраняем результат во избежание вылета Блендере и потери результатов. Далее применяем к ландшафту нодовый материал - он позволяет дешево и сердито за котороткое время получить вполне приемлемый результат. Как это делается - смотрим здесь:
http://b3d.org.ua/forum/viewtopic.php?f=30&t=571 Мой коллега denis8424  расстарался. Ноды вообще вещь хорошая, жаль что нет времени для их подробного изучения... Тем более, что в последней версии БГЕ можно некоторые параметры для нодов задавать скриптами.
8. После получения "раскрашенного" террайна начинается самое нудное. Приготовьтесь подождать. Перейдем в режим редактирования меша, выдели все вершины и жмем P - loose party. Оставляем комп в покое - и ничего нетрогаем. Желательно в это время вообще к компу не приставать - у него и без вас заботы хватит. Ландшафт режется на отдельные блоки вдоль уже порезанных нами ребер (см. выще щаг 3). Когда операция завершается, перед вами множество объектов-блоков. Да, забыл написать, что перед разрезанием на блоки назовите свой ландшафт как-то вроде Terrain.000. Получившиеся блоки будут иметь схожие названия, отличающиеся только цифрами.
9. Теперь нам крайне необходимо привести в соотетствие имена мешей и объектов - они должны совпадать и дать каждому блоку свой центр, отличный от нуля - так будет меньше нагрузка на БГЕ. Для этого я применял скрипт

 import bpy

scene = bpy.context.scene

for ob in scene.objects:
    if ob.layers[scene.active_layer] == True and ob.name != 'Camera':
        if 'BigDesert' in ob.name:
               
            ob.name = 'BigDesert'
            ob.data.name = ob.name

В данном скрипте просто производится поиск по части имени блока (у вас может быть какое угодно, вместо BigDesert, но при создании серии ландшафтов лучше использовать что-то одинаковое - не будете же вы грузить сразу пару-тройку разных террайнов).
Для выправления геометрии, в смысле создания новых центров объектов применяется второй скрипт:
 import bpy

scene = bpy.context.scene
for obj in scene.objects:
    if 'Terrain' in obj.name: # здесь может быть другое имя
        obj.select = True
        obj.data.name = obj.name
        bpy.ops.object.origin_set(type = 'ORIGIN_GEOMETRY', center = 'MEDIAN')
        scene.cursor_location = obj.location
        scene.cursor_location[2] = 0.0
        bpy.ops.object.origin_set(type = 'ORIGIN_CURSOR')
        obj.location[2] = 0.0
        obj.select = False
Оба скрипта запускаются с помощью RunScript в текстовом редакторе Блендера.
Можно считать, что ландшафт создан. можно встраивать его в игру.

Процесс встраивания ландшафта в игру к настоящему времени таков:
1. Создаем текстовый файл в той же папке, где лежит ландшафт. Имя выбирайте сами, но лучше сразу принять для себя какие-то стандарты - потом будет легче. В этот файл мы записываем данные о каждом блоке ландшафта - его координаты, и местоположение среди других блоков. О принципе квадратов типа 25-27 я писал в прошлом посте - поймете. Скрипт записи данных в текстовый файл - бге-шный, но одноразовый, в самой игре он не используется. Рассматривайте его, как инструмент, наподобие первых двух скриптов выше.

import bge
cont = bge.logic.getCurrentController()
scene = bge.logic.getCurrentScene()
own = cont.owner

listCoord = [-49,-47,-45,-43,-41,-39,-37,-35,-33,-31,-29,-27,-25,-23,-21,-19,-17,-15,-13,-11,-9,-7,-5,-3,-1,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49]

listIndex = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]

for obj in scene.objects:
    if 'Terrain' in obj.name:
       
        X, Y, Z = obj.worldPosition
       
        try:
            for coordX in listCoord:
                if  (coordX-1)*10000 < X < (coordX+1)*10000:
                    blockX = listCoord.index(coordX)
               
            for coordY in listCoord:
                if  (coordY-1)*10000 < Y < (coordY+1)*10000:
                    blockY = listCoord.index(coordY) 
       
       
            coordText = open(bge.logic.expandPath('//BlockTerrain_2.txt'),'a')
            coordText.write('b|' + str(blockX) + '|' + str(blockY) + '|' + obj.name + '|'+ str(X)+'|'+str(Y) +'|'+ str(Z)+'|'+'\n')
            coordText.close()
        except:
            pass

После отработки скрипта заглядываем в наш текстовый файл с данными. Там будет запись типа:

b|30|29|Terrain.2499|110000.0|90000.0|0.0|
b|45|29|Terrain.2498|410000.4375|90000.0|0.0|
b|46|29|Terrain.2497|430000.4375|90000.0|0.0|
b|45|13|Terrain.2496|410000.4375|-230000.515625|0.0|
b|46|13|Terrain.2495|430000.4375|-230000.515625|0.0|
b|13|29|Terrain.2494|-230000.546875|90000.0|0.0|

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

# -*- coding: utf8 -*-

import bge
scene = bge.logic.getCurrentScene()  
cont = bge.logic.getCurrentController()
own = cont.owner
#Загрузка выбранного ландшафта - временно закомменченная
terrain = bge.logic.globalDict['terrain']
         
class terrain(bge.types.KX_GameObject):   
    def __init__(self, old_owner):   
      
        #Список необходимых в данный момент блоков
        self.listBlock = []
   
        #Вычисление нужного квадрата, центрального блока ландшафта под камерой, целочисленные значения
        #20 - размер блока в км, 490 - поправка сдвиг влево и вниз на "начало отсчета" от левого нижнего угла
        #1000 - чтобы было меньше возни, отбрасываем метры, нас интересуют целые числа в диапазоне от 0 до 49
        activeCam = scene.active_camera
        quadroX = int((int(activeCam.worldPosition[0]/1000)+ 490)/20)
        quadroY = int((int(activeCam.worldPosition[1]/1000)+ 490)/20)
       
        #Отыскиваем и открываем нужный нам текстовый файл с названиями, координатами и значениями квадратов для блоков ландшафта
        #print('//Terrain/' + terrain + '_/Block' + terrain + '.txt')
        #brikeLand = open(bge.logic.expandPath('//Terrain/' + terrain + '_/Block' + terrain + '.txt'),'r')
        brikeLand = open(bge.logic.expandPath('//Terrain/Terrain_2_/BlockTerrain_2.txt'),'r')
   
        #Дальше по маркерам смотрим, что нам нужно
        for string in brikeLand:
            if string[0] == '#':
                continue
            elif string[0] == 'b':
                tempList = string.split('|')
                X = int(tempList[1])
                Y = int(tempList[2])
           
                #Вычисляем индексы квадратов, нужных для вызова
                #Если индексы квадрата по ХУ находятся внутри требуемого диапазона - добавляем название блока ландшафта в список
                #нужных для появления блоков
                if quadroY-2 < Y < quadroY+2 and quadroX-2 < X < quadroX+2:
                    self.listBlock.append(tempList[3])
                   
                    #Обратная проверка - если объекты есть в списке, но их нет в сцене - добавляем (не хватало еще дублей наплодить)
                    for blockLand in self.listBlock:
                        if blockLand not in scene.objects:
                            blockLand = scene.addObject(tempList[3], self)
                            blockLand.worldPosition = [float(tempList[4]), float(tempList[5]), float(tempList[6])]
                           
            #Проверка на наличие лишних блоков ландшафта в сцене
            for landBlock in scene.objects:
                if "Terrain" in landBlock.name:
                    if landBlock.name not in self.listBlock:
                        #Если есть таковые - ликвидируем
                        landBlock.endObject()
                       
        #Закрываем текст с данными террайна           
        brikeLand.close()
       
def mutate(cont):
    old_object = cont.owner
    mutated_object = air(cont.owner)
   
    assert(old_object is not mutated_object)
    assert(old_object.invalid)
    assert(mutated_object is cont.owner)

# Called later - note we are now working with the mutated object.
def update(cont):
    cont.owner.update()

Комментарии на английском - это просто невырезанные строчки из исходных примеров, приведенных в АПИ Блендера. Можете смело удалять. Думаю русские комментарии вполне понятны. И зачем нам понадобился здоровенный список с именами блоков и их данными теперь тоже понятно.
3. Само использование класса в игре. Сами по себе классы  экономят ресурсы и упрощают жизнь (скрипт класса блоков террайна я разместил в пусковом файле игры - это стандартная вещь для всех террайнов). Далее у меня получился террайн из 2500 блоков, размером 1000 на 1000 км, по 50 блоков по Х и Y, в сумме террайн имеет 640 тысяч поликов, каждый его блок имеет размер 20 на 20 км и 256 поликов. В игре надо вызывать 9 или максимум 25 блоков  -образуя видимую камерой часть ландшафта. Для подгрузки всего террайна использовался инструмент LibLoad, о его использовании информации довольно много, так что разберетесь. Сам процесс вызова класса происходит лишь при смене камеры или в строго определенные моменты времени (тут надо рассчитывать в зависимости от скорости перемешения активной камеры). Пока что скрипт работы генерации блоков террайна написан лишь для клавиш ф1-12 (смена камеры),  но общий принцип, думаю, вы поймете. Не забудьте заодно - блоки ландшафта в подгружаемом файле должны быть на неактивном слое!

# -*- coding: utf8 -*-
import bge

def keyButton():
    scene = bge.logic.getCurrentScene()
    cont = bge.logic.getCurrentController()
    own = cont.owner
    sens = cont.sensors['keyButtonCam']
   
    if sens.positive:
       
        for key,status in sens.events:
            #Клавиши переключения камер
            if status == bge.logic.KX_INPUT_JUST_ACTIVATED:
                #Импорт класса террайн - для замены или подгрузки блоков террайна
                if key == bge.events.F1KEY or key == bge.events.F2KEY or key == bge.events.F3KEY or key == bge.events.F4KEY or key == bge.events.F5KEY or key == bge.events.F6KEY or key == bge.events.F7KEY or key == bge.events.F8KEY or key == bge.events.F9KEY or key == bge.events.F10KEY or key == bge.events.F11KEY or key == bge.events.F12KEY:
                    import ClassTerrain
                    blockTerrain = ClassTerrain.terrain(own)

Все. Ландшафт создан, затекстурен, порезан на кусочки, проименован, "взвешен и учтен". Он подключен к игре, используется только нужная в данный момент его часть. Есть еще нюансы насчет использования блоков, на которых ведет бой наземная техника, они должны также присутствлвать изначально, чтобы техника "не проваливалась", но эта задача вполне решаема. Основная часть работы проделана и описана здесь. Заодно может пригодиться не только всем желающим, но и мне, "на всякий пожарный". Сделал, что мог, пусть другие сделают лучше (с). Выражаю благодарность denis8424 за терпение и помощь в написании скриптов-инструментов и вылавливании моих косяков.