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

вторник, 28 июля 2020 г.

Снова о ландшафтах и квадратах, то есть блоках.

Чтение учебной литературы по разным темам отнимает много времени, но работа над проектом все ще продолжается, хотя и непонятно, чем все это закончится и закончится ли вообще. Но и сидеть, ничего не делая, тоже неохота.
Недавно отработал самонаведение ракеты в Unity, отыскал аналогичный урок для UE4, провел массовое обновление ПО - Юнити и UE4, плюс VisualStudio, обновил Блендер несколько раз, пробовал UPBGE для EEVEE. Увы, для последнего альфа сборка - это альфа и есть. Категорически не хочет работать подгрузка бленд-файлов через LibLoad - UPBGE мгновенно вылетает. Смотрел и Armory3D, но толком не занимался. Просмотрел и прослушал курсы по Блендеру от А. Слаквы, для Блендер 2.8, пытаюсь в нем работать - с непривычки тяжело.
Есть мысли по поводу упрощения и сокращения проектов в Юнити и БГЕ, касающиеся прежде всего моделей оружия и их json файлов. Если вкратце - стоит провести объединение в одном файле вариантов подвески для ракет однотипного семейства, например AIM-120, AIM-7, Р-3/13, Р-23/24, Р-27 и так далее. Все дело в том, что для нескольких файлов json имеются одинаковые координаты и углы поворота ракет для подвески, отличаются они лишь наименованиями самих ракет - всего-то пара слов, даже не строчек. Поэтому имеет смысл просто перечислить в отдельной строке меши ракет, которые надо найти для подгрузки, а вместо названий ракет в словах проставить missileStr, а не R-23R, которые будут указаны выше.

{

"objList":["R-23R", "R-23T", "R-24R", "R-24T", "R-24RM"],

"obves":{"FLG_APU23_|":{"parentObj":"CntAircraft","locObj":[0.0,0.0,0.0],"rotObj":[0.0,0.0,0.0]},
         "missileStr|1":{"parentObj":"CntAircraft","locObj":[-1.428,-2.0,0.03],"rotObj":[-0.035,0.0,0.0],"weapon":1,"CatapultSbros":0},
         "missileStr|2":{"parentObj":"CntAircraft","locObj":[1.428,-2.0,0.03],"rotObj":[-0.035,0.0,0.0],"weapon":1,"CatapultSbros":0}
         }
}

Поскольку у меня отлажена система поиска файлов с разным расширением, то поисковик-скрипт отыщет и подгрузит все нужное. А вместо пяти файлов для ракет можно получить один. Это для Р-23 и Р-24, а для aIM-120 вместо 8 будет 1 и для "Спэрроу" вместо 12 - 1, столько же для "Сайдуиндеров"...
Есть еще один нюанс. Для ракет типа "Спэрроу" и "АМРААМ модели внешне почти неотличимы или совсем неотличимы, поэтому в файле ТТХ ракеты надо просто перечислить названия моделей ракет например Sparrow_II и скоратить число блендов. А сами ТТХ ракет объединить в json - поисковик все найдет...

Но все это по ракетам, а есть еще ландшафт. Тут тоже есть новые задумки. Ландшафт разбивается на квадраты типа А1, Б4 и так далее. Все эти квадраты упакованы в отдельные бленды, и снабжены json  с перечислениями стоящих на них объектов. В зависимости от положения активной камеры сначала грузятся стартовый квадрат и 8 квадратов вокруг него. В адльнейшем идет отслеживание положения камеры и "догрузка", если надо, но происходить это будет редко. Фактически, выбирается один квадрат, что-то вроде "центра мира" и вокруг него выстраивается "периферия". Но и это еще не все. Новый "центр мира" "оттаскивается" в нулевое исходное положение, вместе с ним на ту же величину переносятся и ранее сгенеренные "квадраты" со всем их содержимым. Плюс юниты игры также сменяют сове положение на величину "единицы" ландшафта. А создавалась эта система с прицелом на Юнити. Большой ландшафт единым кусокм делать неудобно, плюс говорилось, что координаты больше 100 тысяч единипц приводят к некорректной работе и тормозам, значит, надо уменьшать масштаб самих юнитов, ну раз в 10. Тогда надо учесть, что и их скорости и величина ускорения свободного падения и размеры статических объектов (деревья, здания, дороги) надо также отмасшатбировать, уменьшив в 10 раз.
Возвращаясь к ландшафтуи отрабатываемой сейчас системой его "постройки", скажу, что с "передвиганиями" юнитов, по идее, должно получиться "удерживать" юнит игрока внутри некоторого предела, да и остальные юниты, в общем-то тоже.

Чкрипт  на данный момент:
import bge
scene = bge.logic.getCurrentScene()

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

bge.logic.globalDict["nameBlock"] = "D4"
scaleBlock = 2.5

#Конфигурация расположения блоков террайна - вложенные списки
configBlock = [
              ["A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8"],
              ["B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8"],
              ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8"],
              ["D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8"],
              ["E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8"],
              ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"],
              ["G1", "G2", "G3", "G4", "G5", "G6", "G7", "G8"],
              ["H1", "H2", "H3", "H4", "H5", "H6", "H7", "H8"]                             
              ]
   
def BlockTerrain():
    cont = bge.logic.getCurrentController()
    own = cont.owner       
    #Индексы списка блоков - внешний и вложенный
    x = 0
    y = 0
    #Величина перемещения блоков и их направление
    posX = 0.0
    posY = 0.0
   
    nameBlock = bge.logic.globalDict["nameBlock"]
   
    #Список элементов, содержащих информацию о блоках и их смещении при появлении
    listIndex = []
    #Список уже имеющихся блоков террайна
    terrainList = []
   
    #Сначала ищем в общем списке вложенный с наименованием "центра", вокруг которого
    # выстраиваются еще 8 дополнительных блоков террайна 
    for listObj in configBlock:
        for obj in listObj:
            #После нахождения "центра мира" в списке блоков внутри общего списка блоков террайна
            if nameBlock == obj:
                #Заносим его в список индексов, это обязательно, смещение для "центра" нулевое
                listIndex.append( str( configBlock.index(listObj) ) + "_" + str( listObj.index(obj) ) + "|" + "0.0" + "_" + "0.0" )
                #Загоняем в список блоки "перед" и "позади" "центра мира", учитывая пределы индексов списка
                if listObj.index(obj)-1 > -1:
                    listIndex.append( str( configBlock.index(listObj) ) + "_" + str( listObj.index(obj)-1 ) + "|" + "0.0" + "_" + str(-scaleBlock) )
                if listObj.index(obj)+1 < len(listObj):
                    listIndex.append( str( configBlock.index(listObj) ) + "_" + str( listObj.index(obj)+1 ) + "|" + "0.0" + "_" + str(scaleBlock) )
               
                #А теперь осматриваем вложенные списки "выше" и "ниже"  найденного, опять учитываем пределы индексов
                #Если такие списки есть, то заносим в listIndex информацию о блоках с индеками "центра мира" и плюс-минус 1
                if configBlock.index(listObj)-1 > -1:         
                    listIndex.append( str( configBlock.index(listObj)-1 ) + "_" + str( listObj.index(obj) ) + "|" + str(scaleBlock) + "_" + "0.0" )
                    if listObj.index(obj)-1 > -1:
                        listIndex.append( str( configBlock.index(listObj)-1 ) + "_" + str( listObj.index(obj)-1 ) + "|" + str(scaleBlock) + "_" + str(-scaleBlock) )
                    if listObj.index(obj)+1 < len(listObj):
                        listIndex.append( str( configBlock.index(listObj)-1 ) + "_" + str( listObj.index(obj)+1 ) + "|" + str(scaleBlock) + "_" + str(scaleBlock) )
               
                if configBlock.index(listObj)+1 < len(configBlock):
                    listIndex.append( str( configBlock.index(listObj)+1 ) + "_" + str( listObj.index(obj) ) + "|" + str(-scaleBlock) + "_" + "0.0" )
                    if listObj.index(obj)-1 > -1:
                        listIndex.append( str( configBlock.index(listObj)+1 ) + "_" + str( listObj.index(obj)-1 ) + "|" + str(-scaleBlock) + "_" + str(-scaleBlock) )
                    if listObj.index(obj)+1 < len(listObj): 
                        listIndex.append( str( configBlock.index(listObj)+1 ) + "_" + str( listObj.index(obj)+1 ) + "|" + str(-scaleBlock) + "_" + str(scaleBlock) )
   
    #Проверка на нличие блоков террайна в сцене
    for obj in scene.objects:
        if "Terrain" in obj.name:
            terrainList.append(obj.name)
   
    #По окончании составления списка препарируем каждый его элемент типа 3_2|2.5_-2.5     
    for element in listIndex:
        #Перед "|" указаны индексы общего списка и вложенного списка, они дают выход на элемент "D3" в данном случае
        x = int( element.split("|")[0].split("_")[0] )
        y = int( element.split("|")[0].split("_")[1] )
        if "TerrainQuad_" + configBlock[x][y] not in terrainList:
            #Добавляем блок террайна и препарируем элементы после "|"
            BlockTerrain = scene.addObject("TerrainQuad_" + configBlock[x][y], own)
            #Получаем смещение ОТНОСИТЕЛЬНО "ЦЕНТРАЛЬНОГО" блока, весь отсчет идет относительно него
            posX = float( element.split("|")[1].split("_")[1] )
            posY = float( element.split("|")[1].split("_")[0] )
            #Смещаем только что добавленный блок и переходим к следующему - и так до конца списка
            BlockTerrain.worldPosition[0] += posX
            BlockTerrain.worldPosition[1] += posY
           
def control():
    cont = bge.logic.getCurrentController()
    own = cont.owner
    cam = scene.objects["Camera"]
    deltaX = 0.0
    deltaY = 0.0
    nameBlock = ""
    if cam.worldPosition[0] < -scaleBlock or cam.worldPosition[1] < -scaleBlock or cam.worldPosition[1] > scaleBlock or cam.worldPosition[0] > scaleBlock:
        if cam.worldPosition[0] < -scaleBlock:
            deltaX = -scaleBlock
        elif cam.worldPosition[0] > scaleBlock:
            deltaX = scaleBlock
        elif cam.worldPosition[1] < -scaleBlock:
            deltaY = -scaleBlock
        elif cam.worldPosition[1] > scaleBlock:
            deltaY = scaleBlock
       
        for obj in scene.objects:
            if "Terrain" in obj.name:
                obj.worldPosition[0] -= deltaX
                obj.worldPosition[1] -= deltaY
               
                if -scaleBlock * 0.1 < obj.worldPosition[0] < scaleBlock * 0.1 and -scaleBlock * 0.1 < obj.worldPosition[1] < scaleBlock * 0.1:
                    bge.logic.globalDict["nameBlock"] = obj.name.split("_")[1]
                   
        cam.worldPosition[0] -= deltaX
        cam.worldPosition[1] -= deltaY
        BlockTerrain()
       
#Первый стартовый запуск функции генерации и расстановки блоков террайна
BlockTerrain()

Думаю, комментарии делают этот код понятным... Надеюсь, во всяком случае. )))
Сам же террайн в БГЕ (или УПБГЕ, когда его отладят) планируется рскрасить по способу denis8424 - с разделением материалов по высоте, но с одним дополнением. Для каждого блока террайна провести смешивание текстур через маски, правда, это уж как получится. Там надо большое разрешение масок, все же даже масштабированные блоки - это 10 км, но можно попробовать маски при наложении дублировать - при больших размерах повторяемость не будет сильно бросаться в глаза, плюс для разных блоков маски будут разными, а число блоков в сумме - не слишком велико, вряд ли больше 100 (уж точно не 2500).


воскресенье, 4 марта 2018 г.

Доводка классов. Прячем все.

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

import mathutils
import random
import bge
scene = bge.logic.getCurrentScene()

def control(self):
    cont = bge.logic.getCurrentController()
    own = self
    if own.engineWeapon == 'CatapultWeapon':
        CatapultWeapon(own)
    else:
        Missile(own)
   
   
#Полет ракеты
def CatapultWeapon(own):
    if own.vzryvatel == 10:
        own.suspendDynamics
        own.engineWeapon = "Missile"

#Полет ракеты
def Missile(own):
   
    own.applyMovement([0.0,own.speed/60,0.0],True)
    own.timerAmountFire += 1
    if own.amountFire > own.timerAmountFire:
        #Срабатывание добавления частиц дыма от ракет
        own.timerSmoke += 1
        if own.timerSmoke == 1:
            #Дым снаряда
            smoke = scene.addObject('ParticleUniversal', own)
            smoke.replaceMesh("SmokeLong", True, False)
            smoke.setParent(own, False, False)
        if own.timerSmoke == 3:
            if 'ParticleUniversal' in own.childrenRecursive:
                own.childrenRecursive['ParticleUniversal'].scaleX = own.speed/360
                own.childrenRecursive['ParticleUniversal'].scaleY = own.speed/12
                own.childrenRecursive['ParticleUniversal'].scaleZ = own.speed/360
                own.childrenRecursive['ParticleUniversal'].Delta_scaleX = 0.1
                own.childrenRecursive['ParticleUniversal'].Delta_scaleY = 0.1
                own.childrenRecursive['ParticleUniversal'].Delta_scaleZ = 0.1
                own.childrenRecursive['ParticleUniversal'].Delta_colorAlpha = -0.0005
                own.childrenRecursive['ParticleUniversal'].colorAlpha = 0.2
                own.childrenRecursive['ParticleUniversal'].removeParent()
        if own.timerSmoke == 5:
            own.timerSmoke = 0
             


А теперь скрипт класса Р-27Р, который вызывает этот самый скрипт и скрипт головки самонаведения.

import bge

from ClassWeapon import typeWeapon

class R27R(typeWeapon):
       
    import Weapon_Missile as __Weapon_Missile
    import Weapon_GSN as __Weapon_GSN
   
    def __init__(self, old_owner):
        typeWeapon.__init__(self, old_owner)

    def engine(self):
        self.__Weapon_GSN.GSN(self)
        self.__Weapon_Missile.control(self)
       
def mutate(cont):
    R27R(cont.owner)
             

Ну, и собственно, функция работы оружия в игровом файле.

#Функция эффекта взрывов
def controlWeapon():
    cont = bge.logic.getCurrentController()
    own = cont.owner
    own.engine()
   

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

import random
scene = bge.logic.getCurrentScene()

#Эта переменная определяет длину "хвоса" огня
fireLong = random.randrange(8, 20)

def FireAir():
    cont = bge.logic.getCurrentController()
    own = cont.owner
   
    #Длина списка потомков
    particleLen = len(own.childrenRecursive)
   
    #Это просто списко для ускорения добавления частиц - удвоение или утроение за один проход в тик
    listObj = ["num0"]
    #listObj = ["num0","num1"]
   
    #Характеристики частицы - локация, размер и цыкт с прозрачностью
    partColor = random.randrange(2, 10)/10
    partLoc = random.randrange(particleLen, particleLen+2)
    partScale = random.randrange(particleLen, (particleLen+2)*2)/(particleLen+2)*4
   
    #Эта переменная используется для отслеживания индекса потомка в списке
    indexObj = 0
   
    #Этап1 - добавляем, парентим и раставляем частицы, отслеживая длину списка потомков
    if particleLen < fireLong:
        for newParticle in listObj:
            newParticle = scene.addObject('Plane', own)
            newParticle.setParent(own, False, False)
            newParticle.replaceMesh("FireAir_", True, False)
            newParticle.localPosition = [partLoc/10, -partLoc, partLoc/10]
            newParticle.worldScale = [partScale, partScale, partScale]
           
    #Этап 2 - работаем с тем, что есть, больше ничего не добавляем
    else:
        for obj in own.childrenRecursive:
            #Здесь переменные локации и размера используются в качестве эталона,
            #к которому подтягиваются ТТХ частиц
            indexObj = own.childrenRecursive.index(obj)
            #partLoc = random.randrange(indexObj, indexObj*indexObj*indexObj+2)
            partLoc = random.randrange(indexObj, indexObj+2)
            partScale = random.randrange(indexObj, (indexObj+2)*2)/(indexObj+2)*3
           
            #Можно поиграться с цветом и прозрачностью
            obj.color = [1.0, partColor, partColor, partColor]
           
            #Хвост пламени - локальные координаты Х
            if obj.localPosition[1] < -partLoc:
                obj.localPosition[1] += partLoc/50
            elif obj.localPosition[1] > -partLoc:
                obj.localPosition[1] -= partLoc/50 
            #Размазанность и толщина хвоста пламени - координаты Икс и Зет
            obj.localPosition[0] = partLoc/4
            obj.localPosition[2] = partLoc/4
           
         
Вот так. Хотя, все это - костыли. Пусть и работающие. Подожду пока прояснения обстоятельств с перхватом рендера и управления БГЕ. Если обстоятельства позволят, то откроются новые перспективы.

вторник, 9 января 2018 г.

Размышления на тему игрового ИИ.

Всех  с прошедшими праздниками, которые вновь сумели пережить... правда, новгодние каникулы закончились не для всех, и явно еще не израсъодована пиротехника, заботливо приберегаемая для встречи Сатрого Нового Года...
Пока что успешно завершена война с анимацией подвижных частей авиатехники - ее последним аккордом стала борьба с элеронами, которая отрабатывалась уже на МиГ-29. Как обычно, казавшаяся простой и легкорешаемая задача сожрала больше времени, чем та, которая признавалась сложной и запутанной. Это я про интерцепторы. Затем последовала стандартизация уже собранных ранее классов самолетов, которая пока не коснулась Ф-16 и Су-25. У Ф-16, кстати, как и у Су-27 имеются "интерцепоторы наоборот" - флапероны, так что там придется менять знаки перед углами отклонения - интерцепторы поднимаются, а флапероны опускаются...
Подошла очередь искусственного интеллекта. Скрипт этого имитатора интеллекта, будем уж честными до конца, у меня насчитывал уже больше пары тысяч строк и отыскивать в нем нужное  место для внесения изменений становилось все сложнее... Поскольку метод дробления больших скриптов на узкоспециализированные модули уже отработан, то супер-скрипт ИИ (в смысле размеров) постигла та же участь. Он раздробился примерно на дюжину модулей, относительно небольших. Пока раздробление прошло начерно - я еще не приступал к отработке связей между отдельными модулями. Но примерная схема уже вырисовывается. Модули можно разделить на несколько категорий.
1. Стандартные модули разворотов юнита и его ориентаций.
-Модуль стандартного движения при крене-тангаже-рыске -приложение сил в определенном направлении.
-Модуль ориентации в пространстве для горизонтального полета. Выравнивание по крену и тангажу, чтоб самолет летел прямо с выдерживанием нужной высоты. Также в этом модуле есть набор высоты и снижение. Некоторые машины имеют ограничения по крену и тангажу (это характерно для тяжелых машин, таких, как транспортники, хотя были уникумы, крутившие "бочку" на Ту-16, но для тяжелых бомберов такая вещь ни к чему, на мой взглядд), так что в этом модуле они так же соблюдаются.
-Модуль с ориентацией на цель - противника или точку маршрута. Ту, думаю, понятно.
-Модуль выдерживания строя. Пока в природе не существует - только в моей голове и весьма смутно.
2. Модули сканирования и выбора сенсоров и вооружения.
-Пока один модуль - в нем есть функции выбора отимального вооружения (подальнобойнее) и подбора к нему сенсора. Также в этом модуле идет сканирование окружающего мира на предмет бодания лбом земли и наличия угроз - например летящей к боту ракеты.
3. Стандартные модели поведения ботов на земле и в полете. Их много, этих модулей...
-Модуль руления на взлет.
-Модуль руления после посадки.
-Модуль взлета.
-Модуль посадки.
-Модуль полета по маршруту.
-Модуль уклонения от атаки. Включает в себя выбор варианта уклоонения от атаки при помощи сочетания маневров типа "горка" с "размезанно бочкой" и тд. А также постановку активных и пассивных помех.
-Модуль атаки цели. в нем имеются подварианты - атака наземной или воздушной цели. и этот модуль будет потихоньку разрастаться по мере наработки опыта.
-Модуль уклонения от столкновения с землей.
-Модуль набора энергии после маневра. Как бы ни был самолет маневрен, но он может потерять скорость на том же вираже и сорваться в штопор, чего допускать нельзя. Поэтому самолет надлежит аккуратно выровнять, врубить форсаж и снова разогнать.

вот пока примерно так.Есть еще любопытные мысли на этот счет. А именно - по поводу сканирования встречи сземлей. Пока у меня имеется один террайн из 2500 блоков. На мой взгляд, это все же жирно, поэтому лучше сделать из пары сотен блоков. Или 400 - макимум. Причем для террайна ввести свой json, в котором можно перечислить положение блока в прсотранстве, его высоту, отметить координаты горных районов и тд. Зачем? А вот зачем...
Как известно, модули стандартных функций БГЕ типа луча много кушают, поэтому лучше лишний раз их не трогать. А зачем врубать этот самый луч, если самолет находится над блоком, самая высокая вершина которого имеет 500 метров, а высотв полета самолета - 2000? Незачем... Вот для этого и нужны данные по высоте блока. Но это еще не все. В горных районах ботам следует соблюдать осторожность и идти с огибанием рельефа - тут пригодится список маршрутных точек в этом районе. Чтобы не впечататься в стену ущелья, например. Там еще придется вводить алгоритм постройки маршрута по этим точкам, но суть, я думаю, понятна.
В свое время denis8424 продемонстрировал в своем блоге приер движения бота по земле без использования физики. Но при этом бот плавно повторяет все неровности ландшафта - используется словарь координат вершин. он генерится после появления ландшафта, но можно попытаться забить этот словарь в json блока террайна. Но тут еще думать надо.
Пока с ИИ придется слегка притормозить - надо раздробить хотя бы наскоро скрипт оружия - он тоже здоровенный и с ним тоже надо решать кое-какие набившие оскомину вопросы...

понедельник, 2 октября 2017 г.

Размышления о наземных объектах-2. Как бы все это разрушить...

Как обычно, после перерыва в использовании блога по причине израсходованого трафика, узнаешь много нового... Так, сегодня freenome отказался пускать меня в мой же блог. Это было для меня в первый раз, но не первый раз в Сети. быстро пробежавшись по сети, нашел упоминания о подобном саботаже, причем не только freenome, но и других хостингов. Ну раз так, то вникать я в причины блокировки не стал. Это достаточно хроническое явление для бесплатных доменов и у меня нет времени и сил, чтобы вникать в тонкости взаимоотношений сайтоы, блогов и хостингов. спросил у dron-а, что с этим делать и утвердился в мысле, что надо просто убрать использование домена из настроек. Надеюсь, получилось и эти строчки могут прочитать все остальные.
Несколько дней я пытался нащупать хоть какую-то схему воздействия на игровые объекты. Задача состояла в том, что разные типы боевых частей по-разному воздействуют на объекты. В конце концов, пришел к выводу, что объект должен обладать неким атрибутом, назовем его "уровнем боевой устойчивости". Или crashDefens, как-то так. Сей уровень характеризуется неким порогом, ниже которого поразить объект оружием с низким ТТХ невозможно. Например, как-то натолкнулся на вопрос:"Может ли ЗУ-23-2 остановить танк?". Ответ воевавшего в Чечне танкиста был таков: "Может. Если экипаж танка ее заметит и остановится. Для прицеливания..."
Вывод:
В оружейные ТТХ надо вводить уровень  "преодоления защиты", эффективности, что ли, так скажем.
И тут начинается самое нитересное.  Нужен какойто эталон, от которого можно отталкиваться.  И в качестве эталона была выбрана бомба ФАБ-100. Известно, что ее радиус поражения (сплошной) составляет 12 метров. Вообще в самих бомбах где-то половина веса - это металл, остальное - взрывчатка. Ну что ж, берем 50 кг за 0.1 . Извращение, конечно, ну ладно, в конце концв изготовлять эталон веса или длины мне не надо. Примем за основу. С бомбами далее оказалось проще - 28 метров для ФАБ-250 и 40 - для полутонных бомб. исходя из этого я и правил показатель...
предполагаю, что внутри этого радиуса сплошного поражения наземные юниты уничтожаются, если, это, конечно, не бункер с уровне защиты 0.5. Тому же "Абрамсу" будет фиолетово, что упало ему на башню - ФАБ-100, 250 или ОФАБ-250. А вот потом...
На расстоянии двойного радиуса поражения эффективность оружия падаеь  вдвое,  еще дальше - вчетверо. Но, если рядом с "Абрамсом" в момент взрыва стоял еще один танк и БМП в зоне падения ущерба, то порог воздействия на танк преодолен не будет, а вот БМП может и выйти из строя после попадания осколков и удара взрывной волны.
Понимаю, сии измышления неплохо бы подкрепить кодом, но был занят - срочно вписывал в json бомб и УРВВ  данные об их могуществе и типе БЧ.
Прикол заключается в том, что БЧ ракет "воздух-воздух" на три четверти состоят из поражающих элементов и только четверть забирает взрывчатка. В общем-то опять натягивание совы на глобус получается, но вменяемой и систематизированной информации по этому вопросу нет. нет, найти массу БЧ ракет не проблема, но далеко не всегда указывается вес ВВ. Ну ладно, решил так сделать.
Объем работ оказался приличный. А впереди еще ракеты "воздух-земля"...
Попутно выяснил еще ое-каие вещи. Например, что ракета Р-27ЭМ заточена под перехват КР "томагавк" или ПКР "Гарпун". А вот ракета Р-27ЭП специализируется на выбивании постановщиков помех. Это дело надо учесть. Еще ракетами типа Р-77, Р-73, Р-77 и AIM-9X можно организовать персональное ПРО, отстреливая пущенные по тебе ракеты.
Также попалась серия интересных статей на Афтершоке по авианосцам и крылатым ракетам. Выяснилось, что многие, так называемые эксперты не знают некоторых элементарных вещей. Например, среди поклонников западной военной мощи бытует мнение, что ПКР российских кораблей посшибают ракетами и пушками "Хорнеты", причем за считанные минуты, а потом наши корабли будут расстреляны, как в тире. Ну-ну... Другие, наоборот, впадают в крайность и яростно отмахиваются от необходимости иметь свои авианосцы для обеспечения ПВО кораблей. Но тут есть свои нюансы...
Недавно на Дальнем востоке прошли учения по отражению удара КР с моря силами ВВС и ПВО. Причем с боевыми стрельбами. и хотя цели были поражены, выяснилась одна настораживающая деталь. МиГ-31 дал залп двумя ракетами по ПКР "Гранит", шедшей по маршруту и попал. Цель была изрешечена, но невозмутимо продолжала себе лететь. Последовал второй залп. Только после этого ПКР удалось свалить. все дело в том, что у "Гранитов2 есть бронирование, защишающее важные узлы и сбить ее не так то просто. На одну ракету ушло 4 Р-33 - весь БК одного МиГ-31. "Хорнеты" могут стрелять AIM-120, БЧ которых сдабее больше чем в два раза. Итого, ладно, с натяжкой будем считать - 6 АМРААМ на один "Гранит". Но в залпе 20 ПКР. И кроме них, кое-что еще помельче. По поводу пушек - даже не смешно - отстрел "Гранитов2 на полигоне из 20-мм пушки не дал НИЧЕГО.  Поразить эту ПКР может лишь 30-мм пушка, да и то с рядом условий.
Придется ставить уровень боевой устойчивости еще и на оружие... Добавлю, что для надежного уничтожения ПКР нужен ЗРК, но обнаружить подлетающую над волнами ПКР он может не так далеко, есть еще время реакции, что приводит к тому, что ЗУР стартует, когда расстояние до корабля для ПКР уже меньше километра (а иногда сильно меньше). И есть еще мертвые зоны, и есть бортовая РЭБ самой ракеты и есть увеличенная точность попадания в уязвимые места.
Короче, морской бой, если я до него доберусь, обещает быть очень интересной задачкой.
Ладно, вернемся к нашим баранам, то бишь наземным объектам. Действие фугасных и осколочно-фугасных бомб и снарядов я описал выше. Все зависти от расстояния до окружающих объектов.
Но есть подкалиберные снаряды и кумулятивные снаряды. Вот для них радиус поражения нулевой. Они втыкаются в броню конкретного танка, БМП или БТР, "не отвлекаясь" на окружающее. Если при взрыве фугаса надо циклом перебирать все объекты-юниты по расстоянию, то в этом случае просто идет проверка условий по единственной цели - в которую снаряд попал. Тут уже вступает в строй условие наличия-отсутствия  динамической защиты и угла встречи с броней, если таковая есть и имеются данные по ее толщине.
На b3d.ua есть WIP от  exooman-a, который занимается танками, в отличие от меня. Он тоже всячески рассчитывает подобные мелочи, у него тоже есть ДЗ, толщина и угол брони и много чего...
Надо добивать ракеты класса "воздух-земля", дописывая для них json, в основном с оружием будет закончено, но остаются еще зажигательные баки, кассеты, контейнеры с суббоеприпасами и тому подобная мелось. И такие извращения, как "радость танкиста" - кассеты с самоприцеливающимися боевыми элементами, опускающимися на парашютах и прошибающими крыши танков и прочей техники кумулятивной струей.
А еще шейдеры и карта высот, которую я еще не успел поновой закачать у denis8424 (движение по карте высот без физики). Разные интересные мысли крутятся, однако... Вроде полногог отказа от физики, кроме снарядов и бомб. В ракетах же отказался и ничего, все нормально...

вторник, 5 апреля 2016 г.

"Случай в квадрате 36-80 или "Операция Святой Януарий". Вторая серия, однако...

Ранее  в посте со схожим названием я описал свой способ подгрузки блоков ландшафта. чуть позже  выяснилось, что не все так гладко, как хотелось бы. К сожалению, далеко не сразу москх додумывается до более полного использования возможностей им же изобретенного алгоритма.
Выяснилось, рнаботать-то подгрузка ландшафта работает, вот только идет лютая, хотя и кратковременная просадка логики - аж до 70 процентов и имеет место быть "мигание" террайна. В предыдущем посте я уже писал о путях решения проблемы. Выяснилось, что ни один из них не нужен, поскольку есть еще один - более простой и быстрый. Сначала приведу текст переименования террайна и выправления координат центра для каждого его блока.

import bpy
#print('xxx') # пoтому что камасутра чистой воды
scene = bpy.context.scene

listCoord = [-49,-47,-45,-43,-41,-39,-37,-35,-33,-31,-29,-27,-25,-23,-21,-19,-17,-15,-13,-11,-9,-7,-5,-3,-1,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49]

listIndex = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]

blockX = 0
blockY = 0

cursorX = 0
cursorY = 0

for obj in scene.objects:
    if '.' in obj.name: # у тебя должно быть другое имя
        obj.select = True
        X, Y, Z = obj.location
        obj.data.name = obj.name
        bpy.ops.object.origin_set(type = 'ORIGIN_GEOMETRY', center = 'MEDIAN')
      
        for coordX in listCoord:
            if (coordX-1)*10000 < X < (coordX+1)*10000:
                blockX = listCoord.index(coordX)
                scene.cursor_location[0] = coordX*10000
              
        for coordY in listCoord:
            if (coordY-1)*10000 < Y < (coordY+1)*10000:
                blockY = listCoord.index(coordY)
                scene.cursor_location[1] = coordY*10000
      
        scene.cursor_location[2] = 0.0
        bpy.ops.object.origin_set(type = 'ORIGIN_CURSOR')
      
        newName = str(blockX) + '_' + str(blockY)
        obj.name = newName
        obj.data.name = obj.name
        print(obj)
        obj.select = False

Смысл всего этогог заключается в том, что каждый блок получает конкретное название, состоящее из слова "блок" и индекса квадрата, в котором он находится. в коде, думаю, ясно, как формируются индексы квадратов по Х и Y. Такое же название получает и меш блока. Теперь не надо создавать текстовый файл с длиннющим списком названий блоков, и их координатами. КООРДИНАТЫ каждого блока ВШИТЫ в его имя. с определенной поправкой, правда. У меня нет блоков с отрицательными значениями типа -35 или -2. Все индексы блоков заключены в интервале от 0 до 49.
А теперь приведем код подгрузки блоков ландшафта в новом варианте. Он пока еще не полон, осталось ввести список блоков района боевых действий для наземки, чтобы та не теряла опору под собой. Я не буду приводить "шапку" и окончание класса для экономии места

        listCoord = [-49,-47,-45,-43,-41,-39,-37,-35,-33,-31,-29,-27,-25,-23,-21,-19,-17,-15,-13,-11,-9,-7,-5,-3,-1,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49]
        listIndex = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]
       
        #Камера ТВ-прицела - вычисление нужного квадрата ее местонахождения
        targetTV = scene.objects["CameraWorld"]
        targetTVquadroX = int((int(targetTV.worldPosition[0]/1000)+ 490)/20)
        targetTVquadroY = int((int(targetTV.worldPosition[1]/1000)+ 490)/20)
       
        #Индексы "догружаемых" в сцену блоков - для этой камеры хватит и 9
        targetTVblockX = str(listIndex.index(targetTVquadroX))
        targetTVblockY = str(listIndex.index(targetTVquadroY))
        targetTVblockX0 = str(listIndex.index(targetTVquadroX+1))
        targetTVblockY0 = str(listIndex.index(targetTVquadroY+1))
        targetTVblockX1 = str(listIndex.index(targetTVquadroX-1))
        targetTVblockY1 = str(listIndex.index(targetTVquadroY-1))
       
        #Жестко зафиксированный список для 9 блоков - "догружаемых" к камере ТВ-прицела
        targetTVtempIndex = [('block_' + targetTVblockX + '_' + targetTVblockY + '_'),
                             ('block_' + targetTVblockX0 + '_' + targetTVblockY0 + '_'),
                             ('block_' + targetTVblockX1 + '_' + targetTVblockY1 + '_'),
                             ('block_' + targetTVblockX1 + '_' + targetTVblockY + '_'),
                             ('block_' + targetTVblockX + '_' + targetTVblockY1 + '_'),
                             ('block_' + targetTVblockX0 + '_' + targetTVblockY + '_'),
                             ('block_' + targetTVblockX + '_' + targetTVblockY0 + '_'),
                             ('block_' + targetTVblockX1 + '_' + targetTVblockY0 + '_'),
                             ('block_' + targetTVblockX0 + '_' + targetTVblockY1 + '_')]
       
        for block in targetTVtempIndex:
            if block not in scene.objects:
               
                blockTerrain = scene.addObject(block, self)
                blockTerrain.worldPosition[2] = 0.0
               
                blockX = blockTerrain.name.split('_')[1]
                blockY = blockTerrain.name.split('_')[2]
               
                blockTerrain.worldPosition[0] = int(blockX)*20000 - 490000
                blockTerrain.worldPosition[1] = int(blockY)*20000 - 490000
       
        #Вычисление нужного квадрата, центрального блока ландшафта под камерой, целочисленные значения
        #20 - размер блока в км, 490 - поправка сдвиг влево и вниз на "начало отсчета" от левого нижнего угла
        #1000 - чтобы было меньше возни, отбрасываем метры, нас интересуют целые числа в диапазоне от 0 до 49
        activeCam = scene.active_camera
        quadroX = int((int(activeCam.worldPosition[0]/1000)+ 490)/20)
        quadroY = int((int(activeCam.worldPosition[1]/1000)+ 490)/20)
       
        #Вычисление индексов "огружаемых" блоков к камере игрока на ЛА - 25 блоков
        blockX = str(listIndex.index(quadroX))
        blockY = str(listIndex.index(quadroY))
        blockX0 = str(listIndex.index(quadroX+1))
        blockY0 = str(listIndex.index(quadroY+1))
        blockX1 = str(listIndex.index(quadroX-1))
        blockY1 = str(listIndex.index(quadroY-1))
        blockX2 = str(listIndex.index(quadroX+2))
        blockY2 = str(listIndex.index(quadroY+2))
        blockX3 = str(listIndex.index(quadroX-2))
        blockY3 = str(listIndex.index(quadroY-2))
       
        #Жестко зафиксированный список для 25 блоков - "догружаемых" к камере игрока на ЛА
        tempIndex = [('block_' + blockX + '_' + blockY + '_'),
                     ('block_' + blockX0 + '_' + blockY0 + '_'),
                     ('block_' + blockX1 + '_' + blockY1 + '_'),
                     ('block_' + blockX1 + '_' + blockY + '_'),
                     ('block_' + blockX + '_' + blockY1 + '_'),
                     ('block_' + blockX0 + '_' + blockY + '_'),
                     ('block_' + blockX + '_' + blockY0 + '_'),
                     ('block_' + blockX1 + '_' + blockY0 + '_'),
                     ('block_' + blockX0 + '_' + blockY1 + '_'),
                     ('block_' + blockX2 + '_' + blockY + '_'),
                     ('block_' + blockX2 + '_' + blockY0 + '_'),
                     ('block_' + blockX2 + '_' + blockY1 + '_'),
                     ('block_' + blockX2 + '_' + blockY2 + '_'),
                     ('block_' + blockX2 + '_' + blockY3 + '_'),
                     ('block_' + blockX3 + '_' + blockY + '_'),
                     ('block_' + blockX3 + '_' + blockY0 + '_'),
                     ('block_' + blockX3 + '_' + blockY1 + '_'),
                     ('block_' + blockX3 + '_' + blockY2 + '_'),
                     ('block_' + blockX3 + '_' + blockY3 + '_'),
                     ('block_' + blockX0 + '_' + blockY2 + '_'),
                     ('block_' + blockX1 + '_' + blockY2 + '_'),
                     ('block_' + blockX + '_' + blockY2 + '_'),
                     ('block_' + blockX0 + '_' + blockY3 + '_'),
                     ('block_' + blockX1 + '_' + blockY3 + '_'),
                     ('block_' + blockX + '_' + blockY3 + '_')]
                    
        for block in tempIndex:
            if block not in scene.objects:
               
                blockTerrain = scene.addObject(block, self)
                blockTerrain.worldPosition[2] = 0.0
               
                blockX = blockTerrain.name.split('_')[1]
                blockY = blockTerrain.name.split('_')[2]
               
                blockTerrain.worldPosition[0] = int(blockX)*20000 - 490000
                blockTerrain.worldPosition[1] = int(blockY)*20000 - 490000
           
        #Проверка на наличие лишних блоков и их уборка  
        for terrain in scene.objects:
            if 'block_' in terrain.name:
                if terrain.name not in tempIndex:
                    if terrain.name not in targetTVtempIndex:
                        terrain.endObject()

Суть в том, что есть два жестко фиксированных списка - один для камеры игрока на ЛА и вообще активной камеры в игре. Он рассчитан на 25 блоков, чтобы не было "пустот" на горипзонте (скорости-то большие). Второй список  рассчитан на 9 блоков и нужен он для камеры ТВ прицела, передающей изображение на мониторы в кабине. Вроде бы он и лишний, но в то же время могут произойти такие события, когда он понадобится. Обратите внимание на длинные списки переменных для вычисления индексов сопутствующих блоков. Названия, наверное, не вполне удачные, а списки имен блоков я сделал "напрямую", без циклов и перебора значений в списке. Зато это работает и ничто не мешает в будущем это дело изменить. Как вычисляются координаты блоков по Х и Y, исходя из их имен, думаю, тоже понятно. Там просто вводится поправка по причине отсутствия отрицательных значений - так сложилось.
в конце идет проверка на наличие лишних блоков и их удаление. Перед тем, как задействовать этот алгоритм я пытался действовать через замену мешей террайна. Однако он кушал лишние 10 процентов логики, потому что объектов в сцене было ну очень много. При проверке работоспособности данного скрипта выяснилось, что кушает он меньше процента при частоте обновления раз в секунду (против 70 процентов при частоте обновления раз в три секунды). Зарекусь пока говорить о полной и безоговорочной победе, чтоб не сглазить - об этом можно будет уверенно сказать при работающих миссиях и кампаниях. Тем не менее, это явный шаг вперед, по сравнению с предыдущими вариантами. И да, ландшафт имеет 640 килополиков в сумме, вылетов БГЕ пока не отмечено, игровой процесс пока кушает  4 процента.
Картинок пока не будет - поскольку изменения были внутренние, а не внешние.
Теперь необходимо переходить к созданию меню с возможностью выбора оружия и редактирования миссий и кампаний. К тому же меню еще и будет завязано на выбор района БД, погоды и 2декораций", типа городов и авиабаз.
В заключение о декрациях. что-то мне не улыбается добавлять построчно каждый объект на тот же аэродром при его вызове. Гораздо проще сделать вызов группы объектов - ВПП и ее потомков - ангаров, башен и прочего. И произвести замену мешей. Опять будут задействованы имена объектов с "отсечением" лишнего типа _021. И вообще сильно мне полюбилось менять меши, да. Недавно тестил новые эффекты взрывов с помощью замены мешей-кадров, а не вызова плейнов-объектов, как раньше. выяснилось что две сотни одновременных взрывов кушают всего-то 5 процентов логики. Для движка это большое облегчение. Чуть позже напишу и об этом.






вторник, 1 марта 2016 г.

О ландшафте серьезно. Подведение итогов.

Сразу оговорюсь, что итоги очень даже могут оказаться промежуточными, поскольку пределов совершенства нет, да и для многих более опытных в этом деле игроделов этот пост не будет являться откровением. К тому же для создания террайна и его использования в игре применялся только Блендер (ну и ГИМП, если быть честным).
В настоящее время процесс создания террайна (ландшафта) выглядит так:

1. Берем плейн, обыкновенный Plane. Подразделяем его (W - Subdivide) на квадраты. Отмечу, что заранее надо примерно представить себе размеры террайна по Х и Y и сам террайн должен быть квадратным в этом плане.  Также заранее надо рассчитать, сколько блоков-квадратов необходимо получить по сторонам террайна - то есть знать величину блока террайна, сам блок должен быть тоже квадратным.
2. Получив требуемый уровень подразделения, ставим размеры заготовки будущего террайна в стандартные рамки. Изначально в Блендере добавляемые плейны имеют размер 2х2х0 метров и Scale 1х1х1. Поэтому полученная заготовка должна быть именно таких размеров - узнано на собственном горьком опыте, если имеются другие размеры - будут искажения при дальнейшей работе. Когда все размеры подогнаны, нажимаем U - Unwrap и проверяем раскладку вершин на УВ-развертке - она должна быть равномерной  - просто квадрат в "мелкую клеточку", не более.
3. Выделяем все вершины заготовки и жмем Ctrl-E - Edge Split.  Происходит разделение выделенных ребер заготовки, но она сама еще представляет из себя единое целое (это очень важно- в этом шаге закладывается основа для дальнейшего "дробления" террайна на блоки).
4. Далее опять выделяем все вершины заготовки и снова применяем W - subdivide. Здесь уже вам решать, насколько будет сложен и детализирован террайн. У меня комп выдерживал 640 тысяч полигонов, но для страховки после каждого шага я сохранял файл, потому что вылеты Блендера  редкостью уже не были.
5. Берем карту высот, заранее подготовленную в ГИМПЕ - какого формата и размера - неважно, главное, чтобы комп выдержал следующую операцию. В Блендере есть такой отличный инструмент, как Displace в модификаторах, который позволяет создать модель на основе карты высот - очень быстро и вполне качественно. Настройки модификатора - на ваше усмотрение - в зависимости от требуемого результата. После нажатия Аpply получаем мини-террайн размерами 2 на 2 метра и очень сильно детализированный. Сохраняем файл.
6. Над террайном подвешиваем камеру (повыше, этак км на 500-700 (да именно так высоко, не забудьте ввести поправки в дальность видения для камеры). Переключаемся в вид из этой камеры. Увеличиваем ландашафт до требуемых размеров по всем осям. У меня размеры террайна - 1000 на 1000 км, поэтому и высота камеры такая, если у вас размеры меньше, камеру можно высоко не поднимать, но ваш ландшафт должен быть видимым целиком!). Далее добавляем материалы и текстуры к ним к нашему террайну.
7. Постоянно после каждого шага сохраняем результат во избежание вылета Блендере и потери результатов. Далее применяем к ландшафту нодовый материал - он позволяет дешево и сердито за котороткое время получить вполне приемлемый результат. Как это делается - смотрим здесь:
http://b3d.org.ua/forum/viewtopic.php?f=30&t=571 Мой коллега denis8424  расстарался. Ноды вообще вещь хорошая, жаль что нет времени для их подробного изучения... Тем более, что в последней версии БГЕ можно некоторые параметры для нодов задавать скриптами.
8. После получения "раскрашенного" террайна начинается самое нудное. Приготовьтесь подождать. Перейдем в режим редактирования меша, выдели все вершины и жмем P - loose party. Оставляем комп в покое - и ничего нетрогаем. Желательно в это время вообще к компу не приставать - у него и без вас заботы хватит. Ландшафт режется на отдельные блоки вдоль уже порезанных нами ребер (см. выще щаг 3). Когда операция завершается, перед вами множество объектов-блоков. Да, забыл написать, что перед разрезанием на блоки назовите свой ландшафт как-то вроде Terrain.000. Получившиеся блоки будут иметь схожие названия, отличающиеся только цифрами.
9. Теперь нам крайне необходимо привести в соотетствие имена мешей и объектов - они должны совпадать и дать каждому блоку свой центр, отличный от нуля - так будет меньше нагрузка на БГЕ. Для этого я применял скрипт

 import bpy

scene = bpy.context.scene

for ob in scene.objects:
    if ob.layers[scene.active_layer] == True and ob.name != 'Camera':
        if 'BigDesert' in ob.name:
               
            ob.name = 'BigDesert'
            ob.data.name = ob.name

В данном скрипте просто производится поиск по части имени блока (у вас может быть какое угодно, вместо BigDesert, но при создании серии ландшафтов лучше использовать что-то одинаковое - не будете же вы грузить сразу пару-тройку разных террайнов).
Для выправления геометрии, в смысле создания новых центров объектов применяется второй скрипт:
 import bpy

scene = bpy.context.scene
for obj in scene.objects:
    if 'Terrain' in obj.name: # здесь может быть другое имя
        obj.select = True
        obj.data.name = obj.name
        bpy.ops.object.origin_set(type = 'ORIGIN_GEOMETRY', center = 'MEDIAN')
        scene.cursor_location = obj.location
        scene.cursor_location[2] = 0.0
        bpy.ops.object.origin_set(type = 'ORIGIN_CURSOR')
        obj.location[2] = 0.0
        obj.select = False
Оба скрипта запускаются с помощью RunScript в текстовом редакторе Блендера.
Можно считать, что ландшафт создан. можно встраивать его в игру.

Процесс встраивания ландшафта в игру к настоящему времени таков:
1. Создаем текстовый файл в той же папке, где лежит ландшафт. Имя выбирайте сами, но лучше сразу принять для себя какие-то стандарты - потом будет легче. В этот файл мы записываем данные о каждом блоке ландшафта - его координаты, и местоположение среди других блоков. О принципе квадратов типа 25-27 я писал в прошлом посте - поймете. Скрипт записи данных в текстовый файл - бге-шный, но одноразовый, в самой игре он не используется. Рассматривайте его, как инструмент, наподобие первых двух скриптов выше.

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

listCoord = [-49,-47,-45,-43,-41,-39,-37,-35,-33,-31,-29,-27,-25,-23,-21,-19,-17,-15,-13,-11,-9,-7,-5,-3,-1,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49]

listIndex = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]

for obj in scene.objects:
    if 'Terrain' in obj.name:
       
        X, Y, Z = obj.worldPosition
       
        try:
            for coordX in listCoord:
                if  (coordX-1)*10000 < X < (coordX+1)*10000:
                    blockX = listCoord.index(coordX)
               
            for coordY in listCoord:
                if  (coordY-1)*10000 < Y < (coordY+1)*10000:
                    blockY = listCoord.index(coordY) 
       
       
            coordText = open(bge.logic.expandPath('//BlockTerrain_2.txt'),'a')
            coordText.write('b|' + str(blockX) + '|' + str(blockY) + '|' + obj.name + '|'+ str(X)+'|'+str(Y) +'|'+ str(Z)+'|'+'\n')
            coordText.close()
        except:
            pass

После отработки скрипта заглядываем в наш текстовый файл с данными. Там будет запись типа:

b|30|29|Terrain.2499|110000.0|90000.0|0.0|
b|45|29|Terrain.2498|410000.4375|90000.0|0.0|
b|46|29|Terrain.2497|430000.4375|90000.0|0.0|
b|45|13|Terrain.2496|410000.4375|-230000.515625|0.0|
b|46|13|Terrain.2495|430000.4375|-230000.515625|0.0|
b|13|29|Terrain.2494|-230000.546875|90000.0|0.0|

Довольно исчерпывающие данные, квадрат такой-то, имя блока террайна, его координаты. Можно писать класс для генерации блоков террайна, причем нужных в данный момент.
2. Написание класса много времени у опытных людей не займет. я вроде бы уж и научился писать, но ляпы все же делаю и с первого захода у меня не получилось. После правки у меня получилось следующее:

# -*- coding: utf8 -*-

import bge
scene = bge.logic.getCurrentScene()  
cont = bge.logic.getCurrentController()
own = cont.owner
#Загрузка выбранного ландшафта - временно закомменченная
terrain = bge.logic.globalDict['terrain']
         
class terrain(bge.types.KX_GameObject):   
    def __init__(self, old_owner):   
      
        #Список необходимых в данный момент блоков
        self.listBlock = []
   
        #Вычисление нужного квадрата, центрального блока ландшафта под камерой, целочисленные значения
        #20 - размер блока в км, 490 - поправка сдвиг влево и вниз на "начало отсчета" от левого нижнего угла
        #1000 - чтобы было меньше возни, отбрасываем метры, нас интересуют целые числа в диапазоне от 0 до 49
        activeCam = scene.active_camera
        quadroX = int((int(activeCam.worldPosition[0]/1000)+ 490)/20)
        quadroY = int((int(activeCam.worldPosition[1]/1000)+ 490)/20)
       
        #Отыскиваем и открываем нужный нам текстовый файл с названиями, координатами и значениями квадратов для блоков ландшафта
        #print('//Terrain/' + terrain + '_/Block' + terrain + '.txt')
        #brikeLand = open(bge.logic.expandPath('//Terrain/' + terrain + '_/Block' + terrain + '.txt'),'r')
        brikeLand = open(bge.logic.expandPath('//Terrain/Terrain_2_/BlockTerrain_2.txt'),'r')
   
        #Дальше по маркерам смотрим, что нам нужно
        for string in brikeLand:
            if string[0] == '#':
                continue
            elif string[0] == 'b':
                tempList = string.split('|')
                X = int(tempList[1])
                Y = int(tempList[2])
           
                #Вычисляем индексы квадратов, нужных для вызова
                #Если индексы квадрата по ХУ находятся внутри требуемого диапазона - добавляем название блока ландшафта в список
                #нужных для появления блоков
                if quadroY-2 < Y < quadroY+2 and quadroX-2 < X < quadroX+2:
                    self.listBlock.append(tempList[3])
                   
                    #Обратная проверка - если объекты есть в списке, но их нет в сцене - добавляем (не хватало еще дублей наплодить)
                    for blockLand in self.listBlock:
                        if blockLand not in scene.objects:
                            blockLand = scene.addObject(tempList[3], self)
                            blockLand.worldPosition = [float(tempList[4]), float(tempList[5]), float(tempList[6])]
                           
            #Проверка на наличие лишних блоков ландшафта в сцене
            for landBlock in scene.objects:
                if "Terrain" in landBlock.name:
                    if landBlock.name not in self.listBlock:
                        #Если есть таковые - ликвидируем
                        landBlock.endObject()
                       
        #Закрываем текст с данными террайна           
        brikeLand.close()
       
def mutate(cont):
    old_object = cont.owner
    mutated_object = air(cont.owner)
   
    assert(old_object is not mutated_object)
    assert(old_object.invalid)
    assert(mutated_object is cont.owner)

# Called later - note we are now working with the mutated object.
def update(cont):
    cont.owner.update()

Комментарии на английском - это просто невырезанные строчки из исходных примеров, приведенных в АПИ Блендера. Можете смело удалять. Думаю русские комментарии вполне понятны. И зачем нам понадобился здоровенный список с именами блоков и их данными теперь тоже понятно.
3. Само использование класса в игре. Сами по себе классы  экономят ресурсы и упрощают жизнь (скрипт класса блоков террайна я разместил в пусковом файле игры - это стандартная вещь для всех террайнов). Далее у меня получился террайн из 2500 блоков, размером 1000 на 1000 км, по 50 блоков по Х и Y, в сумме террайн имеет 640 тысяч поликов, каждый его блок имеет размер 20 на 20 км и 256 поликов. В игре надо вызывать 9 или максимум 25 блоков  -образуя видимую камерой часть ландшафта. Для подгрузки всего террайна использовался инструмент LibLoad, о его использовании информации довольно много, так что разберетесь. Сам процесс вызова класса происходит лишь при смене камеры или в строго определенные моменты времени (тут надо рассчитывать в зависимости от скорости перемешения активной камеры). Пока что скрипт работы генерации блоков террайна написан лишь для клавиш ф1-12 (смена камеры),  но общий принцип, думаю, вы поймете. Не забудьте заодно - блоки ландшафта в подгружаемом файле должны быть на неактивном слое!

# -*- coding: utf8 -*-
import bge

def keyButton():
    scene = bge.logic.getCurrentScene()
    cont = bge.logic.getCurrentController()
    own = cont.owner
    sens = cont.sensors['keyButtonCam']
   
    if sens.positive:
       
        for key,status in sens.events:
            #Клавиши переключения камер
            if status == bge.logic.KX_INPUT_JUST_ACTIVATED:
                #Импорт класса террайн - для замены или подгрузки блоков террайна
                if key == bge.events.F1KEY or key == bge.events.F2KEY or key == bge.events.F3KEY or key == bge.events.F4KEY or key == bge.events.F5KEY or key == bge.events.F6KEY or key == bge.events.F7KEY or key == bge.events.F8KEY or key == bge.events.F9KEY or key == bge.events.F10KEY or key == bge.events.F11KEY or key == bge.events.F12KEY:
                    import ClassTerrain
                    blockTerrain = ClassTerrain.terrain(own)

Все. Ландшафт создан, затекстурен, порезан на кусочки, проименован, "взвешен и учтен". Он подключен к игре, используется только нужная в данный момент его часть. Есть еще нюансы насчет использования блоков, на которых ведет бой наземная техника, они должны также присутствлвать изначально, чтобы техника "не проваливалась", но эта задача вполне решаема. Основная часть работы проделана и описана здесь. Заодно может пригодиться не только всем желающим, но и мне, "на всякий пожарный". Сделал, что мог, пусть другие сделают лучше (с). Выражаю благодарность denis8424 за терпение и помощь в написании скриптов-инструментов и вылавливании моих косяков.

воскресенье, 28 февраля 2016 г.

"Случай в квадрате 36-80" или "Операция Святой Януарий".

Когда-то достаточно давно я видел оба фильма. И их названия очень даже соответствовали моим извращениям по поиску алгоритма генерации блоков ландшафта. Как я уже говорил ранее, совершенно необязательно грузить весь огромный террайн, если в игре ты видишь максимум десятую часть егог (в лучшем случае). Как я писал в предыдущем посте, террайн разбивается на множество квадратных блоков, из которых на активный слой грузятся те, которые в данный момент должны присутствовать под камерой. И вот тут началось самое интересное...
Для начала о "Случай в квадрате 36-80". Фильм был снят в начале 80-х, небезызвестным режиссером Михаилом Туманишвили, который также был автором фильмов  "В зоне особого внимания" и "Ответный ход". Также в свое время он "перековался" в соответствии с 2демократическими веяниями" и снял фильм "Сто дней до приказа". За что был нещадно бит в прессе и на ковре у тогда еще советского руководства Минкультуры. Затем он перековался еще раз и снял относительно недавно еще один патриотический боевик "Ноль седьмой меняет курс". Пересказывать содержание фильмов я не буду, тем более, что "Сто дней" не смотрел - если есть желание - сами найдете. Могу только сказать, что хотя все вышеперечисленные фильмы (за исключением "Ста дней" это агитка наоборот) являются агитками, как сейчас модно "заклеймит" все, не соответствующее либеральному взгляду (анекдот середины 90-х: "В России существуют два взгляда в экономике и политике - один - либеральный, второй - неправильный"), смотреть эти фильмы можно. Если выкинуть на время просмотра из головы некоторые детали. Например, что "новейшая система заправки крыло в крыло" к началу 80-х уже была признана устаревшей, или что для ремонта реактора совершенно необязательна кувалда (хотя кто его знает, может, даже сейчас у нас найдутся умельцы, способные починить Большой Адронный Коллайдер этим инструментом), а также то, что Ту-16, как и заправщик на его базе, с отключенными по причине израсходования топлива, планирует чуть лучше брошенного кирпича, ну и для запуска крылатых ракет с подводной лодки нужно проделать много нудных манипуляций, да и выдающиеся боевые и летные качества СВВП Як-38 вызывают сомнения... Но все же уровень "Случая в квадрате 36-80" будет повыше, чем у многих американских агиток (например лично видел в каком-то боевике эпизод с советским офицером-подводником, носящим почему-то фуражку явно не черного цвета, да еще и с красным околышем (!!!) или играющих в карты советских же подводников в фильме "К-19"). Впрочем, киноляпов можно вспомнить много, но в данный момент интересен мой BGE-ляп.
Итак, мне нужно было сделать запись в текстовый файл типа:

b|30|29|Terrain.2499|110000.0|90000.0|0.0|
b|45|29|Terrain.2498|410000.4375|90000.0|0.0|
b|46|29|Terrain.2497|430000.4375|90000.0|0.0|
b|45|13|Terrain.2496|410000.4375|-230000.515625|0.0|
b|46|13|Terrain.2495|430000.4375|-230000.515625|0.0|

Первые две цифры - индексы квадрата по Х и У. Индексы 0-0 соответствуют самому дальнему левому нижнему блоку ландшафта, а 49-49 - самому дальнему правому верхнему блоку ландшафта. Соответственно координаты по Х и у у этих блоков будут минимальными и максимальными. Далее я задумывал следующую операцию - уже во время игры следовало определение квадрата, в котором находится активная камера. определялся он по формуле:

quadroX = int((int(parentCam.worldPosition[0]/1000)+ 490)/20)
quadroY = int((int(parentCam.worldPosition[1]/1000)+ 490)/20)

В данном случае - 490 - поправка с учетом отрицательных координат части блоков (можно было бы и оставить минус перед блоками, но непривычно так именовать квадраты, да и сложилось как-то по ходу дела). Далее следует открытие списка и поиск в нем нужных индексов в диапазоне от минус до плюс (справ и слева, сверху и снизу от квадрата, над которым висит камера). и их генерация. в коде это выглядит так:

#Отыскиваем и открываем нужный нам текстовый файл с названиями, координатами и значениями квадратов для блоков ландшафта
    brikeLand = open(bge.logic.expandPath('//Terrain/Terrain_2_/BlockTerrain_2.txt'),'r')
   
    #Дальше по маркерам смотрим, что нам нужно
    for string in brikeLand:
        if string[0] == '#':
            continue
        elif string[0] == 'b':
            tempList = string.split('|')
            X = int(tempList[1])
            Y = int(tempList[2])
            #print(X, Y)
            #Вычисляем индексы квадратов, нужных для вызова
            #Если индексы квадрата по ХУ находятся внутри требуемого диапазона - добавляем название блока ландшафта в список
            #нужных для появления блоков
            if quadroY-2 < Y < quadroY+2 and quadroX-2 < X < quadroX+2:
                    listBlock.append(tempList[3])
                    blockLand = scene.addObject(tempList[3], own)
                    blockLand.worldPosition = [float(tempList[4]), float(tempList[5]), float(tempList[6])]
                   
                    
    brikeLand.close()

Как выяснилось позже, алгоритм был верен. НО! Когда я в файле с террайном создавал этот самый текстовый список с индексами, названиями и координатами, я тупо отсортировал все блоки террейна по возрастанию координат Х и у и записал индексы элементов этиъ списков в файл. ОШИБКА! Во-первых, имеются 50 блоков с одной и той же координатой, во-вторых,  индексы блоков должны быть в диапазоне от 0 до 49. у меня же максимальный индекс был 2499! И в соем террайне НИКАК не могло быть квадрата 36-80 (теперь, думаю понятен смысл первой части названия этого поста).

Потратив три дня на составление скриптов, разбивку ландшафта на блоки и попытку осознать, что я сделал не так, решение с индексами квадратов было найдено. На мой взгляд несколько коряво, но работающее. и, как ни странно, работающее довольно быстро. Если взглянуть на мой террайн, то координаты каждого его отдельного блока находятся в определнном диапазоне, начиная от -49000 до 49000. Как по Х, так и по У. Шаг составляет 20000. Поскольку в процессе присвоения новых центров нарезанным блокам были отклонения, типа, -37020. 08739, то пришлось опять немного изощриться...
 Я создал список такого вида [-49, -47, -45...45, 47, 49] Из 50 чисел от -49 до 49 , с шагом в 2. Это были примерные координаты центров блоков. И ввел для них поправки плюс-минус 1. Я специально опустил четыре нуля позади чисел в списке, чтобы выглядело попроше. Поправки с нулями были уже в коде:

for obj in scene.objects:
    if 'Terrain' in obj.name:
       
        X, Y, Z = obj.worldPosition
       
        try:
            for coordX in listCoord:
                if  (coordX-1)*10000 < X < (coordX+1)*10000:
                    blockX = listCoord.index(coordX)
               
            for coordY in listCoord:
                if  (coordY-1)*10000 < Y < (coordY+1)*10000:
                    blockY = listCoord.index(coordY) 
       
       
            coordText = open(bge.logic.expandPath('//BlockTerrain_2.txt'),'a')
            coordText.write('b|' + str(blockX) + '|' + str(blockY) + '|' + obj.name + '|'+ str(X)+'|'+str(Y) +'|'+ str(Z)+'|'+'\n')
            coordText.close()
        except:
            pass

Часть кода симпортом модулей и стандартных функций со списками я опустил - и так разберетесь. Поясню лишь, что, скажем нижний левый блок имеет координаты в пределах от -48000 до -50000 (то бишь -49-1 и -49+1, если отбросить нули. Точные его координаты и так прописаны в текстовом файле, нам же нужно знать, индексы блока для их записи в текстовый файл. Так вот у блока в нижнем левом углу обе кординаты соответствуют -49 и имеют нулевой индекс в списке [-49...49]. аналогично - для всех 2500 блоков. Отмечу, что скрипт, задействованный в файле террайна в самой игре не используется, мне он был нужен лишь для составления текстового файла, который-то как раз и используется в самой игре. Скрипт получился очень простым и довольно быстрым, даже жаль, что в игре он не нужен. А перед этим была перепробована уйма способов, и все они были отброшены. по сложности и скорости исполнения они, мягко говоря, были попросту не нужны.
Это называется - "ломиться в открытую дверь". И заодно напомнило мне эпизод из итальянской комедии "Операция Святой Януарий". Когда банда американских гангстеров вместе с их итальянскими коллегами пыталась вскрыть витрину с драгоценностями в соборе. Применялось все - хитроумные отмычки, сверхпрочные сверла, зубила. Закончилось все тем, что главный герой фильма с досады швырнул зажигалку в бронестекло... и оно рассыпалось. Теперь ясно и со второй частью названия этого поста.
А теперь немного полюбуемся на результаты. Первый скрин - сгенеренные 9 блоков ландшафта. Камера висит на приличной высоте, и видно, что сгенерились именно те 9 блоков, которые были нужны. Если бы камера находилась на высоте хотя бы 20 км, блоки заняли бы все поле зрения, даже с некоторым запасом. Кстати, как потом до меня дошло, в процессе опробования скрипта с неверными индексами списка, генерились бы блоки по левым и нижнему краю, наподобие буквы L. Очень смешно...

Второй и третий скрины - это небольшой оффтоп - очень давно я скачал свободно распространяемые модели с cайта http://www.sharecg.com/  и посмотрел, как они выглядят в Блендере. вряд ли они будут задействованы в самой игре, по крайней мере в том виде, в котором они есть сейчас, но мысль, что неплохо бы ввести старые машины, полагающиеся не на умные ракеты, а на старые добрые пушки и пулеметы, у меня мелькала не раз.