вторник, 9 января 2018 г.

Размышления на тему игрового ИИ.

Всех  с прошедшими праздниками, которые вновь сумели пережить... правда, новгодние каникулы закончились не для всех, и явно еще не израсъодована пиротехника, заботливо приберегаемая для встречи Сатрого Нового Года...
Пока что успешно завершена война с анимацией подвижных частей авиатехники - ее последним аккордом стала борьба с элеронами, которая отрабатывалась уже на МиГ-29. Как обычно, казавшаяся простой и легкорешаемая задача сожрала больше времени, чем та, которая признавалась сложной и запутанной. Это я про интерцепторы. Затем последовала стандартизация уже собранных ранее классов самолетов, которая пока не коснулась Ф-16 и Су-25. У Ф-16, кстати, как и у Су-27 имеются "интерцепоторы наоборот" - флапероны, так что там придется менять знаки перед углами отклонения - интерцепторы поднимаются, а флапероны опускаются...
Подошла очередь искусственного интеллекта. Скрипт этого имитатора интеллекта, будем уж честными до конца, у меня насчитывал уже больше пары тысяч строк и отыскивать в нем нужное  место для внесения изменений становилось все сложнее... Поскольку метод дробления больших скриптов на узкоспециализированные модули уже отработан, то супер-скрипт ИИ (в смысле размеров) постигла та же участь. Он раздробился примерно на дюжину модулей, относительно небольших. Пока раздробление прошло начерно - я еще не приступал к отработке связей между отдельными модулями. Но примерная схема уже вырисовывается. Модули можно разделить на несколько категорий.
1. Стандартные модули разворотов юнита и его ориентаций.
-Модуль стандартного движения при крене-тангаже-рыске -приложение сил в определенном направлении.
-Модуль ориентации в пространстве для горизонтального полета. Выравнивание по крену и тангажу, чтоб самолет летел прямо с выдерживанием нужной высоты. Также в этом модуле есть набор высоты и снижение. Некоторые машины имеют ограничения по крену и тангажу (это характерно для тяжелых машин, таких, как транспортники, хотя были уникумы, крутившие "бочку" на Ту-16, но для тяжелых бомберов такая вещь ни к чему, на мой взглядд), так что в этом модуле они так же соблюдаются.
-Модуль с ориентацией на цель - противника или точку маршрута. Ту, думаю, понятно.
-Модуль выдерживания строя. Пока в природе не существует - только в моей голове и весьма смутно.
2. Модули сканирования и выбора сенсоров и вооружения.
-Пока один модуль - в нем есть функции выбора отимального вооружения (подальнобойнее) и подбора к нему сенсора. Также в этом модуле идет сканирование окружающего мира на предмет бодания лбом земли и наличия угроз - например летящей к боту ракеты.
3. Стандартные модели поведения ботов на земле и в полете. Их много, этих модулей...
-Модуль руления на взлет.
-Модуль руления после посадки.
-Модуль взлета.
-Модуль посадки.
-Модуль полета по маршруту.
-Модуль уклонения от атаки. Включает в себя выбор варианта уклоонения от атаки при помощи сочетания маневров типа "горка" с "размезанно бочкой" и тд. А также постановку активных и пассивных помех.
-Модуль атаки цели. в нем имеются подварианты - атака наземной или воздушной цели. и этот модуль будет потихоньку разрастаться по мере наработки опыта.
-Модуль уклонения от столкновения с землей.
-Модуль набора энергии после маневра. Как бы ни был самолет маневрен, но он может потерять скорость на том же вираже и сорваться в штопор, чего допускать нельзя. Поэтому самолет надлежит аккуратно выровнять, врубить форсаж и снова разогнать.

вот пока примерно так.Есть еще любопытные мысли на этот счет. А именно - по поводу сканирования встречи сземлей. Пока у меня имеется один террайн из 2500 блоков. На мой взгляд, это все же жирно, поэтому лучше сделать из пары сотен блоков. Или 400 - макимум. Причем для террайна ввести свой json, в котором можно перечислить положение блока в прсотранстве, его высоту, отметить координаты горных районов и тд. Зачем? А вот зачем...
Как известно, модули стандартных функций БГЕ типа луча много кушают, поэтому лучше лишний раз их не трогать. А зачем врубать этот самый луч, если самолет находится над блоком, самая высокая вершина которого имеет 500 метров, а высотв полета самолета - 2000? Незачем... Вот для этого и нужны данные по высоте блока. Но это еще не все. В горных районах ботам следует соблюдать осторожность и идти с огибанием рельефа - тут пригодится список маршрутных точек в этом районе. Чтобы не впечататься в стену ущелья, например. Там еще придется вводить алгоритм постройки маршрута по этим точкам, но суть, я думаю, понятна.
В свое время denis8424 продемонстрировал в своем блоге приер движения бота по земле без использования физики. Но при этом бот плавно повторяет все неровности ландшафта - используется словарь координат вершин. он генерится после появления ландшафта, но можно попытаться забить этот словарь в json блока террайна. Но тут еще думать надо.
Пока с ИИ придется слегка притормозить - надо раздробить хотя бы наскоро скрипт оружия - он тоже здоровенный и с ним тоже надо решать кое-какие набившие оскомину вопросы...

четверг, 28 декабря 2017 г.

Дао скриптов-2 Анимация и лазейки для ИИ..

()Официальным голосомЖ "Всех с наступающим Новым Годом, товарищи! Пусть Новый Год будет лучше прошедшего (для меня это болеее, чем актуально), но хуже идущего за ним 9это, наверное, актуально для всех)."
С поздравлениями закончили, переходим к делу. Только что, минут десять назад, подопытный кролик (а точнее, уже Очень Опытный Кролик) МиГ-23БН выпустил и убрал шасси. Кк положено, с характерным стоном гидравлики, открытием и закрытием створок, вращением и передвижением многочисленных деталей. На этом, похоже, эпопея с анимациями завершается. В очередной раз, надо заметить.
Постепенно проявилась схема работы скриптов, которая сменит предыдущую. Как я уже писал выше, теперь скрипты в подавляющем большинстве своем раскиданы по тематическим папкам, многие из них раздроблены для удобства на более мелкие модули, занимающиеся исключительно своим делом и не обращающие внимания на соседей.
Прежде чем я добил шасси, мне пришлось изрядно повозиться с анимацией крена.  Анимацию тангажа я сделал с ходу, практически без проблем, как оно выглядит, смотрите в предыдущих постах, там должно быть про анимацию рыска yaw, то жесамое, только используются другие обозначения и и детали самолета. Не оставило особой проблемы справиться и с анимацией стабилизаторов при крене, даже с учетом хитрого механизма отклонения для МиГ-23. У этой машины при стреловидности до 72 половины стабилизаторов работают в режиме "ножницы" с углами отклонения плюс-минус десять градусов. А при максимальной стреловидности они отклоняются на угол шесть  с половиной. Решилась эта проблема так...
#Наличие-отсутствие стабилизатора
    def is_ROLL(self):
        return True
    def is_ROLL_Device(self):
        ROLL_Device = [
                      ["ElvL_",[-0.0087,0.0,0.0],"rotat",-1],
                      ["ElvR_",[0.0087,0.0,0.0],"rotat",-1],
                      ["ElvL_",[-0.0087,0.0,0.0],"rotat",1],
                      ["ElvR_",[0.0087,0.0,0.0],"rotat",1]
                     ]
        return ROLL_Device
    def is_POINT_ROLL(self):
        reper = [-20, 20]
        if self.WINGS > 300:
            reper = [-13, 13]
        #print(reper)
return reper


Этот код находится внутри класса-наследника самолета. Из него можно узнать имена деталей. скорость их отклонения за один тик, а также ограничения по отклонению.  Первая функция говорит о том, есть ли такие детали в принципе. Втораяя перечисляет детали и их параметры, а вот третья... Как видно из кода, она способна выдать ограничения на угол отклонения. Это, на мой взгляд, проще, чем менять списки для деталей во второй функции. Да и скорость отклонения не меняется.
А вот далее пошла война с интерцепторами. Засада заключалась в том, что рабоать должен ТОЛЬКО один интерцептор, но НЕ ОБА ОДНОВРЕМЕННО. Для элеронов подобной проблемы нет. Они работают аналогично стабилизаторам - смотрим выше.
В общем, перпробовав уйму вариантов, я решение все-таки нашел. Пока один интерцептор не займет нулевое положение, второй работать не будет. Учитываются направление крена или его отсутствие...
if self.ROLLwings < 0 and self.rollSelf == -1:
            ROLLwings_Device = [["InpL_",[0.0174,0.0,0.0],"rotat",-1]]
        elif self.ROLLwings > 0 and self.rollSelf == 1:
            ROLLwings_Device = [["InpR_",[-0.0174,0.0,0.0],"rotat",1]]
        elif self.ROLLwings < 0 and self.rollSelf == 0:
            ROLLwings_Device = [["InpL_",[0.0174,0.0,0.0],"rotat",1]]
        elif self.ROLLwings > 0 and self.rollSelf == 0:
            ROLLwings_Device = [["InpR_",[-0.0174,0.0,0.0],"rotat",-1]]
        elif self.ROLLwings < 0 and self.rollSelf == 1:
            ROLLwings_Device = [["InpL_",[0.0174,0.0,0.0],"rotat",1]]
        elif self.ROLLwings > 0 and self.rollSelf == -1:
            ROLLwings_Device = [["InpR_",[-0.0174,0.0,0.0],"rotat",-1]]
       
        #print(self.ROLLwings,self.rotatY)
        return ROLLwings_Device
    def is_POINT_ROLLwings(self):
        reper = [-45, 45]
        if 299 < self.WINGS < 549:
            reper = [-30, 30]
        elif self.WINGS > 549:
            reper = [0, 0]
        #print(reper)
        return reper

Из последней функции можно заметить отключение интерцепторов на максимальном угле стреловидности. Так и должно быть. Хотя можно и просто влепить выдачу False в первой функции. Может, так и сделаю...
Ладно, с анимацией все, переходим к шасси и лазейкам для ИИ.
import bge
#print("animat")
cont = bge.logic.getCurrentController()
own = cont.owner
#Импортируем модуль юнита
#unit_module = __import__(own.unitModule)
scene = bge.logic.getCurrentScene()

import sys
pathFolder = own.unitName + own.unitNation + "/folderPy"
sys.path.append(bge.logic.expandPath("//Aircraft/" + pathFolder))
unitmodule = own.unitNode + own.unitNation + "NodeScript"
unit_module = __import__(unitmodule)


def animat(own):
    audioProp = "NULL"
   
    ########################
    #Анимации механики юнита
   
    #Перекладка крыла
    if own.Temp_WINGS != own.WINGS:
        wings(own)
   
    #Шасси
    if own.Temp_CHASSY != own.CHASSY:
        #Звук
        audioProp = "GEAR"
        if own.Temp_CHASSY > own.CHASSY and own.CHASSY == 98:
            audioGen(own, audioProp)
        elif own.Temp_CHASSY < own.CHASSY and own.CHASSY == 2:
            audioGen(own, audioProp)
        unit_module.CHASSY(
       
Это часть кода  из скрипта анимаций самолета. Он работает для всех машин. Изюминка здесь в том, что отыскивается и импортируется модуль с анимацией шасс по названию, указанному в атрибутах юнита. Смотрим строчки с sys.path  и ниже. В импортированном модуле есть не только шасси, но есть и функция корректировки поведения самолета, КОНКРЕТНОГО самолета, в зависимости от разных условий. Смотрим код из этого "нодового" скрипта.
   
        import bge
scene = bge.logic.getCurrentScene()

def correctData(own):
   
    Airbrake = 0.35*own.AIRBRAKE/100
    Chassy = 0.25*own.CHASSY/100
    own.correctSpeed = 1.0 - Airbrake - Chassy
   
    if own.Temp_WINGS != own.WINGS:
        #Изменения в поведении самолета
        changeWings(own)
        #Сброс подкрыльевых ьаков, если они есть
        if own.WINGS == 2:
            sbrosPTB(own)

Ничего не мешает вставить и сюда в "шапку" импорт модуля искусственного интеллекта из этой же папки. Или более тонко смоделировать поведение КОНКРЕТНО этого типа самолета. Смоделировать что-то вроде национального характера пведения юнитов (есть такая поговорка - есть бойцы, есть военнослужащие, а есть арабы - наши советники на Ближнем Востоке навидались).
В общем, пока чистая бюрократия, картинок новых, извиняюсь, нет. Надеюсь, в Новом году будут.
Еще раз всех с Новым Годом!

понедельник, 18 декабря 2017 г.

Дао скриптов. Перманентный погром, как оптимизация.

"Лев Революции", товарищ Троцкий (ставший потом Иудушкой после изменения политичекой еонъюктуры) как-то провозгласил: "Есть у революции начало и нет у революции конца". Это изречение вполне применимо к моему проекту, хотя, если честно, мне гораздо больше нпавится другое его изречение: "И пусть наши враги знают - на всякую принципиальность с их стороны мы ответим абсолютной беспринципностью!" Но что есть - то есть.
В прошлом посте я писал, по поводу "дробления" огромных кусков кода на удобочитаемые скрипты относительно небольшого объема. Что ж, схема обретает свои очертания. Итак, сначала мы используем священное заклинание import sys. А потом перечисляем требующиеся нам пути к папкам с новыми скриптами.
import bge

import sys
for pathFolder in ["Scene_Scripts","UnitAir_Scripts", "UnitGround_Scripts", "Weapon_Scripts"]:
    sys.path.append(bge.logic.expandPath("//Scripts/" + pathFolder))

Всего-то три строчки привели прямо-таки к тектоническим сдвигам... Постепенно стали исчезать скрипты из пускового бленда, а оставшиеся стали "съехиваться" в размерах, порой всего-то то десятка-другого строчек. А огромные куски кода стали распадаться на мелкие скрипты и скриптики, узкоспециализированные, не интересующиеся жизнью соседей и занимающихся исключительно своим делом.
Сама схема теперь выглядит примерно так. Смотрим по уровням.

1. Первый уровень, видимый в пусковом бленде и там же находящийся. Я его обозвал скрипт-нод. Например CONTROL_UnitAir. Он отвечает за поведение летательного аппарата, безразлично от того, летит ли он или стоит на аэродроме, изображая мишень. Теперь от огромного скрипта остались только пара десятков строчек. Непрервыно в этом скрипте работает лишь система детализации. При условии, что ЛА летит, из папки со скриптами вызывается скрипт UnitAir_engine.

def UnitAir():
    cont = bge.logic.getCurrentController()
    own = cont.owner
   
    if own.controlUnit != "Statist":
        UnitAir_engine.engine(own)
       
    #Работа уровня детализации идет ВСЕГДА
    UnitAir_LODes.LODes(own)

2. Уровень два. Включает два скрипта. Первый - уровни детализации - смена объектов потомков или замена их мешей в зависмости от расстояния. Все то же, о чем раньше писал, просто вынесено в отдельный скрипт. И второй скрипт - описание поведения ЛА. А вот здесь кроется еще одна ниточка, ведущая на третий уровень. Это приводит к тому, что в начале скрипта взываются модкли анимации, модели сенсоров, модкли ИИ или модуль управления игроком. так сказать, "смерть кощея": "Игла в яйце, яйцо в утке, утка в зайце, заяц в шоке". Ну, или в сундуке, который висит на огромном дубе за тридевять земель...

3. Уровень три. так сказать, уровень нюансов. Здесь скриптов гораздо больше и лни помельче. Это, к примеру, описание работы сенсоров самолета (РЛС, теплопеленгатор и тд), описание анимации подвижных деталей (рули высоты, элероны и тд), описание поведения самолета при штопоре, модель разрушения и так далее. В свою очередь, некоторые из этих скриптов ведут все дальге и вниз. На четвертый уровень, если надо.

Вообще главное в этом деле, - точно прописать в вызываемых модулях в "шапке" перед функциями импортирование нужных именно на этом этапе модулей. А поскольку на самом первом этапе мы уже перечислили, какие папки нам нужны, можно обойтись без прописания строчек типа бгеЭспандПатч(чего-то-там). Для профессиональных программистов ничего нового в этом нет, но я-то всего лишь самоучка и пишу эти строчки для таких же, как и я. Ну или позабавить более продвинутых своими героическими штурмами открытх дверей, которых надо просто потянуть на себя, а не пытаться вышибать с налету.

Тем временем, похоже идет к концу разрешение проблемы с анимацией всех без исклюяения подвижных объектов, за исключением шасси. Мне ранее удалось сделать универсальную функцию работы рулей направления, перекладки крыла, открытия фонаря, выпуска-уборки закрылков и предкрылков.. Вроде я об этом писал, потом напишу еще для элеронов, интерцепторов и рулей высоты. Проблема крылась в том, что у многих машин оно отклоняются на больший или меньший угол в зависмости от скорости или стреловидности.  Кажется, теперь понятно, как этого добиться, не сильно напрягая мозг большим количеством строчек кода. Но пока данное "изобретение" нуждается в проверке и об этом напишу в другой раз.
На данный момент неторопливо и осторожно я "растаскиваю" скрипты, не делая слишком резких движений, чтобы не обвалить работающую конструкцию. Надеюсь, в пусковом бленде останется совсем мало скриптов, а все остальное будет лежать в папках, и вытаскиваться не все подряд, а только то, что нужно в данный момент.

вторник, 12 декабря 2017 г.

Прерву несколько затянувшееся молчание...

Некоторое время не писал по причине разом свалившихся проблем, как чисто технических, так и отсутствия вдохновения (бывает со мной такое). Сначала пришлось чистить комп от скопившихся файлов - как дублей, так и устаревших и ставших мусором. Потом воевать с модемом от Билайна - складывается стойкое подозрение, что доблестные связисты зажрались, обленились и перестали ловить мышей - хронически слабый сигнал, но трафик тот же и денбги те же. Только качество стало хуже. А я еще на ни ив чем не повинный модем грешил...
Как всегда, как только я начинаю осваивать новые методы программирования, Вечно Голубое Небо посылает конкретный намек: "Верной дорогой идешь, товарищ!", через наших невероятно трудолюбивых энергетиков, которые проводят "плановые реконструкционно-восстановительные работы на объектах энергообеспечения". Для этого им нужен именно декабрь... Впрочем, см. чуть выше. Два вторника подряд нашу улицу ы числе прочих отключали на весь день. Хотелось бы послущать, что об энергетиках в это время говорили, хотя и так понятно...
Тем временем, проект со страшным скрипом опять сдвинулся с места и пополз вперед. Но для начала мне пришлось опять откатиться назад с шейдерами неба. Причина нетривиальная - перестали нормально работать текстовые маркеры объектов - они стали появляться только в верхней половине экрана. После возврашения старого шейдера неба все опять заработало. Ну ладно, в конце концов небо все равно придется переделывать под свой код...
А затем пришло время новшеств. А именно - назрела необходимость "дробления" скриптов и их вызова из папок. Это позволит разгрузить пусковой файл, сделать скрипты более компактными и понятными, правда, их станет больше. Но все-таки можно теперь не крутить скрипт из полторы тыщи строк, отыскивая нужную функцию и корректируя строчки именно в ней. Особено это касается ИИ ботов. Сурипт раздут очень сильно и его придется разбить на множество "типов поведения" - полет по маршруту, ведение боя, взлет, посадка, уклонение от препятствия и так далее.
Пока что не привожу ни картинок, ни кода, поскольку работа в процессе и код меняется очень быстро, скажу лишь, что с подсказки dron-а используется sys.path(путь к папкам). Этот метод планируется запустить в самом начале при старте игры и в него загонять все нужные пути к папкам со скриптами. Прикол еще в том, что теперь "раздробленные" скрипты при вызове сами вызывают дополнительные надстройки  - еще дополнительные модули, к примеру скрипт для движения юнита тащит за собой модуль анимации подвижных частей, модули управления 9бот или игрок) и модуль сенсоров.
По идее, в самом пусковом файле останутся "узловые" скрипты, которые и будут дкргать цепочки нужных модулей и не все подряд, а только те, что необходимы. Придется, конечно, повозиться, выстраивая новую систему связей, но оно того стоит...
А, да, сделал было приборные панели МиГ-21, но в кабину пока не вставил - см. выше.

пятница, 20 октября 2017 г.

Опытный кролик-2. Новые методы использования классов.

В прошлом посте я писал о своих опытах с МиГ-23БН, который был моим подопытным кроликом в деле отработки новых методов использования классов.
Тогда речь шла всего-навсего о создании класса-наследника с целью "развести" и не допустить "перекрещивания" данных при создании юнитов.
однако совет dron-a по поводу использования методов в самом классе заставил меня задуматься... когда я только начинал осваивать классы, я уже, сам того не зная, писал в них методы, но не знал, как правильно их использовать. видимо, для осознания новых возможностей и способов требуется время и с "лету" схватывать я таки не способен. Но все-таки медленно, но учусь. Для начала приведу пример с использованием методов теле класса на примере класса уже очень опытного виртуального колика - многострадального МиГ-23БН.

import bge

from ClassUnitAir  import UnitAir

class MiG23BN_Libya_(UnitAir):
    def __init__(self, old_owner):
        UnitAir.__init__(self, old_owner)
    #Летательный аппарат
    def is_AIR(self):
        return True
    #Подтип ЛA - вртолет, самолет (реактивный или внитовой)
    def is_AIRTYPE(self):
        return "JET"
    #Пилотируемый или БПЛА
    def is_DRON(self):
        return False
   
    #Одно- или многодвигательный
    def is_MULTIENGINE(self):
        return False
    def is_CHASSY(self):
        return True
   
    #######################################
    def is_CANOPY(self):
        return True
    def is_CANOPY_Device(self):
        CANOPY_Device = [
                        ["Cnp_",[0.0087,0.0,0.0],"rotat"]
                        ]
        return CANOPY_Device
    def is_POINT_CANOPY(self):
        reper = [0, 114]
        return reper
    ########################################
    def is_FLAPS(self):
        return True
    def is_FLAPS_Device(self):
        FLAPS_Device = [
                        ["FlpR_",[-0.0087,0.0,0.0],"rotat"],
                        ["FlpL_",[-0.0087,0.0,0.0],"rotat"]
                        ]
        return FLAPS_Device
    def is_POINT_FLAPS(self):
        reper = [-50, 0, 100]
        return reper
   
   
    ######################################
    #Наличие-отсутствие предкрылков
    def is_SLATS(self):
        return True
    def is_SLATS_Device(self):
        SLATS_Device = [
                        ["SltR_",[-0.0087,0.0,0.0],"rotat"],
                        ["SltL_",[-0.0087,0.0,0.0],"rotat"]
                        ]
        return SLATS_Device
    def is_POINT_SLATS(self):
        reper = [0, 40]
        return reper
   
    #####################################
    #Наличие-отсутствие крыла изменяемой стреловидности
    def is_SWINGWING(self):
        return True
    def is_POINT_WINGS(self):
        reper = [0, 300, 550]
        return reper
   
    def is_WINGS_Device(self):
        WINGS_Device = [
                        ["WngR_",[0.0,0.0,-0.00174],"rotat"],
                        ["WngL_",[0.0,0.0,0.00174],"rotat"]
                        ]
        return WINGS_Device
   
    ####################################
    #Наличие-отсутствие воздушных тормозов
    def is_AIRBRAKE(self):
        return True
    def is_AIRBRAKE_Device(self):
        AIRBRAKE_Device = [
                           ["ArbUR_",[-0.00783,0.0,0.0],"rotat"],
                           ["ArbUL_",[-0.00783,0.0,0.0],"rotat"],
                           ["ArbDR_",[0.00696,0.0,0.0],"rotat"],
                           ["ArbDL_",[0.00696,0.0,0.0],"rotat"]
                          ]
        return AIRBRAKE_Device
   
    def is_POINT_AIRBRAKE(self):
        reper = [0, 100]
        return reper

             
def mutate(cont):
    MiG23BN_Libya_(cont.owner)

Смотрим на функции типа def is_чегго-то-там. Это не что иное, как некие константы - например наличие или отсутствие крыла изменяемой стреловидности, принадлежности к воздушным юнитам, но эти константы, например, могут нести такую ценную информацию, как скорость поворота крыла и его название. Первоначально я хотел ставить не многомерные списки, а словари. Однако, после многократной ругани со стороны консоли БГЕ о невозможности прочесть из функции класса метод с этим самым словарем, решил все же поставить многомерные списки. Посмотрим, мрожет потом я все-таки пойму, где ошибался и поставлю словари.
Что это дает? Это дакт возможность пока для стандартных анимаций (точнее псевдоанимаций, потому как идет просто движение деталей через скрипт) загнать их работу в единый скрипт, избавив бленды моделей от лишнего кода по тем же перемещениям КИС для МиГ-23. Экономия существенная.
Посмотрим, как это работает. Для начала команда на перекладку крыла из скрипта для игрока CONTROL_gamer.

#Смена стреловидности - для самолетов с КИС
    if keyboard.events[bge.events.QKEY] == JUST_ACTIVATED or keyboard.events[bge.events.TABKEY] == JUST_ACTIVATED:
        #Проперти wing у самолетов с КИС имеет минимальное значение 0. При смене стреловидности вызывается класс юнита
        #и проводится смена значений максимальных значений скорости и маневренности
        if own.FLAPS == 0:
            if own.Temp_WINGS == own.WINGS:
                if keyboard.events[bge.events.QKEY] == JUST_ACTIVATED:
                    if own.is_SWINGWING():
                        own.WINGS += 1

Сначала при нажатии клавиши идет проверка наличия крыла изменяемой стреловидности у данного юнита. смотрим is_SWINGWING  в коде выше.Если там стоит True, то команда проходит и начинается самое  интересное. Функция перекладки в скрипте CONTTROL_UnitAir универсальна для всех типов самолетов с КИС. Вообще всех. Такова особенность построения моделей и использования в них названий.
#Псевдоанимации крыльев
def wings(own):
    #Направоение перекладки крыла
    wings = 0
   
    #Смена стреловидности возможна лишь при убранных закрылках
    if own.WINGS > own.is_POINT_WINGS()[-1]:
        own.WINGS = own.is_POINT_WINGS()[-1]
    elif own.WINGS < 0:
        own.WINGS = 0
   
    #Выставление напрaвления перекладки и убывания-возрастания проперти
    if own.Temp_WINGS < own.WINGS:
        wings = 1
        if own.WINGS-own.Temp_WINGS == 5:
            AudioEmitter = scene.addObject('AudioEmitter', own, 220)
            AudioEmitter.setParent(own, False, False)
            AudioEmitter["audioProp"] = "FLAPS"
           
        own.WINGS += 1
   
    elif own.Temp_WINGS > own.WINGS:
        wings = -1
        own.WINGS -= 1
        if own.WINGS-own.Temp_WINGS == -5:
            AudioEmitter = scene.addObject('AudioEmitter', own, 220)
            AudioEmitter.setParent(own, False, False)
            AudioEmitter["audioProp"] = "FLAPS"
       
    #Собственно, движение крыльев - это действие не зависит от уровня детализации
    for key in own.is_WINGS_Device():
        if key[2] == "rotat":
            own.childrenRecursive[key[0]].applyRotation([key[1][0]*wings,
                                                         key[1][1]*wings,
                                                         key[1][2]*wings
                                                         ],True)
   
    #Остановка псевдоанимации на кадрах со значением 0,300,550   
    if own.WINGS in own.is_POINT_WINGS():
        own.Temp_WINGS = own.WINGS

Здесь используются данные о реперных точках положения крыла - там , где должна прекратиться анимация и жестко выставлены ограничения слева и справа. Это is_POINT_WINGS.  А еще используются данные из многомерного списка. я думаю, понятно, как - название детали, скорость поворота, флаг - "поворот".

Остается добавить, что подобные вещи можно проделать с фонарями кабин, щитками тормозов, закрылками и предкрылками. Потому что все это вещи стандартные и неизменяемые. Они либо есть, либо их нет.полагаю, что можно в общем-то все данные по всем деталям загнать в класс и их использовать. Но пока ограничился вышеперчисленным.
Наверняка, в класс можно вогнать и данные по сенсорам, встроенным пушкам, отстреливаемым ловушкам и прочему. Это дело будущего. По идее, все это приведет к сокращению кода. И упрощению кода тоже.

пятница, 13 октября 2017 г.

Опытный кролик и наследство. Классы и новый (небольшой) погром.

Наконец, я добрался до файлов Упитиса и честнго прочитал их. И даже сумел кое-что понять. Но все это касалось скрипта lum.py, а еще надо было разбираться с другими скриптами - для неба и земли, воды и еще мелочью вроде шумов и тп.
Но тут застарелая проблема все-таки выползла наружу и заявила о себе в полный голос.
Да, я вроде как научился создавать экземпляры классов и более-менее вменяемо могу ответить, зачем - для придания игровым объектам свойств, с помощью которых они реагируют на события. В более широком смысле - создания больших групп объектов, со своими схожими атрибутами (вроде полета) и своими ТТХ (скорость, потолок).
Закавыка, однако, заключалась в том, что класс один на все самолеты и вертолеты, артиллерию, танки и так далее. Юниты отличаются лишь своими ТТХ внутри группы.
Видимо, ручки у меня все же не совсем прямые, потому что время от времени происходило странное "перекрещивание". Например ф-15 у себя искал детали МиГ-29, если верить распечатке консоли. Причем этот прикол начинался после внесения изменений и сохранения пускового файла. Стало ясно, что с этим надо что-то делатью Окончательно мое терпение лопнуло после того, как в миссии с МиГ-23 и Ф-16 "двадцатьтретий" отказался менять стреловидность, а его атрибут WINGS стал равен минус 1, как у Ф-16, хотя в json прописан ноль.
И решил я попробовать наследование классов. Спасибо подсказкам dron-a, вроде получилось.
Для начала я создал еще один json и вынес его в папку с самолетами - это был общий файл для всех летательных аппратов. Приводить я его не буду, нет смысла - нудное перечисление через запятую ключей и их нулевых значений. Но все это привело к резкому сокращению скрипта класса ЛА. За счет выкидывания из него этих ключей - идет чтение json.

import bge
import json

with open(bge.logic.expandPath('//Aircraft/AIRCRAFT_CLASS.json'), 'r') as directAircraft:
    JSONaircraft = json.load(directAircraft)

class UnitAir(bge.types.KX_GameObject):
   
    def __init__(self, old_owner):
        self.__dict__.update(JSONaircraft)
               
def mutate(cont):
    UnitAir(cont.owner)

Этот скрипт висит в пусковом файле и выдает создание класса для ЛА. А запускает его функция из другого скрипта, в том же бленде.

def UnitAir(cont):
    own = cont.owner
    nameGeneral = own.childrenRecursive["confaUnit"]["confaUnit"]["unitClass"]
    #unit_module = importlib.import_module(nameGeneral)
    unit_module = __import__(nameGeneral)
    unit_class = getattr(unit_module, nameGeneral)
    x = unit_class(own)

Тут была возня с поиском и импортом нужного скрипта класса-наследника из другого бленда (который загружается при запуске игры - файл модели). К моему удивлению импортлиб скрипт не нашел. А вот поставленная из любопытства строчка с __impot__ом помогла. В скрипте ищется файл с именем класса, который прописан в объекте-потомке confaUnit. Этот объект своеобразный пакет в командирском сейфе - в случае начала БД его вскрывают и читают, что надо делать. Потом уничтожают.
В качестве подопытного кролика был выбран МиГ-23БН. Но это скорее уже опытный кролик - сколько на нем всего опробовалось... Скрипт класса-наследника выглядит так:

import bge

from ClassUnitAir  import UnitAir

class MiG23BN_Libya_(UnitAir):
   def __init__(self, old_owner):
      UnitAir.__init__(self, old_owner)
      #self.__dict__.update(JSONaircraft)
               
def mutate(cont):
    MiG23BN_Libya_(cont.owner)

Скрипт содержится в стороннем файле и надо обязательно вызвать сначала класс-родитель - ClassUnitAir. Точнее скрипт, а из него выдернуть сам класс - UnitAir.
Схема эта сработала и я кинулся вносить строчки с названием класса в json. После чего опробовал МиГ-23МФ и Ф-16. На сей раз никто не искал несуществующие детали и механика работала нормально.
А теперь вот мыслю, пока припрятать наследование класса для дальнейшего использования. А классы наследники сделать самостоятельными. для этого надо слегка поменять аргументы в скриптах и ввести чтение json. Поскольку на мой взгляд, наследование здесь не имеет пока особого смысла. Но оно может пригодиться в других местах и полученный опыт никогда не бвает бесполезным.
Да и попробую резко сократить файл вызова и старта самого класса, если их можно отыскивать и грузить по именам.

понедельник, 9 октября 2017 г.

Отработка действия БЧ снарядов на юниты. Наведение артиллерийских орудий.

В двух предыдущих постах  я писал про взаимодействие снарядов и юнитов. Точнее, воздействие поражающих факторов на сами юниты. Было это все чисто умозрительно и пока не было воплощено на практике, так и оставалось у меня в голове.
Скрипт работы снаряда, точнее, его БЧ (боевой части) был собран не сразу. Точнее, не скрипт, а функция в скрипте CONTROL_Weapon. А когда она была написана, то пришлось поломать голову, почему оно не работает (ну как всегда - там опечатка, там не то имя указал). А главной причиной была нихкая точность - снаряды рвались уж очень далеко от зенитки.  Я использовал миссию теста МиГ-23БН для проверки работы НАР С-8 по зенитке GDF-001 Oerlicon. Выяснилось, что прицельная сетка на самолете не соответствует дальности полета НАР. Пришлось лезть в файл кабины и корректировать ее положение. Теперь, видимо, ту же процедуру надо провести и в остальных машинах. Ничего не поделаешь - до НАР и пушек я просто до сих пор еще не добирался - руки просто не доходили.
Но в конце концов  скрипт заработал полностью в том виде, какой он сейчас есть. Необходимо дописать проверку лучом от эпицентра взрыва до юнита на наличие препятствия (укрытия) на пути ударной волны и осколков. Приведу текст функции для БЧ типа "осколочно-фугасная":
 def OskolFugas(own):
  
    for units in ArbitrGame.UNITS[1]:
        #Проверяем наличие атрибута повреждений у объекта - ударной волне и осколкам все равно, что разрушать
        if hasattr(units, "crash") == True:
            #В зависимости от расстояния подрыва  рассчитывается величина поражающего фактора
            if own.radiusExplode*1.5 < own.getDistanceTo(units) < own.radiusExplode*3:
                own.LEVELS_CRASH = 0.4*own.levelsCrash*own.radiusExplode/own.getDistanceTo(units)
            elif own.radiusExplode < own.getDistanceTo(units) < own.radiusExplode*1.5:
                own.LEVELS_CRASH = 0.8*own.levelsCrash*own.radiusExplode/own.getDistanceTo(units)
            elif own.getDistanceTo(units) < own.radiusExplode:
                own.LEVELS_CRASH = own.levelsCrash
           
            #Вычисление нанесенного урона
            if own.LEVELS_CRASH > units.levelDefens:
                #При условии, что защита "пробита", смотрим на уровень повреждений юнита и вносим коррективы
                if own.LEVELS_CRASH/units.levelDefens < units.crash:
                    units.crash -= own.LEVELS_CRASH/units.levelDefens
                else:
                    units.crash = 0.0
            print(units, units.crash, own.getDistanceTo(units), own.LEVELS_CRASH)

Последнюю строчку можног не принимать во внимание, я ее потом закомментирую или вообще уберу. В ней я отсматривал работу функции. В принципе, все остальные функции типа "проникающе-фугасной", "Фугасной" и прочей БЧ будут работать так же. Разница будет заключаться лишь в градации расстояния для ослабления действия снаряда.
А вот ненаписанные пока функции для кумулятивных БЧ и бронебойно-подкалиберных снарядов будут вести себя по-другому. Там не нужен цикл перебора юнитов сцены - цель одна-единственная и вступают в действие такие факторы, как толщина брони, угол встречи с броней, наличие динамической защиты и ее тип... То же самое, кстиати, относится к пулям стрелкового оружия - они действуют примерно по тому же принципу - скорость, калибр, плюс дальность выстрела. Мда, придется еще как-то по бронепробиваемости в зависимости от дальности , с которой был сделан выстрел, что-то придумывать...
И в конце об артиллерии. Здесь я приведу  скрипт работы артиллерийского орудия. На данный момент на нем работает "Эрликон", но там еще надо смотреть по его точности - в скрипт введен разброс снарядов и ошибки прицеливания. Весьма вероятно, что с этим я переборщил...
import bge
import json
import sys
import random

cont = bge.logic.getCurrentController()
own = cont.owner
scene = bge.logic.getCurrentScene()
ArbitrGame = scene.objects["ArbitrGame"]
#Импортируем модуль юнита
#unit_module = __import__(own.unitModule)
t = 0.0
xS = 0.0
yS = 0.0
zS = 0.0
S = 0.0

newPos = [xS, yS, zS]
import CONTROL_Operations
import CONTROL_Sensor
import mathutils

scene = bge.logic.getCurrentScene()


def UnitArtillery():
    cont = bge.logic.getCurrentController()
    own = cont.owner
   
    ################################################################
    #Просчет уровней детализации
    if own.maxVisibleDist < own.getDistanceTo(scene.active_camera):
        own.levelsDetails = 2
    elif own.maxVisibleDist/10 < own.getDistanceTo(scene.active_camera) < own.maxVisibleDist:
        own.levelsDetails = 1
    elif own.maxVisibleDist/10 > own.getDistanceTo(scene.active_camera):
        own.levelsDetails = 0
    #print(own.getDistanceTo(scene.active_camera))
       
    #Работа с детализацией   
    if own.Temp_levelsDetails != own.levelsDetails:
        CONTROL_Operations.levelsDetailArtillery(own)
        own.Temp_levelsDetails = own.levelsDetails
   
    if own.crash > 0.1:   
        if own.typeMissions != "":
            if own.statusBattery == "Commander":
                own.timerTargetChanged += 1
                if own.timerTargetChanged == 600:
                    targetChangedOwn(own)
                    own.timerTargetChanged = 0
                if own.targetID != "":
                    Ballistica(own)
   
        #Блокировка возможности стрельбы при отсутствии бокомплекта
        if own.BK == 0:
            own.shoot = 0
            own.targetID = ""
            own.PR = 0
       
        #Разрешение на открытие огня
        if own.PR == 1:
            own.shoot = 1
        else:
            own.shoot = 0
   
        #Стрельба
        if own.shoot == 1:
            shooting(own)   
        #Остановка стрельбы
        if own.Temp_shoot != own.shoot:
            if own.Temp_shoot == 1:
                own.Temp_shoot = own.shoot
           
           
           
#Функция выбора ближайшей цели. Используется для зениток и стрельбы прямой наводкой
#по конкретной цели, для стрельбы с закрытых позиций по площадям и квадратам
#используются координаты
def targetChangedOwn(own):
    if own.typeMissions == "AntiAircraft":
        own.targetType = 0
    sceneObjList = ArbitrGame.UNITS[0][own.targetType][own.target]
    tempSortedList = []
    if len(sceneObjList) > 0:
        tempsortedList = sorted(sceneObjList, key = lambda obj:own.getDistanceTo(obj))
        #print(tempsortedList)
        own.targetID = str(id(tempsortedList[0]))
    else:
        own.PR = 0
        own.targetID = ""

#Расчет точки упреждения      
def Ballistica(own):
   
    try:
        target = scene.objects.from_id(int(own.targetID))
        #Тип прицеливания - отслеживание самой цели
        if own.typeCoordTarget == "AimingTarget":
            newPos = target.worldPosition
        #Тип прицеливание с упреждением
        if own.typeCoordTarget == "VisibleTarget":
            # Рассчитываем время полета снаряда.       
            t = own.getDistanceTo(target) / own.speedBullet  
            # Рассчитываем путь перемещения объекта за это время.
            S = target.worldLinearVelocity * t
       
            xS = target.worldPosition[0] + S[0] + random.randrange(-own.razbros,own.razbros)
            yS = target.worldPosition[1] + S[1] + random.randrange(-own.razbros,own.razbros)
            zS = target.worldPosition[2] + S[2] + abs((-10*t*t)/2) + random.randrange(-own.razbros,own.razbros)
            newPos = [xS, yS, zS]
        #print(target)
        Turret(own, newPos)
    except:
        own.PR = 0
        own.targetID != ""

#Управление наведением в горизонтальной плоскости       
def Turret(own, newPos):
    #print(own.PR)
    Turret = own.childrenRecursive[own.unitName + "Turret_"]
    #Данные по ориентации
    orientY = Turret.getVectTo(newPos)[2][1]
    orientX = Turret.getVectTo(newPos)[2][0]
   
    #Скорость наведения
    kZ = own.speedMaxGor
   
    correct = 1.0
    znak = 0
   
    if orientX > 0:
        znak = -1
    elif orientX < 0:
        znak = 1
   
    if orientY > 0.9:
        correct = 1 - orientY
    else:
        correct = 1.0  
   
    Turret.applyRotation([0.0,0.0,kZ*correct*znak], True)
    Canon = own.childrenRecursive[own.unitName + "Canon_"]
    orientZ = Canon.getVectTo(newPos)[2][2]
    kX = own.speedMaxVert
    correctCanon = 1.0
    znakCanon = 0
   
    if orientY > 0.7:
        if orientZ > 0:
            znakCanon = 1
        elif orientZ < 0:
            znakCanon = -1
     
        if abs(orientZ) < 0.2:
            correctCanon = abs(orientZ)
        else:
            correctCanon = 1.0
       
        Canon.applyRotation([kX*correctCanon*znakCanon,0.0,0.0], True)
       
    if abs(orientZ) < 0.1 and orientY > 0.9 and own.getDistanceTo(newPos) < own.distMax:
        #print(own.getDistanceTo(newPos))
        if own.Temp_cassetteBK < own.cassetteBK:
            own.PR = 1
           
        else:
            own.PR = 0
    else:
        own.PR = 0
   
    #Перезарядка - магазин, кассета, обойма или снаряд
    if own.Temp_cassetteBK == own.cassetteBK:
        own.PR = 0
        if own.Temp_cassetteBK > 0:
            if own.Temp_timerCassetteChanged < own.timerCassetteChanged:
                own.Temp_timerCassetteChanged += 1
            elif own.Temp_timerCassetteChanged == own.timerCassetteChanged:
                own.Temp_timerCassetteChanged = 0
                own.Temp_cassetteBK = 0
               
       

   
def shooting(own):
    #Стрельба из пушек, пулеметов и многоствольных систем
    if own.typeShoot == "Zalp":
        for FLAME in own.fireGun:
            addBullet(own, FLAME)

    #Стрельба из РСЗО, РБУ и прочих реактивных(ракетных) систем
    elif own.typeShoot == "Paket":
        FLAME = own.fireGun[own.BK-1]
        addBullet(own, FLAME)
       
    own.BK -= 1
    own.Temp_cassetteBK += 1

#Функция добавления снаряда и прочего - звука, вспышки...           
def addBullet(own, FLAME):
    Canon = own.childrenRecursive[own.unitName + "Canon_"]
    #Вспышка - лампа
    vspyshka = scene.addObject('LampUniversal', Canon, own.gunPausa+1)
    vspyshka.setParent(Canon, False, False)
    vspyshka.localPosition = FLAME
    #Вспышка - меш
    vspyshkaMesh = scene.addObject('AudioGun', Canon, own.gunPausa+5)
    vspyshkaMesh.setParent(Canon, False, False)
    vspyshkaMesh["audioProp"] = own.audioGun
    vspyshkaMesh.replaceMesh("FireUniversal", True, False)
    vspyshkaMesh.localPosition = FLAME
    razmer = own.scaleGun
    vspyshkaMesh.worldScale = [razmer, razmer, razmer]
    vspyshkaMesh.visible = 1
       
    #Снаряд цепляется к самому юниту и происходит мутация
    #Проверяется наличие атрибута вроде калибрСнаряд = 23мм
    #и объект мутирует в своем классе
    dynObj = scene.addObject('UniversalBullet',vspyshka , 300)
    dynObj.worldScale = [1.0,1.0,1.0]
    if "calibr" not in dynObj:
        dynObj["calibr"] = own.gunBullet   
    dynObj.localLinearVelocity = [random.randrange(-own.randomSpeed,own.randomSpeed),
                                  random.randrange(-own.randomSpeed,own.randomSpeed)+own.speedBullet,
                                  random.randrange(-own.randomSpeed,own.randomSpeed)] 

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