вторник, 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).


понедельник, 11 мая 2020 г.

Новое меню и несколько туманные перспективы...

После очень долгого перерыва решил все же написать очередной пост. Поскольку за это время произошло довольно мн6ого событий в мире 3D, а многие так и довольно давно, но мой консерватизм вкупе с известными проблемами не давал возможности заняться новым вплотную...
Итак, БГЕ - все. Обновлять его не будут, из версии 2.8 он выпилен, это и так многие знают. Остается, правда, UPBGE, да время от времени всплывают слухи, что в Блендер все же вставят что-то вроде игрового движка, но что это будет за новый "БГЕ" и будет ли он вообще - неясно.
Версия 2.8 уверенно развивается, 2.83 уже почему-то обозвали 2.9 - во всяком случае, при распаковке 2.9 в названии присутствует.
Следовательно, рано или поздно, но пришлось бы осваивать 2.8, с его новым интерфейсом, и новыми фишками, в которых автор этих строк пока не слишком (он и в 2.7 не слишком-то - имеется в виду рендер и использование всех возможностей программы).
За это время я принялся изучать Cycles и делать по урокам материалы (уроки Striver), а также читать, смотреть видеоуроки (которые перед этим скачивал ночью, по причине огромного трафика), и вообще пытаться больше понять 3D, его терминологию и возможности. И не только Блендер, но и Юнити и Анрил Энжин (с последним все же сильно не очень, потому как в Юнити куда как больше справочных материалов).
Но бросать свой бге-ешный проект не хотелось и работа продолжилась. Итогом стал очередной погром, который, похоже, выльется в переход на UPBGE  с новым, еще большим погромом. Все дело в том, что два с половиной года, отнятые у меня катарактой и сопутствующими ей "прелестями" привели к тому, что многое из того, что делалось, забылось, и просто-напросто устарело. Фактически проект будет делаться заново, правда в нем будут использоваться огромные куски кода, которые не забыты и могут быть использованы. Помимо этого необходимо освоить PBR-материалы, рендер EEVEE, заодно до кучи продолжить изучение Юнити и Cycles, а также заняться еще кое-чем, что непосредственно к 3D отношения не имеет и здесь поэтому перечисляться не будет. Как говорил Ильич: "Учиться, учиться и еще раз учиться" (Я помню этот лозунг, сложенный из вырезанных из пенопласта букв, прикленных к стене нашего школьного класса под потолком рядом с портретом вождя мирового пролетариата).
Некоторое время назад я занялся меню игры, которое было собрано наспех и кое-как. Сделать его удалось, хотя и было в итоге завалено дело с загрузкой объектов, но меню тут ни при чем, все дело было в загрузочном скрипте игровой сцены, которое было чрезмерно раздуто и усложнено. Само же меню прекрасно работает (хотя и не доделано по причине провала с загрузкой, но, судя по распечаткам консоли, все что нужно, оно делало, а редактор миссий и кампаний делать пока бессмысленно).
Меню позволяет переключаться между разделами, выбирать одиночную миссию, переходить на игровую сцену (где все и рвется), вызывает справку, меняет язык интерфейса. В общем, все работает, кроме редактора миссий и кампаний.
Ниже приводится текст скрипта меню, для которого используется логика лишь на одном объекте стартовой сцены-меню, причем логических кирпичей используется только 4. В ранней версии их было втрое больше, плюс имелась логика на других объектах. Само меню перед стартом подгружает бленд с нужными объектами, изначально в сцене только камера с логикой и плейн со стартовым черным фоном.
Итак, скрипт:

import bge
import sys
import os
import json
scene = bge.logic.getCurrentScene()

#Обьявление объекта сцены курсора
CursorMenu = scene.objects["CursorMenu"]

#Загрузка элементов меню из папки с компонентами
bge.logic.LibLoad("//Menu/Blend_Folder/Menu_Component.blend", 'Scene', load_actions = True)
   
pathJSON = ""
pathFon = ""
bge.logic.globalDict["bglText"] = ""
bge.logic.globalDict["DayNight"] = 1.3
bge.logic.globalDict["textGame"] = "Rus"

#Далее идет блок клавиатурных команд
keyboard = bge.logic.keyboard
JUST_ACTIVATED = bge.logic.KX_INPUT_JUST_ACTIVATED
JUST_RELEASED = bge.logic.KX_INPUT_JUST_RELEASED
INPUT_ACTIVE = bge.logic.KX_INPUT_ACTIVE

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

#Сенсоры для управления меню
mouseMotion = cont.sensors["MouseMotion"]
mouseClick = cont.sensors["MouseClick"]

#Звуковой актуатор щелчка по кнопке
audioTemp = cont.actuators['SoundClick']

#Стартовые значения для меню - миссия по умолчанию
bge.logic.globalDict['currentMission'] = "MiG-23MF_vs_F-5E"
bge.logic.globalDict["dictUnit"] = []
bge.logic.globalDict["listUnit"] = []
bge.logic.globalDict["listEnemy"] = [ [[],[]], [[],[]] ]

scaleButtonStandart = {
                       "ButtonShort":[5.775, 1.5, 0.0],
                       "ButtonLong":[8.49, 1.5, 0.0],
                       "ButtonExit":[1.5, 1.5, 0.0],
                       "ButtonUp":[1.5, 1.5, 0.0],
                       "ButtonDown":[1.5, 1.5, 0.0],
                       #"ButtonUniversal":[1.5, 1.5, 0.0],
                       #"ButtonBig":[5.775, 4.12, 0.0],
                       "ButtonForward":[1.5, 1.5, 0.0],
                       "ButtonBack":[1.5, 1.5, 0.0]
                       }
 
#Создание проперти объекта для положения курсора
if "cursorPos" not in own:
    own["cursorPos"] = {"addSceneGame":0,
                        "tempX":0.0,
                        "tempY":0.0,
                        "language":"",
                        "listButton":[],
                        "animationButton":0,
                        "indexButton":"",
                        "oldPathFon":"",
                        "newPathFon":"",
                        "TimerColor":0,
                        "AlphaColor":0.0,
                        "newPatchMenu":"//Menu/JSON_Folder/Menu_Start.json",
                        "oldPatchMenu":"",
                        "PathFon":"",
                        "PathFonMonitor":"",
                        "limitXscroll":0,
                        "limitYscroll":0,
                        "valueButtonCoord":[0.0, 0.0, 0.0],
                        "pathButtonTXT":{},
                        "pathNameMission":{}
                        }

#Функция создания файла json с путями к файлам всего проекта
def startPath():
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner
 
    #Создаем переменные - списки путей блендов и json плюс пустую строку для путей
    k = ""
    endStrPy = ""
    pathGeneral = []
    pathBlend = []
    pathJSON = []
    pathPy = []
 
    #Циклом перебираем файлы, директории и папки, создаем и сшиваем строки в пути
    for d, dirs, files in os.walk(bge.logic.expandPath("//")):
        for f in files:
         
            k = os.path.join(d,f)
         
            if ".blend1" in k:
                os.remove(k)
         
            #Питон-файлы добавляем в sys.path, остальные в список путей
            if ".py" not in k:
                k = k.split("BlendSim_2.1")[1]
                k = k.replace("\\", "/")
                if ".blend" in k:
                    if ".blend1" not in k:
                        pathGeneral.append("/"+k)
                if ".json" in k:
                    pathGeneral.append("/"+k)
         
            elif ".py" in k:
                if "__pycache__" not in k:
                    endStrPy = k.split("\\")[-1]
                    k = k.replace(endStrPy, "")
                    sys.path.append(k)
                    pathPy.append(k)
                 
    #Открываем файл json  с путями (если его нет, то он создается) и сбрасываем туда список путей
    with open(bge.logic.expandPath('//NodePath.json'), 'w') as NODEPATH:
        json.dump(pathGeneral, NODEPATH)

#Функция работы курсора меню и всех его компонентов - УЗЛОВАЯ ФУНКЦИЯ для  GeneratorMenuObj, buttonJob и on_click
def cursorPos():
    cont = bge.logic.getCurrentController()
    own = cont.owner
 
    #Сработка псевдоанимации при старте игры
    if mouseMotion.positive:
        if own["cursorPos"]["addSceneGame"] == 0:
            #Работет таймер
            if own["cursorPos"]["TimerColor"] < 600:
                own["cursorPos"]["TimerColor"] += 1
         
                #Сначала меняем меш курсора на логотип и медленно увеличиваем его "видимость"
                if own["cursorPos"]["TimerColor"] == 1:
                    CursorMenu.replaceMesh("Logo", True, False)
                    CursorMenu.color = [1.0, 1.0, 1.0, 0.0]
         
                if 1 < own["cursorPos"]["TimerColor"] < 200:
                    if own["cursorPos"]["AlphaColor"] < 1.0:
                        own["cursorPos"]["AlphaColor"] += 0.005
                 
                #Затем логотип медленно истаивает - увеличивается прозрачность
                if 200 < own["cursorPos"]["TimerColor"] < 400:
                    if own["cursorPos"]["AlphaColor"] > 0.0:
                        own["cursorPos"]["AlphaColor"] -= 0.005
         
                #Теперь меняем меш логотипа обратно на меш курсора
                if own["cursorPos"]["TimerColor"] == 401:
                    CursorMenu.replaceMesh("CursorMenu", True, False)
             
                #И заставляем курсор постепенно "проступать" на фоне меню
                if 400 < own["cursorPos"]["TimerColor"] < 600:
                    if own["cursorPos"]["AlphaColor"] < 1.0:
                        own["cursorPos"]["AlphaColor"] += 0.005
         
                #За все время работы таймера управление прозрачностью курсора идет через словарь свойств
                CursorMenu.color = [1.0, 1.0, 1.0, own["cursorPos"]["AlphaColor"]]
                #И местоположение курсора задется жестко - в центре меню
                CursorMenu.worldPosition = [0.0, 0.0, 0.5]
 
        #И только когда таймер прекратил работу - начинает работать перемещение и клики курсором
        if own["cursorPos"]["TimerColor"] == 600:
            if mouseMotion.positive:
                if own["cursorPos"]["tempX"] != mouseMotion.position[0]:
                    CursorMenu.worldPosition[0] += (mouseMotion.position[0] - own["cursorPos"]["tempX"])/4
                    own["cursorPos"]["tempX"] = mouseMotion.position[0]
                if own["cursorPos"]["tempY"] != mouseMotion.position[1]:
                    CursorMenu.worldPosition[1] -= (mouseMotion.position[1] - own["cursorPos"]["tempY"])/4
                    own["cursorPos"]["tempY"] = mouseMotion.position[1]

                if CursorMenu.worldPosition[0] > 43.0:
                    CursorMenu.worldPosition[0] = 43.0
                if CursorMenu.worldPosition[0] < -45.0:
                    CursorMenu.worldPosition[0] = -45.0
         
                if CursorMenu.worldPosition[1] > 26.0:
                    CursorMenu.worldPosition[1] = 26.0
                if CursorMenu.worldPosition[1] < -24.0:
                    CursorMenu.worldPosition[1] = -24.0
         
            if mouseClick.positive:
                on_click(own)
                if own["cursorPos"]["oldPathFon"] != own["cursorPos"]["newPathFon"]:
                    FonChanging(own)
     
            if own["cursorPos"]["animationButton"] > 0:
                buttonJob(own)
 
        #Эта часть кода отвечает за смену языка - при нажатии кнопок с флажками производится выбор и замена текста     
        if own["cursorPos"]["language"] != bge.logic.globalDict["textGame"]:
            for buttonObj in own["cursorPos"]["listButton"]:
                #Выбираются только кнопки с текстом
                if "IDtext" in buttonObj:
                    if bge.logic.globalDict["textGame"] == "Eng":
                        scene.objects.from_id(int(buttonObj["IDtext"]))["Text"] = buttonObj["textObj"]
                    elif bge.logic.globalDict["textGame"] == "Rus":
                        scene.objects.from_id(int(buttonObj["IDtext"]))["Text"] = buttonObj["textRus"]
            #После замены текста исправляется значение в словаре, чтобы не проверять понапрасну настройки языка по многу раз
            own["cursorPos"]["language"] = bge.logic.globalDict["textGame"]
 
        #Переустановка кнопок и прочего при смене пути к файлу json для данного раздела меню 
        if own["cursorPos"]["newPatchMenu"] != own["cursorPos"]["oldPatchMenu"]:
            GeneratorMenuObj(own)
 
    #СТАРТ ИГРЫ! - переход на игровую сцену 
    if own["cursorPos"]["addSceneGame"] != 0:
        own["cursorPos"]["addSceneGame"] += 1
        if own["cursorPos"]["addSceneGame"] == 20:
            cont.activate(cont.actuators['Scene'])
            own["cursorPos"]["addSceneGame"] = 0
         
#Функция расстановки кнопок и текста, фона раздела меню и так далее - УЗЛОВАЯ ФУНКЦИЯ для FonChanging
def GeneratorMenuObj(own):
    FonObj = None
    Monitor = None
    #Местотоположение курсора задется жестко - в центре меню
    CursorMenu.worldPosition = [0.0, 0.0, 0.5]
 
    #Прежде чем добавлять новые кнопки, удалим старые
    for oldButton in own["cursorPos"]["listButton"]:
        oldButton.endObject()
    own["cursorPos"]["listButton"].clear()

    for textObj in scene.objects:
        if textObj.name == "TextMenu":
            textObj.endObject()
 
    #После очистки экрана от старыхобъектов начинаем создавать новый вид меню, открыв нужный json
    with open(bge.logic.expandPath(own["cursorPos"]["newPatchMenu"]), 'r', encoding = 'utf-8') as directMenu:
        JSONmenu = json.load(directMenu)
 
    if own["cursorPos"]["TimerColor"] == 600:
        for obj in JSONmenu["Buttons"]: 
            newSceneObject = scene.addObject(obj.split('|')[0],own)
            newSceneObject.worldPosition = JSONmenu["Buttons"][obj]["coordObj"]
            newSceneObject.color = JSONmenu["Buttons"][obj]["colorObj"]
            own["cursorPos"]["listButton"].append(newSceneObject)
            if "meshButton" in JSONmenu["Buttons"][obj]:
                newSceneObject.replaceMesh(JSONmenu["Buttons"][obj]["meshButton"],True,False)
            if "scaleObj" in JSONmenu["Buttons"][obj]:
                newSceneObject.worldScale = JSONmenu["Buttons"][obj]["scaleObj"]
            if newSceneObject.name in scaleButtonStandart:
                newSceneObject.worldScale = scaleButtonStandart[newSceneObject.name]
         
            #Иногда нужен текст без кнопки, который еще надо прокручивать 
            if newSceneObject.name == "TextMenu":
                #Печатаем текст из txt файла, находя его по нужному пути
                if "TextPath" in JSONmenu["Buttons"][obj]:
                    newSceneObject["Text"] = ""
                 
            #Задаем кнопке проперти (для прокрутки или других изменений объект помечается тегом "objTag"
            #Кнопка, которая должна управлять изиенениями остальных объектов получает ключ "tagObj", что
            #позволяет ей "опознать" объекты, необходимые для воздействия на них
            if "propObj" in JSONmenu["Buttons"][obj]:
                for key in JSONmenu["Buttons"][obj]["propObj"]:
                    if key not in newSceneObject:
                        newSceneObject[key] = JSONmenu["Buttons"][obj]["propObj"][key]
                             
            if "childObj" in JSONmenu["Buttons"][obj]:
                #Если надо добавляем текст, парентим его к кнопке, выставляем его размер
                newObj = scene.addObject(JSONmenu["Buttons"][obj]["childObj"], newSceneObject)
                newObj.worldPosition[2] = newSceneObject.worldPosition[2] + 0.02
                newObj.worldScale = newSceneObject.worldScale
                newObj.setParent(newSceneObject, False, False)
                if "childObjScale" in JSONmenu["Buttons"][obj]:
                    newObj.worldScale = JSONmenu["Buttons"][obj]["childObjScale"]
     
            #Текст на кнопке
            if "textObj" in JSONmenu["Buttons"][obj]:
                newSceneObject["textObj"] = JSONmenu["Buttons"][obj]["textObj"]
                newSceneObject["textRus"] = JSONmenu["Buttons"][obj]["textRus"]
                #Если надо добавляем текст, парентим его к кнопке, выставляем его размер
                newTextObj = scene.addObject("TextMenu", newSceneObject)
                if "IDtext" not in newSceneObject:
                    newSceneObject["IDtext"] = str(id(newTextObj))
                newTextObj.worldScale = [ newSceneObject.worldScale[1], newSceneObject.worldScale[1], newSceneObject.worldScale[1] ]
                #Выставляем локальные координаты текста
                newTextObj.worldPosition[0] -= 0.8*newSceneObject.worldScale[0]
                newTextObj.worldPosition[1] -= 0.3*newSceneObject.worldScale[1]
                newTextObj.worldPosition[2] += 0.01
                #Печатаем текст на кнопке
                if bge.logic.globalDict["textGame"] == "Rus":
                    newTextObj["Text"] = JSONmenu["Buttons"][obj]["textRus"]
                elif bge.logic.globalDict["textGame"] == "Eng":
                    newTextObj["Text"] = JSONmenu["Buttons"][obj]["textObj"]
     
        #В этом блоке содержатся инструкции по замене мешей и текстур фоновых объектов
        if "meshFonObj" in JSONmenu:
            scene.objects["FonObj"].replaceMesh(JSONmenu["meshFonObj"],True,False)
        #Для монитора (плоскости под фоном) можно задать свой цвет, меш, и координаты
        if "meshMonitor" in JSONmenu:
            scene.objects["Monitor"].replaceMesh(JSONmenu["meshMonitor"],True,False)
        if "coordMonitor" in JSONmenu:
            scene.objects["Monitor"].worldPosition = JSONmenu["coordMonitor"]
        if "scaleMonitor" in JSONmenu:
            scene.objects["Monitor"].worldScale = JSONmenu["scaleMonitor"]
        if "colorMonitor" in JSONmenu:
            scene.objects["Monitor"].color = JSONmenu["colorMonitor"]
        #Замена текстур фоновой плоскости и монитора (необходим для отображения карты террайна и закрытия отверстий в плоскости общего фона)         
        if "pathFonObjTexture" in JSONmenu:
            FonObj = scene.objects["FonObj"]
            own["cursorPos"]["PathFon"] = JSONmenu["pathFonObjTexture"]
            FonChanging(own, FonObj)
        if "pathMonitorTexture" in JSONmenu:
            Monitor = scene.objects["Monitor"]
            own["cursorPos"]["PathFonMonitor"] = JSONmenu["pathMonitorTexture"]
            FonChanging(own, FonObj)
             
        own["cursorPos"]["newPatchMenu"] = own["cursorPos"]["oldPatchMenu"]
       
#Функция смены текстуры фона меню
def FonChanging(own, FonObj):
    pathFon = own["cursorPos"]["PathFon"]
    if FonObj.name == "Monitor": 
        pathFon = own["cursorPos"]["PathFonMonitor"]
    ID = bge.texture.materialID(FonObj, "IM" + FonObj.name + ".jpg")
    object_texture = bge.texture.Texture(FonObj, ID)
    url = bge.logic.expandPath(pathFon)
    new_source = bge.texture.ImageFFmpeg(url)
    bge.logic.texture = object_texture
    bge.logic.texture.source = new_source
    bge.logic.texture.refresh(False)
    FonObj[FonObj.name] = new_source
           
#Функция сработки клика по кнопке - УЗЛОВАЯ ФУНКЦИЯ для buttonMove
def on_click(own):
    #Сработка кнопки идет, когда курсор находится в определенной области, с заданными координатами,
    #проверка которых идет в зависимости от размера кнопки и ее центра, с координатами, уже данными
    #в списке при загрузке и расстановке деталей меню
    for button in own["cursorPos"]["listButton"]:
        if button.worldPosition[0]-button.worldScale[0] < CursorMenu.worldPosition[0] < button.worldPosition[0]+button.worldScale[0]:
            if button.worldPosition[1]-button.worldScale[1] < CursorMenu.worldPosition[1] < button.worldPosition[1]+button.worldScale[1]:
                own["cursorPos"]["indexButton"] = str(id(button))
                own["cursorPos"]["animationButton"] = 1
                buttonMove(own, button)
                     
#Фунция работы кнопки при ее нажатии, действия, направленные на смену разделов или объектов емню, старт или выход из игры   
def buttonJob(own):
    own["cursorPos"]["animationButton"] += 1
    buttonText = None
 
    try:         
        #Находим кнопку под курсором - область координат, ограниченная размером кнопки
        button = scene.objects.from_id(int(own["cursorPos"]["indexButton"]))
     
        #Находим надписи (если есть) и окрашиваем и нажатую кнопку и ее надпись
        if "IDtext" in button:
            buttonText = scene.objects.from_id(int(button["IDtext"]))
        if own["cursorPos"]["animationButton"] == 2:
            button.color = [1.0, 0.0, 1.0, 1.0]
            if buttonText != None:
                buttonText.color = [1.0, 0.0, 1.0, 1.0]
     
        #Окрашиваем незадействованные кнопки в исходный цвет вместе с надписями (если имеются)
        for obj in own["cursorPos"]["listButton"]:
            if obj != button:
                obj.color = [1.0, 1.0, 1.0, 1.0]
                if "IDtext" in obj:
                    scene.objects.from_id(int(obj["IDtext"])).color = [1.0, 1.0, 1.0, 1.0]
     
        #Двигаем кнопку, обнаруживаем и используем ее свойства для смены меню, картинок и сцен 
        if 1 < own["cursorPos"]["animationButton"] < 3:
            button.applyMovement([0.2,-0.2,0.0],True)
            if buttonText != None:
                buttonText.applyMovement([0.2,-0.2,0.0],True)
        if 3 < own["cursorPos"]["animationButton"] < 5:
            button.applyMovement([-0.2, 0.2, 0.0],True)
            if buttonText != None:
                buttonText.applyMovement([-0.2,0.2,0.0],True)
        if own["cursorPos"]["animationButton"] > 4:
            own["cursorPos"]["indexButton"] = ""
            own["cursorPos"]["animationButton"] = 0
            if "GAME_body" in button:
                #Блок для пути к файлу миссии при нажатии кнопки выбора миссии
                if "BGE_globalDict" in button["GAME_body"]:
                    if button["GAME_body"]["BGE_globalDict"] == "pathNameMission":
                        bge.logic.globalDict['currentMission'] = own["cursorPos"]["pathNameMission"]["key_" + str(id(button))]
                #Кнопка выхода из игры
                if button["GAME_body"] == "Stop_Game":
                    bge.logic.endGame()
                #Кнопка перехода на игровую сцену
                if button["GAME_body"] == "Start_Game":
                    own["cursorPos"]["addSceneGame"] = 1
                #Кнопка выбора языка игры
                if "languageGame" in button["GAME_body"]:
                    bge.logic.globalDict["textGame"] = button["GAME_body"]["languageGame"]
                #Это смена пути к файлу раздела меню - меняется само меню, его види кнопки, для кнопки "Назад" или кнопок ивыбора других разделов
                #вроде "Миссии", "Кампании", "Справки" и тому подобных, при котором не происходит выхода из игры
                if "pathJSON" in button["GAME_body"]:
                    own["cursorPos"]["TimerColor"] = 595
                    own["cursorPos"]["newPatchMenu"] = button["GAME_body"]["pathJSON"]
                 
                #Многоступенчатый процесс "опознания" и сравнения тэгов кнопок и текста для их передвижения
                if "tagObj" in button["GAME_body"]:
                    for scrollObj in own["cursorPos"]["listButton"]:
                        if "GAME_body" in scrollObj:
                            #Проверяем совпадение тэгов после их обнаружения
                            if "objTag" in scrollObj["GAME_body"]:
                                #Теперь передвигаем найденный объект
                                if scrollObj["GAME_body"]["objTag"] == button["GAME_body"]["tagObj"]:
                                    if "scrollTagObj" in button["GAME_body"]:
                                        scrollObj.worldPosition[0] += button["GAME_body"]["scrollTagObj"][0]
                                        scrollObj.worldPosition[1] += button["GAME_body"]["scrollTagObj"][1]
                                     
                                        if "nullCoordX" in scrollObj["GAME_body"]:
                                            if scrollObj.worldPosition[0] < scrollObj["GAME_body"]["nullCoordX"]:
                                                scrollObj.worldPosition[0] = scrollObj["GAME_body"]["nullCoordX"]
                                            if scrollObj.worldPosition[0] > own["cursorPos"]["limitXscroll"]:
                                                scrollObj.worldPosition[0] -= scrollObj["Game_body"]["limitXscroll"]
                                                if "listDriverObj" in scrollObj["GAME_body"]:
                                                    for scrollChild in scrollObj["GAME_body"]["listDriverObj"]:
                                                        scrollChild.worldPosition[0] += scrollObj["GAME_body"]["scaleAddObj"][0]*button["GAME_body"]["valueButton"] #     own["cursorPos"]["limitXscroll"]
                                         
                                        if "nullCoordY" in scrollObj["GAME_body"]:
                                            #print(scrollObj.name)
                                            if scrollObj.worldPosition[1] < scrollObj["GAME_body"]["nullCoordY"]:
                                                scrollObj.worldPosition[1] = scrollObj["GAME_body"]["nullCoordY"]
                                            if scrollObj.worldPosition[1] > scrollObj["Game_body"]["limitYscroll"]:
                                                scrollObj.worldPosition[1] -= own["cursorPos"]["limitYscroll"]
                                                if "listDriverObj" in scrollObj["GAME_body"]:
                                                    for scrollChild in scrollObj["GAME_body"]["listDriverObj"]:
                                                        scrollChild.worldPosition[1] += scrollObj["GAME_body"]["scaleAddObj"][1]*button["GAME_body"]["valueButton"]  #own["cursorPos"]["limitYscroll"]
                             
                                #Изменение текста на объекте TextMenu, например при смене текста справки для видов юнитов
                                if "changTextTagObj" in button["GAME_body"]:
                                    if "pathButtonTXT" not in button["GAME_body"]["changTextTagObj"]:
                                        with open(bge.logic.expandPath(button["GAME_body"]["changTextTagObj"]+bge.logic.globalDict["textGame"]+".txt"), 'r', encoding = 'utf-8') as MenuHelpTXT:
                                            scrollObj["Text"] = MenuHelpTXT.read()
                                        #print(scrollObj.name)
                                        #with open(bge.logic.expandPath(button["GAME_body"]["changTextTagObj"]+bge.logic.globalDict["textGame"]+".txt"), 'r', encoding = 'utf-8').readlines() as changText:
                                            #own["cursorPos"]["limitYscroll"] = scrollObj.worldScale[1]*len(changText)
                                         #   scrollObj["Game_body"]["limitYscroll"] = scrollObj.worldScale[1]*len(changText)
                                    else:
                                        with open(bge.logic.expandPath(own["cursorPos"]["pathButtonTXT"]["key_"+str(id(button))]+bge.logic.globalDict["textGame"]+".txt"), 'r', encoding = 'utf-8') as MenuHelpTXT:
                                            scrollObj["Text"] = MenuHelpTXT.read()
                                     
                                if "changButtonTagObj" in button["GAME_body"]:
                                    if "listDriverObj" in scrollObj["GAME_body"]:
                                        own["cursorPos"]["pathButtonTXT"] = {}
                                        #Убираем кнопки из списка драйвера объекта
                                        if len(scrollObj["GAME_body"]["listDriverObj"]) != 0:
                                            for objOLD in scrollObj["GAME_body"]["listDriverObj"]:
                                                if objOLD in own["cursorPos"]["listButton"]:
                                                    own["cursorPos"]["listButton"].remove(objOLD)
                                                #scrollObj["GAME_body"]["listDriverObj"].remove(objOLD)
                                                objOLD.endObject()
                                            own["cursorPos"]["valueButtonCoord"] = [0.0,0.0,0.0]
                                            scrollObj["GAME_body"]["listDriverObj"] = []
                                     
                                        with open(bge.logic.expandPath('//NodePath.json'), 'r', encoding = 'utf-8') as NODEPATH:
                                            jsonButtonPath = json.load(NODEPATH)
                                            for pathButton in jsonButtonPath:
                                                if button["GAME_body"]["changButtonTagObj"] in pathButton:
                                                    if ".json" in pathButton:
                                                        newObjGen = scene.addObject(scrollObj["GAME_body"]["addObjName"], scrollObj)
                                                        scrollObj["GAME_body"]["listDriverObj"].append(newObjGen)
                                                        own["cursorPos"]["listButton"].append(newObjGen)
                                                        newObjGen.replaceMesh(scrollObj["GAME_body"]["meshAddObj"], True, False)
                                                        newObjGen.worldScale = scrollObj["GAME_body"]["scaleAddObj"]
                                                        own["cursorPos"]["valueButtonCoord"][0] += scrollObj["GAME_body"]["valueCoord"][0]
                                                        own["cursorPos"]["valueButtonCoord"][1] += scrollObj["GAME_body"]["valueCoord"][1]
                                                        own["cursorPos"]["valueButtonCoord"][2] += scrollObj["GAME_body"]["valueCoord"][2]
                                                        newObjGen.worldPosition[0] += own["cursorPos"]["valueButtonCoord"][0]
                                                        newObjGen.worldPosition[1] += own["cursorPos"]["valueButtonCoord"][1]
                                                        newObjGen.worldPosition[2] += own["cursorPos"]["valueButtonCoord"][2]
                                                     
                                                        if "textChild" in scrollObj["GAME_body"]:
                                                            with open(bge.logic.expandPath(pathButton), 'r', encoding = 'utf-8') as textButton:
                                                                jsonTextButton = json.load(textButton)
                                                                newTextGen = scene.addObject("TextMenu", newObjGen)
                                                                scrollObj["GAME_body"]["listDriverObj"].append(newTextGen)
                                                                own["cursorPos"]["listButton"].append(newTextGen)
                                                                newTextGen.worldScale = scrollObj["GAME_body"]["textChild"]
                                                                newTextGen.color = scrollObj["GAME_body"]["textChildColor"]
                                                                if bge.logic.globalDict["textGame"] == "Rus":
                                                                    newTextGen["Text"] = jsonTextButton["textRus"]
                                                                elif bge.logic.globalDict["textGame"] == "Eng":
                                                                    newTextGen["Text"] = jsonTextButton["textEng"]
                                                                #Выставляем локальные координаты текста
                                                                newTextGen.worldPosition[0] -= 0.8*newObjGen.worldScale[0]
                                                                newTextGen.worldPosition[1] -= 0.15*newObjGen.worldScale[1]
                                                             
                                                        if "addObjGAME_body" in scrollObj["GAME_body"]:
                                                            newObjGen["GAME_body"] = scrollObj["GAME_body"]["addObjGAME_body"]
                                                            if "BGE_globalDict" in newObjGen["GAME_body"]:
                                                                if newObjGen["GAME_body"]["BGE_globalDict"] == "pathNameMission":
                                                                    if "key_" + str(id(newObjGen)) not in own["cursorPos"]["pathNameMission"]:
                                                                        own["cursorPos"]["pathNameMission"]["key_" + str(id(newObjGen))] = pathButton
                                                                        #print(own["cursorPos"]["pathNameMission"])   
                                                            if "key_" + str(id(newObjGen)) not in own["cursorPos"]["pathButtonTXT"]:
                                                                own["cursorPos"]["pathButtonTXT"]["key_" + str(id(newObjGen))] = pathButton.split(".json")[0] + "_"
                                                                                                           
    except:
        print(error)
        own["cursorPos"]["indexButton"] = ""
 
#Функция звукового сопровождения и выдачи на кнопку свойств проперти
def buttonMove(own, button):
    cont = bge.logic.getCurrentController()
    audioTemp = cont.actuators['SoundClick']
 
    if "GAME_body" in button:
        if "scrollTagObj" not in button["GAME_body"]:
            cont.activate(audioTemp)
 
    if "GLOBAL_DICT_name" in button:
        if "GLOBAL_DICT_body" in button:
            if button["GLOBAL_DICT_name"] not in bge.logic.globalDict:
                bge.logic.globalDict[button["GLOBAL_DICT_name"]] = button["GLOBAL_DICT_body"]
            else:
                bge.logic.globalDict[button["GLOBAL_DICT_name"]] = button["GLOBAL_DICT_body"]
             
#Поиск путей к файлам, выстраивание списков, ОЧЕНЬ ВАЖНОЕ СТАРТОВОЕ ДЕЙСТВИЕ
startPath()

Функция startPath срабатывает всегда только один раз, отыскивая пути и файлы и добавляя их в список файла json NodePath. Это - ключ ко всему. Там расположены пути к файлам blend и файлы json  с описаниями объектов. Из-за этого файла я и решил в итоге фактически все начать с нуля. Пусть объекты сами ищут пути к нужным данным, вскрывают нужные файлы и действуют по заданным инструкциям. Раньше надо было это делать, ну да ладно, опыт сын ошибок трудных...
Фото были взяты из Сети, временно, потом будут заменены на скрины из самой игры.


Меню справки для юнитов разных "сред". Вверху - меню выбора миссии с коротким описанием оной.

Так пока и не доделанное меню редактора.

Стартовое меню.

     
             
 


                         




суббота, 14 декабря 2019 г.

Дорогу осилит ползущий...

Долгое время не писал. Особо не о ч ем было. Как-тог подзабросил дела - очередное лечение с его порцией уколов в глаза, виски и прочие места, чтение накопившихся книг, ползанья по Сети в поисках учебников, чтение оных же, поиск информации и примеров. Да и лень-матушка, куда ж без нее...
На днях выправил ситуацию с неправильной индикацией выбранного оружия на подвесках самолета игрока. Как оказалось, задача была не такой страшной, как виделось. Для этого понадобилось ввести в стартовый json летательного аппарата еще одну переменную, раскомментировать строку в одном скрипте, добавить пару строк в другом. В итоге теперь информация с названием и оружия и его БК послушно высвечивается при перебре оружия и гаснет при выключении системы управления вооружением. Вот так:
Цель - F-16C ВВС США, удаление 24 км, тип оружия - УРСД Р-23Р, БК-2, режим - РЛС с дальностью обнаружения - 60 км.

воскресенье, 4 августа 2019 г.

Четвертая операция.

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

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

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

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

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

import bge
import sys
import os
import json

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

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

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

Теперь о Юнити. В свое время я скачал и Юнити и Анрил Энжин 4. Второй движок ощутимо мощнее и совершеннее первого, да вот беда, пользоваться им смогут не все, я - пока точно нет. Его недостатком является отсутствие большого количества документации, в отличие от Юнити, к тому же, на форуме b3d.ua мне много раз говорили, что Си Шарп относительно простой в освоении язык (но, к сожалению, я тугодум и с налету его понять не могу, изображая обезьяну, повторяющую поведение человека, а именно, создавая код по примерам и образцам, не понимая, для чего и ка, впрочем, при освоении Питона начиналось так же). В Юнити для меня еще остались кое-какие нюансы, лежащие за пределами моего понимания, например, как присвоить материал дереву и его листьям (перетаскивание порой сильно раздражает, проше было бы набрать на клавиатуре). Но все-таки, я добавил в Юнити многострадального подопытного кролика - МиГ-23МФ из моего же бленд-проекта и попробовал для начала помахать крылышками. Не сразу, но это получилось. Код, наверное, я пока приводить не буду - он состоит из занудного объявления публичных переменных и всего-то из десятка строчек кода для крыльев. Пока я не столько работаю в Юнити, сколько пытаюсь уложить в свою голову хотя бы азы Сишарпа. И прежде всего - взаимодействие скриптов между собой, минуя Инспектор Юнити. У меня он ассоциируется с логическими кирпичами БГЕ, которые необходимо использовать только там, где это действительно нужно. А многократно переделывать  еще и переменные в инспекторе - нет уж. Лучше сразу научиться хотя бы по минимуму работать с кодом, а не таскать переменные и оббъекты с одно края экрана на другой (да и окошечки там маленькие, с моим зрением сейчас...)
Ну ладно, будем считать, что стартовый шаг сделан (точнее, самопинок) и блогспот не накроется медным тазом, как Гуглплюс (ходили т акие слухи).

понедельник, 24 сентября 2018 г.

Прерывая вынужденное долгое молчание...

Пришлось надолго прервать свою "блогерскую" деятельность. Причиной тому были проблемы со зрением. В течение полутора лет медленно, но верно прогрессировала катаракта, которая в моем случае являлась довольно сложным случаем. Настолько сложным, что наши, местные, врачи-офтальмологи, хорошо меня знающие, как постоянного клиента соответствующего лечебного учреждения, не рискнули проводить операцию имеющимися у них средствами, посчитав это слишком опасным - вероятность полдной потери зрения была ненулевой. Так что направили меня в Москву, в МНТК имени академика Федорова. Самым сложным из всей эпопеи с документами, необходимыми для поездки, оказалось попасть на прием к нашему областному офтальмологу. Регистратура в нашей областной клинике работает по принципу "правая рука не знает, что делает левая", так что первый раз записали на прием в 18:57, разумеется, рабочий день уже закончился, и пришлось переться на прием второй раз, да еще и вроде как она собралась в отпуск... Записались на 7:00, пришлось сначала ждать до 12:00, как всем сказали, но пришла врач только в 16:00... Особо ее винить в этом не стогит, у нее, между прочим, своих пациентов хватает, да и считается она хорошим хирургом, и операции делает, и весьма сложные. Просто система такая у нас теперь - все квоты на бесплатные высокотехнологичные оепрации теперь сосредоточены в одних руках, поэтому и на прием надо пробиваться. Ну ладно, это понятно. Но была в очереди одна тетка, возмущавшаяся "палачами в белых халатах", которая будучи второй, все же проперлась впереди нас, как танк (наверное, потенциальный правозащитник, не иначе). Пришлось ждать, когда она свалит, наконец из кабинета офтальмолога. Потом уже меня приняли, вместе с мамой, которая последние лет двадцать выступает в качестве моего сопровождающего везде, кроме дома и собственного двора (где я все и так наизусть знаю и даже в темноте или с закрытыми глазами могу перемщаться). Врач и ее ассистент не очерствели, сразу бросились помогать найти стул и сесть перед прибором. Посмотрели, затем офтальмолог выписала кучу направлений на анализы и снимки, проинструктировала, что и как нужно сделать, предупредила, что если что понадобится, то обратились к ее заму и тот ее из отпуска вызовет, если что понадобится. Дальше наша бюрократическая машина закрутилась с бешеной скоростью - анализы и снимки были сделаны очень быстро, в Москву ушел запрос, а ответ пришел уже на следующий день.Как-то так вышло, что мама взяла билеты в Москву очень удачно - мы приехали туда за два дня до операции, остановились в гостинице при МНТК, взяв заранее забронированный номер (бронирование осущетсвил мой друг и тезка, которому за это отдельное спасибо - он сейчас в Москве работает). Там и дождались госпитализации. Вышла, правда заминка - не было анализа крови на сифилис(есть подозрение, что тамошняя лаборатория таким нехитрым образом немного подрабатывает - деньги за нализ небольшие, но капают регулярно, а с другой стороны, зная, как просела медицина в бывших советских республиках после ухода "русских дармоедов и оккупатнов", а потом обретшие "долгожданную независимость" рванули на заработки в страну-"оккупанта и агрессора", в основном ту же Москву...), но и с этим тоже справились и в "последней волне" - около 17:00 мы отправились в больничные коридоры. Толпа страждущих и сопровождающих идет за санитаркой, как стадо слонов со своими рюкзаками и сумками. Встречные обитатели жмутся к стенам, а проходящая медсестра, под общий хохот спрашивает у нашей вожатой: "Где ты их столько нбрала?!" Определили нас с мамой в отдельный бокс - две койки - для больного и его сопровождающего, небольшой, но по-своему уютный, тем же вечером к нам заглянула ассистент хирурга, представилась, сказала, что меня прооперируют после обеда, после чего я поставил свю кракозябру под согласием на лечение и стали ждать. Оперировали меня раньше - около 11 часов дня - врач пришел гораздо раньше и откладывать дело в долгий ящик не стал. Группу больных и меня в том числе привели в оперционный корпус (или как он там нзывается), и стали одного за другим заводить в операционные. Сначала - закапывание в глаз обезболивающего, затем укол в морду возле уха анестетиком (у меня богатая практика получения уколов в глаза и виски, поэтому особых эмоций это не вызвало). Потом дождался своей очереди и лег на операционный стол. Зафиксировали веки, в глаз ударил яркий белый свет, который потом стал желтым (лазер? - наверное...) и глазу стало сильно горячо, но терпимо. Потом все исчезло и осталась только ослепительно белая пелена - будто все поле зрения занял лист чистой бумаги. Затем на ее фоне проявилось нечто - это если бы на лист бумаги положили бы, ну скажем, точилку для карандашей и стали ею двигать в разные стороны (ну как-то так, в 1996 году я видел картинку и пожестче - скальпель, входящий в мой глаз - это как бы кто-то сует палку в чистую воду, а ты, значит, находишься под пленкой воды и это целится тебе точно в лицо - как-то так, да еще два столба из воды торчат, то бишь из твоего галаза - какой-то еще инструмент тогда применялся). Предмет то появлялся, то исчезал, я слышал, как работает что-то вроде микромотора, но могу и ошибаться - там всяких звуков хватает... Затем в глаз начало что-то влезать - больно, но не сильно, И я стал видеть. Уже перебираясь на каталку, я слышал, как хирург подробно объясняет своей ассистентке, как надо работать с оборудованием - готовит молодое поколение, так сказать... Мама потом мне говорила, что когда мне открыли склеившиеся веки на следующий день при осмотре, что глаз у меня был абсолютно чистый и совсем не походил на тот, каким он был после операции в 1996 - подозреваю, что тогда с пол-мордой в зеленке и кроваво-красным белком оперированного глаза я сильно смахивал на вурдалака... Но что делать - лазера тогда не было и много чег7о не было. После осмотра на следующий день после операции врач констатировал, что осложнений нет и нас выпишут на следующий день. А мама взяла обратные билеты как раз на этот день (кто-то наверху подсказал, не иначе). Так и отбыли обратно... В Москве я был в 1995 году и тоже по офтальмолшогическим делам - потерял зрение на левый глаз из-за отслойки сетчатки и тогда я ездил с моим, к сожалению, ныне покойным папой, и запомнилась мне Моска тогдашняя неустроенностью, суетой, кучами мусора и всеобщим бардаком... Сейчас это другая Москва - чистая, опрятная, ярко освещенная, с ровными дорогами, где дети не боятся играть на улицах после часа ночи... Да и наш родной город все же прибавил, пусть и не так сильно - Россия выкарабкивается из той ямы, в которую мы угодили почти тридцать лет назад. Да и народ изменился - в лучшую сторону (за исключением отдельных индивидуумов - смотрии выше, но мама была свидетелем того, как в нашей поликлинике очередь дружно пропустила вперед женщину на инвалидной коляске, так что...). Такие дела.