воскресенье, 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, поэтому и надо вносить коррекцию на размеры для ботов.
Отдельный вопрос - функции наведения для УР и всевозможныъ КАБов - там необходимо прописать скорость реакции на маневр цели, нужность-ненужность подсветки, условия прекращения наведения, снижение энергии при полете или маневре, реакцию на РЭБ, облака, дым, да много чего. Похоже, все это будет отлаживаться, как и в пераой версии - постепенно - шаг за шагом...

Комментариев нет:

Отправить комментарий