четверг, 12 января 2017 г.

Возвращаясь к напечатанному. Работа с blf - текстовые маркеры.

В конце прошлого года с помощью dron-fя ввел текстовый маркер цели на эуране. Об этом я подробно писал в посте о работе с blf и приводил срипт с кодом. Прошло время и я вновь был вынужден заняться этой темой. Как известно, во многих играх существуют текстовые подсказки, которые, как правило, сопровождают юниты или предметы, показывают боекомплект, направление движения и так далее.
В том варианте скрипта текстовый маркер был один и касался он только одной - захваченной цели. Однако хотелось получить более полную информацию об обстановке вокруг и иметь возможность включения-выключения текстовых маркеров. Практически с первой попытки удалось добиться маркировки всех целей в поле зрения камеры, плюс маркировка выбранной цели. Однако скрипт вышел усложненным и корявым. dron указал на некоторые мои промахи (в частности совершенно ненужный повторный вызов функций), после чего я вновь переработал скрипт. Пришлось вводить функцию "затирания" текста, иначе маркеры не выключались. Дополнительно пришлось переводить индекс выбранной цели в глобальный словарь,дописать строчки кода для выбора режима отображения информации - но это так - по мелочам. Теперь скрипт имеет измененный вид, но изменения еще произойдут, поскольку Андрей в своих подсказках применил мною ранее не употреблявшиеся способы форматирования текста и их требуется осмыслить  и принять на вооружение.Итак, код (надо еще разобраться с подсветкой кода в Блоггере - первая попытка окончилась провалом - но программист в html из меня не очень пока что):

import bge
import bgl
import blf

#МЕТКИ ЦЕЛИ НА КОЛЛИМАТОРЕ - ОБЕСПЕЧИВАЕТСЯ ЕЕ ПЛАВНОЕ ПЕРЕМЕЩЕНИЕ ВСЛЕД ЗА ЦЕЛЬЮ  
#Чтение экранных координат    
def readCoord():
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner
    camera = scene.active_camera
   
    targetLabel = scene.objects['targetMarka']
   
    camera = scene.active_camera
   
    #Метка цели работает только при включенном радаре и выставленной дальности
    try:
        #
        marka = own.getScreenPosition(targetLabel)
       
        if abs(marka[0] - bge.logic.globalDict['targetCoord'][0]) > 0.001:
            targetLabel.worldPosition[0] -= (marka[0] - bge.logic.globalDict['targetCoord'][0])/5
        if abs(marka[1] - bge.logic.globalDict['targetCoord'][1]) > 0.001:
            targetLabel.worldPosition[2] += (marka[1] - bge.logic.globalDict['targetCoord'][1])/5
       
        plusX = targetLabel['limXplus'] #collimatorRight.worldPosition[0]
        minusX = targetLabel['limXminus'] #collimatorLeft.worldPosition[0]
        plusY = targetLabel['limYplus'] #collimatorRight.worldPosition[2]
        minusY = targetLabel['limYminus'] #collimatorLeft.worldPosition[2]
         
        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:
        centerX = (targetLabel['limXminus'] + targetLabel['limXplus'])/2
        centerY = (-targetLabel['limYminus'] + targetLabel['limYplus'])/2
        targetLabel.worldPosition[0] = centerX
        targetLabel.worldPosition[2] = centerY

def writeCoord():
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner
    bge.logic.font_id = 0 #blf.load(font_path)
   
    if 'localDict' not in own:
        own['localDict'] = {'typeWriteCoord':own['typeWriteCoord']}
    else:
        if own['localDict']['typeWriteCoord'] != own['typeWriteCoord']:
            #Однократный прогон функции стирания текста при выкоючении маркеров
            if own['typeWriteCoord'] == 0:
                scene.post_draw = [resetWrite]
            own['localDict']['typeWriteCoord'] = own['typeWriteCoord']
       
        #Маркеры текстовой информации включены
        if own['typeWriteCoord'] == 1:
            scene.post_draw = [write]
        #Если макеры выключены, работает  упрощенная функция слежения только за одной целью  
        else:
            targetWrite()
   
   

def write():
    scene = bge.logic.getCurrentScene()
    camera = scene.active_camera
   
    width = bge.render.getWindowWidth()
    height = bge.render.getWindowHeight()
 
    textInfo = ''
    distance = 0
   
    targetInScene = 0
   
    bgl.glDisable(bgl.GL_DEPTH_TEST)
    bgl.glMatrixMode(bgl.GL_PROJECTION)
    bgl.glLoadIdentity()
    bgl.gluOrtho2D(0, width, 0, height)
    bgl.glMatrixMode(bgl.GL_MODELVIEW)
    bgl.glLoadIdentity()
    font_id = bge.logic.font_id
   
   
    if len(bge.logic.globalDict['listAllObject']) > 0:
       for target in bge.logic.globalDict['listAllObject']:
           #Отрисовке информации подлежат только объекты в поле зрения камеры
           if -0.6 < camera.getVectTo(target)[2][0] < 0.6 or str(id(target)) == bge.logic.globalDict['idTarget']:
               #Несколько коряво с повтором условия
               if -0.32 < camera.getVectTo(target)[2][1] < 0.32 or str(id(target)) == bge.logic.globalDict['idTarget']:
                   if camera.getDistanceTo(target) > 30:
                     
                       ScreenPositionTarget = camera.getScreenPosition(target)
                     
                       winXcoord = bge.render.getWindowWidth(target) * ScreenPositionTarget[0]
                       winYcoord = bge.render.getWindowHeight(target) *(1-ScreenPositionTarget[1])
                 
                       distance = int(camera.getDistanceTo(target)/1000)
                       #Формируем текстовый блок
                       textInfo = target['unitName'] + target['unitNation'] + str(distance)
                     
                       blf.position(font_id, winXcoord, winYcoord, 0)
                       blf.size(font_id, 25, 36)
                       if target['target'] == 0:
                           bgl.glColor3f(1.0,0.0,0.0)
                       if target['target'] == 1:
                           bgl.glColor3f(0.0,0.0,1.0)
                       #bgl.glColor4f
                       bgl.glDisable(bgl.GL_LIGHTING)
                       blf.draw(font_id, textInfo)
                       blf.draw(font_id, textInfo)
                     
                       #Для захваченной цели
                       if str(id(target)) == bge.logic.globalDict['idTarget']:
                           targetInScene = 1
                           bge.logic.globalDict['targetCoord'] = ScreenPositionTarget
                           targetDistance = camera.getDistanceTo(target)
                           targetSpeed = target.localLinearVelocity[1]
                           targetHeight = target.worldPosition[2]
                           bge.logic.globalDict['targetData'] = [targetDistance, targetSpeed, targetHeight]
                 
    #Последнее уточнение -  если после прохождения цикла цели в сцене не имеется
    if targetInScene == 0:
        bge.logic.globalDict['targetCoord'] = [0, 0]
        bge.logic.globalDict['targetData'] = [0, 0, 0]
   
    bgl.glEnable(bgl.GL_DEPTH_TEST)
               

#Упрощенная функция чтения экранных координат целм
def targetWrite():
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner
   
    try:
        target = scene.objects.from_id(int(own['idTarget']))
        camera = scene.active_camera
        bge.logic.globalDict['targetCoord'] = camera.getScreenPosition(target)
        targetDistance = own.getDistanceTo(target)
        targetSpeed = target.localLinearVelocity[1]
        targetHeight = target.worldPosition[2]
        bge.logic.globalDict['targetData'] = [targetDistance, targetSpeed, targetHeight]
    except:
        bge.logic.globalDict['targetCoord'] = [0, 0]
        bge.logic.globalDict['targetData'] = [0, 0, 0]
       
#Функция сброса экранных маркеров  
def resetWrite():
    scene = bge.logic.getCurrentScene()
    camera = scene.active_camera
    width = bge.render.getWindowWidth()
    height = bge.render.getWindowHeight()
    #bgl.glDisable(bgl.GL_DEPTH_TEST)
    bgl.glMatrixMode(bgl.GL_PROJECTION)
    bgl.glLoadIdentity()
    bgl.gluOrtho2D(0, width, 0, height)
    bgl.glMatrixMode(bgl.GL_MODELVIEW)
    bgl.glLoadIdentity()
    font_id = bge.logic.font_id
    blf.position(font_id, 0, 0, 0)
    blf.size(font_id, 25, 36)
    bgl.glDisable(bgl.GL_LIGHTING)
    blf.draw(font_id, '')
    blf.draw(font_id, '')
    #bgl.glEnable(bgl.GL_DEPTH_TEST)
               

Ну и, как это выглядит на экране. Пара Ф-16 с национальной принадлежностью и дистанцией в км. Кстати, при тестировании, когда мой МиГ-23МФ был сбит и камера "отсоединилась", то я увидел и красную строчку с указанием моего юнита и дистанцией с нацпринадлежностью (когда сбитый самолет пошел вперед и вниз). Клавиша М(лат) срабатывает - можно включать и отключать текст.

вторник, 10 января 2017 г.

Вам слово, благородный дон РЭБа!

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


Как обычно, последовало создание json для СПС-142 "Гвоздика" и подгружаемого бленда. А далее последовало написание скрипта РЭБ. Суть работы "Гвоздики" (кстати, вроде как еще ее называют СПС-141МВГ и эта система опровергает тезис об отставании СССР в области РЭБ от США - во время ирано-иракской вофны ни один иракский самолет, оснащенный этим контейнером не был сбит УР с радиолокационной головкой, а вот иранцам американские системы помогали мало) заключается в ослеплении БРЛС вражеских машин и выдаче ложных координат летящей в самолет-носитель вражеской ракете. Это - "уводящая помеха" - ракета идет не на сам самолет,а на его "призрак". После некоторой возни скрипт заработал. После проверки его работоспособности пришлось срочно ухудшать свойства СПС-142, потому что она делала самолет игрока неуязвимым и бессмертным.
Скрин - СПС-142 "Гвоздика" на МиГ-23МФ.


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


# -*- coding: utf8 -*-

"""
"JAMMER":"ECM",
"timerCycle":0,
"limitCycle":360,
"timerPause":0,
"limitPause":300,
"limitCoord":100,
"distanceAntiMissile":25000,
"distanceAntiBRLS":30000,
 "massChild":0.1,
 "antiForce":0.0018
 """


import sys
import bge
import random

#import mathutils

#Скрипт работы систем противодействия - электронные помехи, УФ-помехи и так далее

#Функция-коммутатор - определяет специализацию систем противодействия
def jammer():
    scene = bge.logic.getCurrentScene()
    cont = bge.logic.getCurrentController()
    own = cont.owner
   
    if 'threatDict' not in own:
        own['threatDict'] = {"threatList":[],"enemyList":[],"kX":0,"ky":0,"kZ":0}
   
    else:
        #Вызов метода
        g = globals()
       
        #Тип головки самонаведения - название функции
        typeJAMMER = own['JAMMER']
       
        #Проверка на функцирнирование ГСН и ее особенностей
        g[typeJAMMER](own)

#Активные помехи - контейнерные и встроенные системы РЭБ
def ECM(own):
    scene = bge.logic.getCurrentScene()
   
    randomBLINDER = random.randrange(0,100)
    randomJAMMER = random.randrange(1,101)
    phantomCoord = random.randrange(0,own['limitCoord'])
    #print('JAMMER')
    #Словари носителя - списки угроз и списрк в словаре обрабатываемого вражеского оружия  
    #own.parent.parent['threatListWeapon']
    #own['weaponDict'] = {'dopX':0,'dopY':0,'dopZ':0}
   
    #Таймер цикла противодействия
    if own['timerCycle'] < own['limitCycle']:
        own['timerCycle'] += 1
   
    #Формируем список угроз от ракет с радиолокационными головками
    if own['timerCycle'] == 1:
        CoordPlusMinus(own)
        try:
            for missile in own.parent['localDict']['threatListWeapon']:
                #print('localDict')
                if 'typeGSN' in missile:
                    if 'RGSN' in missile['typeGSN']:
                        own['threatDict']['threatList'].append(missile)
                    elif 'RK' in missile['typeGSN']:
                        own['threatDict']['threatList'].append(missile)
        except:
            pass
   
    #Начинаем воздействие
    if own['timerCycle'] > 1:
       
        #Ослепляющая помеха для РЛС противника
        if randomBLINDER > 90:
            if len(own.parent['localDict']['threatListUnit'][0]):
                for objThreatRLS in own.parent['localDict']['threatListUnit'][0]:
                    #Ослепляющая помеха срабатывает на дальних рубежах, вблизи радары противника помехи "прожигают"
                    if own.getDistanceTo(objThreatRLS) > own['distanceAntiBRLS']:
                        #При низком уровне помехозащищенности захват будет сорван и противнику придется вновь выполнять прицеливание
                        if randomJAMMER/100 > objThreatRLS['antiECM']:
                            objThreatRLS['idTarget'] = '1'
       
        if len(own['threatDict']['threatList']) > 0:
            try:
                #Начинаем попытку  "взломать" ГСН ракеты и подчинить ее системе РЭБ
                MISSILEattack = own['threatDict']['threatList'][0]
                if own.getDistanceTo(MISSILEattack) < own['distanceAntiMissile']*MISSILEattack['antiAC']/100:
                    #print(randomJAMMER)
                    if MISSILEattack['antiAC'] > randomJAMMER:
                       
                        if MISSILEattack['antiAC'] < 101:
                            #Взлом произошел
                            MISSILEattack['antiAC'] = 101
                        #Включаем функцию исажений координат
                        escapeCoord(own, MISSILEattack)
                       
            except:
                own['threatDict']['threatList'] = []
   
    #Включаем таймер паузы              
    if own['timerCycle'] == own['limitCycle']:
        if own['timerPause'] < own['limitPause']:
            own['timerPause'] += 1
       
        if own['timerPause'] == 1:
            #Сбрасываем в ноль искажение для ракеты, можно даже вернуть ей привычную стойкость к помехам
            if len(own['threatDict']['threatList']) > 0:
                CoordNull(own, MISSILEattack)
               
    #Сброс таймеров в ноль  
    if own['timerPause'] == own['limitPause']:
        own['threatDict']['threatList'] = []
        own['timerPause'] = 0
        own['timerCycle'] = 0
           
       
#Активные помехи - оптика - инфракрасные ГСН (тепловы)
def UF(own):
    scene = bge.logic.getCurrentScene()
   
    randomJAMMER = random.randrange(1,101)
    phantomCoord = random.randrange(0,own['limitCoord'])
    #print('JAMMER')
    #Словари носителя - списки угроз и списрк в словаре обрабатываемого вражеского оружия  
    #own.parent.parent['threatListWeapon']
    #own['weaponDict'] = {'dopX':0,'dopY':0,'dopZ':0}
   
    #Таймер цикла противодействия
    if own['timerCycle'] < own['limitCycle']:
        own['timerCycle'] += 1
   
    #Формируем список угроз от ракет с радиолокационными головками
    if own['timerCycle'] == 1:
        CoordPlusMinus(own)
        try:
            for missile in own.parent['localDict']['threatListWeapon']:
                #print('localDict')
                if 'typeGSN' in missile:
                    if 'IKGSN' in missile['typeGSN']:
                        own['threatDict']['threatList'].append(missile)
                       
                       
        except:
            pass
   
    #Начинаем воздействие
    if own['timerCycle'] > 1:
        if len(own['threatDict']['threatList']) > 0:
            try:
                #Начинаем попытку  "взломать" ГСН ракеты и подчинить ее системе РЭБ
                MISSILEattack = own['threatDict']['threatList'][0]
                if own.getDistanceTo(MISSILEattack) < own['distanceAntiMissile']*MISSILEattack['antiUF']/100:
                    #print(randomJAMMER)
                    if MISSILEattack['antiUF'] > randomJAMMER:
                        if MISSILEattack['antiUF'] < 101:
                            #Взлом произошел
                            MISSILEattack['antiUF'] = 101
                        #Включаем функцию исажений координат
                        escapeCoord(own, MISSILEattack)
                       
            except:
                own['threatDict']['threatList'] = []
   
    #Включаем таймер паузы              
    if own['timerCycle'] == own['limitCycle']:
        if own['timerPause'] < own['limitPause']:
            own['timerPause'] += 1
       
        if own['timerPause'] == 1:
            #Сбрасываем в ноль искажение для ракеты, можно даже вернуть ей привычную стойкость к помехам
            if len(own['threatDict']['threatList']) > 0:
                CoordNull(own, MISSILEattack)
               
    #Сброс такмеров в ноль  
    if own['timerPause'] == own['limitPause']:
        own['threatDict']['threatList'] = []
        own['timerPause'] = 0
        own['timerCycle'] = 0


#Выборнаправления уводящей помехи
def CoordPlusMinus(own):
   
    #Показатели
    own['threatDict']["kX"] = random.randrange(-2,2)
    own['threatDict']["kY"] = random.randrange(-2,2)
    own['threatDict']["kZ"] = random.randrange(-2,2)

#Уводящая помеха
def escapeCoord(own, MISSILEattack):
   
    #Искажаем координаты - доработать - слишком длинная запись
    MISSILEattack['weaponDict']['dopZ'] += phantomCoord*own['threatDict']["kX"]
    MISSILEattack['weaponDict']['dopY'] += phantomCoord*own['threatDict']["kY"]
    MISSILEattack['weaponDict']['dopX'] += phantomCoord*own['threatDict']["kZ"]
   
   
#Обнуление искажений
def CoordNull(own, MISSILEattack):  
   
    MISSILEattack['weaponDict']['dopZ'] = 0.0
    MISSILEattack['weaponDict']['dopY'] = 0.0
    MISSILEattack['weaponDict']['dopX'] = 0.0
               
    Думаю, понятно. Весь скрипт висит на объекте Jammer, который и занимается своей работой. Причем информацию об угрозах он получает от своего родителя юнита. Работает он длинными импульсами с длинными же паузами - циклами. система РЭБ не всесильна, но одну ракету точно успевает отвести, если сильно повезет - пару. В общем-то, так и задумывалось.
Кроме систем РЭБ я в авральном режиме занимался головками самонаведения ракет - вводил подсветку, реакцию на ловушки и диполи и так далее. А еще писал дальше искусственный интеллект. В первом приближении систему воздушного боя можно считать выстроенной. Конечно, придется ее корректировать, дополнять, настраивать, но основные параметры и величины, принцип работы уже выстроены. Теперь надо начинать выстраивать работу ИИ по земле и заканчивать с анархией - учить ботов воевать группами, держать строй и даже принимать "командные" решения...

вторник, 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 все обстоит гораздо лучше, подробных панелей и схем гораздо больше. Вероятно, придется делать версии кабин, а потом их обновлять по мере улучшения качества картинки.

Сегодня

аСе