пятница, 21 апреля 2017 г.

И снова "Операция "Святой Януарий". Теория классовой борьбы.

Программист из меня, мягко говоря, средненький, специального образования я на этот счет не получал, так что можног сказать, самоучка.
Некоторые вещи доходят до своего осознания довольно долго, даже не с десятого раза. Вероятно, виной тому зацикленность на практических результатах и более-менее постоянно тем же Питоном я стал заниматься где-то с 2013.
Довольно давно, года уж два как минимум, мне говорили, что осознание дзена классов объектов Питона ведет к экономии ресурсов и выигрышу в использовании БГЕ. Несколько неудачных попыток, в том числе с применением мутации, попытки написания собственных классов в конце концов навели на путь истинный. Опять получилось что-то вроде бросания зажигалки на бронированное стекло в фильме "Операция "Святой Януарий". Все оказалось довольно просто. Определение класса я здесь излагать не буду - есть много статей на эту тему. Изложу свой крайне конкретный и приземленный взгляд на эту тему. И главное, зачем, это надо? 
Сточки зрения дилетанта, меня то есть, проблема вот в чем - в коде постоянно используются проперти вида ["speed"], которые пишутся после священного слова own, либо какого-то другого имени объекта. Если их много, они начинают мешать, а если учесть, что очень многие из них используются редко, либо вообще разово, получается мертвый груз. Хотелось бы их убрать, да нельзя - раз проперти названо - все, уничтожить егог нельзя. Да и от скобочек с кавычками в глазах рябит...
А теперь смотрим, что делает класс на примере создания экземпляра класса (если я правильно написал это название) юнита летательного аппарата.

import bge
import json

scene = bge.logic.getCurrentScene()
cont = bge.logic.getCurrentController()
own = cont.owner

Ind = "UnitAir"+str(id(own))
ArbitrGame = scene.objects["ArbitrGame"]
KEY = getattr(ArbitrGame, Ind)
  
#Часть данных записываем сразу с нулевыми значениями
config = {"WINGS":-1, "Temp_WINGS":-1, "FLAPS":0, "Temp_FLAPS":0, "SLATS":0, "Temp_SLATS":0, "CANOPY":0, "Temp_CANOPY":0,
          "AIRBRAKE":0, "Temp_AIRBRAKE":0, "CHASSY":0, "Temp_CHASSY":0, "ROLL":0, "Temp_ROLL":0, "YAW":0, "Temp_YAW":0,
          "PITCH":0, "Temp_PITCH":0, "ROLLwings":0, "Temp_ROLLwings":0, "avto":0, "Temp_avto":0, "PR":0, "Temp_PR":0,"LockOn":0,"enemy":0,
          "weapon":-1, "Temp_weapon":-1, "crash":1.0, "Temp_crash":1.0, "correctSpeedAngleOfAttack":0.0,
          "levelsDetails":3, "Temp_levelsDetails":3, "angleOfAttack":0.0,"ownType":0,
          "rotatX":0.0,"rotatY":0.0,"rotatZ":0.0,"rollSelf":0, "pitchSelf":0, "yawSelf":0, "antiForce":0.0,
          "massFuel":0.0, "massChild":0.0,"massFuelTank":0.0, "ownAntiForce":0.0001,"lovushki":0,
          "correctRotat":1.0, "correctSpeed":1.0, "slideRotat":0.0, "speedTemp":0.0,"typeShtopor":"NULL",
          "Temp_BK":0 ,"BK":0,"typeSensor":0,"targetType":0, "timerImpulse":0,"sonicBoom":0, "idTarget":["00000"],
          "PuskSbros":0, "Temp_PuskSbros":0, "indexRadarDist":0, "timerShoot":0, "classWeapon":"", "nameWeapon":"",
          "Temp_typeSensor":0, "localTargetList":[], "THREAT":{
          "UnitAir":{'RL':[],'TP':[],'LD':[],'RD':[],'TV':[],'MG':[],'SN':[],'EL':[]},
          "UnitGround":{'RL':[],'TP':[],'LD':[],'RD':[],'TV':[],'MG':[],'SN':[],'EL':[]}},
          "threatWeapon":[], "sbrosInterval":0, "ochered":0, "Temp_ochred":0, "dictWeapon":{}}
   
#Первое обновление - стартовые проперти - управление, сторона, статус, задача
config.update(KEY['startProp'])
   
#Второе обновление
with open(bge.logic.expandPath(KEY['unitPath']), 'r') as directClass:
    config.update(json.load(directClass)["listPropertys"])
   
class air(bge.types.KX_GameObject):
    nameIndex = Ind
    def __init__(self, old_owner):
        for key in config:
            setattr(self, key, config[key])
            #print(key)
       
        #Небольшое примечание - если самолет изображает мишень на стоянке, то он переводится в разряд наземных объектов
        if self.controlUnit == "Statist":
            self.ownType = 1
       
        if self.controlUnit == "Gamer":                   
           
            gamerConfig = ["a_x","a_y","a_z","Temp_a_x","Temp_a_y","Temp_a_z"]
            for key in gamerConfig:
                 setattr(self, key, 0.0)
           
            setattr(self, "timerFuel", 0)
            setattr(self, "colorHUD", 0) 
           
            sensor1 = scene.objects['SENSOR1']
            sensor2 = scene.objects['SENSOR2']
            sensor3 = scene.objects['SENSOR3']
            #Расстановка сенсоров пустышек для отслеживания ориентации в пространстве
            sensor1.setParent(self, False,False)
            sensor1.worldOrientation = self.worldOrientation
            sensor1.localPosition = [0.0, 1.0, 0.0]
            sensor2.setParent(self, False,False)
            sensor2.worldOrientation = self.worldOrientation
            sensor2.localPosition = [1.0, 0.0, 0.0]
            sensor3.setParent(self, False,False)
            sensor3.worldOrientation = self.worldOrientation
            sensor3.localPosition = [0.0, 0.0, 1.0]

            if 'Cockpit' not in bge.logic.getSceneList():
                bge.logic.addScene('Cockpit',1)
                unit = self.unitName + self.unitNation
                typeCockpit = "//Aircraft/" + unit + "/Cockpit_" + unit + "/Cockpit_" + unit + ".blend"
                bge.logic.globalDict["cockpitPath"] = typeCockpit
               
        elif self.controlUnit == "Bot":                   
            setattr(self, "etalonDvig", 0)
            setattr(self, "typeManeur", "")


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


До строчки со словом class происходит вскрывание json-файлов и обновление словаря config. В этот словарь уже "вшиты" некоторые стандартные значения будущих атрибутов (ну, тех же свойтв-проперти), которые присущи всем машинам этого класса. Это позволило выкинуть эти свойства из перечисления в файле json, уменьшив его объем и сделав более удобочитаемым. В принципе, словарь config можно было бы втиснуть в тело класса (опять же, не совсем уверен в правильности терминологии), а по-простому говоря - между строчками со словами class и def __init__, но в этом случае объект получит еще один атрибут - этот самый словарь с дублированием всех данных внутри него - оно мне надо? Сами свойства объект получает из словаря config либо циклом-перебором всех ключей и значений из config, либо можно и проще  self.__dict__.update(config). В итоге вместо доставших скобочек и кавычек получаем значения типа self.speed = 50. Что немаловажно, этими свойствами атрибутами можно оперировать - добавляя новые - в коде вы увидите строчки с командой setattr - добавть новый атрибут. Кроме них имеются еще hasattr и delattr. Настоятельно рекомендую эти вещи, и в особенности getattr. Что это такое и с чем их едят, вы найдете сами (я имею в виду новичков и самоучек вроде меня, серьезные программисты и так все знают). С помощью этих команд можно закручивать хитроумные комбинации, получая нужные результаты.
После добавления всех атрибутов следует мутация объекта - его трансформация в новый объект с атрибутам, перечисленными в классе. Это можно сравнить с превращением гусеницы в бабочку или фиксацией проявленной фотографии раствором фиксажа (было дело, работал я с ними, но очень недолго - уже наступала эра цветных пленок, а теперь и они сошли на нет - цифровики вытеснили. Прогресс, одним словом).
Функция фиксации результата - def mutate. Кстати, dron предупреждал, что в примере к БГЕ по созданию класса, разработчики намудрили лишнего - достаточно обойтись лишь этой простенькой функцией.
Теперь процесс создания юнита выглядит так - срабатывает ОДНОРАЗОВО сенсор, дающий команду контроллеру на создание экземпляра класса. После чего включается скрипт поведения юнита - этот уже идет непрерывно. Да, логики стало чуть больше, может потом допру, как сделать одноразовый запуск скрипта создания экземпляра класса.
Уже проврено создание и удаление атрибутов сенсоров в зависимости от его названия, написаны новые функции стрельбы ракетами, пушками, сброса бомб и баков, работают кабины для МиГ-23МФ и БН. Подобрался к сенсорам и поведению ботов. плюс можно начинать отработку самонаведения ракет и противодействия им. Теперь борьбу ведут классы.
На мой взгляд, использование классов сильно упрощает работу и дает прирост в скорости.  Единственно, что надо осознать сразу - создание экземпляра класса и мутация объекта делается ОДИН РАЗ. Моя проблема была именно в том, что я этого не понимал и заставлял объект непрерывно мутировать и пересоздавать класс. Надеюсь, те новички, которые читатют этот текст, избегнут моих ошибок.
Картинок в этот раз не будет, потому как идет процесс переписывания по большей части кода с вырезанием кавычек и скобок. Скорее всего, после восстановления работы сенсоров, РЭБ, самонаведения и ИИ ботов я займусь меню. Что-то мне влом писать json для каждой миссии вручную - лучше один раз напрячься с написанием редактора миссий...

суббота, 1 апреля 2017 г.

Напоминание о своем существовании...

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

среда, 8 февраля 2017 г.

Как не надо кодить. Создание и ликвидация бага своими руками.

Я не раз замечал за собой особенность создавать такие ситуации, которые, по-видимому, кроме меня никто не создаст. Во всяком случае в здравом уме, трезвом состоянии и твердой памяти. По-видимому, это у меня такой талант.
Продолжая работу над проектом, я внезапно столкнулся с одной очень специфической проблемой. Как известно, если самолет ударяется об землю, он взрывается. Дальше все зависти от фантазии автора проекта - появляется ли черный дымщийся остов или самолет просто исчезает. В любом случае юнит "убирается с доски". Так вот, неожиданно выяснилось, что если об землю разбивается самолет игрока, то после нажатия кнопки Esc немедленно следует вылет Блендера. Причем было непонятно, что это за баг. dron написал файл для запуска файла через блендерплейер с выводом ошибок в файл error, но ситуацию это не прояснило. Файл оставался пустым.
Баг крайне непрятный и его надо было ликвидировать любой ценой. Но и понять, в чем причина было сложно. я рассуждал так: если вылет следует после исчезновения самолета игрока, значит, это как-то связано с управлением. спешно переделал код управления клавишами - уже тогда у меня появились подозрения, что в этом виновато исезновение оверлейной сцены кабины. Запустил - то же самое. Хорошо, тогда почистим скрипт управления камерами. Дело в том, что я по жадности своей и лени не стал, как в первой версии делать обмен данными между сценами через глобальный словарь, а запускал цикл проверки списка сцен с выбором сцены кабины и дальше уже традиционно обращался к scene.objects с названием объекта. Как говорится: "Жадность фраера сгубила." Однако и после зачистки скрипта камер веселье продолжилось. У меня оставался один-единственный скрипт, где еще существовал вызов списка сцен. А теперь смотрим...

def controlUnit():
    cont = bge.logic.getCurrentController()
    own = cont.owner
    scene = bge.logic.getCurrentScene()
    sensor = cont.sensors["unit"]
    kontakt = cont.sensors["kontakt"]
   
    #Название пропенрти для механики ведомого - при условии
    #что юнит - ведущий, при выпуске тормоза, крыла, шасси и тп он передает команду на ведомого для выполнения такой же операции
    mechanicWingman = ''
   
    #словарь свойств - для анимации, применения оружия и смены подвесок, плюс отслеживание повреждений
    if 'localDict' not in own:
        controlUnitStart()
       
    #Активная камера в сцене
    activeCam = scene.active_camera
       
    #Камера в кокпите юнита игрока
    cameraPilot = scene.objects["CameraPilot"]
   
    #Камера внешнего обзора, она же камера телевизионного прицела
    cameraWorld = scene.objects["CameraWorld"]
   
    #Центр юнита - опорный объект для всх потомков, он же нужен для смены меша на меш номера, объект присутствует изначально, это потомок
    #двигателя, причем неубираемый, также он нужен для анимации проворота фюзеляжа при выпуске-уборке шасси, поскольку на земле многие
    #машины не стоят ровно - как правилог их нос приподнят - больше или меньше
    CntAircraft = own.childrenRecursive['CntAircraft']
   
    #Это - собственно, полет
    if sensor.positive:
       
        #Контакт с ландшафтом
        if kontakt.positive: 
            #столкновение с землей сбитого самолета или вертолета
            if own['crash'] < 0.2 or own['CHASSY'] < 98:
                if 'CameraPilot' in own.childrenRecursive:
                    own.childrenRecursive['CameraPilot'].removeParent()
                    #Убираем оверлейную сцену
                    sceneList = bge.logic.getSceneList()
                    for sceneGame in sceneList:
                        if sceneGame.name == 'Cockpit':
                            sceneGame.end()
               
                CrashPropertyAir()
           
                import ControlScene
                ControlScene.nearUnitExplode()
                #Убираем сам объект
                own.endObject()
       
       
        if own['bot'] == 0:
            #Вызов управления юнита игрока с помощью клавиатуры, вызов идет постоянно
           
            bge.logic.globalDict['idTarget'] = own['idTarget']
            import ClassGamer
            ClassGamer.control()
        elif own['bot'] > 0:
            #Вызов искусственного интеллекта - необходимо доработать, поскольку own['bot']=1 - взлет, 2 - посадка, 3 - полет по маршруту,
            import ClassBotAir
            ClassBotAir.control()
       
        #Пока ЛА исправен, он может лететь
        if own['crash'] > 0 and own['bot'] > -1:
            import ClassEngineAir
            ClassEngineAir.control()
       
        #Падение по балиистической кривой для сбитого
        elif own['crash'] < 0.1 and own['bot'] > -1:
            unit_Crash = __import__(own['unitModule'])
            unit_Crash.CRASHscenery()
            #Для самолета игрока убираем сцену оверлей и камеру пилота
            if own['bot'] == 0:
                if 'sceneryCrash' in own:
                    if own['sceneryCrash']['timerCrash'] == 1: 
                        if 'CameraPilot' in own.childrenRecursive:
                            own.childrenRecursive['CameraPilot'].removeParent()
                    #Убираем оверлейную сцену
                    sceneList = bge.logic.getSceneList()
                    for sceneGame in sceneList:
                        if sceneGame.name == 'Cockpit':
                            sceneGame.end()        
            import ClassEngineAir
            ClassEngineAir.traectoryCrash()
         
        #Просчет уровней детализации
        #Высокий - детализированная анимированная модель
       
        if own.getDistanceTo(activeCam) < 200:
            if activeCam.getVectTo(own)[2][2] < -0.8 and abs(activeCam.getVectTo(own)[2][1]) < 0.4 and abs(activeCam.getVectTo(own)[2][0]) < 0.24:
                own['levelsDetails'] = 0
        #Средний - детализированная неанимированная модель
        elif 200 < own.getDistanceTo(activeCam) < 3500:
            if activeCam.getVectTo(own)[2][2] < -0.8 and abs(activeCam.getVectTo(own)[2][1]) < 0.4 and abs(activeCam.getVectTo(own)[2][0]) < 0.24:
                own['levelsDetails'] = 1
        #Низкий - малодетализированная модель
        elif 3500 < own.getDistanceTo(activeCam) < 10000:
            if activeCam.getVectTo(own)[2][2] < -0.8 and abs(activeCam.getVectTo(own)[2][1]) < 0.4 and abs(activeCam.getVectTo(own)[2][0]) < 0.24:
                own['levelsDetails'] = 2
        #Малый - модели, как таковой, нет
        elif own.getDistanceTo(activeCam) > 10000:
            if activeCam.getVectTo(own)[2][2] < -0.8 and abs(activeCam.getVectTo(own)[2][1]) < 0.4 and abs(activeCam.getVectTo(own)[2][0]) < 0.24:
                own['levelsDetails'] = 3
       
       
           
        unit_module = __import__(own['unitModule'])
        unit_module.correctData()
       
        ####################################
        #Смена уровня детализации
        if own['localDict']['levelsDetails'] != own['levelsDetails']:
            ClassOperation.LOD()
            own['localDict']['levelsDetails'] = own['levelsDetails']
           
        if own['localDict']['dvig'] != own['dvig']:
            ClassOperation.dvig()
            own['localDict']['dvig'] = own['dvig']
           
        if own['localDict']['crash'] != own['crash']:
            import ClassCrash
            ClassCrash.CrashModel()
            own['localDict']['crash'] = own['crash']
       
        #Пробитие звукового барьера  
        if own['localDict']['sonicBoom'] != own['sonicBoom']:
            ClassOperation.sonicBoom()
            own['localDict']['sonicBoom'] = own['sonicBoom']
       
        if own['localDict']['startLO'] != own['startLO']:
            for generatorLO in own.childrenRecursive:
                if generatorLO.name == 'generatorLO':
                    generatorLO['startLO'] = own['startLO']
            own['localDict']['startLO'] = own['startLO']
       
        if own['levelsDetails'] == 0:          
            #Работа проперти рысканья              
            if own['rotatZ'] != 0 or own['localDict']['YAW'] != own['YAW']:
                unit_module.yaw()
            #Работа проперти тангажа              
            if own['rotatY'] != 0 or own['localDict']['ROLL'] != own['ROLL']:
                unit_module.roll()
            #Работа проперти тангажа              
            if own['rotatX'] != 0 or own['localDict']['PITCH'] != own['PITCH']:
                unit_module.pitch()
           
        #Стандартная операция - отсчет кадров с помощью проперти
        #Шасси
        if own['localDict']['CHASSY'] != own['CHASSY']:
            unit_module.chassy()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['CHASSY'] > own['CHASSY']:
                if own['localDict']['CHASSY'] == own['CHASSY'] + 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'GEAR'
                    audioUnit()
            if own['localDict']['CHASSY'] < own['CHASSY']:
                if own['localDict']['CHASSY'] == own['CHASSY'] - 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'GEAR'
                    audioUnit()
       
        #Тормоз
        if own['localDict']['AIRBRAKE'] != own['AIRBRAKE']:
            unit_module.airbrake()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['AIRBRAKE'] > own['AIRBRAKE']:
                if own['localDict']['AIRBRAKE'] == own['AIRBRAKE'] + 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'AIRBRAKE'
                    audioUnit()
            if own['localDict']['AIRBRAKE'] < own['AIRBRAKE']:
                if own['localDict']['AIRBRAKE'] == own['AIRBRAKE'] - 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'AIRBRAKE'
                    audioUnit()
            #Передача команды ведомому срабатывает только один раз
            #Заодно и передается название проперти, которое надо подтянуть
            if own['statusDict']['listWingman'][0] != '1':
                if abs(own['localDict']['AIRBRAKE'] - own['AIRBRAKE']) == 2:
                    mechanicWingman = 'AIRBRAKE'
                    controlUnitWingman(own, mechanicWingman)
                   
           
        #Перекладка крыла - для самолетоа с КИС
        if own['localDict']['WINGS'] != own['WINGS']:
            unit_module.wings()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['WINGS'] > own['WINGS']:
                if own['localDict']['WINGS'] == own['WINGS'] + 2:
                    own['localDict']['audioLife'] = 230
                    own['localDict']['audioProp'] = 'FLAPS'
                    audioUnit()
            if own['localDict']['WINGS'] < own['WINGS']:
                if own['localDict']['WINGS'] == own['WINGS'] - 2:
                    own['localDict']['audioLife'] = 230
                    own['localDict']['audioProp'] = 'FLAPS'
                    audioUnit()
            #Передача команды ведомому срабатывает только один раз
            #Заодно и передается название проперти, которое надо подтянуть
            if own['statusDict']['listWingman'][0] != '1':
                if abs(own['localDict']['WINGS'] - own['WINGS']) == 2:
                    mechanicWingman = 'WINGS'
                    controlUnitWingman(own, mechanicWingman)
             
        #Закрылки
        if own['localDict']['FLAPS'] != own['FLAPS']:
            unit_module.flaps()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['FLAPS'] > own['FLAPS']:
                if own['localDict']['FLAPS'] == own['FLAPS'] + 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'FLAPS'
                    audioUnit()
            if own['localDict']['FLAPS'] < own['FLAPS']:
                if own['localDict']['FLAPS'] == own['FLAPS'] - 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'FLAPS'
                    audioUnit()
           
           
        #Предкрылки
        if own['localDict']['SLATS'] != own['SLATS']:
            unit_module.slats()
            #Передача команды ведомому срабатывает только один раз
            #Заодно и передается название проперти, которое надо подтянуть
            if own['statusDict']['listWingman'][0] != '1':
                if abs(own['localDict']['SLATS'] - own['SLATS']) == 2:
                    mechanicWingman = 'SLATS'
                    controlUnitWingman(own, mechanicWingman)
           
        #Фонарь кабины
        if own['localDict']['CANOPY'] != own['CANOPY']:
            unit_module.canopy()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['CANOPY'] > own['CANOPY']:
                if own['localDict']['CANOPY'] == own['CANOPY'] + 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'GEAR'
                    audioUnit()
            if own['localDict']['CANOPY'] < own['CANOPY']:
                if own['localDict']['CANOPY'] == own['CANOPY'] - 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'GEAR'
                    audioUnit()
       
        ######################################################
        ######################################################
        ######################################################
       
        #Смена подвесок - одиночный вызов функции из скрипта ClassOperation, он изначально вызван в этом скрипте
        if own['localDict']['weapon'] != own['weapon']:
            own['PR'] = 0
            own['sbros'] = 0
            #Запускаем функцию работы по отсоединению подвесок и использованию оружия
            ClassOperation.weaponsUnit()
            #Корректируем значения словаря юнита
            own['localDict']['weapon'] = own['weapon']
           
        #Стрельба - одиночный вызов функции из скрипта ClassOperation, он изначально вызван в этом скрипте
        if own['localDict']['sbros'] != own['sbros']:
            if own['sbros'] == 1:
                ClassOperation.shooting()
            own['localDict']['sbros'] = own['sbros']
       
        #Это - определение целей при смене текущй цели или выборе типа прицеливания
        if own['localDict']['enemy'] != own['enemy'] or own['localDict']['targetType'] != own['targetType']:
            if len(own['localDict']['localTargetList']) > 0:
                try:
                    own['idTarget'] = own['localDict']['localTargetList'][own['enemy']]
                    if 'CameraPilot' in own.childrenRecursive:
                        own.childrenRecursive['CameraPilot']['idTarget'] = own['idTarget']
                except:
                    own['enemy'] = 0
                    own['idTarget'] = own['localDict']['localTargetList'][0]
                    #При ошибке убираем индекс из списка
                    #if own['idTarget'] in own['localDict']['localTargetList']:
                       #own['localDict']['localTargetList'].remove(str(id(target)))
                   
           
            own['localDict']['enemy'] = own['enemy']
            own['localDict']['targetType'] = own['targetType']

        #Сортировка списка угроз
        if own['timerThreat'] > 3.0:
            threatSorted()
           
       
        #Работа сенсоров - вызов функции из скрипта ClassSensor, он вызывается через разные промежутки времени, минимум раз в секунду
        #и только при "живом юните", время паузы между сканированием регулируется - оно задается при переключении сенсоров
        if own['crash'] > 0.1 and own['bot'] > -1:
            #if own['limTimerScan'] > own['scanTimer']:
               
                sensorUnit = own['localDict']['sensList'][own['typeSensor']]
                ClassSensor.SensorDef()

Для облегчения понимания я подчеркнул место своей логической ошибки. Получается, что при получении нулевого уровня исправности происходит повторный вызов списка сцены и требование убрать уже несуществующую! Причем эта операция осуществляется за один тик - в один проход функции. Во всяком случае, я такой вывод сделал, скажем так. Из этого следует вывод, что так быдлокодить нельзя. Питон, конечно терпелив и высвечивает программеру-кодеру его ошибки в консоли, но и у него иногда лопается терпение. Может быть, кто-то из гораздо более сведущих в программировании людей посчитает этот вылет багом самого Питона, в котором не выполняется исключение перехвата ошибок. Однако подобный прискорбный случай говорит и том, что никто, кроме меня, подобную ситуацию еще не создавал...
В общем, проблема была решена путем удаления лишнего обращения к функции. я жестко указал в коде, что операция по поиску и удалению оверлейной сцены должна происходить только при получении проперти crash = 0. И неважно, чем это вызвано - стокновением с землей, сбитием, столкновением с другим юнитом. Меняется проперти crash? Оно равно нулю? Вызываем функцию CrashModel. Только ОДИН раз, и в ней же проверяем - есть в потомках камера пилота? Тогда убираем оверлейную сцену. ВСЕ!
После переделки кода последовал запуск-тест. специально гробанул многострадального "кролика" подопытного МиГ-23 об землю. Выхожу из игрового режима. Все в норме. Еще раз! все чисто. Но, товарищи, так кодить нельзя.
правда, попутно еще выловил и устранил баг с пушкой, которую я сломал в ходе своих "оптимизаций" довольно давно, почистил код. К тому же, действуя по принципу: "Коль пошла такая пьянка, режь последний огурец", в очередной раз устроил погром в скрипте кабины. Собирался я это сделать давно, просто удачно под руку подвернулось. Многие оперции в кабинном скрипте стандартны, как и имена объектов сцены. к примеру, лампы индикации подвесок, закрылков, тормоза, шасси. Нет смысла из файла в файл плодить одни и те же функции с одинаковыми названиями и одинаковым до буквы кодом. Поэтому в игровом файле-стартере я создал еще один скрипт ClassCockpit, куда и сбросил все стандартные функции из кабинногог кода. Теперь в коде кабины работают только стрелки приборов да те вещи, которые уникальны для того или иного самолета. Например индикация топливной системы.
Буквально на днях Андрей предложил переделать скрипт загрузки сторонних бленд-файлов через асинхронизацию и дал образец-пример. Надеюсь, сумею разобраться и применить относительно своего файла, потому что собирался это делать в любом случае, к тому же в способе dron-a есть возможность вставить красивую картинку, а не черный плейн, как у меня. Будем продолжать движение.
                   

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