Показаны сообщения с ярлыком #Python Radar. Показать все сообщения
Показаны сообщения с ярлыком #Python Radar. Показать все сообщения

понедельник, 1 апреля 2019 г.

Большой Апдейт. И прочие новости...

Не писал я довольно давно. Тут причин мног7о, но главная, просто не знал, о чем писать...
БГЕ таки мертв, будут ли из него делать форки, помимо известных мне upBGE, неясно... С другой стороны, мне все ж стало жалко своих трудов на ниве быдлокодерства, и, помявшись, походив вокруг да около, я все же решил начать Большой Апдейт. Так сказать, прощальный аккорд для БГЕ. Плюс, таким образом я рассчитываю не забыть совсем уж навыки работы с Питоном, а заодно отработать кое-какие приемчики, могущие мне пригодиться в работе уже с Юнити (о нем чуть позже).
Правда, я не знаю, к чему и когда все это приведет, потому как назревает уже четвертая по счету операция для моего глаза (вполне ожидаемая вторичная катаракта), на какое время и когда мне придется "заткнуть свой фонтан" (С), пока непонятн6о, но и сидеть, сложа ручки, тоже неохота, а водку и прочие средства расширения сознания я не уважаю категорически.
Данный пост должен сыграть (и я надеюсь, сыграет), роль своеобразного пинка самому себе, дабы подьянуть расщатавшуюся за последнее время самодисциплину и хотя бы частично прогнать лень.
Итак.
В чем сущность Большого Апдейта (данное понятие следует рассматривать сугубо с иронической точки зрения)?
Сущность его в том, чтобы довести проект до хоть какой-то завершенности. Здесь основной задачей является упорядочивание структуры проекта и обретения новых навыков работы с файлами. А именно - новые методы поиска и загрузки моделей в игру, создание редактора миссий и кампаний и введения в состав доступных игроку юнитов зенитно-ракетных комплексов, вертолетов и танков. Хотя бы и в единственном экземпляре, причем необходимо повысить гибкость проекта и сделать его открытым для добавления новых юнитов (точнее, сохранить открытость). Это основная задача.
Вспомогательная задача - избавиться от завышенных ожиданий, ввести ограничения в проект, превращающий его в, гм... в симулятор конфликтов малой интенсивности (поскольку БГЕ не любит большого количества объектов). Например, для редакторов миссий и кампаний ограничить число одновременно находящихся в воздухе самолетов и вертолетов, скажем, не больше 20 с обеих сторон (в "Ил-2", кстати, ЕМНИП максимум можно было выставить 32 самолета с разных сторон). Скорее всего, онлайн-версию делать не буду (а так, не знаю, что там получится, зарекаться не буду). Приведу в порядок код, в соответствии с новыми знаниями, почерпнутыми мною из всевозможных наставлений и инструкций. Пейзаж покрасивше сделаю (кроме БГЕ, у меня есть и другие планы, но о них я пока помолчу ввиду неопределенности с кое чем - см. выше).

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

import bge
import sys
import os
import json

#Функция создания файла json с путями к файлам всего проекта
def startPath():
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner

    #Создаем переменные - список путей и пустую строку для путей
    pathList = []
    k = ""
   
    #Циклом перебираем файлы, директории и папки, создаем и сшиваем строки в пути
    for d, dirs, files in os.walk(bge.logic.expandPath("//")):
        for f in files:
           
            #Обрабатываем вид путей, убирая слеши и название стартовой директории
            k = os.path.join(d,f)
            k = k.split("BlendSim_2.1")[1]
            k = k.replace("\\", "/")
           
            #Питон-файлы добавляем в sys.path, остальные в список путей
            if ".py" not in k:
                pathList.append("/"+k)
            else:
                sys.path.append("/"+k)
                   
    #Открываем файл json  с путями (если его нет, то он создается) и сбрасываем туда список путей
    with open(bge.logic.expandPath('//NodePath.json'), 'w') as NODEPATH:
        json.dump(pathList, NODEPATH)
   
    #Это просто проверка   
    proverka(own, scene)
 
def proverka(own, scene):
    missile = None

    with open(bge.logic.expandPath('//NodePath.json'), 'r') as READPATH:
        JSONREADPATH = json.load(READPATH)
       
        for ob in JSONREADPATH:
            if "R-24R.blend" in ob:
                print(ob)
                bge.logic.LibLoad(bge.logic.expandPath(ob), 'Scene', load_actions = True)
                missile = scene.addObject("R-24R", own)
       
Ну, я думаю, с комментариями будет все ясно, для чего все это надо. Проверка в виде появления на сцене ракеты Р-24Р прошла успешно и неоднократно...

Теперь о Юнити. В свое время я скачал и Юнити и Анрил Энжин 4. Второй движок ощутимо мощнее и совершеннее первого, да вот беда, пользоваться им смогут не все, я - пока точно нет. Его недостатком является отсутствие большого количества документации, в отличие от Юнити, к тому же, на форуме b3d.ua мне много раз говорили, что Си Шарп относительно простой в освоении язык (но, к сожалению, я тугодум и с налету его понять не могу, изображая обезьяну, повторяющую поведение человека, а именно, создавая код по примерам и образцам, не понимая, для чего и ка, впрочем, при освоении Питона начиналось так же). В Юнити для меня еще остались кое-какие нюансы, лежащие за пределами моего понимания, например, как присвоить материал дереву и его листьям (перетаскивание порой сильно раздражает, проше было бы набрать на клавиатуре). Но все-таки, я добавил в Юнити многострадального подопытного кролика - МиГ-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, на вторую - АМРААМ, а на третью - "Сайдуиндеры". Самые дальнобойные-то и выбрались.  Для них нужна работа БРЛС, и она также включилась.
Далее, необходимо обеспечить многочисленные нюансы. Разворот бота в сторону цели, корректирование его ориентации в пространстве. Очень тщательно выверить работу взаимодействия сенсоров юнитов между собой - в первую очередь. Все дело в том, что необходимо сообщать в случае работы сенсоров о совем местонахождении. Самолет, облучающий перед собой участок неба, буквально орет на весь район о своем присутствии. И наоборот - тихо (ну ладно, громко летящий) у самой земли перхватчик, включивший вместо БРЛС теплопленгатор, не насторожит оппонента, прущего где-то вверху и стороне от его курса. И противник поймет, что что-то не так только после взрыва ракеты с тепловой ГСН...
Но ботам я пока стрелять не дам. Сначала необходимо отработать списки угроз, их обновление и структуру, отработать СПО с ее истерикой - мерзким зуммером и миганием разноцветных огоньков, а уж потом дать возможность стрелять противнику.
такая вот диспозиция на сегодняшний день.

среда, 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 строчек, больше половины из них пояснения и комментарии, надеюсь те, кому это нужно, поймут без дополнительных обяснений. В сущности все "это новое слово" не более, чем компиляция из старых способов, которых я использовал в первой версии чисто механически, боясь слишком сильно менять что либо (а вдруг испорчу?). Ну а теперь дело - за шлифовкой и дописыванием скрипта - раеты уже летают и цели видят. как и сенсоры юнитов...

понедельник, 27 июня 2016 г.

И снова о сенсорах...

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

01_120_60_1.2_1_15 - так выглядит запись в текстовом файле для БРЛС истребителя МиГ-23МЛАЭ2. Для ее расшифровки сначала приведу список проперти юнита - в данном случае - летательного аппарата.
target - "краные" (0) или "синие" (1)
targetScan - тип сканирования - "земля" (1) или "воздух" (0)
sensBRLS - проперти бортовой РЛС (как раз первая строчка из цифр).
sensTP - проперти теплопеленгатора
sensLD - проперти лазерного дальномера
sensTV - проперти телевизионного прицела

А теперь расшифруем символы в трочке, перемежаемые подчеркиваниями.

01 - способность к отслеживанию целей, в данном случае - как воздушных, так и наземных (может быть так, что сенсор приспособлен исключительно для работы по земле или небу - 1 или 0 соответственно).

120 -  дальность действия - 120 км.

60 - угол обзора - в данном случае - 60 градусов

1.2 - пауза между сканирующими импульсами, иначе БГЕ может зависнуть. Величина идет скорее "на глазок, меньше секунды точно не будет.

1 - количество подсвечиваемых целей - означает возможность обстреливать одну или несколько целей сразу ракетами с РЛ ГСН (например МиГ-31 и F-14 долгое время в мире были единственными, способными на такое).

15 - это "мертвая зона"  радара. во многом эта величина взята от балды и нуждается в дополнительной проработке. Пока что мертвая зона вычисляется таким образом - сначала берется высота самого перехватчика, делится на то число и полученный результат сравнивается с текущей высотой цели над данным участком местности (не высотой над нулевой отметкой!!!). Но, скорее всего, придется внести коррективы - не абсолютная высота перехватчика, а относительная, как и у цели. Ну, здесь можно сильно поизвращаться... Главное, чтоб движок выдержал.

Вообще-то "мертвая" зона вводится именно для радара, причем только в режиме прицеливания "воздух". Для ракет же существует своя мертвая зона - по абсолютной максимальной высоте и текущей минимальной высоте над землей. К примеру, старые ракеты типа Р-98 на высоте меньше 2 км уже теряли цель в любом случае, что на фоне земли, что на фоне неба - достаточно было атакуемому "нырнуть" пониже к земле (но в данном случае это было не слишком опасно, от Р-24 увернуться было уже проблематично - требовалось опуститься на высоту ниже 25 метров).
Сейчас идет подгонка формата файлов под новые обстоятельства. Учитывая, что я не слишком сильно заморачивался этим в свое время не успел наплодить кучу юнитов, процесс переделки(доработки) будет не таким сложным, как казалось...

воскресенье, 26 июня 2016 г.

Сенсоры. Боты не должны видеть тебя все время, ибо это нечестно...

Давным-давно как-то раз я просматривал форум, посвященный знаменитой серии "Ил-2" (благодаря которой на Западе таки выучили слово "штурмовик", поняв, что "стормовик" - это неправильно). И был там вопрос по поводу "зрения" ботов. Вопрошающий, получив ответ: "Боты видят тебя всегда", возопил: "Но так нечестно!!!". Вот и я тоже считаю, что нечестно. Разумеется, "мне сверху видно все, ты так и знай" (С), но все же очень не хотелось бы, чтобы бот сумел бы разглядеть тебя, крадущегося, где-нибудь по ущелью прямо сквозь горный хребет... К тому же есть такая штука, как помехи , создаваемые подстилающей поверхностью, которые причиняли на заре развития БРЭО (да и потом) разработчикам этого самого БРЭА и летчикам немало хлопот. Известно, например, что БРЛС "Фантомов" довольно плохо видит цели на фоне земли и неспособна произвести захват и сопровождение цели. Поджобные недостатки имеют и БРЛС МиГ-21, ранних МиГ-23, и в какой-то степени МиГ-25 и F-14(как ни странно). Во время ирано-иракской войны иракцы изрядно проредили парк "Фантомов" ВВС Ирана, применяя тактику засад и приманок. Обычно пара МиГ-23МС или МиГ-23БН начинала либо бомбить и обстреливать позиции наземных войск иранцев, либо демонстративно обращалась в бегство, провоцируя F-4 на погоню. Но убегали иракцы не абы куда, а в заранее оговоренный район, в котором на малых высотах нарезали круги и восьмерки МиГ-25 или Mirage F.1. Их летчики, получив соответствующую информацию с земли или от своих товарищей, работавших в качестве "приманки", шли навстречу  "Фантомам" на предельно малой высоте, РЛС иранцев их не видели. Проскочив под оппонентами, перхватчики выполняли полупетлю с полупереворотом и оказывались на хвосте у "Фантомов". Как правило, когда иранцы понимали, что из охотников они сами превратились в жертву, предпринимать что-либо было уже поздно. За довольно короткое время иранцы недосчитались примерно десятка F-4/Любопытно, что против "Тайгеров" F-5 подобная тактика почти не работала - у них просто не хватало скорости, чтобы хотя бы не отстать от МиГов. Похоже, при схожих же обстоятельствах иранцы потеряли и два "Томкэта" от МиГ-23МЛАЭ-2 (экспортный вариант МЛД, поставлявшийся в Ирак, без "клыков" у неподвижной части крыла) -  во всяком случае, на сайте Тома Купера указывается, что один из F-14 был сбит ракетой Р-60 (учитывая ее очень небольшой радиус действия и высокие летные характеристики  "Томкэта", рискну предположить, что МиГ-23 достал своего противника неожиданно и с задней полусферы, БРЛС "Томкэта" видит очень далеко, но на фоне земли цели различает плоховато).
Ну да ладно, вернемся к проекту, а точнее, к созданию скрипта-сенсора. Когда-то denis8424 создал пару примеров радара с использованием Питона. Я же, всячески модифицируя, дополняя и подгоняя под свои требования скрипт, получил в итоге пару скриптов-монстров, которые, тем не менее, все-таки работали и вокруг них во многом игра и строилась.
Теперь же задачка стояла создать на основе того же скрипта радара более совершенный скрипт-сенсор. Дабы отделить одно от другого, чтобы потом не морщить лоб в попытках вспомнить, что же я имел в виду этой строчкой, я решил в модуле-сенсоре сделать несколько функций, которые будут отвечать за разные способы навигации, прицеливания и ориентирования.
Было сделано ника не меньше дюжины попыток, которые заканчивались иногда более-менее приемлемыми результатами, но, поскольку я толком сам не понимал, чего хотел, изыскания пришлось продолжить. в конце концов я пришел к следующей схеме - вначале идет так называемый сканирующий импульс, который в списке оппонентов отсекает всех, кто не укладывается в конус радара или скрыт за препятствием. А уже затем идет "сопровождающий" импульс, который будет повторяться до тех пор, пока цель не будет сменена, сорвет завхват или будет уничтожена. Была сперва мысль сделать для каждого типа сенсора свою функцию, но пока я от этого отказался (хотя и не окончательно). Сейчас у меня идет работа над отладкой двух типов сенсоров - радара и "взгляда летчика". Но сначала я отладил принцип сортировки юнитов в игре. Занимается этим объект с говорящим названием "Arbitr". скрипт его работы привожу чуть ниже, думаю, все понятно:

 import bge
cont = bge.logic.getCurrentController()
own = cont.owner
scene = bge.logic.getCurrentScene()
sens = cont.sensors[0]
    
#Главный список всех задействованных юнитов
listUnit = [[[],[]],[[],[]]]
bge.logic.globalDict['listUnit'] = listUnit
  
#Запись индексов объектов-юнитов в подсписки и общий список
if sens.positive:
    for obj in scene.objects:
          
        if 'target' in obj:
            #Если в имени присутствует 'Air'  - то это - летательный аппарат
            if 'Air' in obj.name:
                if obj.get('target') == 0:
                    listUnit[0][1].append(obj)
                elif obj.get('target') == 1:
                    listUnit[0][0].append(obj)
            #Если в имени присутствует 'Ground'  - то это наземный объект
            elif 'Ground' in obj.name:
                if obj.get('target') == 0:
                    listUnit[1][1].append(obj)
                elif obj.get('target') == 1:
                    listUnit[1][0].append(obj)
    #print(bge.logic.globalDict['listUnit'])

Сортировка идет по части названия объектов - чуть быстрее, как мне кажется, нежели чем искать какое-то проперти в свойствах объектов.
А далее мы имеем на ботах и своем юните проперти targetScan - которое отвечает за просмотр списка наземных (1) или воздушных (0) целей. Анаоргично 0 или 1 работает свойство target, которое, собственно, обозначаете сторону - условно - 0 - "красные", 1 - "синие". Я не стал во второй версии умножать сущности и вводить еще два значения проперти target, для наземки. И так сработает.
На данный момент скрипт радара выглядит так:
import bge
import mathutils

#Сенсор обнаружения и сопровождения цели
def unitSensor():
    cont = bge.logic.getCurrentController()
    own = cont.owner
    scene = bge.logic.getCurrentScene()
    sens = cont.sensors[0]
   
    print(own['PR'])
   
    #Создаем словарь свойств
    if 'unitSensor' not in own:
        own['unitSensor'] = {}
        own['unitSensor']['idTarget'] = own['idTarget'] #Индекс текущей цели
   
   
    if sens.positive:
       
       
        if own['idTarget'] == '' or own['unitSensor']['idTarget'] != own['idTarget']:
            def inConeOfRadar(own, target):
               axisVect = mathutils.Vector((0.0, 1.0, 0.0))
               targetData = own.getVectTo(target)
               targetVect = targetData[2]
               dist = targetData[0]
               angle = own['radarAngle']
               if angle > axisVect.angle(targetVect, None): 
                   rayOwnTarget = own.rayCastTo(target, dist, 'objScene')
                   if rayOwnTarget == None: 
                       #Если тип сканирования - воздушные цели, то дополнительно проверяем еще и "мертвую зону" радара
                       if own['targetScan'] == 0:
                           #Некоторые радары плохо видят цель на фоне земли, поэтому не способны засекать низколетящие цели
                           #Высота "мертвой зоны радара" - по высоте цели над землей
                           deadZoneRadar = own.worldPosition[2]/own['antiEarth']
                           #Введение поправки на мертвую зону
                           targetDeadZone = [target.worldPosition[0], target.worldPosition[1], target.worldPosition[2] - deadZoneRadar]
                           targetPosition = target.worldPosition
           
                           #Мертвая зона радара
                           hitEarth = target.rayCast(targetPosition ,targetDeadZone, deadZoneRadar, 'objScene', 0)               
                           if hitEarth == (None, None, None):
                               return True
                   
                       #В противном случае для наземных объектов такой проверки не требуется
                       else:
                           return True
                   
                   
               else:
                   return False
               return False      
                               
            def aiming():
                cont = bge.logic.getCurrentController()
                scene = bge.logic.getCurrentScene()
                own = cont.owner
   
                ownTargetList = []
                sceneObjList = bge.logic.globalDict['listUnit'][own['targetScan']][own['target']]
                       
                if len(sceneObjList) > 0:
               
                    for target in sceneObjList:
                           
                        if own['distRadarMax']*target['stealth'] > own.getDistanceTo(target):
                            if inConeOfRadar(own, target):
                                ownTargetList.append(target)
                                if len(ownTargetList) - 1 == own['enemy']:
                                    own['idTarget'] = str(id(target))
                    #print(own, ownTargetList, own['idTarget'])
                               
            aiming()
            own['unitSensor']['idTarget'] = own['idTarget']
           
            #
            if own['unitSensor']['idTarget'] == own['idTarget']:
               
               
               
               
                #print(own, own['idTarget'])
                try:
                    ob = scene.objects.from_id(int(own['idTarget']))

                    #if inConeOfRadar(own, target):                                    #Это - условие НАВЕДЕНИЯ на цель
                    vect = own.getVectTo(ob)[1]
                    own.alignAxisToVect(vect, 1, 0.5)
                   
                    vectX = own.getVectTo(ob)[2][0]
                    vectY = own.getVectTo(ob)[2][1]
                    vectZ = own.getVectTo(ob)[2][2]
                   
                    if abs(vectX) < 0.2 and abs(vectZ) < 0.2:
                        own['PR'] = 1
                    else:
                        own['PR'] = 0
                       
                except:
                    pass

Текст будет еще многократно меняться, в него будут внесены поправки  на использование средств РЭБ. Кроме того, отдельно будудт функции баллистического прицела и "взгляда летчика". Впрочем, последняя, уже создана и в ней большую роль играет проперти maxVisibleDist - у "просматриваемых" объектов - максимальная дистанция видимости. Дело в том, что здоровенную тушу Б-52 видно гораздо дальше, чем, скажем маленький Миг-21, поэтому и надо вносить коррекцию на размеры для ботов.
Отдельный вопрос - функции наведения для УР и всевозможныъ КАБов - там необходимо прописать скорость реакции на маневр цели, нужность-ненужность подсветки, условия прекращения наведения, снижение энергии при полете или маневре, реакцию на РЭБ, облака, дым, да много чего. Похоже, все это будет отлаживаться, как и в пераой версии - постепенно - шаг за шагом...