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

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

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