вторник, 20 декабря 2016 г.

Освоение новых возможностей. Работа с blf.

В первой версии была у меня система текстовых меток и подсказок. И хотя результат меня худо-бедно устраивал, наличие большого количества дополнительных объектов и второй оверлейной сцены меня несколько напрягало.Но тогда у меня было стремление любой ценой довести проект хотя бы до промежуточного результат и я не стал искать других путей. А они были, между прочим...
Есть в составе инструментов Блендера весьма полезная функция blf, правда, чтобы ей пользоваться, никак не обойтись без дао обезъяны, а именно - для начала иметь хоть какой-то исходный код, который можно сплагиатить, извратить переработать. Такой код с выводом текст а"Хелло Ворлд" в АПИ Блендера имелся.
Однако тут воспоследовали проблемы. Сам текст, понятно, как сформировать, но надо его запихнуть в нужное место экрана, а потом еще и дать ему нужный цвет для удобоваримого чтения. После трехдневной возни с подсказками dron-a, на свет божий явился промежуточный результат. Скрин приводится ниже.



На ИЛС присутствует надпись F-16_цифры. Синего цвета. Это говорит о том, что радар взял на сопровождение F-16C "синих", плюс до него указывается дистанция. В перспективе охота получить возможность маркировать все цели в округе, как свои, так и чужие, плюс иметь возможность отключать или включать это маркирование. Пока вариант весьма сырой, хотя и работающий, поэтому приведу код. Часть первая - скрипт ClassHud с функциями чтения и отсчета экранных координат цели с возможностью вывода положения метки в пределах ИЛС.

import bge

#МЕТКИ ЦЕЛИ НА КОЛЛИМАТОРЕ - ОБЕСПЕЧИВАЕТСЯ ЕЕ ПЛАВНОЕ ПЕРЕМЕЩЕНИЕ ВСЛЕД ЗА ЦЕЛЬЮ  
#Чтение экранных координат    
def readCoord():
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner
    camera = bge.logic.getCurrentScene().active_camera
   
    targetMarka = scene.objects['targetMetka']
    #collimatorRight = scene.objects['collRight']
    #collimatorLeft = scene.objects['collLeft']
    targetLabel = scene.objects['targetMarka']
   
    camera = scene.active_camera
   
    #Метка цели работает только при включенном радаре и выставленной дальности
    try:
       
        targetMarka.worldPosition[0] = bge.logic.globalDict['targetCoord'][0] * 20.0
        targetMarka.worldPosition[1] = -bge.logic.globalDict['targetCoord'][1] * 11.3
       
       
        plusX = targetLabel['limX'] #collimatorRight.worldPosition[0]
        minusX = -targetLabel['limX'] #collimatorLeft.worldPosition[0]
        plusY = targetLabel['limY'] #collimatorRight.worldPosition[2]
        minusY = -targetLabel['limY'] #collimatorLeft.worldPosition[2]
       
        targetLabel.worldPosition[0] = targetMarka.worldPosition[0] - 10.0
        targetLabel.worldPosition[2] = targetMarka.worldPosition[1] + 5.65
       
        if targetLabel.worldPosition[0] < minusX:
            targetLabel.worldPosition[0] = minusX
        elif targetLabel.worldPosition[0] > plusX:
            targetLabel.worldPosition[0] = plusX
        if targetLabel.worldPosition[2] < minusY:
            targetLabel.worldPosition[2] = minusY
        elif targetLabel.worldPosition[2] > plusY:
            targetLabel.worldPosition[2] = plusY
    
    except:
        targetLabel.worldPosition = [0.0, 0.0, 0.0]
        
    import RenderText
    RenderText.init()
   
       
#Передача экранных координат   
def writeCoord():
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner

    try:
        target = scene.objects.from_id(int(own['idTarget']))
       
        camera = scene.active_camera

        #if camera.getVectTo(target)[2][2] > 0:
        bge.logic.globalDict['targetCoord'] = camera.getScreenPosition(target)
        #print(target['unitName'], bge.logic.globalDict['targetCoord'])
       
      
        winXcoord = bge.render.getWindowWidth(target)*camera.getScreenPosition(target)[0]
        winYcoord = bge.render.getWindowHeight(target)*camera.getScreenPosition(target)[1]
        infoTarget = target['unitName'] + str(int(own.getDistanceTo(target)/1000))
        bge.logic.globalDict['targetPixel'] = [winXcoord, winYcoord, infoTarget]
       
        targetDistance = own.getDistanceTo(target)
        targetSpeed = target.localLinearVelocity[1]
        targetHeight = target.worldPosition[2]
       
        bge.logic.globalDict['targetData'] = [targetDistance, targetSpeed, targetHeight]
       
    except:
       
        bge.logic.globalDict['targetPixel'] = [0, 0, '']
        bge.logic.globalDict['targetCoord'] = [0, 0]
        bge.logic.globalDict['targetData'] = [0, 0, 0]
       


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

# import game engine modules
from bge import render
from bge import logic
# import stand alone modules
import bgl
import blf

winXcoord = logic.globalDict['targetPixel'][0]
winYcoord = logic.globalDict['targetPixel'][1]
infoText = logic.globalDict['targetPixel'][2]


def init():
    logic.font_id = 0 #blf.load(font_path)
    scene = logic.getCurrentScene()
    scene.post_draw = [write]
    #print(logic.globalDict['targetPixel'][2])
   

def write():
    width = render.getWindowWidth()
    height = render.getWindowHeight()
  
   
    bgl.glMatrixMode(bgl.GL_PROJECTION)
    bgl.glLoadIdentity()
    bgl.gluOrtho2D(0, width, 0, height)
    bgl.glMatrixMode(bgl.GL_MODELVIEW)
    bgl.glLoadIdentity()
    
    font_id = logic.font_id
    blf.position(font_id, logic.globalDict['targetPixel'][0], logic.globalDict['targetPixel'][1], 0)
    blf.size(font_id, 25, 36)
   
    bgl.glColor3f(0.0,0.0,1.0)
    #bgl.glColor4f
   
    bgl.glDisable(bgl.GL_LIGHTING)
    blf.draw(font_id, logic.globalDict['targetPixel'][2])
   
   
   

вторник, 13 декабря 2016 г.

Цветомузыка СПО.

СПО - сокращение от "Системы Предупреждения об облучении". Ныне эти системы весьма распространены, в том числе и среди автолюбителей, называясь "антирадаром" (чтобы заранее узнать, где сидит злой гаишник, готовый выписать штраф за превышение скорости)...
На самолетах военных же СПО предназначена для идентификации угрозы со стороны вражеских машин, использующих радары. И именно радары - именно на них реагирует СПО. Впрочем, также успешно СПО реагирует на облучение радаров ЗРК, если известна их частота 9в первые дни войны Судного Дня израильтянам здорово досталось от "Кубов",  частоты работы которых, в отличие от ЗРК С-75 и С-125, израильтянам известны не были и СПО их самолетов на работу станций ЗРК "Куб" не реагировали).
Так что СПО не спасет от внезапного выстрела из ПЗРК или неожиданно пущенной в хвост ракеты с ТГСН, которую враг запустил, используя теплопеленгатор. Но вот внезапного пуска ракеты с радийной головкой самонаведения не выйдет в принципе, если СПО зафиксировала работу радара. Причем радар выдает себя на гораздо большем удалении, нежели может охватить - отраженные импульсы с увеличением дистанции теряют силу,  не доходя до приемника, а вот излученные импульсы вполне способны СПО "потревожить" на дистанции, с которой противник просто не сможет принять отреженный сигнал.
все это - небольшое лирико-техническое отступление. В игре СПО только одна - и находится она у самолета игрока, точнее, на оверлейной сцене кабины. Ее единственное назначение - сообщить игроку об угрозе со стороны противника. Причем во второй версии СПО угрозы селектирует по степени опасности. тут играет свою роль не только дистанция до противника, но и его готовность открыть огонь или уже выпустившего ракету.
Ниже приведен скрин работающей СПО-10 "Сирена-3" в кабине истребителя МиГ-23МФ.
 Панель СПО - в белом прямоугольнике.


 Устройство включает в себя по четыре жельые и красные лампы, расположенных по периферии круглой панели и одну большую красную лампу посередине на изображении самолета. На скрине она потушена. Горят одна жельая лампа (правый передний квадрант - угроза спереди справа) и четыре красные лампы - показываюшие расстояние до источника угрозы - в пределах 20-30 км.
Мне неизвестно точно в деталях, как работает СПО-10, поэтому в соответствии работы красных лампочек я не уверен - тут я исходил из индикации работы СПО-15, на которой имеется ряд лампочек, отвечающих за отображение  дистанции до угрозы. Да и индикация СПО-15 тоже куда как более продвинута, о ней я напишу потом, когда до нее доберусь.
Пришлось повозиться со звуками, потому что СПО выдает не только световые, но и звукаовые сигналы земмером, меняющим тон и частоту звучания в соответствии с обстановкой.
А теперь немного кода.Сначала я дописал небольшую функцию для передачи в кабину данных об угрозах.

def SPODATA(own, target):
 
    indexOwn = str(id(own))                                        #Индекс перехватчика
    #indexTarget = str(id(target))                                  #Индекс цели
    distance = own.getDistanceTo(target)/1000                #Дистанция от источника угрозы
    LockOn = own['LockOn']                                   #Режим работы сенсора - облучение, захват, подсветка
    typeSensor = own['localDict']['sensList'][own['typeSensor']]   #Тип сенсора
    targetWectOwn = target.getVectTo(own)[2]                       #Ориентация цели относительно перехватчика
    #ownVectTarget = own.getVectTo(target)[2]                        #Обратная ориентация
    heigtUpDown = own.worldPosition[2] - target.worldPosition[2]   #Показатель превышения-принижения перехватчика перед целью
    PR = own['PR']
    AVTO = own['avto']
   
    #Список харакетирисимкм угрозыдля работы СПО оверлейной сцены
    SPODATA = [indexOwn, AVTO, PR, LockOn, distance, typeSensor, targetWectOwn, heigtUpDown]
   
    #Глобальный словарь
   
    bge.logic.globalDict['SPODATA'].append(SPODATA)

Думаю, в комментариях к коду понятно, ради чего все это нужно. Через определенные промежутки времени идет обнуление данных - многомерный список "пустеет", чтобы не засорять память. А "на том конце провода" идет селекция угроз из списка и работает индикация.  Звкеи работают в другом модуле, его я приводить не буду, поскольку ЕМНИП, я уже выкладывал тут его код. Саму функцию выбора звука я чуть доработал, но принцип все тот же - выбирается актуатор по НАЗВАНИЮ, задаваемый проперти audioProp.
Ну, в этой функции и задана работа ламп индикации по принципу "видно- не видно". СПО-10 система относительно простая, даже примитивная, гораздо сложнее ее СПО-15, правда, там индикация более подробная.
Итак, код работы СПО-10.

#SPODATA = [indexOwn, AVTO, PR, LockOn, distance, typeSensor, targetWectOwn, heigtUpDown]

def spo10():   
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner
   
    #Это списки по степени угрозы
    listAVTO = []
    listPR = []
    listLockOn = []
    listNotThreat = []
   
    SelectThreatList = bge.logic.globalDict['SPODATA']
   
    #Список ламп СПО-10
    quad1 = scene.objects['SPOquad1']
    quad2 = scene.objects['SPOquad2']
    quad3 = scene.objects['SPOquad3']
    quad4 = scene.objects['SPOquad4']
    SPOalarm = scene.objects['SPOalarm']
    SPOdist5km = scene.objects['SPOdist5km']
    SPOdist10km = scene.objects['SPOdist10km']
    SPOdist15km = scene.objects['SPOdist15km']
    SPOdist20km = scene.objects['SPOdist20km']
   
    Cockpit = scene.objects['Cockpit']
   
    lampQuadrant = [quad1,quad2,quad3,quad4]
    lampDistance = [SPOdist5km,SPOdist10km,SPOdist15km,SPOdist20km]
   
    SPODATA = bge.logic.globalDict['SPODATA']
   
    #Если есть что  обрабатывать
    if len(SPODATA) > 0:
        for threat in SPODATA:
           
            #Если просто облучают
            if threat[1] == 0:
                listNotThreat.append(threat)
           
            #Если взяли на сопровождение
            elif threat[1] == 1:
                listNotAVTO.append(threat)
               
                #Если смаолет игрока захвачен и противник готов применить оружие
                if threat[2] == 1:
                    listPR.append(threat)
               
                #Если противник уже выпустил ракету и подсвечивает ее
                if threat[3] == 1:
                    listLockOn.append(threat)
                   
    #Теперь селектируем приоритетную угрозу в списке SelectThreatList                  
    #Идет сортировка по степени угрозы, выбирается сначала самая опасная
    if len(listLockOn) > 0:
        SelectThreatList = sorted(listLockOn, key = lambda obj:obj[4])
    else: 
        if len(listPR) > 0:
            SelectThreatList = sorted(listPR, key = lambda obj:obj[4])
        else: 
            if len(listAVTO) > 0:
                SelectThreatList = sorted(listAVTO, key = lambda obj:obj[4])
            else: 
                if len(listNotThreat) > 0:
                    SelectThreatList = sorted(listNotThreat, key = lambda obj:obj[4])
                else: 
                    SelectThreatList = []  
   
    #Если угрозы отсутствую, гасим все лампы на панели СПО       
    if len(SelectThreatList) == 0:
        for lampQuadrants in lampQuadrant:
            lampQuadrants.visible = 0
        for lampDistances in lampDistance:
            lampDistances.visible = 0      
    else:
        #А иначе, начинаем обработку обстановки
       
        #Лампа ракетной атаки противника
        if SelectThreatList[0][3] == 1:
            SPOalarm.visible - 1
        else:
            SPOalarm.visible = 0
       
        #Лмапы дистанции до угрозы   
        if SelectThreatList[0][4] > 30:
            lampDistance[0].visible = 0
            lampDistance[1].visible = 0
            lampDistance[2].visible = 0
            lampDistance[3].visible = 0
       
        elif 20 < SelectThreatList[0][4] < 30:
            lampDistance[0].visible = 1
            lampDistance[1].visible = 1
            lampDistance[2].visible = 1
            lampDistance[3].visible = 1
           
        elif 10 < SelectThreatList[0][4] < 15:
            lampDistance[0].visible = 1
            lampDistance[1].visible = 1
            lampDistance[2].visible = 1
            lampDistance[3].visible = 0
           
        elif 5 < SelectThreatList[0][4] < 10:
            lampDistance[0].visible = 1
            lampDistance[1].visible = 1
            lampDistance[2].visible = 0
            lampDistance[3].visible = 0
           
        elif SelectThreatList[0][4] < 5:
            lampDistance[0].visible = 1
            lampDistance[1].visible = 0
            lampDistance[2].visible = 0
            lampDistance[3].visible = 0
           
        #Лампы квадрантов
       
        #Слева спереди
        if SelectThreatList[0][6][0] < 0 and SelectThreatList[0][6][0] > 0:              
            lampQuadrant[0].visible = 0
            lampQuadrant[1].visible = 1
            lampQuadrant[2].visible = 0
            lampQuadrant[3].visible = 0
      
        #Справа спереди
        elif SelectThreatList[0][6][0] > 0 and SelectThreatList[0][6][0] > 0:              
            lampQuadrant[0].visible = 1
            lampQuadrant[1].visible = 0
            lampQuadrant[2].visible = 0
            lampQuadrant[3].visible = 0
          
       
        #Справа сзади
        elif SelectThreatList[0][6][0] > 0 and SelectThreatList[0][6][0] < 0:              
            lampQuadrant[0].visible = 0
            lampQuadrant[1].visible = 0
            lampQuadrant[2].visible = 1
            lampQuadrant[3].visible = 0
           
        #Слева сзади
        elif SelectThreatList[0][6][0] < 0 and SelectThreatList[0][6][0] < 0:              
            lampQuadrant[0].visible = 0
            lampQuadrant[1].visible = 0
            lampQuadrant[2].visible = 0
            lampQuadrant[3].visible = 1

        #Звуковая сигнализация
        if len(SelectThreatList) == 0:
            Cockpit['audioProp'] = 'NULL'
            Cockpit['volume'] = 0.0
            Cockpit['pitchSound'] = 0.0
        else:
            if SelectThreatList[0][3] == 1:
                Cockpit['audioProp'] = 'InfoLaunch'
                Cockpit['volume'] = 1.0
                Cockpit['pitchSound'] = 2.0
            else:
                if SelectThreatList[0][2] == 1:
                    Cockpit['audioProp'] = 'InfoLock'
                    Cockpit['volume'] = 1.0
                    Cockpit['pitchSound'] = 3.0
                else:
                    if SelectThreatList[0][1] == 1:
                        Cockpit['audioProp'] = 'InfoLock'
                        Cockpit['volume'] = 1.0
                        Cockpit['pitchSound'] = 1.0
                    else:
                        Cockpit['audioProp'] = 'InfoLock'
                        Cockpit['volume'] = 1.0
                        Cockpit['pitchSound'] = 0.0

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

Цветомузыка СПО.

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

четверг, 8 декабря 2016 г.

Пробуждение искусственного разума.

В этом посте не будет картинок. по причине довольно рутинных вещей на этих самых картинках.
После ударного труда над подключением F-16, и особенно отработки размещения бомб и ракет типа "Мейверки" на пилонах внешней подвески встал извечный вопрос. Немного похожий на извечный русский вопрос: "Что делать?" Точнее, как мне кажется, извечный вопрос игродела: "Что делать дальше?". Пара оппонентов г7отовы - Ф-15 и Ф-16. МиГ-23 и Су-25 вроде как тоже готовы. Хочешь не хочешь, а надо браться за искусственный интеллект. Для опробования смены уровней детализации хватало примитивногог рефлекса для бота - тот тупо разгонялся и пер себе по прямой, позволяя камере кружить вокруг него и выискивать недочеты. Но рано или поздно большая часть недочетов устраняется и приходится заниматься уже искусственным интеллектом.
Для начала я попробовал несколько усложнить маршевый режим. На высоте менее 65 процентов от предельной для бота в скрипте я выставил ему увеличение мощности до 900 из 1100 и прописал увеличить тангаж, но не слишком сильно, насовав в код ограничений:

def marsh():
    scene = bge.logic.getCurrentScene()
    cont = bge.logic.getCurrentController()
    self = cont.owner
   
    orient = self.worldOrientation[2][1]
   
    ROLLnull(self)
   
    if self.worldPosition[2] < self['heightMax'] * 0.65:
        if orient < self['limitPitch'] * 0.3:
            upPitch(self)
        elif orient > self['limitPitch'] * 0.3:
            #downPitch(self)
            self['rotatX'] = 0.0
        self['localDict']['etalonDvig'] = 400
    else:
        PITCHnull(self)
        self['localDict']['etalonDvig'] = 900
      
После ряда подгонок и вылавливания мелких ошибок я с удовлетворением наблюдал на экране, как Ф-16 послушно задрал нос и полез вверх. Причем, как и хотел - не строго вертикально, а под некоторым углом.
Набрав нужную высоту, истребитель начал опускать нос и пробил звуковой барьер. Но ненадолго, - он начал постепено сбрасывать тягу до 400, как и прописано в скрипте. Сбросив тягу он полетел дальше, по горизонтали, выровнявшись по глобальным осям. Первые зачатки искусственного разума проявились...
Далее быстренько насовав ограничителей по тем функциям, где картина уже более-менее ясна, типа набора энергии, при котором надо включить форсаж, убрать тормоз и выровняться, как и в описанном выше случае, я задался вопросом применения оружия. Но прежде чем егог применять, боту еще надо выбрать6
1. Тип работы прицела - "земля" или "воздух".
2. Выбрать ВСЕ оружие, пригодное в данном случае,
3. Из всего арсенала выбрать что-то подальнобойнее.
4. Включить нужный сенсор, ибо, к примеру УРВВ Р-24Р нужна обязательная подсветка БРЛС, а вот Р-24Т абсолютно все равно, получит ли она целеуказание от БРЛС или теплопеленгатора. И подсвечивать ей цель не надо.

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

 def sensorBot(self):
    listBotWeapon = [[],[],[]]
    listWeaponTemp = []
    #Выбор режима работы прицельных систем - воздух или земля
    if self['tipMissions'] in battleAir:
        self['targetType'] = 0
    elif self['tipMissions'] in battleGround:
        self['targetType'] = 1
       
    for weapon in self.childrenRecursive:
        if 'weapon' in weapon:
             
            if weapon['childBK'] > 0:
                #Если имеется такое проперти, то этот тип оружия -
                #либо управляемые или неуправляемые ракеты или же управляемые бомбы
                if 'distMax' in weapon:
                    #все управляемое оружие имеет приоритет по очередности использования
                    #заносим в сегментр 1 многомерного списка (то есть нулевой)
                    if 'tipGSN' in weapon:
                        #Кроме наличия ГСН, необходимо, чтобы режим работы прицела соответствовал
                        #предназначению оружия - земля или небо
                        if str(self['targetType']) in weapon['typeTarget']:
                            listBotWeapon[0].append(weapon)
                   
                    #а вот ракеты НАР, блоки НАР ставим с ледующий сегмент
                    elif 'tipGSN' not in weapon:
                        listBotWeapon[1].append(weapon)
               
                #Это - для всевозможных кассет, бомб и зажигательных баков
                elif 'distMax' not in weapon:
                    listBotWeapon[2].append(weapon)
                """
                Таким образом выстраивается оечередность использования вооружений
                1. Управляемое оружие - ракеты и бомбы
                2. Неуправляемые ракеты - одиночные и блоки
                3. Всевозможные боеприпасы СВОБОДНОГО падения
                4. Пушки и пушечные контейнеры - для них значение проперти weapon - нулевое
                """
      
    #А далее - сортируем пог дальности действия оружия - выбираем самое дальнобойное.   
    for objWeaponListSegment in listBotWeapon:
        #Сначала сортируем список управляемого оружия
        if len(listBotWeapon[0]) > 0:
            listWeaponTemp = sorted(listBotWeapon[0], key = lambda obj:obj['distMax'])
            self['weapon'] = listWeaponTemp[-1]['weapon']
        #Теперь - неуправляемые ракеты и блоки НАР
        elif len(listBotWeapon[1]) > 0:
            listWeaponTemp = sorted(listBotWeapon[1], key = lambda obj:obj['distMax'])
            self['weapon'] = listWeaponTemp[-1]['weapon']
        #И затем - неуправляемые боеприпасы свободногог падения   
        elif len(listBotWeapon[2]) > 0:
            listWeaponTemp = sorted(listBotWeapon[2], key = lambda obj:obj['distMax'])
            self['weapon'] = listWeaponTemp[-1]['weapon']
        #Если вообще ничего нет, то переходим на пушки
        elif len(listBotWeapon[2]) == 0:
            self['weapon'] = 0 
   
   
    #Последний штрих - оружие-то выбрано и режим работы прицела, но надо еще и сенсор выбрать
    #для сопряжения оружия, режима работы сенсора и типа самого сенсора. Есть РЛГСН,которым нужна
    #только РЛС для работы, а вот целеуказание ИКГСН можно и РЛС задавать и теплопеленгатор
    #использовать, так что этот момент тоже весьма важен      
    for sensorUnit in self['localDict']['sensList']:
        if sensorUnit in listWeaponTemp[-1]['typeSensor']:       
            self['typeSensor'] = self['localDict']['sensList'].index(sensorUnit)
           
    #print(self['typeSensor'], self['unitName'],self['weapon'],self['nameWeapon'])

Для облегчения понимания того, что здесь наворочено, написано много комментариев. Это освежит и собственную память. И да, надо вставлять пояснения, откуда берутся проперти типа ['localdict']['listSensor'], пготому как оно появляется при старте игры во время загрузки юнита и в другом скрипте. Ничего не поделаешь, нынешняя боевая техника - вещь сложная(хотя если делать модель персонажа, то там проперти наберется как бы не больше).

Запустив очередной тест игрового файла я дождался загрузки и нормального старта без ругани консоли (это произошло где-то с третьей попытки после отлова синтаксических ошибок), посмотрел распечатку принат в консоли. Она гласила - 1 F-16C_ 2 AIM-120C, что в переводе на человеческий означало:
1. Сенсор из списка - по номеру второй от начала, то есть 1 после нулевого. Это - БРЛС.
2. Тип бота - Ф-16Це.
3. Выбрана вторая пара подвесок.
4. Имя выбранного оружия - АИМ-120Це АМРААМ.

ЧТД.

Поскольку в легенде миссии я прописал на Ф-16 на первую пару АИМ-7, на вторую - АМРААМ, а на третью - "Сайдуиндеры". Самые дальнобойные-то и выбрались.  Для них нужна работа БРЛС, и она также включилась.
Далее, необходимо обеспечить многочисленные нюансы. Разворот бота в сторону цели, корректирование его ориентации в пространстве. Очень тщательно выверить работу взаимодействия сенсоров юнитов между собой - в первую очередь. Все дело в том, что необходимо сообщать в случае работы сенсоров о совем местонахождении. Самолет, облучающий перед собой участок неба, буквально орет на весь район о своем присутствии. И наоборот - тихо (ну ладно, громко летящий) у самой земли перхватчик, включивший вместо БРЛС теплопленгатор, не насторожит оппонента, прущего где-то вверху и стороне от его курса. И противник поймет, что что-то не так только после взрыва ракеты с тепловой ГСН...
Но ботам я пока стрелять не дам. Сначала необходимо отработать списки угроз, их обновление и структуру, отработать СПО с ее истерикой - мерзким зуммером и миганием разноцветных огоньков, а уж потом дать возможность стрелять противнику.
такая вот диспозиция на сегодняшний день.

понедельник, 28 ноября 2016 г.

И снова - записки бюрократа...

Давно я так плотно не работал с Geany... поскольку на подходе нарисовался F-15 Eagle, а характеристики многочисленных AIM-7/9/120 зияли пустотой, оставленные на потом, пришлось срочно  набивать джейсоны для этих (и не только) ракет "воздух-воздух". Помимо американских ракет я "раздал слонов" также нашим, плюс английским, французским, итальянским и израильским УРВВ (про английские зря я написал во множественном числе, потому как присутствует только одна ракета - Skyflash).
Работа велась по принципу "эталона". Брался аналог той или иной модели другой страны и по прикидкам и характеристикам в монографиях и всевозможных вики печатались ТТХ, в том числе скорость реакции слежения, устойчивость к помехам и тд. Понятно, что кроме дальности и скорости с массой ракеты, все остальные данные брались в общем-то "от балды", но джейсоны хороши тем, что если очень надо, достаточно открыть файл и изменить в нем нужные цифры, причем в строчке понятно, о чем идет речь.
В результате были "осчастливлены" ракеты следующих семейств:
Р-3/13/60/73/33/37/40/27/77/98/23/24,
AIM-7/9/120,
Python3/4/5,
Shafrir1/2,
Skyflash,
Matra Super 530D/F,
MICA-EM/IR,
Aspide Mk1/2,Matra R550 Magic.
Все? Кажись, все. Нет ракет типа Р-4, юаровских "Кукри", китайских PL-7/9/12 и шведских клонов. Ах да, еще есть "Метеоры" и "Ирис-Т", общеевропейские, так сказать. АМРАМ - 8 штук, Р-27 - 7 штук, "Сайдуиндеров" - около 15, дюжина (или уже больше?) - "Спэрроу". Много в сумме, короче получается.
А далее наступил черед подключения Ф-15... Сначала пришлось подправлять имена в файле модели. Затем спешно писать json для баков и пилонов самолета, поскольку БГЕ пребывал в недоумении, пытаясь понять, что от него хотят. Потом новый юнит вдруг полетел хвостом вперед и вниз, причем скорость начала приближаться к субсветовой... Как оказалось, у одной детали не был убран статки в настройках, а поскольку она оказалась внутри кубика-двигателя, тот попытался ее "выплюнуть" и "подавился". Наконец "Игл" полетел прямо вперед и строго по горизонтали. Включил внешнюю камеру обзора. Тому, что я увидел, в русском языке соответствует цензурное слово "фигня" (но есть много других, не столь благозвучных и начинающихся на "х"). Ракеты, баки и летчик сдвинуты вперед и вверх. Причем фигура летчика парит в воздухе перед носовым обтекателем.
Сначала я подумал, что где-то переборщил со шкалой потомков. Проверил - все норамльно. После нескольких попыток понять, что это было, отложил на сегодняшнее утро. Как оказалось, утро действительно мудренее вечера. Ошибку нашел почти сразу - несовпадение центра промежуточного потомка с нулевыми координатами. После чего вновь запустил тест и с гордостью пронаблюдал результат:

Сегодня успел основательно поработать над моделью F-16C, для которой теперь надо сделать текстуры формата dds и изменить скрипт (обычно я беру аналогичный скрипт из другого бленда и его изменяю под новую модель).
Что касаемо кабин, то здесь дела обстоят не столь радужно. Если центральную панель "Игла" еще как-то более-менее похожей на оригинал сделать можно, то с боковыми - просто беда. Для F-16 все обстоит гораздо лучше, подробных панелей и схем гораздо больше. Вероятно, придется делать версии кабин, а потом их обновлять по мере улучшения качества картинки.

Сегодня

аСе


четверг, 10 ноября 2016 г.

Первый сбитый, сверхзвук, штопор и подопытные кролики.

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


среда, 26 октября 2016 г.

Дао дятла, змеи и обезьяны.

Опять озадачиваю читающих эти строчки столь вычурным названием. Между тем, речь пойдет о кодинге, точнее о стиле, котрый еще именуют быдлокодингом.
Как известно, в восточных боевых искусствах существуют  "животные стили", адепты которых подражают поведению различных животных, дающих название тому или другому стилю. Програмирование -это тоже искусство и в нем тоже есть свои стили.
я лично использую в основном стиль дятла. Суть его состоит в штурме незапертой двери путем методичной долбежки стены рядом с ней. Код работает, он большой, такой, ято порой самого себя перестаешь спустя месяц понимать. В коде много повторов, выглядит он монолитно и впечатляюще.
Иногда, как недавно с форматом json, дверь оказывается запертой на сломанный замок, а пробить стенку "клювом своего кода"(о, завернул...) не удается, ибо стенка железная и дверь тоже. И тогда на помощь приходит обезьяна. Недаром же говорят: "Что человек - то и обезьяна". Наверное, именно этот способ - лучший путь к обретению новых знаний - почитать чужой код, поюзать чужие примеры, поковыряться в том, что когда-то сделали до тебя. Но иногда, как с json, не помогает и это. И тогда на помощь приходит змея. Достаточно отыскать небольшую щелку, чтобы пролезть туда, куда тебе ну очень надо. Я не слишком люблю этот стиль, потому что конструкция, выстраиваемая на этом принципе, громоздка и хрупка (но просто деваться некуда.).
Однако оставим json, и перейдем к текущим делам. dron не раз уже говорил о разбитии скрипта на множество мелких функций, которые имеют узкую специализацию и легко поддаются коррекции. Каюсь, до недавнего времени я понимал его предложения не до конца, но потом, после очередного штудирования скрипта радара и его "разборке-сборке" (Обезьяна в действии), до меня все же стало кое-чего доходить...
Сейчас идет работа над скриптом оружия, в который, кроме модели движения, должны присутствовать модель нанесения урона, эффектов взрыва, звуков и прочей мелочи, такие вещи, как самонаведение. Методов самноведения много. Как и головок самонаведения - иннфракрасные (ИКГСН), радилокационные активные (АРГСН) и полуактивные (ПАРГСН), лазерные, телевизионные инерциальные и так адалее.
Пока идет работа над ГСН УРВВ радийными и тепловыми. Алгоритм таков. в случае наличия проперти в объекте с названием typeGSN вызывается функция-коммутатор GSN. в ней производится проверка на поападание цели в обзорный конус, затем вызывается функция с названием типа ГСН, например PARGSN.  В этой функции по очереди вызываются СТАНДАРТНЫЕ функции-ограничители - по высоте, дальности, ракурсу, превышению-принижению цели, по воздействию помех, наличию препятствий и так адлее. Все дело в том, что головки самонаведения имеют различный набор ограничений - РЛ ГСН, напирмер, способны увидеть цель в облаках, а ИК ГСН в облаках "слепнут", зато им не нужна подсветка цели, как ПАРГСН. Но функции-то проверки на тип препятствия ОДИНАКОВЫ! Зачем повторять код из функции в функциюв самом модуле? Достаточно просто перечислить условия для каждого типа ГСН... Ниже я привожу сам код. Он большой, многое еще предстоит сделать, но общий принцип ясен.

# -*- coding: utf8 -*-

import sys
import bge

#Скрипт для обеспечения работы оружия - прежде всего его воздействии на юниты, движения, вызов эффектов взрывов, расчете урона
#Самонаведение планируется все же использовать в скрипте ClassSensor. Подлежит доработке в классы, с мутацией объекта.

#Класс снаряда для пушки или пули для стрелковки
class bulletGun(bge.types.KX_GameObject):
    def __init__(self, old_owner):
        bulletObj = scene.addObject('dynObj',self, 300)      #Добавляем сам снаряд
        bulletObj['calibr'] = self['calibr']                 #Снаряду даем проперти "калибр", дальше он приобретает необходимые свойства
        bulletObj.setParent(self, False, False)              #Временно парентим снаряд к источнику, чтоб летел правильно
        trasser = scene.addObject('UniversalMesh',self)      #Добавляем трассер к снаряду
        trasser.setParent(bulletObj, False, False)           #Парентим трассер к снаряду
        trasser.replaceMesh('Trasser', True, False)          #Меняем меш плейна на меш трассера
        self['BK'] -= 1
        #Возможно, стоит подумать просто над заменой меша у самого снаряда, но нужно еще выставить worldScale
       
    #Это - пушка ГШ-23Л   
    def GSh23L(self):
        self.childrenRecursive['dynObj'].removeParent()
   
def init(cont):
    sys.modules["bulletGun"] = bulletGun(cont.owner)
   


   
#Функция, задающая траекторию движения динамического объекта - баллистическая кривая
def traectory():
    import mathutils
    #Объект - dynObj, сцена - Scene, слой 2
    scene = bge.logic.getCurrentScene()
    cont = bge.logic.getCurrentController()
    own = cont.owner
    sens = cont.sensors[0]
   
    if own.worldLinearVelocity != mathutils.Vector((0.0, 0.0, 0.0)):
        own.alignAxisToVect(own.worldLinearVelocity, 1, 1.0)
       
#Функция двигателя ракеты
def engine():
    import mathutils
    #Объект - dynObj, сцена - Scene, слой 2
    scene = bge.logic.getCurrentScene()
    cont = bge.logic.getCurrentController()
    own = cont.owner
    sens = cont.sensors[0]
   
    own.localLinearVelocity.y = own['speed']
   
    if 'typeGSN' in own:
        GSN()
    

#Функция-коммутатор головок самонаведения
def GSN():
    import mathutils
    #Объект - dynObj, сцена - Scene, слой 2
    scene = bge.logic.getCurrentScene()
    cont = bge.logic.getCurrentController()
    own = cont.owner
   
    #Проперти, задействованные в этой функции
    #['sensRLscanTarget']         #Типы распознаваемых целей(0 - только небо, 01 - небо и земля, 1  - только земля, проперти строчное)
    #['sensRLscanDistMax']        #Максимальная дистанция обзора
    #['sensRLscanAngle']          #Угол обзора
    #['sensRLscanTimer']          #Время сканирования
    #['sensRLscanCanal']          #Количество одновременно сопровождаемых целей
    #['sensRLscanDeadZone']
    if 'timerImpulse' not in own:
        own['timerImpulse'] = 0
   
    own['timerImpulse'] += 1
    if own['timerImpulse'] > 100:
        own['timerImpulse'] = 0
    try:
        #Определение цели, вектора на нее и так далее
        target = scene.objects.from_id(int(own['idTarget']))
       
        if inConeOfGSN(own, target):
            vect = own.getVectTo(target)[1]
            own.alignAxisToVect(vect, 1, 0.5)
            vectX = own.getVectTo(target)[2][0]
            vectY = own.getVectTo(target)[2][1]
            vectZ = own.getVectTo(target)[2][2]
            print("La-17")
    except:
        pass
             
#Этот   блок - конус сенсора
def inConeOfGSN(own, target):
    import mathutils
   
    axisVect = mathutils.Vector((0.0, 1.0, 0.0))
    targetData = own.getVectTo(target)
    targetVect = targetData[2]
    dist = targetData[0]
   
    angleGSN = own['angleGSN']
       
    #Если объект попадает в конус действия сенсора
    if angleGSN > axisVect.angle(targetVect, None): 
       
        #Вызов метода
        g = globals()
       
        #Тип головки самонаведения - название функции
        typeGSN = own['typeGSN']
       
        #Проверка на функцирнирование ГСН и ее особенностей
        if g[typeGSN](own, target):
            return True
       
    #Или не попадает
    else:
        return False
    return False

#Полуактивная головка самонаведения радиолокационная
def PARGSN(own, target):
    #Проверка на ограничения по высотам и превышению-принижению
    if limitHeightMax(own, target) and limitHeightAbs(own, target) and limitHeightMin(own, target):
        #Проверка по дистанции и отсутствию препятствия
        if limitDistMax(own, target) and rayEARTH(own, target):
            return True
    else:
        return False                   

#Активная головка самонаведения радиолокационная
def ARGSN(own, target):
    #Проверка на ограничения по высотам и превышению-принижению
    if limitHeightMax(own, target) and limitHeightAbs(own, target) and limitHeightMin(own, target):
        #Проверка по дистанции и отсутствию препятствия
        if limitDistMax(own, target) and rayEARTH(own, target):
            return True
    else:
        return False

#Инфракрасная головка самонаведения радиолокационная
def IKGSN(own, target):
    #Проверка на ограничения по высотам и превышению-принижению
    if limitHeightMax(own, target) and limitHeightAbs(own, target) and limitHeightMin(own, target):
        #Проверка по дистанции и отсутствию препятствия и отсутствие оптических помех (дым, туман, облака)
        if limitDistMax(own, target) and rayEARTH(own, target) and rayFOGS(own, target):
            return True
    else:
        return False

#Полуактивная головка самонаведения лазерная
def PLDGSN(own, target):
    return True

#Активная головка самонаведения лазерная
def ALDGSN(own, target):
    return True

#Полуактивная головка самонаведения телевизионная
def PTVGSN(own, target):
    return True

#Активная головка самонаведения телевизионная
def ATVGSN(own, target):
    return True

##################################################################################
#БЛОК ОГРАНИЧЕНИЙ ПО ВЫСОТЕ И ДИСТАНЦИИ
"""
Этот блок - проверка оограничений по дистанции и высотам, поскольку у многих видов ракет имеются
существенные ограничения по высотам применения, превышения-принижения над целью, что ведет к усложнению
модели поведения такого типа оружия в игре.
"""
#Проверка параметров дистанции
def limitDistance(own, target):
    targetDist = own.getDistanceTo(target)
    distLimitMax = own['distMax']
   
    if distLimitMax > targetDist:
        return True
    else:
        return False 

#Проверка параметров МЕНЯЮЩЕЙСЯ дистанции
def limitDistMax(own, target):
   
    #Координаты цели и собственные
    targetPosition = target.worldPosition
    ownPosition = own.worldPosition
    targetDist = own.getDistanceTo(target)
   
    #Собственная высот а и высота цели
    ownHeight = own.worldPosition[2]
    targetHeight = target.worldPosition[2]
   
    #Поправка на ракурс цели - стрельба на догонном курсе возможна с втрое меньшей дистанции
    racursX = (2 - abs(target.getVectTo(own)[2][0]))/2
    racursY = (2 + target.getVectTo(own)[2][1])/2
    racursZ = (2 - abs(target.getVectTo(own)[2][2]))/2
       
    #Еще одна переменная, влияющая на дальность пуска - общий ракурс цели
    RACURS = (racursX + racursY + racursZ)/3
       
    distLimitMax = own['distMax']
   
    #Введение поправки на дальность ниже 3 км дальность составляет 30 процентов
    if targetHeight < 3000:
        distLimitMax = own['distMax'] * target['stealth'] * RACURS * 0.3
       
    #Введение поправки на дальность от 3 до  10 км дальность изменяется в сторону уменьшения с уменьшением высоты
    if 3000 < targetHeight < 10000:
        distLimitMax = own['distMax'] * target['stealth'] * RACURS * targetHeight/10000
       
    if distLimitMax > targetDist:
        return True
    else:
        return False 
   

#Проверка параметров высоты - максимум
def limitHeightMax(own, target):
    if own['heightMax'] > own.worldPosition[2]:
        return True
    else:
        return False

#Проверка параметров высоты - минимум
def limitHeightMin(own, target):
    DeadZone = [target.worldPosition[0], target.worldPosition[1], target.worldPosition[2] - own['heightMin']]
    if own['timerImpulse'] > 100:
        #Проверка на лимит по минимальной высоте пуска по цели
        hitEarth = target.rayCast(targetPosition, DeadZone, own['heightMin'], 'objScene', 0)               
        if hitEarth == (None, None, None):
            return True
        else:
            return False
   
    elif own['timerImpulse'] < 100:
        return True

#Проверка параметров высоты превышение-принижение относительно цели
def limitHeightAbs(own, target):
    ownHeight = own.worldPosition[2]
    targetHeight = target.worldPosition[2]
    if own['heightAbsLimit'] > abs(ownHeight - targetHeight):
        return True
    else:
        return False

##################################################################################
#БЛОК ОГРАНИЧЕНИЙ ПО  НАЛИЧИЮ ПРЕПЯТСТВИЯ
"""
Этот блок - для проверки наличия препятствия перед целью для головок самонаведегия,точнее, здесь
несколько малых функций, поскольку операция стандартная дл я всех ГСН, хотя набор препятствий как
раз неодинаков, не стоит повторять код лишний раз, достаточно просто вызвать нужные  проверки по
цепочке, плюс это упростит коррекцию кода в дальнейшем.
"""
#Препятствие - туман, облако, дым
def rayFOGS(own, target):
    return True

#Препятствие - земля
def rayEARTH(own, target):
    return True

########################################################################
########################################################################
"""
Этот блок из нескольких функций задает поведение оружия в условиях воздействия разного рода помех -
тепловых ловушек, Солнца, электромагнитных, оптических, акустических  и так далее
"""

#Отстрел тепловых ловушек
def obstacleLO(own, target):
    return True

#Отстрел диполей
def obstacleDIPOL(own, target):
    return True

#Уводящая помеха
def obstacleESCAPIST(own, target):
    return True

#Солнце
def obstacleSUN(own, target):
    return True

#Радиопротиводействие
def obstacleECM(own, target):
    return True

Там, где в функции лишь одна строчка return True, еще надо дописывать код. Пока что проверка принтом "La-17" исправно выдает нужный результат - значит, цепочка функций работает. Отдельно замечу, что метод типа rayCast и ему подобные я стараюсь вызывать раз в секунду или больше. постоянные вызовы таких вещей могут притормозить игру из-за их прожорливости. В скрипте около 300 строчек, больше половины из них пояснения и комментарии, надеюсь те, кому это нужно, поймут без дополнительных обяснений. В сущности все "это новое слово" не более, чем компиляция из старых способов, которых я использовал в первой версии чисто механически, боясь слишком сильно менять что либо (а вдруг испорчу?). Ну а теперь дело - за шлифовкой и дописыванием скрипта - раеты уже летают и цели видят. как и сенсоры юнитов...